<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marjo</title>
    <description>The latest articles on DEV Community by Marjo (@marjoballabani).</description>
    <link>https://dev.to/marjoballabani</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%2F3551943%2F84205117-bf35-4240-a920-cb5bb699320d.png</url>
      <title>DEV Community: Marjo</title>
      <link>https://dev.to/marjoballabani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marjoballabani"/>
    <language>en</language>
    <item>
      <title>Your AI Agent Wastes 87% of Its Tokens Just Finding Code. I Fixed That.</title>
      <dc:creator>Marjo</dc:creator>
      <pubDate>Sun, 29 Mar 2026 21:12:35 +0000</pubDate>
      <link>https://dev.to/marjoballabani/your-ai-agent-wastes-87-of-its-tokens-just-finding-code-i-fixed-that-4d5p</link>
      <guid>https://dev.to/marjoballabani/your-ai-agent-wastes-87-of-its-tokens-just-finding-code-i-fixed-that-4d5p</guid>
      <description>&lt;h2&gt;
  
  
  Or: How I Stopped Worrying and Learned to Love the Trigram
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you ask an AI agent to fix a simple bug, and it spends 45 seconds reading your entire codebase before changing 3 lines?&lt;/p&gt;

&lt;p&gt;I do. I watched it happen. Repeatedly. So I decided to count exactly how bad it was.&lt;/p&gt;

&lt;p&gt;Turns out, &lt;strong&gt;60-80% of the tokens your AI agent consumes go to navigation&lt;/strong&gt; -- searching for code, reading files, searching again, reading more files. Not reasoning. Not writing code. Just finding things.&lt;/p&gt;

&lt;p&gt;It's like hiring a plumber who spends 4 hours opening every door in your house before fixing the one leaking pipe in the bathroom.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/marjoballabani/hypergrep" rel="noopener noreferrer"&gt;Hypergrep&lt;/a&gt;. And the plumber now has a floor plan.&lt;/p&gt;




&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The Problem Nobody Talks About&lt;/li&gt;
&lt;li&gt;The Experiment&lt;/li&gt;
&lt;li&gt;How Hypergrep Works&lt;/li&gt;
&lt;li&gt;The Secret Sauce: Semantic Compression&lt;/li&gt;
&lt;li&gt;Real Benchmarks (No Hand-Waving)&lt;/li&gt;
&lt;li&gt;The Feature That Changes Everything&lt;/li&gt;
&lt;li&gt;Everything Else It Does&lt;/li&gt;
&lt;li&gt;What I Got Wrong&lt;/li&gt;
&lt;li&gt;The Prior Art&lt;/li&gt;
&lt;li&gt;Install and Try It&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Every AI coding agent -- Claude Code, Cursor, Copilot, Cody, aider -- uses the same approach to understand your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. grep for something
2. get raw text lines back
3. have no idea what those lines mean
4. read the full file to understand context
5. repeat 50-200 times per session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the equivalent of navigating a city by reading street signs one at a time. It works. It's just painfully inefficient.&lt;/p&gt;

&lt;p&gt;Jake Nesler measured this in early 2026 and found that a single question consumed ~12,000 tokens when the actual answer required ~800. The agent read 25 files to locate 3 functions. That's a 93% waste ratio.&lt;/p&gt;

&lt;p&gt;And this isn't a bug. It's the architecture. grep was built for humans who want to see matching lines. AI agents don't want lines -- they want understanding.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;I picked a real codebase (ripgrep's own source code -- 208 files, 52K lines, because irony is the best testing methodology) and measured what happens when an agent investigates how the &lt;code&gt;Matcher&lt;/code&gt; system works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Approach A: Agent with ripgrep
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: rg "Matcher"
         -&amp;gt; 376 matching lines across dozens of files
         -&amp;gt; 10,174 tokens consumed
         -&amp;gt; Agent's internal monologue: "cool, 376 lines. I understand nothing."

Step 2: Read 5 files to understand context
         -&amp;gt; auth.rs, session.rs, middleware.rs...
         -&amp;gt; 9,284 tokens consumed
         -&amp;gt; Agent: "OK, I think I'm starting to get it..."

Step 3: rg "impl.*Matcher" (refine search)
         -&amp;gt; 43 lines
         -&amp;gt; 1,122 tokens consumed
         -&amp;gt; Agent: "Now I can actually answer the question"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Total: 20,580 tokens. 5+ tool calls. Agent spent 45% of budget reading files.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Approach B: Agent with Hypergrep
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: hypergrep --model
         -&amp;gt; Codebase overview: directory structure, key abstractions, entry points
         -&amp;gt; 1,413 tokens
         -&amp;gt; Agent: "I know this codebase now."

Step 2: hypergrep --layer 1 --budget 1000 "Matcher"
         -&amp;gt; Top results with function signatures + who calls what
         -&amp;gt; 1,400 tokens
         -&amp;gt; Agent: "I see the Matcher trait, its implementations, and the call chain."

Step 3: hypergrep --impact "Matcher"
         -&amp;gt; What breaks if Matcher changes
         -&amp;gt; 1 token
         -&amp;gt; Agent: "And I know the blast radius."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Total: 2,814 tokens. 3 tool calls. Zero file reads.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same understanding. &lt;strong&gt;87% fewer tokens.&lt;/strong&gt; Not estimated. Measured.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tokens consumed:

ripgrep:    ==================================================  20,580
hypergrep:  ======                                               2,814
                                                         87% reduction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How Hypergrep Works
&lt;/h2&gt;

&lt;p&gt;Hypergrep is &lt;strong&gt;not&lt;/strong&gt; a faster grep. Let me be clear about this because I wasted a lot of time thinking the goal was speed.&lt;/p&gt;

&lt;p&gt;ripgrep is faster for single text searches: 23ms vs 40ms. If you need to grep once and leave, use ripgrep. Seriously.&lt;/p&gt;

&lt;p&gt;Hypergrep is a different tool entirely. It combines three capabilities that no other single tool provides:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Trigram-Indexed Text Search
&lt;/h3&gt;

&lt;p&gt;When you search for "authenticate", Hypergrep doesn't scan every file. It looks up which files contain the trigrams "aut", "uth", "the", "hen", "ent", "nti", "tic", "ica", "cat", "ate" -- and only checks those files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All 208 files
     |
     | trigram filter (which files contain these 3-char sequences?)
     v
12 candidate files
     |
     | regex verification (does the regex actually match?)
     v
3 true matches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same technique behind Google Code Search (Russ Cox, 2012). Zero false negatives -- if a file matches, it's guaranteed to be in the candidate set. The filter can only remove files that provably cannot match.&lt;/p&gt;

&lt;p&gt;First query builds the index (~40ms for 200 files, cached to disk). After that, searches are 4.4ms. Seven times faster than ripgrep's 31ms.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tree-sitter Structural Awareness
&lt;/h3&gt;

&lt;p&gt;Here's where it gets interesting.&lt;/p&gt;

&lt;p&gt;ripgrep returns this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hashed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 47. Great. Which function is this in? What are the arguments? What does the function return? The agent doesn't know. It has to read the file.&lt;/p&gt;

&lt;p&gt;Hypergrep returns this:&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;hypergrep &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"hash_password"&lt;/span&gt; src/

src/auth.rs:1-8 &lt;span class="k"&gt;function &lt;/span&gt;authenticate
fn authenticate&lt;span class="o"&gt;(&lt;/span&gt;user: &amp;amp;str, pass: &amp;amp;str&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt; bool &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;let &lt;/span&gt;hashed &lt;span class="o"&gt;=&lt;/span&gt; hash_password&lt;span class="o"&gt;(&lt;/span&gt;pass&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    check_db&lt;span class="o"&gt;(&lt;/span&gt;user, hashed&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;complete function body&lt;/strong&gt;. The agent immediately knows: this is the &lt;code&gt;authenticate&lt;/code&gt; function, it takes a user and password, it calls &lt;code&gt;hash_password&lt;/code&gt; and &lt;code&gt;check_db&lt;/code&gt;, and it returns a bool.&lt;/p&gt;

&lt;p&gt;No file read needed. The search result &lt;em&gt;is&lt;/em&gt; the understanding.&lt;/p&gt;

&lt;p&gt;This works because Hypergrep parses every file with tree-sitter during indexing. It knows where every function, class, struct, method, and trait boundary is. When a line matches, it expands to the smallest enclosing symbol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;16 languages supported&lt;/strong&gt;: Rust, Python, JavaScript, TypeScript, Go, Java, C, C++, Ruby, PHP, Swift, C#, Scala, Lua, Zig, Bash. Plus tree-sitter parsing for HTML, CSS, JSON, TOML, YAML, and HCL.&lt;/p&gt;

&lt;p&gt;If a file is in a language without a grammar, it falls back to regular line-level search. Same as ripgrep. No worse.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Live Call Graph
&lt;/h3&gt;

&lt;p&gt;During indexing, Hypergrep also extracts function call relationships from the AST. It builds a call graph: function A calls function B, function C calls function A.&lt;/p&gt;

&lt;p&gt;This enables two commands that no grep tool can answer:&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;# Who calls this function?&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;hypergrep &lt;span class="nt"&gt;--callers&lt;/span&gt; &lt;span class="s2"&gt;"authenticate"&lt;/span&gt; src/
  src/api.rs:login_handler
  src/api.rs:api_key_verify
  src/middleware.rs:auth_middleware

&lt;span class="c"&gt;# What does this function call?&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;hypergrep &lt;span class="nt"&gt;--callees&lt;/span&gt; &lt;span class="s2"&gt;"authenticate"&lt;/span&gt; src/
  src/auth.rs:hash_password
  src/auth.rs:check_db
  src/session.rs:create_session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response time: &lt;strong&gt;2.5 microseconds&lt;/strong&gt;. Not milliseconds. Microseconds. It's a hash table lookup.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Secret Sauce: Semantic Compression
&lt;/h2&gt;

&lt;p&gt;Speed is nice, but the real innovation is &lt;strong&gt;information density per token&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I built three output layers:&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;What you get&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--layer 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File + function name + kind&lt;/td&gt;
&lt;td&gt;~15&lt;/td&gt;
&lt;td&gt;"Which files are relevant?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--layer 1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Signature + calls + called_by&lt;/td&gt;
&lt;td&gt;~80-120&lt;/td&gt;
&lt;td&gt;"What does this do?" (sweet spot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--layer 2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full source code&lt;/td&gt;
&lt;td&gt;~200-800&lt;/td&gt;
&lt;td&gt;"I need to edit this function"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Layer 1 is the sweet spot. Here's what the agent actually receives:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"crates/core/main.rs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"line_range"&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="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;151&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fn search(args: &amp;amp;HiArgs, mode: SearchMode) -&amp;gt; Result&amp;lt;bool&amp;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;"calls"&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;"search_path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"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;"printer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"walk_builder"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"called_by"&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;"search_parallel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"try_main"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"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;85&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;85 tokens. The agent knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function name, file, and line range&lt;/li&gt;
&lt;li&gt;The full type signature&lt;/li&gt;
&lt;li&gt;Everything it calls (dependencies going down)&lt;/li&gt;
&lt;li&gt;Everything that calls it (dependents going up)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without Hypergrep, getting this understanding means reading &lt;code&gt;main.rs&lt;/code&gt; (~2,000 tokens) and probably &lt;code&gt;search.rs&lt;/code&gt; too (~3,000 tokens). That's 5,000 tokens for what Hypergrep delivers in 85.&lt;/p&gt;

&lt;p&gt;And then there's the budget parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hypergrep &lt;span class="nt"&gt;--layer&lt;/span&gt; 1 &lt;span class="nt"&gt;--budget&lt;/span&gt; 500 &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="s2"&gt;"authenticate"&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Give me the best results that fit in 500 tokens." Hypergrep ranks by relevance and fills the budget with the top results. The agent gets maximum information density within its context constraints.&lt;/p&gt;

&lt;p&gt;No other search tool has this concept. grep returns everything. You deal with the overflow. Hypergrep optimizes for the consumer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Benchmarks (No Hand-Waving)
&lt;/h2&gt;

&lt;p&gt;Every number here is from a real run. I benchmarked against ripgrep's own source code (208 Rust files, 52K lines) because I wanted a real project, and also because benchmarking a search tool against the search tool's own code felt appropriately meta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query latency (warm index, median of 20 runs):

"fn search"        hypergrep  4.5ms  ||||
                   ripgrep   31.0ms  |||||||||||||||||||||||||||||||

"Searcher"         hypergrep  3.7ms  |||
                   ripgrep   31.0ms  |||||||||||||||||||||||||||||||

"TODO"             hypergrep  0.5ms  |
                   ripgrep   31.0ms  |||||||||||||||||||||||||||||||

"Result&amp;lt;"          hypergrep  4.9ms  ||||
                   ripgrep   31.0ms  |||||||||||||||||||||||||||||||
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hypergrep is &lt;strong&gt;7x faster&lt;/strong&gt; for warm queries. ripgrep is constant at ~31ms because it scans every file every time. Hypergrep varies from 0.5ms to 7.5ms depending on how many candidates the trigram filter produces.&lt;/p&gt;

&lt;h3&gt;
  
  
  50-Query Agent Session
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cumulative time:

Queries:     1     5    10    20    50   100
ripgrep:    31   155   310   620  1550  3100 ms
hypergrep:   4    22    44    88   220   440 ms
                                   ^^^
                                  7x faster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over a 50-query session: ripgrep takes 1,550ms. Hypergrep takes 220ms. The gap widens linearly because Hypergrep pays the index cost once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token Savings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Investigation task: "How does Matcher work?"

                          Tokens
ripgrep + file reads:     ████████████████████░  20,580
hypergrep (L1 + budget):  ██░                     2,814

                          87% reduction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Full Numbers Table
&lt;/h3&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;ripgrep&lt;/th&gt;
&lt;th&gt;Hypergrep&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Warm text search&lt;/td&gt;
&lt;td&gt;31ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.4ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50-query session&lt;/td&gt;
&lt;td&gt;1,550ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;220ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens (3-query task)&lt;/td&gt;
&lt;td&gt;20,580&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,814&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;87% less&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Callers query&lt;/td&gt;
&lt;td&gt;impossible&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.5us&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;new capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existence check&lt;/td&gt;
&lt;td&gt;31ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;291ns&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100,000x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codebase summary&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;699 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;new capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correctness&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5/5 match&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;zero false negatives&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All numbers from warm index. Cold start (first-ever run, no cache) is 40ms for text search, 800ms including tree-sitter + graph. The index is cached to disk (&lt;code&gt;.hypergrep/index.bin&lt;/code&gt;, ~581 KB) and loads in 25ms on subsequent runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Feature That Changes Everything
&lt;/h2&gt;

&lt;p&gt;Impact analysis.&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;hypergrep &lt;span class="nt"&gt;--impact&lt;/span&gt; &lt;span class="s2"&gt;"hash_password"&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Impact analysis for 'hash_password' (depth 3):

  [depth 1] WILL BREAK   src/auth.rs:authenticate
  [depth 2] MAY BREAK    src/api.rs:login_handler
  [depth 3] REVIEW        src/main.rs:setup_routes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call graph:

  setup_routes -----&amp;gt; login_handler -----&amp;gt; authenticate -----&amp;gt; hash_password
   [REVIEW]            [MAY BREAK]         [WILL BREAK]        [you change this]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No AI agent today checks blast radius before editing. They just edit and hope. "I changed the return type of &lt;code&gt;hash_password&lt;/code&gt; from &lt;code&gt;String&lt;/code&gt; to &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt;" -- and three files break.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;--impact&lt;/code&gt;, the agent sees the damage before it happens. In 2.5 microseconds.&lt;/p&gt;

&lt;p&gt;This alone is worth the install. Everything else is a bonus.&lt;/p&gt;




&lt;h2&gt;
  
  
  Everything Else It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Codebase Mental Model
&lt;/h3&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;hypergrep &lt;span class="nt"&gt;--model&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generates a ~700 token structural summary of your entire codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Codebase Mental Model&lt;/span&gt;

&lt;span class="gu"&gt;## Languages&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Rust: 100+ files, TypeScript: 8 files

&lt;span class="gu"&gt;## Structure&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; src/auth/ (3 files) -- 5 functions, 2 structs
&lt;span class="p"&gt;-&lt;/span&gt; src/api/ (6 files) -- 12 functions, 3 structs

&lt;span class="gu"&gt;## Key Abstractions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; function search (main.rs) -- 16 callers, 8 callees
&lt;span class="p"&gt;-&lt;/span&gt; struct Searcher (searcher/mod.rs) -- 12 callers

&lt;span class="gu"&gt;## Entry Points&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; src/main.rs

&lt;span class="gu"&gt;## Hot Spots&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; src/api/handlers.rs (15 symbols, 340 lines)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load this once at session start. The agent immediately knows where everything is. Replaces 10-20 exploratory searches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bloom Filter Existence Checks
&lt;/h3&gt;

&lt;p&gt;"Does this project use Redis?"&lt;/p&gt;

&lt;p&gt;Every agent eventually asks this. With ripgrep, it's a full codebase scan that returns zero results. 31ms to learn nothing.&lt;/p&gt;

&lt;p&gt;With Hypergrep:&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;hypergrep &lt;span class="nt"&gt;--exists&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt; src/
NO: &lt;span class="s1"&gt;'redis'&lt;/span&gt; is definitely not &lt;span class="k"&gt;in &lt;/span&gt;this codebase    &lt;span class="c"&gt;# 291 nanoseconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;291 nanoseconds. That's not a typo. It's a bloom filter lookup.&lt;/p&gt;

&lt;p&gt;The bloom filter parses Cargo.toml, package.json, go.mod, requirements.txt, and pyproject.toml for real dependency names. "NO" is guaranteed correct (zero false negatives). "YES" means "probably" (~2% false positive rate).&lt;/p&gt;

&lt;h3&gt;
  
  
  Daemon Mode
&lt;/h3&gt;

&lt;p&gt;For heavy sessions (50+ queries), the daemon keeps the index in memory:&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;hypergrep-daemon &lt;span class="nt"&gt;--background&lt;/span&gt; src/
Daemon started &lt;span class="o"&gt;(&lt;/span&gt;PID 18067&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;hypergrep-daemon &lt;span class="nt"&gt;--status&lt;/span&gt; src/
Running
  PID:    18067
  Memory: 8.5 MB
  Socket: /tmp/hypergrep-f983e88f.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Safety features&lt;/strong&gt; (because nobody wants a rogue process eating their RAM):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-stops after 30 min idle&lt;/li&gt;
&lt;li&gt;Hard memory cap at 500 MB&lt;/li&gt;
&lt;li&gt;0% CPU when idle&lt;/li&gt;
&lt;li&gt;PID file prevents duplicates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--stop&lt;/code&gt; to kill it manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;8.5 MB for 100 files. Less than a Chrome tab.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Got Wrong
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the limitations because too many project READMEs aren't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold start is slower than ripgrep.&lt;/strong&gt; On first run with no cache, Hypergrep takes 40ms for text search vs ripgrep's 23ms. With tree-sitter + graph, it's 800ms. The index pays for itself after ~40 queries. If you're doing one search and leaving, use ripgrep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The call graph is incomplete.&lt;/strong&gt; Static analysis can't see dynamic dispatch (&lt;code&gt;getattr(obj, "method")()&lt;/code&gt;), reflection, callbacks passed as arguments, or macro-generated code. The &lt;code&gt;--impact&lt;/code&gt; results will miss things. It's useful for orientation, not for guarantees. If you need 100% accuracy, use your language's type checker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binary is 29 MB.&lt;/strong&gt; 16 tree-sitter grammars embedded. It's a chunky boy. ripgrep is 5 MB. The tradeoff is structural understanding for 16 languages vs text matching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large codebases (&amp;gt;10K files) need daemon mode.&lt;/strong&gt; The CLI cold start with tree-sitter parsing is too slow. The daemon builds once, serves forever (well, for 30 minutes of idle time, then politely exits).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bloom filter has ~2% false positives.&lt;/strong&gt; If it says "YES, this project uses graphql", it might be because "graphql" appears in a test fixture string, not because GraphQL is actually used. "NO" is always correct though.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Prior Art
&lt;/h2&gt;

&lt;p&gt;I didn't invent most of the techniques. Here's what existed before and what Hypergrep actually adds:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Text search&lt;/th&gt;
&lt;th&gt;Code graph&lt;/th&gt;
&lt;th&gt;Structural&lt;/th&gt;
&lt;th&gt;Token compression&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Code Search&lt;/td&gt;
&lt;td&gt;2006&lt;/td&gt;
&lt;td&gt;Trigram index&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;livegrep&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;Suffix array&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoekt&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;Positional trigram&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;ctags&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Blackbird&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;Sparse n-grams&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ast-grep&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;Pattern (no index)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Tree-sitter&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axon&lt;/td&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Call graph&lt;/td&gt;
&lt;td&gt;Tree-sitter&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;codebase-memory-mcp&lt;/td&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Call graph&lt;/td&gt;
&lt;td&gt;Tree-sitter&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor indexed search&lt;/td&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;Client-side n-gram&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hypergrep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Trigram index&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Call graph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Tree-sitter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;L0/L1/L2 + budget&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Text search tools can't do graph queries. Graph tools can't do regex search. Nobody does semantic compression with token budgets. Hypergrep is the first to combine all four.&lt;/p&gt;

&lt;p&gt;The full research document (42 references, including the theoretical foundations from Cox 2012, GitHub Blackbird 2023, and the speculative execution papers): &lt;a href="https://github.com/marjoballabani/hypergrep/blob/main/RESEARCH.md" rel="noopener noreferrer"&gt;RESEARCH.md&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Install and Try It
&lt;/h2&gt;

&lt;p&gt;30 seconds:&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;# macOS / Linux (downloads pre-built binary)&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSfL&lt;/span&gt; https://github.com/marjoballabani/hypergrep/releases/latest/download/hypergrep-installer.sh | sh

&lt;span class="c"&gt;# Or from source&lt;/span&gt;
git clone https://github.com/marjoballabani/hypergrep.git
&lt;span class="nb"&gt;cd &lt;/span&gt;hypergrep &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try it on your project:&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;# See the codebase overview&lt;/span&gt;
hypergrep &lt;span class="nt"&gt;--model&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; src/

&lt;span class="c"&gt;# Search with semantic compression&lt;/span&gt;
hypergrep &lt;span class="nt"&gt;--layer&lt;/span&gt; 1 &lt;span class="nt"&gt;--budget&lt;/span&gt; 500 &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="s2"&gt;"your_function"&lt;/span&gt; src/

&lt;span class="c"&gt;# Check blast radius before refactoring&lt;/span&gt;
hypergrep &lt;span class="nt"&gt;--impact&lt;/span&gt; &lt;span class="s2"&gt;"your_function"&lt;/span&gt; src/

&lt;span class="c"&gt;# Does your project use something?&lt;/span&gt;
hypergrep &lt;span class="nt"&gt;--exists&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set Up Your AI Agent
&lt;/h3&gt;

&lt;p&gt;One command configures Claude Code, Cursor, Copilot, and Windsurf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./hypergrep-setup.sh /path/to/your/project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your agents will automatically use Hypergrep for code search. No other setup needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uninstall
&lt;/h3&gt;

&lt;p&gt;If it's not for 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;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which hypergrep&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which hypergrep-daemon&lt;span class="si"&gt;)&lt;/span&gt;
find ~ &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;".hypergrep"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; + 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean. Nothing left behind.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The daemon needs real-world battle testing with actual agent sessions. The call graph needs type-aware resolution (not just name matching). And I want to build an MCP server so agents can use Hypergrep as a native tool, not just a Bash command.&lt;/p&gt;

&lt;p&gt;But the core thesis is proven: &lt;strong&gt;agents don't need faster text search. They need smarter results.&lt;/strong&gt; Return function signatures instead of lines. Return call graphs instead of file contents. Return impact analysis instead of making the agent guess.&lt;/p&gt;

&lt;p&gt;87% token reduction. Measured, not projected. On a real codebase. With 120 tests.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/marjoballabani/hypergrep" rel="noopener noreferrer"&gt;github.com/marjoballabani/hypergrep&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://marjoballabani.github.io/hypergrep" rel="noopener noreferrer"&gt;marjoballabani.github.io/hypergrep&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Research (42 refs): &lt;a href="https://github.com/marjoballabani/hypergrep/blob/main/RESEARCH.md" rel="noopener noreferrer"&gt;RESEARCH.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Benchmarks: &lt;a href="https://github.com/marjoballabani/hypergrep/blob/main/BENCHMARKS.md" rel="noopener noreferrer"&gt;BENCHMARKS.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT License. 120 tests. 16 languages. Built with Rust.&lt;/p&gt;

&lt;p&gt;If you use AI coding agents and burn tokens on navigation, give it a try. Star the repo if it saves you money. Open an issue if it doesn't.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #rust #cli #ai #devtools #opensource #codesearch #programming #rustlang&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
    <item>
      <title>I Built a Terminal UI for Firebase Firestore (and It Changed How I Work)</title>
      <dc:creator>Marjo</dc:creator>
      <pubDate>Sat, 10 Jan 2026 12:37:18 +0000</pubDate>
      <link>https://dev.to/marjoballabani/i-built-a-terminal-ui-for-firebase-firestore-and-it-changed-how-i-work-5h4m</link>
      <guid>https://dev.to/marjoballabani/i-built-a-terminal-ui-for-firebase-firestore-and-it-changed-how-i-work-5h4m</guid>
      <description>&lt;h2&gt;
  
  
  The Frustration That Started It All
&lt;/h2&gt;

&lt;p&gt;Picture this: You're deep in a debugging session. Your terminal is split between your code editor, logs streaming in real-time, and a test runner. You're in the zone. Then you need to check a document in Firestore.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Alt-tab.&lt;/em&gt; Open browser. Navigate to Firebase Console. Wait for it to load. Click on your project. Wait again. Click Firestore. Expand the collection. Scroll. Find the document. Click it. Finally see the data.&lt;/p&gt;

&lt;p&gt;By the time you get back to your terminal, you've lost your train of thought. The context switch killed your flow.&lt;/p&gt;

&lt;p&gt;If you've worked with Firebase Firestore, you know exactly what I'm talking about. The Firebase Console is fine for occasional use, but when you're actively developing and need to check data constantly, it becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;I kept thinking: &lt;em&gt;Why can't I just browse Firestore from my terminal?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing LazyFire
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LazyFire&lt;/strong&gt; is a terminal user interface (TUI) for browsing Firebase Firestore. It's heavily inspired by &lt;a href="https://github.com/jesseduffield/lazygit" rel="noopener noreferrer"&gt;lazygit&lt;/a&gt; - the fantastic terminal UI for git that changed how many of us interact with version control.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6n2t1uu9x93kvmbu4rb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6n2t1uu9x93kvmbu4rb.gif" alt="LazyFire Preview" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core philosophy is simple: &lt;strong&gt;stay in your terminal, stay in your flow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LazyFire connects to your Firestore database using your existing Firebase CLI credentials. No separate authentication, no API keys to manage - if you can run &lt;code&gt;firebase&lt;/code&gt; commands, you can run LazyFire.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interface
&lt;/h2&gt;

&lt;p&gt;When you launch LazyFire, you're greeted with a clean, multi-panel interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─ Projects ────┬─ Collections ─┬─ Tree ─────────────┬─ Details ──────────────┐
│               │               │                    │                        │
│ 🔥 my-app     │ 📁 users      │ ▼ 📁 users         │ {                      │
│   my-app-stg  │ 📁 products   │     📄 user_001    │   "name": "John Doe",  │
│   my-app-dev  │ 📁 orders     │     📄 user_002    │   "email": "john@...", │
│               │ 📁 analytics  │   ▶ 📁 products    │   "created": "2024..." │
│               │               │                    │ }                      │
├───────────────┴───────────────┴────────────────────┴────────────────────────┤
│ Commands: GET users/user_001 ✓ 234ms                                        │
└─────────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Five panels, each with a purpose:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Projects&lt;/strong&gt; - All your Firebase projects, pulled from your CLI config&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collections&lt;/strong&gt; - Root-level collections in the selected project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tree&lt;/strong&gt; - Documents and subcollections in an expandable tree view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Details&lt;/strong&gt; - The selected document's data with syntax highlighting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commands&lt;/strong&gt; - A log of API calls with timing information&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can navigate between panels with &lt;code&gt;h&lt;/code&gt; and &lt;code&gt;l&lt;/code&gt; (or arrow keys), and move within lists using &lt;code&gt;j&lt;/code&gt; and &lt;code&gt;k&lt;/code&gt;. If you've used vim or lazygit, you'll feel right at home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Deep Dive
&lt;/h2&gt;

&lt;p&gt;Let me walk you through the features that make LazyFire genuinely useful for day-to-day development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vim-Style Navigation
&lt;/h3&gt;

&lt;p&gt;Everything is keyboard-driven. Here's the complete keybinding reference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;h&lt;/code&gt; &lt;code&gt;←&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move to left panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;l&lt;/code&gt; &lt;code&gt;→&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move to right panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;j&lt;/code&gt; &lt;code&gt;↓&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move down in list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;k&lt;/code&gt; &lt;code&gt;↑&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move up in list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jump to details panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Select item / Expand collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle expand/collapse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enter visual select mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;F&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open query builder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filter current panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy JSON to clipboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Save JSON to file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open in external editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Refresh current view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show help&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show command history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Esc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go back / Cancel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The beauty of these keybindings is muscle memory. After a few sessions, you'll be flying through your database without thinking about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expandable Tree View with Subcollections
&lt;/h3&gt;

&lt;p&gt;Firestore's nested structure is fully supported. When you select a collection, LazyFire loads its documents. Each document can be expanded to reveal its subcollections, which can themselves be expanded to show their documents, and so on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▼ 📁 users
    📄 user_001
    ▼ 📁 orders
        📄 order_abc
        📄 order_def
        ▶ 📁 items
    ▶ 📁 preferences
    📄 user_002
▶ 📁 products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The arrow indicators (&lt;code&gt;▶&lt;/code&gt; for collapsed, &lt;code&gt;▼&lt;/code&gt; for expanded) make it easy to see the current state at a glance. Collection folders are cyan, document icons are green - subtle color coding that helps you parse the tree quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive Query Builder
&lt;/h3&gt;

&lt;p&gt;This is one of my favorite features. Press &lt;code&gt;F&lt;/code&gt; (Shift+F) on any collection or subcollection to open the query builder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2t3w7dkepnd402g7lrbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2t3w7dkepnd402g7lrbq.png" alt="Query view" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate with &lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt; between rows (WHERE, ORDER BY, LIMIT, buttons)&lt;/li&gt;
&lt;li&gt;Navigate with &lt;code&gt;h&lt;/code&gt;/&lt;code&gt;l&lt;/code&gt; between fields within a row&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Enter&lt;/code&gt; to edit a field&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;a&lt;/code&gt; to add a new filter condition&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;d&lt;/code&gt; to delete a filter&lt;/li&gt;
&lt;li&gt;Tab through operators and value types using popup selectors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Supported operators:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comparison: &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Array operations: &lt;code&gt;in&lt;/code&gt;, &lt;code&gt;not-in&lt;/code&gt;, &lt;code&gt;array-contains&lt;/code&gt;, &lt;code&gt;array-contains-any&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Value types:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;auto&lt;/code&gt; - LazyFire guesses the type (string, number, boolean)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;string&lt;/code&gt; - Force string interpretation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;integer&lt;/code&gt; - Force integer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;double&lt;/code&gt; - Force floating point&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;boolean&lt;/code&gt; - Force boolean&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null&lt;/code&gt; - Null value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;array&lt;/code&gt; - Comma-separated values for &lt;code&gt;in&lt;/code&gt;/&lt;code&gt;not-in&lt;/code&gt; operators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you execute the query, results replace the tree view (or appear under the subcollection if you queried a nested collection). It's a massive time saver compared to writing queries in code or using the Firebase Console.&lt;/p&gt;

&lt;h3&gt;
  
  
  jq Query Support
&lt;/h3&gt;

&lt;p&gt;The details panel shows your document as syntax-highlighted JSON. But what if you only care about a specific field? Or you want to transform the data before copying it?&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;/&lt;/code&gt; in the details panel and enter a jq expression:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extract just the name field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.address.city&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nested field access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.users[0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;First element of users array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.users[].name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All names from users array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`.data \&lt;/td&gt;
&lt;td&gt;keys`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`.items \&lt;/td&gt;
&lt;td&gt;length`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;select(.status == "active")&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filter by condition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The filtered result is displayed in the details panel. Here's the powerful part: when a jq filter is active, &lt;strong&gt;copy and save operations use the filtered result&lt;/strong&gt;, not the full document.&lt;/p&gt;

&lt;p&gt;This means you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a complex document&lt;/li&gt;
&lt;li&gt;Filter to just the fields you need with &lt;code&gt;.data.users[].email&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;c&lt;/code&gt; to copy only those email addresses to your clipboard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No more copying a massive JSON blob and manually extracting what you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Select Mode
&lt;/h3&gt;

&lt;p&gt;Sometimes you need to fetch multiple documents at once. Visual select mode makes this painless.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;code&gt;v&lt;/code&gt; in the tree panel to enter select mode&lt;/li&gt;
&lt;li&gt;Move up/down with &lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt; to extend your selection (works in both directions, like vim)&lt;/li&gt;
&lt;li&gt;Selected documents are highlighted with a dim yellow &lt;code&gt;+&lt;/code&gt; marker&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Space&lt;/code&gt; to fetch all selected documents in parallel&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Enter&lt;/code&gt; to view the combined results&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Esc&lt;/code&gt; to exit select mode&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The parallel fetching is key here. Instead of fetching documents one by one (which would be slow due to network latency), LazyFire fires off all requests simultaneously. Even fetching 20 documents feels instant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smart Caching
&lt;/h3&gt;

&lt;p&gt;Network calls are expensive. LazyFire implements a two-level caching system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document cache&lt;/strong&gt; - Once you fetch a document's data, it's cached. You'll see a small yellow dot &lt;code&gt;·&lt;/code&gt; next to cached documents in the tree.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Collection cache&lt;/strong&gt; - When you expand a collection, the list of documents is cached. Collapsing and re-expanding uses the cache instead of hitting the API again.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes navigation feel snappy. The first time you expand a collection, there's a brief loading spinner. After that, it's instant.&lt;/p&gt;

&lt;p&gt;The cache is session-based (cleared when you quit) and can be refreshed anytime with &lt;code&gt;r&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syntax Highlighting
&lt;/h3&gt;

&lt;p&gt;Document JSON is displayed with full syntax highlighting using the &lt;a href="https://github.com/alecthomas/chroma" rel="noopener noreferrer"&gt;chroma&lt;/a&gt; library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keys are colored distinctly from values&lt;/li&gt;
&lt;li&gt;Strings, numbers, booleans, and nulls each have their own color&lt;/li&gt;
&lt;li&gt;Proper indentation for nested objects&lt;/li&gt;
&lt;li&gt;Large documents are scrollable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a small thing, but it makes a huge difference when you're scanning through document data.&lt;/p&gt;

&lt;h3&gt;
  
  
  External Editor Integration
&lt;/h3&gt;

&lt;p&gt;Press &lt;code&gt;e&lt;/code&gt; in the details panel to open the current document in your external editor. LazyFire checks these environment variables in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;$EDITOR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$VISUAL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Falls back to &lt;code&gt;nvim&lt;/code&gt; if installed, otherwise &lt;code&gt;vim&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The document opens as a temporary JSON file. This is read-only for now (edits aren't saved back to Firestore), but it's useful when you need vim's full power to search or analyze a complex document.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customizable Themes
&lt;/h3&gt;

&lt;p&gt;LazyFire comes with a clean default theme, but you can customize every color. Create a config file at &lt;code&gt;~/.lazyfire/config.yaml&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;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Nerd Fonts version: "3", "2", or "" to disable icons&lt;/span&gt;
  &lt;span class="na"&gt;nerdFontsVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;

  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;activeBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#ed8796"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bold&lt;/span&gt;
    &lt;span class="na"&gt;inactiveBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#5f626b"&lt;/span&gt;
    &lt;span class="na"&gt;optionsTextColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#8aadf4"&lt;/span&gt;
    &lt;span class="na"&gt;selectedLineBgColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#494d64"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Color options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Named colors:&lt;/strong&gt; &lt;code&gt;black&lt;/code&gt;, &lt;code&gt;red&lt;/code&gt;, &lt;code&gt;green&lt;/code&gt;, &lt;code&gt;yellow&lt;/code&gt;, &lt;code&gt;blue&lt;/code&gt;, &lt;code&gt;magenta&lt;/code&gt;, &lt;code&gt;cyan&lt;/code&gt;, &lt;code&gt;white&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hex colors:&lt;/strong&gt; &lt;code&gt;#ed8796&lt;/code&gt;, &lt;code&gt;#ff79c6&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;256-color palette:&lt;/strong&gt; Numbers &lt;code&gt;0&lt;/code&gt; through &lt;code&gt;255&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attributes:&lt;/strong&gt; &lt;code&gt;bold&lt;/code&gt;, &lt;code&gt;underline&lt;/code&gt;, &lt;code&gt;reverse&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example themes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Catppuccin Macchiato:&lt;/em&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;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;activeBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#ed8796"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bold"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;inactiveBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#5f626b"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;optionsTextColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#8aadf4"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;selectedLineBgColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#494d64"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Dracula:&lt;/em&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;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;activeBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#ff79c6"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bold"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;inactiveBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#6272a4"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;optionsTextColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#8be9fd"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;selectedLineBgColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#44475a"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tokyo Night:&lt;/em&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;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;activeBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#7aa2f7"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bold"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;inactiveBorderColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#565f89"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;optionsTextColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#7dcfff"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;selectedLineBgColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#292e42"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mouse Support
&lt;/h3&gt;

&lt;p&gt;While keyboard navigation is the primary interface, LazyFire also supports mouse input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on any panel to focus it&lt;/li&gt;
&lt;li&gt;Click on items to select them&lt;/li&gt;
&lt;li&gt;Click outside a popup to close it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it accessible to team members who might not be familiar with vim-style navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nerd Font Icons
&lt;/h3&gt;

&lt;p&gt;If you use a &lt;a href="https://www.nerdfonts.com/" rel="noopener noreferrer"&gt;Nerd Font&lt;/a&gt; (and you should - they're great), LazyFire displays beautiful icons throughout the interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔥 Firebase flame for the welcome screen and projects&lt;/li&gt;
&lt;li&gt;📁 Folder icons for collections&lt;/li&gt;
&lt;li&gt;📄 Document icons for documents&lt;/li&gt;
&lt;li&gt;Various status indicators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If icons don't display correctly, you can switch to Nerd Fonts v2 compatibility mode or disable icons entirely:&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;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nerdFontsVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;  &lt;span class="c1"&gt;# For older Nerd Fonts&lt;/span&gt;
  &lt;span class="c1"&gt;# or&lt;/span&gt;
  &lt;span class="na"&gt;nerdFontsVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# Disable icons&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Getting started takes less than a minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Homebrew (macOS/Linux)
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Go Install
&lt;/h3&gt;

&lt;p&gt;If you have Go installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/marjoballabani/lazyfire@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  From Source
&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/marjoballabani/lazyfire.git
&lt;span class="nb"&gt;cd &lt;/span&gt;lazyfire
go build &lt;span class="nt"&gt;-o&lt;/span&gt; lazyfire &lt;span class="nb"&gt;.&lt;/span&gt;
./lazyfire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Download Binary
&lt;/h3&gt;

&lt;p&gt;Pre-built binaries for macOS and Linux are available on the &lt;a href="https://github.com/marjoballabani/lazyfire/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Firebase CLI&lt;/strong&gt; - Install with &lt;code&gt;npm install -g firebase-tools&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firebase Authentication&lt;/strong&gt; - Run &lt;code&gt;firebase login&lt;/code&gt; if you haven't already&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal with true color support&lt;/strong&gt; - Most modern terminals (iTerm2, Alacritty, Kitty, Windows Terminal) support this&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nerd Font&lt;/strong&gt; (optional) - For icons. &lt;a href="https://www.nerdfonts.com/" rel="noopener noreferrer"&gt;Download here&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;Once installed, getting started is simple:&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;# Make sure you're logged in&lt;/span&gt;
firebase login

&lt;span class="c"&gt;# Launch LazyFire&lt;/span&gt;
lazyfire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a welcome screen with your Firebase projects. Use &lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt; to navigate, &lt;code&gt;Enter&lt;/code&gt; to select a project, and start exploring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; If you work with multiple Firebase projects, LazyFire remembers them all. Switch between production, staging, and development environments without re-authenticating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Behind It
&lt;/h2&gt;

&lt;p&gt;For those curious about the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Language:&lt;/strong&gt; Go - chosen for its excellent cross-platform support, single binary distribution, and strong concurrency primitives (goroutines make parallel document fetching trivial)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Terminal UI:&lt;/strong&gt; &lt;a href="https://github.com/jesseduffield/gocui" rel="noopener noreferrer"&gt;gocui&lt;/a&gt; - Jesse Duffield's fork of the gocui library, which powers lazygit. It's battle-tested and handles all the complexity of terminal rendering.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;jq Implementation:&lt;/strong&gt; &lt;a href="https://github.com/itchyny/gojq" rel="noopener noreferrer"&gt;gojq&lt;/a&gt; - A pure Go implementation of jq. No external dependencies, works on all platforms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Syntax Highlighting:&lt;/strong&gt; &lt;a href="https://github.com/alecthomas/chroma" rel="noopener noreferrer"&gt;chroma&lt;/a&gt; - Fast, accurate syntax highlighting with theme support.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Firebase API:&lt;/strong&gt; Direct REST API calls using the access token from Firebase CLI. No Firebase Admin SDK means fewer dependencies and a smaller binary.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire application is around 5,000 lines of Go code. It's not a massive project, but every feature is intentional and polished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;I'll be honest: I built this for myself.&lt;/p&gt;

&lt;p&gt;I spend most of my working day in the terminal. Neovim for editing code, lazygit for version control, various CLIs for deployment and infrastructure. My terminal is my IDE, and I've optimized it over years to minimize friction.&lt;/p&gt;

&lt;p&gt;But Firestore was always the exception. Every time I needed to check a document or verify that a write succeeded, I had to leave my terminal and open a browser. It broke my concentration every single time.&lt;/p&gt;

&lt;p&gt;One day, after the hundredth alt-tab to the Firebase Console, I thought: "lazygit exists for git... why doesn't something like this exist for Firestore?"&lt;/p&gt;

&lt;p&gt;I couldn't find anything that fit the bill, so I built it.&lt;/p&gt;

&lt;p&gt;The first version was rough - just enough to browse collections and view documents. But over time, I kept adding features that would save me time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"I keep running the same queries" → Query builder&lt;/li&gt;
&lt;li&gt;"I just want to see one field" → jq support&lt;/li&gt;
&lt;li&gt;"Fetching documents one by one is slow" → Visual select mode + parallel fetching&lt;/li&gt;
&lt;li&gt;"I hate waiting for data to load again" → Caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each feature came from real frustration during real development work. That's why LazyFire feels cohesive - it's not a collection of features someone thought might be useful, it's a collection of solutions to problems I actually had.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;LazyFire is actively maintained and I have a roadmap of features I'm considering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Near-term:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document editing (create, update, delete) - currently read-only&lt;/li&gt;
&lt;li&gt;Batch operations on selected documents&lt;/li&gt;
&lt;li&gt;Export to CSV format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Medium-term:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firestore emulator support for local development&lt;/li&gt;
&lt;li&gt;Realtime listeners for live document updates&lt;/li&gt;
&lt;li&gt;Saved queries that persist across sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Long-term:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firebase Auth user browsing&lt;/li&gt;
&lt;li&gt;Cloud Functions log viewing&lt;/li&gt;
&lt;li&gt;Multiple database support (for projects with multiple Firestore instances)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these would be particularly useful for your workflow, let me know in the comments or open an issue on GitHub. User feedback directly influences what I prioritize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;The project is open source under the MIT license:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/marjoballabani/lazyfire" rel="noopener noreferrer"&gt;github.com/marjoballabani/lazyfire&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you work with Firestore and spend any amount of time in the terminal, give it a try. The installation takes 30 seconds, and you might find it changes how you interact with your database.&lt;/p&gt;

&lt;p&gt;Stars on GitHub are always appreciated - they help others discover the project. And if you run into any issues or have feature requests, please open an issue. I read every single one.&lt;/p&gt;




&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I'm Marjo Ballabani, a software developer who believes that good developer tools should get out of your way. I build things that make my workflow faster, and sometimes those things are useful to others too.&lt;/p&gt;

&lt;p&gt;You can find me on &lt;a href="https://github.com/marjoballabani" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; where I occasionally open source projects like this one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What terminal tools have changed your workflow? I'd love to hear about your favorite TUIs and CLI tools in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>opensource</category>
      <category>cli</category>
      <category>go</category>
    </item>
  </channel>
</rss>
