Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aa61b40e2 | ||
|
|
4afe365c00 | ||
|
|
92bb276a3e |
@@ -418,6 +418,7 @@ export async function setSummary(
|
||||
export interface QueueParams {
|
||||
meshId: string;
|
||||
senderMemberId: string;
|
||||
senderSessionPubkey?: string;
|
||||
targetSpec: string;
|
||||
priority: Priority;
|
||||
nonce: string;
|
||||
@@ -432,6 +433,7 @@ export async function queueMessage(params: QueueParams): Promise<string> {
|
||||
.values({
|
||||
meshId: params.meshId,
|
||||
senderMemberId: params.senderMemberId,
|
||||
senderSessionPubkey: params.senderSessionPubkey ?? null,
|
||||
targetSpec: params.targetSpec,
|
||||
priority: params.priority,
|
||||
nonce: params.nonce,
|
||||
@@ -520,7 +522,7 @@ export async function drainForMember(
|
||||
AND m.id = mq.sender_member_id
|
||||
RETURNING mq.id, mq.priority, mq.nonce, mq.ciphertext,
|
||||
mq.created_at, mq.sender_member_id,
|
||||
m.peer_pubkey AS sender_pubkey
|
||||
COALESCE(mq.sender_session_pubkey, m.peer_pubkey) AS sender_pubkey
|
||||
)
|
||||
SELECT * FROM claimed ORDER BY created_at ASC, id ASC
|
||||
`);
|
||||
|
||||
@@ -438,6 +438,7 @@ async function handleSend(
|
||||
const messageId = await queueMessage({
|
||||
meshId: conn.meshId,
|
||||
senderMemberId: conn.memberId,
|
||||
senderSessionPubkey: conn.sessionPubkey ?? undefined,
|
||||
targetSpec: msg.targetSpec,
|
||||
priority: msg.priority,
|
||||
nonce: msg.nonce,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claudemesh-cli",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.13",
|
||||
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
|
||||
"keywords": [
|
||||
"claude-code",
|
||||
|
||||
@@ -25,6 +25,7 @@ interface LaunchArgs {
|
||||
joinLink: string | null;
|
||||
meshSlug: string | null;
|
||||
quiet: boolean;
|
||||
skipPermConfirm: boolean;
|
||||
claudeArgs: string[];
|
||||
}
|
||||
|
||||
@@ -34,6 +35,7 @@ function parseArgs(argv: string[]): LaunchArgs {
|
||||
joinLink: null,
|
||||
meshSlug: null,
|
||||
quiet: false,
|
||||
skipPermConfirm: false,
|
||||
claudeArgs: [],
|
||||
};
|
||||
|
||||
@@ -54,6 +56,8 @@ function parseArgs(argv: string[]): LaunchArgs {
|
||||
result.meshSlug = arg.slice("--mesh=".length);
|
||||
} else if (arg === "--quiet") {
|
||||
result.quiet = true;
|
||||
} else if (arg === "-y" || arg === "--yes") {
|
||||
result.skipPermConfirm = true;
|
||||
} else if (arg === "--") {
|
||||
result.claudeArgs.push(...argv.slice(i + 1));
|
||||
break;
|
||||
@@ -91,6 +95,44 @@ async function pickMesh(meshes: JoinedMesh[]): Promise<JoinedMesh> {
|
||||
});
|
||||
}
|
||||
|
||||
// --- Permission confirmation ---
|
||||
|
||||
async function confirmPermissions(): Promise<void> {
|
||||
const useColor =
|
||||
!process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
|
||||
const bold = (s: string): string => (useColor ? `\x1b[1m${s}\x1b[22m` : s);
|
||||
const dim = (s: string): string => (useColor ? `\x1b[2m${s}\x1b[22m` : s);
|
||||
const yellow = (s: string): string => (useColor ? `\x1b[33m${s}\x1b[39m` : s);
|
||||
|
||||
console.log(yellow(bold(" Autonomous mode")));
|
||||
console.log("");
|
||||
console.log(" For peers to chat seamlessly, Claude needs to send and");
|
||||
console.log(" receive messages without asking for approval each time.");
|
||||
console.log(" This means tool calls (like sending a peer message) will");
|
||||
console.log(" run automatically — the same as running claude with");
|
||||
console.log(" --dangerously-skip-permissions.");
|
||||
console.log("");
|
||||
console.log(dim(" Claude still can't access anything outside your mesh —"));
|
||||
console.log(dim(" peers only exchange text messages, not tool calls."));
|
||||
console.log(dim(" Skip this prompt next time with: claudemesh launch -y"));
|
||||
console.log("");
|
||||
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
return new Promise((resolve, reject) => {
|
||||
rl.question(` ${bold("Continue?")} [Y/n] `, (answer) => {
|
||||
rl.close();
|
||||
const a = answer.trim().toLowerCase();
|
||||
if (a === "" || a === "y" || a === "yes") {
|
||||
resolve();
|
||||
} else {
|
||||
console.log("\n Aborted. Run without autonomous mode:");
|
||||
console.log(" claude --dangerously-load-development-channels server:claudemesh\n");
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// --- Banner ---
|
||||
|
||||
function printBanner(name: string, meshSlug: string): void {
|
||||
@@ -188,16 +230,22 @@ export async function runLaunch(extraArgs: string[]): Promise<void> {
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
// 5. Banner.
|
||||
if (!args.quiet) printBanner(displayName, mesh.slug);
|
||||
// 5. Banner + permission confirmation.
|
||||
if (!args.quiet) {
|
||||
printBanner(displayName, mesh.slug);
|
||||
// Auto-permissions confirmation — needed for autonomous peer messaging.
|
||||
if (!args.skipPermConfirm) {
|
||||
await confirmPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Spawn claude with ephemeral config + dev channel + display name.
|
||||
// Strip any user-supplied --dangerously-load-development-channels
|
||||
// to avoid duplicates — we always inject our own.
|
||||
// 6. Spawn claude with ephemeral config + dev channel + auto-permissions.
|
||||
// Strip any user-supplied --dangerously flags to avoid duplicates.
|
||||
const filtered: string[] = [];
|
||||
for (let i = 0; i < args.claudeArgs.length; i++) {
|
||||
if (args.claudeArgs[i] === "--dangerously-load-development-channels") {
|
||||
i++; // skip the next arg (the channel value) too
|
||||
if (args.claudeArgs[i] === "--dangerously-load-development-channels"
|
||||
|| args.claudeArgs[i] === "--dangerously-skip-permissions") {
|
||||
if (args.claudeArgs[i] === "--dangerously-load-development-channels") i++;
|
||||
continue;
|
||||
}
|
||||
filtered.push(args.claudeArgs[i]!);
|
||||
@@ -205,6 +253,7 @@ export async function runLaunch(extraArgs: string[]): Promise<void> {
|
||||
const claudeArgs = [
|
||||
"--dangerously-load-development-channels",
|
||||
"server:claudemesh",
|
||||
"--dangerously-skip-permissions",
|
||||
...filtered,
|
||||
];
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export async function startMcpServer(): Promise<void> {
|
||||
|
||||
IMPORTANT: When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Do not wait until your current task is finished. Pause what you are doing, reply to the message using send_message, then resume your work. Treat incoming peer messages like a coworker tapping you on the shoulder — answer right away, even if you're in the middle of something.
|
||||
|
||||
Read the from_id, from_name, mesh_slug, and priority attributes to understand context. Reply by calling send_message with the same target (for direct messages the from_id is the sender's pubkey).
|
||||
Read the from_id, from_name, mesh_slug, and priority attributes to understand context. Reply by calling send_message with to set to the from_name (display name) of the sender.
|
||||
|
||||
Available tools:
|
||||
- list_peers: see joined meshes + their connection status
|
||||
@@ -251,9 +251,17 @@ If you have multiple joined meshes, prefix the \`to\` argument of send_message w
|
||||
for (const client of allClients()) {
|
||||
client.onPush(async (msg) => {
|
||||
const fromPubkey = msg.senderPubkey || "";
|
||||
const fromName = fromPubkey
|
||||
// Resolve sender's display name from the peer list.
|
||||
let fromName = fromPubkey
|
||||
? `peer-${fromPubkey.slice(0, 8)}`
|
||||
: "unknown";
|
||||
try {
|
||||
const peers = await client.listPeers();
|
||||
const match = peers.find((p) => p.pubkey === fromPubkey);
|
||||
if (match) fromName = match.displayName;
|
||||
} catch {
|
||||
/* best effort — fall back to truncated pubkey */
|
||||
}
|
||||
const content = msg.plaintext ?? decryptFailedWarning(fromPubkey);
|
||||
try {
|
||||
await server.notification({
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "mesh"."message_queue" ADD COLUMN "sender_session_pubkey" text;
|
||||
@@ -222,6 +222,7 @@ export const messageQueue = meshSchema.table("message_queue", {
|
||||
senderMemberId: text()
|
||||
.references(() => meshMember.id, { onDelete: "cascade", onUpdate: "cascade" })
|
||||
.notNull(),
|
||||
senderSessionPubkey: text(),
|
||||
targetSpec: text().notNull(),
|
||||
priority: messagePriorityEnum().notNull().default("next"),
|
||||
nonce: text().notNull(),
|
||||
|
||||
Reference in New Issue
Block a user