claudemesh-cli 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +586 -30
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39930,6 +39930,81 @@ var require_libsodium_wrappers = __commonJS((exports) => {
|
|
|
39930
39930
|
})(exports);
|
|
39931
39931
|
});
|
|
39932
39932
|
|
|
39933
|
+
// src/crypto/keypair.ts
|
|
39934
|
+
var exports_keypair = {};
|
|
39935
|
+
__export(exports_keypair, {
|
|
39936
|
+
generateKeypair: () => generateKeypair2,
|
|
39937
|
+
ensureSodium: () => ensureSodium
|
|
39938
|
+
});
|
|
39939
|
+
async function ensureSodium() {
|
|
39940
|
+
if (!ready) {
|
|
39941
|
+
await import_libsodium_wrappers.default.ready;
|
|
39942
|
+
ready = true;
|
|
39943
|
+
}
|
|
39944
|
+
return import_libsodium_wrappers.default;
|
|
39945
|
+
}
|
|
39946
|
+
async function generateKeypair2() {
|
|
39947
|
+
const s = await ensureSodium();
|
|
39948
|
+
const kp = s.crypto_sign_keypair();
|
|
39949
|
+
return {
|
|
39950
|
+
publicKey: s.to_hex(kp.publicKey),
|
|
39951
|
+
secretKey: s.to_hex(kp.privateKey)
|
|
39952
|
+
};
|
|
39953
|
+
}
|
|
39954
|
+
var import_libsodium_wrappers, ready = false;
|
|
39955
|
+
var init_keypair = __esm(() => {
|
|
39956
|
+
import_libsodium_wrappers = __toESM(require_libsodium_wrappers(), 1);
|
|
39957
|
+
});
|
|
39958
|
+
|
|
39959
|
+
// src/crypto/file-crypto.ts
|
|
39960
|
+
var exports_file_crypto = {};
|
|
39961
|
+
__export(exports_file_crypto, {
|
|
39962
|
+
sealKeyForPeer: () => sealKeyForPeer,
|
|
39963
|
+
openSealedKey: () => openSealedKey,
|
|
39964
|
+
encryptFile: () => encryptFile,
|
|
39965
|
+
decryptFile: () => decryptFile
|
|
39966
|
+
});
|
|
39967
|
+
async function encryptFile(plaintext) {
|
|
39968
|
+
const sodium2 = await ensureSodium();
|
|
39969
|
+
const key = sodium2.randombytes_buf(sodium2.crypto_secretbox_KEYBYTES);
|
|
39970
|
+
const nonce = sodium2.randombytes_buf(sodium2.crypto_secretbox_NONCEBYTES);
|
|
39971
|
+
const ciphertext = sodium2.crypto_secretbox_easy(plaintext, nonce, key);
|
|
39972
|
+
return {
|
|
39973
|
+
ciphertext,
|
|
39974
|
+
nonce: sodium2.to_base64(nonce, sodium2.base64_variants.ORIGINAL),
|
|
39975
|
+
key
|
|
39976
|
+
};
|
|
39977
|
+
}
|
|
39978
|
+
async function decryptFile(ciphertext, nonceB64, key) {
|
|
39979
|
+
const sodium2 = await ensureSodium();
|
|
39980
|
+
try {
|
|
39981
|
+
const nonce = sodium2.from_base64(nonceB64, sodium2.base64_variants.ORIGINAL);
|
|
39982
|
+
return sodium2.crypto_secretbox_open_easy(ciphertext, nonce, key);
|
|
39983
|
+
} catch {
|
|
39984
|
+
return null;
|
|
39985
|
+
}
|
|
39986
|
+
}
|
|
39987
|
+
async function sealKeyForPeer(kf, recipientPubkeyHex) {
|
|
39988
|
+
const sodium2 = await ensureSodium();
|
|
39989
|
+
const recipientCurve = sodium2.crypto_sign_ed25519_pk_to_curve25519(sodium2.from_hex(recipientPubkeyHex));
|
|
39990
|
+
const sealed = sodium2.crypto_box_seal(kf, recipientCurve);
|
|
39991
|
+
return sodium2.to_base64(sealed, sodium2.base64_variants.ORIGINAL);
|
|
39992
|
+
}
|
|
39993
|
+
async function openSealedKey(sealedB64, myPubkeyHex, mySecretKeyHex) {
|
|
39994
|
+
const sodium2 = await ensureSodium();
|
|
39995
|
+
try {
|
|
39996
|
+
const myCurvePub = sodium2.crypto_sign_ed25519_pk_to_curve25519(sodium2.from_hex(myPubkeyHex));
|
|
39997
|
+
const myCurveSec = sodium2.crypto_sign_ed25519_sk_to_curve25519(sodium2.from_hex(mySecretKeyHex));
|
|
39998
|
+
const sealed = sodium2.from_base64(sealedB64, sodium2.base64_variants.ORIGINAL);
|
|
39999
|
+
return sodium2.crypto_box_seal_open(sealed, myCurvePub, myCurveSec);
|
|
40000
|
+
} catch {
|
|
40001
|
+
return null;
|
|
40002
|
+
}
|
|
40003
|
+
}
|
|
40004
|
+
var init_file_crypto = __esm(() => {
|
|
40005
|
+
init_keypair();
|
|
40006
|
+
});
|
|
40007
|
+
|
|
39933
40008
|
// ../../node_modules/citty/dist/_chunks/libs/scule.mjs
|
|
39934
40009
|
var NUMBER_CHAR_RE = /\d/;
|
|
39935
40010
|
var STR_SPLITTERS = [
|
|
@@ -46963,7 +47038,7 @@ var TOOLS = [
|
|
|
46963
47038
|
},
|
|
46964
47039
|
{
|
|
46965
47040
|
name: "share_file",
|
|
46966
|
-
description: "Share a persistent file with the mesh. All current and future peers can access it.",
|
|
47041
|
+
description: "Share a persistent file with the mesh. All current and future peers can access it. If `to` is specified, the file is E2E encrypted and only accessible to that peer (and you).",
|
|
46967
47042
|
inputSchema: {
|
|
46968
47043
|
type: "object",
|
|
46969
47044
|
properties: {
|
|
@@ -46976,6 +47051,10 @@ var TOOLS = [
|
|
|
46976
47051
|
type: "array",
|
|
46977
47052
|
items: { type: "string" },
|
|
46978
47053
|
description: "Tags for categorization"
|
|
47054
|
+
},
|
|
47055
|
+
to: {
|
|
47056
|
+
type: "string",
|
|
47057
|
+
description: "Peer display name or pubkey hex — if set, file is E2E encrypted for this peer only"
|
|
46979
47058
|
}
|
|
46980
47059
|
},
|
|
46981
47060
|
required: ["path"]
|
|
@@ -47029,6 +47108,18 @@ var TOOLS = [
|
|
|
47029
47108
|
required: ["id"]
|
|
47030
47109
|
}
|
|
47031
47110
|
},
|
|
47111
|
+
{
|
|
47112
|
+
name: "grant_file_access",
|
|
47113
|
+
description: "Grant a peer access to an E2E encrypted file you shared. You must be the owner.",
|
|
47114
|
+
inputSchema: {
|
|
47115
|
+
type: "object",
|
|
47116
|
+
properties: {
|
|
47117
|
+
fileId: { type: "string", description: "File ID" },
|
|
47118
|
+
to: { type: "string", description: "Peer display name or pubkey hex to grant access to" }
|
|
47119
|
+
},
|
|
47120
|
+
required: ["fileId", "to"]
|
|
47121
|
+
}
|
|
47122
|
+
},
|
|
47032
47123
|
{
|
|
47033
47124
|
name: "vector_store",
|
|
47034
47125
|
description: "Store an embedding in a per-mesh Qdrant collection. Auto-creates the collection on first use.",
|
|
@@ -47321,26 +47412,8 @@ var wrapper_default = import_websocket.default;
|
|
|
47321
47412
|
// src/ws/client.ts
|
|
47322
47413
|
import { randomBytes } from "node:crypto";
|
|
47323
47414
|
|
|
47324
|
-
// src/crypto/keypair.ts
|
|
47325
|
-
var import_libsodium_wrappers = __toESM(require_libsodium_wrappers(), 1);
|
|
47326
|
-
var ready = false;
|
|
47327
|
-
async function ensureSodium() {
|
|
47328
|
-
if (!ready) {
|
|
47329
|
-
await import_libsodium_wrappers.default.ready;
|
|
47330
|
-
ready = true;
|
|
47331
|
-
}
|
|
47332
|
-
return import_libsodium_wrappers.default;
|
|
47333
|
-
}
|
|
47334
|
-
async function generateKeypair2() {
|
|
47335
|
-
const s = await ensureSodium();
|
|
47336
|
-
const kp = s.crypto_sign_keypair();
|
|
47337
|
-
return {
|
|
47338
|
-
publicKey: s.to_hex(kp.publicKey),
|
|
47339
|
-
secretKey: s.to_hex(kp.privateKey)
|
|
47340
|
-
};
|
|
47341
|
-
}
|
|
47342
|
-
|
|
47343
47415
|
// src/crypto/envelope.ts
|
|
47416
|
+
init_keypair();
|
|
47344
47417
|
var HEX_PUBKEY = /^[0-9a-f]{64}$/;
|
|
47345
47418
|
function isDirectTarget(targetSpec) {
|
|
47346
47419
|
return HEX_PUBKEY.test(targetSpec);
|
|
@@ -47371,6 +47444,7 @@ async function decryptDirect(envelope, senderPubkeyHex, recipientSecretKeyHex) {
|
|
|
47371
47444
|
}
|
|
47372
47445
|
|
|
47373
47446
|
// src/crypto/hello-sig.ts
|
|
47447
|
+
init_keypair();
|
|
47374
47448
|
async function signHello(meshId, memberId, pubkey, secretKeyHex) {
|
|
47375
47449
|
const s = await ensureSodium();
|
|
47376
47450
|
const timestamp = Date.now();
|
|
@@ -47380,6 +47454,7 @@ async function signHello(meshId, memberId, pubkey, secretKeyHex) {
|
|
|
47380
47454
|
}
|
|
47381
47455
|
|
|
47382
47456
|
// src/ws/client.ts
|
|
47457
|
+
init_keypair();
|
|
47383
47458
|
var MAX_QUEUED = 100;
|
|
47384
47459
|
var HELLO_ACK_TIMEOUT_MS = 5000;
|
|
47385
47460
|
var BACKOFF_CAPS = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
@@ -47401,6 +47476,7 @@ class BrokerClient {
|
|
|
47401
47476
|
stateChangeHandlers = new Set;
|
|
47402
47477
|
sessionPubkey = null;
|
|
47403
47478
|
sessionSecretKey = null;
|
|
47479
|
+
grantFileAccessResolvers = [];
|
|
47404
47480
|
closed = false;
|
|
47405
47481
|
reconnectAttempt = 0;
|
|
47406
47482
|
helloTimer = null;
|
|
@@ -47421,6 +47497,12 @@ class BrokerClient {
|
|
|
47421
47497
|
get pushHistory() {
|
|
47422
47498
|
return this.pushBuffer;
|
|
47423
47499
|
}
|
|
47500
|
+
getSessionPubkey() {
|
|
47501
|
+
return this.sessionPubkey;
|
|
47502
|
+
}
|
|
47503
|
+
getSessionSecretKey() {
|
|
47504
|
+
return this.sessionSecretKey;
|
|
47505
|
+
}
|
|
47424
47506
|
async connect() {
|
|
47425
47507
|
if (this.closed)
|
|
47426
47508
|
throw new Error("client is closed");
|
|
@@ -47767,7 +47849,10 @@ class BrokerClient {
|
|
|
47767
47849
|
"X-File-Name": fileName,
|
|
47768
47850
|
"X-Tags": JSON.stringify(opts.tags ?? []),
|
|
47769
47851
|
"X-Persistent": String(opts.persistent ?? true),
|
|
47770
|
-
"X-Target-Spec": opts.targetSpec ?? ""
|
|
47852
|
+
"X-Target-Spec": opts.targetSpec ?? "",
|
|
47853
|
+
...opts.encrypted ? { "X-Encrypted": "true" } : {},
|
|
47854
|
+
...opts.ownerPubkey ? { "X-Owner-Pubkey": opts.ownerPubkey } : {},
|
|
47855
|
+
...opts.fileKeys?.length ? { "X-File-Keys": JSON.stringify(opts.fileKeys) } : {}
|
|
47771
47856
|
},
|
|
47772
47857
|
body: data,
|
|
47773
47858
|
signal: AbortSignal.timeout(30000)
|
|
@@ -47778,6 +47863,22 @@ class BrokerClient {
|
|
|
47778
47863
|
}
|
|
47779
47864
|
return body.fileId;
|
|
47780
47865
|
}
|
|
47866
|
+
async grantFileAccess(fileId, peerPubkey, sealedKey) {
|
|
47867
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
47868
|
+
return false;
|
|
47869
|
+
return new Promise((resolve) => {
|
|
47870
|
+
const resolvers = this.grantFileAccessResolvers;
|
|
47871
|
+
resolvers.push(resolve);
|
|
47872
|
+
this.ws.send(JSON.stringify({ type: "grant_file_access", fileId, peerPubkey, sealedKey }));
|
|
47873
|
+
setTimeout(() => {
|
|
47874
|
+
const idx = resolvers.indexOf(resolve);
|
|
47875
|
+
if (idx !== -1) {
|
|
47876
|
+
resolvers.splice(idx, 1);
|
|
47877
|
+
resolve(false);
|
|
47878
|
+
}
|
|
47879
|
+
}, 5000);
|
|
47880
|
+
});
|
|
47881
|
+
}
|
|
47781
47882
|
async vectorStore(collection, text, metadata) {
|
|
47782
47883
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
47783
47884
|
return null;
|
|
@@ -48178,7 +48279,12 @@ class BrokerClient {
|
|
|
48178
48279
|
const resolver = this.fileUrlResolvers.shift();
|
|
48179
48280
|
if (resolver) {
|
|
48180
48281
|
if (msg.url) {
|
|
48181
|
-
resolver({
|
|
48282
|
+
resolver({
|
|
48283
|
+
url: String(msg.url),
|
|
48284
|
+
name: String(msg.name ?? ""),
|
|
48285
|
+
encrypted: msg.encrypted ? true : undefined,
|
|
48286
|
+
sealedKey: msg.sealedKey ? String(msg.sealedKey) : undefined
|
|
48287
|
+
});
|
|
48182
48288
|
} else {
|
|
48183
48289
|
resolver(null);
|
|
48184
48290
|
}
|
|
@@ -48199,6 +48305,12 @@ class BrokerClient {
|
|
|
48199
48305
|
resolver(accesses);
|
|
48200
48306
|
return;
|
|
48201
48307
|
}
|
|
48308
|
+
if (msg.type === "grant_file_access_ok") {
|
|
48309
|
+
const resolver = this.grantFileAccessResolvers.shift();
|
|
48310
|
+
if (resolver)
|
|
48311
|
+
resolver(true);
|
|
48312
|
+
return;
|
|
48313
|
+
}
|
|
48202
48314
|
if (msg.type === "vector_stored") {
|
|
48203
48315
|
const resolver = this.vectorStoredResolvers.shift();
|
|
48204
48316
|
if (resolver)
|
|
@@ -48795,7 +48907,7 @@ ${lines.join(`
|
|
|
48795
48907
|
return text(`Forgotten: ${id}`);
|
|
48796
48908
|
}
|
|
48797
48909
|
case "share_file": {
|
|
48798
|
-
const { path: filePath, name: fileName, tags } = args ?? {};
|
|
48910
|
+
const { path: filePath, name: fileName, tags, to: fileTo } = args ?? {};
|
|
48799
48911
|
if (!filePath)
|
|
48800
48912
|
return text("share_file: `path` required", true);
|
|
48801
48913
|
const { existsSync: existsSync2 } = await import("node:fs");
|
|
@@ -48804,6 +48916,56 @@ ${lines.join(`
|
|
|
48804
48916
|
const client = allClients()[0];
|
|
48805
48917
|
if (!client)
|
|
48806
48918
|
return text("share_file: not connected", true);
|
|
48919
|
+
if (fileTo) {
|
|
48920
|
+
const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
|
|
48921
|
+
const { readFileSync: readFileSync2, writeFileSync: writeFileSync2, mkdtempSync, unlinkSync, rmdirSync } = await import("node:fs");
|
|
48922
|
+
const { tmpdir } = await import("node:os");
|
|
48923
|
+
const { join: join2, basename } = await import("node:path");
|
|
48924
|
+
const peers = await client.listPeers();
|
|
48925
|
+
const targetPeer = peers.find((p) => p.pubkey === fileTo || p.displayName === fileTo);
|
|
48926
|
+
if (!targetPeer) {
|
|
48927
|
+
return text(`share_file: peer not found: ${fileTo}`, true);
|
|
48928
|
+
}
|
|
48929
|
+
const plaintext = readFileSync2(filePath);
|
|
48930
|
+
const { ciphertext, nonce, key } = await encryptFile2(new Uint8Array(plaintext));
|
|
48931
|
+
const sealedForTarget = await sealKeyForPeer2(key, targetPeer.pubkey);
|
|
48932
|
+
const myPubkey = client.getSessionPubkey();
|
|
48933
|
+
const sealedForSelf = myPubkey ? await sealKeyForPeer2(key, myPubkey) : null;
|
|
48934
|
+
const fileKeys = [
|
|
48935
|
+
{ peerPubkey: targetPeer.pubkey, sealedKey: sealedForTarget },
|
|
48936
|
+
...sealedForSelf && myPubkey ? [{ peerPubkey: myPubkey, sealedKey: sealedForSelf }] : []
|
|
48937
|
+
];
|
|
48938
|
+
const { ensureSodium: ensureSodium2 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
|
|
48939
|
+
const sodium2 = await ensureSodium2();
|
|
48940
|
+
const nonceBytes = sodium2.from_base64(nonce, sodium2.base64_variants.ORIGINAL);
|
|
48941
|
+
const combined = new Uint8Array(nonceBytes.length + ciphertext.length);
|
|
48942
|
+
combined.set(nonceBytes, 0);
|
|
48943
|
+
combined.set(ciphertext, nonceBytes.length);
|
|
48944
|
+
const baseName = fileName ?? basename(filePath);
|
|
48945
|
+
const tmpDir = mkdtempSync(join2(tmpdir(), "cm-"));
|
|
48946
|
+
const tmpPath = join2(tmpDir, baseName);
|
|
48947
|
+
writeFileSync2(tmpPath, combined);
|
|
48948
|
+
try {
|
|
48949
|
+
const fileId = await client.uploadFile(tmpPath, client.meshId, client.meshSlug, {
|
|
48950
|
+
name: baseName,
|
|
48951
|
+
tags,
|
|
48952
|
+
persistent: true,
|
|
48953
|
+
encrypted: true,
|
|
48954
|
+
ownerPubkey: myPubkey ?? undefined,
|
|
48955
|
+
fileKeys
|
|
48956
|
+
});
|
|
48957
|
+
return text(`Shared (E2E encrypted): ${baseName} → ${targetPeer.displayName} (${fileId})`);
|
|
48958
|
+
} catch (e) {
|
|
48959
|
+
return text(`share_file: upload failed — ${e instanceof Error ? e.message : String(e)}`, true);
|
|
48960
|
+
} finally {
|
|
48961
|
+
try {
|
|
48962
|
+
unlinkSync(tmpPath);
|
|
48963
|
+
} catch {}
|
|
48964
|
+
try {
|
|
48965
|
+
rmdirSync(tmpDir);
|
|
48966
|
+
} catch {}
|
|
48967
|
+
}
|
|
48968
|
+
}
|
|
48807
48969
|
try {
|
|
48808
48970
|
const fileId = await client.uploadFile(filePath, client.meshId, client.meshSlug, {
|
|
48809
48971
|
name: fileName,
|
|
@@ -48825,6 +48987,34 @@ ${lines.join(`
|
|
|
48825
48987
|
const result = await client.getFile(id);
|
|
48826
48988
|
if (!result)
|
|
48827
48989
|
return text(`get_file: file ${id} not found`, true);
|
|
48990
|
+
if (result.encrypted && result.sealedKey) {
|
|
48991
|
+
const { openSealedKey: openSealedKey2, decryptFile: decryptFile2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
|
|
48992
|
+
const { ensureSodium: ensureSodium2 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
|
|
48993
|
+
const myPubkey = client.getSessionPubkey();
|
|
48994
|
+
const mySecret = client.getSessionSecretKey();
|
|
48995
|
+
if (!myPubkey || !mySecret) {
|
|
48996
|
+
return text("get_file: no session keypair — cannot decrypt", true);
|
|
48997
|
+
}
|
|
48998
|
+
const kf = await openSealedKey2(result.sealedKey, myPubkey, mySecret);
|
|
48999
|
+
if (!kf)
|
|
49000
|
+
return text("get_file: failed to open sealed key", true);
|
|
49001
|
+
const resp = await fetch(result.url, { signal: AbortSignal.timeout(30000) });
|
|
49002
|
+
if (!resp.ok)
|
|
49003
|
+
return text(`get_file: download failed (${resp.status})`, true);
|
|
49004
|
+
const buf = new Uint8Array(await resp.arrayBuffer());
|
|
49005
|
+
const sodium2 = await ensureSodium2();
|
|
49006
|
+
const NONCE_BYTES = sodium2.crypto_secretbox_NONCEBYTES;
|
|
49007
|
+
const nonce = sodium2.to_base64(buf.slice(0, NONCE_BYTES), sodium2.base64_variants.ORIGINAL);
|
|
49008
|
+
const ciphertext = buf.slice(NONCE_BYTES);
|
|
49009
|
+
const plaintext = await decryptFile2(ciphertext, nonce, kf);
|
|
49010
|
+
if (!plaintext)
|
|
49011
|
+
return text("get_file: decryption failed", true);
|
|
49012
|
+
const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = await import("node:fs");
|
|
49013
|
+
const { dirname: dirname3 } = await import("node:path");
|
|
49014
|
+
mkdirSync3(dirname3(save_to), { recursive: true });
|
|
49015
|
+
writeFileSync3(save_to, plaintext);
|
|
49016
|
+
return text(`Downloaded and decrypted: ${result.name} → ${save_to}`);
|
|
49017
|
+
}
|
|
48828
49018
|
const res = await fetch(result.url, { signal: AbortSignal.timeout(30000) });
|
|
48829
49019
|
if (!res.ok)
|
|
48830
49020
|
return text(`get_file: download failed (${res.status})`, true);
|
|
@@ -49167,6 +49357,38 @@ ${rows.join(`
|
|
|
49167
49357
|
return text(results.join(`
|
|
49168
49358
|
`));
|
|
49169
49359
|
}
|
|
49360
|
+
case "grant_file_access": {
|
|
49361
|
+
const { fileId, to: grantTo } = args ?? {};
|
|
49362
|
+
if (!fileId || !grantTo)
|
|
49363
|
+
return text("grant_file_access: `fileId` and `to` required", true);
|
|
49364
|
+
const client = allClients()[0];
|
|
49365
|
+
if (!client)
|
|
49366
|
+
return text("grant_file_access: not connected", true);
|
|
49367
|
+
const peers = await client.listPeers();
|
|
49368
|
+
const targetPeer = peers.find((p) => p.pubkey === grantTo || p.displayName === grantTo);
|
|
49369
|
+
if (!targetPeer)
|
|
49370
|
+
return text(`grant_file_access: peer not found: ${grantTo}`, true);
|
|
49371
|
+
const result = await client.getFile(fileId);
|
|
49372
|
+
if (!result)
|
|
49373
|
+
return text("grant_file_access: file not found", true);
|
|
49374
|
+
if (!result.encrypted)
|
|
49375
|
+
return text("grant_file_access: file is not encrypted", true);
|
|
49376
|
+
if (!result.sealedKey)
|
|
49377
|
+
return text("grant_file_access: no key available (are you the owner?)", true);
|
|
49378
|
+
const { openSealedKey: openSealedKey2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
|
|
49379
|
+
const myPubkey = client.getSessionPubkey();
|
|
49380
|
+
const mySecret = client.getSessionSecretKey();
|
|
49381
|
+
if (!myPubkey || !mySecret)
|
|
49382
|
+
return text("grant_file_access: no session keypair", true);
|
|
49383
|
+
const kf = await openSealedKey2(result.sealedKey, myPubkey, mySecret);
|
|
49384
|
+
if (!kf)
|
|
49385
|
+
return text("grant_file_access: cannot decrypt your own key", true);
|
|
49386
|
+
const sealedForPeer = await sealKeyForPeer2(kf, targetPeer.pubkey);
|
|
49387
|
+
const ok = await client.grantFileAccess(fileId, targetPeer.pubkey, sealedForPeer);
|
|
49388
|
+
if (!ok)
|
|
49389
|
+
return text("grant_file_access: broker did not confirm", true);
|
|
49390
|
+
return text(`Access granted: ${targetPeer.displayName} can now download file ${fileId}`);
|
|
49391
|
+
}
|
|
49170
49392
|
default:
|
|
49171
49393
|
return text(`Unknown tool: ${name}`, true);
|
|
49172
49394
|
}
|
|
@@ -49399,6 +49621,73 @@ function writeClaudeSettings(obj) {
|
|
|
49399
49621
|
writeFileSync2(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
|
|
49400
49622
|
`, "utf-8");
|
|
49401
49623
|
}
|
|
49624
|
+
var CLAUDEMESH_TOOLS = [
|
|
49625
|
+
"mcp__claudemesh__send_message",
|
|
49626
|
+
"mcp__claudemesh__list_peers",
|
|
49627
|
+
"mcp__claudemesh__check_messages",
|
|
49628
|
+
"mcp__claudemesh__set_summary",
|
|
49629
|
+
"mcp__claudemesh__set_status",
|
|
49630
|
+
"mcp__claudemesh__join_group",
|
|
49631
|
+
"mcp__claudemesh__leave_group",
|
|
49632
|
+
"mcp__claudemesh__get_state",
|
|
49633
|
+
"mcp__claudemesh__set_state",
|
|
49634
|
+
"mcp__claudemesh__list_state",
|
|
49635
|
+
"mcp__claudemesh__remember",
|
|
49636
|
+
"mcp__claudemesh__recall",
|
|
49637
|
+
"mcp__claudemesh__forget",
|
|
49638
|
+
"mcp__claudemesh__share_file",
|
|
49639
|
+
"mcp__claudemesh__get_file",
|
|
49640
|
+
"mcp__claudemesh__list_files",
|
|
49641
|
+
"mcp__claudemesh__file_status",
|
|
49642
|
+
"mcp__claudemesh__delete_file",
|
|
49643
|
+
"mcp__claudemesh__vector_store",
|
|
49644
|
+
"mcp__claudemesh__vector_search",
|
|
49645
|
+
"mcp__claudemesh__vector_delete",
|
|
49646
|
+
"mcp__claudemesh__list_collections",
|
|
49647
|
+
"mcp__claudemesh__graph_query",
|
|
49648
|
+
"mcp__claudemesh__graph_execute",
|
|
49649
|
+
"mcp__claudemesh__mesh_info",
|
|
49650
|
+
"mcp__claudemesh__ping_mesh",
|
|
49651
|
+
"mcp__claudemesh__message_status",
|
|
49652
|
+
"mcp__claudemesh__share_context",
|
|
49653
|
+
"mcp__claudemesh__get_context",
|
|
49654
|
+
"mcp__claudemesh__list_contexts",
|
|
49655
|
+
"mcp__claudemesh__create_task",
|
|
49656
|
+
"mcp__claudemesh__claim_task",
|
|
49657
|
+
"mcp__claudemesh__complete_task",
|
|
49658
|
+
"mcp__claudemesh__list_tasks",
|
|
49659
|
+
"mcp__claudemesh__create_stream",
|
|
49660
|
+
"mcp__claudemesh__publish",
|
|
49661
|
+
"mcp__claudemesh__subscribe",
|
|
49662
|
+
"mcp__claudemesh__list_streams",
|
|
49663
|
+
"mcp__claudemesh__mesh_execute",
|
|
49664
|
+
"mcp__claudemesh__mesh_query",
|
|
49665
|
+
"mcp__claudemesh__mesh_schema"
|
|
49666
|
+
];
|
|
49667
|
+
function installAllowedTools() {
|
|
49668
|
+
const settings = readClaudeSettings();
|
|
49669
|
+
const existing = new Set(settings.allowedTools ?? []);
|
|
49670
|
+
const toAdd = CLAUDEMESH_TOOLS.filter((t) => !existing.has(t));
|
|
49671
|
+
if (toAdd.length > 0) {
|
|
49672
|
+
settings.allowedTools = [...Array.from(existing), ...toAdd];
|
|
49673
|
+
writeClaudeSettings(settings);
|
|
49674
|
+
}
|
|
49675
|
+
return { added: toAdd, unchanged: CLAUDEMESH_TOOLS.length - toAdd.length };
|
|
49676
|
+
}
|
|
49677
|
+
function uninstallAllowedTools() {
|
|
49678
|
+
if (!existsSync2(CLAUDE_SETTINGS))
|
|
49679
|
+
return 0;
|
|
49680
|
+
const settings = readClaudeSettings();
|
|
49681
|
+
const existing = settings.allowedTools ?? [];
|
|
49682
|
+
const toolSet = new Set(CLAUDEMESH_TOOLS);
|
|
49683
|
+
const kept = existing.filter((t) => !toolSet.has(t));
|
|
49684
|
+
const removed = existing.length - kept.length;
|
|
49685
|
+
if (removed > 0) {
|
|
49686
|
+
settings.allowedTools = kept;
|
|
49687
|
+
writeClaudeSettings(settings);
|
|
49688
|
+
}
|
|
49689
|
+
return removed;
|
|
49690
|
+
}
|
|
49402
49691
|
function installHooks() {
|
|
49403
49692
|
const settings = readClaudeSettings();
|
|
49404
49693
|
const hooks = (settings.hooks ??= {}) ?? {};
|
|
@@ -49475,6 +49764,19 @@ function runInstall(args = []) {
|
|
|
49475
49764
|
console.log(`✓ MCP server "${MCP_NAME}" ${action}`);
|
|
49476
49765
|
console.log(dim(` config: ${CLAUDE_CONFIG}`));
|
|
49477
49766
|
console.log(dim(` command: ${desired.command}${desired.args?.length ? " " + desired.args.join(" ") : ""}`));
|
|
49767
|
+
try {
|
|
49768
|
+
const { added, unchanged } = installAllowedTools();
|
|
49769
|
+
if (added.length > 0) {
|
|
49770
|
+
console.log(`✓ allowedTools: ${added.length} claudemesh tools pre-approved${unchanged > 0 ? `, ${unchanged} already present` : ""}`);
|
|
49771
|
+
console.log(dim(` This lets claudemesh tools run without --dangerously-skip-permissions.`));
|
|
49772
|
+
console.log(dim(` Your existing allowedTools entries were preserved.`));
|
|
49773
|
+
} else {
|
|
49774
|
+
console.log(`✓ allowedTools: all ${unchanged} claudemesh tools already pre-approved`);
|
|
49775
|
+
}
|
|
49776
|
+
console.log(dim(` config: ${CLAUDE_SETTINGS}`));
|
|
49777
|
+
} catch (e) {
|
|
49778
|
+
console.error(`⚠ allowedTools update failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
49779
|
+
}
|
|
49478
49780
|
if (!skipHooks) {
|
|
49479
49781
|
try {
|
|
49480
49782
|
const { added, unchanged } = installHooks();
|
|
@@ -49508,6 +49810,16 @@ function runUninstall() {
|
|
|
49508
49810
|
} else {
|
|
49509
49811
|
console.log(`· MCP server "${MCP_NAME}" not present`);
|
|
49510
49812
|
}
|
|
49813
|
+
try {
|
|
49814
|
+
const removed = uninstallAllowedTools();
|
|
49815
|
+
if (removed > 0) {
|
|
49816
|
+
console.log(`✓ allowedTools: ${removed} claudemesh tools removed`);
|
|
49817
|
+
} else {
|
|
49818
|
+
console.log("· No claudemesh allowedTools to remove");
|
|
49819
|
+
}
|
|
49820
|
+
} catch (e) {
|
|
49821
|
+
console.error(`⚠ allowedTools removal failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
49822
|
+
}
|
|
49511
49823
|
try {
|
|
49512
49824
|
const removed = uninstallHooks();
|
|
49513
49825
|
if (removed > 0) {
|
|
@@ -49523,6 +49835,7 @@ function runUninstall() {
|
|
|
49523
49835
|
}
|
|
49524
49836
|
|
|
49525
49837
|
// src/invite/parse.ts
|
|
49838
|
+
init_keypair();
|
|
49526
49839
|
function validatePayload(obj) {
|
|
49527
49840
|
if (!obj || typeof obj !== "object")
|
|
49528
49841
|
throw new Error("invite payload is not an object");
|
|
@@ -49643,6 +49956,7 @@ async function enrollWithBroker2(args) {
|
|
|
49643
49956
|
}
|
|
49644
49957
|
|
|
49645
49958
|
// src/commands/join.ts
|
|
49959
|
+
init_keypair();
|
|
49646
49960
|
init_config();
|
|
49647
49961
|
init_env();
|
|
49648
49962
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
@@ -49911,12 +50225,12 @@ async function confirmPermissions() {
|
|
|
49911
50225
|
const yellow = (s) => useColor ? `\x1B[33m${s}\x1B[39m` : s;
|
|
49912
50226
|
console.log(yellow(bold2(" Autonomous mode")));
|
|
49913
50227
|
console.log("");
|
|
49914
|
-
console.log(" Claude will
|
|
49915
|
-
console.log("
|
|
49916
|
-
console.log(" no
|
|
50228
|
+
console.log(" Claude will run with --dangerously-skip-permissions, bypassing");
|
|
50229
|
+
console.log(" ALL permission prompts — not just claudemesh tools.");
|
|
50230
|
+
console.log(" Peers exchange text only — no file access, no tool calls.");
|
|
49917
50231
|
console.log("");
|
|
49918
|
-
console.log(dim("
|
|
49919
|
-
console.log(dim("
|
|
50232
|
+
console.log(dim(" Without -y: only claudemesh tools are pre-approved (via allowedTools)."));
|
|
50233
|
+
console.log(dim(" Use -y for autonomous agents. Omit it for shared/multi-person meshes."));
|
|
49920
50234
|
console.log("");
|
|
49921
50235
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
49922
50236
|
return new Promise((resolve2, reject) => {
|
|
@@ -50089,7 +50403,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
50089
50403
|
const claudeArgs = [
|
|
50090
50404
|
"--dangerously-load-development-channels",
|
|
50091
50405
|
"server:claudemesh",
|
|
50092
|
-
"--dangerously-skip-permissions",
|
|
50406
|
+
...args.skipPermConfirm ? ["--dangerously-skip-permissions"] : [],
|
|
50093
50407
|
...args.systemPrompt ? ["--system-prompt", args.systemPrompt] : [],
|
|
50094
50408
|
...filtered
|
|
50095
50409
|
];
|
|
@@ -50142,7 +50456,7 @@ init_config();
|
|
|
50142
50456
|
// package.json
|
|
50143
50457
|
var package_default = {
|
|
50144
50458
|
name: "claudemesh-cli",
|
|
50145
|
-
version: "0.6.
|
|
50459
|
+
version: "0.6.2",
|
|
50146
50460
|
description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
|
50147
50461
|
keywords: [
|
|
50148
50462
|
"claude-code",
|
|
@@ -50550,6 +50864,185 @@ function runWelcome() {
|
|
|
50550
50864
|
console.log("");
|
|
50551
50865
|
}
|
|
50552
50866
|
|
|
50867
|
+
// src/commands/connect.ts
|
|
50868
|
+
import { hostname as hostname3 } from "node:os";
|
|
50869
|
+
init_config();
|
|
50870
|
+
async function withMesh(opts, fn) {
|
|
50871
|
+
const config2 = loadConfig();
|
|
50872
|
+
if (config2.meshes.length === 0) {
|
|
50873
|
+
console.error("No meshes joined. Run `claudemesh join <url>` first.");
|
|
50874
|
+
process.exit(1);
|
|
50875
|
+
}
|
|
50876
|
+
let mesh;
|
|
50877
|
+
if (opts.meshSlug) {
|
|
50878
|
+
const found = config2.meshes.find((m) => m.slug === opts.meshSlug);
|
|
50879
|
+
if (!found) {
|
|
50880
|
+
console.error(`Mesh "${opts.meshSlug}" not found. Joined: ${config2.meshes.map((m) => m.slug).join(", ")}`);
|
|
50881
|
+
process.exit(1);
|
|
50882
|
+
}
|
|
50883
|
+
mesh = found;
|
|
50884
|
+
} else if (config2.meshes.length === 1) {
|
|
50885
|
+
mesh = config2.meshes[0];
|
|
50886
|
+
} else {
|
|
50887
|
+
console.error(`Multiple meshes joined. Specify one with --mesh <slug>.
|
|
50888
|
+
Joined: ${config2.meshes.map((m) => m.slug).join(", ")}`);
|
|
50889
|
+
process.exit(1);
|
|
50890
|
+
}
|
|
50891
|
+
const displayName = opts.displayName ?? config2.displayName ?? `${hostname3()}-${process.pid}`;
|
|
50892
|
+
const client = new BrokerClient(mesh, { displayName });
|
|
50893
|
+
try {
|
|
50894
|
+
await client.connect();
|
|
50895
|
+
const result = await fn(client, mesh);
|
|
50896
|
+
return result;
|
|
50897
|
+
} finally {
|
|
50898
|
+
client.close();
|
|
50899
|
+
}
|
|
50900
|
+
}
|
|
50901
|
+
|
|
50902
|
+
// src/commands/peers.ts
|
|
50903
|
+
async function runPeers(flags) {
|
|
50904
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
50905
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
50906
|
+
const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
|
|
50907
|
+
const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
|
|
50908
|
+
const yellow = (s) => useColor ? `\x1B[33m${s}\x1B[39m` : s;
|
|
50909
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
|
|
50910
|
+
const peers = await client.listPeers();
|
|
50911
|
+
if (flags.json) {
|
|
50912
|
+
console.log(JSON.stringify(peers, null, 2));
|
|
50913
|
+
return;
|
|
50914
|
+
}
|
|
50915
|
+
if (peers.length === 0) {
|
|
50916
|
+
console.log(dim(`No peers connected on mesh "${mesh.slug}".`));
|
|
50917
|
+
return;
|
|
50918
|
+
}
|
|
50919
|
+
console.log(bold2(`Peers on ${mesh.slug}`) + dim(` (${peers.length})`));
|
|
50920
|
+
console.log("");
|
|
50921
|
+
for (const p of peers) {
|
|
50922
|
+
const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
|
|
50923
|
+
const statusIcon = p.status === "working" ? yellow("●") : green("●");
|
|
50924
|
+
const name = bold2(p.displayName);
|
|
50925
|
+
const summary = p.summary ? dim(` ${p.summary}`) : "";
|
|
50926
|
+
console.log(` ${statusIcon} ${name}${groups}${summary}`);
|
|
50927
|
+
}
|
|
50928
|
+
console.log("");
|
|
50929
|
+
});
|
|
50930
|
+
}
|
|
50931
|
+
|
|
50932
|
+
// src/commands/send.ts
|
|
50933
|
+
async function runSend(flags, to, message) {
|
|
50934
|
+
const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
|
|
50935
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
50936
|
+
let targetSpec = to;
|
|
50937
|
+
if (!to.startsWith("@") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
|
|
50938
|
+
const peers = await client.listPeers();
|
|
50939
|
+
const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
|
|
50940
|
+
if (!match) {
|
|
50941
|
+
const names = peers.map((p) => p.displayName).join(", ");
|
|
50942
|
+
console.error(`Peer "${to}" not found. Online: ${names || "(none)"}`);
|
|
50943
|
+
process.exit(1);
|
|
50944
|
+
}
|
|
50945
|
+
targetSpec = match.pubkey;
|
|
50946
|
+
}
|
|
50947
|
+
const result = await client.send(targetSpec, message, priority);
|
|
50948
|
+
if (result.ok) {
|
|
50949
|
+
console.log(`✓ Sent to ${to}${result.messageId ? ` (${result.messageId.slice(0, 8)})` : ""}`);
|
|
50950
|
+
} else {
|
|
50951
|
+
console.error(`✗ Send failed: ${result.error ?? "unknown error"}`);
|
|
50952
|
+
process.exit(1);
|
|
50953
|
+
}
|
|
50954
|
+
});
|
|
50955
|
+
}
|
|
50956
|
+
|
|
50957
|
+
// src/commands/inbox.ts
|
|
50958
|
+
function formatMessage(msg, useColor) {
|
|
50959
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
50960
|
+
const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
|
|
50961
|
+
const text2 = msg.plaintext ?? `[encrypted: ${msg.ciphertext.slice(0, 32)}…]`;
|
|
50962
|
+
const from = msg.senderPubkey.slice(0, 8);
|
|
50963
|
+
const time3 = new Date(msg.createdAt).toLocaleTimeString();
|
|
50964
|
+
const kindTag = msg.kind === "direct" ? "→ direct" : msg.kind;
|
|
50965
|
+
return ` ${bold2(from)} ${dim(`[${kindTag}] ${time3}`)}
|
|
50966
|
+
${text2}`;
|
|
50967
|
+
}
|
|
50968
|
+
async function runInbox(flags) {
|
|
50969
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
50970
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
50971
|
+
const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
|
|
50972
|
+
const waitMs = (flags.wait ?? 1) * 1000;
|
|
50973
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
|
|
50974
|
+
await new Promise((resolve2) => setTimeout(resolve2, waitMs));
|
|
50975
|
+
const messages = client.drainPushBuffer();
|
|
50976
|
+
if (flags.json) {
|
|
50977
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
50978
|
+
return;
|
|
50979
|
+
}
|
|
50980
|
+
if (messages.length === 0) {
|
|
50981
|
+
console.log(dim(`No messages on mesh "${mesh.slug}".`));
|
|
50982
|
+
return;
|
|
50983
|
+
}
|
|
50984
|
+
console.log(bold2(`Inbox — ${mesh.slug}`) + dim(` (${messages.length} message${messages.length === 1 ? "" : "s"})`));
|
|
50985
|
+
console.log("");
|
|
50986
|
+
for (const msg of messages) {
|
|
50987
|
+
console.log(formatMessage(msg, useColor));
|
|
50988
|
+
console.log("");
|
|
50989
|
+
}
|
|
50990
|
+
});
|
|
50991
|
+
}
|
|
50992
|
+
|
|
50993
|
+
// src/commands/state.ts
|
|
50994
|
+
async function runStateGet(flags, key) {
|
|
50995
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
50996
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
50997
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
50998
|
+
const entry = await client.getState(key);
|
|
50999
|
+
if (!entry) {
|
|
51000
|
+
console.log(dim(`(not set)`));
|
|
51001
|
+
return;
|
|
51002
|
+
}
|
|
51003
|
+
if (flags.json) {
|
|
51004
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
51005
|
+
return;
|
|
51006
|
+
}
|
|
51007
|
+
const val = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value);
|
|
51008
|
+
console.log(val);
|
|
51009
|
+
console.log(dim(` set by ${entry.updatedBy} at ${new Date(entry.updatedAt).toLocaleString()}`));
|
|
51010
|
+
});
|
|
51011
|
+
}
|
|
51012
|
+
async function runStateSet(flags, key, value) {
|
|
51013
|
+
let parsed;
|
|
51014
|
+
try {
|
|
51015
|
+
parsed = JSON.parse(value);
|
|
51016
|
+
} catch {
|
|
51017
|
+
parsed = value;
|
|
51018
|
+
}
|
|
51019
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
51020
|
+
await client.setState(key, parsed);
|
|
51021
|
+
console.log(`✓ ${key} = ${JSON.stringify(parsed)}`);
|
|
51022
|
+
});
|
|
51023
|
+
}
|
|
51024
|
+
async function runStateList(flags) {
|
|
51025
|
+
const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
|
51026
|
+
const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
|
|
51027
|
+
const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
|
|
51028
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client, mesh) => {
|
|
51029
|
+
const entries = await client.listState();
|
|
51030
|
+
if (flags.json) {
|
|
51031
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
51032
|
+
return;
|
|
51033
|
+
}
|
|
51034
|
+
if (entries.length === 0) {
|
|
51035
|
+
console.log(dim(`No state on mesh "${mesh.slug}".`));
|
|
51036
|
+
return;
|
|
51037
|
+
}
|
|
51038
|
+
for (const e of entries) {
|
|
51039
|
+
const val = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
|
|
51040
|
+
console.log(`${bold2(e.key)}: ${val}`);
|
|
51041
|
+
console.log(dim(` ${e.updatedBy} · ${new Date(e.updatedAt).toLocaleString()}`));
|
|
51042
|
+
}
|
|
51043
|
+
});
|
|
51044
|
+
}
|
|
51045
|
+
|
|
50553
51046
|
// src/index.ts
|
|
50554
51047
|
var launch = defineCommand({
|
|
50555
51048
|
meta: {
|
|
@@ -50672,6 +51165,69 @@ var main = defineCommand({
|
|
|
50672
51165
|
}
|
|
50673
51166
|
}),
|
|
50674
51167
|
leave,
|
|
51168
|
+
peers: defineCommand({
|
|
51169
|
+
meta: { name: "peers", description: "List connected peers in the mesh" },
|
|
51170
|
+
args: {
|
|
51171
|
+
mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
|
|
51172
|
+
json: { type: "boolean", description: "Output as JSON", default: false }
|
|
51173
|
+
},
|
|
51174
|
+
async run({ args }) {
|
|
51175
|
+
await runPeers(args);
|
|
51176
|
+
}
|
|
51177
|
+
}),
|
|
51178
|
+
send: defineCommand({
|
|
51179
|
+
meta: { name: "send", description: "Send a message to a peer, group, or broadcast" },
|
|
51180
|
+
args: {
|
|
51181
|
+
to: { type: "positional", description: "Recipient: display name, @group, pubkey, or *", required: true },
|
|
51182
|
+
message: { type: "positional", description: "Message text", required: true },
|
|
51183
|
+
mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
|
|
51184
|
+
priority: { type: "string", description: "now | next (default) | low" }
|
|
51185
|
+
},
|
|
51186
|
+
async run({ args }) {
|
|
51187
|
+
await runSend(args, args.to, args.message);
|
|
51188
|
+
}
|
|
51189
|
+
}),
|
|
51190
|
+
inbox: defineCommand({
|
|
51191
|
+
meta: { name: "inbox", description: "Read pending peer messages" },
|
|
51192
|
+
args: {
|
|
51193
|
+
mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
|
|
51194
|
+
json: { type: "boolean", description: "Output as JSON", default: false },
|
|
51195
|
+
wait: { type: "string", description: "Seconds to wait for broker delivery (default: 1)" }
|
|
51196
|
+
},
|
|
51197
|
+
async run({ args }) {
|
|
51198
|
+
await runInbox({ ...args, wait: args.wait ? parseInt(args.wait, 10) : undefined });
|
|
51199
|
+
}
|
|
51200
|
+
}),
|
|
51201
|
+
state: defineCommand({
|
|
51202
|
+
meta: { name: "state", description: "Read or write shared mesh state" },
|
|
51203
|
+
args: {
|
|
51204
|
+
action: { type: "positional", description: "get | set | list", required: true },
|
|
51205
|
+
key: { type: "positional", description: "State key (required for get/set)" },
|
|
51206
|
+
value: { type: "positional", description: "Value to set (required for set)" },
|
|
51207
|
+
mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
|
|
51208
|
+
json: { type: "boolean", description: "Output as JSON", default: false }
|
|
51209
|
+
},
|
|
51210
|
+
async run({ args }) {
|
|
51211
|
+
if (args.action === "list") {
|
|
51212
|
+
await runStateList(args);
|
|
51213
|
+
} else if (args.action === "get") {
|
|
51214
|
+
if (!args.key) {
|
|
51215
|
+
console.error("Usage: claudemesh state get <key>");
|
|
51216
|
+
process.exit(1);
|
|
51217
|
+
}
|
|
51218
|
+
await runStateGet(args, args.key);
|
|
51219
|
+
} else if (args.action === "set") {
|
|
51220
|
+
if (!args.key || !args.value) {
|
|
51221
|
+
console.error("Usage: claudemesh state set <key> <value>");
|
|
51222
|
+
process.exit(1);
|
|
51223
|
+
}
|
|
51224
|
+
await runStateSet(args, args.key, args.value);
|
|
51225
|
+
} else {
|
|
51226
|
+
console.error(`Unknown action "${args.action}". Use: get, set, list`);
|
|
51227
|
+
process.exit(1);
|
|
51228
|
+
}
|
|
51229
|
+
}
|
|
51230
|
+
}),
|
|
50675
51231
|
status: defineCommand({
|
|
50676
51232
|
meta: { name: "status", description: "Check broker reachability for each joined mesh" },
|
|
50677
51233
|
async run() {
|