mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:27:19 +00:00
Major Features: - Custom .aethex programming language with cross-platform compilation - Compiles to JavaScript, Lua (Roblox), Verse (UEFN), and C# (Unity) - Built-in COPPA compliance and PII detection for safe metaverse development Integration Points: 1. Terminal Integration - Added 'aethex' command for in-terminal compilation - Support for all compilation targets with --target flag - Real-time error reporting and syntax highlighting 2. IDE Integration - Native .aethex file support in Monaco editor - One-click compilation with target selector - Download compiled code functionality - Two example files: hello.aethex and auth.aethex 3. Curriculum Integration - New "AeThex Language" section in Foundry tech tree - Three modules: Realities & Journeys, Cross-Platform Sync, COPPA Compliance - Certification path for students 4. Documentation Site - Complete docs at /docs route (client/src/pages/aethex-docs.tsx) - Searchable documentation with sidebar navigation - Language guide, standard library reference, and examples - Ready for deployment to aethex.dev 5. npm Package Publishing - @aethex.os/core@1.0.0 - Standard library (published) - @aethex.os/cli@1.0.1 - Command line compiler (published) - Both packages live on npm and globally installable Domain Configuration: - DNS setup for 29+ domains (aethex.app, aethex.co, etc.) - nginx reverse proxy configuration - CORS configuration for cross-domain requests - OAuth redirect fixes for hash-based routing Standard Library Features: - Passport: Universal identity across platforms - DataSync: Cross-platform data synchronization - SafeInput: PII detection (phone, email, SSN, credit cards) - Compliance: COPPA/FERPA age gates and audit logging Documentation Package: - Created aethex-dev-docs.zip with complete documentation - Ready for static site deployment - Includes examples, API reference, and quickstart guide Technical Improvements: - Fixed OAuth blank page issue (hash routing) - Added .gitignore rules for temp files - Cleaned up build artifacts and temporary files - Updated all package references to @aethex.os namespace Co-Authored-By: Claude <noreply@anthropic.com>
525 lines
16 KiB
TypeScript
525 lines
16 KiB
TypeScript
import { useState, useEffect, useRef } from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { Link } from "wouter";
|
|
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";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { AeThexCompiler, type CompileTarget } from "@/lib/aethex/compiler";
|
|
|
|
interface TerminalLine {
|
|
type: 'input' | 'output' | 'error' | 'system';
|
|
content: string;
|
|
timestamp: number;
|
|
}
|
|
|
|
export default function Terminal() {
|
|
const [lines, setLines] = useState<TerminalLine[]>([
|
|
{
|
|
type: 'system',
|
|
content: '▸ AeThex Terminal v4.2 - Type "help" for commands',
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
|
|
const [cliCommand, setCliCommand] = useState<string>("build");
|
|
const [cliStatus, setCliStatus] = useState<"idle" | "running" | "done" | "error">("idle");
|
|
const [cliLabel, setCliLabel] = useState<string>("");
|
|
const eventSourceRef = useRef<EventSource | null>(null);
|
|
const currentRunId = useRef<string | null>(null);
|
|
const { toast } = useToast();
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
eventSourceRef.current?.close();
|
|
};
|
|
}, []);
|
|
|
|
const [input, setInput] = useState("");
|
|
const [commandHistory, setCommandHistory] = useState<string[]>([]);
|
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
const terminalEndRef = useRef<HTMLDivElement>(null);
|
|
const { executionHistory, executeCode, isExecuting } = useLabTerminal();
|
|
|
|
const scrollToBottom = () => {
|
|
terminalEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
};
|
|
|
|
useEffect(() => {
|
|
scrollToBottom();
|
|
}, [lines]);
|
|
|
|
// Show last execution in terminal
|
|
useEffect(() => {
|
|
if (executionHistory.length > 0) {
|
|
const latest = executionHistory[0];
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'input',
|
|
content: `> run "${latest.code.split('\n')[0]}..."`,
|
|
timestamp: latest.timestamp,
|
|
},
|
|
{
|
|
type: latest.status === 'error' ? 'error' : 'output',
|
|
content: latest.output,
|
|
timestamp: latest.timestamp,
|
|
},
|
|
]);
|
|
}
|
|
}, [executionHistory]);
|
|
|
|
const processCommand = (cmd: string) => {
|
|
const trimmed = cmd.trim();
|
|
if (!trimmed) return;
|
|
|
|
setLines((prev) => [
|
|
...prev,
|
|
{ type: 'input', content: `> ${trimmed}`, timestamp: Date.now() },
|
|
]);
|
|
|
|
const parts = trimmed.split(' ');
|
|
const command = parts[0].toLowerCase();
|
|
|
|
switch (command) {
|
|
case 'help':
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'system',
|
|
content: `Available commands:
|
|
help - Show this help message
|
|
clear - Clear terminal
|
|
execute <code> - Execute JavaScript code
|
|
run <file> - Run code from Lab
|
|
aethex <cmd> - AeThex language compiler
|
|
compile <code> - Compile AeThex code
|
|
--target <t> - Set target (javascript, roblox, uefn, unity)
|
|
--help - Show AeThex help
|
|
history - Show command history
|
|
exit - Exit terminal`,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
break;
|
|
|
|
case 'clear':
|
|
setLines([]);
|
|
break;
|
|
|
|
case 'history':
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'output',
|
|
content: commandHistory.join('\n') || '(empty)',
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
break;
|
|
|
|
case 'execute':
|
|
const code = parts.slice(1).join(' ');
|
|
if (code) {
|
|
executeCode(code, 'javascript');
|
|
} else {
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'error',
|
|
content: 'Usage: execute <code>',
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
}
|
|
break;
|
|
|
|
case 'run':
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'output',
|
|
content: '▸ Open Lab to run files',
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
break;
|
|
|
|
case 'exit':
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'system',
|
|
content: '▸ Type "help" to see available commands',
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
break;
|
|
|
|
case 'build':
|
|
case 'migrate-status':
|
|
case 'migrate':
|
|
case 'seed':
|
|
case 'test':
|
|
executeCliCommand(command);
|
|
break;
|
|
|
|
case 'aethex':
|
|
handleAeThexCommand(parts.slice(1));
|
|
break;
|
|
|
|
default:
|
|
setLines((prev) => [
|
|
|
|
...prev,
|
|
{
|
|
type: 'error',
|
|
content: `Command not found: ${command}. Type "help" for available commands.`,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
}
|
|
|
|
setCommandHistory((prev) => [...prev, trimmed]);
|
|
setInput("");
|
|
setHistoryIndex(-1);
|
|
};
|
|
|
|
const handleAeThexCommand = (args: string[]) => {
|
|
if (args.length === 0 || args[0] === '--help') {
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'system',
|
|
content: `AeThex Language Compiler v1.0
|
|
|
|
Usage:
|
|
aethex compile <code> Compile AeThex code
|
|
aethex --target <target> Set compilation target
|
|
aethex --help Show this help
|
|
|
|
Targets:
|
|
javascript (default) Compile to JavaScript
|
|
roblox Compile to Lua (Roblox)
|
|
uefn Compile to Verse (UEFN) [Coming soon]
|
|
unity Compile to C# (Unity) [Coming soon]
|
|
|
|
Example:
|
|
aethex compile journey Hello() { notify "Hello World!" }
|
|
aethex --target roblox compile journey Hello() { notify "Hello World!" }`,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
return;
|
|
}
|
|
|
|
if (args[0] === 'compile') {
|
|
// Find target flag
|
|
const targetIndex = args.findIndex(arg => arg === '--target');
|
|
const target: CompileTarget = targetIndex !== -1 && args[targetIndex + 1]
|
|
? args[targetIndex + 1] as CompileTarget
|
|
: 'javascript';
|
|
|
|
// Get code (everything except --target and its value)
|
|
const codeArgs = args.slice(1).filter((arg, idx, arr) => {
|
|
return arg !== '--target' && (idx === 0 || arr[idx - 1] !== '--target');
|
|
});
|
|
const code = codeArgs.join(' ');
|
|
|
|
if (!code) {
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'error',
|
|
content: 'Usage: aethex compile <code>',
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const compiler = new AeThexCompiler({ target, sourceFile: 'terminal' });
|
|
const result = compiler.compile(code);
|
|
|
|
if (result.errors.length > 0) {
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'error',
|
|
content: `Compilation failed:\n${result.errors.map(e => ` Line ${e.line}: ${e.message}`).join('\n')}`,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
return;
|
|
}
|
|
|
|
if (result.warnings.length > 0) {
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'output',
|
|
content: `Warnings:\n${result.warnings.map(w => ` Line ${w.line}: ${w.message}`).join('\n')}`,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
}
|
|
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'system',
|
|
content: `✓ Compiled to ${target}`,
|
|
timestamp: Date.now(),
|
|
},
|
|
{
|
|
type: 'output',
|
|
content: result.code,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
} catch (error: any) {
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'error',
|
|
content: `Compilation error: ${error.message}`,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
setLines((prev) => [
|
|
...prev,
|
|
{
|
|
type: 'error',
|
|
content: `Unknown AeThex command: ${args[0]}. Try 'aethex --help'`,
|
|
timestamp: Date.now(),
|
|
},
|
|
]);
|
|
};
|
|
|
|
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 executeCliCommand = async (cmd: string) => {
|
|
if (cliStatus === "running") return;
|
|
setCliStatus("running");
|
|
appendCliLine('system', `▸ Running ${cmd}...`);
|
|
toast({ title: "CLI", description: `Started ${cmd}`, variant: "default" });
|
|
|
|
try {
|
|
const res = await fetch('/api/admin/cli/start', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ command: cmd })
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
appendCliLine('error', `Start failed: ${text || res.status}`);
|
|
setCliStatus("error");
|
|
toast({ title: "CLI Error", description: `Failed to start ${cmd}`, variant: "destructive" });
|
|
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");
|
|
toast({ title: "CLI Error", description: `Stream error for ${cmd}`, variant: "destructive" });
|
|
es.close();
|
|
});
|
|
|
|
es.addEventListener('done', (evt) => {
|
|
const status = evt.data === 'success' ? 'done' : 'error';
|
|
setCliStatus(status as any);
|
|
appendCliLine(status === 'done' ? 'system' : 'error', `▸ ${data.label || cmd} ${status}`);
|
|
toast({ title: status === 'done' ? "CLI Success" : "CLI Failed", description: `${data.label || cmd} ${status}` });
|
|
es.close();
|
|
currentRunId.current = null;
|
|
});
|
|
|
|
} catch (err) {
|
|
appendCliLine('error', 'Failed to start CLI command');
|
|
setCliStatus("error");
|
|
toast({ title: "CLI Error", description: `Failed to start ${cmd}`, variant: "destructive" });
|
|
}
|
|
};
|
|
|
|
const startCli = () => executeCliCommand(cliCommand);
|
|
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.key === 'Enter') {
|
|
processCommand(input);
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
const newIndex = historyIndex + 1;
|
|
if (newIndex < commandHistory.length) {
|
|
setHistoryIndex(newIndex);
|
|
setInput(commandHistory[commandHistory.length - 1 - newIndex]);
|
|
}
|
|
} else if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
if (historyIndex > 0) {
|
|
const newIndex = historyIndex - 1;
|
|
setHistoryIndex(newIndex);
|
|
setInput(commandHistory[commandHistory.length - 1 - newIndex]);
|
|
} else if (historyIndex === 0) {
|
|
setHistoryIndex(-1);
|
|
setInput("");
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="h-screen w-full bg-[#0a0a0c] text-[#a9b7c6] flex flex-col font-mono">
|
|
{/* Header */}
|
|
<div className="h-12 border-b border-[#2b2d30] bg-[#1e1f22] flex items-center px-4 justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Link href="/">
|
|
<button className="text-[#a9b7c6] hover:text-white transition-colors">
|
|
<ArrowLeft className="w-4 h-4" />
|
|
</button>
|
|
</Link>
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<TerminalIcon className="w-4 h-4 text-cyan-400" />
|
|
<span className="font-bold text-white">AeThex Terminal v4.2</span>
|
|
<span className="text-xs text-green-500 bg-green-500/10 px-2 py-0.5 rounded ml-2">
|
|
[ ONLINE ]
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => navigator.clipboard.writeText(lines.map((l) => l.content).join('\n'))}
|
|
className="text-[#a9b7c6] hover:bg-[#2b2d30]"
|
|
>
|
|
<Copy className="w-4 h-4" />
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => setLines([])}
|
|
className="text-[#a9b7c6] hover:bg-[#2b2d30]"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Terminal Output */}
|
|
|
|
{/* Admin CLI runner */}
|
|
<div className="border-b border-[#2b2d30] bg-[#121216] px-4 py-3 flex flex-wrap items-center gap-3 text-sm">
|
|
<div className="text-white/80 font-semibold">AeThex CLI</div>
|
|
<Select value={cliCommand} onValueChange={setCliCommand}>
|
|
<SelectTrigger className="w-48 bg-[#0f0f12] border-white/10 text-white/80">
|
|
<SelectValue placeholder="Select command" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-[#0f0f12] text-white/80 border-white/10">
|
|
<SelectItem value="build">build (npm run build)</SelectItem>
|
|
<SelectItem value="migrate-status">migrate-status (drizzle status)</SelectItem>
|
|
<SelectItem value="migrate">migrate (drizzle migrate:push)</SelectItem>
|
|
<SelectItem value="seed">seed (ts-node script/seed.ts)</SelectItem>
|
|
<SelectItem value="test">test (./test-implementation.sh)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<Button
|
|
size="sm"
|
|
onClick={startCli}
|
|
disabled={cliStatus === "running"}
|
|
className="bg-cyan-600 hover:bg-cyan-500"
|
|
>
|
|
{cliStatus === "running" ? "Running..." : "Run"}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={stopCli}
|
|
disabled={!currentRunId.current}
|
|
className="border-white/10 text-white/70 hover:text-white"
|
|
>
|
|
Stop
|
|
</Button>
|
|
<div className="text-xs text-white/50">
|
|
Status: {cliStatus.toUpperCase()}
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 overflow-y-auto p-4 space-y-1 bg-[#0a0a0c]">
|
|
{lines.map((line, i) => (
|
|
<motion.div
|
|
key={i}
|
|
initial={{ opacity: 0, y: -5 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className={`text-xs leading-relaxed ${
|
|
line.type === 'error'
|
|
? 'text-red-500'
|
|
: line.type === 'input'
|
|
? 'text-cyan-400'
|
|
: line.type === 'system'
|
|
? 'text-yellow-600'
|
|
: 'text-[#a9b7c6]'
|
|
}`}
|
|
>
|
|
{line.content.split('\n').map((part, idx) => (
|
|
<div key={idx}>{part}</div>
|
|
))}
|
|
</motion.div>
|
|
))}
|
|
<div ref={terminalEndRef} />
|
|
</div>
|
|
|
|
{/* Input Bar */}
|
|
<div className="border-t border-[#2b2d30] bg-[#1e1f22] p-3">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-cyan-400">▸</span>
|
|
<Input
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder="Type command... (help for list)"
|
|
className="flex-1 bg-[#0a0a0c] border-0 text-[#a9b7c6] placeholder-[#555] focus:ring-0 focus:outline-none font-mono text-xs"
|
|
disabled={isExecuting}
|
|
autoFocus
|
|
/>
|
|
{isExecuting && (
|
|
<span className="text-yellow-500 text-xs animate-pulse">executing...</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|