Download src/ui/termux.ts from teddyoas/Asnip: direct link, hf CLI and curl.
- Browser
- Download file 9.03 kB
-
https://huggingface.co/teddyoas/Asnip/resolve/main/src/ui/termux.ts
- Command line
-
hf download hf://teddyoas/Asnip/src/ui/termux.ts
-
curl -L -o termux.ts https://huggingface.co/teddyoas/Asnip/resolve/main/src/ui/termux.ts
9.03 kB
| import * as readline from "readline"; | |
| import { EventBus, EventHandler } from "../core/eventbus"; | |
| import { AgentManager } from "../agents/manager"; | |
| import { AgentIdentity } from "../agents/types"; | |
| import { phantomTheme } from "./theme"; | |
| import { loadConfig } from "../core/config"; | |
| import { createOpenAIProvider, createOllamaProvider, LLMProvider } from "../providers/openai"; | |
| const R = "\x1b[0m"; | |
| const BOLD = "\x1b[1m"; | |
| const DIM = "\x1b[2m"; | |
| const GREEN = "\x1b[38;2;0;255;136m"; | |
| const CYAN = "\x1b[38;2;0;204;255m"; | |
| const MAGENTA = "\x1b[38;2;255;0;204m"; | |
| const YELLOW = "\x1b[38;2;255;136;0m"; | |
| const RED = "\x1b[38;2;255;34;68m"; | |
| const BLUE = "\x1b[38;2;68;68;170m"; | |
| const GRAY = "\x1b[38;2;51;51;85m"; | |
| const BG = "\x1b[48;2;10;10;26m"; | |
| function colorFromHex(hex: string): string { | |
| const r = parseInt(hex.slice(1, 3), 16); | |
| const g = parseInt(hex.slice(3, 5), 16); | |
| const b = parseInt(hex.slice(5, 7), 16); | |
| return `\x1b[38;2;${r};${g};${b}m`; | |
| } | |
| const AGENT_COLORS: Record<string, string> = {}; | |
| export class PhantomTermuxUI { | |
| private rl: readline.Interface; | |
| private agentManager: AgentManager; | |
| private bus: EventBus; | |
| private running = true; | |
| private commandHistory: string[] = []; | |
| private historyIndex = -1; | |
| private log: string[] = []; | |
| private llm?: LLMProvider; | |
| constructor() { | |
| this.bus = EventBus.getInstance(); | |
| const config = loadConfig(); | |
| if (config.providers.openai.apiKey) { | |
| this.llm = createOpenAIProvider(config); | |
| } else if (config.providers.ollama.baseUrl) { | |
| try { | |
| this.llm = createOllamaProvider(config); | |
| } catch { | |
| // fallback to no LLM | |
| } | |
| } | |
| this.agentManager = new AgentManager(this.llm); | |
| readline.emitKeypressEvents(process.stdin); | |
| if (process.stdin.isTTY) process.stdin.setRawMode?.(true); | |
| this.rl = readline.createInterface({ | |
| input: process.stdin, | |
| output: process.stdout, | |
| prompt: "", | |
| terminal: true, | |
| }); | |
| this.registerEvents(); | |
| } | |
| private registerEvents(): void { | |
| this.bus.on("agent:spawned", ((data: unknown) => { | |
| const agent = data as AgentIdentity; | |
| AGENT_COLORS[agent.id] = agent.color; | |
| this.writeLog(`${GREEN}β${R} ${colorFromHex(agent.color)}${agent.name}${R} spawned [${DIM}${agent.role}${R}]`); | |
| }) as EventHandler); | |
| this.bus.on("agent:speaking", ((data: unknown) => { | |
| const { agent, message } = data as { agent: AgentIdentity; message: string }; | |
| const c = colorFromHex(agent.color); | |
| this.writeLog(`${c}${agent.name}${R} ${DIM}Β»${R} ${message}`); | |
| }) as EventHandler); | |
| this.bus.on("agent:evolving", ((data: unknown) => { | |
| const { agent, level } = data as { agent: AgentIdentity; level: number }; | |
| this.writeLog(`${MAGENTA}β¬${R} ${colorFromHex(agent.color)}${agent.name}${R} evolved to ${BOLD}level ${level}${R}`); | |
| }) as EventHandler); | |
| this.bus.on("agent:removed", ((data: unknown) => { | |
| const id = data as string; | |
| this.writeLog(`${RED}β${R} Agent ${DIM}${id}${R} removed`); | |
| }) as EventHandler); | |
| } | |
| private writeLog(msg: string): void { | |
| this.log.push(msg); | |
| if (this.log.length > 500) this.log.shift(); | |
| } | |
| private clearScreen(): void { | |
| process.stdout.write("\x1b[2J\x1b[H"); | |
| } | |
| private draw(): void { | |
| const [rows, cols] = process.stdout.getWindowSize(); | |
| const agents = this.agentManager.listAgents(); | |
| const statusLine = agents.length > 0 | |
| ? `${GREEN}β${R} ${agents.length} agents` | |
| : `${GRAY}no agents${R}`; | |
| let thinking = agents.filter((a) => a.status === "thinking").length; | |
| const status = thinking > 0 ? `${YELLOW}π§ ${thinking} thinking${R}` : `${GREEN}β‘ idle${R}`; | |
| const header = `${BOLD}${GREEN}PHANTOM${R}${GRAY} β space evolving terminal${R} ${GRAY}|${R} ${statusLine} ${GRAY}|${R} ${status}`; | |
| const footer = `${GRAY}ESC cmd TAB focus ββ panels q quit${R}`; | |
| const logLines = this.log.slice(-(rows - 4)); | |
| const maxContentWidth = cols - 2; | |
| this.clearScreen(); | |
| process.stdout.write(`${BG}${header}\n${R}`); | |
| if (logLines.length === 0) { | |
| process.stdout.write(`${DIM}${GRAY} No activity yet. Press ESC to open command palette.${R}\n`); | |
| } else { | |
| for (const line of logLines) { | |
| const display = line.length > maxContentWidth | |
| ? line.substring(0, maxContentWidth - 3) + "..." | |
| : line; | |
| process.stdout.write(` ${display}\n`); | |
| } | |
| } | |
| process.stdout.write(`\n${DIM}${footer}${R}`); | |
| } | |
| private promptCommand(): void { | |
| this.rl.question(`${GREEN}β‘${R} `, (input) => { | |
| const cmd = input.trim(); | |
| if (!cmd) { | |
| this.draw(); | |
| this.waitForKey(); | |
| return; | |
| } | |
| this.commandHistory.push(cmd); | |
| this.historyIndex = this.commandHistory.length; | |
| this.handleCommand(cmd); | |
| this.draw(); | |
| this.waitForKey(); | |
| }); | |
| } | |
| private waitForKey(): void { | |
| if (!this.running) return; | |
| process.stdin.once("keypress", (_str, key) => { | |
| if (!key) return; | |
| if (key.name === "escape") { | |
| this.promptCommand(); | |
| } else if (key.name === "q") { | |
| this.stop(); | |
| } else if (key.name === "right") { | |
| // no-op in termux mode | |
| this.waitForKey(); | |
| } else if (key.name === "left") { | |
| this.waitForKey(); | |
| } else { | |
| this.waitForKey(); | |
| } | |
| }); | |
| } | |
| private handleCommand(cmd: string): void { | |
| const parts = cmd.trim().split(/\s+/); | |
| const command = parts[0]?.toLowerCase(); | |
| const args = parts.slice(1); | |
| switch (command) { | |
| case "spawn": | |
| case "s": { | |
| this.agentManager.spawnAgent(args[0], args[1], args.slice(2).join(" ")); | |
| break; | |
| } | |
| case "list": | |
| case "ls": { | |
| const agents = this.agentManager.listAgents(); | |
| if (agents.length === 0) { | |
| this.writeLog(`${GRAY}No active agents. Use 'spawn' to create one.${R}`); | |
| } else { | |
| this.writeLog(`${CYAN}β Agents (${agents.length}):${R}`); | |
| agents.forEach((a) => { | |
| const c = colorFromHex(a.color); | |
| this.writeLog(` ${c}β${R} ${BOLD}${a.name}${R} ${DIM}${a.id}${R} β ${a.role} ${GRAY}[${a.status}]${R} ${MAGENTA}β ${a.evolutionLevel}${R}`); | |
| }); | |
| } | |
| break; | |
| } | |
| case "broadcast": | |
| case "b": { | |
| const msg = args.join(" "); | |
| const id = this.agentManager.listAgents()[0]?.id; | |
| if (id && msg) { | |
| this.agentManager.broadcast(id, msg); | |
| } | |
| break; | |
| } | |
| case "debate": | |
| case "d": { | |
| const topic = args.join(" ") || "what should we build today?"; | |
| this.agentManager.debate(topic); | |
| break; | |
| } | |
| case "evolve": | |
| case "e": { | |
| this.agentManager.evolveAll(); | |
| this.writeLog(`${MAGENTA}β¬ Mass evolution initiated${R}`); | |
| break; | |
| } | |
| case "clear": | |
| this.log = []; | |
| break; | |
| case "help": | |
| case "h": { | |
| this.writeLog(`${BOLD}${GREEN}PHANTOM COMMANDS${R}`); | |
| this.writeLog(` ${GREEN}spawn${R} [name] [role] [persona] β create agent`); | |
| this.writeLog(` ${GREEN}list${R} β list agents`); | |
| this.writeLog(` ${GREEN}broadcast${R} <msg> β message all agents`); | |
| this.writeLog(` ${GREEN}debate${R} [topic] β agents debate a topic`); | |
| this.writeLog(` ${GREEN}evolve${R} β evolve all agents`); | |
| this.writeLog(` ${GREEN}clear${R} β clear screen`); | |
| this.writeLog(` ${GREEN}help${R} β this help`); | |
| this.writeLog(` ${GREEN}quit${R} β exit`); | |
| break; | |
| } | |
| case "quit": | |
| case "q": | |
| this.stop(); | |
| break; | |
| default: | |
| this.writeLog(`${RED}Unknown command:${R} ${cmd}. Type ${GREEN}help${R} for commands.`); | |
| } | |
| } | |
| start(): void { | |
| this.clearScreen(); | |
| process.stdout.write(`\n${BOLD}${GREEN}ββββββββββββββββββββββββββββββββββββββββ${R}`); | |
| process.stdout.write(`\n${BOLD}${GREEN}β${R} ${BOLD}PHANTOM${R}${GRAY} β space evolving terminal${R} ${BOLD}${GREEN}β${R}`); | |
| process.stdout.write(`\n${BOLD}${GREEN}ββββββββββββββββββββββββββββββββββββββββ${R}\n\n`); | |
| this.agentManager.spawnDefaults(); | |
| if (!this.llm) { | |
| this.writeLog(`${YELLOW}β No LLM configured. Agents will use basic responses.${R}`); | |
| this.writeLog(`${GRAY} Set OPENAI_API_KEY or configure ~/.config/phantom/config.json${R}`); | |
| } else { | |
| this.writeLog(`${GREEN}β LLM connected${R}`); | |
| } | |
| this.draw(); | |
| this.waitForKey(); | |
| } | |
| stop(): void { | |
| this.running = false; | |
| this.clearScreen(); | |
| process.stdout.write(`${GREEN}Phantom terminated.${R}\n`); | |
| process.exit(0); | |
| } | |
| } | |