Add headless puppet bridge: control socket, renderer handlers, MCP daemon
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Veskslop Phase 0 spike — mock Vesktop control socket.
|
||||
*
|
||||
* Stands in for the real Vesktop main process so the MCP control bridge can be
|
||||
* developed and tested without Electron. Implements the JSON-lines protocol.
|
||||
*
|
||||
* Usage:
|
||||
* VESKSLOP_SOCKET=/tmp/veskslop-mock.sock pnpm control:mock
|
||||
*/
|
||||
|
||||
import { rmSync } from "node:fs";
|
||||
import { createServer } from "node:net";
|
||||
|
||||
import { socketPath } from "./vesktop-socket.js";
|
||||
|
||||
const FAKE_GUILDS = [
|
||||
{
|
||||
id: "100000000000000001",
|
||||
name: "Mock Guild",
|
||||
channels: [
|
||||
{ id: "200000000000000001", name: "general", type: 0 },
|
||||
{ id: "200000000000000002", name: "voice", type: 2 }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const FAKE_MESSAGES = [
|
||||
{ id: "300000000000000001", author: { id: "1", username: "mockuser" }, content: "hello from the mock", timestamp: new Date().toISOString() }
|
||||
];
|
||||
|
||||
function handle(command: string | undefined, data: any) {
|
||||
switch (command) {
|
||||
case "ping":
|
||||
return "pong";
|
||||
case "state:get":
|
||||
return { user: { id: "1", username: "mockuser" }, guilds: FAKE_GUILDS };
|
||||
case "message:send":
|
||||
return { channelId: data?.channelId, content: data?.content, sent: true };
|
||||
case "message:read":
|
||||
return { channelId: data?.channelId, cached: FAKE_MESSAGES.length, messages: FAKE_MESSAGES };
|
||||
case "dm:send":
|
||||
return { userId: data?.userId, channelId: `dm-${data?.userId}`, content: data?.content, sent: true };
|
||||
default:
|
||||
throw new Error(`Unknown command: ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
rmSync(socketPath, { force: true });
|
||||
|
||||
const server = createServer(socket => {
|
||||
socket.setEncoding("utf-8");
|
||||
let buffer = "";
|
||||
|
||||
socket.on("data", (chunk: string) => {
|
||||
buffer += chunk;
|
||||
|
||||
let newline: number;
|
||||
while ((newline = buffer.indexOf("\n")) !== -1) {
|
||||
const line = buffer.slice(0, newline).trim();
|
||||
buffer = buffer.slice(newline + 1);
|
||||
if (!line) continue;
|
||||
|
||||
const start = Date.now();
|
||||
try {
|
||||
const request = JSON.parse(line);
|
||||
const result = handle(request.command, request.data);
|
||||
console.log(`[mock] ${new Date().toISOString()} ${request.command} (${Date.now() - start}ms)`);
|
||||
socket.write(JSON.stringify({ id: request.id, ok: true, data: result }) + "\n");
|
||||
} catch (e) {
|
||||
const error = e instanceof Error ? e.message : String(e);
|
||||
console.log(`[mock] ${new Date().toISOString()} ERROR ${error}`);
|
||||
socket.write(JSON.stringify({ ok: false, error }) + "\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", () => socket.destroy());
|
||||
});
|
||||
|
||||
server.listen(socketPath, () => {
|
||||
console.log(`[mock] listening on ${socketPath}`);
|
||||
});
|
||||
|
||||
process.on("SIGINT", () => {
|
||||
server.close();
|
||||
rmSync(socketPath, { force: true });
|
||||
process.exit(0);
|
||||
});
|
||||
Reference in New Issue
Block a user