Files
ericxliu-me/posts/reverse-engineering-antigravity-ide/index.html
2026-01-22 06:48:23 +00:00

27 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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/6100dca">[6100dca]</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>