<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: will-mcgugan</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/will-mcgugan.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-07-23T16:17:46+00:00</updated><author><name>Simon Willison</name></author><entry><title>Announcing Toad - a universal UI for agentic coding in the terminal</title><link href="https://simonwillison.net/2025/Jul/23/announcing-toad/#atom-tag" rel="alternate"/><published>2025-07-23T16:17:46+00:00</published><updated>2025-07-23T16:17:46+00:00</updated><id>https://simonwillison.net/2025/Jul/23/announcing-toad/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://willmcgugan.github.io/announcing-toad/"&gt;Announcing Toad - a universal UI for agentic coding in the terminal&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Will McGugan is building his own take on a terminal coding assistant, in the style of Claude Code and Gemini CLI, using his &lt;a href="https://github.com/Textualize/textual"&gt;Textual&lt;/a&gt; Python library as the display layer.&lt;/p&gt;
&lt;p&gt;Will makes some confident claims about this being a better approach than the Node UI libraries used in those other tools:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Both Anthropic and Google’s apps flicker due to the way they perform visual updates. These apps update the terminal by removing the previous lines and writing new output (even if only a single line needs to change). This is a surprisingly expensive operation in terminals, and has a high likelihood you will see a partial frame—which will be perceived as flicker. [...]&lt;/p&gt;
&lt;p&gt;Toad doesn’t suffer from these issues. There is no flicker, as it can update partial regions of the output as small as a single character. You can also scroll back up and interact with anything that was previously written, including copying un-garbled output — even if it is cropped.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using Node.js for terminal apps means that users with &lt;code&gt;npx&lt;/code&gt; can run them easily without worrying too much about installation - Will points out that &lt;code&gt;uvx&lt;/code&gt; has closed the developer experience there for tools written in Python.&lt;/p&gt;
&lt;p&gt;Toad will be open source eventually, but is currently in a private preview that's open to companies who sponsor Will's work for $5,000:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] you can gain access to Toad by &lt;a href="https://github.com/sponsors/willmcgugan/sponsorships?sponsor=willmcgugan&amp;amp;tier_id=506004"&gt;sponsoring me on GitHub sponsors&lt;/a&gt;. I anticipate Toad being used by various commercial organizations where $5K a month wouldn't be a big ask. So consider this a buy-in to influence the project for communal benefit at this early stage.&lt;/p&gt;
&lt;p&gt;With a bit of luck, this sabbatical needn't eat in to my retirement fund too much. If it goes well, it may even become my full-time gig.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I really hope this works! It would be great to see this kind of model proven as a new way to financially support experimental open source projects of this nature.&lt;/p&gt;
&lt;p&gt;I wrote about Textual's streaming markdown implementation &lt;a href="https://simonwillison.net/2025/Jul/22/textual-v4/"&gt;the other day&lt;/a&gt;, and this post goes into a whole lot more detail about optimizations Will has discovered for making that work better.&lt;/p&gt;
&lt;p&gt;The key optimization is to only re-render the last displayed block of the Markdown document, which might be a paragraph or a heading or a table or list, avoiding having to re-render the entire thing any time a token is added to it... with one important catch:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It turns out that the very last block can change its type when you add new content. Consider a table where the first tokens add the headers to the table. The parser considers that text to be a simple paragraph block up until the entire row has arrived, and then all-of-a-sudden the paragraph becomes a table.&lt;/p&gt;
&lt;/blockquote&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&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/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/coding-agents"&gt;coding-agents&lt;/a&gt;&lt;/p&gt;



</summary><category term="open-source"/><category term="markdown"/><category term="ai"/><category term="will-mcgugan"/><category term="generative-ai"/><category term="llms"/><category term="uv"/><category term="coding-agents"/></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>Quoting Will McGugan</title><link href="https://simonwillison.net/2021/Aug/6/will-mcgugan/#atom-tag" rel="alternate"/><published>2021-08-06T16:17:31+00:00</published><updated>2021-08-06T16:17:31+00:00</updated><id>https://simonwillison.net/2021/Aug/6/will-mcgugan/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://twitter.com/willmcgugan/status/1423678688802058244"&gt;&lt;p&gt;The thing about semver major version numbers are that they don't mean new stuff, they're a permanent reminder of how many times you got the API wrong. Semver doesn't mean MAJOR.MINOR.PATCH, it means FAILS.FEATURES.BUGS&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://twitter.com/willmcgugan/status/1423678688802058244"&gt;Will McGugan&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/semantic-versioning"&gt;semantic-versioning&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/versioning"&gt;versioning&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/will-mcgugan"&gt;will-mcgugan&lt;/a&gt;&lt;/p&gt;



</summary><category term="semantic-versioning"/><category term="versioning"/><category term="will-mcgugan"/></entry><entry><title>How to get Rich with Python (a terminal rendering library)</title><link href="https://simonwillison.net/2020/May/4/rich/#atom-tag" rel="alternate"/><published>2020-05-04T23:27:08+00:00</published><updated>2020-05-04T23:27:08+00:00</updated><id>https://simonwillison.net/2020/May/4/rich/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.willmcgugan.com/blog/tech/post/how-to-get-rich-with-python-a-terminal-rendering-library/"&gt;How to get Rich with Python (a terminal rendering library)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Will McGugan introduces Rich, his new Python library for rendering content on the terminal. This is a very cool piece of software—out of the box it supports coloured text, emoji, tables, rendering Markdown, syntax highlighting code, rendering Python tracebacks, progress bars and more. “pip install rich” and then “python -m rich” to render a “test card” demo demonstrating the features of the library.


    &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/will-mcgugan"&gt;will-mcgugan&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rich"&gt;rich&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="python"/><category term="will-mcgugan"/><category term="rich"/></entry></feed>