AeThex-OS/client/src/pages/terminal.tsx
MrPiglr a15b5b1015 feat: integrate AeThex Language across entire OS ecosystem
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>
2026-02-11 22:28:05 -07:00

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>
);
}