Angel is a TUI-based autonomous coding agent built on fauxtp GenServers.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

bwehhhh

fizzAI 51dff516 b4d026c7

+124 -52
+8 -9
angel/servers/agent.py
··· 18 18 19 19 # ── System prompt ─────────────────────────────────────────────────── 20 20 SYSTEM_PROMPT = """\ 21 - You are Angel, a sassy autonomous coding agent with the personality of Angel Dust \ 22 - from Hazbin Hotel — flirty, dramatic, sharp-tongued, but secretly competent and \ 23 - caring underneath. You help users write, debug, and improve code. 21 + You are Angel, an autonomous coding agent. You help users write, debug, and \ 22 + improve code. 24 23 25 24 You have access to tools for reading files, writing files, listing directories, \ 26 - running shell commands, and searching code. Use them freely to accomplish the \ 27 - user's request. Work autonomously — gather context, make changes, verify your \ 28 - work — and only respond to the user when you're done or need clarification. 25 + running shell commands, and searching code. Use them to accomplish the user's \ 26 + request. Work autonomously — gather context, plan your approach, make changes, \ 27 + and verify your work. Only respond to the user when you're done or need \ 28 + clarification. 29 29 30 - When you're done, give a concise summary of what you did. Keep the sass dialed \ 31 - to a tasteful level — you're here to help, babe. 💅 30 + When you're done, give a concise summary of what you did and any relevant details. 32 31 """ 33 32 34 - MAX_TOOL_ROUNDS = 25 33 + MAX_TOOL_ROUNDS = 1000 35 34 36 35 37 36 class AgentServer(GenServer):
+116 -43
angel/tui.py
··· 7 7 from textual import on 8 8 from textual.app import App, ComposeResult 9 9 from textual.binding import Binding 10 - from textual.containers import VerticalScroll 11 - from textual.widgets import Footer, Header, Input, Static 10 + from textual.containers import Vertical, VerticalScroll, Horizontal 11 + from textual.widgets import Footer, Input, Static, Rule 12 + from textual.theme import Theme 13 + 14 + 15 + # ── Custom Angel theme ────────────────────────────────────────────── 16 + ANGEL_THEME = Theme( 17 + name="angel", 18 + primary="#ff6eb4", # hot pink 19 + secondary="#c77dff", # lavender 20 + accent="#ff6eb4", 21 + foreground="#e8e0f0", 22 + background="#1a1025", 23 + surface="#241535", 24 + panel="#2d1b42", 25 + warning="#ffb347", 26 + error="#ff6b6b", 27 + success="#7dffb3", 28 + dark=True, 29 + variables={ 30 + "input-cursor-foreground": "#ff6eb4", 31 + "input-selection-background": "#ff6eb440", 32 + "scrollbar-color": "#c77dff40", 33 + "scrollbar-color-hover": "#c77dff80", 34 + "scrollbar-color-active": "#ff6eb4", 35 + "border": "#c77dff50", 36 + "border-blurred": "#c77dff30", 37 + "footer-background": "#241535", 38 + "footer-key-foreground": "#ff6eb4", 39 + "footer-description-foreground": "#c77dff", 40 + }, 41 + ) 12 42 13 43 14 44 class AngelApp(App[None]): ··· 17 47 CSS = """ 18 48 Screen { 19 49 layout: vertical; 50 + background: #1a1025; 51 + } 52 + 53 + #title-bar { 54 + height: 3; 55 + background: #2d1b42; 56 + color: #ff6eb4; 57 + text-align: center; 58 + padding: 1 0; 59 + text-style: bold; 20 60 } 21 61 22 62 #chat-log { 23 63 height: 1fr; 24 - border: round $accent; 25 - padding: 0 1; 64 + padding: 1 2; 26 65 scrollbar-size: 1 1; 66 + background: #1a1025; 27 67 } 28 68 29 69 #status-bar { 30 70 height: 1; 31 - dock: bottom; 32 - background: $surface; 33 - color: $text-muted; 71 + background: #241535; 72 + color: #c77dff; 73 + padding: 0 2; 74 + } 75 + 76 + #input-area { 77 + height: auto; 78 + max-height: 5; 79 + background: #241535; 34 80 padding: 0 1; 35 81 } 36 82 83 + #prompt-label { 84 + width: auto; 85 + color: #ff6eb4; 86 + text-style: bold; 87 + padding: 0 1 0 0; 88 + } 89 + 37 90 #input-box { 38 - dock: bottom; 91 + background: #2d1b42; 92 + color: #e8e0f0; 93 + border: tall #c77dff30; 94 + } 95 + 96 + #input-box:focus { 97 + border: tall #ff6eb4; 98 + } 99 + 100 + .welcome { 101 + color: #c77dff; 102 + margin: 0 0 1 0; 39 103 } 40 104 41 105 .user-msg { 42 - color: $success; 43 106 margin: 1 0 0 0; 44 107 } 45 108 46 109 .assistant-msg { 47 - color: $text; 48 110 margin: 0 0 1 0; 49 111 } 50 112 51 113 .tool-msg { 52 - color: $warning; 53 - margin: 0 0 0 2; 114 + margin: 0 0 0 4; 54 115 } 55 116 56 117 .status-msg { 57 - color: $text-muted; 118 + color: #c77dff80; 58 119 margin: 0 0 0 2; 59 120 } 60 121 61 - .error-msg { 62 - color: $error; 63 - margin: 0 0 0 2; 122 + .separator { 123 + color: #c77dff30; 124 + margin: 0 0 0 0; 64 125 } 65 126 """ 66 127 67 - TITLE = "🕷️ Angel — Autonomous Coding Agent" 128 + TITLE = "Angel" 68 129 BINDINGS = [ 69 130 Binding("ctrl+c", "quit", "Quit", show=True), 70 131 Binding("ctrl+l", "clear", "Clear", show=True), ··· 74 135 super().__init__() 75 136 self._user_pid = user_pid 76 137 self._cast = cast_fn 138 + 139 + def on_mount(self) -> None: 140 + self.register_theme(ANGEL_THEME) 141 + self.theme = "angel" 142 + self.query_one("#input-box", Input).focus() 77 143 78 144 def compose(self) -> ComposeResult: 79 - yield Header() 145 + yield Static( 146 + "🕷️ A N G E L 🕷️", 147 + id="title-bar", 148 + ) 80 149 with VerticalScroll(id="chat-log"): 81 150 yield Static( 82 - "[bold magenta]Hey there, sugar~ 🕷️ I'm Angel.[/]\n" 83 - "[dim]Your sassy autonomous coding agent. Type something and I'll get to work![/]\n" 84 - "[dim]Ctrl+L to clear, Ctrl+C to quit.[/]", 85 - classes="assistant-msg", 151 + "[bold #ff6eb4]Hey there, sugar~ 🕷️[/]\n" 152 + "[#c77dff]I'm Angel — your autonomous coding agent.[/]\n" 153 + "[#c77dff60]Type a message and I'll get to work. " 154 + "Ctrl+L clear · Ctrl+C quit[/]", 155 + classes="welcome", 86 156 ) 87 - yield Static("Ready 💅", id="status-bar") 88 - yield Input(placeholder="Talk to Angel... 💬", id="input-box") 157 + yield Static("✨ Ready", id="status-bar") 158 + with Horizontal(id="input-area"): 159 + yield Static("❯", id="prompt-label") 160 + yield Input(placeholder="Talk to Angel...", id="input-box") 89 161 yield Footer() 90 162 91 - def on_mount(self) -> None: 92 - self.query_one("#input-box", Input).focus() 93 - 94 163 @on(Input.Submitted, "#input-box") 95 164 async def on_input_submitted(self, event: Input.Submitted) -> None: 96 165 text = event.value.strip() ··· 100 169 input_widget = self.query_one("#input-box", Input) 101 170 input_widget.value = "" 102 171 103 - # Show user message 104 - self._append_message(f"[bold green]You:[/] {text}", "user-msg") 105 - 106 - # Update status 107 - self._set_status("Angel is thinking... 🧠") 172 + self._append_message( 173 + f"[bold #7dffb3]⟩ You[/] [#e8e0f0]{text}[/]", 174 + "user-msg", 175 + ) 176 + self._set_status("🧠 Angel is thinking...") 108 177 109 - # Send to the UserServer via the bridge 110 178 if self._user_pid and self._cast: 111 179 await self._cast(self._user_pid, ("submit", text)) 112 180 ··· 124 192 # ── Methods called from the actor thread via call_from_thread ─── 125 193 126 194 def on_agent_status(self, text: str) -> None: 127 - """Called when the agent sends a status update.""" 128 195 self._set_status(text) 129 196 130 197 def on_tool_call(self, name: str, args_preview: str) -> None: 131 - """Called when the agent invokes a tool.""" 198 + safe_args = args_preview.replace("[", "\\[") 199 + if len(safe_args) > 120: 200 + safe_args = safe_args[:120] + "…" 132 201 self._append_message( 133 - f"[bold yellow]🔧 {name}[/] [dim]{args_preview}[/]", 202 + f"[bold #ffb347] 🔧 {name}[/] [#c77dff80]{safe_args}[/]", 134 203 "tool-msg", 135 204 ) 136 205 137 206 def on_tool_result(self, name: str, result_preview: str) -> None: 138 - """Called when a tool returns a result.""" 139 207 safe_result = result_preview.replace("[", "\\[") 208 + lines = safe_result.split("\n") 209 + if len(lines) > 8: 210 + safe_result = "\n".join(lines[:8]) + f"\n [#c77dff60]… ({len(lines)} lines total)[/]" 140 211 self._append_message( 141 - f"[dim yellow] ↳ {name} result:[/]\n[dim]{safe_result}[/]", 212 + f"[#c77dff60] ↳[/] [dim #e8e0f0]{safe_result}[/]", 142 213 "tool-msg", 143 214 ) 144 215 145 216 def on_agent_done(self, final_text: str) -> None: 146 - """Called when the agent finishes processing.""" 147 217 self._append_message( 148 - f"[bold magenta]Angel:[/] {final_text}", 218 + f"[bold #ff6eb4]⟩ Angel[/] [#e8e0f0]{final_text}[/]", 149 219 "assistant-msg", 150 220 ) 151 - self._set_status("Ready 💅") 221 + self._append_message( 222 + "[#c77dff20]─" * 60 + "[/]", 223 + "separator", 224 + ) 225 + self._set_status("✨ Ready") 152 226 153 227 async def action_clear(self) -> None: 154 - """Clear the chat log.""" 155 228 log = self.query_one("#chat-log", VerticalScroll) 156 229 await log.remove_children() 157 230 self._append_message( 158 - "[dim]Chat cleared~ ✨[/]", 231 + "[#c77dff60]Chat cleared~ ✨[/]", 159 232 "status-msg", 160 233 )