<?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>Forem: Recca Tsai</title>
    <description>The latest articles on Forem by Recca Tsai (@recca0120).</description>
    <link>https://forem.com/recca0120</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%2F50274%2F427ea12d-13dd-4a0e-8955-dddfbf0a39ea.png</url>
      <title>Forem: Recca Tsai</title>
      <link>https://forem.com/recca0120</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/recca0120"/>
    <language>en</language>
    <item>
      <title>React Compiler 1.0 + Vite 8: The Right Way to Install After @vitejs/plugin-react v6 Drops Babel</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 14 Apr 2026 22:10:33 +0000</pubDate>
      <link>https://forem.com/recca0120/react-compiler-10-vite-8-the-right-way-to-install-after-vitejsplugin-react-v6-drops-babel-p0i</link>
      <guid>https://forem.com/recca0120/react-compiler-10-vite-8-the-right-way-to-install-after-vitejsplugin-react-v6-drops-babel-p0i</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/14/react-compiler-vite-v6/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;React Compiler reached &lt;strong&gt;1.0 stable&lt;/strong&gt; in October 2025 — it auto-applies what &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt; / &lt;code&gt;React.memo&lt;/code&gt; do by hand. Most tutorials online still teach the old &lt;code&gt;react({ babel: { plugins: [...] } })&lt;/code&gt; form, which &lt;strong&gt;no longer works on Vite 8 + &lt;code&gt;@vitejs/plugin-react&lt;/code&gt; v6&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;v6 made one big change: &lt;strong&gt;internal Babel was dropped in favor of oxc&lt;/strong&gt;. JSX transform and Fast Refresh now run in Rust — much faster, but the tradeoff is that Babel plugins can no longer be passed through &lt;code&gt;react({ babel: {...} })&lt;/code&gt;. To run Babel-based tools like React Compiler, you now need to add &lt;code&gt;@rolldown/plugin-babel&lt;/code&gt; separately.&lt;/p&gt;

&lt;p&gt;This post documents the correct 2026 install flow and how to use React Compiler itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What React Compiler Actually Does
&lt;/h2&gt;

&lt;p&gt;The most tedious part of writing React is &lt;strong&gt;sprinkling memoization everywhere&lt;/strong&gt;. Children re-render, function references change, shallow comparison fails — solved by &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt; / &lt;code&gt;React.memo&lt;/code&gt;, but knowing &lt;em&gt;when&lt;/em&gt; they help is an eyeball exercise.&lt;/p&gt;

&lt;p&gt;React Compiler analyzes your components at build time and &lt;strong&gt;inserts memoization where it belongs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TodoList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;})&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;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;filter&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;onClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compiled (conceptual):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TodoList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;})&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;$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// onClick and the JSX are cached the same way&lt;/span&gt;
  &lt;span class="c1"&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;code&gt;_c()&lt;/code&gt; is a slot cache from &lt;code&gt;react/compiler-runtime&lt;/code&gt;. Granularity is &lt;strong&gt;per-expression&lt;/strong&gt;, not per-component — finer than any hand-written useMemo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status in April 2026
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1.0 stable&lt;/strong&gt;: GA on 2025/10/7&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Battle-tested&lt;/strong&gt;: ran at Meta scale (Instagram, Quest Store) for over a year pre-GA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React version&lt;/strong&gt;: 19 is native; 17 / 18 need &lt;code&gt;react-compiler-runtime&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function components only&lt;/strong&gt;: class components are skipped&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Vite 8 + @vitejs/plugin-react v6 (the new way)
&lt;/h2&gt;

&lt;p&gt;v6 removed Babel, so Babel-based plugins need &lt;code&gt;@rolldown/plugin-babel&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @vitejs/plugin-react @rolldown/plugin-babel babel-plugin-react-compiler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.js&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;defineConfig&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;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;reactCompilerPreset&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;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&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;babel&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;@rolldown/plugin-babel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;babel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;jt&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;sx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;babelConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;reactCompilerPreset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nf"&gt;react&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;Two things to notice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;reactCompilerPreset()&lt;/code&gt; is exported from &lt;code&gt;@vitejs/plugin-react&lt;/code&gt;&lt;/strong&gt; — it bundles &lt;code&gt;babel-plugin-react-compiler&lt;/code&gt; with sane defaults so you don't hand-roll a preset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order&lt;/strong&gt;: &lt;code&gt;babel()&lt;/code&gt; before &lt;code&gt;react()&lt;/code&gt;. The compiler must run before other transforms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To stack other Babel plugins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;babel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;jt&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;sx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;babelConfig&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;span class="nf"&gt;reactCompilerPreset&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/plugin-proposal-throw-expressions&lt;/span&gt;&lt;span class="dl"&gt;'&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;h2&gt;
  
  
  Vite 7 and below / @vitejs/plugin-react v5 (legacy)
&lt;/h2&gt;

&lt;p&gt;If you haven't upgraded to Vite 8, the old form still works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @vitejs/plugin-react babel-plugin-react-compiler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.js&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;defineConfig&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;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&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;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;babel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-plugin-react-compiler&lt;/span&gt;&lt;span class="dl"&gt;'&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;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;The two forms are incompatible across one major version. Most people who hit this wall pasted an old tutorial into a fresh Vite 8 project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js 15.3.1+
&lt;/h2&gt;

&lt;p&gt;Next bakes it in — no Babel wiring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;reactCompiler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Install the plugin: &lt;code&gt;npm i babel-plugin-react-compiler&lt;/code&gt;. To customize the mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;reactCompiler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;compilationMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;annotation&lt;/span&gt;&lt;span class="dl"&gt;'&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;Heads up&lt;/strong&gt;: if Next.js enables the compiler, &lt;strong&gt;don't also add the Babel plugin manually&lt;/strong&gt; — double compilation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webpack / Rspack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// webpack.config.js&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;jt&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;sx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-plugin-react-compiler&lt;/span&gt;&lt;span class="dl"&gt;'&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;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;React Compiler must be the &lt;strong&gt;first&lt;/strong&gt; plugin in the Babel chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint Plugin — Enable This Before the Compiler
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; eslint-plugin-react-compiler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint.config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;reactCompiler&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;eslint-plugin-react-compiler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-compiler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reactCompiler&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-compiler/react-compiler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&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 plugin surfaces violations the compiler would silently skip (mutation, conditional hooks, refs read during render). &lt;strong&gt;Run ESLint in CI first, then enable the compiler&lt;/strong&gt; — otherwise the compiler quietly passes over half your components and you don't know it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules the Compiler Assumes
&lt;/h2&gt;

&lt;p&gt;The compiler assumes you follow the Rules of React:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pure render&lt;/strong&gt;: no side effects, no &lt;code&gt;Math.random()&lt;/code&gt;, no fetches during render&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immutability&lt;/strong&gt;: don't mutate props, state, hook return values, or values from previous renders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules of Hooks&lt;/strong&gt;: only at the top level, same order each render&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refs&lt;/strong&gt;: read / write only in effects and handlers, never during render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Violations cause the compiler to &lt;strong&gt;skip that single component, not crash the build&lt;/strong&gt;. The failure mode is &lt;strong&gt;silent regression&lt;/strong&gt; — you think your component is optimized, it isn't. This is exactly what the ESLint plugin prevents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gradual Adoption: compilationMode
&lt;/h2&gt;

&lt;p&gt;Going full-on with a flip is risky. The docs recommend phasing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;'all'&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;Every component is compiled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'annotation'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only components with &lt;code&gt;"use memo"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'infer'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Heuristic selection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Recommended path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upgrade React to 19 (or 17/18 + &lt;code&gt;react-compiler-runtime&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Install the ESLint plugin, fix violations project-wide&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compilationMode: 'annotation'&lt;/code&gt;, add &lt;code&gt;"use memo"&lt;/code&gt; to critical paths, test the water&lt;/li&gt;
&lt;li&gt;Once stable, switch to &lt;code&gt;'all'&lt;/code&gt; and audit DevTools for the ✨ badge on every compiled component&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't rush to remove old &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt;&lt;/strong&gt; — the compiler is additive; manual memo still works. Profile first, then delete file by file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Escape-hatch directive&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Weirdo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use no memo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;// compiler skips this component&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good for temporary workarounds, not a permanent home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Debugging Tools
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. React DevTools badge&lt;/strong&gt;&lt;br&gt;
Successfully compiled components show a &lt;strong&gt;Memo ✨&lt;/strong&gt; badge in the Components tab. No badge? Compiler skipped — check ESLint output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. React Compiler Playground&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://playground.react.dev" rel="noopener noreferrer"&gt;https://playground.react.dev&lt;/a&gt; — paste a component, see compiled output and bailout reasons immediately. Fastest way to answer "why didn't this one get optimized."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Plugin &lt;code&gt;logger&lt;/code&gt; option&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;babelConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;reactCompilerPreset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;logEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&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;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dump every compile event in dev — very direct when hunting down problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Performance Numbers
&lt;/h2&gt;

&lt;p&gt;Numbers from the React team's 1.0 post (Meta production):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instagram heavy screens: render time down &lt;strong&gt;~12%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Quest Store navigation: &lt;strong&gt;2.5× fewer&lt;/strong&gt; re-renders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Synthetic summary: &lt;strong&gt;biggest wins when an expensive child is re-rendering because its parent re-renders every frame&lt;/strong&gt;. Leaf components and hand-optimized code won't get much faster. Don't expect the entire app to magically accelerate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Vite 8 + v6 ordering&lt;/strong&gt;: &lt;code&gt;babel()&lt;/code&gt; must come before &lt;code&gt;react()&lt;/code&gt;. Reverse it and the compiler never runs, only oxc's JSX transform does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js double compilation&lt;/strong&gt;: if Next has enabled it, don't add the Babel plugin manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test environments&lt;/strong&gt;: Jest / Vitest don't get the Babel transform by default, so components in tests aren't compiled — usually fine, but fixtures that intentionally mutate will trip ESLint. Add &lt;code&gt;"use no memo"&lt;/code&gt; to those.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storybook / MDX&lt;/strong&gt;: exclude &lt;code&gt;.stories.*&lt;/code&gt; files to avoid stories violating Rules of React.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source maps&lt;/strong&gt;: set &lt;code&gt;sourcemap: true&lt;/code&gt; in Vite — the compiler's &lt;code&gt;_c()&lt;/code&gt; slot code is painful to read in devtools without them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;React Compiler 1.0 GA, Vite 8, &lt;code&gt;@vitejs/plugin-react&lt;/code&gt; v6 all landed within months of each other in late 2025 / early 2026, and stale tutorials are everywhere. The essentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite 8 + v6: &lt;code&gt;@rolldown/plugin-babel&lt;/code&gt; + &lt;code&gt;reactCompilerPreset()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Vite 7 and below: classic &lt;code&gt;react({ babel: {...} })&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ESLint first, CI before compiler&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'annotation'&lt;/code&gt; mode → &lt;code&gt;'all'&lt;/code&gt;, not a single flip&lt;/li&gt;
&lt;li&gt;Don't bulk-delete &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt;; the compiler is additive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The highest-value first step is the ESLint plugin — even before turning the compiler on, fixing Rules of React violations means almost zero surprises the day you flip the switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react.dev/learn/react-compiler" rel="noopener noreferrer"&gt;React Compiler Official Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.dev/blog/2025/10/07/react-compiler-1" rel="noopener noreferrer"&gt;React Compiler 1.0 Announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vitejs/vite-plugin-react/releases" rel="noopener noreferrer"&gt;@vitejs/plugin-react v6 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://playground.react.dev" rel="noopener noreferrer"&gt;React Compiler Playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/api-reference/next-config-js/reactCompiler" rel="noopener noreferrer"&gt;Next.js reactCompiler Config&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>reactcompiler</category>
      <category>vite</category>
      <category>performance</category>
    </item>
    <item>
      <title>git worktree: Multiple Working Directories Per Repo, and the Key to Parallel AI Agents</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 14 Apr 2026 21:05:49 +0000</pubDate>
      <link>https://forem.com/recca0120/git-worktree-multiple-working-directories-per-repo-and-the-key-to-parallel-ai-agents-40</link>
      <guid>https://forem.com/recca0120/git-worktree-multiple-working-directories-per-repo-and-the-key-to-parallel-ai-agents-40</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/14/git-worktree-parallel-work/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You're mid-feature when PM says prod is on fire. The reflex move: &lt;code&gt;git stash&lt;/code&gt;, check out master, fix, come back, &lt;code&gt;stash pop&lt;/code&gt;. But then the dev server restarts, the IDE re-indexes, and stash silently ate your untracked build artifacts.&lt;/p&gt;

&lt;p&gt;git worktree fixes this cleanly: &lt;strong&gt;one repo, many checked-out branches in separate directories&lt;/strong&gt;, each with its own HEAD, index, and untracked files — all sharing the same object database. The feature stays exactly where you left it; the hotfix happens next door.&lt;/p&gt;

&lt;h2&gt;
  
  
  vs stash, vs multiple clones
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Switch cost&lt;/th&gt;
&lt;th&gt;Disk&lt;/th&gt;
&lt;th&gt;Object sync&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;git stash&lt;/code&gt; + checkout&lt;/td&gt;
&lt;td&gt;High (dev server restarts, IDE reindex)&lt;/td&gt;
&lt;td&gt;Cheap&lt;/td&gt;
&lt;td&gt;Single repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple clones&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Wasteful (GBs on large repos)&lt;/td&gt;
&lt;td&gt;Each fetches separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git worktree&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Cheap (shared objects)&lt;/td&gt;
&lt;td&gt;One fetch updates all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Worktree is the synthesis — filesystem isolation with a unified object store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add — last path segment becomes the new branch name&lt;/span&gt;
git worktree add ../hotfix                   &lt;span class="c"&gt;# branch "hotfix" from HEAD&lt;/span&gt;
git worktree add ../review pr-123            &lt;span class="c"&gt;# checkout existing branch&lt;/span&gt;
git worktree add &lt;span class="nt"&gt;-b&lt;/span&gt; feat-x ../feat-x main    &lt;span class="c"&gt;# new branch from main&lt;/span&gt;
git worktree add &lt;span class="nt"&gt;-d&lt;/span&gt; ../throwaway             &lt;span class="c"&gt;# detached HEAD, no branch&lt;/span&gt;

git worktree list                            &lt;span class="c"&gt;# --porcelain for scripts&lt;/span&gt;
git worktree remove ../hotfix                &lt;span class="c"&gt;# clean only; -f for dirty&lt;/span&gt;
git worktree prune                           &lt;span class="c"&gt;# clean metadata for manually deleted dirs&lt;/span&gt;
git worktree lock ../usb-drive &lt;span class="nt"&gt;--reason&lt;/span&gt; &lt;span class="s2"&gt;"removable drive"&lt;/span&gt;
git worktree unlock ../usb-drive
git worktree move ../old ../new              &lt;span class="c"&gt;# won't move worktrees w/ submodules&lt;/span&gt;
git worktree repair                          &lt;span class="c"&gt;# fix links after moving the main repo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Short aliases make it muscle memory&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.wta &lt;span class="s1"&gt;'worktree add'&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.wtl &lt;span class="s1"&gt;'worktree list'&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.wtr &lt;span class="s1"&gt;'worktree remove'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real Workflows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Hotfix without disturbing feature work&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../hotfix-prod origin/main
&lt;span class="nb"&gt;cd&lt;/span&gt; ../hotfix-prod
&lt;span class="c"&gt;# fix, commit, push, open PR&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ../myrepo
git worktree remove ../hotfix-prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feature's dev server never stopped. node_modules untouched. IDE didn't re-index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Long test run + keep coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pytest&lt;/code&gt; / &lt;code&gt;cargo test&lt;/code&gt; takes 10 minutes. Open a worktree to run it there while you keep editing the next commit. Zero interference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../ci-run branch-a
&lt;span class="nb"&gt;cd&lt;/span&gt; ../ci-run &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pytest &lt;span class="nt"&gt;--slow&lt;/span&gt; &amp;amp;
&lt;span class="nb"&gt;cd&lt;/span&gt; -   &lt;span class="c"&gt;# back to main worktree, keep coding&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Code review without polluting your state&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../review-456 pr-456-branch
&lt;span class="nb"&gt;cd&lt;/span&gt; ../review-456
&lt;span class="c"&gt;# run it, read code, leave comments&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git worktree remove ../review-456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Multiple AI agents in parallel&lt;/strong&gt; (the most underrated 2025 use case)&lt;/p&gt;

&lt;p&gt;Claude Code / Cursor / Aider only work in one directory at a time — running two in the same folder means they overwrite each other's files. One branch per worktree and you can &lt;strong&gt;run three agents on three features simultaneously&lt;/strong&gt;, each with its own dev server port and node_modules, no collisions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../agent-a &lt;span class="nt"&gt;-b&lt;/span&gt; feat-a
git worktree add ../agent-b &lt;span class="nt"&gt;-b&lt;/span&gt; feat-b
git worktree add ../agent-c &lt;span class="nt"&gt;-b&lt;/span&gt; feat-c

&lt;span class="c"&gt;# three tmux panes / three terminal windows, one claude each&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code's Agent tool even has a built-in &lt;code&gt;isolation: "worktree"&lt;/code&gt; option — sub-agents automatically run in a worktree and merge back when done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Directory Layouts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sibling dirs (simplest)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/code/myrepo
~/code/myrepo-hotfix
~/code/myrepo-review-123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plays well with editors' "one folder, one project" mental model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.worktrees/&lt;/code&gt; subdir&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myrepo/
├── src/
└── .worktrees/
    ├── feat-x/
    └── hotfix/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything co-located. Add &lt;code&gt;.worktrees/&lt;/code&gt; to global gitignore. Downside: some tools (ESLint, tsc) recurse into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bare repo pattern (best for heavy multi-branch work)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;myproj &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;myproj
git clone &lt;span class="nt"&gt;--bare&lt;/span&gt; git@github.com:org/repo.git .bare
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"gitdir: ./.bare"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .git
git worktree add main
git worktree add feat-x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myproj/
├── .bare/           # actual object store
├── .git             # file, pointing to .bare
├── main/            # main branch checkout
└── feat-x/          # feat-x branch checkout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No "primary working copy" — every branch is a worktree, &lt;code&gt;cd&lt;/code&gt; is the branch switch. Pairs beautifully with tmux and AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.git&lt;/code&gt; in a linked worktree is a file, not a directory.&lt;/strong&gt; Contents are &lt;code&gt;gitdir: /path/to/main/.git/worktrees/&amp;lt;name&amp;gt;&lt;/code&gt;. Tools that read &lt;code&gt;.git/&lt;/code&gt; as a directory will break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can't check out the same branch in two worktrees.&lt;/strong&gt; By design — prevents index divergence. Override with &lt;code&gt;--force&lt;/code&gt; or use detached HEAD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Submodules are painful.&lt;/strong&gt; &lt;code&gt;worktree move&lt;/code&gt; refuses; &lt;code&gt;remove&lt;/code&gt; needs &lt;code&gt;--force&lt;/code&gt;. &lt;code&gt;.git/modules/&lt;/code&gt; is shared, so switching submodule commits in one worktree affects the others. Heavy-submodule repos need care.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;venv&lt;/code&gt;, &lt;code&gt;target/&lt;/code&gt; are per-worktree.&lt;/strong&gt; Disk gets hungry. Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pnpm's content-addressable store — reinstalling across worktrees uses almost no extra space&lt;/li&gt;
&lt;li&gt;Rust: &lt;code&gt;CARGO_TARGET_DIR=~/.cache/cargo-target&lt;/code&gt; shares target dirs&lt;/li&gt;
&lt;li&gt;uv, poetry caches are shareable too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.env&lt;/code&gt; doesn't get copied.&lt;/strong&gt; Use direnv — drop a &lt;code&gt;.envrc&lt;/code&gt; in each worktree and it auto-loads on cd.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hooks are shared&lt;/strong&gt; (&lt;code&gt;.git/hooks/&lt;/code&gt; is in the common dir). If a hook assumes repo root via a hardcoded path, it breaks in linked worktrees. Always use &lt;code&gt;git rev-parse --show-toplevel&lt;/code&gt; for the current worktree's root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IDE indexes run per worktree.&lt;/strong&gt; VS Code / JetBrains index each one independently — duplicate CPU and disk. Structural limit, no way around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor Integration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;VS Code&lt;/strong&gt;: use multi-root workspace (&lt;code&gt;File → Add Folder to Workspace&lt;/code&gt;) to open multiple worktrees at once, or just open each in its own window. Since 2024, &lt;code&gt;GitHub.vscode-pull-request-github&lt;/code&gt; checks out PRs as worktrees.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JetBrains&lt;/strong&gt;: native worktree UI in the Git tool window since 2023.2 — create, switch, remove from the GUI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;fzf quick-switch&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wtcd&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git worktree list &lt;span class="nt"&gt;--porcelain&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/^worktree /{print $2}'&lt;/span&gt; | fzf&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;wtcd&lt;/code&gt;, fuzzy-search worktree paths, enter to cd.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New in 2024–2026
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Git 2.44 (2024/2)&lt;/strong&gt;: &lt;code&gt;git worktree add --orphan&lt;/code&gt; — creates a worktree with an unborn branch. Handy for &lt;code&gt;gh-pages&lt;/code&gt;-style split deploy branches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git 2.46 (2024/7)&lt;/strong&gt;: &lt;code&gt;worktree.useRelativePaths&lt;/code&gt; config + &lt;code&gt;--relative-paths&lt;/code&gt; flag — internal links use relative paths. The main repo (or the whole dir tree) can move without breaking worktrees. Huge for Dropbox/iCloud sync and containerized dev.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git 2.48 (2025/1)&lt;/strong&gt;: &lt;code&gt;git worktree repair&lt;/code&gt; auto-fixes absolute/relative path mismatches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git 2.50 (mid-2025)&lt;/strong&gt;: relative paths and porcelain output have stabilized.&lt;/p&gt;

&lt;p&gt;Ecosystem: "one worktree per AI agent branch" went mainstream in 2025. &lt;code&gt;git-town&lt;/code&gt;, &lt;code&gt;ghq&lt;/code&gt;, and the &lt;code&gt;gh&lt;/code&gt; CLI all picked up first-class worktree support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In any repo, try &lt;code&gt;git worktree add ../test-wt -b test-branch&lt;/code&gt; and poke around&lt;/li&gt;
&lt;li&gt;Back in the main worktree, notice &lt;code&gt;git branch&lt;/code&gt; sees it but &lt;code&gt;git status&lt;/code&gt; is untouched&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git worktree remove ../test-wt&lt;/code&gt; — main worktree is completely unchanged&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;wta&lt;/code&gt; / &lt;code&gt;wtl&lt;/code&gt; / &lt;code&gt;wtr&lt;/code&gt; aliases, build muscle memory&lt;/li&gt;
&lt;li&gt;Next hotfix, use worktree instead of stash — feel the difference&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For anyone using AI coding agents, worktree isn't a bonus — it's required. The "only one agent per repo" limitation is the exact thing worktree removes.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;git-worktree Official Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/git/git/blob/master/Documentation/RelNotes/2.46.0.txt" rel="noopener noreferrer"&gt;Git 2.46 Release Notes — worktree.useRelativePaths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/agent-sdk" rel="noopener noreferrer"&gt;Claude Code Agent tool — isolation: worktree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.git-town.com/" rel="noopener noreferrer"&gt;git-town — high-level git workflow wrapper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>worktree</category>
      <category>productivity</category>
      <category>agents</category>
    </item>
    <item>
      <title>Cloudflare Tunnel in 2026: Expose localhost Without Opening Ports or Buying an IP</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 14 Apr 2026 17:46:48 +0000</pubDate>
      <link>https://forem.com/recca0120/cloudflare-tunnel-in-2026-expose-localhost-without-opening-ports-or-buying-an-ip-32l5</link>
      <guid>https://forem.com/recca0120/cloudflare-tunnel-in-2026-expose-localhost-without-opening-ports-or-buying-an-ip-32l5</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/14/cloudflare-tunnel-2026/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A side project at home needs a public demo URL for a client. A NUC in the office runs an internal tool I want to reach from outside. The old playbook — rent a static IP, forward ports on the router, manage Let's Encrypt — is all skippable in 2026.&lt;/p&gt;

&lt;p&gt;Cloudflare Tunnel (cloudflared) does the dirty work: your machine opens an &lt;strong&gt;outbound-only&lt;/strong&gt; persistent connection to Cloudflare's edge, and inbound traffic rides that connection back home. No open ports, no public IP, and you get Cloudflare's DDoS protection and WAF for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloudflare Tunnel
&lt;/h2&gt;

&lt;p&gt;Plenty of alternatives exist, and they target different use cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Custom domain&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Self-hosted relay&lt;/th&gt;
&lt;th&gt;TCP support&lt;/th&gt;
&lt;th&gt;Free tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Tunnel&lt;/td&gt;
&lt;td&gt;✅ free&lt;/td&gt;
&lt;td&gt;✅ Access built-in&lt;/td&gt;
&lt;td&gt;❌ CF-managed&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Generous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ngrok&lt;/td&gt;
&lt;td&gt;Paid&lt;/td&gt;
&lt;td&gt;Paid add-on&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Paid&lt;/td&gt;
&lt;td&gt;Connection-capped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tailscale Funnel&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;P2P-ish&lt;/td&gt;
&lt;td&gt;HTTPS only&lt;/td&gt;
&lt;td&gt;3 ports only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;frp&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;✅ self-host&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Your machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Tunnel's real differentiator&lt;/strong&gt;: it doesn't just forward traffic — it plugs your service into Cloudflare's entire Zero Trust platform. Layer Access (SSO, email OTP) on top, pass traffic through WAF, even get browser-rendered SSH without a client. Those are paid upsells on ngrok and simply don't exist on Funnel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2026 Recommended Flow: Zero Trust Dashboard
&lt;/h2&gt;

&lt;p&gt;Historically cloudflared was configured via local &lt;code&gt;config.yml&lt;/code&gt;. In 2026 Cloudflare steers most users to &lt;strong&gt;remotely-managed tunnels&lt;/strong&gt; — config lives in the cloud dashboard, the local cloudflared just needs a token. Bonus: share tunnels across machines, edit ingress without restarts, run multiple replicas for HA.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href="https://one.dash.cloudflare.com" rel="noopener noreferrer"&gt;one.dash.cloudflare.com&lt;/a&gt; → &lt;strong&gt;Networks → Tunnels → Create a tunnel → Cloudflared&lt;/strong&gt;. Name it, copy the install command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install cloudflared
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Linux (Debian/Ubuntu)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://pkg.cloudflare.com/install.sh | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; cf-tunnel &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  cloudflare/cloudflared:latest &lt;span class="se"&gt;\&lt;/span&gt;
  tunnel &lt;span class="nt"&gt;--no-autoupdate&lt;/span&gt; run &lt;span class="nt"&gt;--token&lt;/span&gt; eyJhbGci...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop in the token the dashboard gave you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;cloudflared service &lt;span class="nb"&gt;install &lt;/span&gt;eyJhbGci...TOKEN...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This registers a systemd service (Linux) or launchd plist (macOS), auto-starts on boot, auto-restarts on crash. Check the dashboard — green &lt;strong&gt;Healthy&lt;/strong&gt; means you're live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pushing localhost:3000 to foo.example.com
&lt;/h2&gt;

&lt;p&gt;Prerequisite: &lt;code&gt;example.com&lt;/code&gt; already uses Cloudflare nameservers.&lt;/p&gt;

&lt;p&gt;Back on the tunnel page, switch to the &lt;strong&gt;Public Hostname&lt;/strong&gt; tab → &lt;strong&gt;Add a public hostname&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subdomain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;foo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;example.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HTTP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localhost:3000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Save. Cloudflare auto-creates the DNS CNAME &lt;code&gt;foo.example.com → &amp;lt;tunnel-uuid&amp;gt;.cfargotunnel.com&lt;/code&gt;. Hit &lt;code&gt;https://foo.example.com&lt;/code&gt; — the cert comes from Cloudflare's edge; your local box needs zero TLS config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate It With Zero Trust Access
&lt;/h2&gt;

&lt;p&gt;Don't want random scanners finding your demo URL? Put a login wall in front:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero Trust → Access → Applications → Add an application → Self-hosted&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application domain: &lt;code&gt;foo.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add policy: Action = &lt;strong&gt;Allow&lt;/strong&gt;, Include = &lt;strong&gt;Emails ending in &lt;code&gt;@yourco.com&lt;/code&gt;&lt;/strong&gt; (or a specific email list)&lt;/li&gt;
&lt;li&gt;Identity provider: the default &lt;strong&gt;One-time PIN&lt;/strong&gt; (email OTP) works out of the box, or hook up Google SSO / GitHub via &lt;strong&gt;Settings → Authentication&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next visit to &lt;code&gt;foo.example.com&lt;/code&gt; lands on a Cloudflare login screen first. Free up to &lt;strong&gt;50 users&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TryCloudflare: Disposable Demo Tunnels
&lt;/h2&gt;

&lt;p&gt;Need to show a webhook or demo in 30 seconds, not even wanting to open an account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared tunnel &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prints &lt;code&gt;https://&amp;lt;random-words&amp;gt;.trycloudflare.com&lt;/code&gt; — random subdomain, traffic routed back to your localhost. Dies with the process. Great for temporary use, not production, rate-limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local config.yml (for the GitOps crowd)
&lt;/h2&gt;

&lt;p&gt;To keep tunnel config in Git or drive it from Terraform, the old workflow still works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cloudflared tunnel login
cloudflared tunnel create dev-laptop
cloudflared tunnel route dns dev-laptop foo.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;~/.cloudflared/config.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tunnel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev-laptop&lt;/span&gt;
&lt;span class="na"&gt;credentials-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/Users/me/.cloudflared/&amp;lt;UUID&amp;gt;.json&lt;/span&gt;
&lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo.example.com&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:3000&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.example.com&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:4000&lt;/span&gt;
    &lt;span class="na"&gt;originRequest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;connectTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;noTLSVerify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http_status:404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run: &lt;code&gt;cloudflared tunnel run dev-laptop&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Footguns I've Stepped On
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Always include the catch-all ingress.&lt;/strong&gt; Without &lt;code&gt;- service: http_status:404&lt;/code&gt;, cloudflared refuses to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo.example.com&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:3000&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http_status:404&lt;/span&gt;   &lt;span class="c1"&gt;# mandatory final entry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WebSockets just work.&lt;/strong&gt; Default-on since 2022, no flag needed. Next.js HMR, Socket.IO, fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSH / RDP also work.&lt;/strong&gt; Ingress entry &lt;code&gt;service: ssh://localhost:22&lt;/code&gt;, then enable &lt;strong&gt;Browser rendering&lt;/strong&gt; in the Access app — users get an in-browser terminal with no client install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the same tunnel on multiple hosts.&lt;/strong&gt; Re-use the same token on a second machine and Cloudflare handles HA / load balancing automatically. Reboot one host, service stays up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-hostname originRequest.&lt;/strong&gt; Each ingress can independently set &lt;code&gt;httpHostHeader&lt;/code&gt;, &lt;code&gt;connectTimeout&lt;/code&gt;, &lt;code&gt;noTLSVerify&lt;/code&gt; — no need to change global settings for one service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tunnel itself: 100% free&lt;/strong&gt;, unmetered bandwidth, unlimited tunnels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Trust Access: free up to 50 users&lt;/strong&gt;, then Cloudflare One pay-as-you-go ~$7/user/month&lt;/li&gt;
&lt;li&gt;No egress fees, no connection limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For individuals and small teams, it's effectively free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notable 2025–2026 Updates
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dashboard consolidation&lt;/strong&gt;: &lt;code&gt;dash.teams.cloudflare.com&lt;/code&gt; is fully retired; everything lives at &lt;code&gt;one.dash.cloudflare.com&lt;/code&gt;. If you land on an old tutorial, update the URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARP Connector GA&lt;/strong&gt;: where Tunnel exposes individual services, WARP Connector brings &lt;strong&gt;entire subnets&lt;/strong&gt; onto Cloudflare's network. Site-to-site VPN replacement; complements Tunnel for full-network reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare One rebrand&lt;/strong&gt;: Access, Gateway, Tunnel, WARP, CASB, DLP, Email Security merged into a single SSE platform. One Zero Trust menu now covers all enterprise network security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform provider v5&lt;/strong&gt; is stable: full IaC for tunnel resources, trivial multi-environment deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QUIC as default&lt;/strong&gt;: cloudflared now uses QUIC (HTTP/3) by default, faster connection establishment and more resilient on flaky networks.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Not to Use Tunnel
&lt;/h2&gt;

&lt;p&gt;Great tool, not universal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Want strict peer-to-peer with no third party&lt;/strong&gt;: use Tailscale / WireGuard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory rules require non-US / non-CF transit&lt;/strong&gt;: self-host frp or enterprise VPN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Huge static file traffic&lt;/strong&gt;: Cloudflare Pages / R2 is a better fit than tunneling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Five-minute demo with no domain&lt;/strong&gt;: TryCloudflare or ngrok is faster&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Demo environments, webhook receivers, showing your coworker a work-in-progress — in 2026 Cloudflare Tunnel is almost always the lowest-friction answer. Generous free tier, security handled by Cloudflare, and the same setup carries from dev to production.&lt;/p&gt;

&lt;p&gt;Three steps to live: &lt;code&gt;brew install cloudflared&lt;/code&gt; → dashboard creates tunnel → paste token. Everything complicated lives in a cloud UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" rel="noopener noreferrer"&gt;Cloudflare Tunnel Official Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudflare/cloudflared" rel="noopener noreferrer"&gt;cloudflared on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://one.dash.cloudflare.com" rel="noopener noreferrer"&gt;Zero Trust Dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/" rel="noopener noreferrer"&gt;TryCloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cloudflare.com/plans/zero-trust-services/" rel="noopener noreferrer"&gt;Cloudflare Access Pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloudflare</category>
      <category>tunnel</category>
      <category>zerotrust</category>
      <category>networking</category>
    </item>
    <item>
      <title>I Scanned 95 Days of My Claude Code Logs and Found Anthropic''s Second Silent Cache TTL Regression</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Mon, 13 Apr 2026 18:47:02 +0000</pubDate>
      <link>https://forem.com/recca0120/verify-whether-your-claude-code-uses-5m-or-1h-cache-ttl-with-60-lines-of-python-4548</link>
      <guid>https://forem.com/recca0120/verify-whether-your-claude-code-uses-5m-or-1h-cache-ttl-with-60-lines-of-python-4548</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/14/claude-code-cache-ttl-audit/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/en/2026/04/13/claude-code-session-cost-cache-misconception/"&gt;The previous post&lt;/a&gt; covered prompt caching cost mechanics. While researching it I bumped into a dramatic controversy — in March 2026 Anthropic &lt;strong&gt;silently changed Claude Code's cache TTL from 1 hour back to 5 minutes&lt;/strong&gt;, with community-measured monthly costs inflating 15–53%. Reddit and HN exploded.&lt;/p&gt;

&lt;p&gt;But every public claim is somebody else's billing statement or &lt;a href="https://github.com/anthropics/claude-code/issues/46829" rel="noopener noreferrer"&gt;issue #46829&lt;/a&gt;. I wanted to know whether &lt;strong&gt;my own machine&lt;/strong&gt; was affected. After scanning 95 days of native logs, the answer turned out richer than expected: I not only reproduced the March 6 regression precisely, but also discovered &lt;strong&gt;a second wave starting April 9&lt;/strong&gt; — five consecutive days, 4,840 API calls, sub-agents 100% downgraded to 5m, and no public report I can find.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Evidence Is in ~/.claude/projects JSONL
&lt;/h2&gt;

&lt;p&gt;Claude Code writes complete API interaction logs at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude/projects/{project-path}/{session-uuid}.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each assistant response carries a &lt;code&gt;usage.cache_creation&lt;/code&gt; object that &lt;strong&gt;directly tells you which TTL bucket this write went to&lt;/strong&gt;:&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;"cache_creation"&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;"ephemeral_5m_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ephemeral_1h_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6561&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;These fields come &lt;strong&gt;straight from Anthropic's API&lt;/strong&gt;, bypassing any client-side display logic. If the client wanted to lie, the server wouldn't go along — this data is the truth from the server itself.&lt;/p&gt;

&lt;p&gt;A Python script that scans every project, splits by date, and separates main agent from sub-agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;

&lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.claude/projects&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;sub&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;jsonl&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subagent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;jsonl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;jsonl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache_creation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
                &lt;span class="n"&gt;w5&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ephemeral_5m_input_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ephemeral_1h_input_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w5&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;w1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="n"&gt;w5&lt;/span&gt;
                &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="n"&gt;w1&lt;/span&gt;
                &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tot&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tot&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;M5m&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;M1h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;M%&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S5m&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S1h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S%&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2026-01-01&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5m&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5m&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  95-Day Timeline
&lt;/h2&gt;

&lt;p&gt;Scanning my machine from January 9 through April 13, &lt;strong&gt;four phases with three transitions emerge&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th&gt;Sub-agent&lt;/th&gt;
&lt;th&gt;Main agent&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1/9 ~ 2/5&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100% 5m&lt;/strong&gt; (28 days)&lt;/td&gt;
&lt;td&gt;no data&lt;/td&gt;
&lt;td&gt;1h not yet rolled out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2/6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;79% 1h (transition starts)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The 2/1-announced 1h upgrade actually goes live&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2/7 ~ 3/5&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100% 1h&lt;/strong&gt; stable (28 days)&lt;/td&gt;
&lt;td&gt;100% 1h&lt;/td&gt;
&lt;td&gt;1h golden era&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3/6&lt;/strong&gt; ~ 4/8&lt;/td&gt;
&lt;td&gt;1h ↔ 5m mixed, swinging 6%–97%&lt;/td&gt;
&lt;td&gt;100% 1h&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;First regression&lt;/strong&gt; (the one cnighswonger reported)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;4/9&lt;/strong&gt; ~ now&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100% 5m&lt;/strong&gt; stable (5 days)&lt;/td&gt;
&lt;td&gt;100% 1h&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Second regression&lt;/strong&gt; (no public report)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Key days around each transition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date        | MAIN 1h  calls | SUB 5m         SUB 1h         S1h%   calls
------------|----------------|-----------------------------------------
2026-02-05  | (no data)      |          0    7,974,898       100%   1392    ← still 1h
2026-02-06  | (no data)      |  2,886,030   10,753,834        79%   1684    ← 1h rollout begins
2026-02-07  | (no data)      |          0    4,280,317       100%    639

2026-03-05  | 100%   1503    |          0    6,004,235       100%   2446    ← still 1h
2026-03-06  | 100%   3355    |    461,509    1,281,686        74%    608    ← FIRST regression!
2026-03-07  | 100%   2753    |  9,810,771   10,465,251        52%   7548
2026-03-08  | 100%   4724    | 34,340,003   24,301,557        41%  17514

2026-04-08  | 100%   3041    |  2,650,760    5,533,277        68%   1301    ← still mixed
2026-04-09  | 100%   4155    |  8,451,674            0         0%   1268    ← SECOND regression!
2026-04-10  | 100%   5523    |  5,437,455            0         0%   1170
2026-04-11  | 100%   2579    |  3,325,195            0         0%    778
2026-04-12  | 100%   3738    |  2,981,213            0         0%    993
2026-04-13  | 100%   4443    |  2,648,132            0         0%    631
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decoding the Three Transitions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Transition 1: 2026-02-06 — 1h Rollout Goes Live
&lt;/h3&gt;

&lt;p&gt;Anthropic announced "TTL upgraded from 5m to 1h" on 2/1. &lt;strong&gt;The actual rollout landed on 2/6.&lt;/strong&gt; My logs show sub-agent flipping from 100% 5m to 79% 1h overnight, matching the announcement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transition 2: 2026-03-06 — &lt;a href="https://github.com/anthropics/claude-code/issues/46829" rel="noopener noreferrer"&gt;First Silent Regression&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is the event &lt;a href="https://github.com/anthropics/claude-code/issues/46829" rel="noopener noreferrer"&gt;cnighswonger reported in issue #46829&lt;/a&gt;, using the same methodology to scan 119,866 API calls — &lt;strong&gt;on March 6, sub-agent went from 100% 1h to 74%&lt;/strong&gt;. I reproduced it precisely: 3/5 still 100% 1h, 3/6 dropped to 74%, 3/7 spiked to 9.8M tokens of 5m writes.&lt;/p&gt;

&lt;p&gt;The following month (3/6–4/8), sub-agent oscillated wildly between 6% and 97% 1h share. The server's TTL decision &lt;strong&gt;logic was unstable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Anthropic employee Jarred Sumner defended in &lt;a href="https://www.theregister.com/2026/04/13/claude_code_cache_confusion/" rel="noopener noreferrer"&gt;The Register's coverage&lt;/a&gt; that "sub-agent 5m is cheaper for one-shot calls" — barely plausible for the mixed phase 4 behavior. But the next event breaks that defense.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transition 3: 2026-04-09 — Second Silent Regression (Original Finding)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Starting 4/9, sub-agent 1h share dropped to zero.&lt;/strong&gt; Five consecutive days, 100% 5m, across &lt;strong&gt;4,840 API calls&lt;/strong&gt;. No public report I can find.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not noise.&lt;/strong&gt; 4,840 calls with zero 1h, while 4/8 was still 68% 1h. This is a &lt;strong&gt;sharp binary cutover&lt;/strong&gt;, not gradual drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a quota-triggered downgrade.&lt;/strong&gt; Anthropic's docs say exceeding 5h quota triggers a server-enforced downgrade. But &lt;strong&gt;main agent is 100% 1h on the same days&lt;/strong&gt; — quota mechanism would have downgraded both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a client version issue.&lt;/strong&gt; Same client, same day, two different TTL behaviors for main vs sub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not a workflow change.&lt;/strong&gt; API call volume sits in the normal range (631–1268 per day), comparable to early April.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only plausible explanation: starting 4/9, the server changed sub-agent default TTL from "mixed" to "hard-coded 5m".&lt;/strong&gt; And &lt;strong&gt;no changelog, no announcement, no issue mentions it&lt;/strong&gt; — the same silent-rollout pattern as 3/6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Main Agent Was Never Affected
&lt;/h2&gt;

&lt;p&gt;Across all 95 days, main agent has zero 5m writes. Every TTL action Anthropic took &lt;strong&gt;only touched sub-agents&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Did my data verify it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reddit "Anthropic silently changed TTL on 3/6"&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Strongly verified&lt;/strong&gt; (precise 3/6 transition)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sumner "main agent unaffected"&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Verified&lt;/strong&gt; (main 100% 1h across 95 days)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Regression only hits sub-agent"&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Verified&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sumner "sub-agent 5m is a one-shot optimization"&lt;/td&gt;
&lt;td&gt;⚠️ &lt;strong&gt;Partially refuted&lt;/strong&gt; (4/9 100% 5m isn't optimization, it's forced downgrade)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New finding: 4/9 sub-agent 100% 5m&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🆕 &lt;strong&gt;Original&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Anyone Can Reproduce This
&lt;/h2&gt;

&lt;p&gt;Save the script above as &lt;code&gt;~/bin/cc-ttl-timeline.py&lt;/code&gt; and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 ~/bin/cc-ttl-timeline.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see corresponding 3/6 and 4/9 transitions in your sub-agent — you're a silent regression victim, please add a data point to issue #46829. If you don't see them — the regression isn't a 100% rollout and you're in the control group.&lt;/p&gt;

&lt;p&gt;Both outcomes have value. &lt;strong&gt;The point is this evidence chain doesn't depend on anyone's claims&lt;/strong&gt; — the source is local JSONL written by Claude Code to your disk, and the &lt;code&gt;cache_creation&lt;/code&gt; object structure is part of Anthropic's public API spec. To fake this the server would have to lie in its own API responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use cnighswonger's npm Package
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/cnighswonger/claude-code-cache-fix" rel="noopener noreferrer"&gt;cnighswonger/claude-code-cache-fix&lt;/a&gt; is excellent, but it &lt;strong&gt;only sees data captured after install&lt;/strong&gt; — its monitoring tools (status line, cost-report, quota-analysis) read &lt;code&gt;~/.claude/usage.jsonl&lt;/code&gt;, which only exists while the interceptor is loaded.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;historical&lt;/strong&gt; audit, only Claude Code's own &lt;code&gt;~/.claude/projects/*.jsonl&lt;/code&gt; works. That's what this post uses.&lt;/p&gt;

&lt;p&gt;The two are complementary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Look back, find regression transition dates&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The Python script in this post&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live TTL state visibility + fix client cache bugs&lt;/td&gt;
&lt;td&gt;cnighswonger's package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General token usage analysis&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/ryoppippi/ccusage" rel="noopener noreferrer"&gt;ccusage&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;"Did Anthropic silently change cache TTL?" — everyone should scan their own data. Community rumors and Anthropic's official statements aren't enough. Only the JSONL on your disk doesn't lie.&lt;/p&gt;

&lt;p&gt;The result is valuable either way: see the regression → you're a victim, contribute evidence; don't see it → you're in the control group, which is also evidence that the rollout isn't 100%.&lt;/p&gt;

&lt;p&gt;I'll keep scanning every few days to see how long the 4/9 wave persists and whether it spreads to main agents. If your data shows similar 100% 5m sub-agent behavior post-4/9, please comment on &lt;a href="https://github.com/anthropics/claude-code/issues/46829" rel="noopener noreferrer"&gt;issue #46829&lt;/a&gt; or reach out.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/46829" rel="noopener noreferrer"&gt;Cache TTL silently regressed from 1h to 5m — GitHub Issue #46829&lt;/a&gt; — cnighswonger's original evidence&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/ClaudeAI/comments/1sk3m12/followup_anthropic_quietly_switched_the_default/" rel="noopener noreferrer"&gt;Followup: Anthropic quietly switched the default — r/ClaudeAI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.theregister.com/2026/04/13/claude_code_cache_confusion/" rel="noopener noreferrer"&gt;Anthropic: Claude quota drain not caused by cache tweaks — The Register&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=47736476" rel="noopener noreferrer"&gt;Anthropic downgraded cache TTL on March 6th — Hacker News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/cnighswonger/claude-code-cache-fix" rel="noopener noreferrer"&gt;cnighswonger/claude-code-cache-fix&lt;/a&gt; — fixes client cache-busting bugs + live monitor&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;Prompt Caching — Claude API Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ryoppippi/ccusage" rel="noopener noreferrer"&gt;ccusage — Claude Code Usage CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>promptcaching</category>
      <category>agents</category>
      <category>python</category>
    </item>
    <item>
      <title>Does a Long Claude Code Session Waste Tokens? A Cost Model Most People Get Wrong</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Mon, 13 Apr 2026 11:06:30 +0000</pubDate>
      <link>https://forem.com/recca0120/does-a-long-claude-code-session-waste-tokens-a-cost-model-most-people-get-wrong-20f7</link>
      <guid>https://forem.com/recca0120/does-a-long-claude-code-session-waste-tokens-a-cost-model-most-people-get-wrong-20f7</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/13/claude-code-session-cost-cache-misconception/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A common intuition among developers: Claude Code sessions get expensive over time. Context keeps accumulating, every turn resends the entire history, and token costs add up linearly. The obvious conclusion: &lt;code&gt;/clear&lt;/code&gt; often, start fresh sessions for each task to save money.&lt;/p&gt;

&lt;p&gt;That reasoning is &lt;strong&gt;half right and half wrong&lt;/strong&gt;. The wrong half comes from leaving prompt caching out of the cost model. In practice, &lt;strong&gt;frequent &lt;code&gt;/clear&lt;/code&gt; can cost more than keeping a long session alive&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cumulative Context Really Does Cost
&lt;/h2&gt;

&lt;p&gt;Start with what's true. LLM APIs are stateless — every API call must resend the entire conversation history. After 10 turns with Claude, the 11th request contains all 10 previous turns plus your new question.&lt;/p&gt;

&lt;p&gt;So yes, the input token count per call grows linearly as the session gets longer. It's reasonable to conclude "longer sessions cost more."&lt;/p&gt;

&lt;h2&gt;
  
  
  But Prompt Caching Changes the Rules
&lt;/h2&gt;

&lt;p&gt;Anthropic introduced prompt caching in 2024, and Claude Code enables it by default. The rule is simple: &lt;strong&gt;identical prefixes only cost 10% of the normal price&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sonnet 4.6 pricing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Price (per million tokens)&lt;/th&gt;
&lt;th&gt;Relative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base input (uncached)&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5-minute cache write&lt;/td&gt;
&lt;td&gt;$3.75&lt;/td&gt;
&lt;td&gt;125%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1-hour cache write&lt;/td&gt;
&lt;td&gt;$6.00&lt;/td&gt;
&lt;td&gt;200%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache read&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.30&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Opus is even more dramatic: base input $5, cache read only $0.50.&lt;/p&gt;

&lt;p&gt;Meaning: the first time you send a large context block, it gets written to the cache and you pay a small write premium (25% above base). For the next 5 minutes, resending the same prefix costs 10% of base. The longer the session and the more cache hits accumulate, the lower your average per-token cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Prefix" Actually Means
&lt;/h2&gt;

&lt;p&gt;Before going further, it's worth unpacking the word "prefix." A prompt is an ordered sequence of tokens. Cache matching runs &lt;strong&gt;from the very beginning, token by token&lt;/strong&gt; — and a single differing token breaks everything after it.&lt;/p&gt;

&lt;p&gt;In multi-turn conversations, every new turn &lt;strong&gt;only appends to the tail&lt;/strong&gt;; the prior history stays untouched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn 1: [system] [CLAUDE.md] [Q1]
Turn 2: [system] [CLAUDE.md] [Q1] [A1] [Q2]
         ↑ identical prefix → cache hit at 10% price
                                    ↑ new tail → written to cache
Turn 3: [system] [CLAUDE.md] [Q1] [A1] [Q2] [A2] [Q3]
         ↑ an even longer prefix hits cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So long conversations &lt;strong&gt;aren't a cost disadvantage — they're an advantage&lt;/strong&gt;. The longer the accumulated history, the more tokens per turn get the 90% discount.&lt;/p&gt;

&lt;p&gt;But this only holds while you're strictly appending. If you could go back and edit Turn 5, every token after Turn 5 — even ones that look identical — invalidates because the prefix hash diverges from that point onward. That's the cruelty of "prefix": change one character in the middle and everything downstream is lost.&lt;/p&gt;

&lt;p&gt;Analogy: git commit hashes. Tweak any historical commit and every hash after it changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Topic Switching: How Cache Bills Across A → B → C
&lt;/h2&gt;

&lt;p&gt;The most-overlooked scenario: you discuss topic A with Claude, finish, move to topic B, then topic C — &lt;strong&gt;without &lt;code&gt;/clear&lt;/code&gt; in between&lt;/strong&gt;. A's and B's histories stay glued to the prompt prefix, getting billed at 10% on every single turn while they ride along.&lt;/p&gt;

&lt;p&gt;Concretely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Topic A (30K tokens accumulated over 10 turns)
  → A's 30K written to cache

Switch to B (no /clear)
  Turn 11 = [A's 30K] + [B's new question]
            ↑ 30K × $0.30/M = $0.009 from cache

B accumulates 20K more

Switch to C (still no /clear)
  Every turn = [A's 30K] + [B's 20K] + [C's new question]
               ↑ 50K from cache ≈ $0.015 / turn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;20 turns on C means an extra 20 × $0.015 = $0.30 spent "carrying corpses." A and B may contribute nothing to C, but you're paying for them to ride along.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb for when to &lt;code&gt;/clear&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A, B, C are independent&lt;/strong&gt; (frontend in the morning / SQL in the afternoon / CI at night) → &lt;code&gt;/clear&lt;/code&gt; between topics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A, B, C reference each other&lt;/strong&gt; (A defines spec / B implements / C debugs B) → don't clear; the 10% price on history is cheap and useful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History is heavy but its conclusion is condensable&lt;/strong&gt; (A was a 50K doc you read) → clear, then paste a short summary of A's conclusions as new context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common misconception: "Claude Code automatically detects topic changes and drops stale content." &lt;strong&gt;It doesn't.&lt;/strong&gt; Cache is mechanical prefix matching — it has no semantic understanding. Deciding what to forget is &lt;strong&gt;entirely a human responsibility&lt;/strong&gt;: either &lt;code&gt;/clear&lt;/code&gt; manually, or let auto-compact fire based on context usage (not topic).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Variables That Actually Drive Cost
&lt;/h2&gt;

&lt;p&gt;So the cost model isn't "context size × number of turns." It's these three factors:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cache Hit Rate
&lt;/h3&gt;

&lt;p&gt;In a long, continuous session, every turn's prefix hits the cache written by the previous turn. If a session has accumulated 50K tokens and turn 11 adds 2K new input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Without cache: 51K × $3 = $0.153&lt;/li&gt;
&lt;li&gt;With cache: 50K × $0.30 + 2K × $3 = $0.021&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;About a &lt;strong&gt;7x difference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's worst about aggressive &lt;code&gt;/clear&lt;/code&gt;&lt;/strong&gt;: every new session re-reads &lt;code&gt;CLAUDE.md&lt;/code&gt;, re-learns your project files, re-warms the cache. These warm-up costs can easily exceed the "savings" from keeping context small.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cache Invalidation
&lt;/h3&gt;

&lt;p&gt;Cache requires &lt;strong&gt;100% identical prefixes&lt;/strong&gt; to hit. These actions invalidate it — some loudly, some quietly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Editing message N&lt;/td&gt;
&lt;td&gt;Everything from N onward invalidates (earlier still cached)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adding/removing an MCP tool&lt;/td&gt;
&lt;td&gt;Full invalidation (tool schemas sit at the front)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switching Sonnet ↔ Opus&lt;/td&gt;
&lt;td&gt;Different model, different cache — starts over&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggling web search / citations&lt;/td&gt;
&lt;td&gt;system + message cache invalidates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle &amp;gt; 5 minutes (TTL expires)&lt;/td&gt;
&lt;td&gt;Cache evaporates; next call pays 100% to rewrite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-compact fires&lt;/td&gt;
&lt;td&gt;Prefix is replaced by a summary; subsequent turns warm a fresh cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/clear&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Everything resets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Idle-over-5-minutes is the sneakiest — grab lunch, come back, type a question, and you've quietly paid full write price without any UI warning.&lt;/p&gt;

&lt;p&gt;A nuance about auto-compact worth clarifying: per Anthropic's implementation, the &lt;strong&gt;compaction API call itself&lt;/strong&gt; sends the same prefix as the turn before it, so that call is a cache hit. The real cost lands &lt;strong&gt;after&lt;/strong&gt; compaction — the new session uses the summary in place of the original history as its prefix, so every subsequent turn is warming a brand-new cache from that point on. The net cost is similar to "cache blown away," but the mechanism is prefix replacement, not cache invalidation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. TTL (5 Minutes vs 1 Hour)
&lt;/h3&gt;

&lt;p&gt;Default cache TTL is 5 minutes. Pause for more than 5 minutes and the cache expires — the next call pays full base input price.&lt;/p&gt;

&lt;p&gt;Anthropic offers a 1-hour TTL option at 2x write cost ($6 vs $3) in exchange for longer persistence. Whether it's worth it depends on rhythm — bursty work with 10–30 minute gaps may benefit; continuous work never hits the timeout anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counterintuitive: When Long Sessions Are Cheapest
&lt;/h2&gt;

&lt;p&gt;Combine all three and you arrive at the opposite of "longer = more expensive":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long sessions are cheapest when&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work is continuous, turns within 5 minutes of each other&lt;/li&gt;
&lt;li&gt;No editing of history, no model switches, no MCP churn&lt;/li&gt;
&lt;li&gt;Context stays below the compaction threshold (~155K safe zone)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Short sessions / frequent clearing are costliest when&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every new session re-reads large context (CLAUDE.md, multiple files, skill definitions)&lt;/li&gt;
&lt;li&gt;Every new session pays a "cache warm-up tax"&lt;/li&gt;
&lt;li&gt;You never reap the 10% cache-read discount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My own experience: two hours of continuous work in one session often costs less than splitting the same work into four independent 30-minute sessions — because the latter pays four cold starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Large Context &lt;strong&gt;Is&lt;/strong&gt; Genuinely a Problem
&lt;/h2&gt;

&lt;p&gt;None of this means context can grow forever with no consequence. Two thresholds turn "large context" from a cost problem into a &lt;strong&gt;quality problem&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Approaching the context window limit&lt;/strong&gt; (Sonnet 200K / 1M, Opus 200K)&lt;/p&gt;

&lt;p&gt;Model attention degrades past ~100K tokens, especially on content in the middle (the "lost in the middle" phenomenon). At this point the concern isn't cost — it's that the model &lt;strong&gt;can't find or misuses&lt;/strong&gt; what you gave it earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Auto-compact triggers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code auto-compacts as you approach the limit. Compaction is a major operation — cache fully invalidates, cost spikes, and the result is a summary with possible detail loss.&lt;/p&gt;

&lt;p&gt;So context shouldn't grow unbounded, but the right reset trigger is "task complete" or "about to hit compaction," not "session has been open for X hours."&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Recommendations
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mid-task&lt;/td&gt;
&lt;td&gt;Don't &lt;code&gt;/clear&lt;/code&gt;, continue the session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task done, starting a new one&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/clear&lt;/code&gt; so the next session starts clean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle for &amp;gt;5 minutes&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;/resume&lt;/code&gt; instead of opening a new session (TTL expires but history is preserved)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code open often with idle gaps&lt;/td&gt;
&lt;td&gt;Consider 1h TTL — 2x write cost but idle safety&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context exceeds 155K&lt;/td&gt;
&lt;td&gt;Proactively end the session; don't wait for auto-compact&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To measure your actual cost, try &lt;a href="https://github.com/ryoppippi/ccusage" rel="noopener noreferrer"&gt;ccusage&lt;/a&gt; or &lt;a href="https://dev.to/en/2026/04/07/claude-view-mission-control/"&gt;claude-view&lt;/a&gt;. A high share of &lt;code&gt;cache_read_input_tokens&lt;/code&gt; means you're working efficiently; rising &lt;code&gt;cache_creation_input_tokens&lt;/code&gt; with low reads means cache keeps invalidating — you're burning money.&lt;/p&gt;

&lt;p&gt;"Longer sessions waste more tokens" is a stateless-era intuition, but prompt caching has been rewriting those rules for two years. Check how you actually use Claude Code — the token savings might surprise you.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;Prompt Caching — Claude API Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/costs" rel="noopener noreferrer"&gt;Manage Costs Effectively — Claude Code Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.claudecodecamp.com/p/how-prompt-caching-actually-works-in-claude-code" rel="noopener noreferrer"&gt;How Prompt Caching Actually Works in Claude Code — Claude Code Camp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mindstudio.ai/blog/claude-code-context-compounding-explained-2" rel="noopener noreferrer"&gt;How Context Compounding Works in Claude Code — MindStudio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ryoppippi/ccusage" rel="noopener noreferrer"&gt;ccusage — Claude Code Token Usage CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>promptcaching</category>
      <category>agents</category>
      <category>costoptimization</category>
    </item>
    <item>
      <title>chezmoi: One Dotfiles Repo Across macOS, Linux, and Windows</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Mon, 13 Apr 2026 10:41:33 +0000</pubDate>
      <link>https://forem.com/recca0120/chezmoi-one-dotfiles-repo-across-macos-linux-and-windows-2o3</link>
      <guid>https://forem.com/recca0120/chezmoi-one-dotfiles-repo-across-macos-linux-and-windows-2o3</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/13/chezmoi-dotfiles-management/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My work machine is a MacBook, my home desktop runs Linux, and the company handed me a Windows NUC. All three need synced &lt;code&gt;.gitconfig&lt;/code&gt;, &lt;code&gt;.zshrc&lt;/code&gt;, &lt;code&gt;.tmux.conf&lt;/code&gt; — but each OS has quirks. Windows needs &lt;code&gt;sslCAInfo&lt;/code&gt; pointing at scoop's git cert bundle; macOS uses Homebrew; Linux uses apt.&lt;/p&gt;

&lt;p&gt;I used to hack it with symlinks and shell scripts. Now I use &lt;a href="https://github.com/twpayne/chezmoi" rel="noopener noreferrer"&gt;chezmoi&lt;/a&gt;. One dotfiles repo, three machines, one-line setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not stow, yadm, or dotbot
&lt;/h2&gt;

&lt;p&gt;Plenty of dotfiles managers exist. chezmoi wins on three fronts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Go templates&lt;/strong&gt;: the same file renders differently per OS, no need to maintain three &lt;code&gt;.gitconfig&lt;/code&gt; variants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native encryption&lt;/strong&gt;: age and gpg are first-class, so secrets can live in a public repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onchange scripts&lt;/strong&gt;: the Homebrew bootstrap only re-runs when the package list actually changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;stow is pure symlinks, no templating. yadm wraps git, templating via plugins. dotbot needs a YAML manifest. chezmoi bundles it all into one binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and Init
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;chezmoi

&lt;span class="c"&gt;# Linux&lt;/span&gt;
sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsLS&lt;/span&gt; get.chezmoi.io&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Windows&lt;/span&gt;
winget &lt;span class="nb"&gt;install &lt;/span&gt;twpayne.chezmoi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bootstrap a new machine from an existing repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chezmoi init &lt;span class="nt"&gt;--apply&lt;/span&gt; https://github.com/YOUR_USERNAME/dotfiles.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single line clones the repo, runs the template engine, and writes everything to &lt;code&gt;$HOME&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filename Attribute System
&lt;/h2&gt;

&lt;p&gt;chezmoi uses &lt;strong&gt;filename prefixes&lt;/strong&gt; to encode behavior. The repo layout itself is the manifest — no separate config needed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dot_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target is a hidden file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dot_zshrc&lt;/code&gt; → &lt;code&gt;~/.zshrc&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;private_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User-only permissions (0600)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;private_dot_ssh&lt;/code&gt; → &lt;code&gt;~/.ssh&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;executable_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sets executable bit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;executable_bin_foo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encrypted_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;age/gpg encrypted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;encrypted_dot_env&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;symlink_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a symlink&lt;/td&gt;
&lt;td&gt;&lt;code&gt;symlink_dot_bashrc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;readonly_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strips write permissions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;readonly_dot_config.toml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.tmpl&lt;/code&gt; suffix&lt;/td&gt;
&lt;td&gt;Run through template engine&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dot_gitconfig.tmpl&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Prefixes stack. My repo has combinations like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private_executable_dot_php-cs-fixer.dist.php  → ~/.php-cs-fixer.dist.php (0700)
private_dot_ssh/                              → ~/.ssh (whole dir at 0700)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Templates for Machine Differences
&lt;/h2&gt;

&lt;p&gt;This is chezmoi's killer feature. My &lt;code&gt;dot_gitconfig.tmpl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[user]
    name = {{ .name | quote }}
    email = {{ .email | quote }}

[http]
    sslBackend = openssl
{{ if eq .chezmoi.os "windows" -}}
    sslCAInfo = {{- .chezmoi.homeDir | replace "\\" "/" -}}/scoop/apps/git/current/mingw64/ssl/certs/ca-bundle.crt
{{ end }}

[core]
    autocrlf = false
    symlinks = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.name&lt;/code&gt; and &lt;code&gt;.email&lt;/code&gt; come from &lt;code&gt;~/.config/chezmoi/chezmoi.toml&lt;/code&gt;, so each machine can have its own values. The &lt;code&gt;{{ if eq .chezmoi.os "windows" }}&lt;/code&gt; block only expands on Windows. On apply, chezmoi strips the &lt;code&gt;.tmpl&lt;/code&gt; and writes a clean &lt;code&gt;.gitconfig&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Built-in variables I reach for constantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ .chezmoi.os }}              # "darwin" / "linux" / "windows"
{{ .chezmoi.arch }}            # "amd64" / "arm64"
{{ .chezmoi.hostname }}        # machine name
{{ .chezmoi.username }}        # login user
{{ .chezmoi.homeDir }}         # home directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preview a template without applying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chezmoi execute-template &amp;lt; dot_gitconfig.tmpl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Age Encryption for Secrets
&lt;/h2&gt;

&lt;p&gt;My repo is public, but it contains an SSH key and database password backups. Those are encrypted with &lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;age&lt;/a&gt; before they ever hit a commit.&lt;/p&gt;

&lt;p&gt;Generate an age key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;age-keygen &lt;span class="nt"&gt;-o&lt;/span&gt; ~/key.txt
&lt;span class="c"&gt;# Public key: age1examplepublickeyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure &lt;code&gt;~/.config/chezmoi/chezmoi.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;encryption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"age"&lt;/span&gt;

&lt;span class="nn"&gt;[age]&lt;/span&gt;
    &lt;span class="py"&gt;identity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"~/key.txt"&lt;/span&gt;
    &lt;span class="py"&gt;recipient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"age1examplepublickeyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add files with &lt;code&gt;--encrypt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chezmoi add &lt;span class="nt"&gt;--encrypt&lt;/span&gt; ~/.ssh/id_ed25519
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo only ever stores &lt;code&gt;private_dot_ssh/encrypted_private_id_ed25519.age&lt;/code&gt; — opaque ciphertext. On apply, chezmoi decrypts using &lt;code&gt;~/key.txt&lt;/code&gt; and writes the plaintext to the target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one catastrophic footgun&lt;/strong&gt;: &lt;code&gt;key.txt&lt;/code&gt; itself &lt;strong&gt;must never land in the repo&lt;/strong&gt;. My workflow: GPG-encrypt it and stash it in a password manager. New machines must restore &lt;code&gt;key.txt&lt;/code&gt; manually before running &lt;code&gt;chezmoi init --apply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  run_onchange: Reinstall Only When Lists Change
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;.chezmoiscripts/darwin/run_onchange_00_install-packages.sh.tmpl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{{&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;eq .chezmoi.os &lt;span class="s2"&gt;"darwin"&lt;/span&gt; -&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

brew &lt;span class="nb"&gt;install &lt;/span&gt;mas
brew &lt;span class="nb"&gt;install &lt;/span&gt;asdf

asdf plugin add nodejs
asdf &lt;span class="nb"&gt;install &lt;/span&gt;nodejs latest
asdf &lt;span class="nb"&gt;set &lt;/span&gt;nodejs latest

&lt;span class="c"&gt;# ... many more asdf installs&lt;/span&gt;
&lt;span class="o"&gt;{{&lt;/span&gt; end -&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;run_onchange_&lt;/code&gt; prefix is the key: chezmoi only runs this script when its &lt;strong&gt;content hash changes&lt;/strong&gt;. Unchanged package list means no re-run — no more five-minute &lt;code&gt;brew install&lt;/code&gt; cycles through already-installed tools on every &lt;code&gt;chezmoi apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Script naming variants:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;When It Runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_once_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once per machine, ever, for given content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_onchange_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whenever the contents change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_onchange_before_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Before&lt;/strong&gt; file application (install package manager first)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_onchange_after_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;After&lt;/strong&gt; file application (enable fish plugins last)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The numeric prefix (&lt;code&gt;00_&lt;/code&gt;, &lt;code&gt;01_&lt;/code&gt;, &lt;code&gt;02_&lt;/code&gt;) controls execution order.&lt;/p&gt;

&lt;h2&gt;
  
  
  .chezmoiroot: Source Lives in a Subdirectory
&lt;/h2&gt;

&lt;p&gt;All my files live under &lt;code&gt;home/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;dotfiles/
├── .chezmoiroot        # contains just "home"
├── Readme.md
├── install.sh
├── install.ps1
└── home/
    ├── dot_zshrc.tmpl
    ├── dot_gitconfig.tmpl
    └── .chezmoiscripts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.chezmoiroot&lt;/code&gt; tells chezmoi "source files are under &lt;code&gt;home/&lt;/code&gt;". Now the repo root can host a README, install scripts, and other project artifacts without chezmoi trying to apply them as dotfiles.&lt;/p&gt;

&lt;p&gt;Great for treating your dotfiles repo like a normal project.&lt;/p&gt;

&lt;h2&gt;
  
  
  .chezmoiignore: Skip Certain Files
&lt;/h2&gt;

&lt;p&gt;Same syntax as &lt;code&gt;.gitignore&lt;/code&gt;, but it supports templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;README.md
LICENSE
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;ne&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;chezmoi.os&lt;/span&gt; &lt;span class="s2"&gt;"darwin"&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
.aerospace.toml
Library/
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Non-macOS machines skip the aerospace window manager config and the Library folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command Cheat Sheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chezmoi add ~/.vimrc              &lt;span class="c"&gt;# track an existing file&lt;/span&gt;
chezmoi add &lt;span class="nt"&gt;--encrypt&lt;/span&gt; ~/.env      &lt;span class="c"&gt;# track encrypted&lt;/span&gt;
chezmoi edit ~/.zshrc             &lt;span class="c"&gt;# edit the source file directly&lt;/span&gt;
chezmoi diff                      &lt;span class="c"&gt;# show pending changes&lt;/span&gt;
chezmoi apply                     &lt;span class="c"&gt;# write to $HOME&lt;/span&gt;
chezmoi apply &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;        &lt;span class="c"&gt;# preview without writing&lt;/span&gt;
chezmoi &lt;span class="nb"&gt;cd&lt;/span&gt;                        &lt;span class="c"&gt;# jump to source directory&lt;/span&gt;
chezmoi update                    &lt;span class="c"&gt;# git pull + apply&lt;/span&gt;
chezmoi doctor                    &lt;span class="c"&gt;# check environment health&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;chezmoi doctor&lt;/code&gt; reports the status of encryption tools, template engine, git, and friends. First thing to run when a new machine misbehaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combined with &lt;a href="https://dev.to/en/2026/04/13/zoxide-smarter-cd/"&gt;zoxide&lt;/a&gt;, fish, and More
&lt;/h2&gt;

&lt;p&gt;My fish config, zoxide init, tmux plugins — all managed by chezmoi. New machine ritual:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restore the age key&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply recca0120&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;run_onchange scripts install CLI tools via brew / apt / scoop&lt;/li&gt;
&lt;li&gt;All configs land in place&lt;/li&gt;
&lt;li&gt;Open fish — zoxide, starship, fzf are already wired up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;About 20 minutes end to end, most of it waiting on &lt;code&gt;brew install&lt;/code&gt; downloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downsides and Gotchas
&lt;/h2&gt;

&lt;p&gt;chezmoi isn't free of friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template learning curve&lt;/strong&gt;: Go template syntax isn't beginner-friendly. Whitespace handling with &lt;code&gt;{{- }}&lt;/code&gt; vs. &lt;code&gt;{{ }}&lt;/code&gt; takes practice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Painful debugging&lt;/strong&gt;: template expansion errors are terse — I lean on &lt;code&gt;chezmoi execute-template&lt;/code&gt; to isolate problems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Age key stewardship&lt;/strong&gt;: lose the key, lose every encrypted file forever. Back it up separately (I GPG-encrypt and park it in a password manager)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First apply is destructive&lt;/strong&gt;: if &lt;code&gt;$HOME&lt;/code&gt; already has hand-edited dotfiles, apply overwrites them. Always &lt;code&gt;chezmoi diff&lt;/code&gt; first&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/twpayne/chezmoi" rel="noopener noreferrer"&gt;chezmoi GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chezmoi.io/" rel="noopener noreferrer"&gt;chezmoi Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chezmoi.io/quick-start/" rel="noopener noreferrer"&gt;chezmoi Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;age — Simple File Encryption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://natelandau.com/managing-dotfiles-with-chezmoi/" rel="noopener noreferrer"&gt;Managing Dotfiles With Chezmoi — Nathaniel Landau&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>chezmoi</category>
      <category>dotfiles</category>
      <category>age</category>
      <category>go</category>
    </item>
    <item>
      <title>zoxide: Give cd a Memory — Jump to Any Directory in Two Keystrokes</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Mon, 13 Apr 2026 07:10:46 +0000</pubDate>
      <link>https://forem.com/recca0120/zoxide-give-cd-a-memory-jump-to-any-directory-in-two-keystrokes-5bdo</link>
      <guid>https://forem.com/recca0120/zoxide-give-cd-a-memory-jump-to-any-directory-in-two-keystrokes-5bdo</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/13/zoxide-smarter-cd/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My projects are scattered across a handful of directories with long, inconsistent paths. For years I either mashed &lt;code&gt;cd ~/some/long/path&amp;lt;TAB&amp;gt;&lt;/code&gt; or dragged folders from Finder into the terminal. Then I installed &lt;a href="https://github.com/ajeetdsouza/zoxide" rel="noopener noreferrer"&gt;zoxide&lt;/a&gt;. Now two or three characters is all it takes.&lt;/p&gt;

&lt;p&gt;The trick is that my &lt;code&gt;cd&lt;/code&gt; is no longer a shell builtin. It's zoxide's replacement — same behavior as the original, plus memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What frecency Means
&lt;/h2&gt;

&lt;p&gt;zoxide's algorithm is called frecency (frequency + recency). Every directory you visit earns a score that climbs with use and decays over time. Type &lt;code&gt;cd foo&lt;/code&gt; and zoxide searches its database for paths containing &lt;code&gt;foo&lt;/code&gt;, then jumps to the highest-scoring one.&lt;/p&gt;

&lt;p&gt;Here's what the database looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;zoxide query &lt;span class="nt"&gt;--score&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
 230.0 /Users/demo/projects/frontend
 215.3 /Users/demo/work/api-server
 198.7 /Users/demo/blog
 142.1 /Users/demo/oss/some-tool
  98.5 /Users/demo/Downloads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frequently visited projects float to the top; stale ones sink. Data lives in a local file — fully offline, no network calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and Init
&lt;/h2&gt;

&lt;p&gt;macOS via Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;zoxide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSfL&lt;/span&gt; https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then initialize in your shell config. &lt;strong&gt;The key decision is whether to use &lt;code&gt;--cmd cd&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;&lt;code&gt;zoxide init &amp;lt;shell&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adds &lt;code&gt;z&lt;/code&gt;, &lt;code&gt;zi&lt;/code&gt; commands. Builtin &lt;code&gt;cd&lt;/code&gt; untouched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replace cd&lt;/td&gt;
&lt;td&gt;&lt;code&gt;zoxide init --cmd cd &amp;lt;shell&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replaces &lt;code&gt;cd&lt;/code&gt; outright&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I went with the latter. I use &lt;a href="https://dev.to/en/2024/auto-venv-fish/"&gt;fish shell&lt;/a&gt;, so my config reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.config/fish/config.fish
zoxide init --cmd cd fish | source
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For zsh / bash, use eval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;zoxide init &lt;span class="nt"&gt;--cmd&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why replace &lt;code&gt;cd&lt;/code&gt; wholesale? Because zoxide's &lt;code&gt;cd&lt;/code&gt; is a &lt;strong&gt;superset&lt;/strong&gt; of the builtin: absolute paths, relative paths, &lt;code&gt;cd -&lt;/code&gt;, &lt;code&gt;cd ..&lt;/code&gt; all still work. Frecency lookup only kicks in when the argument isn't a valid path. No regression risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Daily Workflows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Keyword jumps.&lt;/strong&gt; Skip full paths — just type a fragment of the directory name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;blog            &lt;span class="c"&gt;# → ~/work/personal-blog&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;api             &lt;span class="c"&gt;# → ~/work/api-server&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;dotfiles        &lt;span class="c"&gt;# → ~/config/dotfiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Multi-keyword filtering.&lt;/strong&gt; When names collide, chain keywords to narrow down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;work blog       &lt;span class="c"&gt;# → ~/work/personal-blog&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;client api      &lt;span class="c"&gt;# → ~/work/client-project/api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Match rule: every keyword must appear in the path, and the last one must be in the final segment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Interactive selection via &lt;code&gt;zi&lt;/code&gt;.&lt;/strong&gt; When you can't recall the keyword or have multiple candidates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdi               &lt;span class="c"&gt;# since I used --cmd cd, zi becomes cdi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens an &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt; UI listing all candidates with live fuzzy filtering. Install fzf first if you haven't: &lt;code&gt;brew install fzf&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Tricks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Space-triggered completion.&lt;/strong&gt; In fish, typing &lt;code&gt;cd mydir&amp;lt;SPACE&amp;gt;&lt;/code&gt; lists multiple candidates — handy when directories share names. Fish users can also install an enhanced completion pack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fisher &lt;span class="nb"&gt;install &lt;/span&gt;icezyclon/zoxide.fish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query without jumping.&lt;/strong&gt; Preview where zoxide would take you, without actually going:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zoxide query blog
&lt;span class="c"&gt;# → /Users/demo/work/personal-blog&lt;/span&gt;

zoxide query &lt;span class="nt"&gt;--list&lt;/span&gt; blog      &lt;span class="c"&gt;# list all matches&lt;/span&gt;
zoxide query &lt;span class="nt"&gt;--score&lt;/span&gt;          &lt;span class="c"&gt;# view frecency scores&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Manually register a directory.&lt;/strong&gt; For a freshly cloned project you haven't visited yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zoxide add ~/projects/new-repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exclude noise.&lt;/strong&gt; &lt;code&gt;/tmp&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, and friends clutter the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -gx _ZO_EXCLUDE_DIRS "/tmp/*" "*/node_modules/*" "$HOME/.cache/*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Echo destination before jumping.&lt;/strong&gt; Helps catch wrong jumps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -gx _ZO_ECHO 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Migrate from older tools.&lt;/strong&gt; autojump, fasd, z.lua all have import paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zoxide import &lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;autojump ~/.local/share/autojump/autojump.txt
zoxide import &lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;z ~/.z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Combining with yazi and tmux
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;.zshrc&lt;/code&gt; has a function &lt;code&gt;y&lt;/code&gt; that syncs &lt;a href="https://github.com/sxyazi/yazi" rel="noopener noreferrer"&gt;yazi&lt;/a&gt;'s final directory back to the shell on exit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function y
    set tmp (mktemp -t "yazi-cwd.XXXXXX")
    yazi $argv --cwd-file="$tmp"
    set cwd (cat -- "$tmp")
    if [ -n "$cwd" ] &amp;amp;&amp;amp; [ "$cwd" != "$PWD" ]
        cd -- "$cwd"  # this cd is zoxide
    end
    rm -f -- "$tmp"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trailing &lt;code&gt;cd&lt;/code&gt; is zoxide's version, so directories I browse through yazi also feed into the frecency database. The two tools cross-pollinate — both get smarter with use.&lt;/p&gt;

&lt;p&gt;In tmux, each pane is its own shell, but zoxide's database is shared globally. Visit a directory in pane A and pane B can jump there with &lt;code&gt;cd foo&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Not to Use --cmd cd
&lt;/h2&gt;

&lt;p&gt;Honestly, &lt;code&gt;--cmd cd&lt;/code&gt; isn't uncontroversial. Arguments against overriding the builtin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shell scripts might accidentally inherit zoxide behavior&lt;/li&gt;
&lt;li&gt;Shared terminals could confuse other users&lt;/li&gt;
&lt;li&gt;Certain &lt;code&gt;cd&lt;/code&gt; edge cases (like &lt;code&gt;CDPATH&lt;/code&gt;) may behave differently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;zoxide's implementation only overrides &lt;code&gt;cd&lt;/code&gt; in &lt;strong&gt;interactive shells&lt;/strong&gt;, so the first concern is mostly academic. But if you value purity, sticking with the default &lt;code&gt;z&lt;/code&gt; / &lt;code&gt;zi&lt;/code&gt; gets you 95% of the benefit — you just have to pause each time to pick &lt;code&gt;cd&lt;/code&gt; vs. &lt;code&gt;z&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Personally, I prefer &lt;code&gt;--cmd cd&lt;/code&gt;. Muscle memory doesn't want to change, so the tool should adapt to the human, not the other way around.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ajeetdsouza/zoxide" rel="noopener noreferrer"&gt;zoxide GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zoxide.org/" rel="noopener noreferrer"&gt;zoxide Official Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://batsov.com/articles/2025/06/12/zoxide-tips-and-tricks/" rel="noopener noreferrer"&gt;zoxide: Tips and Tricks — Bozhidar Batsov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf Fuzzy Finder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/icezyclon/zoxide.fish" rel="noopener noreferrer"&gt;icezyclon/zoxide.fish — enhanced fish completions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>zoxide</category>
      <category>fish</category>
      <category>terminal</category>
      <category>productivity</category>
    </item>
    <item>
      <title>MemPalace: 170 Tokens to Recall Everything — A Long-Term Memory System for AI Agents</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 07 Apr 2026 17:44:15 +0000</pubDate>
      <link>https://forem.com/recca0120/mempalace-170-tokens-to-recall-everything-a-long-term-memory-system-for-ai-agents-2855</link>
      <guid>https://forem.com/recca0120/mempalace-170-tokens-to-recall-everything-a-long-term-memory-system-for-ai-agents-2855</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/08/mempalace-ai-memory-system/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Six months of daily AI conversations. 19.5 million tokens of history. Start a new session and it remembers nothing. You can dump important things into CLAUDE.md, but that file quickly balloons to thousands of lines, eating up your context window on every startup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/milla-jovovich/mempalace" rel="noopener noreferrer"&gt;MemPalace&lt;/a&gt; takes a different approach: instead of cramming all memories into the prompt, build a structured memory vault that AI queries on demand. Startup loads just 170 tokens, search accuracy hits 96.6%, completely offline, zero API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Memory Palace Architecture
&lt;/h2&gt;

&lt;p&gt;MemPalace uses the ancient Greek memory technique as its organizational metaphor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wing&lt;/strong&gt;: Projects, people, or topics. One wing per major category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Room&lt;/strong&gt;: Sub-topics within a wing — auth, billing, deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hall&lt;/strong&gt;: Memory type corridors shared across all wings

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hall_facts&lt;/code&gt; — locked-in decisions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hall_events&lt;/code&gt; — sessions and milestones&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hall_discoveries&lt;/code&gt; — breakthroughs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hall_preferences&lt;/code&gt; — habits and opinions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hall_advice&lt;/code&gt; — recommendations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Closet&lt;/strong&gt;: Compressed summaries pointing to original content&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Drawer&lt;/strong&gt;: Verbatim original files, preserved losslessly&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Tunnel&lt;/strong&gt;: Cross-wing connections when the same room appears in multiple wings&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The structure alone improves search accuracy. Benchmark results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Search Scope&lt;/th&gt;
&lt;th&gt;R@10&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All closets&lt;/td&gt;
&lt;td&gt;60.9%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Within wing&lt;/td&gt;
&lt;td&gt;73.1%&lt;/td&gt;
&lt;td&gt;+12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wing + hall&lt;/td&gt;
&lt;td&gt;84.8%&lt;/td&gt;
&lt;td&gt;+24%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wing + room&lt;/td&gt;
&lt;td&gt;94.8%&lt;/td&gt;
&lt;td&gt;+34%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Structure alone delivers a 34% accuracy boost — no fancy algorithms needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  AAAK Compression Format
&lt;/h2&gt;

&lt;p&gt;This is MemPalace's most interesting design. AAAK is an AI-readable shorthand achieving 30x compression.&lt;/p&gt;

&lt;p&gt;Original (~1,000 tokens):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Priya manages Driftwood team: Kai (backend, 3 years), Soren (frontend),
Maya (infrastructure), Leo (junior, started last month). Building SaaS
analytics platform. Current sprint: auth migration to Clerk. Kai
recommended Clerk over Auth0 based on pricing and DX.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AAAK format (~120 tokens):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TEAM: PRI(lead) | KAI(backend,3yr) SOR(frontend) MAY(infra) LEO(junior,new)
PROJ: DRIFTWOOD(saas.analytics) | SPRINT: auth.migration→clerk
DECISION: KAI.rec:clerk&amp;gt;auth0(pricing+dx) | ★★★★
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key point: no decoder required. Any LLM reads it natively — Claude, GPT, Llama, Mistral. It's essentially structured English abbreviations, not binary encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layered Memory Loading
&lt;/h2&gt;

&lt;p&gt;MemPalace divides memory into four layers, loaded incrementally:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;When Loaded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L0&lt;/td&gt;
&lt;td&gt;Identity — who is this AI&lt;/td&gt;
&lt;td&gt;~50 tokens&lt;/td&gt;
&lt;td&gt;Always&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L1&lt;/td&gt;
&lt;td&gt;Critical facts — team, projects, preferences&lt;/td&gt;
&lt;td&gt;~120 tokens (AAAK)&lt;/td&gt;
&lt;td&gt;Always&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2&lt;/td&gt;
&lt;td&gt;Room recall — recent sessions&lt;/td&gt;
&lt;td&gt;On demand&lt;/td&gt;
&lt;td&gt;When topic surfaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3&lt;/td&gt;
&lt;td&gt;Deep search — semantic across all closets&lt;/td&gt;
&lt;td&gt;On demand&lt;/td&gt;
&lt;td&gt;When explicitly asked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Startup loads only L0 + L1, about 170 tokens total. Compared to alternatives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Tokens Loaded&lt;/th&gt;
&lt;th&gt;Annual Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Paste everything&lt;/td&gt;
&lt;td&gt;19.5M — impossible&lt;/td&gt;
&lt;td&gt;Impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM summaries&lt;/td&gt;
&lt;td&gt;~650K&lt;/td&gt;
&lt;td&gt;~$507&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MemPalace wake-up&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~170&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.70&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MemPalace + 5 searches&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~13,500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Knowledge Graph: Facts Have Expiry Dates
&lt;/h2&gt;

&lt;p&gt;MemPalace includes a temporal knowledge graph stored in local SQLite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_triple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;works_on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Orion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-06-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_triple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maya&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assigned_to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auth-migration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid_from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-01-15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Kai leaves the Orion project
&lt;/span&gt;&lt;span class="n"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;works_on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Orion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ended&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-03-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Query current state
&lt;/span&gt;&lt;span class="n"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# → [Kai → works_on → Orion (ended), Kai → recommended → Clerk]
&lt;/span&gt;
&lt;span class="c1"&gt;# Historical queries
&lt;/span&gt;&lt;span class="n"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maya&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-01-20&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# → [Maya → assigned_to → auth-migration (active)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every fact has a validity window. Invalidation marks end dates without deletion. This solves the most common CLAUDE.md problem: stale information that nobody cleans up, causing the AI to act on outdated data.&lt;/p&gt;

&lt;p&gt;The knowledge graph also detects contradictions — tasks assigned to the wrong person, tenure mismatches, outdated sprint end dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MCP Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add mempalace &lt;span class="nt"&gt;--&lt;/span&gt; python &lt;span class="nt"&gt;-m&lt;/span&gt; mempalace.mcp_server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, Claude Code auto-discovers 19 MCP tools covering search, storage, knowledge graph queries, and agent diaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-Save Hooks
&lt;/h3&gt;

&lt;p&gt;Add two hooks to your Claude Code configuration:&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;"hooks"&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;"Stop"&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;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"hooks"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/mempalace/hooks/mempal_save_hook.sh"&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;"PreCompact"&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;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"hooks"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/mempalace/hooks/mempal_precompact_hook.sh"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Save hook&lt;/strong&gt;: Fires every 15 messages, auto-extracts topics, decisions, and code changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PreCompact hook&lt;/strong&gt;: Fires before context compression, emergency-saving current memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No need to manually tell the AI to remember things — it saves automatically.&lt;/p&gt;

&lt;p&gt;I previously covered &lt;a href="https://dev.to/en/2026/04/07/claude-view-mission-control/"&gt;claude-view&lt;/a&gt;, which monitors Claude Code sessions and costs from the outside. MemPalace extends AI's memory from the inside. They're complementary — claude-view shows you what AI did, MemPalace helps AI remember what it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specialist Agents
&lt;/h2&gt;

&lt;p&gt;Create focused agents with independent memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/.mempalace/agents/
 ├── reviewer.json    &lt;span class="c"&gt;# code review patterns, bug records&lt;/span&gt;
 ├── architect.json   &lt;span class="c"&gt;# design decisions, trade-offs&lt;/span&gt;
 └── ops.json         &lt;span class="c"&gt;# deploys, incidents, infrastructure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent maintains its own wing and AAAK diary, accumulating domain expertise across sessions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Agent writes findings
&lt;/span&gt;&lt;span class="nf"&gt;mempalace_diary_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PR#42|auth.bypass.found|missing.middleware.check|pattern:3rd.quarter|★★★★&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Agent reads history
&lt;/span&gt;&lt;span class="nf"&gt;mempalace_diary_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to stuff agent descriptions into CLAUDE.md. One line suffices: "You have MemPalace agents. Run mempalace_list_agents to see them."&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;mempalace

&lt;span class="c"&gt;# Initialize&lt;/span&gt;
mempalace init ~/projects/myapp

&lt;span class="c"&gt;# Mine different sources&lt;/span&gt;
mempalace mine ~/projects/myapp              &lt;span class="c"&gt;# project code&lt;/span&gt;
mempalace mine ~/chats/ &lt;span class="nt"&gt;--mode&lt;/span&gt; convos        &lt;span class="c"&gt;# conversation history&lt;/span&gt;
mempalace mine ~/chats/ &lt;span class="nt"&gt;--mode&lt;/span&gt; convos &lt;span class="nt"&gt;--extract&lt;/span&gt; general  &lt;span class="c"&gt;# classified import&lt;/span&gt;

&lt;span class="c"&gt;# Search&lt;/span&gt;
mempalace search &lt;span class="s2"&gt;"why did we switch to GraphQL"&lt;/span&gt;

&lt;span class="c"&gt;# Generate startup context&lt;/span&gt;
mempalace wake-up &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; context.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supports importing Claude conversations, ChatGPT exports, and Slack exports. Large files can be split first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mempalace &lt;span class="nb"&gt;split&lt;/span&gt; ~/chats/ &lt;span class="nt"&gt;--dry-run&lt;/span&gt;   &lt;span class="c"&gt;# preview&lt;/span&gt;
mempalace &lt;span class="nb"&gt;split&lt;/span&gt; ~/chats/             &lt;span class="c"&gt;# split into individual sessions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compared to CLAUDE.md
&lt;/h2&gt;

&lt;p&gt;CLAUDE.md is a flat text file — all information mixed together, no temporal awareness, fully loaded on every startup. MemPalace is a structured memory vault with layered loading, temporal knowledge graphs, and semantic search.&lt;/p&gt;

&lt;p&gt;That said, MemPalace isn't perfect. It requires a Python environment, MCP server setup, and hook configuration. If you only need to remember a few coding conventions, CLAUDE.md is sufficient. MemPalace's value shows in long-term, large-scale, cross-project memory management.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/milla-jovovich/mempalace" rel="noopener noreferrer"&gt;MemPalace GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/milla-jovovich/mempalace#aaak-compression" rel="noopener noreferrer"&gt;AAAK Compression Format Spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/milla-jovovich/mempalace#benchmarks" rel="noopener noreferrer"&gt;LongMemEval Benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>claudecode</category>
      <category>mcp</category>
      <category>python</category>
    </item>
    <item>
      <title>NodeWarden: Bitwarden on Cloudflare Workers — No Server Required</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 07 Apr 2026 09:36:33 +0000</pubDate>
      <link>https://forem.com/recca0120/nodewarden-bitwarden-on-cloudflare-workers-no-server-required-1677</link>
      <guid>https://forem.com/recca0120/nodewarden-bitwarden-on-cloudflare-workers-no-server-required-1677</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/07/nodewarden-bitwarden-cloudflare-workers/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Self-hosting Bitwarden gives you two paths. The official version requires Docker and eats memory. &lt;a href="https://github.com/dani-garcia/vaultwarden" rel="noopener noreferrer"&gt;Vaultwarden&lt;/a&gt; rewrites it in Rust, much lighter, but you still need a VPS, HTTPS configuration, regular updates, and database backups.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/shuaiplus/nodewarden" rel="noopener noreferrer"&gt;NodeWarden&lt;/a&gt; takes a third path: run directly on Cloudflare Workers. No VPS, no SSL management, no uptime monitoring. Cloudflare's free tier is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Differs from Vaultwarden
&lt;/h2&gt;

&lt;p&gt;Vaultwarden is the most popular third-party Bitwarden server, written in Rust, running in Docker. NodeWarden is written in TypeScript, running on Cloudflare Workers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Vaultwarden&lt;/th&gt;
&lt;th&gt;NodeWarden&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Docker / VPS&lt;/td&gt;
&lt;td&gt;Cloudflare Workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;SQLite / MySQL / PostgreSQL&lt;/td&gt;
&lt;td&gt;Cloudflare D1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attachment storage&lt;/td&gt;
&lt;td&gt;Local filesystem&lt;/td&gt;
&lt;td&gt;R2 or KV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;Self-configured&lt;/td&gt;
&lt;td&gt;Cloudflare handles it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Manual updates and backups&lt;/td&gt;
&lt;td&gt;Fork + auto-sync upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;VPS monthly fee&lt;/td&gt;
&lt;td&gt;Cloudflare free tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Organizations/Collections&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest difference is operational burden. Vaultwarden needs you to maintain a VPS. NodeWarden is fully serverless. The downside is no organization or collection features, making it unsuitable for teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Architecture
&lt;/h2&gt;

&lt;p&gt;NodeWarden is built entirely on Cloudflare's infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compute&lt;/strong&gt;: Cloudflare Workers (serverless)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: D1 (Cloudflare's SQLite)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attachment storage&lt;/strong&gt;: R2 (object storage) or KV (key-value)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Preact (original Web Vault interface)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two storage options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Credit card required&lt;/th&gt;
&lt;th&gt;Max attachment size&lt;/th&gt;
&lt;th&gt;Free quota&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100 MB (adjustable)&lt;/td&gt;
&lt;td&gt;10 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KV&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;25 MiB (hard limit)&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you don't want to add a credit card, use KV mode. 1 GB free quota is more than enough for personal password management. Only consider R2 if you need large attachments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;p&gt;Compared to official Bitwarden, everything needed for personal use is covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Vault password manager interface&lt;/li&gt;
&lt;li&gt;Full sync (&lt;code&gt;/api/sync&lt;/code&gt;), compatible with official clients&lt;/li&gt;
&lt;li&gt;Attachment upload and download&lt;/li&gt;
&lt;li&gt;Send feature (text and files)&lt;/li&gt;
&lt;li&gt;Import/export (Bitwarden JSON/CSV, ZIP with attachments)&lt;/li&gt;
&lt;li&gt;TOTP and Steam TOTP&lt;/li&gt;
&lt;li&gt;Multi-user (invitation code registration)&lt;/li&gt;
&lt;li&gt;Password hints (viewable directly in web, no email required)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NodeWarden adds one feature the official version lacks: a &lt;strong&gt;cloud backup center&lt;/strong&gt;. Supports WebDAV and E3 protocol for scheduled backups, including &lt;code&gt;db.json&lt;/code&gt;, &lt;code&gt;manifest.json&lt;/code&gt;, and &lt;code&gt;attachments/&lt;/code&gt; directory. During restoration, missing attachments are safely skipped without leaving broken records.&lt;/p&gt;

&lt;p&gt;Not supported: organizations, collections, permission management, SSO, SCIM, enterprise directories. These are team features unnecessary for personal use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client Compatibility
&lt;/h3&gt;

&lt;p&gt;Tested and working:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows desktop&lt;/li&gt;
&lt;li&gt;Mobile apps (iOS / Android)&lt;/li&gt;
&lt;li&gt;Browser extensions&lt;/li&gt;
&lt;li&gt;Linux desktop&lt;/li&gt;
&lt;li&gt;macOS desktop (not fully verified)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web Deployment (Recommended)
&lt;/h3&gt;

&lt;p&gt;The simplest approach, no local tools needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork the &lt;a href="https://github.com/shuaiplus/nodewarden" rel="noopener noreferrer"&gt;NodeWarden repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to the &lt;a href="https://dash.cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare Workers console&lt;/a&gt; and create a new project&lt;/li&gt;
&lt;li&gt;Choose Continue with GitHub, point to your forked repo&lt;/li&gt;
&lt;li&gt;Keep default settings and deploy&lt;/li&gt;
&lt;li&gt;For KV mode, change the deploy command to &lt;code&gt;npm run deploy:kv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;JWT_SECRET&lt;/code&gt; environment variable (at least 32 random characters)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole process takes under five minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/shuaiplus/NodeWarden.git
&lt;span class="nb"&gt;cd &lt;/span&gt;NodeWarden
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npx wrangler login

&lt;span class="c"&gt;# R2 mode&lt;/span&gt;
npm run deploy

&lt;span class="c"&gt;# KV mode&lt;/span&gt;
npm run deploy:kv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev      &lt;span class="c"&gt;# R2 mode&lt;/span&gt;
npm run dev:kv   &lt;span class="c"&gt;# KV mode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automatic Updates
&lt;/h2&gt;

&lt;p&gt;After forking, enable the &lt;code&gt;Sync upstream&lt;/code&gt; workflow in GitHub Actions. It auto-syncs with upstream daily at 3am. For manual updates, click Sync fork → Update branch on your fork page.&lt;/p&gt;

&lt;h2&gt;
  
  
  NodeWarden or Vaultwarden
&lt;/h2&gt;

&lt;p&gt;If you already have a stable VPS, Vaultwarden is more feature-complete with a larger community. Organizations, collections, and login 2FA are all supported.&lt;/p&gt;

&lt;p&gt;NodeWarden fits these scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No VPS management&lt;/strong&gt;. No server means no maintenance — no uptime worries, no expired SSL certs, no full disks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero budget&lt;/strong&gt;. Cloudflare's free tier is plenty for personal use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo user&lt;/strong&gt;. No need for organizations and permission management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offsite backups&lt;/strong&gt;. Built-in WebDAV backup is more convenient than Vaultwarden's approach&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main risk is that your password vault runs on Cloudflare's infrastructure. D1 and Workers are relatively new services. While Cloudflare probably won't shut them down suddenly, free tier limits and terms can change anytime. Regular WebDAV backups are essential.&lt;/p&gt;

&lt;p&gt;Also note that NodeWarden hasn't undergone the same level of community security review as Vaultwarden. Password managers are high-sensitivity applications — assess the risk yourself before using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shuaiplus/nodewarden" rel="noopener noreferrer"&gt;NodeWarden GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dani-garcia/vaultwarden" rel="noopener noreferrer"&gt;Vaultwarden — Rust-based Bitwarden-compatible Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/d1/" rel="noopener noreferrer"&gt;Cloudflare D1 Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/r2/" rel="noopener noreferrer"&gt;Cloudflare R2 Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bitwarden.com/" rel="noopener noreferrer"&gt;Bitwarden Official Website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloudflare</category>
      <category>bitwarden</category>
      <category>typescript</category>
      <category>serverless</category>
    </item>
    <item>
      <title>claude-view: Mission Control for Claude Code — Live Session Monitoring, Cost Tracking, and Analytics</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 07 Apr 2026 09:32:23 +0000</pubDate>
      <link>https://forem.com/recca0120/claude-view-mission-control-for-claude-code-live-session-monitoring-cost-tracking-and-analytics-14ik</link>
      <guid>https://forem.com/recca0120/claude-view-mission-control-for-claude-code-live-session-monitoring-cost-tracking-and-analytics-14ik</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/07/claude-view-mission-control/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After using Claude Code for a while, the most common question I get is "how much are you spending per month?" Honestly, I can't answer that. Claude Code's terminal interface doesn't show cumulative token costs, how many sub-agents ran, or which session burned the most money.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tombelieber/claude-view" rel="noopener noreferrer"&gt;claude-view&lt;/a&gt; fills that gap. One command opens a dashboard that monitors every Claude Code session on your machine in real-time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx claude-view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What It Shows You
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Live Session Monitoring
&lt;/h3&gt;

&lt;p&gt;Open the dashboard and you see all running Claude Code sessions, each card showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Last message&lt;/li&gt;
&lt;li&gt;Model in use (Opus, Sonnet, Haiku)&lt;/li&gt;
&lt;li&gt;Current cost and token count&lt;/li&gt;
&lt;li&gt;Context window utilization (live percentage)&lt;/li&gt;
&lt;li&gt;Prompt cache countdown timer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cards can be arranged in multiple layouts: Grid, List, Kanban, Monitor. Kanban mode groups sessions by project/branch in swimlanes — great when running multiple projects simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversation Browser
&lt;/h3&gt;

&lt;p&gt;Click into any session for the full conversation history. Unlike the terminal view, claude-view visualizes tool calls — file reads, edits, bash commands, and MCP calls each get their own cards.&lt;/p&gt;

&lt;p&gt;A Developer Mode toggle reveals hook metadata, event cards, and raw JSON. Invaluable for debugging.&lt;/p&gt;

&lt;p&gt;Conversations can be exported as Markdown for documentation or feeding back to Claude for continuation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sub-agent Tree View
&lt;/h3&gt;

&lt;p&gt;Claude Code spawns sub-agents for subtasks. In the terminal you only see one level. claude-view renders the full tree structure with per-agent cost and token breakdowns at a glance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full-Text Search
&lt;/h3&gt;

&lt;p&gt;The search engine is &lt;a href="https://github.com/quickwit-oss/tantivy" rel="noopener noreferrer"&gt;Tantivy&lt;/a&gt;, a Rust-native Lucene-class full-text indexer. Search response times across 1,500 sessions stay under 50ms.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cmd+K&lt;/code&gt; opens a command palette for quick session jumping and view switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics: Where Did the Money Go
&lt;/h2&gt;

&lt;p&gt;This is where I see the most value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboard Metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Week-over-week session count, token usage, and cost comparison&lt;/li&gt;
&lt;li&gt;90-day GitHub-style activity heatmap&lt;/li&gt;
&lt;li&gt;Most-used skills, commands, and MCP tools leaderboards&lt;/li&gt;
&lt;li&gt;Most active projects bar chart&lt;/li&gt;
&lt;li&gt;Cross-session totals for edits, reads, and bash commands&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI Contributions Tracking
&lt;/h3&gt;

&lt;p&gt;This feature quantifies Claude Code's output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lines added/removed, files touched, commit counts&lt;/li&gt;
&lt;li&gt;Cost per commit, cost per session, cost per line ROI&lt;/li&gt;
&lt;li&gt;Opus vs Sonnet vs Haiku side-by-side comparison&lt;/li&gt;
&lt;li&gt;Re-edit rate: tracking whether your prompt quality is improving&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also an experimental AI Fluency Score (0-100), calculated from your session history to measure how effectively you use AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  85 MCP Tools
&lt;/h2&gt;

&lt;p&gt;claude-view ships a plugin (&lt;code&gt;@claude-view/plugin&lt;/code&gt;) that auto-loads with every Claude Code session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin add @claude-view/plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin provides 85 MCP tools: 8 hand-crafted core tools plus 77 auto-generated from the OpenAPI spec.&lt;/p&gt;

&lt;p&gt;The core 8:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list_sessions&lt;/code&gt;, &lt;code&gt;get_session&lt;/code&gt;, &lt;code&gt;search_sessions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_stats&lt;/code&gt;, &lt;code&gt;get_fluency_score&lt;/code&gt;, &lt;code&gt;get_token_stats&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_live_sessions&lt;/code&gt;, &lt;code&gt;get_live_summary&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once installed, you can ask Claude Code "how much did I spend today" or "which session took the longest last week" — it queries claude-view via MCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  9 Built-in Skills
&lt;/h3&gt;

&lt;p&gt;Beyond MCP tools, there are 9 built-in skills:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/session-recap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Summarize commits, metrics, duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/daily-cost&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Today's spending and tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/standup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multi-session work log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/coaching&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI usage tips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/insights&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Behavioral pattern analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/project-overview&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cross-session project summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Natural language search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/export-data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CSV/JSON exports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/team-status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Team activity overview&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Technical Architecture
&lt;/h2&gt;

&lt;p&gt;claude-view uses Rust for the backend and React for the frontend.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web framework&lt;/td&gt;
&lt;td&gt;Axum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search engine&lt;/td&gt;
&lt;td&gt;Tantivy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File I/O&lt;/td&gt;
&lt;td&gt;Memory-mapped I/O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time&lt;/td&gt;
&lt;td&gt;SSE + WebSocket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React + Vite + Dockview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monorepo&lt;/td&gt;
&lt;td&gt;Turbo + Bun&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Performance benchmarks (M-series Mac, 1,493 sessions):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;claude-view&lt;/th&gt;
&lt;th&gt;Typical Electron Dashboard&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Download&lt;/td&gt;
&lt;td&gt;~10 MB&lt;/td&gt;
&lt;td&gt;150-300 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On disk&lt;/td&gt;
&lt;td&gt;~27 MB&lt;/td&gt;
&lt;td&gt;300-500 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup&lt;/td&gt;
&lt;td&gt;&amp;lt;500 ms&lt;/td&gt;
&lt;td&gt;3-8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;~50 MB&lt;/td&gt;
&lt;td&gt;300-800 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index 1,500 sessions&lt;/td&gt;
&lt;td&gt;&amp;lt;1 s&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rust's mmap + SIMD-accelerated JSONL parsing enables zero-copy from parse to response. Compared to Electron dashboards, it's 10x smaller and uses 6x less memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Three options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Recommended&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.claudeview.ai/install.sh | sh

&lt;span class="c"&gt;# Or via npx&lt;/span&gt;
npx claude-view

&lt;span class="c"&gt;# Install plugin (auto-starts with Claude Code)&lt;/span&gt;
claude plugin add @claude-view/plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only prerequisite: Claude Code installed. Dashboard runs at &lt;code&gt;http://localhost:47892&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All data stays local, zero telemetry, no account required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compared to Other Tools
&lt;/h2&gt;

&lt;p&gt;There are similar tools, but with different positioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ccusage&lt;/strong&gt;: CLI tool, token stats only, no GUI, no live monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;opcode&lt;/strong&gt;: Tauri-based GUI with session management but no multi-session chat browsing or search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodePilot&lt;/strong&gt;: Electron chat UI for interacting &lt;em&gt;with&lt;/em&gt; Claude Code, not monitoring it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;claude-view is positioned as monitoring and analytics. If you already work in the terminal with Claude Code, it doesn't change your workflow — it just shows you more information.&lt;/p&gt;

&lt;p&gt;I previously covered &lt;a href="https://dev.to/en/2026/04/07/aionui-ai-cowork-app/"&gt;AionUi&lt;/a&gt;, which unifies multiple agents into one GUI. claude-view takes a different approach: keep working in the terminal, but add a dashboard for tracking. The two can work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Needs This
&lt;/h2&gt;

&lt;p&gt;If you use Claude Code occasionally, you probably don't need this tool.&lt;/p&gt;

&lt;p&gt;But if you use it daily, run multiple sessions simultaneously, and want to know where the money goes, which model gives the best ROI, and whether your prompt quality is improving — claude-view provides information density that the terminal simply can't match.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tombelieber/claude-view" rel="noopener noreferrer"&gt;claude-view GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://claudeview.ai" rel="noopener noreferrer"&gt;claude-view Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/quickwit-oss/tantivy" rel="noopener noreferrer"&gt;Tantivy Full-Text Search Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tokio-rs/axum" rel="noopener noreferrer"&gt;Axum Web Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>agents</category>
      <category>mcp</category>
      <category>rust</category>
    </item>
    <item>
      <title>bb-browser: No Scraping, No API Keys — Your Browser Is the API</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 07 Apr 2026 03:37:23 +0000</pubDate>
      <link>https://forem.com/recca0120/bb-browser-no-scraping-no-api-keys-your-browser-is-the-api-1j8l</link>
      <guid>https://forem.com/recca0120/bb-browser-no-scraping-no-api-keys-your-browser-is-the-api-1j8l</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/07/bb-browser-your-browser-is-the-api/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want Twitter search results? Traditional approaches give you three paths: apply for an API key (rate-limited), write a scraper (get IP-banned), or use Playwright with a headless browser (detected as non-human).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/epiral/bb-browser" rel="noopener noreferrer"&gt;bb-browser&lt;/a&gt; takes a fourth path: use the Chrome you already have open. You're logged into Twitter, the cookies are right there, and bb-browser runs &lt;code&gt;fetch()&lt;/code&gt; inside that tab. From the website's perspective, it's just you browsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Differs from Scrapers and Playwright
&lt;/h2&gt;

&lt;p&gt;Let's get the differences clear.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;bb-browser&lt;/th&gt;
&lt;th&gt;Playwright / Selenium&lt;/th&gt;
&lt;th&gt;Scrapers (requests, Scrapy)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;Your real Chrome&lt;/td&gt;
&lt;td&gt;Isolated headless browser&lt;/td&gt;
&lt;td&gt;No browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login state&lt;/td&gt;
&lt;td&gt;Already logged in&lt;/td&gt;
&lt;td&gt;Must re-login or inject cookies&lt;/td&gt;
&lt;td&gt;Manual cookie handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anti-bot detection&lt;/td&gt;
&lt;td&gt;Invisible (it IS the real user)&lt;/td&gt;
&lt;td&gt;Easily detected&lt;/td&gt;
&lt;td&gt;Easily blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fingerprint&lt;/td&gt;
&lt;td&gt;Your real fingerprint&lt;/td&gt;
&lt;td&gt;Headless browser fingerprint&lt;/td&gt;
&lt;td&gt;No fingerprint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key insight: bb-browser doesn't launch a new browser instance. It connects to your running Chrome via CDP (Chrome DevTools Protocol) and injects code into tabs. The User-Agent, cookies, and TLS fingerprint the website sees are all real — because it is your actual browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent (Claude Code, Codex, Cursor)
         │ CLI or MCP (stdio)
         ▼
bb-browser CLI ──HTTP──▶ Daemon ──CDP WebSocket──▶ Real Browser
                            │
                     ┌──────┴──────┐
                     │ Per-tab event│
                     │ cache (net,  │
                     │ console)     │
                     └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;bb-browser runs a daemon (default &lt;code&gt;127.0.0.1:19824&lt;/code&gt;) that communicates with Chrome via CDP WebSocket. CLI commands go to the daemon, which executes them in the corresponding tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and Basic Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; bb-browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull community adapters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bb-browser site update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bb-browser site zhihu/hot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens a Zhihu tab (if you're already logged in), uses your cookies to fetch the trending questions list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured Output
&lt;/h3&gt;

&lt;p&gt;All commands support &lt;code&gt;--json&lt;/code&gt; and &lt;code&gt;--jq&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bb-browser site xueqiu/hot-stock 5 &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.items[] | {name, changePercent}'&lt;/span&gt;
&lt;span class="c"&gt;# {"name":"云天化","changePercent":"2.08%"}&lt;/span&gt;
&lt;span class="c"&gt;# {"name":"东芯股份","changePercent":"-7.60%"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Browser Operations
&lt;/h3&gt;

&lt;p&gt;Beyond running adapters, you can directly control the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bb-browser open https://example.com     &lt;span class="c"&gt;# Open URL&lt;/span&gt;
bb-browser snapshot &lt;span class="nt"&gt;-i&lt;/span&gt;                  &lt;span class="c"&gt;# Accessibility tree snapshot&lt;/span&gt;
bb-browser click @3                     &lt;span class="c"&gt;# Click element&lt;/span&gt;
bb-browser fill @5 &lt;span class="s2"&gt;"hello"&lt;/span&gt;             &lt;span class="c"&gt;# Fill input&lt;/span&gt;
bb-browser &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"document.title"&lt;/span&gt;       &lt;span class="c"&gt;# Execute JavaScript&lt;/span&gt;
bb-browser fetch URL &lt;span class="nt"&gt;--json&lt;/span&gt;            &lt;span class="c"&gt;# Authenticated fetch&lt;/span&gt;
bb-browser screenshot                  &lt;span class="c"&gt;# Take screenshot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  36 Platforms, 103 Commands
&lt;/h2&gt;

&lt;p&gt;bb-browser's adapters cover a wide range:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt;: Google, Baidu, Bing, DuckDuckGo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social&lt;/strong&gt;: Twitter/X, Reddit, Weibo, Xiaohongshu, LinkedIn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev&lt;/strong&gt;: GitHub, StackOverflow, Hacker News, npm, PyPI, arXiv, V2EX, Dev.to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;News&lt;/strong&gt;: BBC, Reuters, 36kr, Toutiao&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video&lt;/strong&gt;: YouTube, Bilibili&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finance&lt;/strong&gt;: Xueqiu, Yahoo Finance, Eastmoney&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge&lt;/strong&gt;: Wikipedia, Zhihu&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each adapter is a single JavaScript file, community-driven. To add a new platform, write a JS file and submit it to the &lt;code&gt;bb-sites&lt;/code&gt; repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Levels of Adapter Complexity
&lt;/h2&gt;

&lt;p&gt;Not every website is equally straightforward. bb-browser categorizes adapters into three levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Dev Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Level 1&lt;/td&gt;
&lt;td&gt;Direct fetch with cookies&lt;/td&gt;
&lt;td&gt;Reddit, GitHub&lt;/td&gt;
&lt;td&gt;~1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 2&lt;/td&gt;
&lt;td&gt;Bearer token + CSRF extraction&lt;/td&gt;
&lt;td&gt;Twitter, Zhihu&lt;/td&gt;
&lt;td&gt;~3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 3&lt;/td&gt;
&lt;td&gt;Webpack injection or Pinia store&lt;/td&gt;
&lt;td&gt;Twitter search&lt;/td&gt;
&lt;td&gt;~10 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Level 1 is simplest — some sites' APIs work with just cookies. Level 3 is most complex, requiring reverse engineering of frontend bundles, extracting data from Webpack's &lt;code&gt;__webpack_require__&lt;/code&gt; or Vue's Pinia store.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Server for AI Agents
&lt;/h2&gt;

&lt;p&gt;This is bb-browser's most compelling use case. Configure it as an MCP server, and Claude Code or Cursor can directly access any website your browser can see.&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;"mcpServers"&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;"bb-browser"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bb-browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--mcp"&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="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;Once configured, you can tell Claude Code "search arXiv for recent RAG papers" and it will search through bb-browser using your real browser.&lt;/p&gt;

&lt;p&gt;Without bb-browser, an AI agent can only work with files and the terminal. With bb-browser, it can access the entire internet — as you.&lt;/p&gt;

&lt;p&gt;I previously wrote about &lt;a href="https://dev.to/en/2026/03/15/cli-anything-agent-native-cli/"&gt;CLI-Anything&lt;/a&gt;, which wraps desktop software as CLIs for agents to call, and &lt;a href="https://dev.to/en/2026/04/07/aionui-ai-cowork-app/"&gt;AionUi&lt;/a&gt;, which provides a unified interface for managing multiple agents. bb-browser extends agent capability from yet another angle: letting it browse the web using your real browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Consider
&lt;/h2&gt;

&lt;p&gt;A few things to think through before using it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It uses your real account&lt;/strong&gt;. bb-browser acts on your behalf. If the operation frequency is too high, your account might get flagged. It's not an invisible scraper — it IS you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;. The daemon binds to localhost by default, but if you open it to &lt;code&gt;0.0.0.0&lt;/code&gt;, anyone who can reach your machine can control your browser. Use Tailscale or ZeroTier for safer remote access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adapter quality varies&lt;/strong&gt;. Community-driven means broad coverage, but some adapters may lag behind website redesigns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS users&lt;/strong&gt;: watch for IPv6 issues — add &lt;code&gt;--host 127.0.0.1&lt;/code&gt; to the daemon command&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use It
&lt;/h2&gt;

&lt;p&gt;bb-browser isn't for scraping millions of records. Use Scrapy for that.&lt;/p&gt;

&lt;p&gt;It's ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Giving AI agents web access without applying for API keys one by one&lt;/li&gt;
&lt;li&gt;Quickly pulling structured data from platforms you're already logged into&lt;/li&gt;
&lt;li&gt;Cross-platform research — query arXiv, Twitter, GitHub, Zhihu, and StackOverflow in under a minute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One command does what used to require writing a scraper, and it won't get blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/epiral/bb-browser" rel="noopener noreferrer"&gt;bb-browser GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nicepkg/bb-sites" rel="noopener noreferrer"&gt;bb-sites Community Adapters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;Chrome DevTools Protocol Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>mcp</category>
      <category>browserautomation</category>
      <category>cli</category>
    </item>
    <item>
      <title>AionUi: One Interface for 12+ AI Agents — A Free, Open-Source Cowork Desktop App</title>
      <dc:creator>Recca Tsai</dc:creator>
      <pubDate>Tue, 07 Apr 2026 03:05:44 +0000</pubDate>
      <link>https://forem.com/recca0120/aionui-one-interface-for-12-ai-agents-a-free-open-source-cowork-desktop-app-1mf2</link>
      <guid>https://forem.com/recca0120/aionui-one-interface-for-12-ai-agents-a-free-open-source-cowork-desktop-app-1mf2</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://recca0120.github.io/en/2026/04/07/aionui-ai-cowork-app/" rel="noopener noreferrer"&gt;recca0120.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You've got Claude Code installed. Also Codex. Maybe Qwen Code for Chinese-language tasks. Each tool gets its own terminal window, MCP configs are duplicated across tools, and conversation history is scattered everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/iOfficeAI/AionUi" rel="noopener noreferrer"&gt;AionUi&lt;/a&gt; tackles exactly this: one desktop app that brings all your AI agents under a single interface. Free, open-source, Apache 2.0 licensed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;AionUi is a cross-platform desktop app built with Electron + React, supporting macOS, Windows, and Linux. Its core purpose is unified management of multiple AI coding agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported Agents
&lt;/h3&gt;

&lt;p&gt;AionUi auto-detects CLI tools installed on your machine. Currently supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code, Codex, Qwen Code, Goose AI, OpenClaw, Augment Code&lt;/li&gt;
&lt;li&gt;iFlow CLI, CodeBuddy, Kimi CLI, OpenCode, Factory Droid, GitHub Copilot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over 12 agents total. No extra configuration needed — install the CLI and it shows up in AionUi. If you don't have any CLI tools installed, AionUi has its own built-in agent that works with Google login or API key authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  20+ Model Platforms
&lt;/h3&gt;

&lt;p&gt;Wide model selection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Major platforms&lt;/strong&gt;: Gemini, Claude, OpenAI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud&lt;/strong&gt;: AWS Bedrock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chinese platforms&lt;/strong&gt;: Dashscope (Qwen), Zhipu, Moonshot (Kimi), Baidu Qianfan, Tencent Hunyuan, ModelScope&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local models&lt;/strong&gt;: Ollama, LM Studio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're in mainland China and can't easily access OpenAI or Claude APIs, just switch to Dashscope or Zhipu. For fully offline work, run Ollama with local models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure MCP Once, Sync Everywhere
&lt;/h3&gt;

&lt;p&gt;This is the most practical design choice. Configure MCP (Model Context Protocol) tools once in AionUi, and all agents sync automatically. No more maintaining separate &lt;code&gt;mcp.json&lt;/code&gt; files for each agent — change it in one place, it applies everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  12 Built-in Professional Assistants
&lt;/h2&gt;

&lt;p&gt;AionUi isn't just an agent launcher. It comes with 12 pre-built assistants:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Assistant&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cowork&lt;/td&gt;
&lt;td&gt;Automated task execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPTX Generator&lt;/td&gt;
&lt;td&gt;Presentation creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF to PPT&lt;/td&gt;
&lt;td&gt;Format conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D Game&lt;/td&gt;
&lt;td&gt;Single-file game prototyping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI/UX Pro Max&lt;/td&gt;
&lt;td&gt;57 styles, 95 color palettes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beautiful Mermaid&lt;/td&gt;
&lt;td&gt;Flowcharts, sequence diagrams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Planning with Files&lt;/td&gt;
&lt;td&gt;File-based project planning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Office features (PPT, Word, Excel) are powered by OfficeCLI, producing editable &lt;code&gt;.pptx&lt;/code&gt;, &lt;code&gt;.docx&lt;/code&gt;, and &lt;code&gt;.xlsx&lt;/code&gt; files — not PDF screenshots. PPT output even supports Morph transition animations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduled Tasks: 24/7 Automation
&lt;/h2&gt;

&lt;p&gt;This feature is uncommon in agent tools. You can set up scheduled tasks using natural language, like "every morning at 9am, summarize yesterday's Git commit log." AionUi converts it to a cron expression and runs it automatically.&lt;/p&gt;

&lt;p&gt;Each scheduled task is bound to a conversation, maintaining context. Results are sent back to the conversation window, and can also be pushed to Telegram, Lark (Feishu), or DingTalk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview Panel
&lt;/h2&gt;

&lt;p&gt;AionUi has a built-in file preview supporting many formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documents&lt;/strong&gt;: PDF, Word, Excel, PowerPoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code&lt;/strong&gt;: 30+ languages with syntax highlighting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images&lt;/strong&gt;: PNG, JPG, SVG, WebP, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markup&lt;/strong&gt;: Markdown and HTML with live editing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also tracks file changes, shows Git version history, and supports one-click rollback.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebUI Remote Access
&lt;/h2&gt;

&lt;p&gt;You don't have to sit in front of your computer to use a desktop app. AionUi can serve a WebUI, accessible via QR code or password login from your phone or another computer. Supports both LAN and cross-network access.&lt;/p&gt;

&lt;p&gt;Combined with Telegram, Lark, and DingTalk bot integration, you can send commands to AI agents from your phone and receive results in your chat groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS&lt;/td&gt;
&lt;td&gt;UnoCSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Vitest + Playwright&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;SQLite (local)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All data is stored locally in SQLite — nothing gets uploaded to any server. With local models (Ollama), the entire workflow can run completely offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Download the installer for your platform from &lt;a href="https://github.com/iOfficeAI/AionUi/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;. Homebrew is also supported on macOS.&lt;/p&gt;

&lt;p&gt;Once installed, just open the app. If Claude Code or Codex is already on your machine, AionUi detects them automatically. Otherwise, use the built-in agent with an API key or Google login to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compared to Using Claude Code Alone
&lt;/h2&gt;

&lt;p&gt;Claude Code is powerful, but it's a terminal tool. AionUi doesn't aim to replace it — it puts Claude Code alongside other agents in one managed workspace.&lt;/p&gt;

&lt;p&gt;Key differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent&lt;/strong&gt;: Claude Code only runs Claude; AionUi runs multiple agents simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GUI&lt;/strong&gt;: Full desktop interface with file preview, Office generation, and image processing built in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling&lt;/strong&gt;: Claude Code has no built-in scheduling; AionUi runs tasks 24/7 automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price&lt;/strong&gt;: Claude Code requires API costs or a $100/month subscription; AionUi itself is free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model choice&lt;/strong&gt;: Not locked to one provider — 20+ platforms available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're interested in the AI agent tool ecosystem, I previously wrote about &lt;a href="https://dev.to/en/2026/03/15/cli-anything-agent-native-cli/"&gt;CLI-Anything: A Universal Bridge for AI Agents to Operate Any Software&lt;/a&gt;, which approaches the problem from the opposite angle — wrapping existing software as CLIs for agents to call. AionUi takes the agent management perspective instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current State
&lt;/h2&gt;

&lt;p&gt;AionUi is iterating rapidly, with 4,400+ commits on GitHub. The community is active on Discord (English) and WeChat groups (Chinese).&lt;/p&gt;

&lt;p&gt;One caveat: while the star count is impressive, Electron apps typically have significant memory overhead. If you only use one agent, running the CLI in a terminal is lighter weight. AionUi's value shows when you genuinely need multiple agents, want GUI management, or require scheduling and Office generation features.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/iOfficeAI/AionUi" rel="noopener noreferrer"&gt;AionUi GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aionui.com" rel="noopener noreferrer"&gt;AionUi Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.electronjs.org/" rel="noopener noreferrer"&gt;Electron Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>electron</category>
      <category>claudecode</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
