Built-in design generation now runs as an agentic MCP tool-loop (reusing the agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the Settings experimental toggle; the orchestrator stays the default. - design-agent system prompt + in-process design toolset (parity-locked with the MCP surface) + flag-gated Intent::Design routing - spawn_agents execution as sequential sub-loops + live creation-mode badges (per-agent glow + 'N/M designing...' header) - new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state, export_nodes, spawn_agents; style-guide local audit - #27 AI panel restyle: rounded tool cards + green check-rings, gray user bubbles, model-pill bottom toolbar, header, empty-state pills, the PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown - multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI (switch / close / + / Cmd+T) with each run bound to its tab Large checkpoint commit spanning the working tree (Rust shell crates).
62 lines
1.7 KiB
TypeScript
62 lines
1.7 KiB
TypeScript
/**
|
|
* Nitro plugin — writes ~/.openpencil/.port on server startup so the MCP
|
|
* server can discover the running instance (dev server or Electron).
|
|
*
|
|
* In Electron production mode the main process also writes this file,
|
|
* but this plugin ensures the dev server (`bun --bun run dev`) is
|
|
* discoverable too.
|
|
*/
|
|
|
|
import { writeFile, mkdir, unlink, readFile } from 'node:fs/promises';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { join } from 'node:path';
|
|
import { homedir } from 'node:os';
|
|
const PORT_FILE_DIR = join(homedir(), '.openpencil');
|
|
const PORT_FILE_PATH = join(PORT_FILE_DIR, '.port');
|
|
const PORT_FILE_TOKEN = randomUUID();
|
|
|
|
function getOwnerPid(): number {
|
|
return process.ppid > 1 ? process.ppid : process.pid;
|
|
}
|
|
|
|
async function writePortFile(port: number): Promise<void> {
|
|
try {
|
|
await mkdir(PORT_FILE_DIR, { recursive: true });
|
|
await writeFile(
|
|
PORT_FILE_PATH,
|
|
JSON.stringify({
|
|
port,
|
|
pid: getOwnerPid(),
|
|
writerPid: process.pid,
|
|
token: PORT_FILE_TOKEN,
|
|
timestamp: Date.now(),
|
|
}),
|
|
'utf-8',
|
|
);
|
|
} catch {
|
|
// Non-critical — MCP sync will fall back to file I/O
|
|
}
|
|
}
|
|
|
|
async function cleanupPortFile(): Promise<void> {
|
|
try {
|
|
const raw = await readFile(PORT_FILE_PATH, 'utf-8');
|
|
const current = JSON.parse(raw) as { token?: string };
|
|
if (current.token !== PORT_FILE_TOKEN) return;
|
|
await unlink(PORT_FILE_PATH);
|
|
} catch {
|
|
// Ignore if already removed
|
|
}
|
|
}
|
|
|
|
export default () => {
|
|
const port = parseInt(process.env.PORT || '3000', 10);
|
|
writePortFile(port);
|
|
|
|
const cleanup = () => {
|
|
cleanupPortFile();
|
|
};
|
|
process.on('SIGINT', cleanup);
|
|
process.on('SIGTERM', cleanup);
|
|
};
|