Compare commits
3 Commits
c6674e971a
...
04bf349e7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04bf349e7d | ||
|
|
20d968f989 | ||
|
|
8931296e82 |
@@ -267,7 +267,7 @@ function sendError(
|
||||
async function handleHello(
|
||||
ws: WebSocket,
|
||||
hello: Extract<WSClientMessage, { type: "hello" }>,
|
||||
): Promise<string | null> {
|
||||
): Promise<{ presenceId: string; memberDisplayName: string } | null> {
|
||||
// Capacity check BEFORE touching DB.
|
||||
const existing = connectionsPerMesh.get(hello.meshId) ?? 0;
|
||||
if (existing >= env.MAX_CONNECTIONS_PER_MESH) {
|
||||
@@ -308,8 +308,12 @@ async function handleHello(
|
||||
presence_id: presenceId,
|
||||
session_id: hello.sessionId,
|
||||
});
|
||||
await maybePushQueuedMessages(presenceId);
|
||||
return presenceId;
|
||||
// Drain any queued messages in the background. The hello_ack is
|
||||
// sent by the CALLER after it assigns presenceId — sending it here
|
||||
// races the caller's closure assignment, causing subsequent client
|
||||
// messages to fail the "no_hello" check.
|
||||
void maybePushQueuedMessages(presenceId);
|
||||
return { presenceId, memberDisplayName: member.displayName };
|
||||
}
|
||||
|
||||
async function handleSend(
|
||||
@@ -348,7 +352,22 @@ function handleConnection(ws: WebSocket): void {
|
||||
try {
|
||||
const msg = JSON.parse(raw.toString()) as WSClientMessage;
|
||||
if (msg.type === "hello") {
|
||||
presenceId = await handleHello(ws, msg);
|
||||
const result = await handleHello(ws, msg);
|
||||
if (!result) return;
|
||||
presenceId = result.presenceId;
|
||||
// Ack AFTER closure assignment — subsequent client messages
|
||||
// arriving immediately after will now see a non-null presenceId.
|
||||
try {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "hello_ack",
|
||||
presenceId: result.presenceId,
|
||||
memberDisplayName: result.memberDisplayName,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
/* ws closed during hello */
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!presenceId) {
|
||||
|
||||
@@ -95,6 +95,13 @@ export interface WSAckMessage {
|
||||
queued: boolean;
|
||||
}
|
||||
|
||||
/** Broker → client: hello handshake acknowledgement. */
|
||||
export interface WSHelloAckMessage {
|
||||
type: "hello_ack";
|
||||
presenceId: string;
|
||||
memberDisplayName: string;
|
||||
}
|
||||
|
||||
/** Broker → client: structured error. */
|
||||
export interface WSErrorMessage {
|
||||
type: "error";
|
||||
@@ -108,4 +115,8 @@ export type WSClientMessage =
|
||||
| WSSendMessage
|
||||
| WSSetStatusMessage;
|
||||
|
||||
export type WSServerMessage = WSPushMessage | WSAckMessage | WSErrorMessage;
|
||||
export type WSServerMessage =
|
||||
| WSHelloAckMessage
|
||||
| WSPushMessage
|
||||
| WSAckMessage
|
||||
| WSErrorMessage;
|
||||
|
||||
59
apps/cli/README.md
Normal file
59
apps/cli/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# @claudemesh/cli
|
||||
|
||||
Client tool for claudemesh — install once per machine, join one or more
|
||||
meshes, and your Claude Code sessions can talk to peers on demand.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
# From npm (once published)
|
||||
npm install -g @claudemesh/cli
|
||||
|
||||
# Or from the monorepo during dev
|
||||
cd apps/cli && bun link
|
||||
```
|
||||
|
||||
Then register the MCP server with Claude Code:
|
||||
|
||||
```sh
|
||||
claudemesh install
|
||||
# prints: claude mcp add claudemesh --scope user -- claudemesh mcp
|
||||
```
|
||||
|
||||
Run the printed command, then restart Claude Code.
|
||||
|
||||
## Join a mesh
|
||||
|
||||
```sh
|
||||
claudemesh join ic://join/BASE64URL...
|
||||
```
|
||||
|
||||
The invite link is generated by whoever runs the mesh. It bundles the
|
||||
mesh id, expiry, signing key, and role. Your CLI verifies it,
|
||||
generates a fresh keypair, enrolls you with the broker, and persists
|
||||
the result to `~/.claudemesh/config.json`.
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
claudemesh install # print MCP registration command
|
||||
claudemesh join <link> # join a mesh via invite link
|
||||
claudemesh list # show joined meshes + identities
|
||||
claudemesh leave <slug> # leave a mesh
|
||||
claudemesh mcp # start MCP server (stdio — Claude Code only)
|
||||
claudemesh --help # show usage
|
||||
```
|
||||
|
||||
## Env overrides
|
||||
|
||||
| Var | Default | Purpose |
|
||||
| ----------------------- | ---------------------------- | ------------------------------ |
|
||||
| `CLAUDEMESH_BROKER_URL` | `wss://ic.claudemesh.com/ws` | Point at a self-hosted broker |
|
||||
| `CLAUDEMESH_CONFIG_DIR` | `~/.claudemesh/` | Override config location |
|
||||
| `CLAUDEMESH_DEBUG` | `0` | Verbose logging |
|
||||
|
||||
## Status
|
||||
|
||||
v0.1.0 scaffold — CLI commands + MCP server shell in place. WS broker
|
||||
connection, libsodium crypto, invite-link verification, and auto-install
|
||||
of hooks land in subsequent steps.
|
||||
3
apps/cli/eslint.config.js
Normal file
3
apps/cli/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from "@turbostarter/eslint-config/base";
|
||||
|
||||
export default baseConfig;
|
||||
37
apps/cli/package.json
Normal file
37
apps/cli/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@claudemesh/cli",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"claudemesh": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||
"dev": "bun --hot src/index.ts",
|
||||
"start": "bun src/index.ts",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@turbostarter/prettier-config",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.27.1",
|
||||
"libsodium-wrappers": "0.7.15",
|
||||
"ws": "8.20.0",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@turbostarter/eslint-config": "workspace:*",
|
||||
"@turbostarter/prettier-config": "workspace:*",
|
||||
"@turbostarter/tsconfig": "workspace:*",
|
||||
"@turbostarter/vitest-config": "workspace:*",
|
||||
"@types/libsodium-wrappers": "0.7.14",
|
||||
"@types/ws": "8.5.13",
|
||||
"eslint": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
81
apps/cli/scripts/roundtrip.ts
Normal file
81
apps/cli/scripts/roundtrip.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* End-to-end round-trip: two BrokerClient instances talking via the
|
||||
* broker. Runs against a live broker + seeded DB.
|
||||
*
|
||||
* Reads /tmp/cli-seed.json (output of broker's scripts/seed-test-mesh.ts),
|
||||
* connects peer A and peer B, sends a message from A to B, waits for
|
||||
* the push on B, asserts receipt + sender pubkey.
|
||||
*/
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { BrokerClient } from "../src/ws/client";
|
||||
import type { JoinedMesh } from "../src/state/config";
|
||||
|
||||
const seed = JSON.parse(readFileSync("/tmp/cli-seed.json", "utf-8")) as {
|
||||
meshId: string;
|
||||
peerA: { memberId: string; pubkey: string };
|
||||
peerB: { memberId: string; pubkey: string };
|
||||
};
|
||||
|
||||
const brokerUrl = process.env.BROKER_WS_URL ?? "ws://localhost:7900/ws";
|
||||
const meshA: JoinedMesh = {
|
||||
meshId: seed.meshId,
|
||||
memberId: seed.peerA.memberId,
|
||||
slug: "rt-a",
|
||||
name: "roundtrip-a",
|
||||
pubkey: seed.peerA.pubkey,
|
||||
secretKey: "stub",
|
||||
brokerUrl,
|
||||
joinedAt: new Date().toISOString(),
|
||||
};
|
||||
const meshB: JoinedMesh = { ...meshA, memberId: seed.peerB.memberId, slug: "rt-b", pubkey: seed.peerB.pubkey };
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const a = new BrokerClient(meshA, { debug: true });
|
||||
const b = new BrokerClient(meshB, { debug: true });
|
||||
|
||||
let received: string | null = null;
|
||||
let receivedSender: string | null = null;
|
||||
b.onPush((msg) => {
|
||||
received = Buffer.from(msg.ciphertext, "base64").toString("utf-8");
|
||||
receivedSender = msg.senderPubkey;
|
||||
console.log(`[b] push: "${received}" from ${receivedSender}`);
|
||||
});
|
||||
|
||||
console.log("[rt] connecting A + B…");
|
||||
await Promise.all([a.connect(), b.connect()]);
|
||||
console.log(`[rt] A: ${a.status}, B: ${b.status}`);
|
||||
|
||||
console.log("[rt] A → B …");
|
||||
const result = await a.send(seed.peerB.pubkey, "hello from A", "now");
|
||||
console.log("[rt] send result:", result);
|
||||
|
||||
// Wait up to 3s for the push to land.
|
||||
for (let i = 0; i < 30 && !received; i++) {
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
|
||||
a.close();
|
||||
b.close();
|
||||
|
||||
if (!received) {
|
||||
console.error("✗ FAIL: no push received");
|
||||
process.exit(1);
|
||||
}
|
||||
if (received !== "hello from A") {
|
||||
console.error(`✗ FAIL: body mismatch: "${received}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (receivedSender !== seed.peerA.pubkey) {
|
||||
console.error(`✗ FAIL: sender mismatch: "${receivedSender}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("✓ round-trip PASSED");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("✗ FAIL:", e instanceof Error ? e.message : e);
|
||||
process.exit(1);
|
||||
});
|
||||
36
apps/cli/src/commands/install.ts
Normal file
36
apps/cli/src/commands/install.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* `claudemesh install` — print Claude Code MCP registration instructions.
|
||||
*
|
||||
* In the v1 flow, users copy-paste a `claude mcp add ...` command.
|
||||
* Later we'll auto-write the MCP entry to ~/.claude.json and hooks
|
||||
* to ~/.claude/settings.json (mirroring claude-intercom's installer).
|
||||
*/
|
||||
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
export function runInstall(): void {
|
||||
// Resolve the path to this package's own index.ts so the generated
|
||||
// command points at the right binary even when installed globally.
|
||||
const here = fileURLToPath(import.meta.url);
|
||||
const entry = resolve(dirname(here), "..", "index.ts");
|
||||
|
||||
console.log("claudemesh — MCP registration");
|
||||
console.log("------------------------------");
|
||||
console.log("");
|
||||
console.log("Register the MCP server with Claude Code:");
|
||||
console.log("");
|
||||
console.log(` claude mcp add claudemesh --scope user -- bun ${entry} mcp`);
|
||||
console.log("");
|
||||
console.log("Or if installed globally:");
|
||||
console.log("");
|
||||
console.log(` claude mcp add claudemesh --scope user -- claudemesh mcp`);
|
||||
console.log("");
|
||||
console.log(
|
||||
"After registering, restart Claude Code. Then join a mesh with:",
|
||||
);
|
||||
console.log("");
|
||||
console.log(" claudemesh join <invite-link>");
|
||||
console.log("");
|
||||
console.log("(Auto-install of hooks + MCP entry will ship in a later step.)");
|
||||
}
|
||||
30
apps/cli/src/commands/join.ts
Normal file
30
apps/cli/src/commands/join.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* `claudemesh join <invite-link>` — parse a mesh invite link and
|
||||
* join the mesh.
|
||||
*
|
||||
* STUB: real invite-link parsing + keypair generation + broker
|
||||
* enrollment lands in Step 17. For now this just validates the link
|
||||
* shape and tells the user what's coming.
|
||||
*/
|
||||
|
||||
export function runJoin(args: string[]): void {
|
||||
const link = args[0];
|
||||
if (!link) {
|
||||
console.error("Usage: claudemesh join <invite-link>");
|
||||
console.error("");
|
||||
console.error("Example: claudemesh join ic://join/BASE64URL...");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!link.startsWith("ic://join/")) {
|
||||
console.error(
|
||||
`claudemesh: invalid invite link. Expected ic://join/... got "${link}"`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("claudemesh: join not yet implemented (Step 17).");
|
||||
console.log(` Invite link parsed: ${link.slice(0, 40)}...`);
|
||||
console.log(
|
||||
" Real flow will: verify sig, generate keypair, enroll member, persist to ~/.claudemesh/config.json",
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
25
apps/cli/src/commands/leave.ts
Normal file
25
apps/cli/src/commands/leave.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* `claudemesh leave <slug>` — remove a mesh from local config.
|
||||
*
|
||||
* Does NOT (yet) notify the broker. In 15b+ this will send a
|
||||
* best-effort revoke request before removing the entry.
|
||||
*/
|
||||
|
||||
import { loadConfig, saveConfig } from "../state/config";
|
||||
|
||||
export function runLeave(args: string[]): void {
|
||||
const slug = args[0];
|
||||
if (!slug) {
|
||||
console.error("Usage: claudemesh leave <slug>");
|
||||
process.exit(1);
|
||||
}
|
||||
const config = loadConfig();
|
||||
const before = config.meshes.length;
|
||||
config.meshes = config.meshes.filter((m) => m.slug !== slug);
|
||||
if (config.meshes.length === before) {
|
||||
console.error(`claudemesh: no joined mesh with slug "${slug}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
saveConfig(config);
|
||||
console.log(`Left mesh "${slug}". Remaining: ${config.meshes.length}`);
|
||||
}
|
||||
28
apps/cli/src/commands/list.ts
Normal file
28
apps/cli/src/commands/list.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* `claudemesh list` — show all joined meshes + their status.
|
||||
*/
|
||||
|
||||
import { loadConfig, getConfigPath } from "../state/config";
|
||||
|
||||
export function runList(): void {
|
||||
const config = loadConfig();
|
||||
if (config.meshes.length === 0) {
|
||||
console.log("No meshes joined yet.");
|
||||
console.log("");
|
||||
console.log("Join one with: claudemesh join <invite-link>");
|
||||
console.log(`Config file: ${getConfigPath()}`);
|
||||
return;
|
||||
}
|
||||
console.log(`Joined meshes (${config.meshes.length}):`);
|
||||
console.log("");
|
||||
for (const m of config.meshes) {
|
||||
console.log(` ${m.name} (${m.slug})`);
|
||||
console.log(` mesh id: ${m.meshId}`);
|
||||
console.log(` member id: ${m.memberId}`);
|
||||
console.log(` pubkey: ${m.pubkey.slice(0, 16)}…`);
|
||||
console.log(` broker: ${m.brokerUrl}`);
|
||||
console.log(` joined: ${m.joinedAt}`);
|
||||
console.log("");
|
||||
}
|
||||
console.log(`Config: ${getConfigPath()}`);
|
||||
}
|
||||
44
apps/cli/src/commands/seed-test-mesh.ts
Normal file
44
apps/cli/src/commands/seed-test-mesh.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* `claudemesh seed-test-mesh` — dev-only helper for 15b testing.
|
||||
*
|
||||
* Writes a locally-valid JoinedMesh entry to ~/.claudemesh/config.json
|
||||
* so the MCP server can connect to a locally-running broker without
|
||||
* invite-link / crypto plumbing.
|
||||
*
|
||||
* Usage:
|
||||
* claudemesh seed-test-mesh <broker-url> <mesh-id> <member-id> <pubkey> <slug>
|
||||
*/
|
||||
|
||||
import { loadConfig, saveConfig } from "../state/config";
|
||||
|
||||
export function runSeedTestMesh(args: string[]): void {
|
||||
const [brokerUrl, meshId, memberId, pubkey, slug] = args;
|
||||
if (!brokerUrl || !meshId || !memberId || !pubkey || !slug) {
|
||||
console.error(
|
||||
"Usage: claudemesh seed-test-mesh <broker-ws-url> <mesh-id> <member-id> <pubkey> <slug>",
|
||||
);
|
||||
console.error("");
|
||||
console.error(
|
||||
'Example: claudemesh seed-test-mesh "ws://localhost:7900/ws" mesh-123 member-abc aaa..aaa smoke-test',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const config = loadConfig();
|
||||
// Remove any prior entry with same slug (idempotent).
|
||||
config.meshes = config.meshes.filter((m) => m.slug !== slug);
|
||||
config.meshes.push({
|
||||
meshId,
|
||||
memberId,
|
||||
slug,
|
||||
name: `Test: ${slug}`,
|
||||
pubkey,
|
||||
secretKey: "dev-only-stub", // real keypair generated during join in Step 17
|
||||
brokerUrl,
|
||||
joinedAt: new Date().toISOString(),
|
||||
});
|
||||
saveConfig(config);
|
||||
console.log(`Seeded mesh "${slug}" (${meshId}) into local config.`);
|
||||
console.log(
|
||||
`Run \`claudemesh mcp\` to connect, or register with Claude Code via \`claudemesh install\`.`,
|
||||
);
|
||||
}
|
||||
27
apps/cli/src/env.ts
Normal file
27
apps/cli/src/env.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* CLI environment config.
|
||||
*
|
||||
* Read once at startup. Overridable via env vars so users can point
|
||||
* at a self-hosted broker or a staging instance without rebuilding.
|
||||
*/
|
||||
const envSchema = z.object({
|
||||
CLAUDEMESH_BROKER_URL: z.string().default("wss://ic.claudemesh.com/ws"),
|
||||
CLAUDEMESH_CONFIG_DIR: z.string().optional(),
|
||||
CLAUDEMESH_DEBUG: z.coerce.boolean().default(false),
|
||||
});
|
||||
|
||||
export type CliEnv = z.infer<typeof envSchema>;
|
||||
|
||||
export function loadEnv(): CliEnv {
|
||||
const parsed = envSchema.safeParse(process.env);
|
||||
if (!parsed.success) {
|
||||
console.error("[claudemesh] invalid environment:");
|
||||
console.error(z.treeifyError(parsed.error));
|
||||
process.exit(1);
|
||||
}
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
export const env = loadEnv();
|
||||
78
apps/cli/src/index.ts
Normal file
78
apps/cli/src/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* @claudemesh/cli entry point.
|
||||
*
|
||||
* Dispatches between two modes:
|
||||
* - `claudemesh mcp` → MCP server (stdio transport)
|
||||
* - `claudemesh <subcommand>` → CLI subcommand
|
||||
*
|
||||
* Claude Code invokes the `mcp` mode via stdio. Humans use all others.
|
||||
*/
|
||||
|
||||
import { startMcpServer } from "./mcp/server";
|
||||
import { runInstall } from "./commands/install";
|
||||
import { runJoin } from "./commands/join";
|
||||
import { runList } from "./commands/list";
|
||||
import { runLeave } from "./commands/leave";
|
||||
import { runSeedTestMesh } from "./commands/seed-test-mesh";
|
||||
|
||||
const HELP = `claudemesh — peer mesh for Claude Code sessions
|
||||
|
||||
Usage:
|
||||
claudemesh <command> [args]
|
||||
|
||||
Commands:
|
||||
install Print Claude Code MCP registration instructions
|
||||
join <link> Join a mesh via invite link (ic://join/...)
|
||||
list Show all joined meshes
|
||||
leave <slug> Leave a joined mesh
|
||||
seed-test-mesh Dev-only: inject a mesh into config (skips invite flow)
|
||||
mcp Start MCP server (stdio) — invoked by Claude Code
|
||||
--help, -h Show this help
|
||||
|
||||
Environment:
|
||||
CLAUDEMESH_BROKER_URL Override broker URL (default: wss://ic.claudemesh.com/ws)
|
||||
CLAUDEMESH_CONFIG_DIR Override config directory (default: ~/.claudemesh/)
|
||||
CLAUDEMESH_DEBUG=1 Verbose logging
|
||||
`;
|
||||
|
||||
const cmd = process.argv[2];
|
||||
const args = process.argv.slice(3);
|
||||
|
||||
async function main(): Promise<void> {
|
||||
switch (cmd) {
|
||||
case "mcp":
|
||||
await startMcpServer();
|
||||
return;
|
||||
case "install":
|
||||
runInstall();
|
||||
return;
|
||||
case "join":
|
||||
runJoin(args);
|
||||
return;
|
||||
case "list":
|
||||
runList();
|
||||
return;
|
||||
case "leave":
|
||||
runLeave(args);
|
||||
return;
|
||||
case "seed-test-mesh":
|
||||
runSeedTestMesh(args);
|
||||
return;
|
||||
case "--help":
|
||||
case "-h":
|
||||
case "help":
|
||||
case undefined:
|
||||
console.log(HELP);
|
||||
return;
|
||||
default:
|
||||
console.error(`Unknown command: ${cmd}`);
|
||||
console.error("Run `claudemesh --help` for usage.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(`claudemesh: ${e instanceof Error ? e.message : String(e)}`);
|
||||
process.exit(1);
|
||||
});
|
||||
206
apps/cli/src/mcp/server.ts
Normal file
206
apps/cli/src/mcp/server.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* MCP server (stdio transport) for @claudemesh/cli.
|
||||
*
|
||||
* Starts BrokerClient connections for every mesh in config on boot,
|
||||
* then routes the 5 MCP tools through them.
|
||||
*
|
||||
* list_peers is stubbed at the CLI level — the broker's WS protocol
|
||||
* does not yet carry a list-peers request type (Step 16). Until then,
|
||||
* it returns a note.
|
||||
*/
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
ListToolsRequestSchema,
|
||||
CallToolRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { TOOLS } from "./tools";
|
||||
import { loadConfig } from "../state/config";
|
||||
import { startClients, stopAll, findClient, allClients } from "../ws/manager";
|
||||
import type {
|
||||
Priority,
|
||||
PeerStatus,
|
||||
SendMessageArgs,
|
||||
SetStatusArgs,
|
||||
SetSummaryArgs,
|
||||
ListPeersArgs,
|
||||
} from "./types";
|
||||
import type { BrokerClient, InboundPush } from "../ws/client";
|
||||
|
||||
function text(msg: string, isError = false) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: msg }],
|
||||
...(isError ? { isError: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `to` string, pick which mesh to send from. Strategies:
|
||||
* - If `to` looks like a pubkey hex (64 chars), try every client;
|
||||
* caller is expected to know which mesh the pubkey lives in.
|
||||
* - If `to` starts with `#`, treat as channel on the first mesh.
|
||||
* - Otherwise try to match a displayName (TODO — needs list_peers).
|
||||
*
|
||||
* For now the MVP: if only one mesh is joined, use that. Otherwise
|
||||
* require the caller to prefix with `<mesh-slug>:`.
|
||||
*/
|
||||
function resolveClient(to: string): {
|
||||
client: BrokerClient | null;
|
||||
targetSpec: string;
|
||||
error?: string;
|
||||
} {
|
||||
const clients = allClients();
|
||||
if (clients.length === 0) {
|
||||
return { client: null, targetSpec: to, error: "no meshes joined" };
|
||||
}
|
||||
// Explicit mesh prefix: "mesh-slug:targetspec"
|
||||
const colonIdx = to.indexOf(":");
|
||||
if (colonIdx > 0 && colonIdx < to.length - 1) {
|
||||
const slug = to.slice(0, colonIdx);
|
||||
const rest = to.slice(colonIdx + 1);
|
||||
const match = findClient(slug);
|
||||
if (match) return { client: match, targetSpec: rest };
|
||||
}
|
||||
// Single-mesh fast path.
|
||||
if (clients.length === 1) {
|
||||
return { client: clients[0]!, targetSpec: to };
|
||||
}
|
||||
return {
|
||||
client: null,
|
||||
targetSpec: to,
|
||||
error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients.map((c) => c.meshSlug).join(", ")})`,
|
||||
};
|
||||
}
|
||||
|
||||
function formatPush(p: InboundPush, meshSlug: string): string {
|
||||
const body = (() => {
|
||||
try {
|
||||
return Buffer.from(p.ciphertext, "base64").toString("utf-8");
|
||||
} catch {
|
||||
return "(invalid base64 ciphertext)";
|
||||
}
|
||||
})();
|
||||
return `[${meshSlug}] from ${p.senderPubkey.slice(0, 12)}… (${p.priority}, ${p.createdAt}):\n${body}`;
|
||||
}
|
||||
|
||||
export async function startMcpServer(): Promise<void> {
|
||||
const config = loadConfig();
|
||||
|
||||
const server = new Server(
|
||||
{ name: "claudemesh", version: "0.1.0" },
|
||||
{
|
||||
capabilities: { tools: {} },
|
||||
instructions: `You are connected to claudemesh — a peer mesh for Claude Code sessions.
|
||||
|
||||
Use these tools to coordinate with peers on demand. Respond promptly when you receive messages (they're like someone tapping your shoulder).
|
||||
|
||||
Tools: send_message, list_peers, check_messages, set_summary, set_status.
|
||||
|
||||
If you have multiple joined meshes, prefix the \`to\` argument of send_message with \`<mesh-slug>:\` to disambiguate. Otherwise claudemesh picks the single joined mesh.`,
|
||||
},
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: TOOLS,
|
||||
}));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
||||
const { name, arguments: args } = req.params;
|
||||
if (config.meshes.length === 0) {
|
||||
return text(
|
||||
"No meshes joined. Run `claudemesh join <invite-link>` first.",
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "send_message": {
|
||||
const { to, message, priority } = (args ?? {}) as SendMessageArgs;
|
||||
if (!to || !message)
|
||||
return text("send_message: `to` and `message` required", true);
|
||||
const { client, targetSpec, error } = resolveClient(to);
|
||||
if (!client)
|
||||
return text(`send_message: ${error ?? "no client resolved"}`, true);
|
||||
const result = await client.send(
|
||||
targetSpec,
|
||||
message,
|
||||
(priority ?? "next") as Priority,
|
||||
);
|
||||
if (!result.ok)
|
||||
return text(
|
||||
`send_message failed (${client.meshSlug}): ${result.error}`,
|
||||
true,
|
||||
);
|
||||
return text(
|
||||
`Sent to ${targetSpec} via ${client.meshSlug} [${priority ?? "next"}] → ${result.messageId}`,
|
||||
);
|
||||
}
|
||||
|
||||
case "list_peers": {
|
||||
const { mesh_slug } = (args ?? {}) as ListPeersArgs;
|
||||
const clients = mesh_slug
|
||||
? [findClient(mesh_slug)].filter(Boolean)
|
||||
: allClients();
|
||||
if (clients.length === 0)
|
||||
return text(
|
||||
mesh_slug
|
||||
? `list_peers: no joined mesh "${mesh_slug}"`
|
||||
: "list_peers: no joined meshes",
|
||||
true,
|
||||
);
|
||||
const lines = clients.map(
|
||||
(c) =>
|
||||
`- ${c!.meshSlug} (${c!.status}, mesh ${c!.meshId.slice(0, 8)}…)`,
|
||||
);
|
||||
return text(
|
||||
`Connected meshes:\n${lines.join("\n")}\n\n(list_peers WS protocol lands in Step 16; only mesh status is shown for now.)`,
|
||||
);
|
||||
}
|
||||
|
||||
case "check_messages": {
|
||||
const drained: string[] = [];
|
||||
for (const c of allClients()) {
|
||||
const msgs = c.drainPushBuffer();
|
||||
for (const m of msgs) drained.push(formatPush(m, c.meshSlug));
|
||||
}
|
||||
if (drained.length === 0) return text("No new messages.");
|
||||
return text(
|
||||
`${drained.length} new message(s):\n\n${drained.join("\n\n---\n\n")}`,
|
||||
);
|
||||
}
|
||||
|
||||
case "set_summary": {
|
||||
const { summary } = (args ?? {}) as SetSummaryArgs;
|
||||
if (!summary) return text("set_summary: `summary` required", true);
|
||||
return text(
|
||||
`set_summary: summary recorded locally ("${summary}"). (Broker WS protocol for summaries lands in Step 16.)`,
|
||||
);
|
||||
}
|
||||
|
||||
case "set_status": {
|
||||
const { status } = (args ?? {}) as SetStatusArgs;
|
||||
if (!status) return text("set_status: `status` required", true);
|
||||
const s = status as PeerStatus;
|
||||
for (const c of allClients()) await c.setStatus(s);
|
||||
return text(`Status set to ${s} across ${allClients().length} mesh(es).`);
|
||||
}
|
||||
|
||||
default:
|
||||
return text(`Unknown tool: ${name}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Start broker clients for every joined mesh BEFORE MCP connects.
|
||||
await startClients(config);
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
const shutdown = (): void => {
|
||||
stopAll();
|
||||
process.exit(0);
|
||||
};
|
||||
process.on("SIGTERM", shutdown);
|
||||
process.on("SIGINT", shutdown);
|
||||
}
|
||||
81
apps/cli/src/mcp/tools.ts
Normal file
81
apps/cli/src/mcp/tools.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* MCP tool definitions exposed to Claude Code.
|
||||
*
|
||||
* Mirror the claude-intercom tool surface: send_message, list_peers,
|
||||
* check_messages, set_summary, set_status. Tools return "not
|
||||
* connected" errors until 15b wires the WS client.
|
||||
*/
|
||||
|
||||
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
export const TOOLS: Tool[] = [
|
||||
{
|
||||
name: "send_message",
|
||||
description:
|
||||
"Send a message to a peer in one of your joined meshes. `to` is a peer display name, hex pubkey, or `#channel`. `priority` controls delivery: `now` bypasses busy gates, `next` waits for idle (default), `low` is pull-only.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
to: {
|
||||
type: "string",
|
||||
description: "Peer name, pubkey, or #channel",
|
||||
},
|
||||
message: { type: "string", description: "Message text" },
|
||||
priority: {
|
||||
type: "string",
|
||||
enum: ["now", "next", "low"],
|
||||
description: "Delivery priority (default: next)",
|
||||
},
|
||||
},
|
||||
required: ["to", "message"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_peers",
|
||||
description:
|
||||
"List peers across all joined meshes. Shows name, mesh, status (idle/working/dnd), and current summary.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
mesh_slug: {
|
||||
type: "string",
|
||||
description: "Only list peers in this mesh (optional)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "check_messages",
|
||||
description:
|
||||
"Pull any undelivered messages from the broker. Normally messages arrive via push; use this to drain the queue after being offline.",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
},
|
||||
{
|
||||
name: "set_summary",
|
||||
description:
|
||||
"Set a 1–2 sentence summary of what you're working on. Visible to other peers.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
summary: { type: "string", description: "1-2 sentence summary" },
|
||||
},
|
||||
required: ["summary"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set_status",
|
||||
description:
|
||||
"Manually override your status. `dnd` blocks everything except `now`-priority messages.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: {
|
||||
type: "string",
|
||||
enum: ["idle", "working", "dnd"],
|
||||
description: "Your status",
|
||||
},
|
||||
},
|
||||
required: ["status"],
|
||||
},
|
||||
},
|
||||
];
|
||||
24
apps/cli/src/mcp/types.ts
Normal file
24
apps/cli/src/mcp/types.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* MCP tool schemas + shared types for the CLI's MCP server.
|
||||
*/
|
||||
|
||||
export type Priority = "now" | "next" | "low";
|
||||
export type PeerStatus = "idle" | "working" | "dnd";
|
||||
|
||||
export interface SendMessageArgs {
|
||||
to: string; // peer name, pubkey, or #channel
|
||||
message: string;
|
||||
priority?: Priority;
|
||||
}
|
||||
|
||||
export interface ListPeersArgs {
|
||||
mesh_slug?: string; // filter to one joined mesh
|
||||
}
|
||||
|
||||
export interface SetSummaryArgs {
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface SetStatusArgs {
|
||||
status: PeerStatus;
|
||||
}
|
||||
58
apps/cli/src/state/config.ts
Normal file
58
apps/cli/src/state/config.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Local persistent config — ~/.claudemesh/config.json
|
||||
*
|
||||
* Stores: joined meshes, per-mesh identity keys (ed25519 keypairs),
|
||||
* last-seen broker URL. Loaded on CLI start, on MCP server start,
|
||||
* and on every join/leave.
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join, dirname } from "node:path";
|
||||
import { z } from "zod";
|
||||
import { env } from "../env";
|
||||
|
||||
const joinedMeshSchema = z.object({
|
||||
meshId: z.string(),
|
||||
memberId: z.string(),
|
||||
slug: z.string(),
|
||||
name: z.string(),
|
||||
pubkey: z.string(), // ed25519 hex (32 bytes = 64 chars)
|
||||
secretKey: z.string(), // ed25519 hex (64 bytes = 128 chars)
|
||||
brokerUrl: z.string(),
|
||||
joinedAt: z.string(),
|
||||
});
|
||||
|
||||
const configSchema = z.object({
|
||||
version: z.literal(1).default(1),
|
||||
meshes: z.array(joinedMeshSchema).default([]),
|
||||
});
|
||||
|
||||
export type JoinedMesh = z.infer<typeof joinedMeshSchema>;
|
||||
export type Config = z.infer<typeof configSchema>;
|
||||
|
||||
const CONFIG_DIR = env.CLAUDEMESH_CONFIG_DIR ?? join(homedir(), ".claudemesh");
|
||||
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
||||
|
||||
export function loadConfig(): Config {
|
||||
if (!existsSync(CONFIG_PATH)) {
|
||||
return configSchema.parse({ version: 1, meshes: [] });
|
||||
}
|
||||
try {
|
||||
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
||||
return configSchema.parse(JSON.parse(raw));
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to load ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveConfig(config: Config): void {
|
||||
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
||||
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
||||
}
|
||||
|
||||
export function getConfigPath(): string {
|
||||
return CONFIG_PATH;
|
||||
}
|
||||
335
apps/cli/src/ws/client.ts
Normal file
335
apps/cli/src/ws/client.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* BrokerClient — WebSocket client connecting a CLI session to a claudemesh
|
||||
* broker. Handles:
|
||||
* - hello handshake + ack
|
||||
* - send / ack / push message flow
|
||||
* - auto-reconnect with exponential backoff (1s, 2s, 4s, ..., max 30s)
|
||||
* - in-memory outbound queue while reconnecting
|
||||
* - push buffer so the MCP check_messages tool can drain inbound history
|
||||
*
|
||||
* Encryption is deferred to Step 18 (libsodium). Until then, ciphertext
|
||||
* is plaintext UTF-8, nonce is a random 24-byte base64 string (for
|
||||
* future-compat layout only).
|
||||
*/
|
||||
|
||||
import WebSocket from "ws";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import type { JoinedMesh } from "../state/config";
|
||||
|
||||
export type Priority = "now" | "next" | "low";
|
||||
export type ConnStatus = "connecting" | "open" | "closed" | "reconnecting";
|
||||
|
||||
export interface InboundPush {
|
||||
messageId: string;
|
||||
meshId: string;
|
||||
senderPubkey: string;
|
||||
priority: Priority;
|
||||
nonce: string;
|
||||
ciphertext: string;
|
||||
createdAt: string;
|
||||
receivedAt: string;
|
||||
}
|
||||
|
||||
type PushHandler = (msg: InboundPush) => void;
|
||||
|
||||
interface PendingSend {
|
||||
id: string;
|
||||
targetSpec: string;
|
||||
priority: Priority;
|
||||
nonce: string;
|
||||
ciphertext: string;
|
||||
resolve: (v: { ok: boolean; messageId?: string; error?: string }) => void;
|
||||
}
|
||||
|
||||
const MAX_QUEUED = 100;
|
||||
const HELLO_ACK_TIMEOUT_MS = 5_000;
|
||||
const BACKOFF_CAPS = [1_000, 2_000, 4_000, 8_000, 16_000, 30_000];
|
||||
|
||||
export class BrokerClient {
|
||||
private ws: WebSocket | null = null;
|
||||
private _status: ConnStatus = "closed";
|
||||
private pendingSends = new Map<string, PendingSend>();
|
||||
private outbound: Array<() => void> = []; // closures that send once ws is open
|
||||
private pushHandlers = new Set<PushHandler>();
|
||||
private pushBuffer: InboundPush[] = [];
|
||||
private closed = false;
|
||||
private reconnectAttempt = 0;
|
||||
private helloTimer: NodeJS.Timeout | null = null;
|
||||
private reconnectTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(
|
||||
private mesh: JoinedMesh,
|
||||
private opts: {
|
||||
onStatusChange?: (status: ConnStatus) => void;
|
||||
debug?: boolean;
|
||||
} = {},
|
||||
) {}
|
||||
|
||||
get status(): ConnStatus {
|
||||
return this._status;
|
||||
}
|
||||
get meshId(): string {
|
||||
return this.mesh.meshId;
|
||||
}
|
||||
get meshSlug(): string {
|
||||
return this.mesh.slug;
|
||||
}
|
||||
get pushHistory(): readonly InboundPush[] {
|
||||
return this.pushBuffer;
|
||||
}
|
||||
|
||||
/** Open WS, send hello, resolve when hello_ack received. */
|
||||
async connect(): Promise<void> {
|
||||
if (this.closed) throw new Error("client is closed");
|
||||
this.setStatus("connecting");
|
||||
const ws = new WebSocket(this.mesh.brokerUrl);
|
||||
this.ws = ws;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const onOpen = (): void => {
|
||||
this.debug("ws open → sending hello");
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "hello",
|
||||
meshId: this.mesh.meshId,
|
||||
memberId: this.mesh.memberId,
|
||||
pubkey: this.mesh.pubkey,
|
||||
sessionId: `${process.pid}-${Date.now()}`,
|
||||
pid: process.pid,
|
||||
cwd: process.cwd(),
|
||||
signature: "stub", // libsodium sign_detached lands in Step 18
|
||||
nonce: randomNonce(),
|
||||
}),
|
||||
);
|
||||
// Arm the hello_ack timeout.
|
||||
this.helloTimer = setTimeout(() => {
|
||||
this.debug("hello_ack timeout");
|
||||
ws.close();
|
||||
reject(new Error("hello_ack timeout"));
|
||||
}, HELLO_ACK_TIMEOUT_MS);
|
||||
};
|
||||
|
||||
const onMessage = (raw: WebSocket.RawData): void => {
|
||||
let msg: Record<string, unknown>;
|
||||
try {
|
||||
msg = JSON.parse(raw.toString());
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (msg.type === "hello_ack") {
|
||||
if (this.helloTimer) clearTimeout(this.helloTimer);
|
||||
this.helloTimer = null;
|
||||
this.setStatus("open");
|
||||
this.reconnectAttempt = 0;
|
||||
this.flushOutbound();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.handleServerMessage(msg);
|
||||
};
|
||||
|
||||
const onClose = (): void => {
|
||||
if (this.helloTimer) clearTimeout(this.helloTimer);
|
||||
this.helloTimer = null;
|
||||
this.ws = null;
|
||||
if (this._status !== "open" && this._status !== "reconnecting") {
|
||||
reject(new Error("ws closed before hello_ack"));
|
||||
}
|
||||
if (!this.closed) this.scheduleReconnect();
|
||||
else this.setStatus("closed");
|
||||
};
|
||||
|
||||
const onError = (err: Error): void => {
|
||||
this.debug(`ws error: ${err.message}`);
|
||||
};
|
||||
|
||||
ws.on("open", onOpen);
|
||||
ws.on("message", onMessage);
|
||||
ws.on("close", onClose);
|
||||
ws.on("error", onError);
|
||||
});
|
||||
}
|
||||
|
||||
/** Fire-and-wait send: resolves when broker acks. */
|
||||
async send(
|
||||
targetSpec: string,
|
||||
message: string,
|
||||
priority: Priority = "next",
|
||||
): Promise<{ ok: boolean; messageId?: string; error?: string }> {
|
||||
const id = randomId();
|
||||
const nonce = randomNonce();
|
||||
const ciphertext = Buffer.from(message, "utf-8").toString("base64");
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (this.pendingSends.size >= MAX_QUEUED) {
|
||||
resolve({ ok: false, error: "outbound queue full" });
|
||||
return;
|
||||
}
|
||||
this.pendingSends.set(id, {
|
||||
id,
|
||||
targetSpec,
|
||||
priority,
|
||||
nonce,
|
||||
ciphertext,
|
||||
resolve,
|
||||
});
|
||||
const dispatch = (): void => {
|
||||
if (!this.ws || this.ws.readyState !== this.ws.OPEN) return;
|
||||
this.ws.send(
|
||||
JSON.stringify({
|
||||
type: "send",
|
||||
id,
|
||||
targetSpec,
|
||||
priority,
|
||||
nonce,
|
||||
ciphertext,
|
||||
}),
|
||||
);
|
||||
};
|
||||
if (this._status === "open") dispatch();
|
||||
else {
|
||||
// Queue the dispatch closure; flushed on (re)connect.
|
||||
if (this.outbound.length >= MAX_QUEUED) {
|
||||
this.pendingSends.delete(id);
|
||||
resolve({ ok: false, error: "outbound queue full" });
|
||||
return;
|
||||
}
|
||||
this.outbound.push(dispatch);
|
||||
}
|
||||
// Ack timeout: 10s to hear back.
|
||||
setTimeout(() => {
|
||||
if (this.pendingSends.has(id)) {
|
||||
this.pendingSends.delete(id);
|
||||
resolve({ ok: false, error: "ack timeout" });
|
||||
}
|
||||
}, 10_000);
|
||||
});
|
||||
}
|
||||
|
||||
/** Subscribe to inbound pushes. Returns an unsubscribe function. */
|
||||
onPush(handler: PushHandler): () => void {
|
||||
this.pushHandlers.add(handler);
|
||||
return () => this.pushHandlers.delete(handler);
|
||||
}
|
||||
|
||||
/** Drain the buffered push history (used by check_messages tool). */
|
||||
drainPushBuffer(): InboundPush[] {
|
||||
const drained = this.pushBuffer.slice();
|
||||
this.pushBuffer.length = 0;
|
||||
return drained;
|
||||
}
|
||||
|
||||
/** Send a manual status override. Fire-and-forget (no ack). */
|
||||
async setStatus(status: "idle" | "working" | "dnd"): Promise<void> {
|
||||
if (!this.ws || this.ws.readyState !== this.ws.OPEN) return;
|
||||
this.ws.send(JSON.stringify({ type: "set_status", status }));
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.closed = true;
|
||||
if (this.helloTimer) clearTimeout(this.helloTimer);
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
||||
if (this.ws) {
|
||||
try {
|
||||
this.ws.close();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
this.setStatus("closed");
|
||||
}
|
||||
|
||||
// --- Internals ---
|
||||
|
||||
private handleServerMessage(msg: Record<string, unknown>): void {
|
||||
if (msg.type === "ack") {
|
||||
const pending = this.pendingSends.get(String(msg.id ?? ""));
|
||||
if (pending) {
|
||||
pending.resolve({
|
||||
ok: true,
|
||||
messageId: String(msg.messageId ?? ""),
|
||||
});
|
||||
this.pendingSends.delete(pending.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (msg.type === "push") {
|
||||
const push: InboundPush = {
|
||||
messageId: String(msg.messageId ?? ""),
|
||||
meshId: String(msg.meshId ?? ""),
|
||||
senderPubkey: String(msg.senderPubkey ?? ""),
|
||||
priority: (msg.priority as Priority) ?? "next",
|
||||
nonce: String(msg.nonce ?? ""),
|
||||
ciphertext: String(msg.ciphertext ?? ""),
|
||||
createdAt: String(msg.createdAt ?? ""),
|
||||
receivedAt: new Date().toISOString(),
|
||||
};
|
||||
this.pushBuffer.push(push);
|
||||
// Cap buffer at 500 entries to avoid unbounded growth.
|
||||
if (this.pushBuffer.length > 500) this.pushBuffer.shift();
|
||||
for (const h of this.pushHandlers) {
|
||||
try {
|
||||
h(push);
|
||||
} catch {
|
||||
/* handler errors are not the transport's problem */
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (msg.type === "error") {
|
||||
this.debug(`broker error: ${msg.code} ${msg.message}`);
|
||||
const id = msg.id ? String(msg.id) : null;
|
||||
if (id) {
|
||||
const pending = this.pendingSends.get(id);
|
||||
if (pending) {
|
||||
pending.resolve({
|
||||
ok: false,
|
||||
error: `${msg.code}: ${msg.message}`,
|
||||
});
|
||||
this.pendingSends.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private flushOutbound(): void {
|
||||
const queued = this.outbound.slice();
|
||||
this.outbound.length = 0;
|
||||
for (const send of queued) send();
|
||||
}
|
||||
|
||||
private scheduleReconnect(): void {
|
||||
this.setStatus("reconnecting");
|
||||
const delay =
|
||||
BACKOFF_CAPS[Math.min(this.reconnectAttempt, BACKOFF_CAPS.length - 1)]!;
|
||||
this.reconnectAttempt += 1;
|
||||
this.debug(
|
||||
`reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`,
|
||||
);
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
if (this.closed) return;
|
||||
this.connect().catch((e) => {
|
||||
this.debug(`reconnect failed: ${e instanceof Error ? e.message : e}`);
|
||||
});
|
||||
}, delay);
|
||||
}
|
||||
|
||||
private setStatus(s: ConnStatus): void {
|
||||
if (this._status === s) return;
|
||||
this._status = s;
|
||||
this.opts.onStatusChange?.(s);
|
||||
}
|
||||
|
||||
private debug(msg: string): void {
|
||||
if (this.opts.debug) console.error(`[broker-client] ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
function randomId(): string {
|
||||
return randomBytes(8).toString("hex");
|
||||
}
|
||||
|
||||
function randomNonce(): string {
|
||||
// 24-byte nonce layout (compatible with libsodium crypto_secretbox later)
|
||||
return randomBytes(24).toString("base64");
|
||||
}
|
||||
55
apps/cli/src/ws/manager.ts
Normal file
55
apps/cli/src/ws/manager.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Process-wide registry of BrokerClient connections, keyed by meshId.
|
||||
*
|
||||
* The MCP server lazily starts a client per joined mesh on startup,
|
||||
* keeps them alive for the life of the process, and uses them to
|
||||
* service MCP tool calls.
|
||||
*/
|
||||
|
||||
import { BrokerClient } from "./client";
|
||||
import type { Config, JoinedMesh } from "../state/config";
|
||||
import { env } from "../env";
|
||||
|
||||
const clients = new Map<string, BrokerClient>();
|
||||
|
||||
/** Ensure a BrokerClient exists + is connecting/open for this mesh. */
|
||||
export async function ensureClient(mesh: JoinedMesh): Promise<BrokerClient> {
|
||||
const existing = clients.get(mesh.meshId);
|
||||
if (existing) return existing;
|
||||
const client = new BrokerClient(mesh, { debug: env.CLAUDEMESH_DEBUG });
|
||||
clients.set(mesh.meshId, client);
|
||||
try {
|
||||
await client.connect();
|
||||
} catch {
|
||||
// Connect failed → client is in "reconnecting" state, leave it
|
||||
// wired so tool calls can surface the status.
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/** Start clients for every joined mesh. Called once on MCP server start. */
|
||||
export async function startClients(config: Config): Promise<void> {
|
||||
await Promise.allSettled(config.meshes.map(ensureClient));
|
||||
}
|
||||
|
||||
/** Look up a client by mesh slug (human-friendly) or meshId. */
|
||||
export function findClient(needle: string): BrokerClient | null {
|
||||
// Try meshId first, then slug.
|
||||
const byId = clients.get(needle);
|
||||
if (byId) return byId;
|
||||
for (const c of clients.values()) {
|
||||
if (c.meshSlug === needle) return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** All clients across all meshes. */
|
||||
export function allClients(): BrokerClient[] {
|
||||
return [...clients.values()];
|
||||
}
|
||||
|
||||
/** Close every client (shutdown hook). */
|
||||
export function stopAll(): void {
|
||||
for (const c of clients.values()) c.close();
|
||||
clients.clear();
|
||||
}
|
||||
15
apps/cli/tsconfig.json
Normal file
15
apps/cli/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@turbostarter/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["es2022"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
},
|
||||
"types": ["bun-types"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -25,7 +25,7 @@ ENV NEXT_PUBLIC_URL=$NEXT_PUBLIC_URL
|
||||
ENV NEXT_PUBLIC_PRODUCT_NAME=$NEXT_PUBLIC_PRODUCT_NAME
|
||||
ENV NEXT_PUBLIC_DEFAULT_LOCALE=$NEXT_PUBLIC_DEFAULT_LOCALE
|
||||
|
||||
RUN npx turbo run build --filter=@claudemesh/web... --filter=web...
|
||||
RUN npx turbo run build --filter=web...
|
||||
|
||||
# Stage 2: runtime — standalone output only
|
||||
FROM node:22-slim AS runner
|
||||
|
||||
428
pnpm-lock.yaml
generated
428
pnpm-lock.yaml
generated
@@ -161,6 +161,52 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(@vitest/ui@4.0.14)(jiti@2.6.1)(jsdom@26.0.0)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.19.2)(yaml@2.8.0)
|
||||
|
||||
apps/cli:
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: 1.27.1
|
||||
version: 1.27.1(zod@4.1.13)
|
||||
libsodium-wrappers:
|
||||
specifier: 0.7.15
|
||||
version: 0.7.15
|
||||
ws:
|
||||
specifier: 8.20.0
|
||||
version: 8.20.0
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.13
|
||||
devDependencies:
|
||||
'@turbostarter/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/eslint
|
||||
'@turbostarter/prettier-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/prettier
|
||||
'@turbostarter/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/typescript
|
||||
'@turbostarter/vitest-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../tooling/vitest
|
||||
'@types/libsodium-wrappers':
|
||||
specifier: 0.7.14
|
||||
version: 0.7.14
|
||||
'@types/ws':
|
||||
specifier: 8.5.13
|
||||
version: 8.5.13
|
||||
eslint:
|
||||
specifier: 'catalog:'
|
||||
version: 9.39.0(jiti@2.6.1)
|
||||
prettier:
|
||||
specifier: 'catalog:'
|
||||
version: 3.6.2
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(@vitest/ui@4.0.14)(jiti@2.6.1)(jsdom@26.0.0)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.19.2)(yaml@2.8.0)
|
||||
|
||||
apps/web:
|
||||
dependencies:
|
||||
'@ai-sdk/react':
|
||||
@@ -3461,6 +3507,12 @@ packages:
|
||||
'@hexagon/base64@1.1.28':
|
||||
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
|
||||
|
||||
'@hono/node-server@1.19.12':
|
||||
resolution: {integrity: sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
hono: ^4
|
||||
|
||||
'@hono/zod-validator@0.7.4':
|
||||
resolution: {integrity: sha512-biKGn3BRJVaftZlIPMyK+HCe/UHAjJ6sH0UyXe3+v0OcgVr9xfImDROTJFLtn9e3XEEAHGZIM9U6evu85abm8Q==}
|
||||
peerDependencies:
|
||||
@@ -3835,6 +3887,16 @@ packages:
|
||||
'@mixpanel/rrweb@2.0.0-alpha.18.2':
|
||||
resolution: {integrity: sha512-J3dVTEu6Z4p8di7y9KKvUooNuBjX97DdG6XGWoPEPi07A9512h9M8MEtvlY3mK0PGfuC0Mz5Pv/Ws6gjGYfKQg==}
|
||||
|
||||
'@modelcontextprotocol/sdk@1.27.1':
|
||||
resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@cfworker/json-schema': ^4.1.1
|
||||
zod: ^3.25 || ^4.0
|
||||
peerDependenciesMeta:
|
||||
'@cfworker/json-schema':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.88':
|
||||
resolution: {integrity: sha512-KEaClPnZuVxJ8smUWjV1wWFkByBO/D+vy4lN+Dm5DFH514oqwukxKGeck9xcKJhaWJGjfruGmYGiwRe//+/zQQ==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -7297,6 +7359,10 @@ packages:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
accepts@2.0.0:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
acorn-import-attributes@1.9.5:
|
||||
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
|
||||
peerDependencies:
|
||||
@@ -7354,6 +7420,14 @@ packages:
|
||||
ajv:
|
||||
optional: true
|
||||
|
||||
ajv-formats@3.0.1:
|
||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
|
||||
ajv-keywords@5.1.0:
|
||||
resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
|
||||
peerDependencies:
|
||||
@@ -7686,6 +7760,10 @@ packages:
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
body-parser@2.2.2:
|
||||
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
@@ -7986,6 +8064,14 @@ packages:
|
||||
constant-case@2.0.0:
|
||||
resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==}
|
||||
|
||||
content-disposition@1.0.1:
|
||||
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
content-type@1.0.5:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
conventional-changelog-angular@7.0.0:
|
||||
resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -8005,6 +8091,10 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cookie-signature@1.2.2:
|
||||
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
|
||||
engines: {node: '>=6.6.0'}
|
||||
|
||||
cookie@0.7.2:
|
||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -8230,6 +8320,15 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -8844,6 +8943,10 @@ packages:
|
||||
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
eventsource@3.0.7:
|
||||
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
exec-async@2.2.0:
|
||||
resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==}
|
||||
|
||||
@@ -8988,6 +9091,16 @@ packages:
|
||||
exponential-backoff@3.1.2:
|
||||
resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==}
|
||||
|
||||
express-rate-limit@8.3.2:
|
||||
resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
express: '>= 4.11'
|
||||
|
||||
express@5.2.1:
|
||||
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
exsolve@1.0.7:
|
||||
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
|
||||
|
||||
@@ -9089,6 +9202,10 @@ packages:
|
||||
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
finalhandler@2.1.1:
|
||||
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
|
||||
find-cache-dir@3.3.2:
|
||||
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -9137,6 +9254,10 @@ packages:
|
||||
forwarded-parse@2.1.2:
|
||||
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@@ -9176,6 +9297,10 @@ packages:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
fresh@2.0.0:
|
||||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
fromentries@1.3.2:
|
||||
resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==}
|
||||
|
||||
@@ -9442,6 +9567,10 @@ packages:
|
||||
resolution: {integrity: sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
hono@4.12.10:
|
||||
resolution: {integrity: sha512-mx/p18PLy5og9ufies2GOSUqep98Td9q4i/EF6X7yJgAiIopxqdfIO3jbqsi3jRgTgw88jMDEzVKi+V2EF+27w==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
hosted-git-info@7.0.2:
|
||||
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
|
||||
engines: {node: ^16.14.0 || >=18.0.0}
|
||||
@@ -9473,6 +9602,10 @@ packages:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
http-errors@2.0.1:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -9519,6 +9652,10 @@ packages:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
@@ -9607,10 +9744,18 @@ packages:
|
||||
invariant@2.2.4:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
|
||||
ip-address@10.1.0:
|
||||
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
ip-address@9.0.5:
|
||||
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
is-alphabetical@2.0.1:
|
||||
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
|
||||
|
||||
@@ -9744,6 +9889,9 @@ packages:
|
||||
is-potential-custom-element-name@1.0.1:
|
||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||
|
||||
is-promise@4.0.0:
|
||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||
|
||||
is-reference@1.2.1:
|
||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||
|
||||
@@ -9932,6 +10080,9 @@ packages:
|
||||
jose@6.1.0:
|
||||
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
|
||||
|
||||
jose@6.2.2:
|
||||
resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -9987,6 +10138,9 @@ packages:
|
||||
json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
|
||||
json-schema-typed@8.0.2:
|
||||
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
|
||||
|
||||
json-schema@0.4.0:
|
||||
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
||||
|
||||
@@ -10370,6 +10524,10 @@ packages:
|
||||
mdn-data@2.0.30:
|
||||
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||
|
||||
media-typer@1.1.0:
|
||||
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
memoize-one@5.2.1:
|
||||
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
||||
|
||||
@@ -10380,6 +10538,10 @@ packages:
|
||||
resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
|
||||
engines: {node: '>=16.10'}
|
||||
|
||||
merge-descriptors@2.0.0:
|
||||
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
merge-options@3.0.4:
|
||||
resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -10556,6 +10718,10 @@ packages:
|
||||
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.2:
|
||||
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
mime@1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -11089,6 +11255,9 @@ packages:
|
||||
resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
path-to-regexp@8.4.2:
|
||||
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -11173,6 +11342,10 @@ packages:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
pkce-challenge@5.0.1:
|
||||
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||
engines: {node: '>=16.20.0'}
|
||||
|
||||
pkg-dir@4.2.0:
|
||||
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -11446,6 +11619,10 @@ packages:
|
||||
property-information@7.1.0:
|
||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
proxy-agent@6.5.0:
|
||||
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -11481,6 +11658,10 @@ packages:
|
||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
qs@6.15.0:
|
||||
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
query-string@7.1.3:
|
||||
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -11514,6 +11695,10 @@ packages:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
raw-body@3.0.2:
|
||||
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
rc@1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
@@ -11962,6 +12147,10 @@ packages:
|
||||
rou3@0.7.10:
|
||||
resolution: {integrity: sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww==}
|
||||
|
||||
router@2.2.0:
|
||||
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
rrweb-cssom@0.8.0:
|
||||
resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
|
||||
|
||||
@@ -12054,6 +12243,10 @@ packages:
|
||||
resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
send@1.2.1:
|
||||
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
sentence-case@2.1.1:
|
||||
resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==}
|
||||
|
||||
@@ -12068,6 +12261,10 @@ packages:
|
||||
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
serve-static@2.2.1:
|
||||
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
server-only@0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
|
||||
@@ -12294,6 +12491,10 @@ packages:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
statuses@2.0.2:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
std-env@3.10.0:
|
||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||
|
||||
@@ -12745,6 +12946,10 @@ packages:
|
||||
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
type-is@2.0.1:
|
||||
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -13362,6 +13567,11 @@ packages:
|
||||
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
zod-to-json-schema@3.25.2:
|
||||
resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
|
||||
peerDependencies:
|
||||
zod: ^3.25.28 || ^4
|
||||
|
||||
zod@3.24.3:
|
||||
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
|
||||
|
||||
@@ -16024,6 +16234,10 @@ snapshots:
|
||||
|
||||
'@hexagon/base64@1.1.28': {}
|
||||
|
||||
'@hono/node-server@1.19.12(hono@4.12.10)':
|
||||
dependencies:
|
||||
hono: 4.12.10
|
||||
|
||||
'@hono/zod-validator@0.7.4(hono@4.10.4)(zod@4.1.13)':
|
||||
dependencies:
|
||||
hono: 4.10.4
|
||||
@@ -16367,6 +16581,28 @@ snapshots:
|
||||
base64-arraybuffer: 1.0.2
|
||||
mitt: 3.0.1
|
||||
|
||||
'@modelcontextprotocol/sdk@1.27.1(zod@4.1.13)':
|
||||
dependencies:
|
||||
'@hono/node-server': 1.19.12(hono@4.12.10)
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.5
|
||||
cross-spawn: 7.0.6
|
||||
eventsource: 3.0.7
|
||||
eventsource-parser: 3.0.6
|
||||
express: 5.2.1
|
||||
express-rate-limit: 8.3.2(express@5.2.1)
|
||||
hono: 4.12.10
|
||||
jose: 6.2.2
|
||||
json-schema-typed: 8.0.2
|
||||
pkce-challenge: 5.0.1
|
||||
raw-body: 3.0.2
|
||||
zod: 4.1.13
|
||||
zod-to-json-schema: 3.25.2(zod@4.1.13)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.88':
|
||||
optional: true
|
||||
|
||||
@@ -20696,6 +20932,11 @@ snapshots:
|
||||
mime-types: 2.1.35
|
||||
negotiator: 0.6.3
|
||||
|
||||
accepts@2.0.0:
|
||||
dependencies:
|
||||
mime-types: 3.0.1
|
||||
negotiator: 1.0.0
|
||||
|
||||
acorn-import-attributes@1.9.5(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -20747,6 +20988,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
ajv: 8.17.1
|
||||
|
||||
ajv-formats@3.0.1(ajv@8.17.1):
|
||||
optionalDependencies:
|
||||
ajv: 8.17.1
|
||||
|
||||
ajv-keywords@5.1.0(ajv@8.17.1):
|
||||
dependencies:
|
||||
ajv: 8.17.1
|
||||
@@ -21147,6 +21392,20 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
body-parser@2.2.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 1.0.5
|
||||
debug: 4.4.3
|
||||
http-errors: 2.0.0
|
||||
iconv-lite: 0.7.2
|
||||
on-finished: 2.4.1
|
||||
qs: 6.15.0
|
||||
raw-body: 3.0.2
|
||||
type-is: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
bowser@2.11.0: {}
|
||||
@@ -21197,8 +21456,7 @@ snapshots:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
bytes@3.1.2:
|
||||
optional: true
|
||||
bytes@3.1.2: {}
|
||||
|
||||
caching-transform@4.0.0:
|
||||
dependencies:
|
||||
@@ -21486,6 +21744,10 @@ snapshots:
|
||||
snake-case: 2.1.0
|
||||
upper-case: 1.1.3
|
||||
|
||||
content-disposition@1.0.1: {}
|
||||
|
||||
content-type@1.0.5: {}
|
||||
|
||||
conventional-changelog-angular@7.0.0:
|
||||
dependencies:
|
||||
compare-func: 2.0.0
|
||||
@@ -21505,6 +21767,8 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie-signature@1.2.2: {}
|
||||
|
||||
cookie@0.7.2: {}
|
||||
|
||||
core-js-compat@3.44.0:
|
||||
@@ -21706,6 +21970,10 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
@@ -22462,6 +22730,10 @@ snapshots:
|
||||
|
||||
eventsource-parser@3.0.6: {}
|
||||
|
||||
eventsource@3.0.7:
|
||||
dependencies:
|
||||
eventsource-parser: 3.0.6
|
||||
|
||||
exec-async@2.2.0:
|
||||
optional: true
|
||||
|
||||
@@ -22660,6 +22932,44 @@ snapshots:
|
||||
|
||||
exponential-backoff@3.1.2: {}
|
||||
|
||||
express-rate-limit@8.3.2(express@5.2.1):
|
||||
dependencies:
|
||||
express: 5.2.1
|
||||
ip-address: 10.1.0
|
||||
|
||||
express@5.2.1:
|
||||
dependencies:
|
||||
accepts: 2.0.0
|
||||
body-parser: 2.2.2
|
||||
content-disposition: 1.0.1
|
||||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
cookie-signature: 1.2.2
|
||||
debug: 4.4.1
|
||||
depd: 2.0.0
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 2.1.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.0
|
||||
merge-descriptors: 2.0.0
|
||||
mime-types: 3.0.1
|
||||
on-finished: 2.4.1
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.14.0
|
||||
range-parser: 1.2.1
|
||||
router: 2.2.0
|
||||
send: 1.2.1
|
||||
serve-static: 2.2.1
|
||||
statuses: 2.0.1
|
||||
type-is: 2.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
exsolve@1.0.7: {}
|
||||
|
||||
extend@3.0.2: {}
|
||||
@@ -22772,6 +23082,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
finalhandler@2.1.1:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
statuses: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
find-cache-dir@3.3.2:
|
||||
dependencies:
|
||||
commondir: 1.0.1
|
||||
@@ -22830,6 +23151,8 @@ snapshots:
|
||||
|
||||
forwarded-parse@2.1.2: {}
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
framer-motion@12.23.24(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
@@ -22855,6 +23178,8 @@ snapshots:
|
||||
|
||||
fresh@0.5.2: {}
|
||||
|
||||
fresh@2.0.0: {}
|
||||
|
||||
fromentries@1.3.2: {}
|
||||
|
||||
fs-constants@1.0.0:
|
||||
@@ -23218,6 +23543,8 @@ snapshots:
|
||||
|
||||
hono@4.10.4: {}
|
||||
|
||||
hono@4.12.10: {}
|
||||
|
||||
hosted-git-info@7.0.2:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
@@ -23260,6 +23587,14 @@ snapshots:
|
||||
statuses: 2.0.1
|
||||
toidentifier: 1.0.1
|
||||
|
||||
http-errors@2.0.1:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
@@ -23310,6 +23645,10 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
@@ -23415,11 +23754,15 @@ snapshots:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
ip-address@10.1.0: {}
|
||||
|
||||
ip-address@9.0.5:
|
||||
dependencies:
|
||||
jsbn: 1.1.0
|
||||
sprintf-js: 1.1.3
|
||||
|
||||
ipaddr.js@1.9.1: {}
|
||||
|
||||
is-alphabetical@2.0.1: {}
|
||||
|
||||
is-alphanumerical@2.0.1:
|
||||
@@ -23533,6 +23876,8 @@ snapshots:
|
||||
|
||||
is-potential-custom-element-name@1.0.1: {}
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
|
||||
is-reference@1.2.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
@@ -23776,6 +24121,8 @@ snapshots:
|
||||
|
||||
jose@6.1.0: {}
|
||||
|
||||
jose@6.2.2: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
@@ -23838,6 +24185,8 @@ snapshots:
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
|
||||
json-schema-typed@8.0.2: {}
|
||||
|
||||
json-schema@0.4.0: {}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||
@@ -24281,6 +24630,8 @@ snapshots:
|
||||
|
||||
mdn-data@2.0.30: {}
|
||||
|
||||
media-typer@1.1.0: {}
|
||||
|
||||
memoize-one@5.2.1: {}
|
||||
|
||||
memoize-one@6.0.0:
|
||||
@@ -24288,6 +24639,8 @@ snapshots:
|
||||
|
||||
meow@12.1.1: {}
|
||||
|
||||
merge-descriptors@2.0.0: {}
|
||||
|
||||
merge-options@3.0.4:
|
||||
dependencies:
|
||||
is-plain-obj: 2.1.0
|
||||
@@ -24689,6 +25042,10 @@ snapshots:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
mime-types@3.0.2:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
mime@1.6.0: {}
|
||||
|
||||
mimic-fn@1.2.0:
|
||||
@@ -25340,6 +25697,8 @@ snapshots:
|
||||
lru-cache: 11.1.0
|
||||
minipass: 7.1.2
|
||||
|
||||
path-to-regexp@8.4.2: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
@@ -25424,6 +25783,8 @@ snapshots:
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pkce-challenge@5.0.1: {}
|
||||
|
||||
pkg-dir@4.2.0:
|
||||
dependencies:
|
||||
find-up: 4.1.0
|
||||
@@ -25626,6 +25987,11 @@ snapshots:
|
||||
|
||||
property-information@7.1.0: {}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
proxy-agent@6.5.0:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
@@ -25666,6 +26032,10 @@ snapshots:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
qs@6.15.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
query-string@7.1.3:
|
||||
dependencies:
|
||||
decode-uri-component: 0.2.2
|
||||
@@ -25751,6 +26121,13 @@ snapshots:
|
||||
|
||||
range-parser@1.2.1: {}
|
||||
|
||||
raw-body@3.0.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
unpipe: 1.0.0
|
||||
|
||||
rc@1.2.8:
|
||||
dependencies:
|
||||
deep-extend: 0.6.0
|
||||
@@ -26556,6 +26933,16 @@ snapshots:
|
||||
|
||||
rou3@0.7.10: {}
|
||||
|
||||
router@2.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
depd: 2.0.0
|
||||
is-promise: 4.0.0
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 8.4.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
rrweb-cssom@0.8.0: {}
|
||||
|
||||
rtl-detect@1.1.2:
|
||||
@@ -26670,6 +27057,22 @@ snapshots:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
send@1.2.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
mime-types: 3.0.2
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.2.1
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
sentence-case@2.1.1:
|
||||
dependencies:
|
||||
no-case: 2.3.2
|
||||
@@ -26690,6 +27093,15 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
serve-static@2.2.1:
|
||||
dependencies:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 1.2.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
server-only@0.0.1:
|
||||
optional: true
|
||||
|
||||
@@ -27012,6 +27424,8 @@ snapshots:
|
||||
|
||||
statuses@2.0.1: {}
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
stdin-discarder@0.2.2: {}
|
||||
@@ -27516,6 +27930,12 @@ snapshots:
|
||||
|
||||
type-fest@0.8.1: {}
|
||||
|
||||
type-is@2.0.1:
|
||||
dependencies:
|
||||
content-type: 1.0.5
|
||||
media-typer: 1.1.0
|
||||
mime-types: 3.0.1
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -28202,6 +28622,10 @@ snapshots:
|
||||
|
||||
yoctocolors@2.1.1: {}
|
||||
|
||||
zod-to-json-schema@3.25.2(zod@4.1.13):
|
||||
dependencies:
|
||||
zod: 4.1.13
|
||||
|
||||
zod@3.24.3: {}
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
Reference in New Issue
Block a user