claudemesh-cli 1.0.0-alpha.9 → 1.0.1
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.
- package/dist/entrypoints/cli.js +8961 -6832
- package/dist/entrypoints/cli.js.map +76 -58
- package/dist/entrypoints/mcp.js +1294 -49
- package/dist/entrypoints/mcp.js.map +31 -5
- package/package.json +24 -21
- package/LICENSE.md +0 -37
package/dist/entrypoints/mcp.js
CHANGED
|
@@ -1299,6 +1299,7 @@ class BrokerClient {
|
|
|
1299
1299
|
outbound = [];
|
|
1300
1300
|
pushHandlers = new Set;
|
|
1301
1301
|
pushBuffer = [];
|
|
1302
|
+
pushChain = Promise.resolve();
|
|
1302
1303
|
listPeersResolvers = new Map;
|
|
1303
1304
|
stateResolvers = new Map;
|
|
1304
1305
|
stateListResolvers = new Map;
|
|
@@ -1316,6 +1317,7 @@ class BrokerClient {
|
|
|
1316
1317
|
return this._serviceCatalog;
|
|
1317
1318
|
}
|
|
1318
1319
|
closed = false;
|
|
1320
|
+
terminalClose = null;
|
|
1319
1321
|
reconnectAttempt = 0;
|
|
1320
1322
|
helloTimer = null;
|
|
1321
1323
|
reconnectTimer = null;
|
|
@@ -1423,8 +1425,10 @@ class BrokerClient {
|
|
|
1423
1425
|
this.startStatsReporting();
|
|
1424
1426
|
if (msg.restored) {
|
|
1425
1427
|
const groups = msg.restoredGroups ? msg.restoredGroups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "none";
|
|
1426
|
-
|
|
1428
|
+
if (!this.opts.quiet) {
|
|
1429
|
+
process.stderr.write(`[claudemesh] session restored — last seen ${msg.lastSeenAt ?? "unknown"}, groups: ${groups}
|
|
1427
1430
|
`);
|
|
1431
|
+
}
|
|
1428
1432
|
if (msg.restoredStats) {
|
|
1429
1433
|
const rs = msg.restoredStats;
|
|
1430
1434
|
this._statsCounters.messagesIn = rs.messagesIn ?? 0;
|
|
@@ -1441,12 +1445,22 @@ class BrokerClient {
|
|
|
1441
1445
|
}
|
|
1442
1446
|
this.handleServerMessage(msg);
|
|
1443
1447
|
};
|
|
1444
|
-
const onClose = () => {
|
|
1448
|
+
const onClose = (code, reasonBuf) => {
|
|
1445
1449
|
if (this.helloTimer)
|
|
1446
1450
|
clearTimeout(this.helloTimer);
|
|
1447
1451
|
this.helloTimer = null;
|
|
1448
1452
|
if (this.ws === ws)
|
|
1449
1453
|
this.ws = null;
|
|
1454
|
+
const reason = reasonBuf?.toString("utf-8") ?? "";
|
|
1455
|
+
if (code === 4001 || code === 4002) {
|
|
1456
|
+
this.closed = true;
|
|
1457
|
+
this.setConnStatus("closed");
|
|
1458
|
+
this.terminalClose = { code, reason };
|
|
1459
|
+
if (this._status !== "open") {
|
|
1460
|
+
reject(new Error(`ws terminal close ${code}: ${reason || "session ended"}`));
|
|
1461
|
+
}
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1450
1464
|
if (this._status !== "open" && this._status !== "reconnecting") {
|
|
1451
1465
|
reject(new Error("ws closed before hello_ack"));
|
|
1452
1466
|
}
|
|
@@ -2443,6 +2457,22 @@ class BrokerClient {
|
|
|
2443
2457
|
return;
|
|
2444
2458
|
this.ws.send(JSON.stringify(payload));
|
|
2445
2459
|
}
|
|
2460
|
+
async sendAndWait(payload, timeoutMs = 1e4) {
|
|
2461
|
+
const reqId = `rw-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2462
|
+
return new Promise((resolve, reject) => {
|
|
2463
|
+
const timer = setTimeout(() => {
|
|
2464
|
+
this.genericResolvers.delete(reqId);
|
|
2465
|
+
reject(new Error("sendAndWait timeout"));
|
|
2466
|
+
}, timeoutMs);
|
|
2467
|
+
this.genericResolvers.set(reqId, (msg) => {
|
|
2468
|
+
clearTimeout(timer);
|
|
2469
|
+
this.genericResolvers.delete(reqId);
|
|
2470
|
+
resolve(msg);
|
|
2471
|
+
});
|
|
2472
|
+
this.sendRaw({ ...payload, _reqId: reqId });
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
genericResolvers = new Map;
|
|
2446
2476
|
close() {
|
|
2447
2477
|
this.closed = true;
|
|
2448
2478
|
this.stopStatsReporting();
|
|
@@ -2643,13 +2673,17 @@ class BrokerClient {
|
|
|
2643
2673
|
}
|
|
2644
2674
|
handleServerMessage(msg) {
|
|
2645
2675
|
const msgReqId = msg._reqId;
|
|
2676
|
+
if (msgReqId && this.genericResolvers.has(msgReqId)) {
|
|
2677
|
+
const resolve = this.genericResolvers.get(msgReqId);
|
|
2678
|
+
resolve(msg);
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2646
2681
|
if (msg.type === "ack") {
|
|
2647
2682
|
const pending = this.pendingSends.get(String(msg.id ?? ""));
|
|
2648
2683
|
if (pending) {
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
});
|
|
2684
|
+
const queued = msg.queued !== false;
|
|
2685
|
+
const errStr = typeof msg.error === "string" ? msg.error : undefined;
|
|
2686
|
+
pending.resolve(queued ? { ok: true, messageId: String(msg.messageId ?? "") } : { ok: false, error: errStr ?? "broker rejected send" });
|
|
2653
2687
|
this.pendingSends.delete(pending.id);
|
|
2654
2688
|
}
|
|
2655
2689
|
return;
|
|
@@ -2664,7 +2698,7 @@ class BrokerClient {
|
|
|
2664
2698
|
const nonce = String(msg.nonce ?? "");
|
|
2665
2699
|
const ciphertext = String(msg.ciphertext ?? "");
|
|
2666
2700
|
const senderPubkey = String(msg.senderPubkey ?? "");
|
|
2667
|
-
(async () => {
|
|
2701
|
+
this.pushChain = this.pushChain.then(async () => {
|
|
2668
2702
|
const isSystem = msg.subtype === "system" || senderPubkey === "system";
|
|
2669
2703
|
const kind = isSystem ? "broadcast" : senderPubkey ? "direct" : "unknown";
|
|
2670
2704
|
let plaintext = null;
|
|
@@ -2689,7 +2723,13 @@ class BrokerClient {
|
|
|
2689
2723
|
plaintext = `[${event}]`;
|
|
2690
2724
|
}
|
|
2691
2725
|
} else if (senderPubkey && nonce && ciphertext) {
|
|
2692
|
-
|
|
2726
|
+
const envelope = { nonce, ciphertext };
|
|
2727
|
+
if (this.sessionSecretKey) {
|
|
2728
|
+
plaintext = await decryptDirect(envelope, senderPubkey, this.sessionSecretKey);
|
|
2729
|
+
}
|
|
2730
|
+
if (plaintext === null) {
|
|
2731
|
+
plaintext = await decryptDirect(envelope, senderPubkey, this.mesh.secretKey);
|
|
2732
|
+
}
|
|
2693
2733
|
}
|
|
2694
2734
|
if (plaintext === null && ciphertext && !senderPubkey) {
|
|
2695
2735
|
try {
|
|
@@ -2735,7 +2775,9 @@ class BrokerClient {
|
|
|
2735
2775
|
h(push);
|
|
2736
2776
|
} catch {}
|
|
2737
2777
|
}
|
|
2738
|
-
})()
|
|
2778
|
+
}).catch((e) => {
|
|
2779
|
+
this.debug(`push handler chain error: ${e instanceof Error ? e.message : e}`);
|
|
2780
|
+
});
|
|
2739
2781
|
return;
|
|
2740
2782
|
}
|
|
2741
2783
|
if (msg.type === "state_result") {
|
|
@@ -3101,6 +3143,9 @@ class BrokerClient {
|
|
|
3101
3143
|
}
|
|
3102
3144
|
if (msg.type === "error") {
|
|
3103
3145
|
this.debug(`broker error: ${msg.code} ${msg.message}`);
|
|
3146
|
+
if (msg.code === "revoked") {
|
|
3147
|
+
this.terminalClose = { code: 4002, reason: String(msg.message ?? "revoked") };
|
|
3148
|
+
}
|
|
3104
3149
|
const id = msg.id ? String(msg.id) : null;
|
|
3105
3150
|
let handledByPendingSend = false;
|
|
3106
3151
|
if (id) {
|
|
@@ -3181,11 +3226,17 @@ class BrokerClient {
|
|
|
3181
3226
|
send();
|
|
3182
3227
|
}
|
|
3183
3228
|
scheduleReconnect() {
|
|
3229
|
+
if (this.reconnectTimer) {
|
|
3230
|
+
this.debug("reconnect already scheduled — skipping");
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3184
3233
|
this.setConnStatus("reconnecting");
|
|
3185
|
-
const
|
|
3234
|
+
const base = BACKOFF_CAPS[Math.min(this.reconnectAttempt, BACKOFF_CAPS.length - 1)];
|
|
3235
|
+
const delay = Math.floor(Math.random() * base);
|
|
3186
3236
|
this.reconnectAttempt += 1;
|
|
3187
|
-
this.debug(`reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
3237
|
+
this.debug(`reconnect in ${delay}ms (attempt ${this.reconnectAttempt}, base ${base}ms)`);
|
|
3188
3238
|
this.reconnectTimer = setTimeout(() => {
|
|
3239
|
+
this.reconnectTimer = null;
|
|
3189
3240
|
if (this.closed)
|
|
3190
3241
|
return;
|
|
3191
3242
|
this.connect().catch((e) => {
|
|
@@ -3286,6 +3337,1091 @@ var init_facade3 = __esm(() => {
|
|
|
3286
3337
|
init_errors();
|
|
3287
3338
|
});
|
|
3288
3339
|
|
|
3340
|
+
// src/commands/connect.ts
|
|
3341
|
+
import { hostname } from "node:os";
|
|
3342
|
+
import { createInterface } from "node:readline";
|
|
3343
|
+
async function pickMesh(meshes) {
|
|
3344
|
+
console.log(`
|
|
3345
|
+
Select mesh:`);
|
|
3346
|
+
meshes.forEach((m, i) => {
|
|
3347
|
+
console.log(` ${i + 1}) ${m.slug}`);
|
|
3348
|
+
});
|
|
3349
|
+
console.log("");
|
|
3350
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3351
|
+
return new Promise((resolve) => {
|
|
3352
|
+
rl.question(" Choice [1]: ", (answer) => {
|
|
3353
|
+
rl.close();
|
|
3354
|
+
const idx = parseInt(answer || "1", 10) - 1;
|
|
3355
|
+
if (idx >= 0 && idx < meshes.length) {
|
|
3356
|
+
resolve(meshes[idx]);
|
|
3357
|
+
} else {
|
|
3358
|
+
console.error(" Invalid choice, using first mesh.");
|
|
3359
|
+
resolve(meshes[0]);
|
|
3360
|
+
}
|
|
3361
|
+
});
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
async function withMesh(opts, fn) {
|
|
3365
|
+
const config = readConfig();
|
|
3366
|
+
if (config.meshes.length === 0) {
|
|
3367
|
+
console.error("No meshes joined. Run `claudemesh join <url>` first.");
|
|
3368
|
+
process.exit(1);
|
|
3369
|
+
}
|
|
3370
|
+
let mesh;
|
|
3371
|
+
if (opts.meshSlug) {
|
|
3372
|
+
const found = config.meshes.find((m) => m.slug === opts.meshSlug);
|
|
3373
|
+
if (!found) {
|
|
3374
|
+
console.error(`Mesh "${opts.meshSlug}" not found. Joined: ${config.meshes.map((m) => m.slug).join(", ")}`);
|
|
3375
|
+
process.exit(1);
|
|
3376
|
+
}
|
|
3377
|
+
mesh = found;
|
|
3378
|
+
} else if (config.meshes.length === 1) {
|
|
3379
|
+
mesh = config.meshes[0];
|
|
3380
|
+
} else {
|
|
3381
|
+
mesh = await pickMesh(config.meshes);
|
|
3382
|
+
}
|
|
3383
|
+
const displayName = opts.displayName ?? config.displayName ?? `${hostname()}-${process.pid}`;
|
|
3384
|
+
const client = new BrokerClient(mesh, { displayName, quiet: true });
|
|
3385
|
+
try {
|
|
3386
|
+
await client.connect();
|
|
3387
|
+
const result = await fn(client, mesh);
|
|
3388
|
+
return result;
|
|
3389
|
+
} catch (e) {
|
|
3390
|
+
if (client.terminalClose) {
|
|
3391
|
+
const { code, reason } = client.terminalClose;
|
|
3392
|
+
if (code === 4002) {
|
|
3393
|
+
console.error(`
|
|
3394
|
+
✘ ${reason}
|
|
3395
|
+
`);
|
|
3396
|
+
} else if (code === 4001) {
|
|
3397
|
+
console.error(`
|
|
3398
|
+
✘ Kicked from this mesh. Run \`claudemesh\` to rejoin.
|
|
3399
|
+
`);
|
|
3400
|
+
} else {
|
|
3401
|
+
console.error(`
|
|
3402
|
+
✘ Broker closed connection: ${reason}
|
|
3403
|
+
`);
|
|
3404
|
+
}
|
|
3405
|
+
process.exit(1);
|
|
3406
|
+
}
|
|
3407
|
+
throw e;
|
|
3408
|
+
} finally {
|
|
3409
|
+
client.close();
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
var init_connect = __esm(() => {
|
|
3413
|
+
init_facade3();
|
|
3414
|
+
init_facade();
|
|
3415
|
+
});
|
|
3416
|
+
|
|
3417
|
+
// src/ui/styles.ts
|
|
3418
|
+
function moveTo(row, col) {
|
|
3419
|
+
return isTTY ? `\x1B[${row};${col}H` : "";
|
|
3420
|
+
}
|
|
3421
|
+
function visibleLength(s) {
|
|
3422
|
+
return s.replace(/\x1b\[[^m]*m/g, "").length;
|
|
3423
|
+
}
|
|
3424
|
+
var isTTY, esc = (code) => (s) => isTTY ? `${code}${s}\x1B[0m` : s, orange, clay, amber, bold, dim, green, yellow, red, cyan, boldOrange, HIDE_CURSOR, SHOW_CURSOR, CLEAR_SCREEN, CLEAR_LINE, icons;
|
|
3425
|
+
var init_styles = __esm(() => {
|
|
3426
|
+
isTTY = process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
3427
|
+
orange = esc("\x1B[38;5;208m");
|
|
3428
|
+
clay = esc("\x1B[38;5;173m");
|
|
3429
|
+
amber = esc("\x1B[38;5;214m");
|
|
3430
|
+
bold = esc("\x1B[1m");
|
|
3431
|
+
dim = esc("\x1B[2m");
|
|
3432
|
+
green = esc("\x1B[32m");
|
|
3433
|
+
yellow = esc("\x1B[33m");
|
|
3434
|
+
red = esc("\x1B[31m");
|
|
3435
|
+
cyan = esc("\x1B[36m");
|
|
3436
|
+
boldOrange = esc("\x1B[1m\x1B[38;5;208m");
|
|
3437
|
+
HIDE_CURSOR = isTTY ? "\x1B[?25l" : "";
|
|
3438
|
+
SHOW_CURSOR = isTTY ? "\x1B[?25h" : "";
|
|
3439
|
+
CLEAR_SCREEN = isTTY ? "\x1B[2J\x1B[H" : "";
|
|
3440
|
+
CLEAR_LINE = isTTY ? "\x1B[K" : "";
|
|
3441
|
+
icons = {
|
|
3442
|
+
check: "✔",
|
|
3443
|
+
cross: "✘",
|
|
3444
|
+
warn: "⚠",
|
|
3445
|
+
arrow: "→",
|
|
3446
|
+
bullet: "●",
|
|
3447
|
+
dash: "—",
|
|
3448
|
+
ellipsis: "…"
|
|
3449
|
+
};
|
|
3450
|
+
});
|
|
3451
|
+
|
|
3452
|
+
// src/ui/render.ts
|
|
3453
|
+
var OUT, ERR, INDENT = " ", render;
|
|
3454
|
+
var init_render = __esm(() => {
|
|
3455
|
+
init_styles();
|
|
3456
|
+
OUT = process.stdout;
|
|
3457
|
+
ERR = process.stderr;
|
|
3458
|
+
render = {
|
|
3459
|
+
blank() {
|
|
3460
|
+
OUT.write(`
|
|
3461
|
+
`);
|
|
3462
|
+
},
|
|
3463
|
+
ok(msg, detail) {
|
|
3464
|
+
const d = detail ? ` ${dim("(" + detail + ")")}` : "";
|
|
3465
|
+
OUT.write(`${INDENT}${green(icons.check)} ${msg}${d}
|
|
3466
|
+
`);
|
|
3467
|
+
},
|
|
3468
|
+
warn(msg, hint) {
|
|
3469
|
+
OUT.write(`${INDENT}${yellow(icons.warn)} ${msg}
|
|
3470
|
+
`);
|
|
3471
|
+
if (hint)
|
|
3472
|
+
OUT.write(`${INDENT} ${dim(hint)}
|
|
3473
|
+
`);
|
|
3474
|
+
},
|
|
3475
|
+
err(msg, hint) {
|
|
3476
|
+
ERR.write(`${INDENT}${red(icons.cross)} ${msg}
|
|
3477
|
+
`);
|
|
3478
|
+
if (hint)
|
|
3479
|
+
ERR.write(`${INDENT} ${dim(hint)}
|
|
3480
|
+
`);
|
|
3481
|
+
},
|
|
3482
|
+
info(msg) {
|
|
3483
|
+
OUT.write(`${INDENT}${msg}
|
|
3484
|
+
`);
|
|
3485
|
+
},
|
|
3486
|
+
section(title) {
|
|
3487
|
+
OUT.write(`
|
|
3488
|
+
${INDENT}${dim("—")} ${clay(title)}
|
|
3489
|
+
|
|
3490
|
+
`);
|
|
3491
|
+
},
|
|
3492
|
+
heading(title) {
|
|
3493
|
+
OUT.write(`${INDENT}${bold(title)}
|
|
3494
|
+
`);
|
|
3495
|
+
},
|
|
3496
|
+
kv(pairs, opts) {
|
|
3497
|
+
const pad = opts?.padTo ?? Math.max(...pairs.map(([k]) => k.length)) + 2;
|
|
3498
|
+
for (const [k, v] of pairs) {
|
|
3499
|
+
OUT.write(`${INDENT}${dim(k.padEnd(pad, " "))}${v}
|
|
3500
|
+
`);
|
|
3501
|
+
}
|
|
3502
|
+
},
|
|
3503
|
+
code(snippet) {
|
|
3504
|
+
for (const line of snippet.split(`
|
|
3505
|
+
`)) {
|
|
3506
|
+
OUT.write(`${INDENT} ${cyan(line)}
|
|
3507
|
+
`);
|
|
3508
|
+
}
|
|
3509
|
+
},
|
|
3510
|
+
link(url) {
|
|
3511
|
+
OUT.write(`${INDENT}${clay(url)}
|
|
3512
|
+
`);
|
|
3513
|
+
},
|
|
3514
|
+
hint(msg) {
|
|
3515
|
+
OUT.write(`${INDENT}${dim(icons.arrow + " " + msg)}
|
|
3516
|
+
`);
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3519
|
+
});
|
|
3520
|
+
|
|
3521
|
+
// src/constants/exit-codes.ts
|
|
3522
|
+
var EXIT;
|
|
3523
|
+
var init_exit_codes = __esm(() => {
|
|
3524
|
+
EXIT = {
|
|
3525
|
+
SUCCESS: 0,
|
|
3526
|
+
USER_CANCELLED: 1,
|
|
3527
|
+
AUTH_FAILED: 2,
|
|
3528
|
+
INVALID_ARGS: 3,
|
|
3529
|
+
NETWORK_ERROR: 4,
|
|
3530
|
+
NOT_FOUND: 5,
|
|
3531
|
+
ALREADY_EXISTS: 6,
|
|
3532
|
+
PERMISSION_DENIED: 7,
|
|
3533
|
+
INTERNAL_ERROR: 8,
|
|
3534
|
+
CLAUDE_MISSING: 9
|
|
3535
|
+
};
|
|
3536
|
+
});
|
|
3537
|
+
|
|
3538
|
+
// src/constants/timings.ts
|
|
3539
|
+
var TIMINGS;
|
|
3540
|
+
var init_timings = __esm(() => {
|
|
3541
|
+
TIMINGS = {
|
|
3542
|
+
DEVICE_CODE_POLL_MS: 1500,
|
|
3543
|
+
DEVICE_CODE_TIMEOUT_MS: 5 * 60 * 1000,
|
|
3544
|
+
WS_RECONNECT_BASE_MS: 1000,
|
|
3545
|
+
WS_RECONNECT_MAX_MS: 30000,
|
|
3546
|
+
UPDATE_CHECK_INTERVAL_MS: 24 * 60 * 60 * 1000,
|
|
3547
|
+
TELEGRAM_CONNECT_TIMEOUT_MS: 5 * 60 * 1000,
|
|
3548
|
+
TELEGRAM_POLL_INTERVAL_MS: 2000,
|
|
3549
|
+
API_TIMEOUT_MS: 15000,
|
|
3550
|
+
API_RETRY_COUNT: 2
|
|
3551
|
+
};
|
|
3552
|
+
});
|
|
3553
|
+
|
|
3554
|
+
// src/constants/urls.ts
|
|
3555
|
+
var exports_urls = {};
|
|
3556
|
+
__export(exports_urls, {
|
|
3557
|
+
env: () => env,
|
|
3558
|
+
VERSION: () => VERSION,
|
|
3559
|
+
URLS: () => URLS
|
|
3560
|
+
});
|
|
3561
|
+
var URLS, VERSION = "1.0.1", env;
|
|
3562
|
+
var init_urls = __esm(() => {
|
|
3563
|
+
URLS = {
|
|
3564
|
+
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
3565
|
+
API_BASE: process.env.CLAUDEMESH_API_URL ?? "https://claudemesh.com",
|
|
3566
|
+
DASHBOARD: "https://claudemesh.com/dashboard",
|
|
3567
|
+
NPM_REGISTRY: "https://registry.npmjs.org/claudemesh-cli"
|
|
3568
|
+
};
|
|
3569
|
+
env = {
|
|
3570
|
+
CLAUDEMESH_BROKER_URL: URLS.BROKER,
|
|
3571
|
+
CLAUDEMESH_CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR || undefined,
|
|
3572
|
+
CLAUDEMESH_DEBUG: process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true"
|
|
3573
|
+
};
|
|
3574
|
+
});
|
|
3575
|
+
|
|
3576
|
+
// src/services/logger/logger.ts
|
|
3577
|
+
function timestamp() {
|
|
3578
|
+
return new Date().toISOString();
|
|
3579
|
+
}
|
|
3580
|
+
function log(msg, ...args) {
|
|
3581
|
+
if (!isQuiet)
|
|
3582
|
+
console.log(msg, ...args);
|
|
3583
|
+
}
|
|
3584
|
+
function debug(msg, ...args) {
|
|
3585
|
+
if (isDebug)
|
|
3586
|
+
console.error(`[${timestamp()}] DEBUG ${msg}`, ...args);
|
|
3587
|
+
}
|
|
3588
|
+
function warn(msg, ...args) {
|
|
3589
|
+
console.error(`⚠ ${msg}`, ...args);
|
|
3590
|
+
}
|
|
3591
|
+
var isDebug, isQuiet;
|
|
3592
|
+
var init_logger = __esm(() => {
|
|
3593
|
+
isDebug = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
|
|
3594
|
+
isQuiet = process.argv.includes("-q") || process.argv.includes("--quiet");
|
|
3595
|
+
});
|
|
3596
|
+
|
|
3597
|
+
// src/services/logger/facade.ts
|
|
3598
|
+
var init_facade4 = __esm(() => {
|
|
3599
|
+
init_logger();
|
|
3600
|
+
});
|
|
3601
|
+
|
|
3602
|
+
// src/services/api/errors.ts
|
|
3603
|
+
var ApiError, NetworkError;
|
|
3604
|
+
var init_errors2 = __esm(() => {
|
|
3605
|
+
ApiError = class ApiError extends Error {
|
|
3606
|
+
status;
|
|
3607
|
+
statusText;
|
|
3608
|
+
body;
|
|
3609
|
+
constructor(status, statusText, body) {
|
|
3610
|
+
super(`API error ${status}: ${statusText}`);
|
|
3611
|
+
this.status = status;
|
|
3612
|
+
this.statusText = statusText;
|
|
3613
|
+
this.body = body;
|
|
3614
|
+
this.name = "ApiError";
|
|
3615
|
+
}
|
|
3616
|
+
get isUnauthorized() {
|
|
3617
|
+
return this.status === 401;
|
|
3618
|
+
}
|
|
3619
|
+
get isNotFound() {
|
|
3620
|
+
return this.status === 404;
|
|
3621
|
+
}
|
|
3622
|
+
get isConflict() {
|
|
3623
|
+
return this.status === 409;
|
|
3624
|
+
}
|
|
3625
|
+
get isRateLimited() {
|
|
3626
|
+
return this.status === 429;
|
|
3627
|
+
}
|
|
3628
|
+
};
|
|
3629
|
+
NetworkError = class NetworkError extends Error {
|
|
3630
|
+
url;
|
|
3631
|
+
constructor(url, cause) {
|
|
3632
|
+
super(`Network error reaching ${url}`);
|
|
3633
|
+
this.url = url;
|
|
3634
|
+
this.name = "NetworkError";
|
|
3635
|
+
this.cause = cause;
|
|
3636
|
+
}
|
|
3637
|
+
};
|
|
3638
|
+
});
|
|
3639
|
+
|
|
3640
|
+
// src/services/api/client.ts
|
|
3641
|
+
async function request(opts) {
|
|
3642
|
+
const base = opts.baseUrl ?? URLS.API_BASE;
|
|
3643
|
+
const url = `${base}${opts.path}`;
|
|
3644
|
+
const method = opts.method ?? "GET";
|
|
3645
|
+
const controller = new AbortController;
|
|
3646
|
+
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? TIMINGS.API_TIMEOUT_MS);
|
|
3647
|
+
const headers = {
|
|
3648
|
+
"Content-Type": "application/json",
|
|
3649
|
+
Accept: "application/json",
|
|
3650
|
+
"User-Agent": "claudemesh-cli/1.0"
|
|
3651
|
+
};
|
|
3652
|
+
if (opts.token)
|
|
3653
|
+
headers.Authorization = `Bearer ${opts.token}`;
|
|
3654
|
+
debug(`${method} ${url}`);
|
|
3655
|
+
try {
|
|
3656
|
+
const res = await fetch(url, {
|
|
3657
|
+
method,
|
|
3658
|
+
headers,
|
|
3659
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
3660
|
+
signal: controller.signal
|
|
3661
|
+
});
|
|
3662
|
+
if (!res.ok) {
|
|
3663
|
+
let body;
|
|
3664
|
+
try {
|
|
3665
|
+
body = await res.json();
|
|
3666
|
+
} catch {
|
|
3667
|
+
body = await res.text();
|
|
3668
|
+
}
|
|
3669
|
+
throw new ApiError(res.status, res.statusText, body);
|
|
3670
|
+
}
|
|
3671
|
+
const text = await res.text();
|
|
3672
|
+
if (!text)
|
|
3673
|
+
return;
|
|
3674
|
+
return JSON.parse(text);
|
|
3675
|
+
} catch (err) {
|
|
3676
|
+
if (err instanceof ApiError)
|
|
3677
|
+
throw err;
|
|
3678
|
+
throw new NetworkError(url, err);
|
|
3679
|
+
} finally {
|
|
3680
|
+
clearTimeout(timeout);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
async function get(path, token) {
|
|
3684
|
+
return request({ path, token });
|
|
3685
|
+
}
|
|
3686
|
+
async function post(path, body, token) {
|
|
3687
|
+
return request({ path, method: "POST", body, token });
|
|
3688
|
+
}
|
|
3689
|
+
var init_client = __esm(() => {
|
|
3690
|
+
init_urls();
|
|
3691
|
+
init_timings();
|
|
3692
|
+
init_facade4();
|
|
3693
|
+
init_errors2();
|
|
3694
|
+
});
|
|
3695
|
+
|
|
3696
|
+
// src/services/api/my.ts
|
|
3697
|
+
var exports_my = {};
|
|
3698
|
+
__export(exports_my, {
|
|
3699
|
+
revokeSession: () => revokeSession,
|
|
3700
|
+
renameMesh: () => renameMesh,
|
|
3701
|
+
getProfile: () => getProfile,
|
|
3702
|
+
getMeshes: () => getMeshes,
|
|
3703
|
+
createMesh: () => createMesh,
|
|
3704
|
+
createInvite: () => createInvite,
|
|
3705
|
+
cliSync: () => cliSync
|
|
3706
|
+
});
|
|
3707
|
+
async function getProfile(token) {
|
|
3708
|
+
return get("/api/my/profile", token);
|
|
3709
|
+
}
|
|
3710
|
+
async function getMeshes(token) {
|
|
3711
|
+
return get("/api/my/meshes", token);
|
|
3712
|
+
}
|
|
3713
|
+
async function createMesh(token, body) {
|
|
3714
|
+
return post("/api/my/meshes", body, token);
|
|
3715
|
+
}
|
|
3716
|
+
async function renameMesh(token, slug, newName) {
|
|
3717
|
+
return request({
|
|
3718
|
+
path: `/api/my/meshes/${slug}`,
|
|
3719
|
+
method: "PATCH",
|
|
3720
|
+
body: { name: newName },
|
|
3721
|
+
token
|
|
3722
|
+
});
|
|
3723
|
+
}
|
|
3724
|
+
async function createInvite(token, meshSlug, body) {
|
|
3725
|
+
return post(`/api/my/meshes/${meshSlug}/invites`, body, token);
|
|
3726
|
+
}
|
|
3727
|
+
async function revokeSession(token) {
|
|
3728
|
+
const BROKER_HTTP = (await Promise.resolve().then(() => (init_urls(), exports_urls))).URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
|
|
3729
|
+
return request({
|
|
3730
|
+
path: "/cli/session/revoke",
|
|
3731
|
+
method: "POST",
|
|
3732
|
+
body: { token },
|
|
3733
|
+
baseUrl: BROKER_HTTP
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
async function cliSync(token) {
|
|
3737
|
+
return post("/cli-sync", undefined, token);
|
|
3738
|
+
}
|
|
3739
|
+
var init_my = __esm(() => {
|
|
3740
|
+
init_client();
|
|
3741
|
+
});
|
|
3742
|
+
|
|
3743
|
+
// src/services/api/public.ts
|
|
3744
|
+
var exports_public = {};
|
|
3745
|
+
__export(exports_public, {
|
|
3746
|
+
requestDeviceCode: () => requestDeviceCode,
|
|
3747
|
+
pollDeviceCode: () => pollDeviceCode,
|
|
3748
|
+
claimInvite: () => claimInvite
|
|
3749
|
+
});
|
|
3750
|
+
async function claimInvite(code, body) {
|
|
3751
|
+
return post(`/api/public/invites/${code}/claim`, body);
|
|
3752
|
+
}
|
|
3753
|
+
async function requestDeviceCode(deviceInfo) {
|
|
3754
|
+
return request({
|
|
3755
|
+
path: "/cli/device-code",
|
|
3756
|
+
method: "POST",
|
|
3757
|
+
body: deviceInfo,
|
|
3758
|
+
baseUrl: BROKER_HTTP
|
|
3759
|
+
});
|
|
3760
|
+
}
|
|
3761
|
+
async function pollDeviceCode(deviceCode) {
|
|
3762
|
+
return request({
|
|
3763
|
+
path: `/cli/device-code/${deviceCode}`,
|
|
3764
|
+
baseUrl: BROKER_HTTP
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
var BROKER_HTTP;
|
|
3768
|
+
var init_public = __esm(() => {
|
|
3769
|
+
init_client();
|
|
3770
|
+
init_urls();
|
|
3771
|
+
BROKER_HTTP = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
|
|
3772
|
+
});
|
|
3773
|
+
|
|
3774
|
+
// src/services/api/facade.ts
|
|
3775
|
+
var init_facade5 = __esm(() => {
|
|
3776
|
+
init_client();
|
|
3777
|
+
init_errors2();
|
|
3778
|
+
init_my();
|
|
3779
|
+
init_public();
|
|
3780
|
+
});
|
|
3781
|
+
|
|
3782
|
+
// src/services/device/info.ts
|
|
3783
|
+
import { hostname as hostname2, platform as platform2, arch, release } from "node:os";
|
|
3784
|
+
function getDeviceInfo() {
|
|
3785
|
+
return {
|
|
3786
|
+
hostname: hostname2(),
|
|
3787
|
+
platform: platform2(),
|
|
3788
|
+
arch: arch(),
|
|
3789
|
+
osRelease: release(),
|
|
3790
|
+
nodeVersion: process.version
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3793
|
+
var init_info = () => {};
|
|
3794
|
+
|
|
3795
|
+
// src/services/device/facade.ts
|
|
3796
|
+
var init_facade6 = __esm(() => {
|
|
3797
|
+
init_info();
|
|
3798
|
+
});
|
|
3799
|
+
|
|
3800
|
+
// src/services/spawn/claude.ts
|
|
3801
|
+
import { spawnSync } from "node:child_process";
|
|
3802
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
3803
|
+
function findClaudeBinary() {
|
|
3804
|
+
const candidates = [
|
|
3805
|
+
process.env.CLAUDE_BIN,
|
|
3806
|
+
"/usr/local/bin/claude",
|
|
3807
|
+
`${process.env.HOME}/.local/bin/claude`,
|
|
3808
|
+
`${process.env.HOME}/.npm/bin/claude`
|
|
3809
|
+
].filter(Boolean);
|
|
3810
|
+
for (const bin of candidates) {
|
|
3811
|
+
if (existsSync2(bin))
|
|
3812
|
+
return bin;
|
|
3813
|
+
}
|
|
3814
|
+
const which = spawnSync("which", ["claude"], { encoding: "utf-8" });
|
|
3815
|
+
if (which.status === 0 && which.stdout.trim())
|
|
3816
|
+
return which.stdout.trim();
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
function spawnClaude(opts) {
|
|
3820
|
+
const bin = findClaudeBinary();
|
|
3821
|
+
if (!bin)
|
|
3822
|
+
throw new Error("Claude binary not found. Install with: npm i -g @anthropic-ai/claude-code");
|
|
3823
|
+
return spawnSync(bin, opts.args, {
|
|
3824
|
+
stdio: "inherit",
|
|
3825
|
+
env: { ...process.env, ...opts.env },
|
|
3826
|
+
cwd: opts.cwd
|
|
3827
|
+
});
|
|
3828
|
+
}
|
|
3829
|
+
var init_claude = () => {};
|
|
3830
|
+
|
|
3831
|
+
// src/services/spawn/browser.ts
|
|
3832
|
+
import { execFile } from "node:child_process";
|
|
3833
|
+
import { platform as platform3 } from "node:os";
|
|
3834
|
+
function openBrowser(url) {
|
|
3835
|
+
return new Promise((resolve, reject) => {
|
|
3836
|
+
const os = platform3();
|
|
3837
|
+
let bin;
|
|
3838
|
+
let args;
|
|
3839
|
+
if (os === "darwin") {
|
|
3840
|
+
bin = "open";
|
|
3841
|
+
args = [url];
|
|
3842
|
+
} else if (os === "win32") {
|
|
3843
|
+
bin = "cmd";
|
|
3844
|
+
args = ["/c", "start", "", url];
|
|
3845
|
+
} else {
|
|
3846
|
+
bin = "xdg-open";
|
|
3847
|
+
args = [url];
|
|
3848
|
+
}
|
|
3849
|
+
execFile(bin, args, (err) => {
|
|
3850
|
+
if (err)
|
|
3851
|
+
reject(err);
|
|
3852
|
+
else
|
|
3853
|
+
resolve();
|
|
3854
|
+
});
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
var init_browser = () => {};
|
|
3858
|
+
|
|
3859
|
+
// src/services/spawn/facade.ts
|
|
3860
|
+
var exports_facade3 = {};
|
|
3861
|
+
__export(exports_facade3, {
|
|
3862
|
+
spawnClaude: () => spawnClaude,
|
|
3863
|
+
openBrowser: () => openBrowser,
|
|
3864
|
+
findClaudeBinary: () => findClaudeBinary
|
|
3865
|
+
});
|
|
3866
|
+
var init_facade7 = __esm(() => {
|
|
3867
|
+
init_claude();
|
|
3868
|
+
init_browser();
|
|
3869
|
+
});
|
|
3870
|
+
|
|
3871
|
+
// src/services/auth/token-store.ts
|
|
3872
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync3, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
|
|
3873
|
+
function getStoredToken() {
|
|
3874
|
+
if (!existsSync3(PATHS.AUTH_FILE))
|
|
3875
|
+
return null;
|
|
3876
|
+
try {
|
|
3877
|
+
const raw = readFileSync2(PATHS.AUTH_FILE, "utf-8");
|
|
3878
|
+
return JSON.parse(raw);
|
|
3879
|
+
} catch {
|
|
3880
|
+
return null;
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
function storeToken(auth) {
|
|
3884
|
+
ensureConfigDir();
|
|
3885
|
+
const data = { ...auth, stored_at: new Date().toISOString() };
|
|
3886
|
+
const content = JSON.stringify(data, null, 2) + `
|
|
3887
|
+
`;
|
|
3888
|
+
const fd = openSync2(PATHS.AUTH_FILE, "w", 384);
|
|
3889
|
+
try {
|
|
3890
|
+
writeFileSync2(fd, content, "utf-8");
|
|
3891
|
+
} finally {
|
|
3892
|
+
closeSync2(fd);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
function clearToken() {
|
|
3896
|
+
try {
|
|
3897
|
+
unlinkSync(PATHS.AUTH_FILE);
|
|
3898
|
+
} catch {}
|
|
3899
|
+
}
|
|
3900
|
+
var init_token_store = __esm(() => {
|
|
3901
|
+
init_paths();
|
|
3902
|
+
init_facade();
|
|
3903
|
+
});
|
|
3904
|
+
|
|
3905
|
+
// src/services/auth/errors.ts
|
|
3906
|
+
var AuthError, DeviceCodeExpired, NotSignedIn;
|
|
3907
|
+
var init_errors3 = __esm(() => {
|
|
3908
|
+
AuthError = class AuthError extends Error {
|
|
3909
|
+
constructor(message) {
|
|
3910
|
+
super(message);
|
|
3911
|
+
this.name = "AuthError";
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
DeviceCodeExpired = class DeviceCodeExpired extends AuthError {
|
|
3915
|
+
constructor() {
|
|
3916
|
+
super("Device code expired. Run `claudemesh login` again.");
|
|
3917
|
+
}
|
|
3918
|
+
};
|
|
3919
|
+
NotSignedIn = class NotSignedIn extends AuthError {
|
|
3920
|
+
constructor() {
|
|
3921
|
+
super("Not signed in. Run `claudemesh login` first.");
|
|
3922
|
+
}
|
|
3923
|
+
};
|
|
3924
|
+
});
|
|
3925
|
+
|
|
3926
|
+
// src/services/auth/device-code.ts
|
|
3927
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
3928
|
+
function parseJwtUser(token) {
|
|
3929
|
+
try {
|
|
3930
|
+
const parts = token.split(".");
|
|
3931
|
+
if (parts[1]) {
|
|
3932
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
3933
|
+
if (payload.exp && payload.exp < Date.now() / 1000)
|
|
3934
|
+
throw new Error("expired");
|
|
3935
|
+
return {
|
|
3936
|
+
id: payload.sub ?? "",
|
|
3937
|
+
display_name: payload.name ?? payload.email ?? "",
|
|
3938
|
+
email: payload.email ?? ""
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3941
|
+
} catch {}
|
|
3942
|
+
throw new Error("Invalid token");
|
|
3943
|
+
}
|
|
3944
|
+
async function loginWithDeviceCode() {
|
|
3945
|
+
const device = getDeviceInfo();
|
|
3946
|
+
const { device_code, user_code, session_id, verification_url, token_url } = await exports_public.requestDeviceCode({
|
|
3947
|
+
hostname: device.hostname,
|
|
3948
|
+
platform: device.platform,
|
|
3949
|
+
arch: device.arch
|
|
3950
|
+
});
|
|
3951
|
+
const browserUrl = `${verification_url}?session=${session_id}`;
|
|
3952
|
+
const isTTY2 = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
3953
|
+
const orange2 = (s) => isTTY2 ? `\x1B[38;5;208m${s}\x1B[0m` : s;
|
|
3954
|
+
const bold2 = (s) => isTTY2 ? `\x1B[1m${s}\x1B[0m` : s;
|
|
3955
|
+
const dim2 = (s) => isTTY2 ? `\x1B[2m${s}\x1B[0m` : s;
|
|
3956
|
+
log("");
|
|
3957
|
+
log(" " + orange2("claudemesh") + " — sign in to connect your terminal");
|
|
3958
|
+
log("");
|
|
3959
|
+
log(" ┌──────────────────────────────────┐");
|
|
3960
|
+
log(" │ │");
|
|
3961
|
+
log(" │ Your code: " + bold2(user_code) + " │");
|
|
3962
|
+
log(" │ │");
|
|
3963
|
+
log(" └──────────────────────────────────┘");
|
|
3964
|
+
log("");
|
|
3965
|
+
log(" " + dim2("Confirm this code matches your browser."));
|
|
3966
|
+
log("");
|
|
3967
|
+
log(" " + dim2("If the browser didn't open, visit:"));
|
|
3968
|
+
log(" " + browserUrl);
|
|
3969
|
+
log("");
|
|
3970
|
+
log(" " + dim2("Can't use a browser? Generate a token at:"));
|
|
3971
|
+
log(" " + (token_url || verification_url.replace("/cli-auth", "/token")));
|
|
3972
|
+
log(" " + dim2("Then paste it below."));
|
|
3973
|
+
log("");
|
|
3974
|
+
log(" Waiting… " + dim2("(paste token or Ctrl-C to cancel)"));
|
|
3975
|
+
try {
|
|
3976
|
+
await openBrowser(browserUrl);
|
|
3977
|
+
} catch {
|
|
3978
|
+
warn(" Could not open browser automatically.");
|
|
3979
|
+
}
|
|
3980
|
+
return new Promise((resolve, reject) => {
|
|
3981
|
+
let done = false;
|
|
3982
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3983
|
+
rl.on("line", (line) => {
|
|
3984
|
+
if (done)
|
|
3985
|
+
return;
|
|
3986
|
+
const trimmed = line.trim();
|
|
3987
|
+
if (trimmed.split(".").length === 3 && trimmed.length > 50) {
|
|
3988
|
+
done = true;
|
|
3989
|
+
rl.close();
|
|
3990
|
+
try {
|
|
3991
|
+
const user = parseJwtUser(trimmed);
|
|
3992
|
+
storeToken({ session_token: trimmed, user, token_source: "manual" });
|
|
3993
|
+
resolve({ user, session_token: trimmed });
|
|
3994
|
+
} catch (e) {
|
|
3995
|
+
reject(new Error("Invalid or expired token. Generate a new one."));
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
});
|
|
3999
|
+
const startTime = Date.now();
|
|
4000
|
+
const poll = async () => {
|
|
4001
|
+
while (!done && Date.now() - startTime < TIMINGS.DEVICE_CODE_TIMEOUT_MS) {
|
|
4002
|
+
await new Promise((r) => setTimeout(r, TIMINGS.DEVICE_CODE_POLL_MS));
|
|
4003
|
+
if (done)
|
|
4004
|
+
return;
|
|
4005
|
+
try {
|
|
4006
|
+
const result = await exports_public.pollDeviceCode(device_code);
|
|
4007
|
+
if (result.status === "approved" && result.session_token && result.user) {
|
|
4008
|
+
if (done)
|
|
4009
|
+
return;
|
|
4010
|
+
done = true;
|
|
4011
|
+
rl.close();
|
|
4012
|
+
storeToken({ session_token: result.session_token, user: result.user, token_source: "device-code" });
|
|
4013
|
+
resolve({ user: result.user, session_token: result.session_token });
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
if (result.status === "expired") {
|
|
4017
|
+
if (done)
|
|
4018
|
+
return;
|
|
4019
|
+
done = true;
|
|
4020
|
+
rl.close();
|
|
4021
|
+
reject(new DeviceCodeExpired);
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
} catch {}
|
|
4025
|
+
}
|
|
4026
|
+
if (!done) {
|
|
4027
|
+
done = true;
|
|
4028
|
+
rl.close();
|
|
4029
|
+
reject(new DeviceCodeExpired);
|
|
4030
|
+
}
|
|
4031
|
+
};
|
|
4032
|
+
poll();
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
var init_device_code = __esm(() => {
|
|
4036
|
+
init_timings();
|
|
4037
|
+
init_facade5();
|
|
4038
|
+
init_facade6();
|
|
4039
|
+
init_facade7();
|
|
4040
|
+
init_facade4();
|
|
4041
|
+
init_token_store();
|
|
4042
|
+
init_errors3();
|
|
4043
|
+
});
|
|
4044
|
+
|
|
4045
|
+
// src/services/auth/client.ts
|
|
4046
|
+
function requireToken() {
|
|
4047
|
+
const auth = getStoredToken();
|
|
4048
|
+
if (!auth)
|
|
4049
|
+
throw new NotSignedIn;
|
|
4050
|
+
return auth.session_token;
|
|
4051
|
+
}
|
|
4052
|
+
async function whoAmI() {
|
|
4053
|
+
const auth = getStoredToken();
|
|
4054
|
+
if (!auth)
|
|
4055
|
+
return { signed_in: false };
|
|
4056
|
+
try {
|
|
4057
|
+
const profile = await exports_my.getProfile(auth.session_token);
|
|
4058
|
+
const meshes = await exports_my.getMeshes(auth.session_token);
|
|
4059
|
+
const owned = meshes.filter((m) => m.role === "owner").length;
|
|
4060
|
+
return {
|
|
4061
|
+
signed_in: true,
|
|
4062
|
+
user: profile,
|
|
4063
|
+
token_source: auth.token_source,
|
|
4064
|
+
meshes: { owned, guest: meshes.length - owned }
|
|
4065
|
+
};
|
|
4066
|
+
} catch (err) {
|
|
4067
|
+
if (err instanceof ApiError && err.isUnauthorized) {
|
|
4068
|
+
clearToken();
|
|
4069
|
+
return { signed_in: false };
|
|
4070
|
+
}
|
|
4071
|
+
throw err;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
async function logout() {
|
|
4075
|
+
const token = requireToken();
|
|
4076
|
+
let revoked = false;
|
|
4077
|
+
try {
|
|
4078
|
+
await exports_my.revokeSession(token);
|
|
4079
|
+
revoked = true;
|
|
4080
|
+
} catch {}
|
|
4081
|
+
clearToken();
|
|
4082
|
+
return { revoked };
|
|
4083
|
+
}
|
|
4084
|
+
async function register(callbackPort) {
|
|
4085
|
+
const { openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_facade7(), exports_facade3));
|
|
4086
|
+
const url = `https://claudemesh.com/register?source=cli&callback=http://localhost:${callbackPort}`;
|
|
4087
|
+
await openBrowser2(url);
|
|
4088
|
+
}
|
|
4089
|
+
var init_client2 = __esm(() => {
|
|
4090
|
+
init_facade5();
|
|
4091
|
+
init_facade5();
|
|
4092
|
+
init_token_store();
|
|
4093
|
+
init_errors3();
|
|
4094
|
+
});
|
|
4095
|
+
|
|
4096
|
+
// src/services/auth/dashboard-sync.ts
|
|
4097
|
+
async function syncWithBroker(syncToken, peerPubkey, displayName, brokerBaseUrl) {
|
|
4098
|
+
const base = brokerBaseUrl ?? deriveHttpUrl(URLS.BROKER);
|
|
4099
|
+
const res = await fetch(`${base}/cli-sync`, {
|
|
4100
|
+
method: "POST",
|
|
4101
|
+
headers: { "Content-Type": "application/json" },
|
|
4102
|
+
body: JSON.stringify({
|
|
4103
|
+
sync_token: syncToken,
|
|
4104
|
+
peer_pubkey: peerPubkey,
|
|
4105
|
+
display_name: displayName
|
|
4106
|
+
})
|
|
4107
|
+
});
|
|
4108
|
+
if (!res.ok) {
|
|
4109
|
+
const body2 = await res.text();
|
|
4110
|
+
let msg;
|
|
4111
|
+
try {
|
|
4112
|
+
msg = JSON.parse(body2).error ?? body2;
|
|
4113
|
+
} catch {
|
|
4114
|
+
msg = body2;
|
|
4115
|
+
}
|
|
4116
|
+
throw new Error(`Broker sync failed (${res.status}): ${msg}`);
|
|
4117
|
+
}
|
|
4118
|
+
const body = await res.json();
|
|
4119
|
+
if (!body.ok)
|
|
4120
|
+
throw new Error(`Broker sync failed: ${body.error ?? "unknown error"}`);
|
|
4121
|
+
return { account_id: body.account_id, meshes: body.meshes };
|
|
4122
|
+
}
|
|
4123
|
+
function deriveHttpUrl(wssUrl) {
|
|
4124
|
+
const url = new URL(wssUrl);
|
|
4125
|
+
url.protocol = url.protocol === "wss:" ? "https:" : "http:";
|
|
4126
|
+
url.pathname = url.pathname.replace(/\/ws\/?$/, "");
|
|
4127
|
+
return url.toString().replace(/\/$/, "");
|
|
4128
|
+
}
|
|
4129
|
+
var init_dashboard_sync = __esm(() => {
|
|
4130
|
+
init_urls();
|
|
4131
|
+
});
|
|
4132
|
+
|
|
4133
|
+
// src/services/auth/callback-listener.ts
|
|
4134
|
+
import { createServer } from "node:http";
|
|
4135
|
+
function startCallbackListener() {
|
|
4136
|
+
return new Promise((resolveStart) => {
|
|
4137
|
+
let resolveToken;
|
|
4138
|
+
let resolved = false;
|
|
4139
|
+
const tokenPromise = new Promise((r) => {
|
|
4140
|
+
resolveToken = r;
|
|
4141
|
+
});
|
|
4142
|
+
const server = createServer((req, res) => {
|
|
4143
|
+
const url = new URL(req.url, "http://localhost");
|
|
4144
|
+
if (req.method === "OPTIONS") {
|
|
4145
|
+
res.writeHead(204, {
|
|
4146
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com",
|
|
4147
|
+
"Access-Control-Allow-Methods": "GET",
|
|
4148
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
4149
|
+
});
|
|
4150
|
+
res.end();
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
if (url.pathname === "/ping") {
|
|
4154
|
+
res.writeHead(200, {
|
|
4155
|
+
"Content-Type": "text/plain",
|
|
4156
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com"
|
|
4157
|
+
});
|
|
4158
|
+
res.end("ok");
|
|
4159
|
+
return;
|
|
4160
|
+
}
|
|
4161
|
+
if (url.pathname === "/callback") {
|
|
4162
|
+
const token = url.searchParams.get("token");
|
|
4163
|
+
if (token && !resolved) {
|
|
4164
|
+
resolved = true;
|
|
4165
|
+
res.writeHead(200, {
|
|
4166
|
+
"Content-Type": "text/html",
|
|
4167
|
+
"Access-Control-Allow-Origin": "https://claudemesh.com"
|
|
4168
|
+
});
|
|
4169
|
+
res.end("<html><body><h2>Done! You can close this tab.</h2></body></html>");
|
|
4170
|
+
resolveToken(token);
|
|
4171
|
+
setTimeout(() => server.close(), 500);
|
|
4172
|
+
} else {
|
|
4173
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
4174
|
+
res.end("Missing token");
|
|
4175
|
+
}
|
|
4176
|
+
return;
|
|
4177
|
+
}
|
|
4178
|
+
res.writeHead(404);
|
|
4179
|
+
res.end();
|
|
4180
|
+
});
|
|
4181
|
+
server.listen(0, "127.0.0.1", () => {
|
|
4182
|
+
const addr = server.address();
|
|
4183
|
+
resolveStart({
|
|
4184
|
+
port: addr.port,
|
|
4185
|
+
token: tokenPromise,
|
|
4186
|
+
close: () => server.close()
|
|
4187
|
+
});
|
|
4188
|
+
});
|
|
4189
|
+
});
|
|
4190
|
+
}
|
|
4191
|
+
var init_callback_listener = () => {};
|
|
4192
|
+
|
|
4193
|
+
// src/services/auth/facade.ts
|
|
4194
|
+
var exports_facade4 = {};
|
|
4195
|
+
__export(exports_facade4, {
|
|
4196
|
+
whoAmI: () => whoAmI,
|
|
4197
|
+
syncWithBroker: () => syncWithBroker,
|
|
4198
|
+
storeToken: () => storeToken,
|
|
4199
|
+
startCallbackListener: () => startCallbackListener,
|
|
4200
|
+
register: () => register,
|
|
4201
|
+
logout: () => logout,
|
|
4202
|
+
loginWithDeviceCode: () => loginWithDeviceCode,
|
|
4203
|
+
getStoredToken: () => getStoredToken,
|
|
4204
|
+
generatePairingCode: () => generatePairingCode,
|
|
4205
|
+
clearToken: () => clearToken,
|
|
4206
|
+
NotSignedIn: () => NotSignedIn,
|
|
4207
|
+
DeviceCodeExpired: () => DeviceCodeExpired,
|
|
4208
|
+
AuthError: () => AuthError
|
|
4209
|
+
});
|
|
4210
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
4211
|
+
function generatePairingCode() {
|
|
4212
|
+
const bytes = randomBytes3(4);
|
|
4213
|
+
return Array.from(bytes, (b) => CHARS[b % CHARS.length]).join("");
|
|
4214
|
+
}
|
|
4215
|
+
var CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
|
|
4216
|
+
var init_facade8 = __esm(() => {
|
|
4217
|
+
init_device_code();
|
|
4218
|
+
init_client2();
|
|
4219
|
+
init_dashboard_sync();
|
|
4220
|
+
init_token_store();
|
|
4221
|
+
init_callback_listener();
|
|
4222
|
+
init_errors3();
|
|
4223
|
+
});
|
|
4224
|
+
|
|
4225
|
+
// src/commands/grants.ts
|
|
4226
|
+
var exports_grants = {};
|
|
4227
|
+
__export(exports_grants, {
|
|
4228
|
+
runRevoke: () => runRevoke,
|
|
4229
|
+
runGrants: () => runGrants,
|
|
4230
|
+
runGrant: () => runGrant,
|
|
4231
|
+
runBlock: () => runBlock,
|
|
4232
|
+
isAllowed: () => isAllowed
|
|
4233
|
+
});
|
|
4234
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4235
|
+
import { homedir as homedir2 } from "node:os";
|
|
4236
|
+
import { join as join2 } from "node:path";
|
|
4237
|
+
async function syncToBroker(meshSlug, grants) {
|
|
4238
|
+
const auth = getStoredToken();
|
|
4239
|
+
if (!auth)
|
|
4240
|
+
return;
|
|
4241
|
+
try {
|
|
4242
|
+
await request({
|
|
4243
|
+
path: `/cli/mesh/${meshSlug}/grants`,
|
|
4244
|
+
method: "POST",
|
|
4245
|
+
body: { grants },
|
|
4246
|
+
baseUrl: BROKER_HTTP2,
|
|
4247
|
+
token: auth.session_token
|
|
4248
|
+
});
|
|
4249
|
+
} catch (e) {
|
|
4250
|
+
render.warn(`broker grant sync failed — client filter still active: ${e instanceof Error ? e.message : e}`);
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
function readGrants() {
|
|
4254
|
+
if (!existsSync4(GRANT_FILE))
|
|
4255
|
+
return {};
|
|
4256
|
+
try {
|
|
4257
|
+
return JSON.parse(readFileSync3(GRANT_FILE, "utf-8"));
|
|
4258
|
+
} catch {
|
|
4259
|
+
return {};
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
function writeGrants(g) {
|
|
4263
|
+
const dir = join2(homedir2(), ".claudemesh");
|
|
4264
|
+
if (!existsSync4(dir))
|
|
4265
|
+
mkdirSync2(dir, { recursive: true });
|
|
4266
|
+
writeFileSync3(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
|
|
4267
|
+
}
|
|
4268
|
+
function resolveCaps(input) {
|
|
4269
|
+
if (input.includes("all"))
|
|
4270
|
+
return [...ALL_CAPS];
|
|
4271
|
+
return input.filter((c) => ALL_CAPS.includes(c));
|
|
4272
|
+
}
|
|
4273
|
+
async function resolvePeer(meshSlug, name) {
|
|
4274
|
+
return await withMesh({ meshSlug }, async (client) => {
|
|
4275
|
+
const peers = await client.listPeers();
|
|
4276
|
+
const match = peers.find((p) => p.displayName === name || p.pubkey === name || p.pubkey.startsWith(name) || p.memberPubkey === name || p.memberPubkey && p.memberPubkey.startsWith(name));
|
|
4277
|
+
if (!match)
|
|
4278
|
+
return null;
|
|
4279
|
+
const key = match.memberPubkey ?? match.pubkey;
|
|
4280
|
+
return { displayName: match.displayName, pubkey: key };
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
function pickMesh2(slug) {
|
|
4284
|
+
const cfg = readConfig();
|
|
4285
|
+
if (slug)
|
|
4286
|
+
return cfg.meshes.find((m) => m.slug === slug) ? slug : null;
|
|
4287
|
+
return cfg.meshes[0]?.slug ?? null;
|
|
4288
|
+
}
|
|
4289
|
+
async function runGrant(peer, caps, opts = {}) {
|
|
4290
|
+
if (!peer || caps.length === 0) {
|
|
4291
|
+
render.err("Usage: claudemesh grant <peer> <capability...>");
|
|
4292
|
+
render.hint(`Capabilities: ${ALL_CAPS.join(", ")}, all`);
|
|
4293
|
+
return EXIT.INVALID_ARGS;
|
|
4294
|
+
}
|
|
4295
|
+
const mesh = pickMesh2(opts.mesh);
|
|
4296
|
+
if (!mesh) {
|
|
4297
|
+
render.err("No matching mesh — join one first.");
|
|
4298
|
+
return EXIT.NOT_FOUND;
|
|
4299
|
+
}
|
|
4300
|
+
const resolved = await resolvePeer(mesh, peer);
|
|
4301
|
+
if (!resolved) {
|
|
4302
|
+
render.err(`Peer "${peer}" not found on ${mesh}.`);
|
|
4303
|
+
return EXIT.NOT_FOUND;
|
|
4304
|
+
}
|
|
4305
|
+
const wanted = resolveCaps(caps);
|
|
4306
|
+
if (wanted.length === 0) {
|
|
4307
|
+
render.err(`Unknown capabilities: ${caps.join(", ")}`);
|
|
4308
|
+
return EXIT.INVALID_ARGS;
|
|
4309
|
+
}
|
|
4310
|
+
const store = readGrants();
|
|
4311
|
+
const meshGrants = store[mesh] ?? {};
|
|
4312
|
+
const existing = meshGrants[resolved.pubkey] ?? DEFAULT_CAPS.slice();
|
|
4313
|
+
const merged = Array.from(new Set([...existing, ...wanted]));
|
|
4314
|
+
meshGrants[resolved.pubkey] = merged;
|
|
4315
|
+
store[mesh] = meshGrants;
|
|
4316
|
+
writeGrants(store);
|
|
4317
|
+
await syncToBroker(mesh, { [resolved.pubkey]: merged });
|
|
4318
|
+
render.ok(`Granted ${wanted.join(", ")} to ${resolved.displayName} on ${mesh}.`);
|
|
4319
|
+
render.kv([["now", merged.join(", ")]]);
|
|
4320
|
+
return EXIT.SUCCESS;
|
|
4321
|
+
}
|
|
4322
|
+
async function runRevoke(peer, caps, opts = {}) {
|
|
4323
|
+
if (!peer || caps.length === 0) {
|
|
4324
|
+
render.err("Usage: claudemesh revoke <peer> <capability...>");
|
|
4325
|
+
return EXIT.INVALID_ARGS;
|
|
4326
|
+
}
|
|
4327
|
+
const mesh = pickMesh2(opts.mesh);
|
|
4328
|
+
if (!mesh) {
|
|
4329
|
+
render.err("No matching mesh.");
|
|
4330
|
+
return EXIT.NOT_FOUND;
|
|
4331
|
+
}
|
|
4332
|
+
const resolved = await resolvePeer(mesh, peer);
|
|
4333
|
+
if (!resolved) {
|
|
4334
|
+
render.err(`Peer "${peer}" not found on ${mesh}.`);
|
|
4335
|
+
return EXIT.NOT_FOUND;
|
|
4336
|
+
}
|
|
4337
|
+
const wanted = caps.includes("all") ? ALL_CAPS.slice() : resolveCaps(caps);
|
|
4338
|
+
const store = readGrants();
|
|
4339
|
+
const meshGrants = store[mesh] ?? {};
|
|
4340
|
+
const existing = meshGrants[resolved.pubkey] ?? DEFAULT_CAPS.slice();
|
|
4341
|
+
const after = existing.filter((c) => !wanted.includes(c));
|
|
4342
|
+
meshGrants[resolved.pubkey] = after;
|
|
4343
|
+
store[mesh] = meshGrants;
|
|
4344
|
+
writeGrants(store);
|
|
4345
|
+
await syncToBroker(mesh, { [resolved.pubkey]: after });
|
|
4346
|
+
render.ok(`Revoked ${wanted.join(", ")} from ${resolved.displayName} on ${mesh}.`);
|
|
4347
|
+
render.kv([["now", after.length ? after.join(", ") : "(none)"]]);
|
|
4348
|
+
return EXIT.SUCCESS;
|
|
4349
|
+
}
|
|
4350
|
+
async function runBlock(peer, opts = {}) {
|
|
4351
|
+
if (!peer) {
|
|
4352
|
+
render.err("Usage: claudemesh block <peer>");
|
|
4353
|
+
return EXIT.INVALID_ARGS;
|
|
4354
|
+
}
|
|
4355
|
+
const mesh = pickMesh2(opts.mesh);
|
|
4356
|
+
if (!mesh) {
|
|
4357
|
+
render.err("No matching mesh.");
|
|
4358
|
+
return EXIT.NOT_FOUND;
|
|
4359
|
+
}
|
|
4360
|
+
const resolved = await resolvePeer(mesh, peer);
|
|
4361
|
+
if (!resolved) {
|
|
4362
|
+
render.err(`Peer "${peer}" not found on ${mesh}.`);
|
|
4363
|
+
return EXIT.NOT_FOUND;
|
|
4364
|
+
}
|
|
4365
|
+
const store = readGrants();
|
|
4366
|
+
const meshGrants = store[mesh] ?? {};
|
|
4367
|
+
meshGrants[resolved.pubkey] = [];
|
|
4368
|
+
store[mesh] = meshGrants;
|
|
4369
|
+
writeGrants(store);
|
|
4370
|
+
await syncToBroker(mesh, { [resolved.pubkey]: [] });
|
|
4371
|
+
render.ok(`Blocked ${resolved.displayName} on ${mesh} (all capabilities revoked).`);
|
|
4372
|
+
render.hint(`Undo with: claudemesh grant ${resolved.displayName} all --mesh ${mesh}`);
|
|
4373
|
+
return EXIT.SUCCESS;
|
|
4374
|
+
}
|
|
4375
|
+
async function runGrants(opts = {}) {
|
|
4376
|
+
const mesh = pickMesh2(opts.mesh);
|
|
4377
|
+
if (!mesh) {
|
|
4378
|
+
render.err("No matching mesh.");
|
|
4379
|
+
return EXIT.NOT_FOUND;
|
|
4380
|
+
}
|
|
4381
|
+
const store = readGrants();
|
|
4382
|
+
const meshGrants = store[mesh] ?? {};
|
|
4383
|
+
if (opts.json) {
|
|
4384
|
+
console.log(JSON.stringify({ schema_version: "1.0", mesh, grants: meshGrants }, null, 2));
|
|
4385
|
+
return EXIT.SUCCESS;
|
|
4386
|
+
}
|
|
4387
|
+
render.section(`grants on ${mesh}`);
|
|
4388
|
+
const peerPubkeys = Object.keys(meshGrants);
|
|
4389
|
+
if (peerPubkeys.length === 0) {
|
|
4390
|
+
render.info("(no overrides — all peers use default caps: " + DEFAULT_CAPS.join(", ") + ")");
|
|
4391
|
+
return EXIT.SUCCESS;
|
|
4392
|
+
}
|
|
4393
|
+
await withMesh({ meshSlug: mesh }, async (client) => {
|
|
4394
|
+
const peers = await client.listPeers();
|
|
4395
|
+
const byPk = new Map(peers.map((p) => [p.pubkey, p.displayName]));
|
|
4396
|
+
for (const [pk, caps] of Object.entries(meshGrants)) {
|
|
4397
|
+
const name = byPk.get(pk) ?? `${pk.slice(0, 10)}…`;
|
|
4398
|
+
render.kv([[name, caps.length ? caps.join(", ") : "(blocked)"]]);
|
|
4399
|
+
}
|
|
4400
|
+
});
|
|
4401
|
+
return EXIT.SUCCESS;
|
|
4402
|
+
}
|
|
4403
|
+
function isAllowed(meshSlug, peerPubkey, cap) {
|
|
4404
|
+
const store = readGrants();
|
|
4405
|
+
const entry = store[meshSlug]?.[peerPubkey];
|
|
4406
|
+
if (entry === undefined)
|
|
4407
|
+
return DEFAULT_CAPS.includes(cap);
|
|
4408
|
+
return entry.includes(cap);
|
|
4409
|
+
}
|
|
4410
|
+
var BROKER_HTTP2, ALL_CAPS, DEFAULT_CAPS, GRANT_FILE;
|
|
4411
|
+
var init_grants = __esm(() => {
|
|
4412
|
+
init_facade();
|
|
4413
|
+
init_connect();
|
|
4414
|
+
init_render();
|
|
4415
|
+
init_exit_codes();
|
|
4416
|
+
init_facade8();
|
|
4417
|
+
init_facade5();
|
|
4418
|
+
init_urls();
|
|
4419
|
+
BROKER_HTTP2 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
|
|
4420
|
+
ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
|
|
4421
|
+
DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
|
|
4422
|
+
GRANT_FILE = join2(homedir2(), ".claudemesh", "grants.json");
|
|
4423
|
+
});
|
|
4424
|
+
|
|
3289
4425
|
// src/mcp/server.ts
|
|
3290
4426
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3291
4427
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -3339,7 +4475,7 @@ async function resolveClient(to) {
|
|
|
3339
4475
|
target = rest;
|
|
3340
4476
|
}
|
|
3341
4477
|
}
|
|
3342
|
-
if (
|
|
4478
|
+
if (target.startsWith("#") || target.startsWith("@") || target === "*") {
|
|
3343
4479
|
if (targetClients.length === 1) {
|
|
3344
4480
|
return { client: targetClients[0], targetSpec: target };
|
|
3345
4481
|
}
|
|
@@ -3349,26 +4485,100 @@ async function resolveClient(to) {
|
|
|
3349
4485
|
error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
|
|
3350
4486
|
};
|
|
3351
4487
|
}
|
|
4488
|
+
if (/^[0-9a-f]{8,64}$/.test(target)) {
|
|
4489
|
+
const hits = [];
|
|
4490
|
+
for (const c of targetClients) {
|
|
4491
|
+
const peers = await c.listPeers();
|
|
4492
|
+
for (const p of peers) {
|
|
4493
|
+
if (p.pubkey.startsWith(target)) {
|
|
4494
|
+
hits.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName });
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
if (hits.length === 1) {
|
|
4499
|
+
return { client: hits[0].mesh, targetSpec: hits[0].pubkey };
|
|
4500
|
+
}
|
|
4501
|
+
if (hits.length > 1) {
|
|
4502
|
+
const lines = hits.map((h) => ` - ${h.displayName} @ ${h.mesh.meshSlug} · pubkey ${h.pubkey.slice(0, 20)}…`).join(`
|
|
4503
|
+
`);
|
|
4504
|
+
return {
|
|
4505
|
+
client: null,
|
|
4506
|
+
targetSpec: target,
|
|
4507
|
+
error: `ambiguous pubkey prefix "${target}" matches ${hits.length} peers:
|
|
4508
|
+
${lines}
|
|
4509
|
+
Use a longer prefix.`
|
|
4510
|
+
};
|
|
4511
|
+
}
|
|
4512
|
+
if (target.length === 64) {
|
|
4513
|
+
if (targetClients.length === 1) {
|
|
4514
|
+
return { client: targetClients[0], targetSpec: target };
|
|
4515
|
+
}
|
|
4516
|
+
return {
|
|
4517
|
+
client: null,
|
|
4518
|
+
targetSpec: target,
|
|
4519
|
+
error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
|
|
4520
|
+
};
|
|
4521
|
+
}
|
|
4522
|
+
return {
|
|
4523
|
+
client: null,
|
|
4524
|
+
targetSpec: target,
|
|
4525
|
+
error: `no online peer's pubkey starts with "${target}".`
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
3352
4528
|
const nameLower = target.toLowerCase();
|
|
4529
|
+
const candidates = [];
|
|
4530
|
+
const exactMatches = [];
|
|
4531
|
+
const partialMatches = [];
|
|
3353
4532
|
for (const c of targetClients) {
|
|
4533
|
+
const ownSession = c.getSessionPubkey();
|
|
3354
4534
|
const peers = await c.listPeers();
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
4535
|
+
candidates.push({ mesh: c.meshSlug, peers });
|
|
4536
|
+
for (const p of peers) {
|
|
4537
|
+
if (ownSession && p.pubkey === ownSession)
|
|
4538
|
+
continue;
|
|
4539
|
+
const nameLow = p.displayName.toLowerCase();
|
|
4540
|
+
if (nameLow === nameLower) {
|
|
4541
|
+
exactMatches.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName, cwd: p.cwd });
|
|
4542
|
+
} else if (nameLow.includes(nameLower)) {
|
|
4543
|
+
partialMatches.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName, cwd: p.cwd });
|
|
4544
|
+
}
|
|
3363
4545
|
}
|
|
3364
4546
|
}
|
|
3365
|
-
if (
|
|
3366
|
-
return { client:
|
|
4547
|
+
if (exactMatches.length === 1) {
|
|
4548
|
+
return { client: exactMatches[0].mesh, targetSpec: exactMatches[0].pubkey };
|
|
3367
4549
|
}
|
|
4550
|
+
if (exactMatches.length > 1) {
|
|
4551
|
+
const lines = exactMatches.map((m) => ` - ${m.displayName} · pubkey ${m.pubkey.slice(0, 16)}…${m.cwd ? ` · cwd ${m.cwd}` : ""}`).join(`
|
|
4552
|
+
`);
|
|
4553
|
+
return {
|
|
4554
|
+
client: null,
|
|
4555
|
+
targetSpec: target,
|
|
4556
|
+
error: `"${target}" is ambiguous — ${exactMatches.length} peers share that display name:
|
|
4557
|
+
${lines}
|
|
4558
|
+
` + `Disambiguate by pubkey prefix (e.g. send to "${exactMatches[0].pubkey.slice(0, 12)}…").`
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
if (partialMatches.length === 1) {
|
|
4562
|
+
process.stderr.write(`[claudemesh] resolved "${target}" → "${partialMatches[0].displayName}" (partial match)
|
|
4563
|
+
`);
|
|
4564
|
+
return { client: partialMatches[0].mesh, targetSpec: partialMatches[0].pubkey };
|
|
4565
|
+
}
|
|
4566
|
+
if (partialMatches.length > 1) {
|
|
4567
|
+
const lines = partialMatches.map((m) => ` - ${m.displayName} · pubkey ${m.pubkey.slice(0, 16)}…`).join(`
|
|
4568
|
+
`);
|
|
4569
|
+
return {
|
|
4570
|
+
client: null,
|
|
4571
|
+
targetSpec: target,
|
|
4572
|
+
error: `"${target}" partially matches ${partialMatches.length} peers:
|
|
4573
|
+
${lines}
|
|
4574
|
+
Be more specific, or use a pubkey prefix.`
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
const known = candidates.flatMap((c) => c.peers.map((p) => `${c.mesh}/${p.displayName}`));
|
|
3368
4578
|
return {
|
|
3369
4579
|
client: null,
|
|
3370
4580
|
targetSpec: target,
|
|
3371
|
-
error: `peer "${target}" not found
|
|
4581
|
+
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.")
|
|
3372
4582
|
};
|
|
3373
4583
|
}
|
|
3374
4584
|
async function resolvePeerName(client, pubkey) {
|
|
@@ -3686,9 +4896,9 @@ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
|
3686
4896
|
const results = [];
|
|
3687
4897
|
const seen = new Set;
|
|
3688
4898
|
for (const target of targets) {
|
|
3689
|
-
const { client, targetSpec, error } = await resolveClient(target);
|
|
4899
|
+
const { client, targetSpec, error: error2 } = await resolveClient(target);
|
|
3690
4900
|
if (!client) {
|
|
3691
|
-
results.push(`✗ ${target}: ${
|
|
4901
|
+
results.push(`✗ ${target}: ${error2 ?? "no client resolved"}`);
|
|
3692
4902
|
continue;
|
|
3693
4903
|
}
|
|
3694
4904
|
if (seen.has(targetSpec))
|
|
@@ -3710,13 +4920,23 @@ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
|
3710
4920
|
if (clients2.length === 0)
|
|
3711
4921
|
return text(mesh_slug ? `list_peers: no joined mesh "${mesh_slug}"` : "list_peers: no joined meshes", true);
|
|
3712
4922
|
const sections = [];
|
|
4923
|
+
const statusCache = {};
|
|
3713
4924
|
for (const c of clients2) {
|
|
3714
4925
|
const peers = await c.listPeers();
|
|
3715
4926
|
const header = `## ${c.meshSlug} (${c.status}, mesh ${c.meshId.slice(0, 8)}…)`;
|
|
4927
|
+
statusCache[c.meshSlug] = {
|
|
4928
|
+
total: peers.length,
|
|
4929
|
+
online: peers.filter((p) => p.status !== "offline").length,
|
|
4930
|
+
updatedAt: new Date().toISOString(),
|
|
4931
|
+
you: process.env.CLAUDEMESH_DISPLAY_NAME ?? undefined
|
|
4932
|
+
};
|
|
3716
4933
|
if (peers.length === 0) {
|
|
3717
4934
|
sections.push(`${header}
|
|
3718
4935
|
No peers connected.`);
|
|
3719
4936
|
} else {
|
|
4937
|
+
const pubkeyCounts = new Map;
|
|
4938
|
+
for (const p of peers)
|
|
4939
|
+
pubkeyCounts.set(p.pubkey, (pubkeyCounts.get(p.pubkey) ?? 0) + 1);
|
|
3720
4940
|
const peerLines = peers.map((p) => {
|
|
3721
4941
|
const summary = p.summary ? ` — "${p.summary}"` : "";
|
|
3722
4942
|
const groupsStr = p.groups?.length ? ` [${p.groups.map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ")}]` : "";
|
|
@@ -3734,13 +4954,24 @@ No peers connected.`);
|
|
|
3734
4954
|
const profileAvatar = p.profile?.avatar ? `${p.profile.avatar} ` : "";
|
|
3735
4955
|
const profileTitle = p.profile?.title ? ` (${p.profile.title})` : "";
|
|
3736
4956
|
const hiddenTag = p.visible === false ? " [hidden]" : "";
|
|
3737
|
-
|
|
4957
|
+
const sameKeyCount = pubkeyCounts.get(p.pubkey) ?? 1;
|
|
4958
|
+
const sameKeyTag = sameKeyCount > 1 ? ` [shares key with ${sameKeyCount - 1} other session(s)]` : "";
|
|
4959
|
+
return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${sameKeyTag}${groupsStr}${metaStr} (pubkey: ${p.pubkey.slice(0, 16)}…)${cwdStr}${summary}`;
|
|
3738
4960
|
});
|
|
3739
4961
|
sections.push(`${header}
|
|
3740
4962
|
${peerLines.join(`
|
|
3741
4963
|
`)}`);
|
|
3742
4964
|
}
|
|
3743
4965
|
}
|
|
4966
|
+
try {
|
|
4967
|
+
const { writeFileSync: writeFileSync4, mkdirSync: mkdirSync3, existsSync: existsSync5 } = await import("node:fs");
|
|
4968
|
+
const { join: joinPath } = await import("node:path");
|
|
4969
|
+
const { homedir: homedir3 } = await import("node:os");
|
|
4970
|
+
const dir = joinPath(homedir3(), ".claudemesh");
|
|
4971
|
+
if (!existsSync5(dir))
|
|
4972
|
+
mkdirSync3(dir, { recursive: true });
|
|
4973
|
+
writeFileSync4(joinPath(dir, "peer-cache.json"), JSON.stringify(statusCache));
|
|
4974
|
+
} catch {}
|
|
3744
4975
|
return text(sections.join(`
|
|
3745
4976
|
|
|
3746
4977
|
`));
|
|
@@ -3762,8 +4993,8 @@ ${peerLines.join(`
|
|
|
3762
4993
|
return text(`Message ${id} not found or timed out.`);
|
|
3763
4994
|
const recipientLines = result.recipients.map((r) => ` - ${r.name} (${r.pubkey.slice(0, 12)}…): ${r.status}`);
|
|
3764
4995
|
return text(`Message ${id.slice(0, 12)}… → ${result.targetSpec}
|
|
3765
|
-
|
|
3766
|
-
|
|
4996
|
+
Delivered: ${result.delivered}${result.deliveredAt ? ` at ${result.deliveredAt}` : ""}
|
|
4997
|
+
Recipients:
|
|
3767
4998
|
${recipientLines.join(`
|
|
3768
4999
|
`)}`);
|
|
3769
5000
|
}
|
|
@@ -3981,23 +5212,23 @@ ${lines.join(`
|
|
|
3981
5212
|
const { path: filePath, name: fileName, tags, to: fileTo } = args ?? {};
|
|
3982
5213
|
if (!filePath)
|
|
3983
5214
|
return text("share_file: `path` required", true);
|
|
3984
|
-
const { existsSync:
|
|
3985
|
-
if (!
|
|
5215
|
+
const { existsSync: existsSync5 } = await import("node:fs");
|
|
5216
|
+
if (!existsSync5(filePath))
|
|
3986
5217
|
return text(`share_file: file not found: ${filePath}`, true);
|
|
3987
5218
|
const client = allClients()[0];
|
|
3988
5219
|
if (!client)
|
|
3989
5220
|
return text("share_file: not connected", true);
|
|
3990
5221
|
if (fileTo) {
|
|
3991
5222
|
const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
|
|
3992
|
-
const { readFileSync:
|
|
5223
|
+
const { readFileSync: readFileSync4, writeFileSync: writeFileSync4, mkdtempSync, unlinkSync: unlinkSync2, rmdirSync } = await import("node:fs");
|
|
3993
5224
|
const { tmpdir } = await import("node:os");
|
|
3994
|
-
const { join:
|
|
5225
|
+
const { join: join3, basename } = await import("node:path");
|
|
3995
5226
|
const peers = await client.listPeers();
|
|
3996
5227
|
const targetPeer = peers.find((p) => p.pubkey === fileTo || p.displayName === fileTo);
|
|
3997
5228
|
if (!targetPeer) {
|
|
3998
5229
|
return text(`share_file: peer not found: ${fileTo}`, true);
|
|
3999
5230
|
}
|
|
4000
|
-
const plaintext =
|
|
5231
|
+
const plaintext = readFileSync4(filePath);
|
|
4001
5232
|
const { ciphertext, nonce, key } = await encryptFile2(new Uint8Array(plaintext));
|
|
4002
5233
|
const sealedForTarget = await sealKeyForPeer2(key, targetPeer.pubkey);
|
|
4003
5234
|
const myPubkey = client.getSessionPubkey();
|
|
@@ -4014,9 +5245,9 @@ ${lines.join(`
|
|
|
4014
5245
|
combined.set(ciphertext, nonceBytes.length);
|
|
4015
5246
|
const rawName = fileName ?? basename(filePath);
|
|
4016
5247
|
const baseName = basename(rawName).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 255);
|
|
4017
|
-
const tmpDir = mkdtempSync(
|
|
4018
|
-
const tmpPath =
|
|
4019
|
-
|
|
5248
|
+
const tmpDir = mkdtempSync(join3(tmpdir(), "cm-"));
|
|
5249
|
+
const tmpPath = join3(tmpDir, baseName);
|
|
5250
|
+
writeFileSync4(tmpPath, combined);
|
|
4020
5251
|
try {
|
|
4021
5252
|
const fileId = await client.uploadFile(tmpPath, client.meshId, client.meshSlug, {
|
|
4022
5253
|
name: baseName,
|
|
@@ -4031,7 +5262,7 @@ ${lines.join(`
|
|
|
4031
5262
|
return text(`share_file: upload failed — ${e instanceof Error ? e.message : String(e)}`, true);
|
|
4032
5263
|
} finally {
|
|
4033
5264
|
try {
|
|
4034
|
-
|
|
5265
|
+
unlinkSync2(tmpPath);
|
|
4035
5266
|
} catch {}
|
|
4036
5267
|
try {
|
|
4037
5268
|
rmdirSync(tmpDir);
|
|
@@ -4091,10 +5322,10 @@ ${lines.join(`
|
|
|
4091
5322
|
const plaintext = await decryptFile2(ciphertext, nonce, kf);
|
|
4092
5323
|
if (!plaintext)
|
|
4093
5324
|
return text(genericErr, true);
|
|
4094
|
-
const { writeFileSync:
|
|
5325
|
+
const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync4 } = await import("node:fs");
|
|
4095
5326
|
const { dirname: dirname2 } = await import("node:path");
|
|
4096
|
-
|
|
4097
|
-
|
|
5327
|
+
mkdirSync4(dirname2(save_to), { recursive: true });
|
|
5328
|
+
writeFileSync5(save_to, plaintext);
|
|
4098
5329
|
return text(`Downloaded and decrypted: ${result.name} → ${save_to}`);
|
|
4099
5330
|
}
|
|
4100
5331
|
let res = await fetch(result.url, { signal: AbortSignal.timeout(1e4) }).catch(() => null);
|
|
@@ -4104,10 +5335,10 @@ ${lines.join(`
|
|
|
4104
5335
|
}
|
|
4105
5336
|
if (!res.ok)
|
|
4106
5337
|
return text(`get_file: download failed (${res.status})`, true);
|
|
4107
|
-
const { writeFileSync:
|
|
5338
|
+
const { writeFileSync: writeFileSync4, mkdirSync: mkdirSync3 } = await import("node:fs");
|
|
4108
5339
|
const { dirname } = await import("node:path");
|
|
4109
|
-
|
|
4110
|
-
|
|
5340
|
+
mkdirSync3(dirname(save_to), { recursive: true });
|
|
5341
|
+
writeFileSync4(save_to, Buffer.from(await res.arrayBuffer()));
|
|
4111
5342
|
return text(`Downloaded: ${result.name} → ${save_to}`);
|
|
4112
5343
|
}
|
|
4113
5344
|
case "list_files": {
|
|
@@ -4865,10 +6096,10 @@ ${lines.join(`
|
|
|
4865
6096
|
const entryType = vType ?? "env";
|
|
4866
6097
|
let plaintextBytes;
|
|
4867
6098
|
if (entryType === "file") {
|
|
4868
|
-
const { existsSync:
|
|
4869
|
-
if (!
|
|
6099
|
+
const { existsSync: existsSync5, readFileSync: readFileSync4 } = await import("node:fs");
|
|
6100
|
+
if (!existsSync5(value))
|
|
4870
6101
|
return text(`vault_set: file not found: ${value}`, true);
|
|
4871
|
-
plaintextBytes = new Uint8Array(
|
|
6102
|
+
plaintextBytes = new Uint8Array(readFileSync4(value));
|
|
4872
6103
|
} else {
|
|
4873
6104
|
plaintextBytes = new TextEncoder().encode(value);
|
|
4874
6105
|
}
|
|
@@ -5196,6 +6427,17 @@ ${lines.join(`
|
|
|
5196
6427
|
}
|
|
5197
6428
|
const fromPubkey = msg.senderPubkey || "";
|
|
5198
6429
|
const fromName = fromPubkey ? await resolvePeerName(client, fromPubkey) : "unknown";
|
|
6430
|
+
if (fromPubkey) {
|
|
6431
|
+
try {
|
|
6432
|
+
const { isAllowed: isAllowed2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
|
|
6433
|
+
const kindCap = msg.kind === "broadcast" ? "broadcast" : "dm";
|
|
6434
|
+
if (!isAllowed2(client.meshSlug, fromPubkey, kindCap)) {
|
|
6435
|
+
process.stderr.write(`[claudemesh] dropped ${kindCap} from ${fromName} (not granted)
|
|
6436
|
+
`);
|
|
6437
|
+
return;
|
|
6438
|
+
}
|
|
6439
|
+
} catch {}
|
|
6440
|
+
}
|
|
5199
6441
|
if (messageMode === "inbox") {
|
|
5200
6442
|
try {
|
|
5201
6443
|
await server.notification({
|
|
@@ -5208,7 +6450,10 @@ ${lines.join(`
|
|
|
5208
6450
|
} catch {}
|
|
5209
6451
|
return;
|
|
5210
6452
|
}
|
|
5211
|
-
const
|
|
6453
|
+
const body = msg.plaintext ?? decryptFailedWarning(fromPubkey);
|
|
6454
|
+
const prioBadge = msg.priority === "now" ? "[URGENT] " : msg.priority === "low" ? "[low] " : "";
|
|
6455
|
+
const kindBadge = msg.kind === "broadcast" ? " (broadcast)" : "";
|
|
6456
|
+
const content = `${prioBadge}${fromName}${kindBadge}: ${body}`;
|
|
5212
6457
|
try {
|
|
5213
6458
|
await server.notification({
|
|
5214
6459
|
method: "notifications/claude/channel",
|
|
@@ -5227,7 +6472,7 @@ ${lines.join(`
|
|
|
5227
6472
|
}
|
|
5228
6473
|
}
|
|
5229
6474
|
});
|
|
5230
|
-
process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${
|
|
6475
|
+
process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${body.slice(0, 60)}
|
|
5231
6476
|
`);
|
|
5232
6477
|
} catch (pushErr) {
|
|
5233
6478
|
process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
|
|
@@ -5421,4 +6666,4 @@ startMcpServer().catch((err) => {
|
|
|
5421
6666
|
process.exit(1);
|
|
5422
6667
|
});
|
|
5423
6668
|
|
|
5424
|
-
//# debugId=
|
|
6669
|
+
//# debugId=B1200DBD10CF1BC164756E2164756E21
|