From f97122135d95d1ea09c62b13260e367bd529c62e Mon Sep 17 00:00:00 2001 From: MrPiglr <31398225+MrPiglr@users.noreply.github.com> Date: Wed, 24 Dec 2025 04:41:58 +0000 Subject: [PATCH] modified: server/routes.ts --- client/src/pages/terminal.tsx | 118 ++++++++++++++++++++++++++++++++++ server/routes.ts | 13 ++++ 2 files changed, 131 insertions(+) diff --git a/client/src/pages/terminal.tsx b/client/src/pages/terminal.tsx index 9b2eea6..432c2e0 100644 --- a/client/src/pages/terminal.tsx +++ b/client/src/pages/terminal.tsx @@ -5,6 +5,7 @@ import { useLabTerminal } from "@/hooks/use-lab-terminal"; import { ArrowLeft, Terminal as TerminalIcon, Copy, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; interface TerminalLine { type: 'input' | 'output' | 'error' | 'system'; @@ -21,6 +22,18 @@ export default function Terminal() { }, ]); + const [cliCommand, setCliCommand] = useState("build"); + const [cliStatus, setCliStatus] = useState<"idle" | "running" | "done" | "error">("idle"); + const [cliLabel, setCliLabel] = useState(""); + const eventSourceRef = useRef(null); + const currentRunId = useRef(null); + + useEffect(() => { + return () => { + eventSourceRef.current?.close(); + }; + }, []); + const [input, setInput] = useState(""); const [commandHistory, setCommandHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); @@ -154,6 +167,74 @@ export default function Terminal() { setHistoryIndex(-1); }; + const appendCliLine = (type: TerminalLine['type'], content: string) => { + setLines((prev) => [...prev, { type, content, timestamp: Date.now() }]); + }; + + const stopCli = async () => { + if (!currentRunId.current) return; + try { + await fetch(`/api/admin/cli/stop/${currentRunId.current}`, { method: "POST", credentials: "include" }); + } catch (_) { + // ignore + } + eventSourceRef.current?.close(); + eventSourceRef.current = null; + currentRunId.current = null; + setCliStatus("idle"); + }; + + const startCli = async () => { + if (cliStatus === "running") return; + setCliStatus("running"); + appendCliLine('system', `▸ Running ${cliCommand}...`); + + try { + const res = await fetch('/api/admin/cli/start', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ command: cliCommand }) + }); + + if (!res.ok) { + const text = await res.text(); + appendCliLine('error', `Start failed: ${text || res.status}`); + setCliStatus("error"); + return; + } + + const data = await res.json(); + currentRunId.current = data.id; + setCliLabel(data.label); + + const es = new EventSource(`/api/admin/cli/stream/${data.id}`, { withCredentials: true } as any); + eventSourceRef.current = es; + + es.addEventListener('message', (evt) => { + appendCliLine('output', evt.data); + }); + + es.addEventListener('error', (evt) => { + appendCliLine('error', 'Stream error'); + setCliStatus("error"); + es.close(); + }); + + es.addEventListener('done', (evt) => { + const status = evt.data === 'success' ? 'done' : 'error'; + setCliStatus(status as any); + appendCliLine(status === 'done' ? 'system' : 'error', `▸ ${cliLabel || cliCommand} ${status}`); + es.close(); + currentRunId.current = null; + }); + + } catch (err) { + appendCliLine('error', 'Failed to start CLI command'); + setCliStatus("error"); + } + }; + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { processCommand(input); @@ -216,6 +297,43 @@ export default function Terminal() { {/* Terminal Output */} + + {/* Admin CLI runner */} +
+
AeThex CLI
+ + + +
+ Status: {cliStatus.toUpperCase()} +
+
{lines.map((line, i) => ( { + + // ===== Admin CLI process registry ===== + const CLI_ALLOWLIST: Record = { + build: { cmd: "npm", args: ["run", "build"], label: "npm run build" }, + "migrate-status": { cmd: "npx", args: ["drizzle-kit", "status"], label: "drizzle status" }, + migrate: { cmd: "npx", args: ["drizzle-kit", "migrate:push"], label: "drizzle migrate" }, + seed: { cmd: "npx", args: ["ts-node", "script/seed.ts"], label: "seed" }, + test: { cmd: "bash", args: ["./test-implementation.sh"], label: "test-implementation" }, + }; + + const cliProcesses = new Map(); // Apply capability guard to Hub and OS routes app.use("/api/hub/*", capabilityGuard);