<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ai-Assisted on Nelson Chen's Blog</title><link>https://mindflakes.com/tags/ai-assisted/</link><description>Recent content in Ai-Assisted on Nelson Chen's Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Copyright Nelson Chen</copyright><lastBuildDate>Mon, 07 Sep 2026 10:34:51 -0700</lastBuildDate><atom:link href="https://mindflakes.com/tags/ai-assisted/index.xml" rel="self" type="application/rss+xml"/><item><title>Weftan, a backup plan that became a Rust rabbit hole</title><link>https://mindflakes.com/posts/2026/09/07/weftan-rust-rabbit-hole/</link><pubDate>Mon, 07 Sep 2026 10:34:51 -0700</pubDate><guid>https://mindflakes.com/posts/2026/09/07/weftan-rust-rabbit-hole/</guid><description>&lt;p&gt;Weftan started as a fun backup plan. If TALA died hard, could I still have the layout engine around in some usable form?&lt;/p&gt;
&lt;p&gt;I built Weftan with the assistance of Codex, using the Luna models available at the time. I supplied the ideas and directed the work; Codex did essentially all of the technical investigation and coding described here, including the reverse engineering and Rust implementation. AI also drafted this post.&lt;/p&gt;
&lt;p&gt;TALA arranges diagrams for D2. Recovering its behavior and rebuilding it in Rust seemed like an interesting thing to try. That eventually became &lt;a href="https://github.com/nelsonjchen/weftan"&gt;Weftan&lt;/a&gt;, and then a larger work-in-progress project called &lt;code&gt;d2-rs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Weftan was faster than the Go version of TALA when that was an external proprietary binary. The slightly annoying part is that TALA built into D2 was faster still. So naturally I started thinking about rewriting more of the surrounding thing too.&lt;/p&gt;
&lt;h2 id="recovering-the-behavior"&gt;Recovering the behavior&lt;/h2&gt;
&lt;p&gt;Under my direction, Codex investigated TALA release artifacts: symbols, debugging information, disassembly, and comparisons with the executable. The Go code it reconstructed informed its Rust implementation. This was recovery work, and the provenance matters; I did not independently invent the same layout engine by looking at a few pictures.&lt;/p&gt;
&lt;p&gt;Then TALA&amp;rsquo;s source became available in D2 v0.9.0. That changed the situation quite a bit. There was now an upstream source implementation to follow and compare against, instead of relying entirely on recovered behavior.&lt;/p&gt;
&lt;p&gt;There is a lot hidden inside “put the boxes somewhere sensible.” Containers, labels, edge routes, and the order in which equally reasonable choices get considered can all affect the final result. Two diagrams can look basically the same while disagreeing on a lot of coordinates.&lt;/p&gt;
&lt;p&gt;So the useful comparison was to give both implementations the same measured graph and seed, then compare the actual geometry. When something differed, there was a smaller question to investigate than “why is this picture weird?”&lt;/p&gt;
&lt;h2 id="matching-a-corpus"&gt;Matching a corpus&lt;/h2&gt;
&lt;p&gt;The recorded Weftan baseline reached exact matches on all 120 successful single-seed comparisons in the checked-in corpus. That included 2,964 boxes and 974 routes, along with their corresponding labels and node icons. One large-grid case hit the work limit in both engines and was recorded separately.&lt;/p&gt;
&lt;p&gt;I was happy with that. I also want to keep the size of the claim attached to it: those are the cases in that corpus, with that reference version and comparison setup. It does not mean every possible D2 input will behave identically.&lt;/p&gt;
&lt;p&gt;Being able to reproduce a mismatch is useful even before it is fixed. It gives the next round of work an actual target. Otherwise it is very easy to keep improving the appearance of one example without knowing whether another example got worse.&lt;/p&gt;
&lt;p&gt;The comparison machinery ended up being a useful result of the experiment in its own right.&lt;/p&gt;
&lt;h2 id="the-plugin-tax"&gt;The plugin tax&lt;/h2&gt;
&lt;p&gt;Weftan plugs into D2 as an external layout process. D2 has to pass a graph across that boundary and get the result back. Built-in TALA does not pay that same process and protocol cost.&lt;/p&gt;
&lt;p&gt;There are two different comparisons here. Against the old external proprietary Go TALA binary, Weftan was faster in my testing. Against TALA built into D2, the end-to-end measurements went the other way. The baseline changed, and that distinction matters.&lt;/p&gt;
&lt;p&gt;Plugin overhead is a plausible explanation for at least some of the gap with built-in TALA, but I have not isolated it well enough to say it accounts for the entire difference. “Rust” on the box does not make the complete workflow faster by itself.&lt;/p&gt;
&lt;p&gt;That leaves Weftan in a perfectly reasonable place as a fun experiment. The backup-plan motivation got me started, and I got a recovered engine, comparisons, and a much closer look at the behavior out of it.&lt;/p&gt;
&lt;p&gt;It also left me curious about removing that boundary altogether.&lt;/p&gt;
&lt;h2 id="following-upstream-in-rust"&gt;Following upstream in Rust&lt;/h2&gt;
&lt;p&gt;The next rabbit hole is &lt;code&gt;d2-rs&lt;/code&gt;: a pure-Rust implementation targeting native execution and WebAssembly, with the layout engines running in the same process. The TALA work from Weftan has moved into that effort.&lt;/p&gt;
&lt;p&gt;I am interested in following upstream behavior and exploring parallel execution where it makes sense. Having the surrounding pipeline in Rust should also make it easier to investigate costs without the external plugin boundary sitting in the middle of every layout.&lt;/p&gt;
&lt;p&gt;This is still under construction. There are working source-processing and rendering paths, but plenty of compatibility work remains. A working SVG example is a nice milestone; it does not establish that the renderer, all the diagram features, or the other engines are interchangeable with D2.&lt;/p&gt;
&lt;p&gt;I also do not have a speedup to announce for the rewrite. That is something to measure as it develops.&lt;/p&gt;
&lt;p&gt;For now, &lt;a href="https://github.com/nelsonjchen/weftan"&gt;Weftan is public&lt;/a&gt;. It was a fun backup plan, it taught me things, and it apparently gave me another project to work on.&lt;/p&gt;</description></item><item><title>Could the Crazy Taxi Arcade TAS use one fewer passenger?</title><link>https://mindflakes.com/posts/2026/08/10/crazy-taxi-one-fewer-passenger/</link><pubDate>Mon, 10 Aug 2026 10:34:51 -0700</pubDate><guid>https://mindflakes.com/posts/2026/08/10/crazy-taxi-one-fewer-passenger/</guid><description>&lt;p&gt;I got curious about whether a Crazy Taxi tool-assisted speedrun could be slightly faster with one fewer passenger.&lt;/p&gt;
&lt;p&gt;I investigated this Crazy Taxi route idea with the assistance of Codex, using the Luna models available at the time. I supplied the ideas and directed the work; Codex did essentially all of the technical investigation and coding, including the game analysis and viewer changes. AI also drafted this post.&lt;/p&gt;
&lt;p&gt;I do not really have TAS skills. This was a “could that work?” rabbit hole that led to looking at passenger tables, destination selection, and the game clock. I have not made a faster run.&lt;/p&gt;
&lt;p&gt;The tips are where the real TAS money is. Chaining Crazy Throughs and drifts can pile up money while carrying a single passenger. &lt;a href="https://tasvideos.org/9374S"&gt;SJ&amp;rsquo;s run&lt;/a&gt; aims for the Crazy License, reaches $20,000 with four passengers, and uses the long Police Station trip for a huge combo chain. The run ends when the clock reaches zero. So the idea is to earn enough money from tips with fewer fares, then have less clock left to burn off.&lt;/p&gt;
&lt;p&gt;The specific idea starts after the first drop-off at &lt;strong&gt;Cable Car Stop TOP&lt;/strong&gt;, the plaza at the top of the hill. In &lt;a href="https://tasvideos.org/9374S"&gt;SJ&amp;rsquo;s four-passenger route&lt;/a&gt;, the next pickup there goes to &lt;strong&gt;West Side Beach&lt;/strong&gt;, followed by a passenger going to &lt;strong&gt;Popcorn Mania&lt;/strong&gt;, and then one going to the &lt;strong&gt;Police Station&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;My idea was to leave the plaza and grab the side-street passenger going to &lt;strong&gt;FILA&lt;/strong&gt; instead of taking the nearby passenger to the beach. FILA puts me near the next Police Station pickup, so I could go from that drop-off to the final passenger without doing the beach-to-Popcorn Mania fare in between. FILA appears as “Shoe Rack” in the Android data I used for the investigation.&lt;/p&gt;
&lt;p&gt;That is why three passengers seemed promising: skip a whole fare and its extra pickup and drop-off, while keeping the long Police Station trip for earning tips. The side-street detour and the changed route could affect traffic and combo opportunities, so I would still need to show that three passengers can earn enough. Fewer stops only help if the money works out too.&lt;/p&gt;
&lt;h2 id="the-clock-is-part-of-the-problem"&gt;The clock is part of the problem&lt;/h2&gt;
&lt;p&gt;Picking up a passenger adds time to the Arcade clock. A successful delivery can add more. So changing the passenger route also changes how much time the game hands back to you.&lt;/p&gt;
&lt;p&gt;The symbol-rich Android release was useful for investigating this. Its code shows a distance-based time allowance at pickup, with a multiplier that changes as play goes on, plus a delivery-rank bonus at drop-off.&lt;/p&gt;
&lt;p&gt;In Codex&amp;rsquo;s calculation recorded in the project notes, a proposed three-passenger route adds about 12.56 fewer seconds than the four-passenger reference route. That comparison assumes the early-game multiplier and the best delivery judgement for each passenger.&lt;/p&gt;
&lt;p&gt;That is less added game time, which made the idea interesting enough to investigate further. It is not a measured 12.56-second improvement in a TAS. Driving, passenger animations, and when the clock actually advances still have to be accounted for.&lt;/p&gt;
&lt;h2 id="which-destination-do-you-get"&gt;Which destination do you get?&lt;/h2&gt;
&lt;p&gt;The Android investigation also showed that passenger destination selection follows a counter. It cycles through three destination slots, with each slot lasting 600 passenger-manager updates, roughly ten seconds at 60 updates per second.&lt;/p&gt;
&lt;p&gt;The choice is latched when the passenger event activates. Waiting until boarding finishes to reason about it would put the timing at the wrong point.&lt;/p&gt;
&lt;p&gt;That makes route planning more interesting. Reaching a passenger at a different time can select a different destination, which changes the trip and the clock allowance. Picking three convenient dots on a map is not enough.&lt;/p&gt;
&lt;p&gt;To look at this spatially, I had Codex adapt a fork of the &lt;a href="https://noclip.website/"&gt;noclip.website&lt;/a&gt; viewer to load the GameCube Arcade city map with the extracted pedestrian pickup locations overlaid on it. Credit to noclip.website for the viewer; Codex implemented the additions under my direction to turn it into a research map for this passenger-routing rabbit hole. It is much easier to reason about a side-street pickup when I can actually see where it sits in the city.&lt;/p&gt;
&lt;p&gt;The extracted passenger records match the Android reference records, which is useful evidence for understanding the data. It does not prove that every timing behavior carries over to the GameCube executable.&lt;/p&gt;
&lt;h2 id="maybe-later"&gt;Maybe later&lt;/h2&gt;
&lt;p&gt;The next step would be to verify the relevant behavior in the GameCube version and try the route in an actual TAS workflow. There is still a gap between a promising calculation and inputs that produce a faster run.&lt;/p&gt;
&lt;p&gt;For now this is a curiosity with some code and notes behind it. I need to sit down with it when things are a bit less crazy and I have time to learn the TAS side properly. I might revisit it then. One fewer passenger sounded like a small question. Apparently it came with homework.&lt;/p&gt;</description></item><item><title>codex-status-json</title><link>https://mindflakes.com/posts/2026/06/16/codex-status-json/</link><pubDate>Tue, 16 Jun 2026 15:03:00 -0700</pubDate><guid>https://mindflakes.com/posts/2026/06/16/codex-status-json/</guid><description>&lt;p&gt;I made a small &lt;a href="https://github.com/nelsonjchen/codex-status-command"&gt;Rust CLI that prints Codex status as JSON&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I built this tool with the assistance of Codex. I supplied the idea and directed the work; Codex did the technical investigation and implementation. Codex also drafted this post.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update, October 11, 2026: this is now mostly a historical workaround for my Codex app workflow.&lt;/strong&gt; The app now gives conversations a built-in way to read usage limits, so I no longer need this helper for that purpose. For manual checks, there is also the &lt;a href="https://developers.openai.com/codex/pricing"&gt;usage dashboard and the CLI&amp;rsquo;s &lt;code&gt;/status&lt;/code&gt; command&lt;/a&gt;. That does not make every use of a standalone JSON tool obsolete, but it fills the gap that motivated mine. The rest of this post describes the workaround as it stood in June.&lt;/p&gt;
&lt;p&gt;The short version is that I wanted Codex conversations to know my account, model, and quota state without opening the interactive TUI and reading &lt;code&gt;/status&lt;/code&gt; like a person.&lt;/p&gt;
&lt;p&gt;This is mostly useful with Codex goals. I am on the &lt;a href="https://developers.openai.com/codex/pricing"&gt;Pro 20x plan&lt;/a&gt;, which is the $200/month tier, so sometimes I really do have quota to burn. If I can read the remaining quota first, I can make a goal with an actual target instead of guessing how ambitious I should be.&lt;/p&gt;
&lt;h2 id="the-missing-command"&gt;The missing command&lt;/h2&gt;
&lt;p&gt;Codex has &lt;code&gt;/status&lt;/code&gt; in the interactive CLI. It shows useful stuff: account, model, context, writable roots, and rate limits.&lt;/p&gt;
&lt;p&gt;That is fine when I am sitting in the TUI. It is less fine when I want another Codex conversation to ask the same question.&lt;/p&gt;
&lt;p&gt;There is an upstream issue asking for this exact kind of thing: &lt;a href="https://github.com/openai/codex/issues/10233"&gt;a non-interactive JSON/headless replacement for &lt;code&gt;/status&lt;/code&gt;&lt;/a&gt;. The issue is still open as I write this. There is also &lt;code&gt;codex doctor --json&lt;/code&gt;, which is useful, but that is a diagnostic report. It is not the same as &amp;ldquo;tell me my account/model/quota status right now.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So I made &lt;code&gt;codex-status-json&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-first-version-was-worse"&gt;The first version was worse&lt;/h2&gt;
&lt;p&gt;The first version did what you would expect from a tool born out of impatience: it opened Codex in a PTY, sent &lt;code&gt;/status&lt;/code&gt;, waited for the panel to settle, captured the terminal output, stripped ANSI escapes, and parsed the text.&lt;/p&gt;
&lt;p&gt;It worked. It was also clearly a bad long-term place to live.&lt;/p&gt;
&lt;p&gt;Terminal scraping is annoying because every little UI change can break you. The parser has to care about box drawing, timing, update notices, trust prompts, terminal width, and whatever else the TUI happens to render that day.&lt;/p&gt;
&lt;p&gt;Still, it was useful. It gave me JSON like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;schema_version&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;account&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;user@example.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;plan&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Pro&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;raw&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;user@example.com (Pro)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;model&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;gpt-5.5&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;details&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;reasoning high&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;raw&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;gpt-5.5 (reasoning high)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;buckets&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;limits&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;5h&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;remaining_percent&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;99&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;resets&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;14:44&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;reset_at_unix&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1780350290&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;weekly&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;remaining_percent&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;70&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;resets&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;08:23 on 7 Jun&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;reset_at_unix&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1780845815&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That was enough to unblock the thing I wanted.&lt;/p&gt;
&lt;h2 id="the-less-cursed-version"&gt;The less cursed version&lt;/h2&gt;
&lt;p&gt;A few days later, I had Codex switch the default backend to Codex&amp;rsquo;s &lt;a href="https://developers.openai.com/codex/app-server"&gt;app-server&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That is a much better source than scraping the TUI. The tool starts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;codex app-server --listen stdio://
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then it sends JSON-RPC calls for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;account/read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;account/rateLimits/read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/read&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That gives it the same kind of account, model, and quota information without pretending a terminal status panel is an API.&lt;/p&gt;
&lt;p&gt;Important caveat: Codex app-server is documented, but it is also described as experimental and may change. So this is still not a supported &lt;code&gt;codex status --json&lt;/code&gt;. It is just a more sensible workaround than screen scraping.&lt;/p&gt;
&lt;p&gt;The PTY backend is still there as a fallback:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;codex-status-json --backend pty
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But the default is now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;codex-status-json --backend app-server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="this-is-a-bridge"&gt;This is a bridge&lt;/h2&gt;
&lt;p&gt;I do not expect this to be the forever answer.&lt;/p&gt;
&lt;p&gt;Codex&amp;rsquo;s usage and billing surface is still moving. OpenAI has already introduced things like &lt;a href="https://developers.openai.com/codex/pricing"&gt;banked rate-limit resets&lt;/a&gt;, so the status shape is not just &amp;ldquo;two counters and call it done.&amp;rdquo; There are plans, windows, reset times, secondary buckets, and whatever else comes next.&lt;/p&gt;
&lt;p&gt;So I can see why OpenAI might not want to commit to a stable status JSON contract yet. That is fine. I still wanted something local that worked now.&lt;/p&gt;
&lt;p&gt;This is the same kind of bridge as a lot of these small tools: use the available pieces, make the annoying gap tolerable, and be happy if the official thing eventually replaces it.&lt;/p&gt;
&lt;h2 id="the-repo"&gt;The repo&lt;/h2&gt;
&lt;p&gt;The project is here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/nelsonjchen/codex-status-command"&gt;https://github.com/nelsonjchen/codex-status-command&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Install it locally with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cargo install --path .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The README has the options and limitations. The tests include a captured &lt;code&gt;/status&lt;/code&gt; fixture so the text parser can at least fail loudly when Codex changes its display.&lt;/p&gt;
&lt;p&gt;I would still rather have an official &lt;code&gt;codex status --json&lt;/code&gt;. Until then, this is small, local, and useful enough.&lt;/p&gt;</description></item><item><title>Plainize Clip</title><link>https://mindflakes.com/posts/2026/06/14/plainize-clip/</link><pubDate>Sun, 14 Jun 2026 23:05:00 -0700</pubDate><guid>https://mindflakes.com/posts/2026/06/14/plainize-clip/</guid><description>&lt;p&gt;I made &lt;a href="https://github.com/nelsonjchen/plainize-clip"&gt;Plainize Clip&lt;/a&gt;, a tiny macOS app that cleans the current clipboard and quits.&lt;/p&gt;
&lt;p&gt;I built Plainize Clip with the assistance of Codex. I supplied the idea and directed the work; Codex did the reverse engineering and implementation. Codex also drafted this post.&lt;/p&gt;
&lt;p&gt;The short version is that I still wanted the old &lt;a href="https://www.macworld.com/article/195102/plainclip2.html"&gt;Plain Clip&lt;/a&gt; shape: copy weird text, launch the app, paste something boring.&lt;/p&gt;
&lt;p&gt;Not a clipboard manager. Not a menu bar app. Not a background process sitting around in RAM forever. I launch it through Spotlight, it rewrites the pasteboard as plain text, and then it exits.&lt;/p&gt;
&lt;h2 id="the-annoying-clipboard-problem"&gt;The annoying clipboard problem&lt;/h2&gt;
&lt;p&gt;Copying text on macOS is often not just copying text.&lt;/p&gt;
&lt;p&gt;Sometimes it brings formatting. Sometimes it brings weird invisible characters. Sometimes it brings smart quotes, hard-wrapped lines, non-breaking spaces, tabs, and whatever else got dragged along from a PDF, web page, chat app, or ticketing system.&lt;/p&gt;
&lt;p&gt;Usually I do not want a whole clipboard workflow. I just want the pasteboard to stop being annoying.&lt;/p&gt;
&lt;p&gt;So Plainize Clip does the small version:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the general pasteboard.&lt;/li&gt;
&lt;li&gt;If there is text, clean it.&lt;/li&gt;
&lt;li&gt;Write back plain text only.&lt;/li&gt;
&lt;li&gt;Quit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That is the whole normal launch path.&lt;/p&gt;
&lt;h2 id="launch-clean-quit"&gt;Launch, clean, quit&lt;/h2&gt;
&lt;p&gt;The important behavior is that Plainize Clip is faceless.&lt;/p&gt;
&lt;p&gt;There is no Dock icon to manage after the cleanup. There is no persistent process. There is no history database. If the current pasteboard has text, it cleans it with the saved preferences and replaces the pasteboard contents with text.&lt;/p&gt;
&lt;p&gt;The prod was Apple&amp;rsquo;s &lt;a href="https://support.apple.com/en-us/102527"&gt;Rosetta&lt;/a&gt; clock. Apple says Rosetta will remain available as a general-purpose Intel app translation tool through macOS 27, and then becomes limited starting with macOS 28. So an Intel-only utility like the old Plain Clip had a stronger &amp;ldquo;you should probably stop depending on this forever&amp;rdquo; vibe.&lt;/p&gt;
&lt;p&gt;Choosing Swift/AppKit/SwiftUI was not a deep architectural statement. I just wanted to follow Apple&amp;rsquo;s current recommendations for a small Mac app.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/nelsonjchen/plainize-clip/releases"&gt;GitHub releases&lt;/a&gt; are unsigned, so macOS does the usual &amp;ldquo;unknown developer&amp;rdquo; dance. Apple&amp;rsquo;s support page documents the &lt;a href="https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac"&gt;Open Anyway&lt;/a&gt; flow. This is annoying, but paying Apple for a Developer ID certificate for a small personal clipboard cleaner is also annoying.&lt;/p&gt;
&lt;h2 id="reverse-engineering-because-codex-made-it-cheap"&gt;Reverse-engineering because Codex made it cheap&lt;/h2&gt;
&lt;p&gt;I had &lt;a href="https://developers.openai.com/codex/"&gt;Codex&lt;/a&gt; reverse-engineer the old app under my direction. That made it practical to investigate behavior I otherwise would not have lifted a finger for. Codex recorded its investigation in the local &lt;code&gt;plain-clip-re&lt;/code&gt; workspace: static inventory, Ghidra headless exports, behavior notes, and then a recovered map of what the old app actually did.&lt;/p&gt;
&lt;p&gt;The useful bits were not mysterious. They were just tedious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;normal launch cleans the pasteboard and quits&lt;/li&gt;
&lt;li&gt;holding Shift at launch opens preferences&lt;/li&gt;
&lt;li&gt;the command-line wrapper has switches for the same cleanup options&lt;/li&gt;
&lt;li&gt;the cleaner reads string content from the general pasteboard&lt;/li&gt;
&lt;li&gt;after cleaning, it replaces the pasteboard with string-only content&lt;/li&gt;
&lt;li&gt;the pipeline handles line endings, whitespace, hard wraps, blank lines, smart quotes, tabs, Unicode normalization, and ASCII conversion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Codex used those findings to implement the replacement under my direction, with the old app&amp;rsquo;s behavior as a reference instead of just making &amp;ldquo;a clipboard cleaner, probably.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Plainize Clip is still a separate implementation and does not reuse the old app&amp;rsquo;s code. The reverse-engineering work was mostly there to answer boring compatibility questions without relying on memory.&lt;/p&gt;
&lt;h2 id="the-cleanup-switches"&gt;The cleanup switches&lt;/h2&gt;
&lt;p&gt;Plainize Clip has preferences because text cleanup is personal enough to need switches.&lt;/p&gt;
&lt;p&gt;The defaults do the stuff I usually want: trim whitespace, remove empty-line noise, join hard-wrapped prose, replace tabs with spaces, collapse repeated spaces, remove invisible control characters, and turn smart quotes and typographic dashes into simpler punctuation.&lt;/p&gt;
&lt;p&gt;There are also options for Unicode normalization and ASCII conversion. The ASCII path tries to romanize non-Latin scripts before filtering, so it does not turn a clipboard full of CJK, Korean, Arabic, Hebrew, or Cyrillic text into an empty string. It is best-effort, not magic.&lt;/p&gt;
&lt;p&gt;Preferences open by holding Shift while launching, or from Terminal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;open -a &lt;span style="color:#e6db74"&gt;&amp;#34;/Applications/Plainize Clip.app&amp;#34;&lt;/span&gt; --args --preferences
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Save the settings and it runs one cleanup pass immediately. Cancel does not.&lt;/p&gt;
&lt;h2 id="the-plain-clip-context"&gt;The Plain Clip context&lt;/h2&gt;
&lt;p&gt;Plainize Clip borrows the spirit of &lt;a href="https://www.macworld.com/article/195102/plainclip2.html"&gt;Plain Clip&lt;/a&gt; by &lt;a href="https://www.bluem.net/"&gt;Carsten Blüm&lt;/a&gt;: stay invisible, do one job, quit.&lt;/p&gt;
&lt;p&gt;It is independent from Plain Clip and is not affiliated with Blüm.&lt;/p&gt;
&lt;p&gt;The context is that I liked Plain Clip, but the old &lt;a href="https://web.archive.org/web/20250331212359/https://www.bluem.net/files/plain-clip-2.5.2.zip"&gt;archived 2.5.2 build&lt;/a&gt; I had found was Intel-only, the source release situation was unclear, and Blüm&amp;rsquo;s current site no longer listed those legacy Mac apps by default. His site says older macOS apps moved out of the normal public listing, and that interested users can contact him for specific versions.&lt;/p&gt;
&lt;p&gt;Blüm later clarified to me that Plain Clip is still in daily use by him and that he has resumed work on it, including an Apple Silicon build. As far as I know, that newer build is still an on-request thing rather than something posted publicly.&lt;/p&gt;
&lt;p&gt;My approach was different: Plainize Clip is open on GitHub from the start. It remains my separate modern build of the shape I wanted.&lt;/p&gt;
&lt;h2 id="the-repo"&gt;The repo&lt;/h2&gt;
&lt;p&gt;The project is here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/nelsonjchen/plainize-clip"&gt;https://github.com/nelsonjchen/plainize-clip&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The releases are here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/nelsonjchen/plainize-clip/releases"&gt;https://github.com/nelsonjchen/plainize-clip/releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The README has the download notes, unsigned-app warning, preferences, CLI wrapper, build command, and tests.&lt;/p&gt;</description></item><item><title>Unofficial MakerWorld PMM OpenSCAD Reference</title><link>https://mindflakes.com/posts/2026/05/04/makerworld-pmm-openscad-reference/</link><pubDate>Mon, 04 May 2026 08:15:00 -0700</pubDate><guid>https://mindflakes.com/posts/2026/05/04/makerworld-pmm-openscad-reference/</guid><description>&lt;p&gt;I made an &lt;a href="https://github.com/nelsonjchen/unofficial-makerworld-parametric-model-maker-openscad-docs"&gt;unofficial MakerWorld PMM OpenSCAD reference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I put this reference together with the assistance of Codex. I supplied the idea and directed the work; Codex did the research, coding, and documentation, including collecting the PMM rules and building the font index. Codex also drafted this post.&lt;/p&gt;
&lt;p&gt;The short version is that I wanted &lt;a href="https://openai.com/codex/"&gt;Codex&lt;/a&gt; to help me make MakerWorld-customizable &lt;a href="https://openscad.org/"&gt;OpenSCAD&lt;/a&gt; models, and I did not want it guessing from old forum posts, UI behavior, and whatever I happened to remember at 1 AM.&lt;/p&gt;
&lt;p&gt;This started because I wanted to use agents on my own OpenSCAD projects without re-teaching the same MakerWorld-specific weirdness every time.&lt;/p&gt;
&lt;h2 id="the-documentation-problem"&gt;The documentation problem&lt;/h2&gt;
&lt;p&gt;MakerWorld&amp;rsquo;s &lt;a href="https://forum.bambulab.com/t/parametric-model-maker-v1-1-0-major-ui-refresh/203564"&gt;Parametric Model Maker&lt;/a&gt; is genuinely useful. &lt;a href="https://openscad.org/"&gt;OpenSCAD&lt;/a&gt; plus a web customizer is a great fit for 3D-printable stuff where the interesting part is &amp;ldquo;same idea, slightly different dimensions.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;If you have not run into it before, &lt;a href="https://openscad.org/"&gt;OpenSCAD&lt;/a&gt; is CAD by writing code. You write variables, modules, and geometry operations, and it turns that into a model. That makes it very good for customizable prints, and very annoying when a target platform has extra rules.&lt;/p&gt;
&lt;p&gt;Unfortunately, the OpenSCAD side of PMM is documented in a very internet way. Some of it is in release posts. Some of it is in support replies. Some of it is in the actual web app. Some of it is people trying things and reporting what happened.&lt;/p&gt;
&lt;p&gt;There is a Bambu forum thread literally titled &lt;a href="https://forum.bambulab.com/t/any-documentation-on-parametric-model-maker/230605"&gt;Any Documentation on Parametric Model Maker?&lt;/a&gt;. The first post asks where to find the requirements for making customizable models. That felt familiar.&lt;/p&gt;
&lt;p&gt;For a human, scattered docs are annoying. For a coding agent, they are a great way to get plausible garbage. It might write OpenSCAD that looks fine locally but misses MakerWorld&amp;rsquo;s actual rules. It might invent a PMM feature because it saw something sort of similar somewhere else.&lt;/p&gt;
&lt;p&gt;I do not want an agent inventing &lt;code&gt;// preview[...]&lt;/code&gt; as a PMM feature. I want it to know that &lt;code&gt;// color&lt;/code&gt; is a thing, &lt;code&gt;// font&lt;/code&gt; is a thing, &lt;code&gt;mw_plate_N()&lt;/code&gt; is a thing, and &lt;code&gt;default.svg&lt;/code&gt; is not just some random example filename.&lt;/p&gt;
&lt;p&gt;So I had Codex collect those rules into a reference under my direction.&lt;/p&gt;
&lt;h2 id="what-the-reference-covers"&gt;What the reference covers&lt;/h2&gt;
&lt;p&gt;The repo is basically a practical map of the PMM OpenSCAD surface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;a href="https://nelsonjchen.github.io/unofficial-makerworld-parametric-model-maker-openscad-docs/pmm-openscad-api/"&gt;PMM OpenSCAD API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;an &lt;a href="https://nelsonjchen.github.io/unofficial-makerworld-parametric-model-maker-openscad-docs/agent-workflow/"&gt;agent workflow&lt;/a&gt; for converting normal OpenSCAD into PMM-ready OpenSCAD&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nelsonjchen.github.io/unofficial-makerworld-parametric-model-maker-openscad-docs/gotchas/"&gt;gotchas&lt;/a&gt; that agents are likely to mess up&lt;/li&gt;
&lt;li&gt;compatibility rules&lt;/li&gt;
&lt;li&gt;public source snapshots&lt;/li&gt;
&lt;li&gt;patterns and checklists&lt;/li&gt;
&lt;li&gt;a generated docs site&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The small examples are the important ones:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-scad" data-lang="scad"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;accent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;#FF0000&amp;#34;&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// color
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;font_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Roboto&amp;#34;&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// font
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;module&lt;/span&gt; mw_plate_1() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// printable plate here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;module&lt;/span&gt; mw_assembly_view() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// preview-only assembly here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;svg_file &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;default.svg&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;None of that is hard once you know it. The problem is making sure the agent knows it before it starts &amp;ldquo;helping.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I also had Codex build a &lt;a href="https://nelsonjchen.github.io/unofficial-makerworld-parametric-model-maker-openscad-docs/font-index/"&gt;PMM font index&lt;/a&gt;. It has 1881 MakerWorld PMM font families and 8267 exact PMM font strings. This got more elaborate than I expected, but fonts are exactly the kind of thing where an agent can confidently choose something that works on your machine and then does not work where the model actually runs.&lt;/p&gt;
&lt;h2 id="why-agent-first"&gt;Why agent-first&lt;/h2&gt;
&lt;p&gt;This is not meant to replace the official PMM UI or be a polished tutorial for someone&amp;rsquo;s first customizable model. It is mostly a pile of receipts and instructions for agents.&lt;/p&gt;
&lt;p&gt;Point &lt;a href="https://openai.com/codex/"&gt;Codex&lt;/a&gt; or another coding agent at the repo, then ask it to adapt a &lt;code&gt;.scad&lt;/code&gt; file for MakerWorld. The repo gives it rules to retrieve before it starts editing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;flatten local includes when PMM probably will not have them&lt;/li&gt;
&lt;li&gt;do not remove bundled PMM libraries like BOSL2 just because arbitrary local includes are risky&lt;/li&gt;
&lt;li&gt;use PMM upload defaults like &lt;code&gt;default.svg&lt;/code&gt; and &lt;code&gt;default.stl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;// color&lt;/code&gt; and &lt;code&gt;// font&lt;/code&gt; only where those controls are intended&lt;/li&gt;
&lt;li&gt;treat multi-plate output as a publishing decision, not just a module name&lt;/li&gt;
&lt;li&gt;cite whether a claim came from an official app endpoint, official release, employee reply, community report, or inference&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last part matters. I do not want a pile of agent-generated confidence. I want the agent to say why it thinks something is true.&lt;/p&gt;
&lt;h2 id="the-repo"&gt;The repo&lt;/h2&gt;
&lt;p&gt;The project is here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/nelsonjchen/unofficial-makerworld-parametric-model-maker-openscad-docs"&gt;https://github.com/nelsonjchen/unofficial-makerworld-parametric-model-maker-openscad-docs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The generated docs site is here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nelsonjchen.github.io/unofficial-makerworld-parametric-model-maker-openscad-docs/"&gt;https://nelsonjchen.github.io/unofficial-makerworld-parametric-model-maker-openscad-docs/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is unofficial, not endorsed by Bambu Lab or MakerWorld, and not a license grant for any font, library, model, or web asset. It is practical documentation from public sources and reverse-engineering.&lt;/p&gt;
&lt;p&gt;It is also probably incomplete or wrong in places. PMM keeps changing, and some of this stuff only becomes obvious after someone trips over it.&lt;/p&gt;
&lt;p&gt;Anyway, this mostly exists so my agents stop making the same MakerWorld OpenSCAD mistakes. If it saves someone else from digging through forum threads while trying to publish a customizable model, great.&lt;/p&gt;</description></item><item><title>Cisco Catalyst 9800-CL on GCP: what a fight</title><link>https://mindflakes.com/posts/2026/03/20/cisco-catalyst-9800-cl-on-gcp-what-a-fight/</link><pubDate>Fri, 20 Mar 2026 13:15:00 -0800</pubDate><guid>https://mindflakes.com/posts/2026/03/20/cisco-catalyst-9800-cl-on-gcp-what-a-fight/</guid><description>&lt;p&gt;I finally wrote up my notes on getting a modern &lt;a href="https://www.cisco.com/c/en/us/products/wireless/catalyst-9800-cl-wireless-controller-cloud/index.html"&gt;Cisco Catalyst 9800-CL&lt;/a&gt; running on &lt;a href="https://cloud.google.com/"&gt;Google Cloud Platform&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was my first time using Codex to bring up a GCP setup from scratch. I supplied the goals and directed the work; Codex did essentially all of the technical investigation, image modifications, and scripting to get the controller working. Codex also drafted this post.&lt;/p&gt;
&lt;p&gt;The short version is that this was much more of a fight than I expected.&lt;/p&gt;
&lt;p&gt;Cisco&amp;rsquo;s &lt;a href="https://www.cisco.com/c/en/us/products/collateral/wireless/catalyst-9800-cl-wireless-controller-cloud/nb-06-cat9800-cl-cloud-wirel-data-sheet-ctp-en.pdf"&gt;public GCP-facing material&lt;/a&gt; still nudges people toward &lt;a href="https://www.cisco.com/c/dam/en/us/td/docs/wireless/controller/9800/9800-cloud/deployment/c9800-cl-gcp-deployment-guide.pdf"&gt;Google Cloud Marketplace&lt;/a&gt;, but the Marketplace offerings I found were outrageously old: &lt;code&gt;16.12.1&lt;/code&gt; and &lt;code&gt;16.12.2s&lt;/code&gt; from the &lt;code&gt;2019-2020&lt;/code&gt; era, plus &lt;code&gt;17.2.1&lt;/code&gt; and &lt;code&gt;17.3.5a&lt;/code&gt; from the &lt;code&gt;2020-2021&lt;/code&gt; era.&lt;/p&gt;
&lt;p&gt;That is a pretty bad situation if what you actually want is a modern WLC on GCP. The version I actually worked from was &lt;code&gt;17.15.04d&lt;/code&gt;, which Cisco&amp;rsquo;s software portal lists with a release date of &lt;code&gt;19-Dec-2025&lt;/code&gt;. Cisco&amp;rsquo;s public guidance still points at a path that appears stuck on software families from years ago, and it was not a good starting point for getting a current controller running.&lt;/p&gt;
&lt;p&gt;Getting a newer image directly from Cisco&amp;rsquo;s site did not end the fight. That image&amp;rsquo;s GCP support was broken too, and Codex ended up ripping into it to figure out what the hell was going wrong. Under my direction, it investigated the boot path and worked through the image modifications needed to get the controller running. This was much more than asking it to write a few deployment commands.&lt;/p&gt;
&lt;p&gt;I have to give Codex credit for sticking with that stuff. Ripping apart a broken appliance image is nasty, patient trial-and-error work, especially when GCP is involved. Finding out whether a change worked can take minutes, and getting somewhere can take many attempts. Codex also configured serial logging and read through the serial logs to figure out what was happening during boot and what to try next. It kept working through that slow feedback loop. That persistence impressed me as much as the technical work itself.&lt;/p&gt;
&lt;p&gt;I put the actual handoff-style guide here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nelsonjchen/c9800-cl-gcp-setup"&gt;Cisco Catalyst 9800-CL on GCP guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That guide covers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;where to get the &lt;code&gt;qcow2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;why Marketplace is not a great baseline right now&lt;/li&gt;
&lt;li&gt;what actually worked to get the controller reachable&lt;/li&gt;
&lt;li&gt;how I validated it was really up&lt;/li&gt;
&lt;li&gt;how to adopt an AP into the supported public-cloud path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the main conclusions is that Cisco&amp;rsquo;s built-in GCP bootstrap path seems to create real first-boot state that the appliance expects, which helps explain why simply editing &lt;code&gt;/varied/iosxe_config.txt&lt;/code&gt; offline was not enough on a pristine raw image.&lt;/p&gt;
&lt;p&gt;Anyway: what a fight. But at least the notes are in one place now.&lt;/p&gt;</description></item></channel></rss>