claudemesh-cli 1.0.0-alpha.31 → 1.0.0-alpha.33

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.
@@ -3465,6 +3465,693 @@ var init_exit_codes = __esm(() => {
3465
3465
  };
3466
3466
  });
3467
3467
 
3468
+ // src/constants/timings.ts
3469
+ var TIMINGS;
3470
+ var init_timings = __esm(() => {
3471
+ TIMINGS = {
3472
+ DEVICE_CODE_POLL_MS: 1500,
3473
+ DEVICE_CODE_TIMEOUT_MS: 5 * 60 * 1000,
3474
+ WS_RECONNECT_BASE_MS: 1000,
3475
+ WS_RECONNECT_MAX_MS: 30000,
3476
+ UPDATE_CHECK_INTERVAL_MS: 24 * 60 * 60 * 1000,
3477
+ TELEGRAM_CONNECT_TIMEOUT_MS: 5 * 60 * 1000,
3478
+ TELEGRAM_POLL_INTERVAL_MS: 2000,
3479
+ API_TIMEOUT_MS: 15000,
3480
+ API_RETRY_COUNT: 2
3481
+ };
3482
+ });
3483
+
3484
+ // src/constants/urls.ts
3485
+ var exports_urls = {};
3486
+ __export(exports_urls, {
3487
+ env: () => env,
3488
+ VERSION: () => VERSION,
3489
+ URLS: () => URLS
3490
+ });
3491
+ var URLS, VERSION = "1.0.0-alpha.33", env;
3492
+ var init_urls = __esm(() => {
3493
+ URLS = {
3494
+ BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
3495
+ API_BASE: process.env.CLAUDEMESH_API_URL ?? "https://claudemesh.com",
3496
+ DASHBOARD: "https://claudemesh.com/dashboard",
3497
+ NPM_REGISTRY: "https://registry.npmjs.org/claudemesh-cli"
3498
+ };
3499
+ env = {
3500
+ CLAUDEMESH_BROKER_URL: URLS.BROKER,
3501
+ CLAUDEMESH_CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR || undefined,
3502
+ CLAUDEMESH_DEBUG: process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true"
3503
+ };
3504
+ });
3505
+
3506
+ // src/services/logger/logger.ts
3507
+ function timestamp() {
3508
+ return new Date().toISOString();
3509
+ }
3510
+ function log(msg, ...args) {
3511
+ if (!isQuiet)
3512
+ console.log(msg, ...args);
3513
+ }
3514
+ function debug(msg, ...args) {
3515
+ if (isDebug)
3516
+ console.error(`[${timestamp()}] DEBUG ${msg}`, ...args);
3517
+ }
3518
+ function warn(msg, ...args) {
3519
+ console.error(`⚠ ${msg}`, ...args);
3520
+ }
3521
+ var isDebug, isQuiet;
3522
+ var init_logger = __esm(() => {
3523
+ isDebug = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
3524
+ isQuiet = process.argv.includes("-q") || process.argv.includes("--quiet");
3525
+ });
3526
+
3527
+ // src/services/logger/facade.ts
3528
+ var init_facade4 = __esm(() => {
3529
+ init_logger();
3530
+ });
3531
+
3532
+ // src/services/api/errors.ts
3533
+ var ApiError, NetworkError;
3534
+ var init_errors2 = __esm(() => {
3535
+ ApiError = class ApiError extends Error {
3536
+ status;
3537
+ statusText;
3538
+ body;
3539
+ constructor(status, statusText, body) {
3540
+ super(`API error ${status}: ${statusText}`);
3541
+ this.status = status;
3542
+ this.statusText = statusText;
3543
+ this.body = body;
3544
+ this.name = "ApiError";
3545
+ }
3546
+ get isUnauthorized() {
3547
+ return this.status === 401;
3548
+ }
3549
+ get isNotFound() {
3550
+ return this.status === 404;
3551
+ }
3552
+ get isConflict() {
3553
+ return this.status === 409;
3554
+ }
3555
+ get isRateLimited() {
3556
+ return this.status === 429;
3557
+ }
3558
+ };
3559
+ NetworkError = class NetworkError extends Error {
3560
+ url;
3561
+ constructor(url, cause) {
3562
+ super(`Network error reaching ${url}`);
3563
+ this.url = url;
3564
+ this.name = "NetworkError";
3565
+ this.cause = cause;
3566
+ }
3567
+ };
3568
+ });
3569
+
3570
+ // src/services/api/client.ts
3571
+ async function request(opts) {
3572
+ const base = opts.baseUrl ?? URLS.API_BASE;
3573
+ const url = `${base}${opts.path}`;
3574
+ const method = opts.method ?? "GET";
3575
+ const controller = new AbortController;
3576
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? TIMINGS.API_TIMEOUT_MS);
3577
+ const headers = {
3578
+ "Content-Type": "application/json",
3579
+ Accept: "application/json",
3580
+ "User-Agent": "claudemesh-cli/1.0"
3581
+ };
3582
+ if (opts.token)
3583
+ headers.Authorization = `Bearer ${opts.token}`;
3584
+ debug(`${method} ${url}`);
3585
+ try {
3586
+ const res = await fetch(url, {
3587
+ method,
3588
+ headers,
3589
+ body: opts.body ? JSON.stringify(opts.body) : undefined,
3590
+ signal: controller.signal
3591
+ });
3592
+ if (!res.ok) {
3593
+ let body;
3594
+ try {
3595
+ body = await res.json();
3596
+ } catch {
3597
+ body = await res.text();
3598
+ }
3599
+ throw new ApiError(res.status, res.statusText, body);
3600
+ }
3601
+ const text = await res.text();
3602
+ if (!text)
3603
+ return;
3604
+ return JSON.parse(text);
3605
+ } catch (err) {
3606
+ if (err instanceof ApiError)
3607
+ throw err;
3608
+ throw new NetworkError(url, err);
3609
+ } finally {
3610
+ clearTimeout(timeout);
3611
+ }
3612
+ }
3613
+ async function get(path, token) {
3614
+ return request({ path, token });
3615
+ }
3616
+ async function post(path, body, token) {
3617
+ return request({ path, method: "POST", body, token });
3618
+ }
3619
+ var init_client = __esm(() => {
3620
+ init_urls();
3621
+ init_timings();
3622
+ init_facade4();
3623
+ init_errors2();
3624
+ });
3625
+
3626
+ // src/services/api/my.ts
3627
+ var exports_my = {};
3628
+ __export(exports_my, {
3629
+ revokeSession: () => revokeSession,
3630
+ renameMesh: () => renameMesh,
3631
+ getProfile: () => getProfile,
3632
+ getMeshes: () => getMeshes,
3633
+ createMesh: () => createMesh,
3634
+ createInvite: () => createInvite,
3635
+ cliSync: () => cliSync
3636
+ });
3637
+ async function getProfile(token) {
3638
+ return get("/api/my/profile", token);
3639
+ }
3640
+ async function getMeshes(token) {
3641
+ return get("/api/my/meshes", token);
3642
+ }
3643
+ async function createMesh(token, body) {
3644
+ return post("/api/my/meshes", body, token);
3645
+ }
3646
+ async function renameMesh(token, slug, newName) {
3647
+ return request({
3648
+ path: `/api/my/meshes/${slug}`,
3649
+ method: "PATCH",
3650
+ body: { name: newName },
3651
+ token
3652
+ });
3653
+ }
3654
+ async function createInvite(token, meshSlug, body) {
3655
+ return post(`/api/my/meshes/${meshSlug}/invites`, body, token);
3656
+ }
3657
+ async function revokeSession(token) {
3658
+ const BROKER_HTTP = (await Promise.resolve().then(() => (init_urls(), exports_urls))).URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
3659
+ return request({
3660
+ path: "/cli/session/revoke",
3661
+ method: "POST",
3662
+ body: { token },
3663
+ baseUrl: BROKER_HTTP
3664
+ });
3665
+ }
3666
+ async function cliSync(token) {
3667
+ return post("/cli-sync", undefined, token);
3668
+ }
3669
+ var init_my = __esm(() => {
3670
+ init_client();
3671
+ });
3672
+
3673
+ // src/services/api/public.ts
3674
+ var exports_public = {};
3675
+ __export(exports_public, {
3676
+ requestDeviceCode: () => requestDeviceCode,
3677
+ pollDeviceCode: () => pollDeviceCode,
3678
+ claimInvite: () => claimInvite
3679
+ });
3680
+ async function claimInvite(code, body) {
3681
+ return post(`/api/public/invites/${code}/claim`, body);
3682
+ }
3683
+ async function requestDeviceCode(deviceInfo) {
3684
+ return request({
3685
+ path: "/cli/device-code",
3686
+ method: "POST",
3687
+ body: deviceInfo,
3688
+ baseUrl: BROKER_HTTP
3689
+ });
3690
+ }
3691
+ async function pollDeviceCode(deviceCode) {
3692
+ return request({
3693
+ path: `/cli/device-code/${deviceCode}`,
3694
+ baseUrl: BROKER_HTTP
3695
+ });
3696
+ }
3697
+ var BROKER_HTTP;
3698
+ var init_public = __esm(() => {
3699
+ init_client();
3700
+ init_urls();
3701
+ BROKER_HTTP = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
3702
+ });
3703
+
3704
+ // src/services/api/facade.ts
3705
+ var init_facade5 = __esm(() => {
3706
+ init_client();
3707
+ init_errors2();
3708
+ init_my();
3709
+ init_public();
3710
+ });
3711
+
3712
+ // src/services/device/info.ts
3713
+ import { hostname as hostname2, platform as platform2, arch, release } from "node:os";
3714
+ function getDeviceInfo() {
3715
+ return {
3716
+ hostname: hostname2(),
3717
+ platform: platform2(),
3718
+ arch: arch(),
3719
+ osRelease: release(),
3720
+ nodeVersion: process.version
3721
+ };
3722
+ }
3723
+ var init_info = () => {};
3724
+
3725
+ // src/services/device/facade.ts
3726
+ var init_facade6 = __esm(() => {
3727
+ init_info();
3728
+ });
3729
+
3730
+ // src/services/spawn/claude.ts
3731
+ import { spawnSync } from "node:child_process";
3732
+ import { existsSync as existsSync2 } from "node:fs";
3733
+ function findClaudeBinary() {
3734
+ const candidates = [
3735
+ process.env.CLAUDE_BIN,
3736
+ "/usr/local/bin/claude",
3737
+ `${process.env.HOME}/.local/bin/claude`,
3738
+ `${process.env.HOME}/.npm/bin/claude`
3739
+ ].filter(Boolean);
3740
+ for (const bin of candidates) {
3741
+ if (existsSync2(bin))
3742
+ return bin;
3743
+ }
3744
+ const which = spawnSync("which", ["claude"], { encoding: "utf-8" });
3745
+ if (which.status === 0 && which.stdout.trim())
3746
+ return which.stdout.trim();
3747
+ return null;
3748
+ }
3749
+ function spawnClaude(opts) {
3750
+ const bin = findClaudeBinary();
3751
+ if (!bin)
3752
+ throw new Error("Claude binary not found. Install with: npm i -g @anthropic-ai/claude-code");
3753
+ return spawnSync(bin, opts.args, {
3754
+ stdio: "inherit",
3755
+ env: { ...process.env, ...opts.env },
3756
+ cwd: opts.cwd
3757
+ });
3758
+ }
3759
+ var init_claude = () => {};
3760
+
3761
+ // src/services/spawn/browser.ts
3762
+ import { execFile } from "node:child_process";
3763
+ import { platform as platform3 } from "node:os";
3764
+ function openBrowser(url) {
3765
+ return new Promise((resolve, reject) => {
3766
+ const os = platform3();
3767
+ let bin;
3768
+ let args;
3769
+ if (os === "darwin") {
3770
+ bin = "open";
3771
+ args = [url];
3772
+ } else if (os === "win32") {
3773
+ bin = "cmd";
3774
+ args = ["/c", "start", "", url];
3775
+ } else {
3776
+ bin = "xdg-open";
3777
+ args = [url];
3778
+ }
3779
+ execFile(bin, args, (err) => {
3780
+ if (err)
3781
+ reject(err);
3782
+ else
3783
+ resolve();
3784
+ });
3785
+ });
3786
+ }
3787
+ var init_browser = () => {};
3788
+
3789
+ // src/services/spawn/facade.ts
3790
+ var exports_facade3 = {};
3791
+ __export(exports_facade3, {
3792
+ spawnClaude: () => spawnClaude,
3793
+ openBrowser: () => openBrowser,
3794
+ findClaudeBinary: () => findClaudeBinary
3795
+ });
3796
+ var init_facade7 = __esm(() => {
3797
+ init_claude();
3798
+ init_browser();
3799
+ });
3800
+
3801
+ // src/services/auth/token-store.ts
3802
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync3, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
3803
+ function getStoredToken() {
3804
+ if (!existsSync3(PATHS.AUTH_FILE))
3805
+ return null;
3806
+ try {
3807
+ const raw = readFileSync2(PATHS.AUTH_FILE, "utf-8");
3808
+ return JSON.parse(raw);
3809
+ } catch {
3810
+ return null;
3811
+ }
3812
+ }
3813
+ function storeToken(auth) {
3814
+ ensureConfigDir();
3815
+ const data = { ...auth, stored_at: new Date().toISOString() };
3816
+ const content = JSON.stringify(data, null, 2) + `
3817
+ `;
3818
+ const fd = openSync2(PATHS.AUTH_FILE, "w", 384);
3819
+ try {
3820
+ writeFileSync2(fd, content, "utf-8");
3821
+ } finally {
3822
+ closeSync2(fd);
3823
+ }
3824
+ }
3825
+ function clearToken() {
3826
+ try {
3827
+ unlinkSync(PATHS.AUTH_FILE);
3828
+ } catch {}
3829
+ }
3830
+ var init_token_store = __esm(() => {
3831
+ init_paths();
3832
+ init_facade();
3833
+ });
3834
+
3835
+ // src/services/auth/errors.ts
3836
+ var AuthError, DeviceCodeExpired, NotSignedIn;
3837
+ var init_errors3 = __esm(() => {
3838
+ AuthError = class AuthError extends Error {
3839
+ constructor(message) {
3840
+ super(message);
3841
+ this.name = "AuthError";
3842
+ }
3843
+ };
3844
+ DeviceCodeExpired = class DeviceCodeExpired extends AuthError {
3845
+ constructor() {
3846
+ super("Device code expired. Run `claudemesh login` again.");
3847
+ }
3848
+ };
3849
+ NotSignedIn = class NotSignedIn extends AuthError {
3850
+ constructor() {
3851
+ super("Not signed in. Run `claudemesh login` first.");
3852
+ }
3853
+ };
3854
+ });
3855
+
3856
+ // src/services/auth/device-code.ts
3857
+ import { createInterface as createInterface2 } from "node:readline";
3858
+ function parseJwtUser(token) {
3859
+ try {
3860
+ const parts = token.split(".");
3861
+ if (parts[1]) {
3862
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3863
+ if (payload.exp && payload.exp < Date.now() / 1000)
3864
+ throw new Error("expired");
3865
+ return {
3866
+ id: payload.sub ?? "",
3867
+ display_name: payload.name ?? payload.email ?? "",
3868
+ email: payload.email ?? ""
3869
+ };
3870
+ }
3871
+ } catch {}
3872
+ throw new Error("Invalid token");
3873
+ }
3874
+ async function loginWithDeviceCode() {
3875
+ const device = getDeviceInfo();
3876
+ const { device_code, user_code, session_id, verification_url, token_url } = await exports_public.requestDeviceCode({
3877
+ hostname: device.hostname,
3878
+ platform: device.platform,
3879
+ arch: device.arch
3880
+ });
3881
+ const browserUrl = `${verification_url}?session=${session_id}`;
3882
+ const isTTY2 = process.stdout.isTTY && !process.env.NO_COLOR;
3883
+ const orange2 = (s) => isTTY2 ? `\x1B[38;5;208m${s}\x1B[0m` : s;
3884
+ const bold2 = (s) => isTTY2 ? `\x1B[1m${s}\x1B[0m` : s;
3885
+ const dim2 = (s) => isTTY2 ? `\x1B[2m${s}\x1B[0m` : s;
3886
+ log("");
3887
+ log(" " + orange2("claudemesh") + " — sign in to connect your terminal");
3888
+ log("");
3889
+ log(" ┌──────────────────────────────────┐");
3890
+ log(" │ │");
3891
+ log(" │ Your code: " + bold2(user_code) + " │");
3892
+ log(" │ │");
3893
+ log(" └──────────────────────────────────┘");
3894
+ log("");
3895
+ log(" " + dim2("Confirm this code matches your browser."));
3896
+ log("");
3897
+ log(" " + dim2("If the browser didn't open, visit:"));
3898
+ log(" " + browserUrl);
3899
+ log("");
3900
+ log(" " + dim2("Can't use a browser? Generate a token at:"));
3901
+ log(" " + (token_url || verification_url.replace("/cli-auth", "/token")));
3902
+ log(" " + dim2("Then paste it below."));
3903
+ log("");
3904
+ log(" Waiting… " + dim2("(paste token or Ctrl-C to cancel)"));
3905
+ try {
3906
+ await openBrowser(browserUrl);
3907
+ } catch {
3908
+ warn(" Could not open browser automatically.");
3909
+ }
3910
+ return new Promise((resolve, reject) => {
3911
+ let done = false;
3912
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
3913
+ rl.on("line", (line) => {
3914
+ if (done)
3915
+ return;
3916
+ const trimmed = line.trim();
3917
+ if (trimmed.split(".").length === 3 && trimmed.length > 50) {
3918
+ done = true;
3919
+ rl.close();
3920
+ try {
3921
+ const user = parseJwtUser(trimmed);
3922
+ storeToken({ session_token: trimmed, user, token_source: "manual" });
3923
+ resolve({ user, session_token: trimmed });
3924
+ } catch (e) {
3925
+ reject(new Error("Invalid or expired token. Generate a new one."));
3926
+ }
3927
+ }
3928
+ });
3929
+ const startTime = Date.now();
3930
+ const poll = async () => {
3931
+ while (!done && Date.now() - startTime < TIMINGS.DEVICE_CODE_TIMEOUT_MS) {
3932
+ await new Promise((r) => setTimeout(r, TIMINGS.DEVICE_CODE_POLL_MS));
3933
+ if (done)
3934
+ return;
3935
+ try {
3936
+ const result = await exports_public.pollDeviceCode(device_code);
3937
+ if (result.status === "approved" && result.session_token && result.user) {
3938
+ if (done)
3939
+ return;
3940
+ done = true;
3941
+ rl.close();
3942
+ storeToken({ session_token: result.session_token, user: result.user, token_source: "device-code" });
3943
+ resolve({ user: result.user, session_token: result.session_token });
3944
+ return;
3945
+ }
3946
+ if (result.status === "expired") {
3947
+ if (done)
3948
+ return;
3949
+ done = true;
3950
+ rl.close();
3951
+ reject(new DeviceCodeExpired);
3952
+ return;
3953
+ }
3954
+ } catch {}
3955
+ }
3956
+ if (!done) {
3957
+ done = true;
3958
+ rl.close();
3959
+ reject(new DeviceCodeExpired);
3960
+ }
3961
+ };
3962
+ poll();
3963
+ });
3964
+ }
3965
+ var init_device_code = __esm(() => {
3966
+ init_timings();
3967
+ init_facade5();
3968
+ init_facade6();
3969
+ init_facade7();
3970
+ init_facade4();
3971
+ init_token_store();
3972
+ init_errors3();
3973
+ });
3974
+
3975
+ // src/services/auth/client.ts
3976
+ function requireToken() {
3977
+ const auth = getStoredToken();
3978
+ if (!auth)
3979
+ throw new NotSignedIn;
3980
+ return auth.session_token;
3981
+ }
3982
+ async function whoAmI() {
3983
+ const auth = getStoredToken();
3984
+ if (!auth)
3985
+ return { signed_in: false };
3986
+ try {
3987
+ const profile = await exports_my.getProfile(auth.session_token);
3988
+ const meshes = await exports_my.getMeshes(auth.session_token);
3989
+ const owned = meshes.filter((m) => m.role === "owner").length;
3990
+ return {
3991
+ signed_in: true,
3992
+ user: profile,
3993
+ token_source: auth.token_source,
3994
+ meshes: { owned, guest: meshes.length - owned }
3995
+ };
3996
+ } catch (err) {
3997
+ if (err instanceof ApiError && err.isUnauthorized) {
3998
+ clearToken();
3999
+ return { signed_in: false };
4000
+ }
4001
+ throw err;
4002
+ }
4003
+ }
4004
+ async function logout() {
4005
+ const token = requireToken();
4006
+ let revoked = false;
4007
+ try {
4008
+ await exports_my.revokeSession(token);
4009
+ revoked = true;
4010
+ } catch {}
4011
+ clearToken();
4012
+ return { revoked };
4013
+ }
4014
+ async function register(callbackPort) {
4015
+ const { openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_facade7(), exports_facade3));
4016
+ const url = `https://claudemesh.com/register?source=cli&callback=http://localhost:${callbackPort}`;
4017
+ await openBrowser2(url);
4018
+ }
4019
+ var init_client2 = __esm(() => {
4020
+ init_facade5();
4021
+ init_facade5();
4022
+ init_token_store();
4023
+ init_errors3();
4024
+ });
4025
+
4026
+ // src/services/auth/dashboard-sync.ts
4027
+ async function syncWithBroker(syncToken, peerPubkey, displayName, brokerBaseUrl) {
4028
+ const base = brokerBaseUrl ?? deriveHttpUrl(URLS.BROKER);
4029
+ const res = await fetch(`${base}/cli-sync`, {
4030
+ method: "POST",
4031
+ headers: { "Content-Type": "application/json" },
4032
+ body: JSON.stringify({
4033
+ sync_token: syncToken,
4034
+ peer_pubkey: peerPubkey,
4035
+ display_name: displayName
4036
+ })
4037
+ });
4038
+ if (!res.ok) {
4039
+ const body2 = await res.text();
4040
+ let msg;
4041
+ try {
4042
+ msg = JSON.parse(body2).error ?? body2;
4043
+ } catch {
4044
+ msg = body2;
4045
+ }
4046
+ throw new Error(`Broker sync failed (${res.status}): ${msg}`);
4047
+ }
4048
+ const body = await res.json();
4049
+ if (!body.ok)
4050
+ throw new Error(`Broker sync failed: ${body.error ?? "unknown error"}`);
4051
+ return { account_id: body.account_id, meshes: body.meshes };
4052
+ }
4053
+ function deriveHttpUrl(wssUrl) {
4054
+ const url = new URL(wssUrl);
4055
+ url.protocol = url.protocol === "wss:" ? "https:" : "http:";
4056
+ url.pathname = url.pathname.replace(/\/ws\/?$/, "");
4057
+ return url.toString().replace(/\/$/, "");
4058
+ }
4059
+ var init_dashboard_sync = __esm(() => {
4060
+ init_urls();
4061
+ });
4062
+
4063
+ // src/services/auth/callback-listener.ts
4064
+ import { createServer } from "node:http";
4065
+ function startCallbackListener() {
4066
+ return new Promise((resolveStart) => {
4067
+ let resolveToken;
4068
+ let resolved = false;
4069
+ const tokenPromise = new Promise((r) => {
4070
+ resolveToken = r;
4071
+ });
4072
+ const server = createServer((req, res) => {
4073
+ const url = new URL(req.url, "http://localhost");
4074
+ if (req.method === "OPTIONS") {
4075
+ res.writeHead(204, {
4076
+ "Access-Control-Allow-Origin": "https://claudemesh.com",
4077
+ "Access-Control-Allow-Methods": "GET",
4078
+ "Access-Control-Allow-Headers": "Content-Type"
4079
+ });
4080
+ res.end();
4081
+ return;
4082
+ }
4083
+ if (url.pathname === "/ping") {
4084
+ res.writeHead(200, {
4085
+ "Content-Type": "text/plain",
4086
+ "Access-Control-Allow-Origin": "https://claudemesh.com"
4087
+ });
4088
+ res.end("ok");
4089
+ return;
4090
+ }
4091
+ if (url.pathname === "/callback") {
4092
+ const token = url.searchParams.get("token");
4093
+ if (token && !resolved) {
4094
+ resolved = true;
4095
+ res.writeHead(200, {
4096
+ "Content-Type": "text/html",
4097
+ "Access-Control-Allow-Origin": "https://claudemesh.com"
4098
+ });
4099
+ res.end("<html><body><h2>Done! You can close this tab.</h2></body></html>");
4100
+ resolveToken(token);
4101
+ setTimeout(() => server.close(), 500);
4102
+ } else {
4103
+ res.writeHead(400, { "Content-Type": "text/plain" });
4104
+ res.end("Missing token");
4105
+ }
4106
+ return;
4107
+ }
4108
+ res.writeHead(404);
4109
+ res.end();
4110
+ });
4111
+ server.listen(0, "127.0.0.1", () => {
4112
+ const addr = server.address();
4113
+ resolveStart({
4114
+ port: addr.port,
4115
+ token: tokenPromise,
4116
+ close: () => server.close()
4117
+ });
4118
+ });
4119
+ });
4120
+ }
4121
+ var init_callback_listener = () => {};
4122
+
4123
+ // src/services/auth/facade.ts
4124
+ var exports_facade4 = {};
4125
+ __export(exports_facade4, {
4126
+ whoAmI: () => whoAmI,
4127
+ syncWithBroker: () => syncWithBroker,
4128
+ storeToken: () => storeToken,
4129
+ startCallbackListener: () => startCallbackListener,
4130
+ register: () => register,
4131
+ logout: () => logout,
4132
+ loginWithDeviceCode: () => loginWithDeviceCode,
4133
+ getStoredToken: () => getStoredToken,
4134
+ generatePairingCode: () => generatePairingCode,
4135
+ clearToken: () => clearToken,
4136
+ NotSignedIn: () => NotSignedIn,
4137
+ DeviceCodeExpired: () => DeviceCodeExpired,
4138
+ AuthError: () => AuthError
4139
+ });
4140
+ import { randomBytes as randomBytes3 } from "node:crypto";
4141
+ function generatePairingCode() {
4142
+ const bytes = randomBytes3(4);
4143
+ return Array.from(bytes, (b) => CHARS[b % CHARS.length]).join("");
4144
+ }
4145
+ var CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
4146
+ var init_facade8 = __esm(() => {
4147
+ init_device_code();
4148
+ init_client2();
4149
+ init_dashboard_sync();
4150
+ init_token_store();
4151
+ init_callback_listener();
4152
+ init_errors3();
4153
+ });
4154
+
3468
4155
  // src/commands/grants.ts
3469
4156
  var exports_grants = {};
3470
4157
  __export(exports_grants, {
@@ -3474,23 +4161,47 @@ __export(exports_grants, {
3474
4161
  runBlock: () => runBlock,
3475
4162
  isAllowed: () => isAllowed
3476
4163
  });
3477
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
4164
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
3478
4165
  import { homedir as homedir2 } from "node:os";
3479
4166
  import { join as join2 } from "node:path";
4167
+ async function syncToBroker(meshSlug, grants) {
4168
+ const auth = getStoredToken();
4169
+ if (!auth)
4170
+ return;
4171
+ let userId = "";
4172
+ try {
4173
+ const payload = JSON.parse(Buffer.from(auth.session_token.split(".")[1], "base64url").toString());
4174
+ userId = payload.sub ?? "";
4175
+ } catch {
4176
+ return;
4177
+ }
4178
+ if (!userId)
4179
+ return;
4180
+ try {
4181
+ await request({
4182
+ path: `/cli/mesh/${meshSlug}/grants`,
4183
+ method: "POST",
4184
+ body: { user_id: userId, grants },
4185
+ baseUrl: BROKER_HTTP2
4186
+ });
4187
+ } catch (e) {
4188
+ render.warn(`broker grant sync failed — client filter still active: ${e instanceof Error ? e.message : e}`);
4189
+ }
4190
+ }
3480
4191
  function readGrants() {
3481
- if (!existsSync2(GRANT_FILE))
4192
+ if (!existsSync4(GRANT_FILE))
3482
4193
  return {};
3483
4194
  try {
3484
- return JSON.parse(readFileSync2(GRANT_FILE, "utf-8"));
4195
+ return JSON.parse(readFileSync3(GRANT_FILE, "utf-8"));
3485
4196
  } catch {
3486
4197
  return {};
3487
4198
  }
3488
4199
  }
3489
4200
  function writeGrants(g) {
3490
4201
  const dir = join2(homedir2(), ".claudemesh");
3491
- if (!existsSync2(dir))
4202
+ if (!existsSync4(dir))
3492
4203
  mkdirSync2(dir, { recursive: true });
3493
- writeFileSync2(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
4204
+ writeFileSync3(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
3494
4205
  }
3495
4206
  function resolveCaps(input) {
3496
4207
  if (input.includes("all"))
@@ -3538,6 +4249,7 @@ async function runGrant(peer, caps, opts = {}) {
3538
4249
  meshGrants[resolved.pubkey] = merged;
3539
4250
  store[mesh] = meshGrants;
3540
4251
  writeGrants(store);
4252
+ await syncToBroker(mesh, { [resolved.pubkey]: merged });
3541
4253
  render.ok(`Granted ${wanted.join(", ")} to ${resolved.displayName} on ${mesh}.`);
3542
4254
  render.kv([["now", merged.join(", ")]]);
3543
4255
  return EXIT.SUCCESS;
@@ -3565,6 +4277,7 @@ async function runRevoke(peer, caps, opts = {}) {
3565
4277
  meshGrants[resolved.pubkey] = after;
3566
4278
  store[mesh] = meshGrants;
3567
4279
  writeGrants(store);
4280
+ await syncToBroker(mesh, { [resolved.pubkey]: after });
3568
4281
  render.ok(`Revoked ${wanted.join(", ")} from ${resolved.displayName} on ${mesh}.`);
3569
4282
  render.kv([["now", after.length ? after.join(", ") : "(none)"]]);
3570
4283
  return EXIT.SUCCESS;
@@ -3589,6 +4302,7 @@ async function runBlock(peer, opts = {}) {
3589
4302
  meshGrants[resolved.pubkey] = [];
3590
4303
  store[mesh] = meshGrants;
3591
4304
  writeGrants(store);
4305
+ await syncToBroker(mesh, { [resolved.pubkey]: [] });
3592
4306
  render.ok(`Blocked ${resolved.displayName} on ${mesh} (all capabilities revoked).`);
3593
4307
  render.hint(`Undo with: claudemesh grant ${resolved.displayName} all --mesh ${mesh}`);
3594
4308
  return EXIT.SUCCESS;
@@ -3628,12 +4342,16 @@ function isAllowed(meshSlug, peerPubkey, cap) {
3628
4342
  return DEFAULT_CAPS.includes(cap);
3629
4343
  return entry.includes(cap);
3630
4344
  }
3631
- var ALL_CAPS, DEFAULT_CAPS, GRANT_FILE;
4345
+ var BROKER_HTTP2, ALL_CAPS, DEFAULT_CAPS, GRANT_FILE;
3632
4346
  var init_grants = __esm(() => {
3633
4347
  init_facade();
3634
4348
  init_connect();
3635
4349
  init_render();
3636
4350
  init_exit_codes();
4351
+ init_facade8();
4352
+ init_facade5();
4353
+ init_urls();
4354
+ BROKER_HTTP2 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
3637
4355
  ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
3638
4356
  DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
3639
4357
  GRANT_FILE = join2(homedir2(), ".claudemesh", "grants.json");
@@ -3703,8 +4421,10 @@ async function resolveClient(to) {
3703
4421
  };
3704
4422
  }
3705
4423
  const nameLower = target.toLowerCase();
4424
+ const candidates = [];
3706
4425
  for (const c of targetClients) {
3707
4426
  const peers = await c.listPeers();
4427
+ candidates.push({ mesh: c.meshSlug, peers });
3708
4428
  const match = peers.find((p) => p.displayName.toLowerCase() === nameLower);
3709
4429
  if (match)
3710
4430
  return { client: c, targetSpec: match.pubkey };
@@ -3715,13 +4435,11 @@ async function resolveClient(to) {
3715
4435
  return { client: c, targetSpec: partials[0].pubkey };
3716
4436
  }
3717
4437
  }
3718
- if (targetClients.length === 1) {
3719
- return { client: targetClients[0], targetSpec: target };
3720
- }
4438
+ const known = candidates.flatMap((c) => c.peers.map((p) => `${c.mesh}/${p.displayName}`));
3721
4439
  return {
3722
4440
  client: null,
3723
4441
  targetSpec: target,
3724
- error: `peer "${target}" not found in any mesh (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
4442
+ error: `peer "${target}" not found. ` + (known.length ? `Known peers: ${known.slice(0, 10).join(", ")}${known.length > 10 ? ", …" : ""}` : "No connected peers on your mesh(es). Use pubkey hex, @group, or * for broadcast.")
3725
4443
  };
3726
4444
  }
3727
4445
  async function resolvePeerName(client, pubkey) {
@@ -4039,9 +4757,9 @@ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
4039
4757
  const results = [];
4040
4758
  const seen = new Set;
4041
4759
  for (const target of targets) {
4042
- const { client, targetSpec, error } = await resolveClient(target);
4760
+ const { client, targetSpec, error: error2 } = await resolveClient(target);
4043
4761
  if (!client) {
4044
- results.push(`✗ ${target}: ${error ?? "no client resolved"}`);
4762
+ results.push(`✗ ${target}: ${error2 ?? "no client resolved"}`);
4045
4763
  continue;
4046
4764
  }
4047
4765
  if (seen.has(targetSpec))
@@ -4077,6 +4795,9 @@ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
4077
4795
  sections.push(`${header}
4078
4796
  No peers connected.`);
4079
4797
  } else {
4798
+ const pubkeyCounts = new Map;
4799
+ for (const p of peers)
4800
+ pubkeyCounts.set(p.pubkey, (pubkeyCounts.get(p.pubkey) ?? 0) + 1);
4080
4801
  const peerLines = peers.map((p) => {
4081
4802
  const summary = p.summary ? ` — "${p.summary}"` : "";
4082
4803
  const groupsStr = p.groups?.length ? ` [${p.groups.map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ")}]` : "";
@@ -4094,7 +4815,9 @@ No peers connected.`);
4094
4815
  const profileAvatar = p.profile?.avatar ? `${p.profile.avatar} ` : "";
4095
4816
  const profileTitle = p.profile?.title ? ` (${p.profile.title})` : "";
4096
4817
  const hiddenTag = p.visible === false ? " [hidden]" : "";
4097
- return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
4818
+ const sameKeyCount = pubkeyCounts.get(p.pubkey) ?? 1;
4819
+ const sameKeyTag = sameKeyCount > 1 ? ` [shares key with ${sameKeyCount - 1} other session(s)]` : "";
4820
+ return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${sameKeyTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
4098
4821
  });
4099
4822
  sections.push(`${header}
4100
4823
  ${peerLines.join(`
@@ -4102,13 +4825,13 @@ ${peerLines.join(`
4102
4825
  }
4103
4826
  }
4104
4827
  try {
4105
- const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3, existsSync: existsSync3 } = await import("node:fs");
4828
+ const { writeFileSync: writeFileSync4, mkdirSync: mkdirSync3, existsSync: existsSync5 } = await import("node:fs");
4106
4829
  const { join: joinPath } = await import("node:path");
4107
4830
  const { homedir: homedir3 } = await import("node:os");
4108
4831
  const dir = joinPath(homedir3(), ".claudemesh");
4109
- if (!existsSync3(dir))
4832
+ if (!existsSync5(dir))
4110
4833
  mkdirSync3(dir, { recursive: true });
4111
- writeFileSync3(joinPath(dir, "peer-cache.json"), JSON.stringify(statusCache));
4834
+ writeFileSync4(joinPath(dir, "peer-cache.json"), JSON.stringify(statusCache));
4112
4835
  } catch {}
4113
4836
  return text(sections.join(`
4114
4837
 
@@ -4350,15 +5073,15 @@ ${lines.join(`
4350
5073
  const { path: filePath, name: fileName, tags, to: fileTo } = args ?? {};
4351
5074
  if (!filePath)
4352
5075
  return text("share_file: `path` required", true);
4353
- const { existsSync: existsSync3 } = await import("node:fs");
4354
- if (!existsSync3(filePath))
5076
+ const { existsSync: existsSync5 } = await import("node:fs");
5077
+ if (!existsSync5(filePath))
4355
5078
  return text(`share_file: file not found: ${filePath}`, true);
4356
5079
  const client = allClients()[0];
4357
5080
  if (!client)
4358
5081
  return text("share_file: not connected", true);
4359
5082
  if (fileTo) {
4360
5083
  const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
4361
- const { readFileSync: readFileSync3, writeFileSync: writeFileSync3, mkdtempSync, unlinkSync, rmdirSync } = await import("node:fs");
5084
+ const { readFileSync: readFileSync4, writeFileSync: writeFileSync4, mkdtempSync, unlinkSync: unlinkSync2, rmdirSync } = await import("node:fs");
4362
5085
  const { tmpdir } = await import("node:os");
4363
5086
  const { join: join3, basename } = await import("node:path");
4364
5087
  const peers = await client.listPeers();
@@ -4366,7 +5089,7 @@ ${lines.join(`
4366
5089
  if (!targetPeer) {
4367
5090
  return text(`share_file: peer not found: ${fileTo}`, true);
4368
5091
  }
4369
- const plaintext = readFileSync3(filePath);
5092
+ const plaintext = readFileSync4(filePath);
4370
5093
  const { ciphertext, nonce, key } = await encryptFile2(new Uint8Array(plaintext));
4371
5094
  const sealedForTarget = await sealKeyForPeer2(key, targetPeer.pubkey);
4372
5095
  const myPubkey = client.getSessionPubkey();
@@ -4385,7 +5108,7 @@ ${lines.join(`
4385
5108
  const baseName = basename(rawName).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 255);
4386
5109
  const tmpDir = mkdtempSync(join3(tmpdir(), "cm-"));
4387
5110
  const tmpPath = join3(tmpDir, baseName);
4388
- writeFileSync3(tmpPath, combined);
5111
+ writeFileSync4(tmpPath, combined);
4389
5112
  try {
4390
5113
  const fileId = await client.uploadFile(tmpPath, client.meshId, client.meshSlug, {
4391
5114
  name: baseName,
@@ -4400,7 +5123,7 @@ ${lines.join(`
4400
5123
  return text(`share_file: upload failed — ${e instanceof Error ? e.message : String(e)}`, true);
4401
5124
  } finally {
4402
5125
  try {
4403
- unlinkSync(tmpPath);
5126
+ unlinkSync2(tmpPath);
4404
5127
  } catch {}
4405
5128
  try {
4406
5129
  rmdirSync(tmpDir);
@@ -4460,10 +5183,10 @@ ${lines.join(`
4460
5183
  const plaintext = await decryptFile2(ciphertext, nonce, kf);
4461
5184
  if (!plaintext)
4462
5185
  return text(genericErr, true);
4463
- const { writeFileSync: writeFileSync4, mkdirSync: mkdirSync4 } = await import("node:fs");
5186
+ const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync4 } = await import("node:fs");
4464
5187
  const { dirname: dirname2 } = await import("node:path");
4465
5188
  mkdirSync4(dirname2(save_to), { recursive: true });
4466
- writeFileSync4(save_to, plaintext);
5189
+ writeFileSync5(save_to, plaintext);
4467
5190
  return text(`Downloaded and decrypted: ${result.name} → ${save_to}`);
4468
5191
  }
4469
5192
  let res = await fetch(result.url, { signal: AbortSignal.timeout(1e4) }).catch(() => null);
@@ -4473,10 +5196,10 @@ ${lines.join(`
4473
5196
  }
4474
5197
  if (!res.ok)
4475
5198
  return text(`get_file: download failed (${res.status})`, true);
4476
- const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = await import("node:fs");
5199
+ const { writeFileSync: writeFileSync4, mkdirSync: mkdirSync3 } = await import("node:fs");
4477
5200
  const { dirname } = await import("node:path");
4478
5201
  mkdirSync3(dirname(save_to), { recursive: true });
4479
- writeFileSync3(save_to, Buffer.from(await res.arrayBuffer()));
5202
+ writeFileSync4(save_to, Buffer.from(await res.arrayBuffer()));
4480
5203
  return text(`Downloaded: ${result.name} → ${save_to}`);
4481
5204
  }
4482
5205
  case "list_files": {
@@ -5234,10 +5957,10 @@ ${lines.join(`
5234
5957
  const entryType = vType ?? "env";
5235
5958
  let plaintextBytes;
5236
5959
  if (entryType === "file") {
5237
- const { existsSync: existsSync3, readFileSync: readFileSync3 } = await import("node:fs");
5238
- if (!existsSync3(value))
5960
+ const { existsSync: existsSync5, readFileSync: readFileSync4 } = await import("node:fs");
5961
+ if (!existsSync5(value))
5239
5962
  return text(`vault_set: file not found: ${value}`, true);
5240
- plaintextBytes = new Uint8Array(readFileSync3(value));
5963
+ plaintextBytes = new Uint8Array(readFileSync4(value));
5241
5964
  } else {
5242
5965
  plaintextBytes = new TextEncoder().encode(value);
5243
5966
  }
@@ -5804,4 +6527,4 @@ startMcpServer().catch((err) => {
5804
6527
  process.exit(1);
5805
6528
  });
5806
6529
 
5807
- //# debugId=89A595C2EEA0442364756E2164756E21
6530
+ //# debugId=176161E2F9EDE48B64756E2164756E21