claudemesh-cli 1.8.0 → 1.9.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/README.md +3 -1
- package/dist/entrypoints/cli.js +79 -12
- package/dist/entrypoints/cli.js.map +7 -7
- package/dist/entrypoints/mcp.js +25 -4
- package/dist/entrypoints/mcp.js.map +4 -4
- package/package.json +1 -1
- package/skills/claudemesh/SKILL.md +71 -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.9.x:** topic threading + multi-session reliability fixes. `claudemesh topic post <topic> <msg> --reply-to <id>` threads a reply onto a previous topic message (full id or 8+ char prefix); `topic tail` renders `↳ in reply to <name>: "<snippet>"` above replies and shows a copyable `#xxxxxxxx` short id on every row. `<channel>` MCP attrs now carry `from_member_id`, `from_pubkey` (stable), `from_session_pubkey` (ephemeral), `message_id`, `topic`, `reply_to_id` — everything the recipient needs to reply directly. Broker fixes (v0.3.2): replies to a stale session pubkey now resolve to the owning member's live session instead of bouncing with "not online", and broadcast `*` no longer loopbacks decrypt-fail warnings to the sender's sibling sessions.
|
|
6
|
+
>
|
|
7
|
+
> **What was 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
8
|
>
|
|
7
9
|
> **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.
|
|
8
10
|
>
|
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.9.2", env;
|
|
92
92
|
var init_urls = __esm(() => {
|
|
93
93
|
URLS = {
|
|
94
94
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -2779,6 +2779,11 @@ class BrokerClient {
|
|
|
2779
2779
|
messageId: String(msg.messageId ?? ""),
|
|
2780
2780
|
meshId: String(msg.meshId ?? ""),
|
|
2781
2781
|
senderPubkey,
|
|
2782
|
+
...msg.senderMemberPubkey ? { senderMemberPubkey: String(msg.senderMemberPubkey) } : {},
|
|
2783
|
+
...msg.senderMemberId ? { senderMemberId: String(msg.senderMemberId) } : {},
|
|
2784
|
+
...msg.senderName ? { senderName: String(msg.senderName) } : {},
|
|
2785
|
+
...msg.topic ? { topic: String(msg.topic) } : {},
|
|
2786
|
+
...msg.replyToId ? { replyToId: String(msg.replyToId) } : {},
|
|
2782
2787
|
priority: msg.priority ?? "next",
|
|
2783
2788
|
nonce,
|
|
2784
2789
|
ciphertext,
|
|
@@ -11798,6 +11803,17 @@ var exports_topic_tail = {};
|
|
|
11798
11803
|
__export(exports_topic_tail, {
|
|
11799
11804
|
runTopicTail: () => runTopicTail
|
|
11800
11805
|
});
|
|
11806
|
+
function rememberRendered(cache2, m, text) {
|
|
11807
|
+
cache2.set(m.id, {
|
|
11808
|
+
name: m.senderName || m.senderPubkey.slice(0, 8),
|
|
11809
|
+
snippet: text.replace(/\s+/g, " ").slice(0, 60)
|
|
11810
|
+
});
|
|
11811
|
+
if (cache2.size > RECENT_CACHE_MAX) {
|
|
11812
|
+
const firstKey = cache2.keys().next().value;
|
|
11813
|
+
if (firstKey)
|
|
11814
|
+
cache2.delete(firstKey);
|
|
11815
|
+
}
|
|
11816
|
+
}
|
|
11801
11817
|
function decodeV1(b64) {
|
|
11802
11818
|
try {
|
|
11803
11819
|
return Buffer.from(b64, "base64").toString("utf-8");
|
|
@@ -11824,15 +11840,24 @@ function fmtTime(iso) {
|
|
|
11824
11840
|
return iso;
|
|
11825
11841
|
}
|
|
11826
11842
|
}
|
|
11827
|
-
async function printMessage(m, topicKey, json) {
|
|
11843
|
+
async function printMessage(m, topicKey, json, cache2) {
|
|
11828
11844
|
const text = await decryptForRender(m, topicKey);
|
|
11829
11845
|
if (json) {
|
|
11830
11846
|
console.log(JSON.stringify({ ...m, message: text }));
|
|
11847
|
+
rememberRendered(cache2, m, text);
|
|
11831
11848
|
return;
|
|
11832
11849
|
}
|
|
11833
11850
|
const v2Marker = (m.bodyVersion ?? 1) === 2 ? dim("\uD83D\uDD12 ") : "";
|
|
11834
|
-
|
|
11851
|
+
if (m.replyToId) {
|
|
11852
|
+
const parent = cache2.get(m.replyToId);
|
|
11853
|
+
const ref = parent ? `${parent.name}: "${parent.snippet}${parent.snippet.length === 60 ? "…" : ""}"` : `${m.replyToId.slice(0, 8)}…`;
|
|
11854
|
+
process.stdout.write(` ${dim("↳ in reply to " + ref)}
|
|
11835
11855
|
`);
|
|
11856
|
+
}
|
|
11857
|
+
const idTag = dim(`#${m.id.slice(0, 8)}`);
|
|
11858
|
+
process.stdout.write(` ${dim(fmtTime(m.createdAt))} ${bold(m.senderName || m.senderPubkey.slice(0, 8))} ${idTag} ${v2Marker}${text}
|
|
11859
|
+
`);
|
|
11860
|
+
rememberRendered(cache2, m, text);
|
|
11836
11861
|
}
|
|
11837
11862
|
async function* readSseStream(reader) {
|
|
11838
11863
|
const decoder = new TextDecoder;
|
|
@@ -11891,6 +11916,7 @@ async function runTopicTail(name, flags) {
|
|
|
11891
11916
|
topicName: cleanName
|
|
11892
11917
|
});
|
|
11893
11918
|
const topicKey = keyResult.ok ? keyResult.topicKey ?? null : null;
|
|
11919
|
+
const snippetCache = new Map;
|
|
11894
11920
|
let resealTimer = null;
|
|
11895
11921
|
if (topicKey) {
|
|
11896
11922
|
const reseal = async () => {
|
|
@@ -11943,7 +11969,7 @@ async function runTopicTail(name, flags) {
|
|
|
11943
11969
|
render.section(`${clay("#" + cleanName)} on ${dim(meshSlug)} — backfill ${history.messages.length}, then live`);
|
|
11944
11970
|
}
|
|
11945
11971
|
for (const m of history.messages.slice().reverse()) {
|
|
11946
|
-
await printMessage(m, topicKey, flags.json ?? false);
|
|
11972
|
+
await printMessage(m, topicKey, flags.json ?? false, snippetCache);
|
|
11947
11973
|
}
|
|
11948
11974
|
} catch (err) {
|
|
11949
11975
|
render.warn(`backfill failed: ${err.message}`);
|
|
@@ -11982,7 +12008,7 @@ async function runTopicTail(name, flags) {
|
|
|
11982
12008
|
if (ev.event === "message") {
|
|
11983
12009
|
try {
|
|
11984
12010
|
const m = JSON.parse(ev.data);
|
|
11985
|
-
await printMessage(m, topicKey, flags.json ?? false);
|
|
12011
|
+
await printMessage(m, topicKey, flags.json ?? false, snippetCache);
|
|
11986
12012
|
} catch {}
|
|
11987
12013
|
}
|
|
11988
12014
|
}
|
|
@@ -12000,6 +12026,7 @@ async function runTopicTail(name, flags) {
|
|
|
12000
12026
|
}
|
|
12001
12027
|
});
|
|
12002
12028
|
}
|
|
12029
|
+
var RECENT_CACHE_MAX = 256;
|
|
12003
12030
|
var init_topic_tail = __esm(() => {
|
|
12004
12031
|
init_urls();
|
|
12005
12032
|
init_with_rest_key();
|
|
@@ -12060,6 +12087,27 @@ async function runTopicPost(topicName, message, flags) {
|
|
|
12060
12087
|
return EXIT.INTERNAL_ERROR;
|
|
12061
12088
|
}
|
|
12062
12089
|
}
|
|
12090
|
+
let replyToId;
|
|
12091
|
+
if (flags.replyTo) {
|
|
12092
|
+
if (flags.replyTo.length >= 16) {
|
|
12093
|
+
replyToId = flags.replyTo;
|
|
12094
|
+
} else if (flags.replyTo.length >= 6) {
|
|
12095
|
+
const recent = await request({
|
|
12096
|
+
path: `/api/v1/topics/${encodeURIComponent(cleanName)}/messages?limit=200`,
|
|
12097
|
+
method: "GET",
|
|
12098
|
+
token: secret
|
|
12099
|
+
});
|
|
12100
|
+
const hit = recent.messages?.find((r) => r.id.startsWith(flags.replyTo));
|
|
12101
|
+
if (!hit) {
|
|
12102
|
+
render.err(`--reply-to ${flags.replyTo}: no recent message id starts with that prefix`);
|
|
12103
|
+
return EXIT.INVALID_ARGS;
|
|
12104
|
+
}
|
|
12105
|
+
replyToId = hit.id;
|
|
12106
|
+
} else {
|
|
12107
|
+
render.err("--reply-to needs at least 6 characters of the message id");
|
|
12108
|
+
return EXIT.INVALID_ARGS;
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12063
12111
|
const result = await request({
|
|
12064
12112
|
path: "/api/v1/messages",
|
|
12065
12113
|
method: "POST",
|
|
@@ -12069,7 +12117,8 @@ async function runTopicPost(topicName, message, flags) {
|
|
|
12069
12117
|
ciphertext,
|
|
12070
12118
|
nonce,
|
|
12071
12119
|
bodyVersion,
|
|
12072
|
-
...mentions.length > 0 ? { mentions } : {}
|
|
12120
|
+
...mentions.length > 0 ? { mentions } : {},
|
|
12121
|
+
...replyToId ? { replyToId } : {}
|
|
12073
12122
|
}
|
|
12074
12123
|
});
|
|
12075
12124
|
if (flags.json) {
|
|
@@ -12077,7 +12126,8 @@ async function runTopicPost(topicName, message, flags) {
|
|
|
12077
12126
|
return EXIT.SUCCESS;
|
|
12078
12127
|
}
|
|
12079
12128
|
const versionTag = bodyVersion === 2 ? green("\uD83D\uDD12 v2") : dim("v1");
|
|
12080
|
-
|
|
12129
|
+
const replyTag = result.replyToId ? ` ${dim("↳ " + result.replyToId.slice(0, 8))}` : "";
|
|
12130
|
+
render.ok("posted", `${clay("#" + cleanName)} ${versionTag}${replyTag} ${dim(`(${result.notifications} mentions)`)}`);
|
|
12081
12131
|
return EXIT.SUCCESS;
|
|
12082
12132
|
});
|
|
12083
12133
|
}
|
|
@@ -12445,7 +12495,16 @@ async function startMcpServer() {
|
|
|
12445
12495
|
You are "${myName}"${myRole ? ` (${myRole})` : ""} — a peer in the claudemesh network. Your groups: ${myGroups}. You are one of several Claude Code sessions connected to the same mesh. No orchestrator exists — peers are equals. Your identity comes from your name and group roles, not from a central authority.
|
|
12446
12496
|
|
|
12447
12497
|
## Responding to messages
|
|
12448
|
-
When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Pause your current task, reply via send_message
|
|
12498
|
+
When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Pause your current task, reply via send_message (or \`claudemesh topic post --reply-to <message_id>\` for topic threads), then resume. Stay in character per your system prompt. Do not ignore low-priority messages — acknowledge them briefly even if you defer action.
|
|
12499
|
+
|
|
12500
|
+
The channel attributes carry everything you need to reply — no extra lookups:
|
|
12501
|
+
- \`from_name\` — sender display name. Use as the \`to\` arg when replying to a DM.
|
|
12502
|
+
- \`from_pubkey\` / \`from_member_id\` — stable ids. Use \`from_member_id\` if the sender's display name might change.
|
|
12503
|
+
- \`mesh_slug\` — pass via \`--mesh\` if your default mesh differs.
|
|
12504
|
+
- \`priority\` — \`now\` / \`next\` / \`low\`.
|
|
12505
|
+
- \`message_id\` — id of THIS message. To thread a reply onto it in a topic, run \`claudemesh topic post <topic> "<text>" --reply-to <message_id>\`.
|
|
12506
|
+
- \`topic\` — set when the message arrived through a topic (vs DM). Reply in the same topic.
|
|
12507
|
+
- \`reply_to_id\` — set when the incoming message is itself a reply. Render thread context if you re-narrate.
|
|
12449
12508
|
|
|
12450
12509
|
If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set for yourself — act on it immediately (no reply needed).
|
|
12451
12510
|
|
|
@@ -12805,20 +12864,27 @@ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
|
12805
12864
|
const prioBadge = msg.priority === "now" ? "[URGENT] " : msg.priority === "low" ? "[low] " : "";
|
|
12806
12865
|
const kindBadge = msg.kind === "broadcast" ? " (broadcast)" : "";
|
|
12807
12866
|
const content = `${prioBadge}${fromName}${kindBadge}: ${body}`;
|
|
12867
|
+
const fromMemberPubkey = msg.senderMemberPubkey ?? fromPubkey;
|
|
12808
12868
|
try {
|
|
12809
12869
|
await server.notification({
|
|
12810
12870
|
method: "notifications/claude/channel",
|
|
12811
12871
|
params: {
|
|
12812
12872
|
content,
|
|
12813
12873
|
meta: {
|
|
12814
|
-
from_id:
|
|
12874
|
+
from_id: fromMemberPubkey,
|
|
12875
|
+
from_pubkey: fromMemberPubkey,
|
|
12876
|
+
from_session_pubkey: fromPubkey,
|
|
12815
12877
|
from_name: fromName,
|
|
12878
|
+
...msg.senderMemberId ? { from_member_id: msg.senderMemberId } : {},
|
|
12816
12879
|
mesh_slug: client.meshSlug,
|
|
12817
12880
|
mesh_id: client.meshId,
|
|
12818
12881
|
priority: msg.priority,
|
|
12819
12882
|
sent_at: msg.createdAt,
|
|
12820
12883
|
delivered_at: msg.receivedAt,
|
|
12821
12884
|
kind: msg.kind,
|
|
12885
|
+
message_id: msg.messageId,
|
|
12886
|
+
...msg.topic ? { topic: msg.topic } : {},
|
|
12887
|
+
...msg.replyToId ? { reply_to_id: msg.replyToId } : {},
|
|
12822
12888
|
...msg.subtype ? { subtype: msg.subtype } : {}
|
|
12823
12889
|
}
|
|
12824
12890
|
}
|
|
@@ -13772,7 +13838,7 @@ Topic (conversation scope, v0.2.0)
|
|
|
13772
13838
|
claudemesh topic history <t> fetch message history [--limit --before]
|
|
13773
13839
|
claudemesh topic read <topic> mark all as read
|
|
13774
13840
|
claudemesh topic tail <topic> live SSE tail [--limit --forward-only]
|
|
13775
|
-
claudemesh topic post <t> <msg> encrypted REST post (v0.3.0 v2)
|
|
13841
|
+
claudemesh topic post <t> <msg> encrypted REST post (v0.3.0 v2) [--reply-to <id>]
|
|
13776
13842
|
claudemesh send "#topic" "msg" send to a topic (WS path, v1 plaintext)
|
|
13777
13843
|
claudemesh member list mesh roster with online state [--online]
|
|
13778
13844
|
claudemesh notification list recent @-mentions of you [--since <ISO>]
|
|
@@ -14567,7 +14633,8 @@ async function main() {
|
|
|
14567
14633
|
const postFlags = {
|
|
14568
14634
|
mesh: flags.mesh,
|
|
14569
14635
|
json: !!flags.json,
|
|
14570
|
-
plaintext: !!flags.plaintext
|
|
14636
|
+
plaintext: !!flags.plaintext,
|
|
14637
|
+
replyTo: flags["reply-to"] || flags.replyTo
|
|
14571
14638
|
};
|
|
14572
14639
|
const message = positionals.slice(2).join(" ");
|
|
14573
14640
|
const { runTopicPost: runTopicPost2 } = await Promise.resolve().then(() => (init_topic_post(), exports_topic_post));
|
|
@@ -14660,4 +14727,4 @@ main().catch((err) => {
|
|
|
14660
14727
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
14661
14728
|
});
|
|
14662
14729
|
|
|
14663
|
-
//# debugId=
|
|
14730
|
+
//# debugId=25CA2A7E4B8DBD3564756E2164756E21
|