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.

its good now

fizzAI cf8eba45 51dff516

+218 -294
+1 -1
angel/__init__.py
··· 1 - """Angel — an autonomous coding agent built on fauxtp GenServers and Textual TUI.""" 1 + """Angel — an autonomous coding agent built on fauxtp GenServers."""
+197 -187
angel/tui.py
··· 1 - """Angel TUI — Textual-based terminal interface for the coding agent.""" 1 + """Angel TUI — prompt_toolkit Application-based terminal interface. 2 + 3 + A lightweight full-screen TUI that keeps an output pane above a persistent 4 + input prompt. Coloring is applied by a simple line-prefix lexer — no Rich, 5 + no CSS, no heavyweight widget toolkit. 6 + """ 2 7 3 8 from __future__ import annotations 4 9 5 - from typing import Any 10 + from typing import Any, Callable 6 11 7 - from textual import on 8 - from textual.app import App, ComposeResult 9 - from textual.binding import Binding 10 - from textual.containers import Vertical, VerticalScroll, Horizontal 11 - from textual.widgets import Footer, Input, Static, Rule 12 - from textual.theme import Theme 12 + from prompt_toolkit.application import Application 13 + from prompt_toolkit.buffer import Buffer 14 + from prompt_toolkit.document import Document 15 + from prompt_toolkit.filters import Condition 16 + from prompt_toolkit.formatted_text import ANSI, StyleAndTextTuples 17 + from prompt_toolkit.key_binding import KeyBindings 18 + from prompt_toolkit.layout import HSplit, Layout, VSplit, Window 19 + from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl 20 + from prompt_toolkit.layout.processors import BeforeInput 21 + from prompt_toolkit.lexers import Lexer 22 + from prompt_toolkit.styles import Style 13 23 14 24 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 - }, 25 + # ── Styles ─────────────────────────────────────────────────────────── 26 + ANGEL_STYLE = Style.from_dict( 27 + { 28 + "output.user": "bold #7dffb3", 29 + "output.angel": "bold #ff6eb4", 30 + "output.tool": "bold #ffb347", 31 + "output.result": "#e8e0f0 dim", 32 + "output.status": "#c77dff", 33 + "output.sep": "#c77dff", 34 + "output.title": "bold #ff6eb4", 35 + "output.welcome": "#c77dff", 36 + "output.default": "#e8e0f0", 37 + "separator": "#c77dff", 38 + "input": "#e8e0f0", 39 + } 41 40 ) 42 41 43 42 44 - class AngelApp(App[None]): 45 - """The Angel TUI application.""" 43 + # ── Lexer — colours output lines by their prefix ──────────────────── 44 + class _ChatLexer(Lexer): 45 + """Assigns a style class to each output line based on its content.""" 46 46 47 - CSS = """ 48 - Screen { 49 - layout: vertical; 50 - background: #1a1025; 51 - } 47 + def lex_document(self, document): # type: ignore[override] 48 + lines = document.lines 52 49 53 - #title-bar { 54 - height: 3; 55 - background: #2d1b42; 56 - color: #ff6eb4; 57 - text-align: center; 58 - padding: 1 0; 59 - text-style: bold; 60 - } 50 + def _get_line(lineno: int) -> StyleAndTextTuples: 51 + if lineno >= len(lines): 52 + return [("", "")] 53 + line = lines[lineno] 54 + cls = _classify(line) 55 + return [(f"class:{cls}", line)] 61 56 62 - #chat-log { 63 - height: 1fr; 64 - padding: 1 2; 65 - scrollbar-size: 1 1; 66 - background: #1a1025; 67 - } 57 + return _get_line 68 58 69 - #status-bar { 70 - height: 1; 71 - background: #241535; 72 - color: #c77dff; 73 - padding: 0 2; 74 - } 75 59 76 - #input-area { 77 - height: auto; 78 - max-height: 5; 79 - background: #241535; 80 - padding: 0 1; 81 - } 60 + def _classify(line: str) -> str: 61 + """Return a style class name for *line*.""" 62 + if line.startswith("⟩ You"): 63 + return "output.user" 64 + if line.startswith("⟩ Angel"): 65 + return "output.angel" 66 + if line.startswith(" 🔧"): 67 + return "output.tool" 68 + if line.startswith(" ↳"): 69 + return "output.result" 70 + if line.startswith("─"): 71 + return "output.sep" 72 + if "A N G E L" in line or line.startswith("Hey there"): 73 + return "output.title" 74 + if any(line.startswith(p) for p in ("✨", "🧠", "Still")): 75 + return "output.status" 76 + if line.startswith("I'm Angel") or line.startswith("Type a message"): 77 + return "output.welcome" 78 + return "output.default" 82 79 83 - #prompt-label { 84 - width: auto; 85 - color: #ff6eb4; 86 - text-style: bold; 87 - padding: 0 1 0 0; 88 - } 89 80 90 - #input-box { 91 - background: #2d1b42; 92 - color: #e8e0f0; 93 - border: tall #c77dff30; 94 - } 81 + # ── Application ────────────────────────────────────────────────────── 82 + class AngelApp: 83 + """Lightweight full-screen TUI for Angel. 95 84 96 - #input-box:focus { 97 - border: tall #ff6eb4; 98 - } 85 + Public interface (consumed by ``main.py``): 86 + * ``__init__(user_pid, cast_fn)`` 87 + * ``._user_pid`` / ``._cast`` — set externally after construction 88 + * ``.run()`` — blocks in the main thread 89 + * ``.call_from_thread(fn, *args)`` 90 + * ``.on_agent_status(text)`` 91 + * ``.on_tool_call(name, preview)`` 92 + * ``.on_tool_result(name, preview)`` 93 + * ``.on_agent_done(text)`` 94 + """ 99 95 100 - .welcome { 101 - color: #c77dff; 102 - margin: 0 0 1 0; 103 - } 96 + def __init__(self, user_pid: Any = None, cast_fn: Any = None) -> None: 97 + self._user_pid = user_pid 98 + self._cast: Callable[..., Any] | None = cast_fn 99 + self._busy = False 100 + self._app: Application[None] | None = None 104 101 105 - .user-msg { 106 - margin: 1 0 0 0; 107 - } 102 + self._output_buffer = Buffer( 103 + name="output", 104 + read_only=Condition(lambda: True), 105 + ) 106 + self._input_buffer = Buffer(name="input", multiline=False) 108 107 109 - .assistant-msg { 110 - margin: 0 0 1 0; 111 - } 108 + # ── build the prompt_toolkit Application ──────────────────────── 112 109 113 - .tool-msg { 114 - margin: 0 0 0 4; 115 - } 110 + def _build(self) -> Application[None]: 111 + kb = KeyBindings() 116 112 117 - .status-msg { 118 - color: #c77dff80; 119 - margin: 0 0 0 2; 120 - } 113 + @kb.add("enter", filter=Condition(lambda: not self._busy)) 114 + def _submit(event: Any) -> None: 115 + text = self._input_buffer.text.strip() 116 + if not text: 117 + return 118 + self._input_buffer.reset() 119 + self._on_submit(text) 121 120 122 - .separator { 123 - color: #c77dff30; 124 - margin: 0 0 0 0; 125 - } 126 - """ 121 + @kb.add("enter", filter=Condition(lambda: self._busy), eager=True) 122 + def _block(_event: Any) -> None: 123 + """Swallow Enter while the agent is working.""" 127 124 128 - TITLE = "Angel" 129 - BINDINGS = [ 130 - Binding("ctrl+c", "quit", "Quit", show=True), 131 - Binding("ctrl+l", "clear", "Clear", show=True), 132 - ] 125 + @kb.add("c-c") 126 + def _quit(event: Any) -> None: 127 + event.app.exit() 133 128 134 - def __init__(self, user_pid: Any = None, cast_fn: Any = None) -> None: 135 - super().__init__() 136 - self._user_pid = user_pid 137 - self._cast = cast_fn 129 + @kb.add("c-l") 130 + def _clear(_event: Any) -> None: 131 + self._output_buffer.set_document(Document(""), bypass_readonly=True) 132 + self._append("Chat cleared~ ✨") 138 133 139 - def on_mount(self) -> None: 140 - self.register_theme(ANGEL_THEME) 141 - self.theme = "angel" 142 - self.query_one("#input-box", Input).focus() 134 + output_window = Window( 135 + content=BufferControl( 136 + buffer=self._output_buffer, 137 + lexer=_ChatLexer(), 138 + focusable=False, 139 + ), 140 + wrap_lines=True, 141 + get_vertical_scroll=self._scroll_to_bottom, 142 + ) 143 143 144 - def compose(self) -> ComposeResult: 145 - yield Static( 146 - "🕷️ A N G E L 🕷️", 147 - id="title-bar", 144 + separator = Window( 145 + content=FormattedTextControl([("class:separator", "─" * 500)]), 146 + height=1, 148 147 ) 149 - with VerticalScroll(id="chat-log"): 150 - yield Static( 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", 156 - ) 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") 161 - yield Footer() 162 148 163 - @on(Input.Submitted, "#input-box") 164 - async def on_input_submitted(self, event: Input.Submitted) -> None: 165 - text = event.value.strip() 166 - if not text: 167 - return 149 + input_window = Window( 150 + content=BufferControl( 151 + buffer=self._input_buffer, 152 + input_processors=[ 153 + BeforeInput(ANSI("\033[1;38;2;255;110;180m❯\033[0m ")), 154 + ], 155 + ), 156 + height=1, 157 + style="class:input", 158 + ) 168 159 169 - input_widget = self.query_one("#input-box", Input) 170 - input_widget.value = "" 160 + root = HSplit([output_window, separator, input_window]) 171 161 172 - self._append_message( 173 - f"[bold #7dffb3]⟩ You[/] [#e8e0f0]{text}[/]", 174 - "user-msg", 162 + return Application( 163 + layout=Layout(root, focused_element=input_window), 164 + key_bindings=kb, 165 + style=ANGEL_STYLE, 166 + full_screen=True, 167 + mouse_support=True, 175 168 ) 176 - self._set_status("🧠 Angel is thinking...") 169 + 170 + # ── output helpers ────────────────────────────────────────────── 177 171 172 + @staticmethod 173 + def _scroll_to_bottom(window: Window) -> int: 174 + """Keep the output pane scrolled to the most recent line.""" 175 + info = window.render_info 176 + if info is not None: 177 + overflow = info.content_height - info.window_height 178 + if overflow > 0: 179 + return overflow 180 + return 0 181 + 182 + def _append(self, text: str) -> None: 183 + """Append one or more lines to the output buffer.""" 184 + current = self._output_buffer.text 185 + new = (current + "\n" + text) if current else text 186 + self._output_buffer.set_document( 187 + Document(new, len(new)), 188 + bypass_readonly=True, 189 + ) 190 + if self._app is not None: 191 + self._app.invalidate() 192 + 193 + def _on_submit(self, text: str) -> None: 194 + self._busy = True 195 + self._append(f"⟩ You {text}") 196 + self._append("🧠 Angel is thinking...") 178 197 if self._user_pid and self._cast: 179 - await self._cast(self._user_pid, ("submit", text)) 198 + self._cast(self._user_pid, ("submit", text)) 180 199 181 - def _append_message(self, content: str, css_class: str = "assistant-msg") -> None: 182 - """Append a message to the chat log.""" 183 - log = self.query_one("#chat-log", VerticalScroll) 184 - widget = Static(content, classes=css_class) 185 - log.mount(widget) 186 - widget.scroll_visible() 200 + # ── public API (used by main.py) ──────────────────────────────── 187 201 188 - def _set_status(self, text: str) -> None: 189 - """Update the status bar.""" 190 - self.query_one("#status-bar", Static).update(text) 202 + def call_from_thread(self, fn: Callable[..., Any], *args: Any) -> None: 203 + """Invoke *fn* directly — ``Application.invalidate`` is thread-safe.""" 204 + fn(*args) 191 205 192 - # ── Methods called from the actor thread via call_from_thread ─── 206 + def run(self) -> None: 207 + """Build the UI, print the welcome banner, and block until quit.""" 208 + self._app = self._build() 209 + 210 + self._append("🕷️ A N G E L 🕷️") 211 + self._append("") 212 + self._append("Hey there, sugar~ 🕷️") 213 + self._append("I'm Angel — your autonomous coding agent.") 214 + self._append("Type a message and I'll get to work. Ctrl+C to quit") 215 + self._append("") 216 + 217 + self._app.run() 218 + 219 + # ── callbacks from the actor thread ───────────────────────────── 193 220 194 221 def on_agent_status(self, text: str) -> None: 195 - self._set_status(text) 222 + self._append(text) 196 223 197 224 def on_tool_call(self, name: str, args_preview: str) -> None: 198 - safe_args = args_preview.replace("[", "\\[") 199 - if len(safe_args) > 120: 200 - safe_args = safe_args[:120] + "…" 201 - self._append_message( 202 - f"[bold #ffb347] 🔧 {name}[/] [#c77dff80]{safe_args}[/]", 203 - "tool-msg", 204 - ) 225 + preview = args_preview 226 + if len(preview) > 120: 227 + preview = preview[:120] + "…" 228 + self._append(f" 🔧 {name} {preview}") 205 229 206 230 def on_tool_result(self, name: str, result_preview: str) -> None: 207 - safe_result = result_preview.replace("[", "\\[") 208 - lines = safe_result.split("\n") 231 + lines = result_preview.split("\n") 209 232 if len(lines) > 8: 210 - safe_result = "\n".join(lines[:8]) + f"\n [#c77dff60]… ({len(lines)} lines total)[/]" 211 - self._append_message( 212 - f"[#c77dff60] ↳[/] [dim #e8e0f0]{safe_result}[/]", 213 - "tool-msg", 214 - ) 233 + result_preview = ( 234 + "\n".join(lines[:8]) 235 + + f"\n … ({len(lines)} lines total)" 236 + ) 237 + self._append(f" ↳ {result_preview}") 215 238 216 239 def on_agent_done(self, final_text: str) -> None: 217 - self._append_message( 218 - f"[bold #ff6eb4]⟩ Angel[/] [#e8e0f0]{final_text}[/]", 219 - "assistant-msg", 220 - ) 221 - self._append_message( 222 - "[#c77dff20]─" * 60 + "[/]", 223 - "separator", 224 - ) 225 - self._set_status("✨ Ready") 226 - 227 - async def action_clear(self) -> None: 228 - log = self.query_one("#chat-log", VerticalScroll) 229 - await log.remove_children() 230 - self._append_message( 231 - "[#c77dff60]Chat cleared~ ✨[/]", 232 - "status-msg", 233 - ) 240 + self._append(f"⟩ Angel {final_text}") 241 + self._append("─" * 60) 242 + self._append("✨ Ready") 243 + self._busy = False
+1 -1
main.py
··· 30 30 def wait_ready(self) -> None: 31 31 self._ready.wait() 32 32 33 - async def cast_to_user(self, pid: Any, message: Any) -> None: 33 + def cast_to_user(self, pid: Any, message: Any) -> None: 34 34 """Schedule a cast to the UserServer from Textual's event loop.""" 35 35 if self._portal: 36 36 self._portal.start_task_soon(cast, pid, message)
+2 -2
pyproject.toml
··· 1 1 [project] 2 2 name = "angel" 3 3 version = "0.1.0" 4 - description = "Angel — a sassy autonomous coding agent built on fauxtp GenServers and Textual TUI" 4 + description = "Angel — a sassy autonomous coding agent built on fauxtp GenServers" 5 5 readme = "README.md" 6 6 requires-python = ">=3.11" 7 7 dependencies = [ 8 8 "fauxtp @ git+https://github.com/fizzAI/fauxtp", 9 9 "litellm>=1.81.9", 10 - "textual>=7.5.0", 10 + "prompt-toolkit>=3.0", 11 11 ] 12 12 13 13 [project.scripts]
+17 -103
uv.lock
··· 133 133 dependencies = [ 134 134 { name = "fauxtp" }, 135 135 { name = "litellm" }, 136 - { name = "textual" }, 136 + { name = "prompt-toolkit" }, 137 137 ] 138 138 139 139 [package.metadata] 140 140 requires-dist = [ 141 141 { name = "fauxtp", git = "https://github.com/fizzAI/fauxtp" }, 142 142 { name = "litellm", specifier = ">=1.81.9" }, 143 - { name = "textual", specifier = ">=7.5.0" }, 143 + { name = "prompt-toolkit", specifier = ">=3.0" }, 144 144 ] 145 145 146 146 [[package]] ··· 702 702 ] 703 703 704 704 [[package]] 705 - name = "linkify-it-py" 706 - version = "2.0.3" 707 - source = { registry = "https://pypi.org/simple" } 708 - dependencies = [ 709 - { name = "uc-micro-py" }, 710 - ] 711 - sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" } 712 - wheels = [ 713 - { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820, upload-time = "2024-02-04T14:48:02.496Z" }, 714 - ] 715 - 716 - [[package]] 717 705 name = "litellm" 718 706 version = "1.81.9" 719 707 source = { registry = "https://pypi.org/simple" } ··· 734 722 sdist = { url = "https://files.pythonhosted.org/packages/ff/8f/2a08f3d86fd008b4b02254649883032068378a8551baed93e8d9dcbbdb5d/litellm-1.81.9.tar.gz", hash = "sha256:a2cd9bc53a88696c21309ef37c55556f03c501392ed59d7f4250f9932917c13c", size = 16276983, upload-time = "2026-02-07T21:14:24.473Z" } 735 723 wheels = [ 736 724 { url = "https://files.pythonhosted.org/packages/0b/8b/672fc06c8a2803477e61e0de383d3c6e686e0f0fc62789c21f0317494076/litellm-1.81.9-py3-none-any.whl", hash = "sha256:24ee273bc8a62299fbb754035f83fb7d8d44329c383701a2bd034f4fd1c19084", size = 14433170, upload-time = "2026-02-07T21:14:21.469Z" }, 737 - ] 738 - 739 - [[package]] 740 - name = "markdown-it-py" 741 - version = "4.0.0" 742 - source = { registry = "https://pypi.org/simple" } 743 - dependencies = [ 744 - { name = "mdurl" }, 745 - ] 746 - sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } 747 - wheels = [ 748 - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, 749 - ] 750 - 751 - [package.optional-dependencies] 752 - linkify = [ 753 - { name = "linkify-it-py" }, 754 725 ] 755 726 756 727 [[package]] ··· 825 796 { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, 826 797 { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, 827 798 { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, 828 - ] 829 - 830 - [[package]] 831 - name = "mdit-py-plugins" 832 - version = "0.5.0" 833 - source = { registry = "https://pypi.org/simple" } 834 - dependencies = [ 835 - { name = "markdown-it-py" }, 836 - ] 837 - sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } 838 - wheels = [ 839 - { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, 840 - ] 841 - 842 - [[package]] 843 - name = "mdurl" 844 - version = "0.1.2" 845 - source = { registry = "https://pypi.org/simple" } 846 - sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 847 - wheels = [ 848 - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 849 799 ] 850 800 851 801 [[package]] ··· 994 944 ] 995 945 996 946 [[package]] 997 - name = "platformdirs" 998 - version = "4.5.1" 947 + name = "prompt-toolkit" 948 + version = "3.0.52" 999 949 source = { registry = "https://pypi.org/simple" } 1000 - sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } 950 + dependencies = [ 951 + { name = "wcwidth" }, 952 + ] 953 + sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } 1001 954 wheels = [ 1002 - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, 955 + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, 1003 956 ] 1004 957 1005 958 [[package]] ··· 1214 1167 ] 1215 1168 1216 1169 [[package]] 1217 - name = "pygments" 1218 - version = "2.19.2" 1219 - source = { registry = "https://pypi.org/simple" } 1220 - sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } 1221 - wheels = [ 1222 - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, 1223 - ] 1224 - 1225 - [[package]] 1226 1170 name = "python-dotenv" 1227 1171 version = "1.2.1" 1228 1172 source = { registry = "https://pypi.org/simple" } ··· 1420 1364 ] 1421 1365 1422 1366 [[package]] 1423 - name = "rich" 1424 - version = "14.3.2" 1425 - source = { registry = "https://pypi.org/simple" } 1426 - dependencies = [ 1427 - { name = "markdown-it-py" }, 1428 - { name = "pygments" }, 1429 - ] 1430 - sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } 1431 - wheels = [ 1432 - { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, 1433 - ] 1434 - 1435 - [[package]] 1436 1367 name = "rpds-py" 1437 1368 version = "0.30.0" 1438 1369 source = { registry = "https://pypi.org/simple" } ··· 1559 1490 ] 1560 1491 1561 1492 [[package]] 1562 - name = "textual" 1563 - version = "7.5.0" 1564 - source = { registry = "https://pypi.org/simple" } 1565 - dependencies = [ 1566 - { name = "markdown-it-py", extra = ["linkify"] }, 1567 - { name = "mdit-py-plugins" }, 1568 - { name = "platformdirs" }, 1569 - { name = "pygments" }, 1570 - { name = "rich" }, 1571 - { name = "typing-extensions" }, 1572 - ] 1573 - sdist = { url = "https://files.pythonhosted.org/packages/9f/38/7d169a765993efde5095c70a668bf4f5831bb7ac099e932f2783e9b71abf/textual-7.5.0.tar.gz", hash = "sha256:c730cba1e3d704e8f1ca915b6a3af01451e3bca380114baacf6abf87e9dac8b6", size = 1592319, upload-time = "2026-01-30T13:46:39.881Z" } 1574 - wheels = [ 1575 - { url = "https://files.pythonhosted.org/packages/9c/78/96ddb99933e11d91bc6e05edae23d2687e44213066bcbaca338898c73c47/textual-7.5.0-py3-none-any.whl", hash = "sha256:849dfee9d705eab3b2d07b33152b7bd74fb1f5056e002873cc448bce500c6374", size = 718164, upload-time = "2026-01-30T13:46:37.635Z" }, 1576 - ] 1577 - 1578 - [[package]] 1579 1493 name = "tiktoken" 1580 1494 version = "0.12.0" 1581 1495 source = { registry = "https://pypi.org/simple" } ··· 1702 1616 ] 1703 1617 1704 1618 [[package]] 1705 - name = "uc-micro-py" 1706 - version = "1.0.3" 1619 + name = "urllib3" 1620 + version = "2.6.3" 1707 1621 source = { registry = "https://pypi.org/simple" } 1708 - sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043, upload-time = "2024-02-09T16:52:01.654Z" } 1622 + sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } 1709 1623 wheels = [ 1710 - { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, 1624 + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, 1711 1625 ] 1712 1626 1713 1627 [[package]] 1714 - name = "urllib3" 1715 - version = "2.6.3" 1628 + name = "wcwidth" 1629 + version = "0.6.0" 1716 1630 source = { registry = "https://pypi.org/simple" } 1717 - sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } 1631 + sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } 1718 1632 wheels = [ 1719 - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, 1633 + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, 1720 1634 ] 1721 1635 1722 1636 [[package]]