claudemesh-cli 1.7.0 → 1.8.0
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/README.md +5 -2
- package/dist/entrypoints/cli.js +278 -14
- package/dist/entrypoints/cli.js.map +7 -5
- package/dist/entrypoints/mcp.js +2 -2
- package/dist/entrypoints/mcp.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Peer mesh for Claude Code sessions. Connect multiple Claude Code instances into a shared mesh with real-time messaging, shared state, memory, file sharing, vector store, scheduled jobs, and more — all driven from the `claudemesh` CLI. The MCP server is a tool-less push-pipe that delivers inbound peer messages to Claude as `<channel>` interrupts; everything else lives behind CLI verbs that Claude learns from the auto-installed `claudemesh` skill.
|
|
4
4
|
|
|
5
|
-
> **What's new in 1.
|
|
5
|
+
> **What's new in 1.8.0:** per-topic end-to-end encryption (v0.3.0 phase 3, CLI side). `claudemesh topic post <topic> <msg>` encrypts the body with `crypto_secretbox` under the topic's symmetric key — broker stores ciphertext only. `claudemesh topic tail` now decrypts v2 messages on render and runs a background re-seal loop every 30s, so new topic joiners get their sealed keys without manual action. `topic-key` cache is process-only — kill the CLI, the key forgets. Web dashboard reads v1 plaintext for now (phase 3.5 brings browser-side identity).
|
|
6
|
+
>
|
|
7
|
+
> **What was new in 1.7.0:** terminal parity for the v1.6.x server features. New verbs: `claudemesh topic tail` (live SSE message stream — Ctrl-C to exit), `claudemesh notification list` (recent `@you` mentions across topics), `claudemesh member list` (mesh roster with online dots, distinct from `peer list`'s live-session view). Each command auto-mints a 5-minute read-only apikey via the WebSocket and revokes it on exit, so no token plumbing is needed.
|
|
6
8
|
>
|
|
7
9
|
> **What was new in 1.6.0:** topics (channel pub/sub), API keys for human/REST clients, and bridge peers that forward a topic between two meshes. New verbs: `claudemesh topic`, `claudemesh apikey`, `claudemesh bridge`. A REST surface at `https://claudemesh.com/api/v1/*` (messages, topics, peers, history) accepts `Authorization: Bearer cm_...` keys, so any HTTPS client can participate without WebSocket + ed25519 plumbing. **Note**: REST lives on the web host (`claudemesh.com`), not the broker host (`ic.claudemesh.com`) — the broker only speaks WebSocket.
|
|
8
10
|
>
|
|
@@ -45,7 +47,8 @@ USAGE
|
|
|
45
47
|
claudemesh profile view or edit your profile
|
|
46
48
|
|
|
47
49
|
claudemesh topic ... create, list, join, send to topics
|
|
48
|
-
claudemesh topic tail <t> live SSE tail of a topic
|
|
50
|
+
claudemesh topic tail <t> live SSE tail of a topic (decrypts v2)
|
|
51
|
+
claudemesh topic post <t> encrypted REST post (v2 ciphertext)
|
|
49
52
|
claudemesh member list mesh roster with online state
|
|
50
53
|
claudemesh notification list recent @-mentions of you
|
|
51
54
|
claudemesh apikey ... issue, list, revoke API keys (REST clients)
|
package/dist/entrypoints/cli.js
CHANGED
|
@@ -88,7 +88,7 @@ __export(exports_urls, {
|
|
|
88
88
|
VERSION: () => VERSION,
|
|
89
89
|
URLS: () => URLS
|
|
90
90
|
});
|
|
91
|
-
var URLS, VERSION = "1.
|
|
91
|
+
var URLS, VERSION = "1.8.0", env;
|
|
92
92
|
var init_urls = __esm(() => {
|
|
93
93
|
URLS = {
|
|
94
94
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -11679,18 +11679,140 @@ var init_with_rest_key = __esm(() => {
|
|
|
11679
11679
|
init_connect();
|
|
11680
11680
|
});
|
|
11681
11681
|
|
|
11682
|
+
// src/services/crypto/topic-key.ts
|
|
11683
|
+
function cacheKey(apiKeySecret, topicName) {
|
|
11684
|
+
return `${apiKeySecret.slice(0, 12)}:${topicName}`;
|
|
11685
|
+
}
|
|
11686
|
+
async function getTopicKey(args) {
|
|
11687
|
+
const cacheId = cacheKey(args.apiKeySecret, args.topicName);
|
|
11688
|
+
if (!args.fresh) {
|
|
11689
|
+
const cached = cache.get(cacheId);
|
|
11690
|
+
if (cached)
|
|
11691
|
+
return { ok: true, topicKey: cached.topicKey };
|
|
11692
|
+
}
|
|
11693
|
+
let sealed;
|
|
11694
|
+
try {
|
|
11695
|
+
sealed = await request({
|
|
11696
|
+
path: `/api/v1/topics/${encodeURIComponent(args.topicName)}/key`,
|
|
11697
|
+
token: args.apiKeySecret
|
|
11698
|
+
});
|
|
11699
|
+
} catch (e) {
|
|
11700
|
+
if (e instanceof ApiError) {
|
|
11701
|
+
if (e.status === 404)
|
|
11702
|
+
return { ok: false, error: "not_sealed" };
|
|
11703
|
+
if (e.status === 409)
|
|
11704
|
+
return { ok: false, error: "topic_unencrypted" };
|
|
11705
|
+
}
|
|
11706
|
+
return {
|
|
11707
|
+
ok: false,
|
|
11708
|
+
error: "network",
|
|
11709
|
+
message: e instanceof Error ? e.message : String(e)
|
|
11710
|
+
};
|
|
11711
|
+
}
|
|
11712
|
+
const sodium4 = (await import("libsodium-wrappers")).default;
|
|
11713
|
+
await sodium4.ready;
|
|
11714
|
+
let recipientX25519Secret;
|
|
11715
|
+
try {
|
|
11716
|
+
const ed = sodium4.from_hex(args.memberSecretKeyHex);
|
|
11717
|
+
recipientX25519Secret = sodium4.crypto_sign_ed25519_sk_to_curve25519(ed);
|
|
11718
|
+
} catch {
|
|
11719
|
+
return { ok: false, error: "bad_member_secret" };
|
|
11720
|
+
}
|
|
11721
|
+
let topicKey;
|
|
11722
|
+
try {
|
|
11723
|
+
const blob = sodium4.from_base64(sealed.encryptedKey, sodium4.base64_variants.ORIGINAL);
|
|
11724
|
+
const nonce = sodium4.from_base64(sealed.nonce, sodium4.base64_variants.ORIGINAL);
|
|
11725
|
+
if (blob.length < 32 + sodium4.crypto_box_MACBYTES) {
|
|
11726
|
+
return {
|
|
11727
|
+
ok: false,
|
|
11728
|
+
error: "decrypt_failed",
|
|
11729
|
+
message: "sealed key blob too short to contain sender pubkey + cipher"
|
|
11730
|
+
};
|
|
11731
|
+
}
|
|
11732
|
+
const senderX25519 = blob.slice(0, 32);
|
|
11733
|
+
const cipher = blob.slice(32);
|
|
11734
|
+
topicKey = sodium4.crypto_box_open_easy(cipher, nonce, senderX25519, recipientX25519Secret);
|
|
11735
|
+
} catch (e) {
|
|
11736
|
+
return {
|
|
11737
|
+
ok: false,
|
|
11738
|
+
error: "decrypt_failed",
|
|
11739
|
+
message: e instanceof Error ? e.message : String(e)
|
|
11740
|
+
};
|
|
11741
|
+
}
|
|
11742
|
+
cache.set(cacheId, { topicKey, fetchedAt: Date.now() });
|
|
11743
|
+
return { ok: true, topicKey };
|
|
11744
|
+
}
|
|
11745
|
+
async function encryptMessage(topicKey, plaintext) {
|
|
11746
|
+
const sodium4 = (await import("libsodium-wrappers")).default;
|
|
11747
|
+
await sodium4.ready;
|
|
11748
|
+
const nonceBytes = sodium4.randombytes_buf(sodium4.crypto_secretbox_NONCEBYTES);
|
|
11749
|
+
const cipher = sodium4.crypto_secretbox_easy(sodium4.from_string(plaintext), nonceBytes, topicKey);
|
|
11750
|
+
return {
|
|
11751
|
+
ciphertext: sodium4.to_base64(cipher, sodium4.base64_variants.ORIGINAL),
|
|
11752
|
+
nonce: sodium4.to_base64(nonceBytes, sodium4.base64_variants.ORIGINAL)
|
|
11753
|
+
};
|
|
11754
|
+
}
|
|
11755
|
+
async function decryptMessage(topicKey, ciphertextB64, nonceB64) {
|
|
11756
|
+
try {
|
|
11757
|
+
const sodium4 = (await import("libsodium-wrappers")).default;
|
|
11758
|
+
await sodium4.ready;
|
|
11759
|
+
const cipher = sodium4.from_base64(ciphertextB64, sodium4.base64_variants.ORIGINAL);
|
|
11760
|
+
const nonce = sodium4.from_base64(nonceB64, sodium4.base64_variants.ORIGINAL);
|
|
11761
|
+
const plain = sodium4.crypto_secretbox_open_easy(cipher, nonce, topicKey);
|
|
11762
|
+
return sodium4.to_string(plain);
|
|
11763
|
+
} catch {
|
|
11764
|
+
return null;
|
|
11765
|
+
}
|
|
11766
|
+
}
|
|
11767
|
+
async function sealTopicKeyFor(topicKey, recipientPubkeyHex, ourMemberSecretKeyHex) {
|
|
11768
|
+
try {
|
|
11769
|
+
const sodium4 = (await import("libsodium-wrappers")).default;
|
|
11770
|
+
await sodium4.ready;
|
|
11771
|
+
const recipientX25519 = sodium4.crypto_sign_ed25519_pk_to_curve25519(sodium4.from_hex(recipientPubkeyHex));
|
|
11772
|
+
const ourEdSecret = sodium4.from_hex(ourMemberSecretKeyHex);
|
|
11773
|
+
const ourX25519Secret = sodium4.crypto_sign_ed25519_sk_to_curve25519(ourEdSecret);
|
|
11774
|
+
const ourEdPublic = ourEdSecret.slice(32, 64);
|
|
11775
|
+
const ourX25519Public = sodium4.crypto_sign_ed25519_pk_to_curve25519(ourEdPublic);
|
|
11776
|
+
const nonceBytes = sodium4.randombytes_buf(sodium4.crypto_box_NONCEBYTES);
|
|
11777
|
+
const cipher = sodium4.crypto_box_easy(topicKey, nonceBytes, recipientX25519, ourX25519Secret);
|
|
11778
|
+
const blob = new Uint8Array(32 + cipher.length);
|
|
11779
|
+
blob.set(ourX25519Public, 0);
|
|
11780
|
+
blob.set(cipher, 32);
|
|
11781
|
+
return {
|
|
11782
|
+
encryptedKey: sodium4.to_base64(blob, sodium4.base64_variants.ORIGINAL),
|
|
11783
|
+
nonce: sodium4.to_base64(nonceBytes, sodium4.base64_variants.ORIGINAL)
|
|
11784
|
+
};
|
|
11785
|
+
} catch {
|
|
11786
|
+
return null;
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
var cache;
|
|
11790
|
+
var init_topic_key = __esm(() => {
|
|
11791
|
+
init_client();
|
|
11792
|
+
init_errors();
|
|
11793
|
+
cache = new Map;
|
|
11794
|
+
});
|
|
11795
|
+
|
|
11682
11796
|
// src/commands/topic-tail.ts
|
|
11683
11797
|
var exports_topic_tail = {};
|
|
11684
11798
|
__export(exports_topic_tail, {
|
|
11685
11799
|
runTopicTail: () => runTopicTail
|
|
11686
11800
|
});
|
|
11687
|
-
function
|
|
11801
|
+
function decodeV1(b64) {
|
|
11688
11802
|
try {
|
|
11689
11803
|
return Buffer.from(b64, "base64").toString("utf-8");
|
|
11690
11804
|
} catch {
|
|
11691
11805
|
return "[decode failed]";
|
|
11692
11806
|
}
|
|
11693
11807
|
}
|
|
11808
|
+
async function decryptForRender(m, topicKey) {
|
|
11809
|
+
if ((m.bodyVersion ?? 1) === 1)
|
|
11810
|
+
return decodeV1(m.ciphertext);
|
|
11811
|
+
if (!topicKey)
|
|
11812
|
+
return "[encrypted — no topic key]";
|
|
11813
|
+
const plain = await decryptMessage(topicKey, m.ciphertext, m.nonce);
|
|
11814
|
+
return plain ?? "[decrypt failed]";
|
|
11815
|
+
}
|
|
11694
11816
|
function fmtTime(iso) {
|
|
11695
11817
|
try {
|
|
11696
11818
|
return new Date(iso).toLocaleTimeString([], {
|
|
@@ -11702,13 +11824,14 @@ function fmtTime(iso) {
|
|
|
11702
11824
|
return iso;
|
|
11703
11825
|
}
|
|
11704
11826
|
}
|
|
11705
|
-
function printMessage(m, json) {
|
|
11706
|
-
const text =
|
|
11827
|
+
async function printMessage(m, topicKey, json) {
|
|
11828
|
+
const text = await decryptForRender(m, topicKey);
|
|
11707
11829
|
if (json) {
|
|
11708
11830
|
console.log(JSON.stringify({ ...m, message: text }));
|
|
11709
11831
|
return;
|
|
11710
11832
|
}
|
|
11711
|
-
|
|
11833
|
+
const v2Marker = (m.bodyVersion ?? 1) === 2 ? dim("\uD83D\uDD12 ") : "";
|
|
11834
|
+
process.stdout.write(` ${dim(fmtTime(m.createdAt))} ${bold(m.senderName || m.senderPubkey.slice(0, 8))} ${v2Marker}${text}
|
|
11712
11835
|
`);
|
|
11713
11836
|
}
|
|
11714
11837
|
async function* readSseStream(reader) {
|
|
@@ -11761,7 +11884,55 @@ async function runTopicTail(name, flags) {
|
|
|
11761
11884
|
purpose: `tail-${cleanName}`,
|
|
11762
11885
|
capabilities: ["read"],
|
|
11763
11886
|
topicScopes: [cleanName]
|
|
11764
|
-
}, async ({ secret, meshSlug }) => {
|
|
11887
|
+
}, async ({ secret, meshSlug, mesh }) => {
|
|
11888
|
+
const keyResult = await getTopicKey({
|
|
11889
|
+
apiKeySecret: secret,
|
|
11890
|
+
memberSecretKeyHex: mesh.secretKey,
|
|
11891
|
+
topicName: cleanName
|
|
11892
|
+
});
|
|
11893
|
+
const topicKey = keyResult.ok ? keyResult.topicKey ?? null : null;
|
|
11894
|
+
let resealTimer = null;
|
|
11895
|
+
if (topicKey) {
|
|
11896
|
+
const reseal = async () => {
|
|
11897
|
+
try {
|
|
11898
|
+
const pending = await request({
|
|
11899
|
+
path: `/api/v1/topics/${encodeURIComponent(cleanName)}/pending-seals`,
|
|
11900
|
+
token: secret
|
|
11901
|
+
});
|
|
11902
|
+
for (const target of pending.pending) {
|
|
11903
|
+
const sealed = await sealTopicKeyFor(topicKey, target.pubkey, mesh.secretKey);
|
|
11904
|
+
if (!sealed)
|
|
11905
|
+
continue;
|
|
11906
|
+
try {
|
|
11907
|
+
await request({
|
|
11908
|
+
path: `/api/v1/topics/${encodeURIComponent(cleanName)}/seal`,
|
|
11909
|
+
method: "POST",
|
|
11910
|
+
token: secret,
|
|
11911
|
+
body: {
|
|
11912
|
+
memberId: target.memberId,
|
|
11913
|
+
encryptedKey: sealed.encryptedKey,
|
|
11914
|
+
nonce: sealed.nonce
|
|
11915
|
+
}
|
|
11916
|
+
});
|
|
11917
|
+
if (!flags.json) {
|
|
11918
|
+
render.info(dim(`re-sealed topic key for ${target.displayName}`));
|
|
11919
|
+
}
|
|
11920
|
+
} catch {}
|
|
11921
|
+
}
|
|
11922
|
+
} catch {}
|
|
11923
|
+
};
|
|
11924
|
+
reseal();
|
|
11925
|
+
resealTimer = setInterval(reseal, 30000);
|
|
11926
|
+
}
|
|
11927
|
+
if (!flags.json && !keyResult.ok) {
|
|
11928
|
+
if (keyResult.error === "topic_unencrypted") {
|
|
11929
|
+
render.info(dim("topic is on v1 (plaintext) — encryption will activate after creator-seal"));
|
|
11930
|
+
} else if (keyResult.error === "not_sealed") {
|
|
11931
|
+
render.warn(yellow("no topic key sealed for you yet — wait for a holder to re-seal"));
|
|
11932
|
+
} else if (keyResult.error === "decrypt_failed") {
|
|
11933
|
+
render.warn(yellow(`topic key fetched but decrypt failed: ${keyResult.message ?? ""}`));
|
|
11934
|
+
}
|
|
11935
|
+
}
|
|
11765
11936
|
if (!flags.forwardOnly && limit > 0) {
|
|
11766
11937
|
try {
|
|
11767
11938
|
const history = await request({
|
|
@@ -11772,7 +11943,7 @@ async function runTopicTail(name, flags) {
|
|
|
11772
11943
|
render.section(`${clay("#" + cleanName)} on ${dim(meshSlug)} — backfill ${history.messages.length}, then live`);
|
|
11773
11944
|
}
|
|
11774
11945
|
for (const m of history.messages.slice().reverse()) {
|
|
11775
|
-
printMessage(m, flags.json ?? false);
|
|
11946
|
+
await printMessage(m, topicKey, flags.json ?? false);
|
|
11776
11947
|
}
|
|
11777
11948
|
} catch (err) {
|
|
11778
11949
|
render.warn(`backfill failed: ${err.message}`);
|
|
@@ -11811,7 +11982,7 @@ async function runTopicTail(name, flags) {
|
|
|
11811
11982
|
if (ev.event === "message") {
|
|
11812
11983
|
try {
|
|
11813
11984
|
const m = JSON.parse(ev.data);
|
|
11814
|
-
printMessage(m, flags.json ?? false);
|
|
11985
|
+
await printMessage(m, topicKey, flags.json ?? false);
|
|
11815
11986
|
} catch {}
|
|
11816
11987
|
}
|
|
11817
11988
|
}
|
|
@@ -11824,6 +11995,8 @@ async function runTopicTail(name, flags) {
|
|
|
11824
11995
|
} finally {
|
|
11825
11996
|
process.removeListener("SIGINT", onSig);
|
|
11826
11997
|
process.removeListener("SIGTERM", onSig);
|
|
11998
|
+
if (resealTimer)
|
|
11999
|
+
clearInterval(resealTimer);
|
|
11827
12000
|
}
|
|
11828
12001
|
});
|
|
11829
12002
|
}
|
|
@@ -11831,6 +12004,87 @@ var init_topic_tail = __esm(() => {
|
|
|
11831
12004
|
init_urls();
|
|
11832
12005
|
init_with_rest_key();
|
|
11833
12006
|
init_client();
|
|
12007
|
+
init_topic_key();
|
|
12008
|
+
init_render();
|
|
12009
|
+
init_styles();
|
|
12010
|
+
init_exit_codes();
|
|
12011
|
+
});
|
|
12012
|
+
|
|
12013
|
+
// src/commands/topic-post.ts
|
|
12014
|
+
var exports_topic_post = {};
|
|
12015
|
+
__export(exports_topic_post, {
|
|
12016
|
+
runTopicPost: () => runTopicPost
|
|
12017
|
+
});
|
|
12018
|
+
async function runTopicPost(topicName, message, flags) {
|
|
12019
|
+
if (!topicName || !message) {
|
|
12020
|
+
render.err("Usage: claudemesh topic post <topic> <message>");
|
|
12021
|
+
return EXIT.INVALID_ARGS;
|
|
12022
|
+
}
|
|
12023
|
+
const cleanName = topicName.replace(/^#/, "");
|
|
12024
|
+
const mentions = [];
|
|
12025
|
+
const mentionRe = /(^|[^A-Za-z0-9_-])@([A-Za-z0-9_-]{1,64})(?=$|[^A-Za-z0-9_-])/g;
|
|
12026
|
+
let m;
|
|
12027
|
+
while ((m = mentionRe.exec(message)) !== null) {
|
|
12028
|
+
mentions.push(m[2].toLowerCase());
|
|
12029
|
+
if (mentions.length >= 16)
|
|
12030
|
+
break;
|
|
12031
|
+
}
|
|
12032
|
+
return withRestKey({
|
|
12033
|
+
meshSlug: flags.mesh ?? null,
|
|
12034
|
+
purpose: `post-${cleanName}`,
|
|
12035
|
+
capabilities: ["read", "send"],
|
|
12036
|
+
topicScopes: [cleanName]
|
|
12037
|
+
}, async ({ secret, mesh }) => {
|
|
12038
|
+
let bodyVersion = 1;
|
|
12039
|
+
let ciphertext;
|
|
12040
|
+
let nonce;
|
|
12041
|
+
if (flags.plaintext) {
|
|
12042
|
+
ciphertext = Buffer.from(message, "utf-8").toString("base64");
|
|
12043
|
+
nonce = Buffer.from(new Uint8Array(24)).toString("base64");
|
|
12044
|
+
} else {
|
|
12045
|
+
const keyResult = await getTopicKey({
|
|
12046
|
+
apiKeySecret: secret,
|
|
12047
|
+
memberSecretKeyHex: mesh.secretKey,
|
|
12048
|
+
topicName: cleanName
|
|
12049
|
+
});
|
|
12050
|
+
if (keyResult.ok && keyResult.topicKey) {
|
|
12051
|
+
const enc = await encryptMessage(keyResult.topicKey, message);
|
|
12052
|
+
ciphertext = enc.ciphertext;
|
|
12053
|
+
nonce = enc.nonce;
|
|
12054
|
+
bodyVersion = 2;
|
|
12055
|
+
} else if (keyResult.error === "topic_unencrypted") {
|
|
12056
|
+
ciphertext = Buffer.from(message, "utf-8").toString("base64");
|
|
12057
|
+
nonce = Buffer.from(new Uint8Array(24)).toString("base64");
|
|
12058
|
+
} else {
|
|
12059
|
+
render.err(`cannot encrypt for #${cleanName}: ${keyResult.error ?? "unknown"}${keyResult.message ? " — " + keyResult.message : ""}`);
|
|
12060
|
+
return EXIT.INTERNAL_ERROR;
|
|
12061
|
+
}
|
|
12062
|
+
}
|
|
12063
|
+
const result = await request({
|
|
12064
|
+
path: "/api/v1/messages",
|
|
12065
|
+
method: "POST",
|
|
12066
|
+
token: secret,
|
|
12067
|
+
body: {
|
|
12068
|
+
topic: cleanName,
|
|
12069
|
+
ciphertext,
|
|
12070
|
+
nonce,
|
|
12071
|
+
bodyVersion,
|
|
12072
|
+
...mentions.length > 0 ? { mentions } : {}
|
|
12073
|
+
}
|
|
12074
|
+
});
|
|
12075
|
+
if (flags.json) {
|
|
12076
|
+
console.log(JSON.stringify({ ...result, bodyVersion, mentions }));
|
|
12077
|
+
return EXIT.SUCCESS;
|
|
12078
|
+
}
|
|
12079
|
+
const versionTag = bodyVersion === 2 ? green("\uD83D\uDD12 v2") : dim("v1");
|
|
12080
|
+
render.ok("posted", `${clay("#" + cleanName)} ${versionTag} ${dim(`(${result.notifications} mentions)`)}`);
|
|
12081
|
+
return EXIT.SUCCESS;
|
|
12082
|
+
});
|
|
12083
|
+
}
|
|
12084
|
+
var init_topic_post = __esm(() => {
|
|
12085
|
+
init_with_rest_key();
|
|
12086
|
+
init_client();
|
|
12087
|
+
init_topic_key();
|
|
11834
12088
|
init_render();
|
|
11835
12089
|
init_styles();
|
|
11836
12090
|
init_exit_codes();
|
|
@@ -11841,7 +12095,7 @@ var exports_notification = {};
|
|
|
11841
12095
|
__export(exports_notification, {
|
|
11842
12096
|
runNotificationList: () => runNotificationList
|
|
11843
12097
|
});
|
|
11844
|
-
function
|
|
12098
|
+
function decodeCiphertext(b64) {
|
|
11845
12099
|
try {
|
|
11846
12100
|
return Buffer.from(b64, "base64").toString("utf-8");
|
|
11847
12101
|
} catch {
|
|
@@ -11868,7 +12122,7 @@ async function runNotificationList(flags) {
|
|
|
11868
12122
|
if (flags.json) {
|
|
11869
12123
|
const decoded = result.notifications.map((n) => ({
|
|
11870
12124
|
...n,
|
|
11871
|
-
message:
|
|
12125
|
+
message: decodeCiphertext(n.ciphertext)
|
|
11872
12126
|
}));
|
|
11873
12127
|
console.log(JSON.stringify({ ...result, notifications: decoded }, null, 2));
|
|
11874
12128
|
return EXIT.SUCCESS;
|
|
@@ -11880,7 +12134,7 @@ async function runNotificationList(flags) {
|
|
|
11880
12134
|
render.section(`mentions of @${bold(result.mentionedAs)} (${result.notifications.length})`);
|
|
11881
12135
|
for (const n of result.notifications) {
|
|
11882
12136
|
const when = fmtRelative(n.createdAt);
|
|
11883
|
-
const msg =
|
|
12137
|
+
const msg = decodeCiphertext(n.ciphertext).replace(/\s+/g, " ").trim();
|
|
11884
12138
|
const snippet = msg.length > 100 ? msg.slice(0, 97) + "…" : msg;
|
|
11885
12139
|
process.stdout.write(` ${clay("#" + n.topicName)} ${dim(when)} ${bold(n.senderName)}
|
|
11886
12140
|
`);
|
|
@@ -13518,7 +13772,8 @@ Topic (conversation scope, v0.2.0)
|
|
|
13518
13772
|
claudemesh topic history <t> fetch message history [--limit --before]
|
|
13519
13773
|
claudemesh topic read <topic> mark all as read
|
|
13520
13774
|
claudemesh topic tail <topic> live SSE tail [--limit --forward-only]
|
|
13521
|
-
claudemesh
|
|
13775
|
+
claudemesh topic post <t> <msg> encrypted REST post (v0.3.0 v2)
|
|
13776
|
+
claudemesh send "#topic" "msg" send to a topic (WS path, v1 plaintext)
|
|
13522
13777
|
claudemesh member list mesh roster with online state [--online]
|
|
13523
13778
|
claudemesh notification list recent @-mentions of you [--since <ISO>]
|
|
13524
13779
|
|
|
@@ -14308,8 +14563,17 @@ async function main() {
|
|
|
14308
14563
|
};
|
|
14309
14564
|
const { runTopicTail: runTopicTail2 } = await Promise.resolve().then(() => (init_topic_tail(), exports_topic_tail));
|
|
14310
14565
|
process.exit(await runTopicTail2(arg, tailFlags));
|
|
14566
|
+
} else if (sub === "post") {
|
|
14567
|
+
const postFlags = {
|
|
14568
|
+
mesh: flags.mesh,
|
|
14569
|
+
json: !!flags.json,
|
|
14570
|
+
plaintext: !!flags.plaintext
|
|
14571
|
+
};
|
|
14572
|
+
const message = positionals.slice(2).join(" ");
|
|
14573
|
+
const { runTopicPost: runTopicPost2 } = await Promise.resolve().then(() => (init_topic_post(), exports_topic_post));
|
|
14574
|
+
process.exit(await runTopicPost2(arg, message, postFlags));
|
|
14311
14575
|
} else {
|
|
14312
|
-
console.error("Usage: claudemesh topic <create|list|join|leave|members|history|read|tail>");
|
|
14576
|
+
console.error("Usage: claudemesh topic <create|list|join|leave|members|history|read|tail|post>");
|
|
14313
14577
|
process.exit(EXIT.INVALID_ARGS);
|
|
14314
14578
|
}
|
|
14315
14579
|
break;
|
|
@@ -14396,4 +14660,4 @@ main().catch((err) => {
|
|
|
14396
14660
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
14397
14661
|
});
|
|
14398
14662
|
|
|
14399
|
-
//# debugId=
|
|
14663
|
+
//# debugId=25484051F3F24E9464756E2164756E21
|