consortium
Version:
Remote control and session sharing CLI for AI coding agents
140 lines (137 loc) • 4.71 kB
JavaScript
import { createServer } from 'http';
import { l as logger } from './types-DETLaopx.mjs';
import { v as validateMcpToken, g as generateMcpToken } from './index-DiNLHtkZ.mjs';
import 'axios';
import 'chalk';
import 'fs';
import 'node:fs';
import 'node:os';
import 'node:path';
import 'node:events';
import 'socket.io-client';
import 'zod';
import 'node:crypto';
import 'tweetnacl';
import 'child_process';
import 'util';
import 'fs/promises';
import 'crypto';
import 'path';
import 'url';
import 'os';
import 'node:child_process';
import 'node:fs/promises';
import 'node:module';
import 'node:util';
import 'expo-server-sdk';
import 'node:readline';
import 'ink';
import 'react';
import 'node:url';
import 'ps-list';
import 'cross-spawn';
import 'tmp';
import 'qrcode-terminal';
import 'open';
import 'fastify';
import 'fastify-type-provider-zod';
import '@modelcontextprotocol/sdk/client/index.js';
import '@modelcontextprotocol/sdk/client/streamableHttp.js';
import 'readline';
import '@modelcontextprotocol/sdk/server/mcp.js';
import 'node:http';
import '@modelcontextprotocol/sdk/server/streamableHttp.js';
const toolCallTimestamps = /* @__PURE__ */ new Map();
const TOOL_RATE_LIMIT = 30;
const TOOL_RATE_WINDOW = 6e4;
function checkToolRateLimit(deploymentId, toolName) {
const key = `${deploymentId}:${toolName}`;
const now = Date.now();
const stamps = toolCallTimestamps.get(key) ?? [];
const recent = stamps.filter((t) => now - t < TOOL_RATE_WINDOW);
if (recent.length >= TOOL_RATE_LIMIT) return false;
recent.push(now);
toolCallTimestamps.set(key, recent);
return true;
}
function startPluginDaemonServer(options = {}) {
const host = options.host ?? "127.0.0.1";
let resolvedPort = options.port ?? 0;
const server = createServer(async (req, res) => {
if (req.method !== "POST" || req.url !== "/tool-call") {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Not found" }));
return;
}
try {
let body;
try {
body = await readBody(req);
} catch (readErr) {
if (readErr instanceof Error && readErr.message === "Payload too large") {
res.writeHead(413, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Payload too large" }));
return;
}
throw readErr;
}
const parsed = JSON.parse(body);
const authHeader = req.headers["authorization"] ?? "";
const token = authHeader.replace(/^Bearer\s+/, "");
if (!validateMcpToken(token, parsed.deploymentId)) {
res.writeHead(401, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Invalid or expired token" }));
return;
}
if (!checkToolRateLimit(parsed.deploymentId, parsed.toolName)) {
logger.debug(`[PLUGIN-DAEMON] Rate limit exceeded for ${parsed.deploymentId}:${parsed.toolName}`);
res.writeHead(429, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: `Rate limit exceeded for tool "${parsed.toolName}"` }));
return;
}
logger.debug(`[PLUGIN-DAEMON] Tool call: ${parsed.deploymentId}:${parsed.toolName}`);
res.writeHead(501, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "error", error: "Tool execution not yet implemented" }));
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
logger.debug(`[PLUGIN-DAEMON] Error handling tool call: ${message}`);
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: message }));
}
});
server.listen(resolvedPort, host, () => {
const addr = server.address();
if (addr && typeof addr === "object") {
resolvedPort = addr.port;
}
logger.debug(`[PLUGIN-DAEMON] Server listening on ${host}:${resolvedPort}`);
});
return {
get port() {
return resolvedPort;
},
stop: () => new Promise((resolve, reject) => {
server.close((err) => err ? reject(err) : resolve());
}),
generateTokenForDeployment: (deploymentId) => generateMcpToken(deploymentId)
};
}
const MAX_BODY_SIZE = 1048576;
function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
let totalBytes = 0;
req.on("data", (chunk) => {
totalBytes += chunk.length;
if (totalBytes > MAX_BODY_SIZE) {
req.destroy();
reject(new Error("Payload too large"));
return;
}
chunks.push(chunk);
});
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
req.on("error", reject);
});
}
export { startPluginDaemonServer };