<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[George Liu]]></title><description><![CDATA[Using AI to build useful web apps, dev workflows, and infrastructure tools. Developer of Centmin Mod LEMP stack.]]></description><link>https://ai.georgeliu.com</link><image><url>https://substackcdn.com/image/fetch/$s_!bxg3!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F661f3c3c-59f5-4ef7-949e-491b1248413f_608x608.png</url><title>George Liu</title><link>https://ai.georgeliu.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 05 Apr 2026 20:21:22 GMT</lastBuildDate><atom:link href="https://ai.georgeliu.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[George Liu]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[georgeliuoz@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[georgeliuoz@substack.com]]></itunes:email><itunes:name><![CDATA[George Liu]]></itunes:name></itunes:owner><itunes:author><![CDATA[George Liu]]></itunes:author><googleplay:owner><![CDATA[georgeliuoz@substack.com]]></googleplay:owner><googleplay:email><![CDATA[georgeliuoz@substack.com]]></googleplay:email><googleplay:author><![CDATA[George Liu]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Running Google Gemma 4 Locally With LM Studio’s New Headless CLI & Claude Code]]></title><description><![CDATA[LM Studio 0.4.0 introduced llmster and the lms CLI. Here is how I set up Gemma 4 26B for local inference on macOS that can be used with Claude Code.]]></description><link>https://ai.georgeliu.com/p/running-google-gemma-4-locally-with</link><guid isPermaLink="false">https://ai.georgeliu.com/p/running-google-gemma-4-locally-with</guid><dc:creator><![CDATA[George Liu]]></dc:creator><pubDate>Sat, 04 Apr 2026 18:42:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9o_j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Why run models locally?</h2><p>Cloud AI APIs are great until they are not. Rate limits, usage costs, privacy concerns, and network latency all add up. For quick tasks like code review, drafting, or testing prompts, a local model that runs entirely on your hardware has real advantages: zero API costs, no data leaving your machine, and consistent availability.</p><p>Google&#8217;s Gemma 4 is interesting for local use because of its mixture-of-experts architecture. The 26B parameter model only activates 4B parameters per forward pass, which means it runs well on hardware that could never handle a dense 26B model. On my 14&#8221; MacBook Pro M4 Pro with 48 GB of unified memory, it fits comfortably and generates at 51 tokens per second. Though there&#8217;s significant slowdowns when used within Claude Code from my experience.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9o_j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9o_j!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 424w, https://substackcdn.com/image/fetch/$s_!9o_j!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 848w, https://substackcdn.com/image/fetch/$s_!9o_j!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 1272w, https://substackcdn.com/image/fetch/$s_!9o_j!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9o_j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png" width="985" height="402" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0321dea-0461-40ae-82da-babbd6fa108e_985x402.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:402,&quot;width&quot;:985,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12148,&quot;alt&quot;:&quot;Google Gemma 4 26B-a4b LLM model served via LM Studio API with Claude Code alias command claude-lm&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Google Gemma 4 26B-a4b LLM model served via LM Studio API with Claude Code alias command claude-lm" title="Google Gemma 4 26B-a4b LLM model served via LM Studio API with Claude Code alias command claude-lm" srcset="https://substackcdn.com/image/fetch/$s_!9o_j!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 424w, https://substackcdn.com/image/fetch/$s_!9o_j!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 848w, https://substackcdn.com/image/fetch/$s_!9o_j!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 1272w, https://substackcdn.com/image/fetch/$s_!9o_j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0321dea-0461-40ae-82da-babbd6fa108e_985x402.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Google Gemma 4 26B-a4b LLM model served via LM Studio API with Claude Code alias command claude-lm</figcaption></figure></div><h2>The Gemma 4 model family</h2><p>Google released <a href="https://ai.google.dev/gemma/docs/core/model_card_4">Gemma 4</a> as a family of four models, not just one. The lineup spans a wide range of hardware targets:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-GEC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-GEC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 424w, https://substackcdn.com/image/fetch/$s_!-GEC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 848w, https://substackcdn.com/image/fetch/$s_!-GEC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 1272w, https://substackcdn.com/image/fetch/$s_!-GEC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-GEC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png" width="1456" height="481" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:481,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39014,&quot;alt&quot;:&quot;Google Gemma 4 LLM models&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Google Gemma 4 LLM models" title="Google Gemma 4 LLM models" srcset="https://substackcdn.com/image/fetch/$s_!-GEC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 424w, https://substackcdn.com/image/fetch/$s_!-GEC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 848w, https://substackcdn.com/image/fetch/$s_!-GEC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 1272w, https://substackcdn.com/image/fetch/$s_!-GEC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ade04ce-48fd-4ae5-aa80-a463752d7b7c_1932x638.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Google Gemma 4 LLM models</figcaption></figure></div><p>The &#8220;E&#8221; models (E2B, E4B) use Per-Layer Embeddings to optimize for on-device deployment and are the only variants that support audio input (speech recognition and translation). The 31B dense model is the most capable, scoring 85.2% on MMLU Pro and 89.2% on AIME 2026.</p><p><strong>Why I picked the 26B-A4B.</strong> The mixture-of-experts architecture is the key. It has 128 experts plus 1 shared expert, but only activates 8 experts (3.8B parameters) per token. A common rule of thumb estimates MoE dense - equivalent quality as roughly sqrt(total x active parameters), which puts this model around 10B effective. In practice, it delivers inference cost comparable to a 4B dense model with quality that punches well above that weight class. On benchmarks, it scores 82.6% on MMLU Pro and 88.3% on AIME 2026, close to the dense 31B (85.2% and 89.2%) while running dramatically faster.</p><p>The chart below tells the story. It plots Elo score against total model size on a log scale for recent open-weight models with thinking enabled. The blue-highlighted region in the upper left is where you want to be: high performance, small footprint.</p><p>Gemma 4 26B-A4B (Elo ~1441) sits firmly in that zone, punching well above its 25.2B parameter weight. The 31B dense variant scores slightly higher (~1451) but is still remarkably compact. For context, models like Qwen 3.5 397B-A17B (~1450 Elo) and GLM-5 (~1457 Elo) need 100-600B total parameters to reach similar scores. Kimi-K2.5 (~1457 Elo) requires over 1,000B. The 26B-A4B achieves competitive Elo with a fraction of the parameters, which translates directly into lower memory requirements and faster local inference.</p><p>This is what makes MoE models transformative for local use. You do not need a cluster or a high-end GPU rig to run a model that competes with 400B+ parameter behemoths. A laptop with 48 GB of unified memory is enough.</p><p>For local inference on a 48 GB Mac, this is the sweet spot. The dense 31B would consume more memory and generate tokens slower because every parameter participates in every forward pass. The E4B is lighter but noticeably less capable. The 26B-A4B gives you 256K max context, vision support (useful for analyzing screenshots and diagrams), native function/tool calling, and reasoning with configurable thinking modes, all at 51 tokens/second on my hardware.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!T7fm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T7fm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 424w, https://substackcdn.com/image/fetch/$s_!T7fm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 848w, https://substackcdn.com/image/fetch/$s_!T7fm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 1272w, https://substackcdn.com/image/fetch/$s_!T7fm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T7fm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png" width="1456" height="1289" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1289,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35795,&quot;alt&quot;:&quot;Google Gemma 4 model performance vs size comparison&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Google Gemma 4 model performance vs size comparison" title="Google Gemma 4 model performance vs size comparison" srcset="https://substackcdn.com/image/fetch/$s_!T7fm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 424w, https://substackcdn.com/image/fetch/$s_!T7fm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 848w, https://substackcdn.com/image/fetch/$s_!T7fm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 1272w, https://substackcdn.com/image/fetch/$s_!T7fm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e5f4f2a-2153-4208-811c-e2b4751c497f_1622x1436.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Google Gemma 4 model performance vs size comparison</figcaption></figure></div><h2>What changed in LM Studio 0.4.0</h2><p>LM Studio has been a popular desktop app for running local models for a while. Version 0.4.0 changed the architecture fundamentally by introducing <strong>llmster</strong>, the core inference engine extracted from the desktop app and packaged as a standalone server.</p><p>The practical result: you can now run LM Studio entirely from the command line using the <code>lms</code> CLI. No GUI required. This makes it usable on headless servers, in CI/CD pipelines, SSH sessions, or just for developers who prefer staying in the terminal.</p><p>Key additions in 0.4.0:</p><ul><li><p><strong>llmster daemon</strong>: a background service that manages model loading and inference without the desktop app</p></li><li><p><code>lms</code><strong> CLI</strong>: full command-line interface for downloading, loading, chatting, and serving models</p></li><li><p><strong>Parallel request processing</strong>: continuous batching instead of sequential queuing, so multiple requests to the same model run concurrently</p></li><li><p><strong>Stateful REST API</strong>: a new <code>/v1/chat</code> endpoint that maintains conversation history across requests</p></li><li><p><strong>MCP integration</strong>: local Model Context Protocol support with permission-key gating</p></li></ul><h2>Installation</h2><p>Install the <code>lms</code> CLI with a single command:</p><pre><code><code># Linux/Mac
curl -fsSL https://lmstudio.ai/install.sh | bash

# Windows
irm https://lmstudio.ai/install.ps1 | iex</code></code></pre><p>Then start the headless daemon:</p><pre><code><code>lms daemon up</code></code></pre><p>On macOS, update both inference runtimes:</p><pre><code><code>lms runtime update llama.cpp
lms runtime update mlx</code></code></pre><h2>Downloading Gemma 4</h2><p>With the daemon running, download Google&#8217;s Gemma 4 26B model:</p><pre><code><code>lms get google/gemma-4-26b-a4b</code></code></pre><p>The CLI shows you the variant it will download (Q4_K_M quantization by default, 17.99 GB) and asks for confirmation:</p><pre><code><code>   &#8595; To download: model google/gemma-4-26b-a4b - 64.75 KB
   &#9492;&#9472; &#8595; To download: Gemma 4 26B A4B Instruct Q4_K_M [GGUF] - 17.99 GB

About to download 17.99 GB.

? Start download?
&#10095; Yes
  No
  Change variant selection</code></code></pre><p>If you already have the model, the CLI tells you and shows the load command:</p><pre><code><code>&#10004; Start download? yes
Model already downloaded. To use, run: lms load google/gemma-4-26b-a4b</code></code></pre><h2>Checking your local model library</h2><p>List all downloaded models:</p><pre><code><code>lms ls</code></code></pre><pre><code><code>You have 10 models, taking up 118.17 GB of disk space.

LLM                                   PARAMS     ARCH             SIZE         DEVICE
gemma-3-270m-it-mlx                   270m       gemma3_text      497.80 MB    Local
google/gemma-4-26b-a4b (1 variant)    26B-A4B    gemma4           17.99 GB     Local
gpt-oss-20b-mlx                       20B        gpt_oss          22.26 GB     Local
llama-3.2-1b-instruct                 1B         Llama            712.58 MB    Local
nvidia/nemotron-3-nano (1 variant)    30B        nemotron_h       17.79 GB     Local
openai/gpt-oss-20b (1 variant)        20B        gpt-oss          12.11 GB     Local
qwen/qwen3.5-35b-a3b (1 variant)      35B-A3B    qwen35moe        22.07 GB     Local
qwen2.5-0.5b-instruct-mlx             0.5B       Qwen2            293.99 MB    Local
zai-org/glm-4.7-flash (1 variant)     30B        glm4_moe_lite    24.36 GB     Local

EMBEDDING                               PARAMS    ARCH          SIZE        DEVICE
text-embedding-nomic-embed-text-v1.5              Nomic BERT    84.11 MB    Local</code></code></pre><p>Worth noting: several of these models use mixture-of-experts architectures (Gemma 4, Qwen 3.5, GLM 4.7 Flash). MoE models punch above their weight for local inference because only a fraction of parameters activate per token.</p><h2>Running an interactive chat</h2><p>Start a chat session with stats enabled to see performance numbers:</p><pre><code><code>lms chat google/gemma-4-26b-a4b --stats</code></code></pre><pre><code><code> &#9581;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9582;
 &#9474; &#128126; lms chat                                     &#9474;
 &#9474; Type exit or Ctrl+C to quit                     &#9474;
 &#9474;                                                 &#9474;
 &#9474; Chatting with google/gemma-4-26b-a4b            &#9474;
 &#9474;                                                 &#9474;
 &#9474; Try one of the following commands:              &#9474;
 &#9474; /model - Load a model (type /model to see list) &#9474;
 &#9474; /download - Download a model                    &#9474;
 &#9474; /clear - Clear the chat history                 &#9474;
 &#9474; /help - Show help information                   &#9474;
 &#9584;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9583;</code></code></pre><p>With <code>--stats</code>, you get prediction metrics after each response:</p><pre><code><code>Prediction Stats:
  Stop Reason: eosFound
  Tokens/Second: 51.35
  Time to First Token: 1.551s
  Prompt Tokens: 39
  Predicted Tokens: 176
  Total Tokens: 215</code></code></pre><p>51 tokens/second on a 14&#8221; MacBook Pro M4 Pro (48 GB) with a 26B model is solid. Time to first token at 1.5 seconds is responsive enough for interactive use.</p><h2>Checking loaded models and memory</h2><p>See what is currently loaded:</p><pre><code><code>lms ps</code></code></pre><pre><code><code>IDENTIFIER                MODEL                     STATUS    SIZE        CONTEXT    PARALLEL    DEVICE    TTL
google/gemma-4-26b-a4b    google/gemma-4-26b-a4b    IDLE      17.99 GB    48000      2           Local     60m / 1h</code></code></pre><p>The model occupies 17.99 GB in memory with a 48K context window and supports 2 parallel requests. The TTL (time-to-live) auto-unloads the model after 1 hour of idle time, freeing memory without manual intervention.</p><p>For detailed model metadata, pipe through jq:</p><pre><code><code>lms ps --json | jq</code></code></pre><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;1d0a1ce5-dec7-4964-b32c-28481e533dd4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">lms ps --json | jq
[
  {
    "type": "llm",
    "modelKey": "google/gemma-4-26b-a4b",
    "format": "gguf",
    "displayName": "Gemma 4 26B A4B",
    "publisher": "google",
    "path": "google/gemma-4-26b-a4b",
    "sizeBytes": 17990911801,
    "indexedModelIdentifier": "google/gemma-4-26b-a4b",
    "deviceIdentifier": null,
    "paramsString": "26B-A4B",
    "architecture": "gemma4",
    "quantization": {
      "name": "Q4_K_M",
      "bits": 4
    },
    "variants": [
      "google/gemma-4-26b-a4b@q4_k_m"
    ],
    "selectedVariant": "google/gemma-4-26b-a4b@q4_k_m",
    "identifier": "google/gemma-4-26b-a4b",
    "ttlMs": 3600000,
    "lastUsedTime": 1775316805638,
    "vision": true,
    "trainedForToolUse": true,
    "maxContextLength": 262144,
    "contextLength": 48000,
    "status": "idle",
    "queued": 0,
    "parallel": 2
  }
]</code></pre></div><p>Key fields from the JSON output:</p><ul><li><p><code>"architecture": "gemma4"</code> with <code>"quantization": {"name": "Q4_K_M", "bits": 4}</code></p></li><li><p><code>"vision": true</code> and <code>"trainedForToolUse": true</code> - Gemma 4 supports both image input and tool calling</p></li><li><p><code>"maxContextLength": 262144</code> - the model supports up to 256K context, though the default load is 48K</p></li><li><p><code>"parallel": 2</code> - two concurrent inference requests via continuous batching</p></li></ul><h2>Memory estimates by context length</h2><p>Before loading a model, you can estimate memory requirements at different context lengths using <code>--estimate-only</code>. I wrote a small script to test across the full range:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!h3N5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!h3N5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 424w, https://substackcdn.com/image/fetch/$s_!h3N5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 848w, https://substackcdn.com/image/fetch/$s_!h3N5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 1272w, https://substackcdn.com/image/fetch/$s_!h3N5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!h3N5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png" width="1456" height="1080" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1080,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:61265,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!h3N5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 424w, https://substackcdn.com/image/fetch/$s_!h3N5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 848w, https://substackcdn.com/image/fetch/$s_!h3N5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 1272w, https://substackcdn.com/image/fetch/$s_!h3N5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d6f7834-a2e2-42ee-afab-33408970edee_1952x1448.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The base model takes about 17.6 GiB regardless of context. Each doubling of context length adds roughly 3-4 GiB. At the default 48K context, you need about 21 GiB. On my 48 GB MacBook Pro, I can push to the full 256K context at 37.48 GiB and still have about 10 GB free for the OS and other apps. A 36 GB Mac could comfortably run 200K context with headroom.</p><p>The estimation command is straightforward:</p><pre><code><code>lms load google/gemma-4-26b-a4b --estimate-only --context-length 48000
</code></code></pre><pre><code><code>Model: google/gemma-4-26b-a4b
Context Length: 48,000
Estimated GPU Memory:   21.05 GiB
Estimated Total Memory: 21.05 GiB

Estimate: This model may be loaded based on your resource guardrails settings.
</code></code></pre><p>This is useful for capacity planning. If you want to run Gemma 4 alongside other applications, check the estimate at your target context length first.</p><p>Here is the full script I used to generate the table above. You can swap in any model name and context length list to profile a different model:</p><pre><code><code>#!/usr/bin/env bash

model="google/gemma-4-26b-a4b"
contexts=(4096 8000 16000 24000 32000 48000 64000 96000 128000 200000 256000)

table_contexts=()
table_gpu=()
table_total=()

for ctx in "${contexts[@]}"; do
  output="$(lms load "$model" --estimate-only --context-length "$ctx" 2&gt;&amp;1)"

  parsed_context="$(printf '%s\n' "$output" | awk -F': ' '/^Context Length:/ {print $2; exit}')"
  parsed_gpu="$(printf '%s\n' "$output" | awk -F': +' '/^Estimated GPU Memory:/ {print $2; exit}')"
  parsed_total="$(printf '%s\n' "$output" | awk -F': +' '/^Estimated Total Memory:/ {print $2; exit}')"

  table_contexts+=("${parsed_context:-$ctx}")
  table_gpu+=("${parsed_gpu:-N/A}")
  table_total+=("${parsed_total:-N/A}")
done

printf '| Model | Context Length | GPU Memory | Total Memory |\n'
printf '|---|---:|---:|---:|\n'
for i in "${!table_contexts[@]}"; do
  printf '| %s | %s | %s | %s |\n' \
    "$model" "${table_contexts[$i]}" "${table_gpu[$i]}" "${table_total[$i]}"
done
</code></code></pre><h2>Tuning model loading for your hardware</h2><p>The default <code>lms load</code> or <code>lms chat</code> commands pick reasonable defaults, but you can tune several parameters to match your specific hardware and use case. Here is a practical decision framework.</p><h3>Context length: match it to your memory budget</h3><p>The memory table above is your starting point. Subtract the OS overhead (macOS typically uses 4-6 GB) from your total memory, then find the largest context length that fits.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YeC0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YeC0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 424w, https://substackcdn.com/image/fetch/$s_!YeC0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 848w, https://substackcdn.com/image/fetch/$s_!YeC0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 1272w, https://substackcdn.com/image/fetch/$s_!YeC0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YeC0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png" width="1456" height="543" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:543,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:45132,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YeC0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 424w, https://substackcdn.com/image/fetch/$s_!YeC0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 848w, https://substackcdn.com/image/fetch/$s_!YeC0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 1272w, https://substackcdn.com/image/fetch/$s_!YeC0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb7d3a77-3db7-4724-8e4f-e269cc4a0f2b_1946x726.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Load with a specific context length:</p><pre><code><code>lms load google/gemma-4-26b-a4b --context-length 128000</code></code></pre><p>If you are unsure, always run <code>--estimate-only</code> first. It accounts for flash attention and vision model overhead in its calculation.</p><h3>GPU offloading</h3><p>On Apple Silicon, the unified memory architecture means CPU and GPU share the same memory pool, so <code>--gpu</code> mostly controls how much computation runs on the GPU versus CPU cores. The default <code>auto</code> setting works well, but you can force full GPU offloading:</p><pre><code><code>lms load google/gemma-4-26b-a4b --gpu=1.0</code></code></pre><p>Use <code>--gpu=max</code> to offload everything possible. On discrete GPU systems (Linux/Windows with NVIDIA cards), this becomes more important because GPU VRAM and system RAM are separate. If your model does not fit entirely in VRAM, partial offloading (<code>--gpu=0.5</code>) splits layers between GPU and CPU, trading some speed for the ability to run larger models.</p><h3>Parallel requests and continuous batching</h3><p>LM Studio supports concurrent inference through continuous batching, where multiple requests are dynamically combined into a single computation batch. This is useful when serving the model to multiple clients or running parallel tool calls. The feature requires the llama.cpp runtime (v2.0.0+) and is not yet available for the MLX backend.</p><p>Configure it through the GUI: open the model loader, toggle <strong>Manually choose model load parameters</strong>, select a model, then toggle <strong>Show advanced settings</strong> to set <strong>Max Concurrent Predictions</strong> (defaults to 4). There is no CLI flag for this setting; it is configured through the desktop app or per-model defaults.</p><p>Each parallel slot consumes additional memory proportional to the context length, so on memory-constrained systems, reduce the parallel count or lower the context length to compensate. With Gemma 4 on 48 GB, 2 parallel slots at 48K context is a good balance.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yMh2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yMh2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 424w, https://substackcdn.com/image/fetch/$s_!yMh2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 848w, https://substackcdn.com/image/fetch/$s_!yMh2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 1272w, https://substackcdn.com/image/fetch/$s_!yMh2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yMh2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png" width="1456" height="1195" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1195,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:112459,&quot;alt&quot;:&quot;LM Studio Max Concurrent Predications set to 2&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ai.georgeliu.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="LM Studio Max Concurrent Predications set to 2" title="LM Studio Max Concurrent Predications set to 2" srcset="https://substackcdn.com/image/fetch/$s_!yMh2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 424w, https://substackcdn.com/image/fetch/$s_!yMh2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 848w, https://substackcdn.com/image/fetch/$s_!yMh2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 1272w, https://substackcdn.com/image/fetch/$s_!yMh2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e3afa7a-2689-42a3-bd33-a899dcb7723b_2738x2248.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">LM Studio Max Concurrent Predications set to 2</figcaption></figure></div><h3>TTL: auto-unload idle models</h3><p>The time-to-live setting automatically unloads models after a period of inactivity, freeing memory:</p><pre><code><code>lms load google/gemma-4-26b-a4b --ttl 1800</code></code></pre><p>That sets a 30-minute idle timeout (value is in seconds). The default is 3600 seconds (1 hour). For shared server setups where multiple models might be needed, shorter TTLs help cycle between models without manual <code>lms unload</code> commands. Set TTL to 0 or -1 to disable auto-unloading.</p><h3>Per-model defaults</h3><p>If you always load Gemma 4 with the same settings, save them as per-model defaults through the desktop app. Navigate to <strong>My Models</strong>, click the gear icon next to the model, and configure your preferred GPU offloading, context size, and flash attention settings. These defaults apply everywhere, including when loading via <code>lms load</code> from the CLI.</p><h3>Speculative decoding</h3><p>LM Studio supports speculative decoding for dense models, which pairs your main model with a smaller &#8220;draft&#8221; model to speed up generation. The draft model proposes tokens quickly, and the main model verifies them in batch, which is faster than generating each token independently.</p><p>However, speculative decoding is problematic for MoE models like Gemma 4 26B-A4B. During verification, the main model must load the union of all experts activated across all speculative tokens. Since different tokens route to different experts, this blows up memory bandwidth usage and can actually slow things down. Benchmarks on Mixtral showed a 39% speedup on code but a 54% slowdown on math with the same settings, meaning no single configuration works reliably. This is an active research area with approaches like MoE-Spec (expert budgeting) and SP-MoE (expert prefetching) working to solve it, and some newer MoE architectures like Qwen 3.5&#8217;s hybrid design are more amenable to speculative approaches. For now, skip speculative decoding with Gemma 4 26B-A4B and rely on its already-fast MoE inference instead.</p><h3>Flash attention</h3><p>Flash attention is an optimization that reduces memory usage for the KV cache during inference, letting you fit longer context lengths in the same memory. It is available per-model in LM Studio&#8217;s settings. For Gemma 4 on Apple Silicon, enabling flash attention can reduce memory usage at higher context lengths by a meaningful margin. The <code>--estimate-only</code> flag accounts for flash attention in its calculations, so check estimates with and without to see the difference.</p><h2>The LM Studio desktop app</h2><p>Everything above used the headless CLI, but LM Studio also ships a full macOS desktop app. The GUI is useful for visual monitoring and quick experiments before committing to a CLI workflow.</p><p>The screenshot below shows the desktop app&#8217;s server view with Gemma 4 loaded. A few things worth noting:</p><ul><li><p><strong>Server status</strong> shows &#8220;Running&#8221; with the local endpoint at </p></li></ul><p>http://192.168.1.121:1234</p><ul><li><p>, reachable by any device on the network</p></li><li><p><strong>Loaded Models</strong> displays the active model with live stats: 29 generations, 1,087 tokens processed, 17.99 GB in memory</p></li><li><p><strong>Supported endpoints</strong> include LM Studio API, OpenAI-compatible, and Anthropic-compatible formats, with <code>POST /v1/messages</code> for the Anthropic protocol</p></li><li><p><strong>Developer Logs</strong> stream prompt processing progress in real time, useful for watching long prompts like code analysis work through the model</p></li></ul><p>The desktop app also supports Gemma 4&#8217;s <strong>vision capabilities</strong>. In below screenshot, you can see the model analyzing an image of the Timezone Scheduler promotional graphic. It correctly identifies the title, world map with timezone color bars, the schedule grid comparing Brisbane/New York/London, feature badges, and the tech stack icons at the bottom. It generated 504 tokens at 54.51 tok/sec with a 3.15s time to first token.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L6Iq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L6Iq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 424w, https://substackcdn.com/image/fetch/$s_!L6Iq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 848w, https://substackcdn.com/image/fetch/$s_!L6Iq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 1272w, https://substackcdn.com/image/fetch/$s_!L6Iq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L6Iq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png" width="1456" height="1381" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1381,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:370831,&quot;alt&quot;:&quot;Google Gemma 4 vision capabilities in describing an image&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Google Gemma 4 vision capabilities in describing an image" title="Google Gemma 4 vision capabilities in describing an image" srcset="https://substackcdn.com/image/fetch/$s_!L6Iq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 424w, https://substackcdn.com/image/fetch/$s_!L6Iq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 848w, https://substackcdn.com/image/fetch/$s_!L6Iq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 1272w, https://substackcdn.com/image/fetch/$s_!L6Iq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff8163e6-6c58-40d5-985f-4fc15e52f5da_3024x2868.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Google Gemma 4 vision capabilities in describing an image</figcaption></figure></div><p>Claude Code alias claude-lm with Google Gemma 4 analysing my <a href="https://github.com/centminmod/timezone-scheduler">Timezones Scheduler benchmark comparison GitHub repository</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BUmO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BUmO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 424w, https://substackcdn.com/image/fetch/$s_!BUmO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 848w, https://substackcdn.com/image/fetch/$s_!BUmO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 1272w, https://substackcdn.com/image/fetch/$s_!BUmO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BUmO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png" width="1295" height="1133" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1133,&quot;width&quot;:1295,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:115362,&quot;alt&quot;:&quot;Claude Code via claude-lm alias with LM Studio API&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Claude Code via claude-lm alias with LM Studio API" title="Claude Code via claude-lm alias with LM Studio API" srcset="https://substackcdn.com/image/fetch/$s_!BUmO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 424w, https://substackcdn.com/image/fetch/$s_!BUmO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 848w, https://substackcdn.com/image/fetch/$s_!BUmO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 1272w, https://substackcdn.com/image/fetch/$s_!BUmO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffddd0c29-1f2c-4f99-8efe-de8517fb6244_1295x1133.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Claude Code via claude-lm alias with LM Studio API</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-FvH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-FvH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 424w, https://substackcdn.com/image/fetch/$s_!-FvH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 848w, https://substackcdn.com/image/fetch/$s_!-FvH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!-FvH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-FvH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png" width="1456" height="1069" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1069,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:877361,&quot;alt&quot;:&quot;LM Studio API server in action while using Claude Code via claude-lm alias&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193191800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="LM Studio API server in action while using Claude Code via claude-lm alias" title="LM Studio API server in action while using Claude Code via claude-lm alias" srcset="https://substackcdn.com/image/fetch/$s_!-FvH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 424w, https://substackcdn.com/image/fetch/$s_!-FvH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 848w, https://substackcdn.com/image/fetch/$s_!-FvH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!-FvH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff355f4e0-0344-4140-be2d-fa6fbdac2795_1962x1440.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">LM Studio API server in action while using Claude Code via claude-lm alias</figcaption></figure></div><p>The system monitor overlay in the screenshots tells the real story of what local inference looks like on hardware. On my M4 Pro (4 E-Cores + 10 P-Cores, 20 GPU-Cores):</p><ul><li><p><strong>Memory pressure</strong>: 46.69 GB used out of 48.00 GB physical, with 38.07 GB wired memory (that is mostly the model plus context). Swap used: 27.49 GB. The system stays responsive despite near-full memory utilization</p></li><li><p><strong>GPU utilization</strong>: 90% during inference, with P-Cluster frequency at 4.50 GHz and GPU at 1.45 GHz</p></li><li><p><strong>CPU utilization</strong>: E-Core at 82.42%, P-Core at 35.96% during generation</p></li><li><p><strong>Temperature</strong>: CPU cores averaging 91 degrees C, GPU averaging 92.46 degrees C, which is within normal sustained load range for the M4 Pro</p></li><li><p><strong>Power draw</strong>: 23.56W package total (CPU 11.06W, GPU 13.32W), which is efficient for running a 26B parameter model</p></li></ul><p>This is what makes Apple Silicon compelling for local LLM work. The unified memory architecture means the CPU and GPU share the same memory pool, so there is no copying data between separate CPU RAM and GPU VRAM like on discrete GPU setups. The model loads once into unified memory and both the CPU and GPU access it directly.</p><h2>Serving models via API</h2><p>Once a model is loaded, start the local server:</p><pre><code><code>lms server start</code></code></pre><p>This exposes an OpenAI-compatible API at <code>http://localhost:1234/v1</code>. Any tool that works with OpenAI&#8217;s API format (Continue, Cursor, custom scripts) can point at your local server instead. LM Studio 0.4.0 also added an Anthropic-compatible endpoint at <code>POST /v1/messages</code>, which means tools that speak the Anthropic protocol can connect directly without an adapter. You can change the port with <code>lms server start --port 8080</code> if 1234 conflicts with something else.</p><p>The server also supports <strong>JIT (Just-In-Time) model loading</strong>: if a client requests a model that is not currently loaded, LM Studio can auto-load it on demand and auto-unload it after the TTL expires. This is useful for serving multiple models without keeping them all in memory.</p><p>To monitor what the server is doing in real time, stream the logs:</p><pre><code><code>lms log stream --source model --stats</code></code></pre><p>This shows each request&#8217;s input/output along with tokens/second and latency. For a machine-readable feed, add <code>--json</code>. You can also filter to just server-level events (startup, endpoint hits) with <code>--source server</code>.</p><p>Combined with the headless daemon, you can run this on a dedicated machine and serve models across your network. The server is reachable at your machine&#8217;s local IP (e.g., </p><p>http://192.168.1.121:1234</p><p>), so other devices on the same network can use it as a shared inference endpoint. If you need access control, enable <strong>Require Authentication</strong> in server settings and generate API tokens with per-token permissions, accessed via the standard <code>Authorization: Bearer $LM_API_TOKEN</code> header.</p><h2>Using Gemma 4 as a Claude Code backend</h2><p>The Anthropic-compatible endpoint opens up an interesting use case: running Claude Code against a local model instead of the Anthropic API. This means fully offline, zero-cost coding assistance with no data leaving your machine.</p><p>I set up a shell function in <code>~/.zshrc</code> called <code>claude-lm</code> that configures all the necessary environment variables and launches Claude Code pointed at the local LM Studio server:</p><pre><code><code>claude-lm() {
    export ANTHROPIC_BASE_URL=http://localhost:1234
    export ANTHROPIC_AUTH_TOKEN=lmstudio
    export CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY="2"
    export CLAUDE_CODE_NO_FLICKER="0"
    export ANTHROPIC_MODEL="gemma-4-26b-a4b"
    export CLAUDE_CODE_AUTO_COMPACT_WINDOW="48000"
    export CLAUDE_AUTOCOMPACT_PCT_OVERRIDE="90"
    export ANTHROPIC_DEFAULT_OPUS_MODEL="google/gemma-4-26b-a4b"
    export ANTHROPIC_DEFAULT_SONNET_MODEL="google/gemma-4-26b-a4b"
    export ANTHROPIC_DEFAULT_HAIKU_MODEL="google/gemma-4-26b-a4b"
    export CLAUDE_CODE_SUBAGENT_MODEL="google/gemma-4-26b-a4b"
    export API_TIMEOUT_MS="30000000"
    export BASH_DEFAULT_TIMEOUT_MS="2400000"
    export BASH_MAX_TIMEOUT_MS="2500000"
    export CLAUDE_CODE_MAX_OUTPUT_TOKENS="8000"
    export CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS="8000"
    export CLAUDE_CODE_ATTRIBUTION_HEADER="0"
    export CLAUDE_CODE_DISABLE_1M_CONTEXT="1"
    export CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING="1"
    claude "$@"
}</code></code></pre><p>What the key variables do:</p><ul><li><p><code>ANTHROPIC_BASE_URL</code> and <code>ANTHROPIC_AUTH_TOKEN</code> point Claude Code at the local LM Studio server. The token value <code>lmstudio</code> is a placeholder; LM Studio does not require authentication by default</p></li><li><p><code>ANTHROPIC_MODEL</code> and the three <code>DEFAULT_*_MODEL</code> variables force all Claude Code model selections (Opus, Sonnet, Haiku) to route through Gemma 4. Without these, Claude Code would try to call Anthropic model names that LM Studio does not recognize</p></li><li><p><code>CLAUDE_CODE_SUBAGENT_MODEL</code> ensures any subagents Claude Code spawns also use the local model</p></li><li><p><code>CLAUDE_CODE_AUTO_COMPACT_WINDOW</code> and <code>CLAUDE_AUTOCOMPACT_PCT_OVERRIDE</code> manage context window compaction. At 48K context, compaction triggers at 90% usage to avoid hitting the limit mid-task</p></li><li><p><code>API_TIMEOUT_MS</code> is set high (30 million ms / ~8.3 hours) because local inference is slower than the Anthropic API and complex tasks need time to complete</p></li><li><p><code>BASH_DEFAULT_TIMEOUT_MS</code> and <code>BASH_MAX_TIMEOUT_MS</code> extend shell command timeouts to 40-42 minutes for long-running operations</p></li><li><p><code>CLAUDE_CODE_MAX_OUTPUT_TOKENS</code> and <code>CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS</code> cap output at 8K tokens per response, which keeps generation times reasonable on local hardware</p></li><li><p><code>CLAUDE_CODE_DISABLE_1M_CONTEXT</code> and <code>CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING</code> turn off features that assume Anthropic API capabilities the local model does not support</p></li></ul><p>After adding this to <code>~/.zshrc</code> and running <code>source ~/.zshrc</code>, you can start a fully local Claude Code session with:</p><pre><code><code>claude-lm</code></code></pre><p>It works like normal Claude Code but every request stays on your machine. The trade-off is speed: Gemma 4 at 51 tok/sec is noticeably slower than the Anthropic API for large code generation tasks, but for code review, small edits, and exploration it is perfectly usable.</p><h2>What I learned</h2><p><strong>MoE models are the sweet spot for local inference.</strong> Gemma 4's 26B-A4B architecture (26B total, 4B active) delivers roughly 10B dense - equivalent quality at 4B inference cost. Look for similar MoE models when choosing what to run locally.</p><p><strong>The headless daemon changes the workflow.</strong> Before 0.4.0, LM Studio required the desktop app open. Now <code>lms daemon up</code> runs in the background and you interact entirely through the CLI or API. This makes it practical for server deployments and SSH sessions.</p><p><strong>Context length is the main memory variable.</strong> The model itself takes a fixed ~17.6 GiB. Context scaling is roughly linear, so you can pick exactly the trade-off you want between context window and available memory.</p><p><code>--estimate-only</code><strong> prevents surprises.</strong> Always check memory estimates before loading a large model at an aggressive context length. It takes a second and saves you from OOM situations.</p><p><strong>The Anthropic-compatible endpoint is a game changer.</strong> Being able to point Claude Code at a local model with a shell alias means you can switch between cloud and local inference depending on the task. Privacy-sensitive code review, offline work, or just saving API costs on exploratory sessions all benefit.</p><h2>What did not work</h2><p>Gemma 4 does not identify itself by name in <code>lms chat</code>. When asked &#8220;what model are you?&#8221;, it responds generically as &#8220;an AI assistant.&#8221; This is a minor limitation of how LM Studio handles system prompts, not a Gemma issue. You can override this with a custom system prompt.</p><p>The default 48K context is conservative for a model that supports 256K. If you have the memory, it is worth loading with a higher context length for tasks like long document analysis or multi-file code review.</p><p>Running Claude Code with a local model is not a drop-in replacement for the Anthropic API. Complex multi-step tasks that rely on Claude&#8217;s extended thinking or very large context windows will hit limitations. The local setup works best for focused, single-file tasks where the 48K context window is sufficient.</p><p>Memory pressure on a 48 GB machine with Gemma 4 loaded is real. The system used 46.69 GB out of 48 GB with 27.49 GB of swap during the test. If you run memory-hungry applications alongside the model, expect some swap thrashing. A 64 GB or higher configuration would be more comfortable for sustained use.</p><h2>What is next</h2><p>I am testing other local models alongside Gemma 4 for different use cases: Qwen 3.5 35B for coding tasks, GLM 4.7 Flash for quick drafting, and Nemotron 3 Nano for structured extraction. A comparison post covering where each model performs best is in the pipeline.</p><p>If you want to try this setup:</p><ul><li><p>Install: <code>curl -fsSL https://lmstudio.ai/install.sh | bash</code></p></li><li><p>Start the daemon: <code>lms daemon up</code></p></li><li><p>Download Gemma 4: <code>lms get google/gemma-4-26b-a4b</code></p></li><li><p>Chat locally: <code>lms chat google/gemma-4-26b-a4b --stats</code></p></li><li><p>Connect Claude Code: add the <code>claude-lm</code> function to your <code>~/.zshrc</code>, then run <code>claude-lm</code> instead of <code>claude</code></p></li></ul><p>If you&#8217;re interested in practical AI building for web apps, developer workflows, and infrastructure, subscribe for future posts. You can also follow my shorter updates on <a href="https://www.threads.com/@george_sl_liu">Threads (@george_sl_liu)</a> and <a href="https://bsky.app/profile/georgesl.bsky.social">Bluesky (@georgesl.bsky.social)</a> or subscribe and follow along.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://buymeacoffee.com/georgeliu&quot;,&quot;text&quot;:&quot;Buy Me A Coffee&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://buymeacoffee.com/georgeliu"><span>Buy Me A Coffee</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How To Get a Second AI Opinion in Claude Code With Codex CLI and GLM]]></title><description><![CDATA[Claude Code skills to get a second opinion from Codex CLI GPT-5.4 and Z.AI GLM-5]]></description><link>https://ai.georgeliu.com/p/how-to-get-a-second-ai-opinion-in</link><guid isPermaLink="false">https://ai.georgeliu.com/p/how-to-get-a-second-ai-opinion-in</guid><dc:creator><![CDATA[George Liu]]></dc:creator><pubDate>Sat, 04 Apr 2026 18:00:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bOGT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Two skills, two external models, one unified analysis. Here is how I set up consult-codex and consult-zai to cross-check Claude Code&#8217;s work with OpenAI Codex and z.ai&#8217;s glm-4.7, glm-5, glm-5.1.</em></p><h2>The problem with single-AI code analysis</h2><p>When Claude Code is the only AI looking at your code, you get one perspective. That is usually fine for routine tasks. But for complex refactors, security-sensitive changes, or architecture decisions, a single perspective has blind spots. I found this the hard way when building <a href="https://timezones.centminmod.com">Timezone Scheduler</a> &#8211; Claude Code missed encoding edge cases that a second AI flagged immediately.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>So I used my custom Claude Code skills that let me ask a question once and get parallel answers from two different AI models, with a structured comparison at the end. No copy-pasting between tools, no switching browser tabs. Just type <code>/consult-codex</code> or <code>/consult-zai</code> and both AIs work simultaneously. I&#8217;ve been using skills within Claude Code for the past 3 months with great success.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bOGT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bOGT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 424w, https://substackcdn.com/image/fetch/$s_!bOGT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 848w, https://substackcdn.com/image/fetch/$s_!bOGT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 1272w, https://substackcdn.com/image/fetch/$s_!bOGT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bOGT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png" width="1295" height="1053" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1053,&quot;width&quot;:1295,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:210445,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193058940?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bOGT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 424w, https://substackcdn.com/image/fetch/$s_!bOGT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 848w, https://substackcdn.com/image/fetch/$s_!bOGT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 1272w, https://substackcdn.com/image/fetch/$s_!bOGT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70fb4e52-5c0f-41da-9b4d-abd9c2a90442_1295x1053.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Claude Code /consult-codex skill asking Claude to get a second opinion from Codex CLI GPT-5.4 LLM model</figcaption></figure></div><h2>What the skills do</h2><p><code>consult-codex</code> pairs a custom <code>code-searcher</code> subagent (from my <a href="https://github.com/centminmod/my-claude-code-setup">GitHub starter template repository</a>) with OpenAI&#8217;s Codex (GPT-5.4) running in readonly mode. <code>consult-zai</code> pairs the same code-searcher with z.ai&#8217;s glm-4.7. Both follow the same pattern:</p><ol><li><p>You ask a code question</p></li><li><p>The skill wraps your question with structured output requirements (file paths with line numbers, confidence levels, limitations)</p></li><li><p>Both AIs run in parallel &#8211; no serial waiting</p></li><li><p>You get a side-by-side comparison table showing where they agree, where they diverge, and which source provided better evidence</p></li></ol><p>The comparison output includes an agreement level indicator: High Agreement (both reached similar conclusions, higher confidence), Partial Agreement (overlapping findings with unique additions from each), or Disagreement (contradicting findings, manual verification needed).</p><p>In practice, I use <code>consult-codex</code> most often for security reviews and large commits. During the <a href="https://ai.georgeliu.com/p/why-11-ai-models-got-timezone-scheduling">Timezone Scheduler build</a>, a Day 3 API commit spanning 837 lines went through dual analysis. Codex caught request limit concerns that Claude alone did not flag, while the code-searcher agent found a caching optimization that Codex overlooked. Neither AI alone would have produced the same result.</p><h2>Prerequisites</h2><p>Before setting up these skills, you need three things installed and working.</p><h3>1. Claude Code</h3><p>Install Claude Code if you have not already. Use whichever package manager you have:</p><pre><code><code># npm
npm install -g @anthropic-ai/claude-code

# bun
bun install -g @anthropic-ai/claude-code</code></code></pre><p>Verify it works: <code>claude --version</code></p><h3>2. OpenAI Codex CLI (for consult-codex)</h3><p>Install the Codex CLI globally:</p><pre><code><code>npm install -g @openai/codex</code></code></pre><p>Authenticate with your OpenAI API key or login to your OpenAI ChatGPT subscription:</p><pre><code><code>export OPENAI_API_KEY="your-openai-api-key"</code></code></pre><p>Add that export to your <code>~/.bashrc</code> or <code>~/.zshrc</code> so it persists. Verify it works: <code>codex --help</code></p><h3>3. Z.AI GLM Coding Plan (for consult-zai)</h3><p>Z.AI provides access to glm-4.7 through a Claude Code-compatible API. It is a subscription service starting at roughly $3/month for the Lite plan (~120 prompts per 5 hours), with Pro (~600 prompts) and Max (~2,400 prompts) tiers available.</p><p><strong>Prerequisites:</strong> Node.js 18 or newer and a Z.AI API key from <a href="https://z.ai">z.ai</a>.</p><p>The <code>zai</code> alias is a shell function that sets environment variables and launches Claude Code pointed at z.ai&#8217;s API endpoint. Add this to your <code>~/.bashrc</code> or <code>~/.zshrc</code>:</p><pre><code><code>zai() {
    export ANTHROPIC_AUTH_TOKEN="your-zai-api-key"
    export ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic"
    export API_TIMEOUT_MS="3000000"
    claude "$@"
}</code></code></pre><p>Reload your shell (<code>source ~/.bashrc</code>) and verify: <code>zai --version</code> should show the Claude Code version, confirming the alias works.</p><p>For Windows PowerShell:</p><pre><code><code>function zai {
    $env:ANTHROPIC_AUTH_TOKEN = "your-zai-api-key"
    $env:ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic"
    $env:API_TIMEOUT_MS = "3000000"
    claude $args
}</code></code></pre><p>By default, Z.AI maps Claude model names to GLM models automatically: Opus and Sonnet map to glm-4.7, Haiku maps to glm-4.5-air. You do not need to configure model names manually.</p><p>If you want to override and pin to a specific model &#8211; for example to try newer releases &#8211; add the model env vars to the function:</p><pre><code><code>zai() {
    export ANTHROPIC_AUTH_TOKEN="your-zai-api-key"
    export ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic"
    export API_TIMEOUT_MS="3000000"
    export ANTHROPIC_DEFAULT_OPUS_MODEL="glm-5"
    export ANTHROPIC_DEFAULT_SONNET_MODEL="glm-5-turbo"
    export ANTHROPIC_DEFAULT_HAIKU_MODEL="glm-4.5-air"
    claude "$@"
}</code></code></pre><p>Available GLM models at time of writing: glm-4.7, glm-5, glm-5-turbo, glm-5.1. Leaving the overrides out lets Z.AI update the mapping automatically as new models release, so only pin if you have a specific reason to.</p><p>For privacy: Z.AI states it does not store any content you provide or generate. All services are based in Singapore. See their <a href="https://docs.z.ai/legal-agreement/privacy-policy">privacy policy</a> for details.</p><h2>Setting up the skills</h2><p>Both skills live in your project&#8217;s <code>.claude/skills/</code> directory. You can grab them from my <a href="https://github.com/centminmod/my-claude-code-setup">Claude Code starter template</a>, which is publicly available and includes these skills plus additional agents and workflow tooling.</p><h3>Skill file structure</h3><pre><code><code>.claude/
  skills/
    consult-codex/
      SKILL.md
    consult-zai/
      SKILL.md
  agents/
    codex-cli.md
    zai-cli.md
    code-searcher.md</code></code></pre><p>Each skill has a single <code>SKILL.md</code> file that tells Claude Code when and how to use it. The agents directory contains the CLI wrapper definitions that the skills invoke.</p><h3>How consult-codex works internally</h3><p>When you run <code>/consult-codex "analyze the auth middleware for security issues"</code>, the skill:</p><ol><li><p><strong>Wraps your question</strong> with structured output requirements &#8211; it asks both AIs for summaries, key findings with <code>file:line</code> citations, confidence levels, and acknowledged limitations</p></li><li><p><strong>Writes a temp file</strong> (<code>$CLAUDE_PROJECT_DIR/tmp/codex-prompt.txt</code>) to avoid shell quoting issues with complex prompts</p></li><li><p><strong>Launches both AIs in parallel</strong> using a single message with multiple tool calls:</p></li><li><p>Codex runs via: <code>bash -i -c 'codex -p readonly exec "$(cat $CLAUDE_PROJECT_DIR/tmp/codex-prompt.txt)" --json 2&gt;&amp;1'</code></p></li><li><p>code-searcher runs as a custom Claude Code subagent (defined in <code>.claude/agents/code-searcher.md</code> in the starter template)</p></li><li><p><strong>Parses Codex JSON output</strong> using jq recipes to extract reasoning, agent messages, and command executions from the JSONL stream</p></li><li><p><strong>Cleans up</strong> the temp file, then builds the comparison table</p></li></ol><p>The readonly permission mode for Codex is important &#8211; it can read your codebase but cannot modify files, which keeps the consultation safe.</p><h3>How consult-zai works internally</h3><p>The <code>consult-zai</code> skill follows the same architecture but calls the z.ai CLI instead:</p><pre><code><code>bash -i -c 'zai -p "$(cat $CLAUDE_PROJECT_DIR/tmp/zai-prompt.txt)" --output-format json --append-system-prompt "You are GLM 4.7 model accessed via z.ai API." 2&gt;&amp;1'</code></code></pre><p>The <code>--append-system-prompt</code> flag tells the model its identity, which helps when the comparison table attributes findings to each source.</p><h3>The comparison output</h3><p>Both skills produce a structured comparison with:</p><ul><li><p>Raw responses from each AI</p></li><li><p>A table comparing file paths found, line number specificity, code snippet quality, unique findings, and accuracy</p></li><li><p>An agreement level assessment</p></li><li><p>A synthesized summary that prioritizes findings corroborated by both agents</p></li><li><p>A recommendation on which source was more helpful for that specific query</p></li></ul><h2>What I learned</h2><p><strong>Parallel execution matters.</strong> Running both AIs simultaneously instead of sequentially cuts total wait time roughly in half. The skill architecture launches both as concurrent tool calls in a single message.</p><p><strong>Structured prompts produce comparable output.</strong> Without the enhanced prompt wrapper, each AI returns answers in different formats, making comparison difficult. Requiring <code>file:line</code> citations and confidence levels from both makes the comparison table meaningful.</p><p><strong>Agreement level is a useful signal.</strong> When both AIs agree, I have higher confidence. When they disagree, that is exactly where I should look manually. Partial agreement &#8211; where each found something the other missed &#8211; is the most common and most valuable outcome.</p><p><strong>The temp file pattern solves a real problem.</strong> Shell quoting breaks when your prompt contains quotes, backticks, or special characters. Writing to a temp file and reading with <code>$(cat ...)</code> avoids all of that.</p><h2>What did not work</h2><p>The initial version tried to pass prompts directly as shell arguments. That broke constantly with complex code questions containing special characters. The temp file approach was the fix.</p><p>Codex CLI can be slow to respond &#8211; sometimes over a minute for large codebase scans. The skill sets a 10-minute timeout to handle this, but it means you should not expect instant results on complex queries. You may find that Claude response will complete before the Codex CLI response. In which case you can prompt Claude to take into account Codex CLI&#8217;s response.</p><p>Z.AI&#8217;s token limits on the Lite plan can be constraining for large analysis tasks. If you plan to use <code>consult-zai</code> heavily, the Pro tier is worth considering.</p><h2>What is next</h2><p>These skills are part of my <a href="https://github.com/centminmod/my-claude-code-setup">Claude Code starter template</a>, which includes the full setup: skills, agents, memory bank system, shell aliases, git worktree launchers, and more. I will cover the complete template in a dedicated post.</p><p>If you want to try these skills today, clone the starter template repo and copy the <code>.claude/skills/consult-codex/</code> and <code>.claude/skills/consult-zai/</code> directories into your own project. Make sure you also grab the agent definitions from <code>.claude/agents/</code>.</p><p>If you&#8217;re interested in practical AI building for web apps, developer workflows, and infrastructure, subscribe for future posts. You can also follow my shorter updates on <a href="https://www.threads.com/@george_sl_liu">Threads (@george_sl_liu)</a> and <a href="https://bsky.app/profile/georgesl.bsky.social">Bluesky (@georgesl.bsky.social)</a> or subscribe and follow along.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://buymeacoffee.com/georgeliu&quot;,&quot;text&quot;:&quot;Buy Me A Coffee&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://buymeacoffee.com/georgeliu"><span>Buy Me A Coffee</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Building an AI Image Creator Skill for Claude Code]]></title><description><![CDATA[ai-image-creator skill for LLM models]]></description><link>https://ai.georgeliu.com/p/building-an-ai-image-creator-skill</link><guid isPermaLink="false">https://ai.georgeliu.com/p/building-an-ai-image-creator-skill</guid><dc:creator><![CDATA[George Liu]]></dc:creator><pubDate>Sat, 04 Apr 2026 17:37:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5Arj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I needed project images for web apps and web sites I was developing. Logos, social media banners, product shots. The usual approach is to open ChatGPT, Google Gemini, Midjourney or DALL-E, generate something, download it, resize it manually, hope the style stays consistent across sizes. Repeat for every asset.</p><p>Instead, I built a Claude Code skill that generates images from the terminal and also via Claude Desktop MacOS app (see Claude Cowork example below). One command, any AI model, with transparent backgrounds, reference image editing, prompt engineering patterns, and composite banner generation built in. Every image on my <a href="https://timezones.centminmod.com">Timezone Scheduler</a> site was created this way without ever leaving my code editor.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This post walks through what the skill does, how it works under the hood, and why building image generation into a developer workflow changes how you think about visual assets.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5Arj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5Arj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 424w, https://substackcdn.com/image/fetch/$s_!5Arj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 848w, https://substackcdn.com/image/fetch/$s_!5Arj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 1272w, https://substackcdn.com/image/fetch/$s_!5Arj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5Arj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1289123,&quot;alt&quot;:&quot;Example Claude Code ai-image-creator Skill generated image&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Example Claude Code ai-image-creator Skill generated image" title="Example Claude Code ai-image-creator Skill generated image" srcset="https://substackcdn.com/image/fetch/$s_!5Arj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 424w, https://substackcdn.com/image/fetch/$s_!5Arj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 848w, https://substackcdn.com/image/fetch/$s_!5Arj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 1272w, https://substackcdn.com/image/fetch/$s_!5Arj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6f7bdd2-701a-430e-8063-e6df17dc8e43_2160x1206.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example Claude Code ai-image-creator Skill generated image</figcaption></figure></div><h2>What problem this solves</h2><p>If you are a developer building web apps, you probably treat image assets as a separate workflow. You switch to a browser-based AI tool, type a prompt, download the result, maybe resize it with a separate tool, then move it into your project. Each image is a context switch.</p><p>The <a href="https://github.com/centminmod/my-claude-code-setup">ai-image-creator skill</a> eliminates that. It runs inside Claude Code, so you stay in your terminal the whole time. You describe what you want in natural language, Claude Code enhances your prompt using built-in patterns, picks the right model, and generates the image directly into your project directory. If you need multiple sizes for social media or ad banners, a separate composite mode handles that from a single config file.</p><p>The skill supports five AI image models through OpenRouter&#8217;s API, all proxied through Cloudflare AI Gateway for monitoring and cost control:</p><ul><li><p><a href="https://openrouter.ai/google/gemini-3.1-flash-image-preview">Gemini 3.1 Flash Image Preview</a> (Google Nano Banana 2)</p></li><li><p><a href="https://openrouter.ai/black-forest-labs/flux.2-max">FLUX.2 Max</a></p></li><li><p><a href="https://openrouter.ai/sourceful/riverflow-v2-pro">Riverflow v2 Pro</a></p></li><li><p><a href="https://openrouter.ai/bytedance-seed/seedream-4.5">Seedream 4.5</a></p></li><li><p><a href="https://openrouter.ai/openai/gpt-5-image">GPT-5 Image</a></p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aXyq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aXyq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 424w, https://substackcdn.com/image/fetch/$s_!aXyq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 848w, https://substackcdn.com/image/fetch/$s_!aXyq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 1272w, https://substackcdn.com/image/fetch/$s_!aXyq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aXyq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png" width="1456" height="661" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:661,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82546,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aXyq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 424w, https://substackcdn.com/image/fetch/$s_!aXyq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 848w, https://substackcdn.com/image/fetch/$s_!aXyq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 1272w, https://substackcdn.com/image/fetch/$s_!aXyq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2ce2091-3215-4957-980d-0bb7f11261cf_2070x940.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Example image created for my <a href="https://centminmod.com/nginx">Centmin Mod site&#8217;s Nginx page</a>. I asked Claude Code read each local copy of my sites&#8217; pages and generate an image that accurately depicts the content conveyed on each page.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-ME_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-ME_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 424w, https://substackcdn.com/image/fetch/$s_!-ME_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 848w, https://substackcdn.com/image/fetch/$s_!-ME_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 1272w, https://substackcdn.com/image/fetch/$s_!-ME_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-ME_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png" width="960" height="536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:536,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:115532,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-ME_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 424w, https://substackcdn.com/image/fetch/$s_!-ME_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 848w, https://substackcdn.com/image/fetch/$s_!-ME_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 1272w, https://substackcdn.com/image/fetch/$s_!-ME_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ddf1fe4-0277-49c1-be59-adfa875b4590_960x536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>How SKILL.md uses progressive disclosure</h2><p>Before getting into what the skill does, it is worth explaining how it is structured, because the design pattern is reusable for any complex Claude Code skill.</p><p>The <code>SKILL.md</code> file is the entry point. It uses <strong>progressive disclosure</strong>: only the instructions immediately needed for any given task are loaded into context. The bulk of the knowledge lives in separate reference files that are only read when a specific category is detected.</p><p>Here is how it works in practice. SKILL.md contains a routing table:</p><blockquote><p>If the request mentions &#8220;product shot&#8221; or &#8220;product photo&#8221; &#8594; read <code>prompt-core.md</code> + <code>prompt-categories.md</code> section <code>product_hero</code></p></blockquote><p>When you ask for a product shot, Claude Code reads only those two files. If you ask for a social media graphic, it reads a different pair. If you ask for a simple image with a basic prompt, it skips the reference files entirely and generates directly.</p><p>The alternative would be loading all ~25,000 words of prompt engineering reference into every single request. That would consume a large portion of the context window even for trivial requests like &#8220;generate a blue circle.&#8221; Progressive disclosure keeps the skill lightweight for simple tasks and deep for complex ones.</p><p>The three reference files behind the routing:</p><ul><li><p><code>prompt-core.md</code> &#8211; foundational rules: narrative prompting, camera/lens specs, lighting setups, text rendering rules, model recommendations</p></li><li><p><code>prompt-categories.md</code> &#8211; 11 category formulas with templates and complete example prompts</p></li><li><p><code>prompt-platforms.md</code> &#8211; social media ratios, IAB ad sizes, web dimensions, POD specs</p></li></ul><p>A fourth file, <code>composite-reference.md</code>, is only loaded when composite banner mode is detected. The analyze mode has its own <code>analyze-reference.md</code>. The setup guide is in <code>setup-guide.md</code>. None of these are loaded unless the routing logic determines they are needed.</p><p>This pattern keeps skill files maintainable too. When I update the product_hero formula, I edit one section of one file. When I add a new model, I update one table in SKILL.md. No sprawling monolith to edit.</p><h2>How it works</h2><p>The skill is a Python script (~1,300 lines) that runs through <code>uv</code> (a fast Python runner). The core workflow is three steps:</p><ol><li><p><strong>Write or enhance a prompt.</strong> For simple requests, pass an inline prompt. For anything complex, write to a temp file. The skill includes prompt engineering reference files covering 11 categories (product hero shots, lifestyle photos, social media graphics, marketing banners, icons, illustrations, food photography, architecture, infographics, print-on-demand designs, and image analysis). Each category has a formula template with camera specs, lighting setups, and composition guidance.</p></li><li><p><strong>Generate the image.</strong> The script calls OpenRouter&#8217;s API (or Google AI Studio directly), routing through Cloudflare AI Gateway when configured. You control aspect ratio, resolution, model selection, and output path.</p></li><li><p><strong>Post-process if needed.</strong> Resize, convert formats, or generate transparent backgrounds using FFmpeg and ImageMagick pipelines.</p></li></ol><p>A typical command looks like this:</p><pre><code><code>uv run python generate-image.py \
  -o "assets/hero-banner.png" \
  -m gemini \
  -a "16:9" \
  -s "2K" \
  -p "A flat-design globe icon with timezone band lines in blue and teal"</code></code></pre><p>This will generate a image at with an accompanying markdown file which contains the actual prompt the ai-image-creator skill used</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;8c6818fd-12e6-4532-8b8e-3aaeb3b2583f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">ls -lAh
total 4696
-rw-r--r--@ 1 george  staff   2.3M  5 Apr 02:12 hero-banner.png
-rw-r--r--@ 1 george  staff   249B  5 Apr 02:12 hero-banner.prompt.md</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Wtgt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wtgt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 424w, https://substackcdn.com/image/fetch/$s_!Wtgt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 848w, https://substackcdn.com/image/fetch/$s_!Wtgt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 1272w, https://substackcdn.com/image/fetch/$s_!Wtgt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wtgt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png" width="640" height="357" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:357,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81678,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Wtgt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 424w, https://substackcdn.com/image/fetch/$s_!Wtgt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 848w, https://substackcdn.com/image/fetch/$s_!Wtgt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 1272w, https://substackcdn.com/image/fetch/$s_!Wtgt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c660832-9242-4ac6-a8e2-716c3171fbbf_640x357.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;markdown&quot;,&quot;nodeId&quot;:&quot;1602d703-ec3f-4bdf-82e3-7754de874246&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-markdown"># Prompt

- **Model:** google/gemini-3.1-flash-image-preview
- **Provider:** openrouter (gateway)
- **Aspect ratio:** 16:9
- **Image size:** 2K
- **Elapsed:** 38.5s

## Prompt Text

A flat-design globe icon with timezone band lines in blue and teal</code></pre></div><h2>The prompt engineering layer</h2><p>This is where most of the value lives. The difference between a mediocre AI image and a professional one is almost entirely in the prompt. The skill includes three reference files totalling ~25,000 words of prompt patterns I refined through hundreds of generations.</p><p><strong>Core principle: narrative over keywords.</strong> &#8220;A warm afternoon in a sunlit cafe with a steaming latte on a marble table&#8221; produces dramatically better results than &#8220;latte, cafe, warm, marble, afternoon, sunlit.&#8221; The reference files teach Claude Code to compose prompts as scene direction, not tag lists.</p><p><strong>Camera language triggers photorealism.</strong> Including real camera specs (Sony A7R IV, 85mm macro at f/2.8) pushes models toward photorealistic output. The reference files map camera/lens/aperture combinations to specific use cases: 85mm macro for product shots, 100mm macro for food, 24mm tilt-shift for architecture, 35mm for lifestyle scenes.</p><p><strong>Quality modifiers are wasted tokens.</strong> Words like &#8220;4K&#8221;, &#8220;ultra HD&#8221;, &#8220;masterpiece&#8221; are ignored by modern image models. The reference files focus on describing what you actually want instead of asking for generic quality.</p><p><strong>Text rendering has specific rules.</strong> AI models can render text in images but need precise instructions. Always wrap text in quotation marks. Describe font style (&#8220;bold condensed sans-serif&#8221;), not font names (&#8220;Bebas Neue&#8221;). Specify placement, case, and how text integrates with the design. Following these rules gets 95%+ text accuracy.</p><p>Each of the 11 categories has a formula template. Here is the product hero pattern:</p><blockquote><p>Create a [lighting] product photograph of [detailed product description] on/against [surface/background]. Shot with [camera] [lens] at [aperture]. [Lighting description]. [Composition notes]. [Mood/atmosphere].</p></blockquote><p>Claude Code detects the category from your request and loads the matching pattern automatically. If you ask for &#8220;a product shot of headphones,&#8221; it reads the product_hero template and composes a detailed prompt with camera specs, lighting direction, surface material, and atmosphere. You still control what you want. The skill just makes sure the prompt is structured for the best possible output.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ogk2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ogk2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 424w, https://substackcdn.com/image/fetch/$s_!Ogk2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 848w, https://substackcdn.com/image/fetch/$s_!Ogk2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 1272w, https://substackcdn.com/image/fetch/$s_!Ogk2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ogk2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png" width="640" height="349" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/10e63d34-80a9-4641-a0c2-843207c85437_640x349.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:349,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:152170,&quot;alt&quot;:&quot;Example Claude Code ai-image-creator Skill generated image&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Example Claude Code ai-image-creator Skill generated image" title="Example Claude Code ai-image-creator Skill generated image" srcset="https://substackcdn.com/image/fetch/$s_!Ogk2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 424w, https://substackcdn.com/image/fetch/$s_!Ogk2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 848w, https://substackcdn.com/image/fetch/$s_!Ogk2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 1272w, https://substackcdn.com/image/fetch/$s_!Ogk2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10e63d34-80a9-4641-a0c2-843207c85437_640x349.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example Claude Code ai-image-creator Skill generated image: A warm afternoon in a sunlit cafe with a steaming latte on a marble table</figcaption></figure></div><h2>Transparent backgrounds without Photoshop</h2><p>One feature I use constantly is transparent mode (<code>-t</code>). It generates images with transparent backgrounds using a three-step pipeline:</p><ol><li><p>The prompt is augmented to place the subject on a solid green (#00FF00) screen</p></li><li><p>FFmpeg removes the green background and cleans up green fringe from edges</p></li><li><p>ImageMagick auto-crops the transparent padding</p></li></ol><p>Gives you clean PNGs with alpha channels for icons, logos, mascots, sprites, and any asset that needs to sit on different backgrounds. No manual masking, no Photoshop.</p><pre><code><code>uv run python generate-image.py \
  -o "mascot.png" -t \
  -p "A friendly robot mascot character in flat illustration style"</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ejij!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ejij!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 424w, https://substackcdn.com/image/fetch/$s_!Ejij!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 848w, https://substackcdn.com/image/fetch/$s_!Ejij!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 1272w, https://substackcdn.com/image/fetch/$s_!Ejij!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ejij!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png" width="486" height="624" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:624,&quot;width&quot;:486,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:119585,&quot;alt&quot;:&quot;A friendly robot mascot character in flat illustration style&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A friendly robot mascot character in flat illustration style" title="A friendly robot mascot character in flat illustration style" srcset="https://substackcdn.com/image/fetch/$s_!Ejij!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 424w, https://substackcdn.com/image/fetch/$s_!Ejij!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 848w, https://substackcdn.com/image/fetch/$s_!Ejij!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 1272w, https://substackcdn.com/image/fetch/$s_!Ejij!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb541ac38-8d64-4b53-a270-a1f94f9ebed2_486x624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><code>A friendly robot mascot character in flat illustration style</code></figcaption></figure></div><h2>Reference image editing</h2><p>Multimodal models (Gemini and GPT-5) accept reference images alongside text prompts. This enables editing, style transfer, and guided generation:</p><pre><code><code># Edit an existing image
uv run python generate-image.py \
  -o "edited.png" -r "photo.png" \
  -p "Make the background white"

# Style transfer with multiple references
uv run python generate-image.py \
  -o "combined.png" -r "style.png" -r "content.png" \
  -p "Apply the style of the first image to the second"
</code></code></pre><p>When you provide a reference, the prompt should describe what to change, not the entire scene. The model sees the original and needs edits, not a full new description. This is useful for iterating on designs: generate a base image, then refine it through successive edits without starting over.</p><h2>Composite banners: consistent branding at every size</h2><p>The second script in the skill (<code>composite-banners.py</code>) solves a different problem. When you have an existing logo and need it rendered consistently across 15 different banner sizes (social media covers, IAB ad formats, email headers, OG images), AI generation is the wrong tool. Each generation would produce slightly different styling.</p><p>Composite mode uses ImageMagick for offline compositing. No API calls, no network required. You define your brand in a JSON config (logo path, brand name, tagline, colors, font preferences) and specify which banner sizes you need. One command generates all of them with pixel-perfect consistency.</p><pre><code><code># Initialize a config
uv run python composite-banners.py --init

# Generate all banners
uv run python composite-banners.py \
  -c banner-config.json -o ./banners/</code></code></pre><p>The config supports horizontal, horizontal-compact, and centered layouts. It includes presets for IAB standard ad sizes (leaderboard, billboard, medium rectangle, skyscraper), social media covers (Twitter, LinkedIn, Facebook, YouTube), and web assets (hero banners, OG images, email headers, favicons).</p><p>The most powerful workflow combines both scripts. Use <code>generate-image.py</code> to AI-create a hero background or textured pattern, then use <code>composite-banners.py</code> to overlay your logo and text onto that background at all standard sizes. Creative AI visuals plus pixel-perfect logo consistency.</p><h2>Image analysis</h2><p>The skill also works in reverse. Instead of generating images, you can analyze existing ones using multimodal AI vision:</p><pre><code><code>uv run python generate-image.py \
  --analyze -r "assets/cafe-latte.png" \
  -p "Describe the subject, style, colors, and composition"</code></code></pre><p>This returns a JSON response with the model&#8217;s analysis. Useful for generating alt text, comparing before/after versions, or understanding what a competitor&#8217;s visual assets are doing well. You can analyze multiple images together for comparison.</p><p>Claude Code Opus 4.6 response:</p><blockquote><p><strong>Subject:</strong> A first-person POV inside a cozy Parisian cafe &#8212; steaming latte with latte art on a marble table, two books (including &#8220;The Hidden Life of Trees&#8221; with tortoise-shell glasses resting on top), a hand holding an open book, and a large window overlooking a bustling street with trees and passersby.</p><p><strong>Style:</strong> Digital photograph with a filmic quality &#8212; visible grain, shallow depth of field, candid/documentary feel.</p><p><strong>Colors:</strong> Warm, natural palette &#8212; rich browns from wood and the cup, greens from potted plants and outdoor trees, stone-beige masonry, warm gold/yellow from overhead lighting, faded whites from furniture.</p><p><strong>Composition:</strong> Two-part split (indoor/outdoor) separated by the window frame. Shallow depth of field keeps the foreground sharp (hand, books, cup) while the background softens. Window panes and wood trim act as leading lines toward the center. Asymmetrical but balanced table arrangement.</p></blockquote><p>An example of using ai-image-creator skill &#8212;analyze to deconstruct an image into JSON structured data with Google Nano Banana 2 and compare it to Claude Opus 4.6&#8217;s own native visual image support. Google Nano Banana 2 picked up more details than Clause Opus 4.6 with my Claude Cowork desktop chat session.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fqbf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fqbf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 424w, https://substackcdn.com/image/fetch/$s_!fqbf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 848w, https://substackcdn.com/image/fetch/$s_!fqbf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 1272w, https://substackcdn.com/image/fetch/$s_!fqbf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fqbf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png" width="1456" height="1676" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1676,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:250932,&quot;alt&quot;:&quot;ai-image-creator skill &#8212;analyze to deconstruct an image into JSON structured data with Google Nano Banana 2&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ai.georgeliu.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ai-image-creator skill &#8212;analyze to deconstruct an image into JSON structured data with Google Nano Banana 2" title="ai-image-creator skill &#8212;analyze to deconstruct an image into JSON structured data with Google Nano Banana 2" srcset="https://substackcdn.com/image/fetch/$s_!fqbf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 424w, https://substackcdn.com/image/fetch/$s_!fqbf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 848w, https://substackcdn.com/image/fetch/$s_!fqbf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 1272w, https://substackcdn.com/image/fetch/$s_!fqbf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa792481-0f40-4fac-9cfc-14464e4a0039_2226x2562.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ai-image-creator skill &#8212;analyze to deconstruct an image into JSON structured data with Google Nano Banana 2</figcaption></figure></div><p>Now that Claude understands the reference images style and composition, I can assign this style a name <strong>Substack Comic Style</strong> - for this Substack&#8217;s creative images that I create for each of my Substack article posts. You&#8217;ll need to subscribe to my Substack to find out what article these generated images belong to &#128521;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0mg9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0mg9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 424w, https://substackcdn.com/image/fetch/$s_!0mg9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 848w, https://substackcdn.com/image/fetch/$s_!0mg9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 1272w, https://substackcdn.com/image/fetch/$s_!0mg9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0mg9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png" width="1456" height="753" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:753,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2180841,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ai.georgeliu.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0mg9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 424w, https://substackcdn.com/image/fetch/$s_!0mg9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 848w, https://substackcdn.com/image/fetch/$s_!0mg9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 1272w, https://substackcdn.com/image/fetch/$s_!0mg9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f628b-8914-476a-aaa8-ca34ac7f8c99_4344x2248.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ai-image-creator generating images in Substack Comic Style derived from &#8212;analyze image deconstruction</figcaption></figure></div><h2>Cost tracking</h2><p>Every generation is logged to a local <code>.ai-image-creator/costs.json</code> file in your project directory. Running <code>--costs</code> shows per-model breakdown: generation count, total tokens, elapsed time, and recent entries. No API keys or credentials are ever stored, just usage metadata.</p><p>This matters when you are generating dozens of images for a project. You can see exactly which models you are using, how much each generation costs, and where your budget is going.</p><h2>The Cloudflare AI Gateway layer</h2><p>All API calls can be routed through Cloudflare AI Gateway using BYOK (Bring Your Own Key). Your API keys are stored securely in Cloudflare&#8217;s dashboard, never sent in request headers from your machine. The gateway provides unified logging across all models and providers, cost monitoring at the account level, rate limiting to prevent accidental overspend, and automatic fallback to direct API calls if the gateway is unavailable.</p><p>This is the same gateway pattern I use for other AI integrations. Once you have it set up, every AI API call in every project goes through the same monitoring and cost control layer.</p><p>One practical note on credentials: the script supports a <code>.env</code> file at <code>scripts/.env</code> inside the skill folder. This matters for desktop AI tools like Cowork, which run in sandboxed shells that do not inherit your <code>~/.zshrc</code> exports. Populate the <code>.env</code> with your gateway variables, zip the skill folder, and install it in Cowork -- the script loads credentials automatically using only stdlib (no extra dependencies) and never overwrites keys already set in the environment. Running from a regular terminal with shell exports works the same as before.</p><h2>Using the skill in other AI coding tools</h2><p>The skill format follows the open Agent Skills standard, which means it works in more than just Claude Code. The same <code>ai-image-creator/</code> directory can be transplanted into Codex CLI, Gemini CLI, Kilo Code, and OpenCode with minimal changes. Each tool has its own discovery path:</p><ul><li><p><strong>Codex CLI</strong> &#8211; Copy the skill folder to <code>~/.codex/skills/</code> for global availability, or to <code>.agents/skills/</code> at the project root for repo-scoped use.</p></li><li><p><strong>Gemini CLI</strong> &#8211; Place the skill inside <code>~/.gemini/extensions/&lt;extension-name&gt;/skills/</code> for a user-level extension, or in <code>.agents/skills/</code> in your workspace.</p></li><li><p><strong>Kilo Code</strong> &#8211; Drop it into <code>.kilocode/skills/</code> at the project root, or add the path via <code>skills.paths</code> in your <code>kilo.jsonc</code> config for global availability across projects.</p></li><li><p><strong>OpenCode</strong> &#8211; Global skills go in <code>~/.config/opencode/skills/</code>. Project-scoped skills go in <code>.opencode/skills/</code> and take higher priority.</p></li></ul><p>The <code>SKILL.md</code> frontmatter (<code>name</code>, <code>description</code>, <code>allowed-tools</code>, <code>compatibility</code>) is read by all of these tools to discover and describe the skill. The <code>generate-image.py</code> script itself is tool-agnostic: it runs via <code>uv run python</code>, which works the same regardless of which AI coding harness invokes it.</p><p>One thing to check across tools: the <code>${CLAUDE_SKILL_DIR}</code> variable used in <code>SKILL.md</code> to reference script paths. Claude Code expands this automatically. Other tools may not. If a tool does not expand <code>${CLAUDE_SKILL_DIR}</code>, replace it with an absolute path or a <code>$(dirname "$0")</code>-style resolution in the script invocations.</p><h2>What I learned building this</h2><p><strong>Prompt structure matters more than model choice.</strong> Switching from a keyword-style prompt to a narrative prompt with camera specs improved output quality more than switching between models. The reference files encode this knowledge so you do not have to rediscover it for each generation.</p><p><strong>Transparent backgrounds are harder than they look.</strong> My first attempt used simple color replacement, which left green fringe artifacts around edges. The FFmpeg chroma key pipeline with edge cleanup handles this properly, but it took several iterations to get clean results on complex shapes like hair or translucent materials.</p><p><strong>Composite mode saved the most time.</strong> Generating 15 banner sizes individually with AI would take 15 API calls, 15 different results, and manual checking for brand consistency. Composite mode does it in one local operation with zero API cost and guaranteed consistency.</p><p><strong>Cost tracking changes behavior.</strong> Once you can see per-generation costs, you start making smarter choices about which model to use for which task. Gemini is cheap and versatile for most web assets. FLUX.2 is better for artistic work but costs more. Having the data makes the trade-off explicit.</p><h2>What did not work</h2><p><strong>Film stock prompts are hit or miss.</strong> The reference files include film stock aesthetics (&#8220;Shot on Kodak Portra 400&#8221; for warm skin tones, &#8220;Shot on CineStill 800T&#8221; for cinematic tungsten tones). Some models respond well to these, others ignore them entirely. I keep them in the reference because when they work, the aesthetic shorthand saves a lot of descriptive text.</p><p><strong>Text rendering still fails on long strings.</strong> Even with the seven-rule system (quotation marks, font style not name, explicit case, short text), anything beyond 5-6 words becomes unreliable depending on the model. Google Gemini 3.1 Image Flash / Google Nano Banana 2 is much better at text rendering though.</p><p><strong>Google AI Studio free tier does not support image generation for Google Gemini 3.1 Image Flash / Google Nano Banana 2.</strong> The quota is zero for image generation unless you enable billing on the linked Google Cloud project. I documented this in the setup guide after debugging a cryptic <code>429 RESOURCE_EXHAUSTED</code> error. OpenRouter&#8217;s pay-as-you-go model is simpler for most users.</p><h2>How to use it</h2><p>The skill is open source, bundled in my <a href="https://github.com/centminmod/my-claude-code-setup">Claude Code starter template repository</a> at <code>.claude/skills/ai-image-creator/</code>. To use it in your own Claude Code projects:</p><ol><li><p>Copy the <code>ai-image-creator/</code> folder to your project&#8217;s <code>.claude/skills/</code> directory</p></li><li><p>Add permission entries to <code>.claude/settings.local.json</code></p></li><li><p>Set your API keys. There are two ways depending on how you run the skill: <strong>Terminal / Claude Code CLI:</strong> Export variables in your shell profile (<code>~/.zshrc</code> or <code>~/.bashrc</code>):</p></li></ol><p>bash</p><pre><code><code>   export AI_IMG_CREATOR_CF_ACCOUNT_ID="your-account-id"
   export AI_IMG_CREATOR_CF_GATEWAY_ID="your-gateway-name"
   export AI_IMG_CREATOR_CF_TOKEN="your-gateway-token"
   # Or for direct OpenRouter access without a gateway:
   export AI_IMG_CREATOR_OPENROUTER_KEY="sk-or-..."</code></code></pre><p><strong>Claude Cowork (desktop app):</strong> Cowork runs in a sandboxed shell that does not inherit your shell profile. Create a <code>.env</code> file at <code>scripts/.env</code> inside the skill folder:</p><pre><code><code>   AI_IMG_CREATOR_CF_ACCOUNT_ID=your-account-id
   AI_IMG_CREATOR_CF_GATEWAY_ID=your-gateway-name
   AI_IMG_CREATOR_CF_TOKEN=your-gateway-token</code></code></pre><p>Then zip the entire <code>ai-image-creator/</code> folder (with the populated <code>.env</code> inside) and install it as a skill in Cowork. The script loads the <code>.env</code> automatically on startup. Shell exports take precedence over the <code>.env</code> if both are present.</p><p>Then Ask Claude Code to generate an image.</p><p>The setup guide in <code>references/setup-guide.md</code> walks through every step, including Cloudflare AI Gateway configuration and BYOK provider key setup.</p><p>An example of ai-image-creator skill in action within Claude Cowork desktop app for MacOS with a Claude Cowork project.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t99u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t99u!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 424w, https://substackcdn.com/image/fetch/$s_!t99u!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 848w, https://substackcdn.com/image/fetch/$s_!t99u!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 1272w, https://substackcdn.com/image/fetch/$s_!t99u!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t99u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png" width="1456" height="1086" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1086,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:865119,&quot;alt&quot;:&quot;Claude Cowork MacOS app creating image using ai-image-creator skill&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ai.georgeliu.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Claude Cowork MacOS app creating image using ai-image-creator skill" title="Claude Cowork MacOS app creating image using ai-image-creator skill" srcset="https://substackcdn.com/image/fetch/$s_!t99u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 424w, https://substackcdn.com/image/fetch/$s_!t99u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 848w, https://substackcdn.com/image/fetch/$s_!t99u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 1272w, https://substackcdn.com/image/fetch/$s_!t99u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2dc74ba4-b886-4196-9495-e9c324cf176b_2942x2194.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Claude Cowork MacOS app creating image using ai-image-creator skill</figcaption></figure></div><p>Claude Desktop MacOS app uploaded ai-image-creator skill:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uoW-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uoW-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 424w, https://substackcdn.com/image/fetch/$s_!uoW-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 848w, https://substackcdn.com/image/fetch/$s_!uoW-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 1272w, https://substackcdn.com/image/fetch/$s_!uoW-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uoW-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png" width="1456" height="812" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:812,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:158947,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ai.georgeliu.com/i/193055371?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uoW-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 424w, https://substackcdn.com/image/fetch/$s_!uoW-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 848w, https://substackcdn.com/image/fetch/$s_!uoW-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 1272w, https://substackcdn.com/image/fetch/$s_!uoW-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc221ea3d-cd91-4f1f-8eea-aea175bc60f4_2734x1524.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Claude Desktop ai-image-creator skill</figcaption></figure></div><h2>What&#8217;s next</h2><p>I am working on expanding model support as new image generation models land on OpenRouter. I am also exploring batch generation workflows where the skill generates all visual assets for a web project (favicon, OG image, social banners, app icons) from a single brand brief.</p><p>If you are building with Claude Code and want to see how custom skills extend what it can do, this is a concrete example.</p><div><hr></div><p>If you&#8217;re interested in practical AI building for web apps, developer workflows, and infrastructure, subscribe for future posts. You can also follow my shorter updates on <a href="https://www.threads.com/@george_sl_liu">Threads (@george_sl_liu)</a> and <a href="https://bsky.app/profile/georgesl.bsky.social">Bluesky (@georgesl.bsky.social)</a> or subscribe and follow along.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://buymeacoffee.com/georgeliu&quot;,&quot;text&quot;:&quot;Buy Me A Coffee&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://buymeacoffee.com/georgeliu"><span>Buy Me A Coffee</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Why 11 AI Models Got Timezone Scheduling Wrong (and What I Built Instead)]]></title><description><![CDATA[If you have ever scheduled a meeting across three continents, you know the math.]]></description><link>https://ai.georgeliu.com/p/why-11-ai-models-got-timezone-scheduling</link><guid isPermaLink="false">https://ai.georgeliu.com/p/why-11-ai-models-got-timezone-scheduling</guid><dc:creator><![CDATA[George Liu]]></dc:creator><pubDate>Sat, 04 Apr 2026 15:49:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!130Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have ever scheduled a meeting across three continents, you know the math. You check one timezone converter, then another, then you do the offset arithmetic in your head, and somewhere in the process you forget that New York already switched to daylight saving but London has not yet.</p><p>I do consulting work across Brisbane, the US, and Europe and it can be a challenge to find a time that works for all folks. So I built a tool with Claude Code (Anthropic&#8217;s AI coding agent that writes and runs code from natural language instructions) to handle it - with both a graphical user web interface as well as command line / API support. Then, out of curiosity, I gave 11 AI models the same timezone scheduling problem my tool solves. The best model scored 86 out of 100. My tool scored 100.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This is not about AI being bad at timezone math. It is about recognizing when a purpose-built tool outperforms general-purpose AI, and what builders can learn from that gap.</p><p>Timezones Scheduler web app interface at <a href="https://timezones.centminmod.com/">https://timezones.centminmod.com</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!130Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!130Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 424w, https://substackcdn.com/image/fetch/$s_!130Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 848w, https://substackcdn.com/image/fetch/$s_!130Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 1272w, https://substackcdn.com/image/fetch/$s_!130Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!130Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png" width="1456" height="1882" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1882,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:99063,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193042702?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!130Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 424w, https://substackcdn.com/image/fetch/$s_!130Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 848w, https://substackcdn.com/image/fetch/$s_!130Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 1272w, https://substackcdn.com/image/fetch/$s_!130Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b9256f6-4ab1-49fd-a51e-676edeb4c4e5_2030x2624.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Same prompt, 11 models, one rubric</h2><p>I gave every model the same prompt for March 23, 2026:</p><blockquote><p>&#8220;Find the most optimal meeting time that works across Brisbane, New York, and London, ideally falling within business hours for all three cities, or as close to business hours as possible.&#8221;</p></blockquote><p>Here is the constraint that makes this hard: Brisbane is 14 hours ahead of New York. When it is 9 AM Monday in New York, it is 11 PM Monday in Brisbane. Getting all three cities into standard business hours simultaneously is mathematically impossible. The best any solution can do is land two cities in core business hours and one outside, which I score as 6 out of a possible 9 points (more on the scoring system below).</p><p>I scored every response across five categories totalling 100 points: time optimization (25), DST accuracy (20), timezone math consistency (15), analysis quality (20), and practical value (20).</p><p><strong>Only 2 out of 11 models found the optimal time slot.</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8rdu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8rdu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 424w, https://substackcdn.com/image/fetch/$s_!8rdu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 848w, https://substackcdn.com/image/fetch/$s_!8rdu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 1272w, https://substackcdn.com/image/fetch/$s_!8rdu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8rdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png" width="405" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/095de54f-3fb9-4cae-943e-09389fe32734_405x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:405,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37938,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193042702?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8rdu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 424w, https://substackcdn.com/image/fetch/$s_!8rdu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 848w, https://substackcdn.com/image/fetch/$s_!8rdu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 1272w, https://substackcdn.com/image/fetch/$s_!8rdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F095de54f-3fb9-4cae-943e-09389fe32734_405x630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Full benchmark with every model&#8217;s raw response: <a href="https://github.com/centminmod/timezone-scheduler">github.com/centminmod/timezone-scheduler</a></p><h2>Why AI struggles with this</h2><p>Three patterns stood out across the 11 models I tested:</p><p><strong>Models contradict their own analysis.</strong> MiniMax M2.7 recommended 5:00 AM New York, then labeled it &#8220;too early&#8221; in the same response. Gemini 3.1 Pro found the optimal slot in its ranked list, then recommended a different, worse time in its summary paragraph. When models reason through trade-offs, they sometimes lose track of their own conclusions. You have probably seen this yourself: an AI gives you a great answer, then undermines it two paragraphs later.</p><p><strong>Models do not check every option.</strong> My tool evaluates all 24 hours across every timezone combination. Models pick a &#8220;reasonable&#8221; slot based on reasoning, which usually means they settle for a good-enough answer instead of finding the best one. Only 2 out of 11 models found the optimal slot.</p><p><strong>Daylight saving transitions are a trap.</strong> March 23, 2026 falls six days before London switches from GMT to British Summer Time. One model, ZAI GLM-5, assumed London was already in BST. That one-hour error cascaded through every time it calculated, dropping its score to 53. If you have ever shown up an hour early or late to a meeting after a clock change, you know the feeling. AI models make the same mistake.</p><h2>What the tool does differently</h2><p>The Timezone Scheduler takes a computational approach. It evaluates every hour of the day across all participant timezones. Business hours (9 AM to 5 PM) score 3 points per participant. Extended hours (7-9 AM or 5-9 PM) score 1 point. Off hours score zero.</p><p>When multiple slots tie on business-hours score, a comfort tie-breaker kicks in. The idea is simple: 3 AM is the worst possible meeting time for anyone. The tool measures how far the worst-off participant is from 3 AM. The farther from 3 AM, the better. This is why the tool picks 11 PM Brisbane over midnight Brisbane. Both score 6/9, but 11 PM is farther from &#8220;miserable o&#8217;clock&#8221; and therefore a more civil hour.</p><p>The tool evaluates every possible slot deterministically. It does not reason about trade-offs and drift toward a plausible answer. It scores them all and returns the best one.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hZV1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hZV1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 424w, https://substackcdn.com/image/fetch/$s_!hZV1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 848w, https://substackcdn.com/image/fetch/$s_!hZV1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 1272w, https://substackcdn.com/image/fetch/$s_!hZV1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hZV1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png" width="1456" height="1721" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1721,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:99634,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193042702?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hZV1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 424w, https://substackcdn.com/image/fetch/$s_!hZV1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 848w, https://substackcdn.com/image/fetch/$s_!hZV1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 1272w, https://substackcdn.com/image/fetch/$s_!hZV1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65af2d10-1b63-435d-82f3-d4a60b7b3f7d_2012x2378.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GhC6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GhC6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 424w, https://substackcdn.com/image/fetch/$s_!GhC6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 848w, https://substackcdn.com/image/fetch/$s_!GhC6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 1272w, https://substackcdn.com/image/fetch/$s_!GhC6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GhC6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png" width="1456" height="1905" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1905,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:122912,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/193042702?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GhC6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 424w, https://substackcdn.com/image/fetch/$s_!GhC6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 848w, https://substackcdn.com/image/fetch/$s_!GhC6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 1272w, https://substackcdn.com/image/fetch/$s_!GhC6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff71c212e-9e54-43e0-a293-649579105aa7_2036x2664.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>How it is built</h2><p>If you are not a developer, here is the short version: the tool runs entirely in the cloud on Cloudflare&#8217;s edge network, loads fast anywhere in the world, and uses the browser&#8217;s own built-in timezone data instead of relying on third-party libraries. The technical details follow for those who want them.</p><p>The architecture is deliberately simple. A Cloudflare Worker (a serverless function that runs at the network edge, close to users) handles all API logic in TypeScript. The frontend is vanilla JavaScript with no framework, styled with Tailwind CSS v4, and bundled by Vite. No React, no Vue, no Angular. For a tool this focused, a framework would add complexity without adding value.</p><pre><code><code>Browser (Vanilla JS)              Cloudflare Worker (TypeScript)
--------------------              --------------------------------
app.js      (orchestrator)        GET      /api/timezone-data
timezone-data.js (Intl utils)     GET      /api/search
map.js      (SVG world map)       GET|POST /schedule/api  (schedule|suggest|search)
scheduler.js (meeting grid)       GET      /schedule/api/schema  (OpenAPI 3.1)
styles.css  (Tailwind v4)         GET|POST /schedule/api/mcp  (MCP server)
</code></code></pre><p>Zero external timezone libraries. The browser&#8217;s native <code>Intl.DateTimeFormat</code> API handles everything: offset calculations, daylight saving transitions, locale-aware formatting. This matters because the timezone data comes from the same database your operating system uses, so it is always up to date without needing to ship or maintain a separate library.</p><p>The meeting scheduler displays a 24-hour comparison grid, color-coded so you can see at a glance which hours are business hours, which are early/late but workable, and which are off hours. You can share a meeting link that preserves the full state (cities, date, time), generate a QR code, or export directly to Google Calendar, Outlook, or any calendar app. It also works offline as a PWA (Progressive Web App, meaning you can install it on your phone like a native app). Deployment is on Cloudflare Workers with automatic deploys through GitHub Actions.</p><h2>Seven days, 117 commits</h2><p>The Timezone Scheduler went from zero to production in seven days. The git history tells the real story of AI-assisted development, not a clean arc from idea to launch, but a messy, iterative process where features, fixes, and rethinks pile up fast.</p><p><strong>Day 1 (March 21): From nothing to live, 19 commits.</strong> By end of day, the tool was deployed on a custom domain with a working world map, city search, 24-hour scheduler grid, light/dark theme, and responsive layout. A functional timezone tool, built and shipped in one day.</p><p><strong>Day 2 (March 22): Making it shareable, 11 commits.</strong> I realized I needed to let people send each other meeting links, so shareable URLs landed. Then scope grew: support for comparing up to 6 cities at once, with an interface that lets you add and remove cities as tags. An accessibility audit made the tool usable for screen readers.</p><p><strong>Day 3 (March 23): Teaching AI to use the tool, 15 commits.</strong> This was the pivotal day. One commit added 837 lines of code that turned the scheduler into an API that AI assistants can call directly (more on this in &#8220;The deeper angle&#8221; below). The same day, the comfort tie-breaker landed after the benchmark revealed the tool was picking midnight over 11 PM. I also spent several commits refining the instructions that teach AI models how to call the API correctly on their first try.</p><p><strong>Day 4 (March 24): Locking it down, 40 commits.</strong> The single busiest day. I added code quality tools (automated formatting, pre-commit checks), a CI/CD pipeline (code gets tested and deployed automatically on every change), and automated dependency updates. Then I ran two rounds of security audits (OWASP methodology), finding and fixing injection vulnerabilities, rate limit bypasses, and cross-site scripting risks. I also built a demo video pipeline using Remotion (a React-based video framework) with custom Claude Code skills to guide the animations and sequencing.</p><p><strong>Days 5-7 (March 25-27): Real-world bugs, 30 commits.</strong> Three more security audit rounds, each catching issues the previous round missed. Offline support so the tool works without an internet connection. An <code>ai-image-creator</code> skill for generating all project images through AI. An automated test suite of 38 tests against the live site. And two bugs that only surfaced when real users tried the tool: a share link that lost its data on load, and a single-character typo that showed wrong times for anyone outside my timezone.</p><p>Every one of those 117 commits was a conversation with Claude Code.</p><h2>How Claude Code built it</h2><p>The whole project was built through back-and-forth sessions with Claude Code. I described what I wanted in plain English, Claude Code wrote the code, and we iterated together. I used a &#8220;memory bank&#8221; system (a set of project files that Claude Code reads at the start of each session) to keep context persistent, so it remembered previous decisions instead of starting fresh every time.</p><p>The irony is not lost on me. The tool that scored 100/100 against 11 AI models was itself written by an AI. But Claude Code was not reasoning about timezone math the way those models were. It was writing deterministic code to evaluate every possibility. That is a fundamentally different task. When I asked Claude Code to implement the scoring algorithm, it produced code that loops through 24 hours, calculates each participant&#8217;s local time using the <code>Intl</code> API, and sums the scores. No reasoning shortcuts, no &#8220;this seems reasonable.&#8221; Just exhaustive evaluation.</p><p>The Day 3 API commit is the clearest example of what this workflow looks like at full speed. That single session produced the scheduling API with three actions (schedule, suggest, search), an OpenAPI spec (a machine-readable description of the API), an MCP server (Model Context Protocol, a standard that lets AI tools connect to external services), and an <code>llms.txt</code> file (plain-text instructions that teach AI models how to use the API). All 837 lines. Before implementation, I used my <code>consult-codex</code> skill to send Claude Code&#8217;s plan to OpenAI Codex CLI (running GPT-5.4) for a second opinion. The dual-AI review caught things neither would have caught alone. The resulting security hardening included encoding fixes for non-English city names, caching that cut redundant timezone calculations from ~3,500 to ~50, request size limits, and daylight saving gap detection.</p><p>Three parts of my Claude Code setup made this kind of velocity possible:</p><ul><li><p><strong>Claude Code auto memory</strong> &#8211; Claude Code has a built-in memory system that accumulates knowledge across sessions automatically. As I worked, it saved notes for itself: build commands, debugging insights, architecture patterns, code style preferences. On top of that, my CLAUDE.md memory bank files give it explicit project instructions. Between the two, each session starts with full contextual awareness of what has been built and decided so far.</p></li><li><p><strong>Context7 MCP</strong> &#8211; Connects Claude Code to live library documentation. Instead of guessing how a browser API works based on its training data, Claude Code reads the current official docs.</p></li><li><p><strong>Cloudflare Docs MCP</strong> &#8211; Does the same for Cloudflare&#8217;s platform, pulling deployment rules and API behavior from Cloudflare&#8217;s own documentation rather than inferring.</p></li></ul><p>Beyond these integrations, I built project-specific Claude Code skills that extended what Claude Code could do on its own. Each was built with Claude Code and stored in the repo&#8217;s <code>.claude/skills/</code> directory, so any future session picks them up automatically:</p><ul><li><p><code>consult-codex</code> &#8211; Sends Claude Code&#8217;s plan to a second AI (OpenAI&#8217;s Codex) for a second opinion before implementation starts. Where Codex flagged something differently, I had a real trade-off to evaluate rather than a single AI perspective to accept or reject. Read about my <a href="https://ai.georgeliu.com/p/how-to-get-a-second-ai-opinion-in">Claude Code /consult-codex and /consult-zai skills</a>.</p></li><li><p><code>ai-image-creator</code> &#8211; Generates web app images and site graphics through multiple AI models (Gemini, FLUX.2, SeedDream, GPT-5 Image) via OpenRouter&#8217;s API, proxied through Cloudflare AI Gateway for monitoring and cost control. Supports transparent backgrounds, reference image editing, and per-project cost tracking. Every image on the site was generated this way. Read about my <a href="https://ai.georgeliu.com/p/building-an-ai-image-creator-skill">Claude Code ai-image-creator skill</a>.</p></li><li><p><code>create-demo-video</code> and <code>remotion-best-practices</code> &#8211; Produces polished MP4 demo videos using Remotion, a React-based video framework. Automated Playwright screen captures are composed into desktop and mobile formats with transitions and animated cursors. You can see the results at <a href="https://timezones.centminmod.com/demo-videos">timezones.centminmod.com/demo-videos</a>.</p></li><li><p><code>timezone-tests</code> &#8211; Runs 29 CLI tests plus 9 Chrome DevTools visual tests against the live site, covering every API endpoint, security header, share link hydration, and PWA asset. This is the skill that caught the share link and sign error bugs described below.</p></li><li><p><strong>Claude Code starter template</strong> &#8211; The <code>consult-codex</code> skill and the memory bank system are part of my reusable Claude Code starter template, which I have shared publicly at <a href="https://github.com/centminmod/my-claude-code-setup">github.com/centminmod/my-claude-code-setup</a>. I will cover that setup in detail in a future post.</p></li></ul><h2>What did not work</h2><p><strong>The tie-breaker gap.</strong> The first version scored 98/100, not 100. When two slots tied on business hours, the tool picked midnight Brisbane over 11 PM Brisbane. Both score 6/9, but midnight is objectively worse. Two models (Gemini 3.1 Pro and Grok 4.1 Fast) had independently chosen 11 PM. I added a comfort scoring layer that measures how far the worst-off participant is from 3 AM on a circular scale. The tool improved because I benchmarked it against real alternatives and found a case it was not handling well.</p><p><strong>The invisible sign error.</strong> Six days after launch, I found that meeting times displayed wrong when your browser timezone differed from the selected reference city. The fix was a single character: <code>-</code> to <code>+</code> in the offset correction. The bug was invisible during all my testing because my browser was set to Brisbane and I was testing with Brisbane as the reference. A one-character mistake that only surfaced when a real user in a different timezone tried it.</p><p><strong>Share link loading order.</strong> When you open a shared meeting link, the tool needs to restore the saved cities and the saved time. The bug: the code restored the time first, then added the cities. But adding a city resets the page, which wiped the time that was just restored. The fix was doing it in the right order: cities first, then time. Obvious in retrospect, but it took tracing through the exact sequence of page updates to find.</p><p><strong>Five rounds of security audits.</strong> I used Claude Code to run structured security audits (following OWASP methodology, the industry-standard checklist for web application security) across Days 4-7. Each round found issues the previous round missed. Round 1 caught ways an attacker could inject malicious input. Round 2 found a way to bypass rate limits on the API. Round 3 caught a timing issue in how the server tracked requests. Round 4 found that the Round 3 fix had accidentally broken how share links handle minutes. Round 5 hardened error messages and filtered special characters in calendar exports. The pattern was clear: each fix could introduce a new edge case, and no single audit pass was sufficient.</p><p>None of these bugs were caught during the initial Claude Code build sessions. They surfaced through adversarial testing, real-world usage in different timezones, and deliberate security audits. AI-assisted development is fast, but the testing and hardening phase is where quality actually gets built in.</p><h2>The deeper angle: building tools that AI can call</h2><p>Here is what makes this more than a scheduling story. The Timezone Scheduler is not just for humans. The API is designed so AI assistants can use it too:</p><ul><li><p><code>/llms.txt</code> &#8211; A plain-text file that teaches AI models how to call the API on their first attempt. It front-loads parameter names (<code>from</code>/<code>to</code>, not <code>source</code>/<code>destination</code>), a GET URL template with placeholders, a city reference table with 33 entries, and GET vs POST syntax differences. I refined this across four commits after testing how models actually called the API. The biggest improvement was putting parameter names and the URL template above the examples, because models were copying example city names instead of substituting user input.</p></li><li><p><code>/schedule/api/schema</code> &#8211; An OpenAPI 3.1 spec that any tool-using AI can consume programmatically. It describes the three API actions (schedule, suggest, search), request/response shapes, and scoring fields. Tools like Copilot and Gemini CLI can auto-generate client calls from this spec without reading prose documentation.</p></li><li><p><code>/schedule/api/mcp</code> &#8211; A real MCP (Model Context Protocol) streamable-HTTP server implementing JSON-RPC 2.0 with zero external dependencies. It supports <code>initialize</code>, <code>tools/list</code>, <code>tools/call</code>, and <code>ping</code>. Claude Code, Codex, Gemini CLI, OpenCode, and Kilo CLI can all register it as a tool server. The <code>llms.txt</code> file includes ready-to-paste MCP config blocks for each of these AI coding tools.</p></li></ul><p>When a user asks any of these tools &#8220;find the best meeting time for Brisbane, New York, and London,&#8221; the AI handles natural language understanding and the Timezone Scheduler handles the precision. The AI gets 100/100 because it calls the API instead of doing the computation itself.</p><p>This pattern applies beyond timezone scheduling. Tax calculations where rules vary by jurisdiction. Unit conversions where precision matters. Compliance checks where the criteria are enumerable. Any domain where &#8220;good enough reasoning&#8221; is not actually good enough is a candidate for a purpose-built tool that AI can call.</p><h2>What I learned</h2><ol><li><p><strong>Benchmark before you trust.</strong> AI models sound confident even when they pick suboptimal answers. Test them on tasks with verifiable correct answers. You might be surprised how often &#8220;good enough&#8221; reasoning misses the best solution.</p></li><li><p><strong>Build tools for the precision layer.</strong> If your domain has clear scoring criteria and the solution space is enumerable, a dedicated algorithm will outperform general-purpose AI consistently. Keep the tool focused and let AI handle everything around it.</p></li><li><p><strong>Make your tools AI-consumable.</strong> The biggest leverage is not choosing between AI and tools. It is building tools that AI can call. Add llms.txt, OpenAPI, or MCP support and your tool becomes part of every AI workflow that touches your domain.</p></li></ol><h2>What&#8217;s next</h2><p>I am planning a follow-up that goes deeper on how the Timezone Scheduler was actually built: the architecture decisions, the Claude Code workflow, and the specific parts where the back-and-forth with AI made a real difference. If that is useful to you, subscribe so you do not miss it.</p><p>The tool is free at <a href="https://timezones.centminmod.com">timezones.centminmod.com</a>. The full benchmark is at <a href="https://github.com/centminmod/timezone-scheduler">github.com/centminmod/timezone-scheduler</a>.</p><div><hr></div><p>If you&#8217;re interested in practical AI building for web apps, developer workflows, and infrastructure, subscribe for future posts. You can also follow my shorter updates on <a href="https://www.threads.com/@george_sl_liu">Threads (@george_sl_liu)</a> and <a href="https://bsky.app/profile/georgesl.bsky.social">Bluesky (@georgesl.bsky.social)</a> or subscribe and follow along.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://buymeacoffee.com/georgeliu&quot;,&quot;text&quot;:&quot;Buy Me A Coffee&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://buymeacoffee.com/georgeliu"><span>Buy Me A Coffee</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Why I Started This Substack]]></title><description><![CDATA[I&#8217;ve spent the last 16 years building and maintaining Centmin Mod, a LEMP stack tooling ecosystem for Linux web server environments.]]></description><link>https://ai.georgeliu.com/p/why-i-started-this-substack</link><guid isPermaLink="false">https://ai.georgeliu.com/p/why-i-started-this-substack</guid><dc:creator><![CDATA[George Liu]]></dc:creator><pubDate>Sat, 04 Apr 2026 15:44:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-uCH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve spent the last 16 years building and maintaining <a href="https://centminmod.com/">Centmin Mod</a>, a LEMP stack tooling ecosystem for Linux web server environments. I have consulted for some of the largest online community forum sites using vBulletin and Xenforo for optimising their servers for scalability and performance. Squeezing out every ounce of performance from servers. I am obsessed with server performance and pagespeed optimisations and after using Cloudflare for over a decade, I officially became a Cloudflare Community MVP in 2018 till now with extensive experience using all Cloudflare free, pro, business and enterprise plans. Before that, my obsession was PC overclocking and PC hardware reviews. The thread through all of it: always looking for better ways to improve existing solutions.</p><p>That is why I started this Substack.</p><p>I want to document how I&#8217;m using AI to build useful web apps, improve developer workflows, and explore better tools and systems. Here I&#8217;ll share what I&#8217;m building, the workflows and tools I use, and an honest look at what works and what doesn&#8217;t.</p><p>I want to show more than finished results. I want to share the process too.</p><p>What I tried. What changed. What worked. What didn&#8217;t.</p><p>Less hype, more implementation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-uCH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-uCH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!-uCH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!-uCH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!-uCH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-uCH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png" width="640" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:251277,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://georgeliuoz.substack.com/i/192902283?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-uCH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!-uCH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!-uCH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!-uCH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6915073d-1f2f-4380-b7a1-52ef2addf5c8_640x640.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I started my AI journey like most folks with the launch of OpenAI ChatGPT website - almost immediate jumping on ChatGPT Plus $20/month paid subscription when it came out. Then it was Claude Pro $20/month and Gemini AI Pro $20/month subscriptions. </p><p>Most of it was for coding first. I have a lot of ideas and devops tooling that was on the back burner and AI chat helped out. AI helped fill in knowledge gaps I had and gave me deeper insights as I asked more questions. I would always get each respectively LLM model response to evaluate each others&#8217; responses to improve the final product. Unfortunately, running into token context limits back then for both input tokens and output tokens. LLM models around then were below 128K tokens with 4-8K max output tokens. This multi-AI consultation process would eventually lead me to creating my <a href="https://ai.georgeliu.com/p/how-to-get-a-second-ai-opinion-in">Claude Code /consult-codex</a> and <a href="https://ai.georgeliu.com/p/how-to-get-a-second-ai-opinion-in">/consult-zai</a> Skills.</p><p>I was still using my trusty Sublime Text editor I tried Visual Studio Code before, but never really took to it. But each time I tried Visual Studio Code, I would eventually return back to the Sublime Text editor as I was used to key mappings and shortcuts. </p><p>It would probably was several months until January-February 2024, that I decide to try Cline in Visual Studio Code. I paired Cline with OpenRouters API burning up tokens. If I recall correctly, the model most use on OpenRouters at that time, was the Google Gemini models. This was a game changer, as I no longer had to be stuck with the context limits within the web chat AI interface and that there was more context information that could provide the AI models.</p><p>From March 2024 to June 2025, I was using the Claude Pro $20/month plan. Up until this point, I was only using the Claude Plan within the web chat interface. Claude Code had been launched in February 2025, but I still haven&#8217;t used it yet. </p><p>One of my earlier AI coded projects using Cline and Gemini 2.0 models was my OpenRouter API python script, <a href="https://github.com/centminmod/or-cli">or-cli.py</a> back in February 2025 when I discovered the wonderful OpenRouter AI API provided a lot of free LLM model usage. I took advantage of that Google Gemini LLM model usage a lot and dabbled for the first time into Cloudflare AI Gateways.</p><p>That changed in March 2025 when I decided to use Claude Code within Visual Studio Code. This would be the game-changing moment, as I decided to focus and go all in on learning Claude Code. My Claude Code usage grew, and by July 2025 I had upgraded to the Claude max $100/month plan and I have been on this plan ever since (~10 months).</p><p>It was around this time I created my <a href="https://github.com/centminmod/my-claude-code-setup">Claude Code starter template GitHub repository</a> to document and share my own Claude Code journey with others which includes custom CLAUDE.md memory bank system modeled after Cline&#8217;s memory bank system which I had gotten use to. Then I added my Claude Code skills, agents/commands and knowledge to the repo which has grown to nearly 2.2K GitHub stars and consistently averages 1,500 to 3,000 visitors per week!</p><p>I had gotten used to Visual Studio Code and no longer use the Sublime Text editor. This meant that I also got to play with other Visual Studio Code AI extensions, including Kilo Code, OpenAI Codex CLI, Google Gemini CLI, and OpenCode. Kilo Code clearly is one of my top favourites behind Claude Code and Codex CLI.</p><p>Also as an open source maintainer of my Centmin Mod LEMP stack that is GPLv3 licensed, I also got free access to <a href="https://github.com/features/copilot/plans">GitHub Copilot Pro plan</a>, which normally costs $10/month. </p><p>So right now my AI is spend includes:</p><ul><li><p>Claude AI Max US$100/month + 10% Australian GST</p></li><li><p>OpenAI ChatGPT Plus US$20/month + 10% Australian GST</p></li><li><p>Google Gemini AI Pro US$20 month + 10% Australian GST</p></li><li><p>GitHub CoPilot Pro US$10/month + 10% Australian GST currently free</p></li><li><p><a href="http://&#128640; You&#8217;ve been invited to join the GLM Coding Plan! Enjoy full support for Claude Code, Cline, and 20+ top coding tools &#8212; starting at just $10/month. Subscribe now and grab the limited-time deal!  &#128073;Join now: https://z.ai/subscribe?ic=WWB8IFLROM">ZAI GLM Coding plan</a> US$129/year (64% discount) + 10% Australian GST</p></li><li><p>t3.chat US$8/month + 10% Australian GST</p></li></ul><p>Currently, my AI coding setup just migrated from multiple iTerm2/tmux terminals + Visual Studio Code to using <a href="https://www.warp.dev/">warp.dev</a> just as a multiple window/tab terminal without the AI bells and whistles. I also have Claude and multi-AI CLI VSC dev containers using Debian Docker images that I documented at <a href="https://claude-devcontainers.centminmod.com/">https://claude-devcontainers.centminmod.com</a>.</p><p>Just a few days ago revisited voice dictation as well, currently using <a href="https://wisprflow.ai/r?GEORGE45291">Wispr Flow</a>. Folks can get a <a href="https://wisprflow.ai/r?GEORGE45291">free month of Wispr Flow Pro</a> using my <a href="https://wisprflow.ai/r?GEORGE45291">invitation link</a>.</p><p>I hope this gives readers, a bit of background of my AI journey to date.</p><p>The next post is a concrete example: I tested <a href="https://ai.georgeliu.com/p/why-11-ai-models-got-timezone-scheduling">11 AI models on timezone scheduling</a>, none of them got it right, so I built something that did.</p><p>If you&#8217;re interested in practical AI building for web apps, developer workflows, and infrastructure, subscribe for future posts. You can also follow my shorter updates on <a href="https://www.threads.com/@george_sl_liu">Threads (@george_sl_liu)</a> and <a href="https://bsky.app/profile/georgesl.bsky.social">Bluesky (@georgesl.bsky.social)</a> or subscribe and follow along.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://buymeacoffee.com/georgeliu&quot;,&quot;text&quot;:&quot;Buy Me A Coffee&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://buymeacoffee.com/georgeliu"><span>Buy Me A Coffee</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ai.georgeliu.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item></channel></rss>