This commit is contained in:
eric
2026-01-22 01:50:14 +00:00
parent 07438a27e9
commit c15d37458e
36 changed files with 75 additions and 45 deletions

View File

@@ -0,0 +1,27 @@
<!doctype html><html lang=en><head><title>How I Built a Blog Agent that Writes About Itself · Eric X. Liu's Personal Page</title><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=color-scheme content="light dark"><meta http-equiv=Content-Security-Policy content="upgrade-insecure-requests; block-all-mixed-content; default-src 'self'; child-src 'self'; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net/; form-action 'self'; frame-src 'self' https://www.youtube.com https://disqus.com; img-src 'self' https://referrer.disqus.com https://c.disquscdn.com https://*.disqus.com; object-src 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/ https://cdn.jsdelivr.net/; script-src 'self' 'unsafe-inline' https://www.google-analytics.com https://cdn.jsdelivr.net/ https://pagead2.googlesyndication.com https://static.cloudflareinsights.com https://unpkg.com https://ericxliu-me.disqus.com https://disqus.com https://*.disqus.com https://*.disquscdn.com https://unpkg.com; connect-src 'self' https://www.google-analytics.com https://pagead2.googlesyndication.com https://cloudflareinsights.com ws://localhost:1313 ws://localhost:* wss://localhost:* https://links.services.disqus.com https://*.disqus.com;"><meta name=author content="Eric X. Liu"><meta name=description content="I&rsquo;ve been spending a lot of time &ldquo;vibe coding&rdquo; in the Antigravity IDE lately. It&rsquo;s an incredible flow state—intense, iterative, and fast. But it has a major flaw: the context is ephemeral. Once the session is over, that rich history of decisions, wrong turns, and &ldquo;aha!&rdquo; moments is locked away in an opaque, internal format.
I wanted to capture that value. I wanted a system that could take my chaotic coding sessions and distill them into structured, technical blog posts (like the one you&rsquo;re reading right now)."><meta name=keywords content="software engineer,performance engineering,Google engineer,tech blog,software development,performance optimization,Eric Liu,engineering blog,mountain biking,Jeep enthusiast,overlanding,camping,outdoor adventures"><meta name=twitter:card content="summary"><meta name=twitter:title content="How I Built a Blog Agent that Writes About Itself"><meta name=twitter:description content="Ive been spending a lot of time “vibe coding” in the Antigravity IDE lately. Its an incredible flow state—intense, iterative, and fast. But it has a major flaw: the context is ephemeral. Once the session is over, that rich history of decisions, wrong turns, and “aha!” moments is locked away in an opaque, internal format.
I wanted to capture that value. I wanted a system that could take my chaotic coding sessions and distill them into structured, technical blog posts (like the one youre reading right now)."><meta property="og:url" content="https://ericxliu.me/posts/reverse-engineering-antigravity-ide/"><meta property="og:site_name" content="Eric X. Liu's Personal Page"><meta property="og:title" content="How I Built a Blog Agent that Writes About Itself"><meta property="og:description" content="Ive been spending a lot of time “vibe coding” in the Antigravity IDE lately. Its an incredible flow state—intense, iterative, and fast. But it has a major flaw: the context is ephemeral. Once the session is over, that rich history of decisions, wrong turns, and “aha!” moments is locked away in an opaque, internal format.
I wanted to capture that value. I wanted a system that could take my chaotic coding sessions and distill them into structured, technical blog posts (like the one youre reading right now)."><meta property="og:locale" content="en"><meta property="og:type" content="article"><meta property="article:section" content="posts"><meta property="article:published_time" content="2026-01-16T00:00:00+00:00"><meta property="article:modified_time" content="2026-01-22T01:49:53+00:00"><link rel=preload href=/fonts/fa-solid-900.woff2 as=font type=font/woff2 crossorigin><link rel=preload href=/fonts/fa-brands-400.woff2 as=font type=font/woff2 crossorigin><link rel=canonical href=https://ericxliu.me/posts/reverse-engineering-antigravity-ide/><link rel=preload href=/fonts/fa-brands-400.woff2 as=font type=font/woff2 crossorigin><link rel=preload href=/fonts/fa-regular-400.woff2 as=font type=font/woff2 crossorigin><link rel=preload href=/fonts/fa-solid-900.woff2 as=font type=font/woff2 crossorigin><link rel=stylesheet href=/css/coder.min.4b392a85107b91dbdabc528edf014a6ab1a30cd44cafcd5325c8efe796794fca.css integrity="sha256-SzkqhRB7kdvavFKO3wFKarGjDNRMr81TJcjv55Z5T8o=" crossorigin=anonymous media=screen><link rel=stylesheet href=/css/coder-dark.min.a00e6364bacbc8266ad1cc81230774a1397198f8cfb7bcba29b7d6fcb54ce57f.css integrity="sha256-oA5jZLrLyCZq0cyBIwd0oTlxmPjPt7y6KbfW/LVM5X8=" crossorigin=anonymous media=screen><link rel=icon type=image/svg+xml href=/images/favicon.svg sizes=any><link rel=icon type=image/png href=/images/favicon-32x32.png sizes=32x32><link rel=icon type=image/png href=/images/favicon-16x16.png sizes=16x16><link rel=apple-touch-icon href=/images/apple-touch-icon.png><link rel=apple-touch-icon sizes=180x180 href=/images/apple-touch-icon.png><link rel=manifest href=/site.webmanifest><link rel=mask-icon href=/images/safari-pinned-tab.svg color=#5bbad5><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3972604619956476" crossorigin=anonymous></script><script type=application/ld+json>{"@context":"http://schema.org","@type":"Person","name":"Eric X. Liu","url":"https:\/\/ericxliu.me\/","description":"Software \u0026 Performance Engineer at Google","sameAs":["https:\/\/www.linkedin.com\/in\/eric-x-liu-46648b93\/","https:\/\/git.ericxliu.me\/eric"]}</script><script type=application/ld+json>{"@context":"http://schema.org","@type":"BlogPosting","headline":"How I Built a Blog Agent that Writes About Itself","genre":"Blog","wordcount":"779","url":"https:\/\/ericxliu.me\/posts\/reverse-engineering-antigravity-ide\/","datePublished":"2026-01-16T00:00:00\u002b00:00","dateModified":"2026-01-22T01:49:53\u002b00:00","description":"\u003cp\u003eI\u0026rsquo;ve been spending a lot of time \u0026ldquo;vibe coding\u0026rdquo; in the Antigravity IDE lately. It\u0026rsquo;s an incredible flow state—intense, iterative, and fast. But it has a major flaw: the context is ephemeral. Once the session is over, that rich history of decisions, wrong turns, and \u0026ldquo;aha!\u0026rdquo; moments is locked away in an opaque, internal format.\u003c\/p\u003e\n\u003cp\u003eI wanted to capture that value. I wanted a system that could take my chaotic coding sessions and distill them into structured, technical blog posts (like the one you\u0026rsquo;re reading right now).\u003c\/p\u003e","author":{"@type":"Person","name":"Eric X. Liu"}}</script></head><body class="preload-transitions colorscheme-auto"><div class=float-container><a id=dark-mode-toggle class=colorscheme-toggle><i class="fa-solid fa-adjust fa-fw" aria-hidden=true></i></a></div><main class=wrapper><nav class=navigation><section class=container><a class=navigation-title href=https://ericxliu.me/>Eric X. Liu's Personal Page
</a><input type=checkbox id=menu-toggle>
<label class="menu-button float-right" for=menu-toggle><i class="fa-solid fa-bars fa-fw" aria-hidden=true></i></label><ul class=navigation-list><li class=navigation-item><a class=navigation-link href=/posts/>Posts</a></li><li class=navigation-item><a class=navigation-link href=https://chat.ericxliu.me>Chat</a></li><li class=navigation-item><a class=navigation-link href=https://git.ericxliu.me/user/oauth2/Authenitk>Git</a></li><li class=navigation-item><a class=navigation-link href=https://coder.ericxliu.me/api/v2/users/oidc/callback>Coder</a></li><li class=navigation-item><a class=navigation-link href=/about/>About</a></li><li class=navigation-item><a class=navigation-link href=/>|</a></li><li class=navigation-item><a class=navigation-link href=https://sso.ericxliu.me>Sign in</a></li></ul></section></nav><div class=content><section class="container post"><article><header><div class=post-title><h1 class=title><a class=title-link href=https://ericxliu.me/posts/reverse-engineering-antigravity-ide/>How I Built a Blog Agent that Writes About Itself</a></h1></div><div class=post-meta><div class=date><span class=posted-on><i class="fa-solid fa-calendar" aria-hidden=true></i>
<time datetime=2026-01-16T00:00:00Z>January 16, 2026
</time></span><span class=reading-time><i class="fa-solid fa-clock" aria-hidden=true></i>
4-minute read</span></div></div></header><div class=post-content><p>I&rsquo;ve been spending a lot of time &ldquo;vibe coding&rdquo; in the Antigravity IDE lately. It&rsquo;s an incredible flow state—intense, iterative, and fast. But it has a major flaw: the context is ephemeral. Once the session is over, that rich history of decisions, wrong turns, and &ldquo;aha!&rdquo; moments is locked away in an opaque, internal format.</p><p>I wanted to capture that value. I wanted a system that could take my chaotic coding sessions and distill them into structured, technical blog posts (like the one you&rsquo;re reading right now).</p><p>But getting the data out turned into a much deeper rabbit hole than I expected.</p><h2 id=the-challenge-check-the-database>The Challenge: Check the Database?
<a class=heading-link href=#the-challenge-check-the-database><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
<span class=sr-only>Link to heading</span></a></h2><p>My first instinct was simple: It&rsquo;s an Electron app, so there&rsquo;s probably a SQLite database.</p><p>I found it easily enough at <code>~/Library/Application Support/Antigravity/User/globalStorage/state.vscdb</code>. But when I opened it up, I hit a wall. The data wasn&rsquo;t plain text; it was stored in the <code>ItemTable</code> under keys like <code>antigravityUnifiedStateSync.trajectorySummaries</code> as Base64-encoded strings.</p><p>Decoding them revealed raw Protobuf wire formats, not JSON.</p><h3 id=the-wire-walking-dead-end>The &ldquo;Wire-Walking&rdquo; Dead End
<a class=heading-link href=#the-wire-walking-dead-end><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
<span class=sr-only>Link to heading</span></a></h3><p>I spent a few hours writing a Python script to &ldquo;wire-walk&rdquo; the Protobuf data without a schema. I managed to extract some human-readable strings, but it was a mess:</p><ol><li><strong>Missing Context</strong>: I got fragments of text, but the user prompts and cohesive flow were gone.</li><li><strong>Encryption</strong>: The actual conversation files (ending in <code>.pb</code>) in <code>~/.gemini/antigravity/conversations/</code> were encrypted.</li></ol><p>It turns out Antigravity uses Electrons <code>safeStorage</code> API, which interfaces directly with the macOS Keychain. Without the app&rsquo;s private key (which is hardware-bound), that data is effectively random noise. I even tried using Frida to hook <code>safeStorage.decryptString()</code>, but macOS SIP (System Integrity Protection) and code signing shut that down immediately.</p><p>I was stuck. I couldn&rsquo;t decrypt the local files, and I couldn&rsquo;t parse the database effectively.</p><h2 id=the-breakthrough-living-off-the-land>The Breakthrough: Living Off the Land
<a class=heading-link href=#the-breakthrough-living-off-the-land><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
<span class=sr-only>Link to heading</span></a></h2><p>When you can&rsquo;t break the front door, look for the side entrance. I realized I wasn&rsquo;t the only one trying to read this state—the official extensions had to do it too.</p><p>I started poking around the source code of the <code>vscode-antigravity-cockpit</code> extension, specifically a file named <code>local_auth_importer.ts</code>. That&rsquo;s where I found the golden ticket.</p><p>The extension <em>doesn&rsquo;t</em> decrypt the local files. Instead, it reads a specific key from the SQLite database: <code>jetskiStateSync.agentManagerInitState</code>.</p><p>When I decoded field #6 of this Protobuf structure, I found an <code>OAuthTokenInfo</code> message. It contained the users active <code>accessToken</code> and <code>refreshToken</code>.</p><h3 id=shifting-strategy-dont-crack-it-join-it>Shifting Strategy: Don&rsquo;t Crack it, Join it
<a class=heading-link href=#shifting-strategy-dont-crack-it-join-it><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
<span class=sr-only>Link to heading</span></a></h3><p>This changed everything. I didn&rsquo;t need to reverse-engineer the local storage encryption; I just needed to impersonate the IDE.</p><p>By &ldquo;piggybacking&rdquo; on this existing auth mechanism, I could extract a valid OAuth token directly from the local state. But I still needed the endpoints.</p><p>Instead of guessing, I opened the <strong>Developer Tools</strong> inside Antigravity itself (it is Electron, after all). By enabling the Chrome network tracing tools and triggering an export manually, I caught the request in the act.</p><p>I saw the exact call to <code>exa.language_server_pb.LanguageServerService/ConvertTrajectoryToMarkdown</code>.</p><p>It was perfect. By sending a gRPC-over-HTTP request to this endpoint using the stolen token, the server—which <em>does</em> have access to the unencrypted history—returned a perfectly formatted Markdown document of my entire coding session.</p><h2 id=the-architecture-the-blog-agent>The Architecture: The Blog-Agent
<a class=heading-link href=#the-architecture-the-blog-agent><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
<span class=sr-only>Link to heading</span></a></h2><p>Once I had the data extraction solved, building the rest of the &ldquo;blog-agent&rdquo; was straightforward. I built a <strong>Node.js</strong> stack to automate the pipeline:</p><ul><li><strong>Backend</strong>: An <strong>Express</strong> server handles the routing, session imports, and post generation.</li><li><strong>Frontend</strong>: A clean <strong>EJS</strong> interface to list sessions, view summaries, and &ldquo;publish&rdquo; them to the filesystem.</li><li><strong>Storage</strong>: A local SQLite database (<code>data/sessions.sqlite</code>) acts as a cache. (I learned my lesson: always cache your LLM inputs).</li><li><strong>The Brain</strong>: I use the <strong>OpenAI SDK</strong> (pointing to a LiteLLM proxy) to interface with <code>gemini-3-flash</code>. I wrote a map-reduce style prompt that first extracts technical decisions from the raw conversation log, then synthesizes them into a narrative.</li><li><strong>Persistence</strong>: The final posts are saved with YAML front matter into a <code>generated_posts/</code> directory.</li></ul><h2 id=key-insights>Key Insights
<a class=heading-link href=#key-insights><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
<span class=sr-only>Link to heading</span></a></h2><ul><li><strong>Don&rsquo;t Fight the OS</strong>: Trying to break macOS Keychain/SIP encryption is a losing battle for a weekend project.</li><li><strong>Follow the Tokens</strong>: Applications often store auth tokens in less-secure places (like plain SQLite or weaker encryption) than the user content itself.</li><li><strong>Extensions are Open Books</strong>: If an app has extensions, their source code is often the best documentation for the internal API.</li></ul><p>In a satisfying detailed loop, <strong>this very article was generated by the blog-agent itself</strong>, analyzing the &ldquo;vibe coding&rdquo; session where I built it.</p><h2 id=references>References
<a class=heading-link href=#references><i class="fa-solid fa-link" aria-hidden=true title="Link to heading"></i>
<span class=sr-only>Link to heading</span></a></h2><ul><li><code>server.js</code>: The Express server and API implementation.</li><li><code>services/antigravity.js</code>: The client for the Antigravity gRPC-over-HTTP API.</li><li><a href=https://github.com/jlcodes99/vscode-antigravity-cockpit class=external-link target=_blank rel=noopener>vscode-antigravity-cockpit</a>: The extension that leaked the auth logic.</li></ul></div><footer><div id=disqus_thread></div><script>window.disqus_config=function(){},function(){if(["localhost","127.0.0.1"].indexOf(window.location.hostname)!=-1){document.getElementById("disqus_thread").innerHTML="Disqus comments not available by default when the website is previewed locally.";return}var t=document,e=t.createElement("script");e.async=!0,e.src="//ericxliu-me.disqus.com/embed.js",e.setAttribute("data-timestamp",+new Date),(t.head||t.body).appendChild(e)}(),document.addEventListener("themeChanged",function(){document.readyState=="complete"&&DISQUS.reset({reload:!0,config:disqus_config})})</script></footer></article><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css integrity=sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0 crossorigin=anonymous><script defer src=https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js integrity=sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4 crossorigin=anonymous></script><script defer src=https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js integrity=sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05 crossorigin=anonymous onload='renderMathInElement(document.body,{delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}]})'></script></section></div><footer class=footer><section class=container>©
2016 -
2026
Eric X. Liu
<a href="https://git.ericxliu.me/eric/ericxliu-me/commit/fe6bf91">[fe6bf91]</a></section></footer></main><script src=/js/coder.min.6ae284be93d2d19dad1f02b0039508d9aab3180a12a06dcc71b0b0ef7825a317.js integrity="sha256-auKEvpPS0Z2tHwKwA5UI2aqzGAoSoG3McbCw73gloxc="></script><script defer src=https://static.cloudflareinsights.com/beacon.min.js data-cf-beacon='{"token": "987638e636ce4dbb932d038af74c17d1"}'></script></body></html>