<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: textual</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/textual.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-12-09T20:19:21+00:00</updated><author><name>Simon Willison</name></author><entry><title>mistralai/mistral-vibe</title><link href="https://simonwillison.net/2025/Dec/9/mistral-vibe/#atom-tag" rel="alternate"/><published>2025-12-09T20:19:21+00:00</published><updated>2025-12-09T20:19:21+00:00</updated><id>https://simonwillison.net/2025/Dec/9/mistral-vibe/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mistralai/mistral-vibe"&gt;mistralai/mistral-vibe&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Here's the Apache 2.0 licensed source code for Mistral's new "Vibe" CLI coding agent, &lt;a href="https://mistral.ai/news/devstral-2-vibe-cli"&gt;released today&lt;/a&gt; alongside Devstral 2.&lt;/p&gt;
&lt;p&gt;It's a neat implementation of the now standard terminal coding agent pattern, built in Python on top of Pydantic and Rich/Textual (here are &lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/pyproject.toml#L29-L46"&gt;the dependencies&lt;/a&gt;.) &lt;a href="https://github.com/google-gemini/gemini-cli"&gt;Gemini CLI&lt;/a&gt; is TypeScript, Claude Code is closed source (TypeScript, now &lt;a href="https://simonwillison.net/2025/Dec/2/anthropic-acquires-bun/"&gt;on top of Bun&lt;/a&gt;), OpenAI's &lt;a href="https://github.com/openai/codex"&gt;Codex CLI&lt;/a&gt; is Rust. &lt;a href="https://github.com/OpenHands/OpenHands"&gt;OpenHands&lt;/a&gt; is the other major Python coding agent I know of, but I'm likely missing some others. (UPDATE: &lt;a href="https://github.com/MoonshotAI/kimi-cli"&gt;Kimi CLI&lt;/a&gt; is another open source Apache 2 Python one.)&lt;/p&gt;
&lt;p&gt;The Vibe source code is pleasant to read and the crucial prompts are neatly extracted out into Markdown files. Some key places to look:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/prompts/cli.md"&gt;core/prompts/cli.md&lt;/a&gt; is the main system prompt ("You are operating as and within Mistral Vibe, a CLI coding-agent built by Mistral AI...")&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/prompts/compact.md"&gt;core/prompts/compact.md&lt;/a&gt; is the prompt used to generate compacted summaries of conversations ("Create a comprehensive summary of our entire conversation that will serve as complete context for continuing this work...")&lt;/li&gt;
&lt;li&gt;Each of the core tools has its own prompt file:&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/bash.md"&gt;.../prompts/bash.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/grep.md"&gt;.../prompts/grep.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/read_file.md"&gt;.../prompts/read_file.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/write_file.md"&gt;.../prompts/write_file.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/search_replace.md"&gt;.../prompts/search_replace.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mistralai/mistral-vibe/blob/v1.0.4/vibe/core/tools/builtins/prompts/todo.md"&gt;.../prompts/todo.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Python implementations of those tools &lt;a href="https://github.com/mistralai/mistral-vibe/tree/v1.0.4/vibe/core/tools/builtins"&gt;can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I tried it out and had it build me a Space Invaders game using three.js with the following prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;make me a space invaders game as HTML with three.js loaded from a CDN&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Animated screenshot demo of Mistral Vibe running in a terminal. The text reads: I've created a Space Invaders game using HTML and Three. js loaded from a CDN. The game is now available in the file space_invaders.html in your current directory. Here's how to play: 1. Open the space_invaders.html file in a web browser 2. Use the left and right arrow keys to move your player (green rectangle) 3. Press the spacebar to shoot at the invaders (red rectangles) 4. Try to get the highest score before the invaders reach you or hit you with their bullets The game features: © Player movement with arrow keys © Shooting mechanics with spacebar © Enemy invaders that move back and forth © Collision detection « Score tracking * Game over screen © Increasing difficulty Writing file (64s esc to interrupt) »» auto-approve on (shift-tab to toggle) - 7% of 100k tokens" src="https://static.simonwillison.net/static/2025/vibe.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/simonw/space-invaders-by-llms/blob/main/mistral-vibe-devstral-2/index.html"&gt;the source code&lt;/a&gt;  and &lt;a href="https://space-invaders.simonwillison.net/mistral-vibe-devstral-2/"&gt;the live game&lt;/a&gt; (hosted in my new &lt;a href="https://github.com/simonw/space-invaders-by-llms"&gt;space-invaders-by-llms&lt;/a&gt; repo). It did OK.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/prompt-engineering"&gt;prompt-engineering&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/textual"&gt;textual&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mistral"&gt;mistral&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pydantic"&gt;pydantic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vibe-coding"&gt;vibe-coding&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/coding-agents"&gt;coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/system-prompts"&gt;system-prompts&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/space-invaders"&gt;space-invaders&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="ai"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llms"/><category term="textual"/><category term="ai-assisted-programming"/><category term="mistral"/><category term="pydantic"/><category term="vibe-coding"/><category term="coding-agents"/><category term="system-prompts"/><category term="space-invaders"/></entry><entry><title>Textual v4.0.0: The Streaming Release</title><link href="https://simonwillison.net/2025/Jul/22/textual-v4/#atom-tag" rel="alternate"/><published>2025-07-22T00:32:53+00:00</published><updated>2025-07-22T00:32:53+00:00</updated><id>https://simonwillison.net/2025/Jul/22/textual-v4/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Textualize/textual/releases/tag/v4.0.0"&gt;Textual v4.0.0: The Streaming Release&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Will McGugan may &lt;a href="https://textual.textualize.io/blog/2025/05/07/the-future-of-textualize/"&gt;no longer be running&lt;/a&gt; a commercial company around Textual, but that hasn't stopped his progress on the open source project.&lt;/p&gt;
&lt;p&gt;He recently released v4 of his Python framework for building TUI command-line apps, and the signature feature is &lt;a href="https://github.com/Textualize/textual/pull/5950"&gt;streaming Markdown support&lt;/a&gt; - super relevant in our current age of LLMs, most of which default to outputting a stream of Markdown via their APIs.&lt;/p&gt;
&lt;p&gt;I took an example &lt;a href="https://github.com/Textualize/textual/blob/03b94706399f110ff93fa396d4afbc79c3738638/tests/snapshot_tests/test_snapshots.py#L4378-L4400"&gt;from one of his tests&lt;/a&gt;, spliced in my &lt;a href="https://llm.datasette.io/en/stable/python-api.html#async-models"&gt;async LLM Python library&lt;/a&gt; and &lt;a href="https://chatgpt.com/share/687c3a6a-4e1c-8006-83a2-706b4bf04067"&gt;got some help from o3&lt;/a&gt; to turn it into &lt;a href="https://github.com/simonw/tools/blob/916b16cd7dfd3c23315d0a4ed02172878feafa45/python/streaming_textual_markdown.py"&gt;a streaming script&lt;/a&gt; for talking to models, which can be run like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv run http://tools.simonwillison.net/python/streaming_textual_markdown.py \
'Markdown headers and tables comparing pelicans and wolves' \
-m gpt-4.1-mini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Running that prompt streams a Markdown table to my console." src="https://static.simonwillison.net/static/2025/epic-table.gif" /&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/async"&gt;async&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/markdown"&gt;markdown&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/will-mcgugan"&gt;will-mcgugan&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/textual"&gt;textual&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;&lt;/p&gt;



</summary><category term="async"/><category term="python"/><category term="markdown"/><category term="ai"/><category term="will-mcgugan"/><category term="generative-ai"/><category term="llms"/><category term="textual"/><category term="llm"/><category term="uv"/></entry><entry><title>Anatomy of a Textual User Interface</title><link href="https://simonwillison.net/2024/Sep/2/anatomy-of-a-textual-user-interface/#atom-tag" rel="alternate"/><published>2024-09-02T16:39:51+00:00</published><updated>2024-09-02T16:39:51+00:00</updated><id>https://simonwillison.net/2024/Sep/2/anatomy-of-a-textual-user-interface/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://textual.textualize.io/blog/2024/09/15/anatomy-of-a-textual-user-interface/"&gt;Anatomy of a Textual User Interface&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Will McGugan used &lt;a href="https://textual.textualize.io/"&gt;Textual&lt;/a&gt; and my &lt;a href="https://llm.datasette.io/en/stable/python-api.html"&gt;LLM Python library&lt;/a&gt; to build a delightful TUI for talking to a simulation of &lt;a href="https://alienanthology.fandom.com/wiki/MU-TH-UR_6000"&gt;Mother&lt;/a&gt;, the AI from the Aliens movies:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Animated screenshot of a terminal app called MotherApp. Mother: INTERFACE 2037 READY FOR INQUIRY. I type: Who is onboard? Mother replies, streaming content to the screen:  The crew of the Nostromo consists of the following personnel: 1. Captain Arthur Dallas - commanding officer. 2. Executive Officer Thomas Kane - second-in-command. 3. Warrant Officer Ellen Ripley - third-in-command. 4. Navigator Joan Lambert - responsible for navigation and communications. 5. Science Officer Ash - responsible for scientific analysis. 6. Engineering Technician Brett - maintenance and repair. 7. Chief Engineer Parker - head of the engineering department. All crew members are currently accounted for. How may I assist you further?" src="https://static.simonwillison.net/static/2024/llm-mother-onboard.gif" /&gt;&lt;/p&gt;
&lt;p&gt;The entire implementation is just &lt;a href="https://gist.github.com/willmcgugan/648a537c9d47dafa59cb8ece281d8c2c"&gt;77 lines of code&lt;/a&gt;. It includes &lt;a href="https://peps.python.org/pep-0723/"&gt;PEP 723&lt;/a&gt; inline dependency information:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;# /// script&lt;/span&gt;
&lt;span class="pl-c"&gt;# requires-python = "&amp;gt;=3.12"&lt;/span&gt;
&lt;span class="pl-c"&gt;# dependencies = [&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "llm",&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "textual",&lt;/span&gt;
&lt;span class="pl-c"&gt;# ]&lt;/span&gt;
&lt;span class="pl-c"&gt;# ///&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;Which means you can run it in a dedicated environment with the correct dependencies installed using &lt;a href="https://docs.astral.sh/uv/guides/scripts/"&gt;uv run&lt;/a&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;wget &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;https://gist.githubusercontent.com/willmcgugan/648a537c9d47dafa59cb8ece281d8c2c/raw/7aa575c389b31eb041ae7a909f2349a96ffe2a48/mother.py&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt; OPENAI_API_KEY=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;sk-...&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
uv run mother.py&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I found the &lt;code&gt;send_prompt()&lt;/code&gt; method particularly interesting. Textual uses &lt;code&gt;asyncio&lt;/code&gt; for its event loop, but LLM currently only supports synchronous execution and can block for several seconds while retrieving a prompt.&lt;/p&gt;
&lt;p&gt;Will used the Textual &lt;code&gt;@work(thread=True)&lt;/code&gt; decorator, &lt;a href="https://textual.textualize.io/guide/workers/#thread-workers"&gt;documented here&lt;/a&gt;, to run that operation in a thread:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;@&lt;span class="pl-en"&gt;work&lt;/span&gt;(&lt;span class="pl-s1"&gt;thread&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;send_prompt&lt;/span&gt;(&lt;span class="pl-s1"&gt;self&lt;/span&gt;, &lt;span class="pl-s1"&gt;prompt&lt;/span&gt;: &lt;span class="pl-s1"&gt;str&lt;/span&gt;, &lt;span class="pl-s1"&gt;response&lt;/span&gt;: &lt;span class="pl-v"&gt;Response&lt;/span&gt;) &lt;span class="pl-c1"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;:
    &lt;span class="pl-s1"&gt;response_content&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;""&lt;/span&gt;
    &lt;span class="pl-s1"&gt;llm_response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;self&lt;/span&gt;.&lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-en"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s1"&gt;prompt&lt;/span&gt;, &lt;span class="pl-s1"&gt;system&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-v"&gt;SYSTEM&lt;/span&gt;)
    &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;chunk&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;llm_response&lt;/span&gt;:
        &lt;span class="pl-s1"&gt;response_content&lt;/span&gt; &lt;span class="pl-c1"&gt;+=&lt;/span&gt; &lt;span class="pl-s1"&gt;chunk&lt;/span&gt;
        &lt;span class="pl-s1"&gt;self&lt;/span&gt;.&lt;span class="pl-en"&gt;call_from_thread&lt;/span&gt;(&lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-s1"&gt;update&lt;/span&gt;, &lt;span class="pl-s1"&gt;response_content&lt;/span&gt;)&lt;/pre&gt;

&lt;p&gt;Looping through the response like that and calling &lt;code&gt;self.call_from_thread(response.update, response_content)&lt;/code&gt; with an accumulated string is all it takes to implement streaming responses in the Textual UI, and that &lt;code&gt;Response&lt;/code&gt; object sublasses &lt;code&gt;textual.widgets.Markdown&lt;/code&gt; so any Markdown is rendered using Rich.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/will-mcgugan"&gt;will-mcgugan&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/textual"&gt;textual&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rich"&gt;rich&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="will-mcgugan"/><category term="textual"/><category term="llm"/><category term="uv"/><category term="rich"/></entry><entry><title>textual-paint</title><link href="https://simonwillison.net/2023/Jul/25/textual-paint/#atom-tag" rel="alternate"/><published>2023-07-25T14:32:12+00:00</published><updated>2023-07-25T14:32:12+00:00</updated><id>https://simonwillison.net/2023/Jul/25/textual-paint/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/1j01/textual-paint"&gt;textual-paint&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Isaiah Odhner built a full working clone of MS Paint that runs entirely in the terminal, using Textual and Python. It’s worth trying this out to get a feel for quite how much you can get done with a CLI app these days—“pipx install textual-paint” worked for me.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/willmcgugan/status/1683762496010612736"&gt;@willmcgugan&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/textual"&gt;textual&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="python"/><category term="textual"/></entry><entry><title>textual-mandelbrot</title><link href="https://simonwillison.net/2023/Apr/1/textual-mandelbrot/#atom-tag" rel="alternate"/><published>2023-04-01T19:23:26+00:00</published><updated>2023-04-01T19:23:26+00:00</updated><id>https://simonwillison.net/2023/Apr/1/textual-mandelbrot/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/davep/textual-mandelbrot"&gt;textual-mandelbrot&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I love this: run “pipx install textual-mandelbrot” and then “mandelexp” to get an interactive Mandelbrot fractal exploration interface right there in your terminal, built on top of Textual. The code for this is only 250 lines of Python and delightfully easy to follow.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/davepdotorg/status/1642083302038200322"&gt;@davepdotorg&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/mandelbrot"&gt;mandelbrot&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/textual"&gt;textual&lt;/a&gt;&lt;/p&gt;



</summary><category term="mandelbrot"/><category term="python"/><category term="textual"/></entry></feed>