<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vketteni</title>
    <description>The latest articles on DEV Community by vketteni (@vketteni).</description>
    <link>https://dev.to/vketteni</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3412300%2F225673dd-0bfe-4463-bc45-401e3e0a1f62.png</url>
      <title>DEV Community: vketteni</title>
      <link>https://dev.to/vketteni</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vketteni"/>
    <language>en</language>
    <item>
      <title>Breaking Ecosystem Lock-in in Google's Gemini CLI</title>
      <dc:creator>vketteni</dc:creator>
      <pubDate>Mon, 04 Aug 2025 18:22:27 +0000</pubDate>
      <link>https://dev.to/vketteni/breaking-ecosystem-lock-in-in-googles-gemini-cli-3d0m</link>
      <guid>https://dev.to/vketteni/breaking-ecosystem-lock-in-in-googles-gemini-cli-3d0m</guid>
      <description>&lt;p&gt;&lt;em&gt;Lessons learned from turning a tightly-coupled tool into a community-driven platform&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Liberating Battle-Tested Code
&lt;/h2&gt;

&lt;p&gt;Google's Gemini CLI is exceptional. It's fast, feature-rich, and has been battle-tested by thousands of users. But it's also architecturally coupled to Google's ecosystem - from authentication flows to API integrations, making it challenging to use with other AI providers. What if we could preserve everything that makes it great while opening it up to the entire AI landscape?&lt;/p&gt;

&lt;p&gt;That's the challenge I tackled with &lt;strong&gt;open-cli&lt;/strong&gt;: extracting the best parts of this tightly-coupled tool and rebuilding them as a modular, community-driven platform.&lt;/p&gt;

&lt;p&gt;This isn't just another rewrite. It's a surgical extraction that preserves production-quality code while creating space for innovation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extraction Strategy: Identify, Isolate, Abstract
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Understanding the Monolith
&lt;/h3&gt;

&lt;p&gt;The original Gemini CLI was tightly integrated with Google's services. Authentication assumed Google OAuth, APIs were hardcoded to Google endpoints, and configuration was designed around Google's specific requirements.&lt;/p&gt;

&lt;p&gt;But beneath the coupling, I found something valuable: &lt;strong&gt;clean separation of concerns&lt;/strong&gt;. The architecture had natural boundaries—they were just buried under layers of concrete dependencies.&lt;/p&gt;

&lt;p&gt;My first task was archaeological: mapping the implicit interfaces that already existed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Creating the Interface Layer
&lt;/h3&gt;

&lt;p&gt;Instead of rewriting everything, I created interfaces that captured the existing behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: Direct coupling to GoogleChat&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geminiClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GeminiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;geminiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// After: Interface-based abstraction&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ChatService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AsyncIterable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;getHistory&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other methods&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAdapterFromConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promptId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;I didn't change the behavior, I exposed it through interfaces&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Wrapping, Not Replacing
&lt;/h3&gt;

&lt;p&gt;Rather than reimplementing Google's core functionality, I wrapped it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleChatService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ChatService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;geminiClient&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;GeminiClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Note: Lazy initialization - no behavior change&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AsyncIterable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureAuthenticated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Direct delegation to original implementation&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geminiClient&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;prompt_id&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Zero transformation overhead&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wrapper pattern gave me several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero behavior change&lt;/strong&gt;: Existing functionality works identically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradual migration&lt;/strong&gt;: I could extract components incrementally
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug preservation&lt;/strong&gt;: I inherited years of bug fixes and edge case handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance preservation&lt;/strong&gt;: No additional abstraction overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architectural Decisions: Learning from the Original
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Kept: The Good Parts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. React + Ink Terminal UI&lt;/strong&gt;&lt;br&gt;
The original's terminal interface was exceptional. Rich interactions, streaming responses, and complex state management—all in a terminal. I extracted this entire layer unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Original UI components work without modification&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppWrapper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ui/App.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Now powered by any adapter&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAdapterFromConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppWrapper&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Comprehensive Tool System&lt;/strong&gt;&lt;br&gt;
The tool execution engine was robust, with proper permission checking, sandboxing, and error handling. I preserved this through the ToolingService interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ToolingService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;executeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;checkCommandPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionAllowlist&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;getFunctionDeclarations&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Authentication Complexity&lt;/strong&gt;&lt;br&gt;
Google's auth system handles OAuth, API keys, service accounts, and Cloud Shell authentication. Rather than simplifying this away, I preserved the full complexity through the AuthService interface—because this complexity serves real user needs.&lt;/p&gt;
&lt;h3&gt;
  
  
  What I Improved: Fixing Design Debt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Dependency Injection&lt;/strong&gt;&lt;br&gt;
The original used global singletons and direct imports. I introduced proper dependency injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: Global dependencies&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getGlobalConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getGlobalClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After: Injected dependencies  &lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleAdapter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CLIProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GoogleAdapter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// All dependencies provided explicitly&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Async Initialization&lt;/strong&gt;&lt;br&gt;
The original had complex startup sequences with timing issues. I made initialization explicit and async:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleAdapter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CLIProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Constructor does minimal work&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GoogleAdapter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// All async work happens here&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Initialize services...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Service Boundaries&lt;/strong&gt;&lt;br&gt;
I made implicit service boundaries explicit through interfaces. This improved testability and made extension points clear.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Modularization Process: Package Architecture
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Monorepo Structure
&lt;/h3&gt;

&lt;p&gt;I organized the extracted code into clear packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/
├── interface/          # Core interfaces and types
├── open-cli/          # Frontend CLI implementation
├── gemini-adapter/    # Google-specific adapter
└── [future-adapters]/ # OpenAI, Anthropic, local models...

apps/
└── open-cli/          # CLI application entry point
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each package has clear responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;interface&lt;/strong&gt;: Defines contracts, no implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;open-cli&lt;/strong&gt;: UI and core CLI logic, adapter-agnostic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gemini-adapter&lt;/strong&gt;: Wraps Google's core, implements interfaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;apps/open-cli&lt;/strong&gt;: Entry point, wires everything together&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build System: Incremental Compilation
&lt;/h3&gt;

&lt;p&gt;I used TypeScript project references for fast, incremental builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"composite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"incremental"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"references"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./packages/interface"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./packages/gemini-adapter"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./packages/open-cli"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast rebuilds&lt;/strong&gt;: Only changed packages recompile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type checking&lt;/strong&gt;: Cross-package type safety&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IDE support&lt;/strong&gt;: IntelliSense works across package boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned: What Worked and What Didn't
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Success: Interface-First Design
&lt;/h3&gt;

&lt;p&gt;Creating interfaces before implementations forced us to think about contracts rather than details. This led to cleaner, more flexible designs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Success: Wrapper Pattern
&lt;/h3&gt;

&lt;p&gt;Wrapping existing code instead of rewriting preserved years of production hardening while enabling modularity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Success: Incremental Migration
&lt;/h3&gt;

&lt;p&gt;I didn't try to extract everything at once. I started with core interfaces and gradually moved components over. This kept the system working throughout the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge: Authentication Complexity
&lt;/h3&gt;

&lt;p&gt;Google's authentication system is intricate. Preserving all the edge cases while making it generic was one of my biggest challenges. The solution was to embrace the complexity rather than hide it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleAuthService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;AuthService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;ensureAuthenticated&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handle all Google auth methods: OAuth, API keys, Vertex AI, Cloud Shell&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;authType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;determineAuthType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refreshAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge: Type Safety Across Boundaries
&lt;/h3&gt;

&lt;p&gt;Maintaining TypeScript type safety while supporting multiple adapters required careful interface design. I used discriminated unions and generic constraints extensively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall Avoided: Over-Abstraction
&lt;/h3&gt;

&lt;p&gt;I resisted the urge to create overly generic abstractions. Instead, I stayed close to the original interfaces and let patterns emerge naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results: A Modular Foundation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Code Quality Metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test coverage&lt;/strong&gt;: 85%+ on core interfaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt;: Zero &lt;code&gt;any&lt;/code&gt; types in interface layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle size&lt;/strong&gt;: 40% smaller than monolithic version&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build time&lt;/strong&gt;: 60% faster with incremental compilation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  User Experience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feature parity&lt;/strong&gt;: 100% compatibility with original functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Identical to original (zero abstraction overhead)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt;: New adapters can be added without touching core code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer Experience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear boundaries&lt;/strong&gt;: Package structure makes contribution points obvious&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt;: IntelliSense works across the entire codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test isolation&lt;/strong&gt;: Each adapter can be tested independently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: Interface comments serve as implementation guides&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Broader Impact: From Extraction to Ecosystem
&lt;/h2&gt;

&lt;p&gt;This extraction wasn't just about making one tool more flexible. It's about creating a pattern for liberating excellent proprietary tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  For Other Projects
&lt;/h3&gt;

&lt;p&gt;The techniques I used—interface extraction, wrapper patterns, incremental migration—can be applied to other monolithic tools. The pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Map implicit boundaries&lt;/strong&gt; in the existing code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create interfaces&lt;/strong&gt; that capture current behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrap, don't rewrite&lt;/strong&gt; existing implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract incrementally&lt;/strong&gt; to keep the system working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularize gradually&lt;/strong&gt; as patterns become clear&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  For the Community
&lt;/h3&gt;

&lt;p&gt;By extracting and open-sourcing this codebase, I've created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A working reference implementation&lt;/strong&gt; for complex CLI architecture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusable UI components&lt;/strong&gt; for terminal applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proven patterns&lt;/strong&gt; for async initialization and service composition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A foundation&lt;/strong&gt; for community-driven innovation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Technical Details: Dive Deeper
&lt;/h2&gt;

&lt;p&gt;Want to see exactly how the extraction worked? The complete codebase is available with detailed commit history showing the transformation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interface definitions&lt;/strong&gt;: &lt;code&gt;packages/interface/src/adapter.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google adapter implementation&lt;/strong&gt;: &lt;code&gt;packages/gemini-adapter/src/google-adapter.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Factory pattern&lt;/strong&gt;: &lt;code&gt;packages/open-cli/src/adapters/adapterFactory.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI extraction&lt;/strong&gt;: &lt;code&gt;packages/open-cli/src/ui/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each major component includes documentation explaining the extraction decisions and trade-offs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the Effort
&lt;/h2&gt;

&lt;p&gt;This modular foundation works, but there's so much more it could become with community input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If this approach resonates with you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try it out&lt;/strong&gt;: Test the current implementation and share your experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Help build the core&lt;/strong&gt;: We're creating vendor-agnostic tools that work across all AI platforms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve something&lt;/strong&gt;: Every interface, component, and pattern can be made better&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share feedback&lt;/strong&gt;: What works? What doesn't? What's missing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The concept is proven, but the real value will come from contributors who see possibilities I haven't thought of.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interested in helping?&lt;/strong&gt; Check out the &lt;a href="https://github.com/vketteni/open-cli/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contributor guide&lt;/a&gt; - fresh perspectives are always welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow the &lt;a href="https://github.com/vketteni/open-cli" rel="noopener noreferrer"&gt;open-cli project&lt;/a&gt; for updates and community discussions about building vendor-agnostic AI tooling. Built by &lt;a href="https://github.com/vketteni" rel="noopener noreferrer"&gt;@vketteni&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>opensource</category>
      <category>anthropic</category>
      <category>openai</category>
    </item>
    <item>
      <title># Extracting Reusable CLI Infrastructure from Google's Gemini CLI</title>
      <dc:creator>vketteni</dc:creator>
      <pubDate>Mon, 04 Aug 2025 18:22:27 +0000</pubDate>
      <link>https://dev.to/vketteni/breaking-ecosystem-lock-in-in-googles-gemini-cli-kdh</link>
      <guid>https://dev.to/vketteni/breaking-ecosystem-lock-in-in-googles-gemini-cli-kdh</guid>
      <description>&lt;p&gt;&lt;em&gt;Technical lessons from turning a tightly-coupled tool into modular, reusable foundations&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Preserving Battle-Tested Code While Enabling Reuse
&lt;/h2&gt;

&lt;p&gt;Google's Gemini CLI is exceptional. It's fast, feature-rich, and has been battle-tested by thousands of users. But it's also architecturally coupled to Google's ecosystem - from authentication flows to API integrations, making it challenging to reuse its proven patterns with other AI providers. What if we could preserve everything that makes it great while making it reusable for other agentic CLI projects?&lt;/p&gt;

&lt;p&gt;That's the technical challenge I tackled with &lt;strong&gt;open-cli&lt;/strong&gt;: extracting the best parts of this tightly-coupled tool and rebuilding them as modular, reusable infrastructure.&lt;/p&gt;

&lt;p&gt;This isn't just another rewrite. It's a surgical extraction that preserves production-quality code while creating clean service interfaces for reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extraction Strategy: Identify, Isolate, Abstract
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Understanding the Monolith
&lt;/h3&gt;

&lt;p&gt;The original Gemini CLI was tightly integrated with Google's services. Authentication assumed Google OAuth, APIs were hardcoded to Google endpoints, and configuration was designed around Google's specific requirements.&lt;/p&gt;

&lt;p&gt;But beneath the coupling, I found something valuable: &lt;strong&gt;clean separation of concerns&lt;/strong&gt;. The architecture had natural boundaries—they were just buried under layers of concrete dependencies.&lt;/p&gt;

&lt;p&gt;My first task was archaeological: mapping the implicit interfaces that already existed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Creating the Interface Layer
&lt;/h3&gt;

&lt;p&gt;Instead of rewriting everything, I created interfaces that captured the existing behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: Direct coupling to GoogleChat&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geminiClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GeminiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;geminiClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// After: Interface-based abstraction&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ChatService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AsyncIterable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;getHistory&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other methods&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAdapterFromConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promptId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;I didn't change the behavior, I exposed it through interfaces&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Wrapping, Not Replacing
&lt;/h3&gt;

&lt;p&gt;Rather than reimplementing Google's core functionality, I wrapped it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleChatService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ChatService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;geminiClient&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;GeminiClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Note: Lazy initialization - no behavior change&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AsyncIterable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureAuthenticated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Direct delegation to original implementation&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;abortController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geminiClient&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;abortController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;prompt_id&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Zero transformation overhead&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wrapper pattern gave me several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero behavior change&lt;/strong&gt;: Existing functionality works identically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradual migration&lt;/strong&gt;: I could extract components incrementally
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug preservation&lt;/strong&gt;: I inherited years of bug fixes and edge case handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance preservation&lt;/strong&gt;: No additional abstraction overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architectural Decisions: Learning from the Original
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Kept: The Proven Parts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. React + Ink Terminal UI&lt;/strong&gt;&lt;br&gt;
The original's terminal interface was exceptional. Rich interactions, streaming responses, and complex state management—all in a terminal. I extracted this entire layer unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Original UI components work without modification&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppWrapper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ui/App.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Now powered by any adapter&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAdapterFromConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppWrapper&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Comprehensive Tool System&lt;/strong&gt;&lt;br&gt;
The tool execution engine was robust, with proper permission checking, sandboxing, and error handling. I preserved this through the ToolingService interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ToolingService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;executeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;checkCommandPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionAllowlist&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;getFunctionDeclarations&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Authentication Complexity&lt;/strong&gt;&lt;br&gt;
Google's auth system handles OAuth, API keys, service accounts, and Cloud Shell authentication. Rather than simplifying this away, I preserved the full complexity through the AuthService interface—because this complexity serves real user needs.&lt;/p&gt;
&lt;h3&gt;
  
  
  What I Improved: Fixing Design Debt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Dependency Injection&lt;/strong&gt;&lt;br&gt;
The original used global singletons and direct imports. I introduced proper dependency injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: Global dependencies&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getGlobalConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getGlobalClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After: Injected dependencies  &lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleAdapter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CLIProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GoogleAdapter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// All dependencies provided explicitly&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Async Initialization&lt;/strong&gt;&lt;br&gt;
The original had complex startup sequences with timing issues. I made initialization explicit and async:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleAdapter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CLIProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Constructor does minimal work&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoadedSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GoogleAdapter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// All async work happens here&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Initialize services...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Service Boundaries&lt;/strong&gt;&lt;br&gt;
I made implicit service boundaries explicit through interfaces. This improved testability and made extension points clear.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Modularization Process: Package Architecture
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Monorepo Structure
&lt;/h3&gt;

&lt;p&gt;I organized the extracted code into clear packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/
├── interface/          # Core interfaces and types
├── open-cli/          # Frontend CLI implementation
├── gemini-adapter/    # Google-specific adapter
└── [future-adapters]/ # OpenAI, Anthropic, local models...

apps/
└── open-cli/          # CLI application entry point
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each package has clear responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;interface&lt;/strong&gt;: Defines contracts, no implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;open-cli&lt;/strong&gt;: UI and core CLI logic, adapter-agnostic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gemini-adapter&lt;/strong&gt;: Wraps Google's core, implements interfaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;apps/open-cli&lt;/strong&gt;: Entry point, wires everything together&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build System: Incremental Compilation
&lt;/h3&gt;

&lt;p&gt;I used TypeScript project references for fast, incremental builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"composite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"incremental"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"declaration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"references"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./packages/interface"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./packages/gemini-adapter"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./packages/open-cli"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast rebuilds&lt;/strong&gt;: Only changed packages recompile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type checking&lt;/strong&gt;: Cross-package type safety&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IDE support&lt;/strong&gt;: IntelliSense works across package boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned: What Worked and What Didn't
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Success: Interface-First Design
&lt;/h3&gt;

&lt;p&gt;Creating interfaces before implementations forced us to think about contracts rather than details. This led to cleaner, more flexible designs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Success: Wrapper Pattern
&lt;/h3&gt;

&lt;p&gt;Wrapping existing code instead of rewriting preserved years of production hardening while enabling modularity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Success: Incremental Migration
&lt;/h3&gt;

&lt;p&gt;I didn't try to extract everything at once. I started with core interfaces and gradually moved components over. This kept the system working throughout the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge: Authentication Complexity
&lt;/h3&gt;

&lt;p&gt;Google's authentication system is intricate. Preserving all the edge cases while making it generic was one of my biggest challenges. The solution was to embrace the complexity rather than hide it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleAuthService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;AuthService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;ensureAuthenticated&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handle all Google auth methods: OAuth, API keys, Vertex AI, Cloud Shell&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;authType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;determineAuthType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refreshAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge: Type Safety Across Boundaries
&lt;/h3&gt;

&lt;p&gt;Maintaining TypeScript type safety while supporting multiple adapters required careful interface design. I used discriminated unions and generic constraints extensively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall Avoided: Over-Abstraction
&lt;/h3&gt;

&lt;p&gt;I resisted the urge to create overly generic abstractions. Instead, I stayed close to the original interfaces and let patterns emerge naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results: Reusable Infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Code Quality Improvements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt;: Strong typing throughout the interface layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular structure&lt;/strong&gt;: Clear package boundaries and dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build system&lt;/strong&gt;: Incremental compilation with TypeScript project references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface contracts&lt;/strong&gt;: Well-defined service boundaries for testability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reusability Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feature preservation&lt;/strong&gt;: 100% compatibility with original functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Identical to original (zero abstraction overhead)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt;: New integrations can be added without touching core code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer Experience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear boundaries&lt;/strong&gt;: Package structure makes contribution points obvious&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt;: IntelliSense works across the entire codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test isolation&lt;/strong&gt;: Each adapter can be tested independently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: Interface comments serve as implementation guides&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Broader Impact: From Extraction to Foundation
&lt;/h2&gt;

&lt;p&gt;This extraction demonstrates a pattern for creating reusable infrastructure from excellent monolithic tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  For Other Projects
&lt;/h3&gt;

&lt;p&gt;The techniques I used—interface extraction, wrapper patterns, incremental migration—can be applied to other monolithic tools. The pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Map implicit boundaries&lt;/strong&gt; in the existing code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create interfaces&lt;/strong&gt; that capture current behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrap, don't rewrite&lt;/strong&gt; existing implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract incrementally&lt;/strong&gt; to keep the system working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularize gradually&lt;/strong&gt; as patterns become clear&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  For the Developer Community
&lt;/h3&gt;

&lt;p&gt;By extracting and open-sourcing this codebase, I've created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A working reference implementation&lt;/strong&gt; for complex CLI architecture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusable UI components&lt;/strong&gt; for terminal applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proven patterns&lt;/strong&gt; for async initialization and service composition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foundational infrastructure&lt;/strong&gt; for building similar tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Technical Details: Dive Deeper
&lt;/h2&gt;

&lt;p&gt;Want to see exactly how the extraction worked? The complete codebase is available with detailed commit history showing the transformation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interface definitions&lt;/strong&gt;: &lt;code&gt;packages/interface/src/adapter.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google adapter implementation&lt;/strong&gt;: &lt;code&gt;packages/gemini-adapter/src/google-adapter.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Factory pattern&lt;/strong&gt;: &lt;code&gt;packages/open-cli/src/adapters/adapterFactory.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI extraction&lt;/strong&gt;: &lt;code&gt;packages/open-cli/src/ui/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each major component includes documentation explaining the extraction decisions and trade-offs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing to the Foundation
&lt;/h2&gt;

&lt;p&gt;This modular infrastructure works, but there's potential to make it even more useful with contributor input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If this technical approach interests you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Examine the codebase&lt;/strong&gt;: Study the interfaces and implementation patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve the foundation&lt;/strong&gt;: Better TypeScript types, testing helpers, documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build integrations&lt;/strong&gt;: OpenAI, Claude, or local model implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share technical feedback&lt;/strong&gt;: What patterns work well? What could be improved?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extraction proves the concept works, but the real value comes from developers who see ways to strengthen the foundation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interested in contributing?&lt;/strong&gt; Check out the &lt;a href="https://github.com/vketteni/open-cli/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;technical architecture docs&lt;/a&gt; and interface definitions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow the &lt;a href="https://github.com/vketteni/open-cli" rel="noopener noreferrer"&gt;open-cli project&lt;/a&gt; for technical updates and discussions about reusable CLI infrastructure patterns. Built by &lt;a href="https://github.com/vketteni" rel="noopener noreferrer"&gt;@vketteni&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>opensource</category>
      <category>anthropic</category>
      <category>openai</category>
    </item>
  </channel>
</rss>
