<?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: Subhraneel</title>
    <description>The latest articles on DEV Community by Subhraneel (@agent_69).</description>
    <link>https://dev.to/agent_69</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3415900%2Fef50900c-22cb-4622-822a-4b9b62dec6ca.jpg</url>
      <title>DEV Community: Subhraneel</title>
      <link>https://dev.to/agent_69</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agent_69"/>
    <language>en</language>
    <item>
      <title>Building a Self-Healing Supervisor in Node.js</title>
      <dc:creator>Subhraneel</dc:creator>
      <pubDate>Tue, 23 Jun 2026 09:47:36 +0000</pubDate>
      <link>https://dev.to/agent_69/building-a-self-healing-supervisor-in-nodejs-4i4l</link>
      <guid>https://dev.to/agent_69/building-a-self-healing-supervisor-in-nodejs-4i4l</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotciywck7u3bgwakwmta.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotciywck7u3bgwakwmta.png" alt=" " width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently, while reading the Reliability chapter from &lt;em&gt;Designing Data-Intensive Applications&lt;/em&gt; (DDIA) by Martin Kleppmann, I built a small experiment to better understand one of the core ideas of reliable systems:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Failures are inevitable. Systems should be designed to detect them and recover automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This experiment is a simple &lt;strong&gt;self-healing supervisor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is straightforward: a supervisor process monitors worker processes and automatically restarts them whenever they become unhealthy.&lt;/p&gt;

&lt;p&gt;In production systems, these workers could be microservices, containers, or background jobs. For this experiment, they're implemented as basic Node.js child processes.&lt;/p&gt;

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

&lt;p&gt;The supervisor is intentionally kept simple.&lt;/p&gt;

&lt;p&gt;It spawns a worker process and continuously monitors it. If the worker crashes or becomes unresponsive, the supervisor kills it and starts a new one.&lt;/p&gt;

&lt;p&gt;Rather than trying to prevent failures entirely, the system assumes they will happen and focuses on recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worker Types
&lt;/h2&gt;

&lt;p&gt;To simulate different real-world failure scenarios, the worker randomly starts in one of three modes:&lt;/p&gt;

&lt;h3&gt;
  
  
  Normal Worker
&lt;/h3&gt;

&lt;p&gt;A healthy worker that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sends heartbeat messages every 3 seconds.&lt;/li&gt;
&lt;li&gt;Performs its task.&lt;/li&gt;
&lt;li&gt;Exits successfully after completion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hung Worker
&lt;/h3&gt;

&lt;p&gt;A worker that appears healthy initially but later becomes unresponsive.&lt;/p&gt;

&lt;p&gt;It sends a few heartbeat messages and then stops sending them entirely by entering an infinite loop. Since no further heartbeats are received, the supervisor detects the worker as unhealthy, terminates it, and starts a replacement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crashed Worker
&lt;/h3&gt;

&lt;p&gt;A worker that intentionally crashes itself.&lt;/p&gt;

&lt;p&gt;After sending a few heartbeats, it throws an error and exits with a non-zero exit code. The supervisor detects the failure and automatically restarts it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heartbeat-Based Liveness Detection
&lt;/h2&gt;

&lt;p&gt;Workers periodically send JSON heartbeat messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"heartbeat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123456789&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;The supervisor tracks these heartbeats to determine whether a worker is alive.&lt;/p&gt;

&lt;p&gt;If no heartbeat is received for 10 seconds, the worker is considered unhealthy and is terminated. This mechanism allows the supervisor to detect not only crashes but also hung processes that are still running but no longer making progress.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fshui0dy1mtney13pau09.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fshui0dy1mtney13pau09.png" alt=" " width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry and Recovery
&lt;/h2&gt;

&lt;p&gt;The supervisor includes a simple retry mechanism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Waits 1 second before restarting a failed worker.&lt;/li&gt;
&lt;li&gt;Limits recovery attempts to 10 consecutive failures.&lt;/li&gt;
&lt;li&gt;Resets the retry counter whenever a worker exits successfully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents endless restart loops while still allowing recovery from transient failures.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6szyk4wv48spxj2iyjwq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6szyk4wv48spxj2iyjwq.png" alt=" " width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;This experiment reinforced a key reliability principle from DDIA:&lt;/p&gt;

&lt;p&gt;A system does not need to eliminate every failure. Instead, it should be able to detect failures quickly and recover automatically.&lt;/p&gt;

&lt;p&gt;It also provided a practical understanding of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OS processes and parent-child relationships&lt;/li&gt;
&lt;li&gt;Process supervision&lt;/li&gt;
&lt;li&gt;Heartbeat-based liveness detection&lt;/li&gt;
&lt;li&gt;Failure recovery strategies&lt;/li&gt;
&lt;li&gt;Restart limits and backoff mechanisms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The supervisor itself is intentionally "dumb"—it only monitors health and restarts workers when necessary. Interestingly, that simplicity is often a strength. A small, predictable supervisor can be more reliable than a complex one.&lt;/p&gt;

&lt;p&gt;Thanks for reading :)&lt;/p&gt;

&lt;p&gt;Github Repo: &lt;a href="https://github.com/subhraneel2005/ddia-lab" rel="noopener noreferrer"&gt;https://github.com/subhraneel2005/ddia-lab&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I built my own Claude code in Typescript</title>
      <dc:creator>Subhraneel</dc:creator>
      <pubDate>Thu, 04 Jun 2026 14:52:58 +0000</pubDate>
      <link>https://dev.to/agent_69/how-i-built-my-own-claude-code-in-typescript-34n7</link>
      <guid>https://dev.to/agent_69/how-i-built-my-own-claude-code-in-typescript-34n7</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Mini Claude Code from Scratch. Here's What I Learned
&lt;/h1&gt;

&lt;p&gt;A few months ago I went down a rabbit hole: reading OpenCode's GitHub repo, studying screenshots of Claude Code's behavior, reverse engineering the flows. I wanted to understand how these terminal coding agents actually work under the hood, not just use them, but build one. This post is about what I built, the real technical challenges I hit, and what I'm planning next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A CLI-based AI coding agent. You open your terminal, run it inside a project, describe a task, and the agent autonomously reads files, edits them, runs commands, searches the web, and commits to Git. This happens all while asking for your approval before changing any of your code.&lt;/p&gt;

&lt;p&gt;I started it as a simple sidequest, to read and implement things side by side. It's built in TypeScript, runs on Bun, uses Vercel's AI SDK (Agents, Tools and Loop Control), and uses Google Gemini 2.5 Flash as the model (because it has a free tier, lol), and the terminal UI is built with Ink, I used it because it is very similar to writing frontends in React.&lt;/p&gt;

&lt;p&gt;Here's a rough breakdown of what the coding agent can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;filesystem tools&lt;/strong&gt;: read, write, search, and edit files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bash tools&lt;/strong&gt;: &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;pwd&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;git tools&lt;/strong&gt;: commit, push, pull, create/manage PRs and issues via github cli&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;command execution&lt;/strong&gt;: run &lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;pnpm&lt;/code&gt;, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;cargo&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;web tools&lt;/strong&gt;: search the web with any query, fetch URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a planner sub-agent&lt;/strong&gt;: breaks big tasks into smaller todos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a memory system&lt;/strong&gt;: persists context across sessions in &lt;code&gt;.agent/&lt;/code&gt; markdown files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is strictly typed end-to-end with Zod schemas, tools have typed inputs and outputs, which I'll get into below.&lt;/p&gt;




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

&lt;p&gt;The core is a &lt;strong&gt;tool-calling agent loop&lt;/strong&gt;. The model receives your prompt, a list of available tools, and the conversation history. It decides which tool to call, the agent executes it, the result is fed back, and the loop continues until the task is done or the model stops requesting tools.&lt;/p&gt;

&lt;p&gt;I used Vercel's AI SDK to handle the streaming and loop control, and registered all my tools in a central &lt;code&gt;tools-registry.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;write_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;writeFileTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;readFileTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;search_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchFilesTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;edit_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;editFileTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lsTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pwdTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grepTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;git_tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gitTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// planner sub-agent tools&lt;/span&gt;
  &lt;span class="nx"&gt;createTodoTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createAllTodosTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;updateTodoStatusTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getNextPendingTodoTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;checkIfAllTodosAreCompletedTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// memory + web&lt;/span&gt;
  &lt;span class="na"&gt;write_memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;writeMemoryTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;run_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;runCommandTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;web_search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webSearchTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;web_fetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;webfetchTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;ToolSet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every tool is defined with a Zod schema for its input and output. This gives the model a clear, typed contract for what each tool expects and returns, and it gives me safe failure handling throughout , if a tool call has malformed input, Zod can catch it before it touches the filesystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 1: The Edit File Tool and Human-in-the-Loop
&lt;/h2&gt;

&lt;p&gt;This was the an interesting problem to solve.&lt;/p&gt;

&lt;p&gt;All other tools: reading files, running commands, searching can execute automatically without any user intervention. But file editing is destructive. If the agent makes a wrong edit, you want a chance to catch it before it's directly written to disk.&lt;/p&gt;

&lt;p&gt;The pattern I took inspiration (from studying Claude Code and OpenCode) is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;agent calls &lt;code&gt;read_file&lt;/code&gt; first to read the current file content&lt;/li&gt;
&lt;li&gt;agent calls &lt;code&gt;edit_file&lt;/code&gt; with the old string and the new string it wants to substitute (diff)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;before writing&lt;/strong&gt;, show the user a colored diff (red for removed lines, green for added lines)&lt;/li&gt;
&lt;li&gt;wait for the user to approve or reject&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tricky part is step 4. The agent loop is running. I can't just &lt;code&gt;await&lt;/code&gt; a user keypress inside a tool execution without some way to pause the loop itself.&lt;/p&gt;

&lt;p&gt;The way I solved this: Vercel's AI SDK's &lt;code&gt;streamText&lt;/code&gt; (and &lt;code&gt;generateText&lt;/code&gt;) exposes a &lt;code&gt;stopWhen&lt;/code&gt; parameter on the loop control. I use this to pause the agent loop when the edit tool is waiting for approval. The TUI sets an &lt;code&gt;isApproved&lt;/code&gt; flag asynchronously, the user sees the diff rendered in the terminal via Ink components, presses a key to approve or reject, the flag flips, and the loop resumes or the edit is discarded.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;isApproved&lt;/code&gt; field is actually part of the edit tool's input schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EditFileInputSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;oldStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;newStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;isApproved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Needs approval before writing new changes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the output schema carries a &lt;code&gt;needsApproval&lt;/code&gt; flag back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;needsApproval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Needs human approval to be true for the agent to write the changes in the file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;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%2Fbs5xtvu7124y3h6ssrlx.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%2Fbs5xtvu7124y3h6ssrlx.png" alt=" " width="800" height="793"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This creates a clear handshake: the tool signals it needs approval, the loop pauses, the human decides, the loop resumes. Everything else runs autonomously.&lt;/p&gt;

&lt;p&gt;I also added &lt;strong&gt;path traversal protection&lt;/strong&gt;, the agent cannot operate outside the project root directory. Every file path is validated against the root before any read or write happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenge 2: The Planner Sub-Agent
&lt;/h2&gt;

&lt;p&gt;For small, focused tasks, the main agent handles everything directly. But when a user gives a bigger, more open-ended task like "refactor this module", "add authentication to my nodejs backend", a single flat loop gets difficult to handle.&lt;/p&gt;

&lt;p&gt;My solution was a &lt;strong&gt;planner sub-agent&lt;/strong&gt;. The main agent calls it as a tool when it detects a bigger task. The planner sub-agent has its own system prompt focused entirely on task decomposition. It breaks the task down into a list of structured todos, each with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A unique ID&lt;/li&gt;
&lt;li&gt;The task description&lt;/li&gt;
&lt;li&gt;A status (&lt;code&gt;not completed&lt;/code&gt;, &lt;code&gt;ongoing&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A priority (1–5)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are typed with Zod too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SingleTodoSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ongoing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the planner creates the todos, the main agent picks them up one by one using &lt;code&gt;getNextPendingTodoTool&lt;/code&gt;, executes them using the available filesystem/git/web tools, and marks each one complete before moving to the next. The TUI renders a live todo list so you can watch the agent work through the task.&lt;/p&gt;

&lt;p&gt;Right now this is &lt;strong&gt;synchronous&lt;/strong&gt;, so one task at a time. That's the current limitation that I'm planning to address next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next: Parallel Sub-Agents
&lt;/h2&gt;

&lt;p&gt;The natural evolution of the planner is running multiple smaller agents in parallel, each picking up one todo from the breakdown independently. But this introduces an obvious conflict problem: what if two agents try to edit the same file at the same time?&lt;/p&gt;

&lt;p&gt;My planned approach is to solve this at the &lt;strong&gt;task metadata level&lt;/strong&gt;, not at the execution level. When the planner sub-agent breaks down a task, each todo will also carry metadata about which files it needs to touch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// rough idea, not yet implemented&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;task-3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Add input validation to auth.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/auth.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/validators.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;guessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;grepped&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;searched&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a smaller agent picks up a task, it only has access to the files assigned to it. The planner ensures no two tasks share the same file. This way, parallel agents work completely independently with well-defined boundaries, no conflict resolution at runtime, because the conflict is prevented structurally at planning time. And to maintain the bigger context the smaller agents will update the main agent about their current status (when it gets updated), e.g: "task1 failed", "task2 succeded", "task 3 in-progress". &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Gemini 2.5 Flash?
&lt;/h2&gt;

&lt;p&gt;Cause it has a generous free tier. When you're building something from scratch and running dozens of test runs a day, it matters.&lt;/p&gt;

&lt;p&gt;The plan is to make the &lt;strong&gt;model configurable&lt;/strong&gt;, so the user will be able to select their &lt;strong&gt;provider&lt;/strong&gt; and &lt;strong&gt;model&lt;/strong&gt; and bring their own API key. The agent's tool-calling logic doesn't care which model is underneath as long as it supports function/tool calling.&lt;/p&gt;




&lt;h2&gt;
  
  
  The TUI: Ink (React for Terminals)
&lt;/h2&gt;

&lt;p&gt;The terminal UI is built with [Ink], which lets you write React components that render in the terminal. If you know React, there's almost no learning curve.&lt;/p&gt;

&lt;p&gt;I used it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rendering the diff display during file edits (the red/green approval screen) + used the "diff" npm package as well.&lt;/li&gt;
&lt;li&gt;showing the live todo list as the planner sub-agent creates and the main agent completes tasks&lt;/li&gt;
&lt;li&gt;the thinking/loading indicators while the model is streaming&lt;/li&gt;
&lt;li&gt;the GitHub activity log component&lt;/li&gt;
&lt;li&gt;the text input field for user prompts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically it gets the job done with a simple, clean UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memory System
&lt;/h2&gt;

&lt;p&gt;One thing I wanted from the start was for the agent to remember things across sessions. And get more personalised based on the user. The memory system stores three types of information in a &lt;code&gt;.agent/&lt;/code&gt; folder in your project root:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it stores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;USER.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your preferences and habits (e.g. "prefers pnpm")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Facts about the repo (e.g. "uses Next.js, tests in /tests")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AGENT.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lessons the agent learned (e.g. "avoid editing generated files")&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agent can call &lt;code&gt;write_memory&lt;/code&gt; tool at any point of time to store something. On the next session, these files are loaded into the system prompt so the agent already knows the context without you having to re-explain it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;A few things I'd approach differently if I started over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;start with the edit tool's approval flow first.&lt;/strong&gt; It touches the most parts of the system (tool schema, loop control, TUI state, async coordination) and sets the pattern for everything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;design the todo schema with file metadata from the beginning.&lt;/strong&gt; Adding it later to support parallel agents means touching the planner sub-agent's prompt, the schema, and the main agent's task-picking logic all at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a proper token tracking display earlier.&lt;/strong&gt; right now it's in the upcoming features list.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where to Find It
&lt;/h2&gt;

&lt;p&gt;Here's the github repo: &lt;a href="https://github.com/subhraneel2005/sidequests" rel="noopener noreferrer"&gt;https://github.com/subhraneel2005/sidequests&lt;/a&gt;&lt;br&gt;
I have started to polish this project again and will work on some improvements. You can check them in the issues section of the repo, everything I do will be completely transparent.&lt;/p&gt;

&lt;p&gt;If you're thinking about building something similar, the best way to start is reading how tool-calling, loops, sub-agents and orchestration actually work in whatever AI SDK you're using. The rest is just building on top of that foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thanks for Reading
&lt;/h2&gt;

&lt;p&gt;If you made it this far, I genuinely appreciate. This was a fun project to build and an even more fun one to write about.&lt;br&gt;
I keep posting about my projects, experiments, and side quests on X. If you want to follow along, here's my X(twitter) account: &lt;a href="https://x.com/subhraneeltwt" rel="noopener noreferrer"&gt;@subhraneeltwt&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you have thoughts, feedback, criticism, questions, or just want to tell me something is wrong or could be done better, DM me, drop a comment, quote the post, whatever you want. I'm always looking to learn. Nothing is too small to share. See ya' :)&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%2Fzijf3vodp12kjpowlv3e.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%2Fzijf3vodp12kjpowlv3e.png" alt=" " width="735" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>agents</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How to setup Tailwind css to your Expo project</title>
      <dc:creator>Subhraneel</dc:creator>
      <pubDate>Wed, 13 Aug 2025 10:58:53 +0000</pubDate>
      <link>https://dev.to/agent_69/how-to-setup-tailwind-css-to-your-expo-project-2l4i</link>
      <guid>https://dev.to/agent_69/how-to-setup-tailwind-css-to-your-expo-project-2l4i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hello everynyan, here i come again!&lt;/strong&gt;&lt;br&gt;
So this time me and my friend were building a ticketing app with Expo and needed TailwindCSS. Turns out, Expo uses something called NativeWind which is basically tailwind for app dev.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem?&lt;/strong&gt;&lt;br&gt;
We noticed that there was no straight-forward docs for NativeWind setup in expo, so I decided why not make a simple straight-forward blog on this. Okay enough yapping&lt;/p&gt;
&lt;h2&gt;
  
  
  Lets start :)
&lt;/h2&gt;

&lt;p&gt;If you haven't already, just setup an expo project by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-expo-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;After thats done, run these 2 commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install nativewind
npm install -D tailwindcss

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this basically adds nativewind as a dependency and tailwindcss as a dev-dependecy in your project&lt;/p&gt;




&lt;p&gt;Now run this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tailwindcss init

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this will generate a &lt;code&gt;tailwind.config.js&lt;/code&gt; file in your project at the root level&lt;/p&gt;

&lt;p&gt;Replace the content of your &lt;code&gt;tailwind.config.js&lt;/code&gt; file with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./App.tsx", "./components/**/*.{js,jsx,ts,tsx}", "./app/**/*.{js,jsx,ts,tsx}"],
  presets: [require("nativewind/preset")],
  theme: { extend: {} },
  plugins: [],
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;We are almost done :)&lt;/p&gt;

&lt;p&gt;Now create a &lt;code&gt;babel.config.js&lt;/code&gt; file (needed for nativewind to work properly)&lt;/p&gt;

&lt;p&gt;and then add this in the babel file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      ["babel-preset-expo", { jsxImportSource: "nativewind" }],
      "nativewind/babel",
    ],
  };
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Now for expo to get all the styles create this file : &lt;code&gt;metro.config.js&lt;/code&gt; and add this code into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");

const config = getDefaultConfig(__dirname);

module.exports = withNativeWind(config, { input: "./global.css" });

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And its done! Now you can simple do this and your styles will get updated instantly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;className="flex-1 items-center justify-center bg-blue-500"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;I have create a Expo + Tailwind starter boilerplate, here's the repo if you want&lt;br&gt;
&lt;a href="https://github.com/subhraneel2005/Expo-Tailwind-Starter" rel="noopener noreferrer"&gt;subhraneel2005/Expo-Tailwind-Starter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you found this helpful do connect. Would love to chat :)&lt;br&gt;
Heres my twitter acc: &lt;a href="https://x.com/subhraneeltwt" rel="noopener noreferrer"&gt;x.com/subhraneeltwt&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to add auth-flow between your webapp and extension</title>
      <dc:creator>Subhraneel</dc:creator>
      <pubDate>Wed, 06 Aug 2025 05:44:23 +0000</pubDate>
      <link>https://dev.to/agent_69/how-to-add-auth-flow-between-your-webapp-and-extension-242p</link>
      <guid>https://dev.to/agent_69/how-to-add-auth-flow-between-your-webapp-and-extension-242p</guid>
      <description>&lt;h2&gt;
  
  
  Hey everyone! 👋
&lt;/h2&gt;

&lt;p&gt;So I just wrapped up this really cool project and wanted to share what I learned. I got to work on a fullstack Chrome extension for a US company, and honestly, it was such a fun challenge!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;They needed a Chrome extension with some pretty neat features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-apply to LinkedIn jobs&lt;/li&gt;
&lt;li&gt;AI integration for those tricky application questions&lt;/li&gt;
&lt;li&gt;Auto-fill for external job forms&lt;/li&gt;
&lt;li&gt;Smart keyword matching&lt;/li&gt;
&lt;li&gt;And a bunch of other cool stuff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We ended up going with Plasmo for the extension framework - honestly its the best out there to build chrome extensions: &lt;a href="https://www.plasmo.com/" rel="noopener noreferrer"&gt;https://www.plasmo.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Fun Part
&lt;/h2&gt;

&lt;p&gt;Here's where things got interesting. After building the extension, they wanted it to sync seamlessly with their main website through JWT auth. The tricky part? How do we actually know if someone's logged in on the website from inside the extension?&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%2Fq114d8rqp7io2sycmmhk.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%2Fq114d8rqp7io2sycmmhk.png" alt="Chrome extension authentication flow diagram" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Solution (Pretty Simple Actually)
&lt;/h2&gt;

&lt;p&gt;So Plasmo gives you these two main pieces to work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;content.tsx&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;background.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Capture the token in your webapp&lt;/strong&gt;&lt;br&gt;
In your React/Next.js app, find where you're handling the login POST request. If you're using Zustand or any global store, even better - just grab it from there.&lt;/p&gt;

&lt;p&gt;Once you have the authToken, create a &lt;code&gt;'use client'&lt;/code&gt; component and add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-webapp-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="c1"&gt;// Your actual token here&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This basically broadcasts the token from your webapp to the extension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Listen in the content script&lt;/strong&gt;&lt;br&gt;
In your extension's &lt;code&gt;content.tsx&lt;/code&gt;, set up a listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postMessageListener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MessageEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-webapp-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AUTH_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SAVE_AUTH_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;token&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Store it in the background&lt;/strong&gt;&lt;br&gt;
Finally, in your &lt;code&gt;background.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SAVE_AUTH_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Got the token!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Token safely stored:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// Keep the response channel open&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  And That's It! 🎉
&lt;/h2&gt;

&lt;p&gt;Three simple steps and voila - your extension and webapp are synced seamlessly. &lt;br&gt;
The cool part is you can extend this pattern for logout events, profile updates, or really anything you need to sync between the two.&lt;/p&gt;

&lt;p&gt;I'm honestly still amazed at how clean this solution turned out. Sometimes the simplest approaches work the best!&lt;/p&gt;

&lt;p&gt;If you have any suggestions or feedback, please comment below, would love to know and learn more about this!&lt;/p&gt;

&lt;p&gt;Happy coding guys :)&lt;/p&gt;

&lt;p&gt;btw this is me debugging the code ai generates🙂&lt;br&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%2Fr78agp8owl7u12vyl3sz.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%2Fr78agp8owl7u12vyl3sz.gif" alt="me debugging the code ai generates" width="320" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>extensions</category>
      <category>nextjs</category>
      <category>jwt</category>
      <category>plasmo</category>
    </item>
  </channel>
</rss>
