<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Akromdev</title>
    <description>The latest articles on Forem by Akromdev (@akromdev).</description>
    <link>https://forem.com/akromdev</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%2F3761554%2F151b5aee-279b-47c2-80ad-48b8d4fd29ee.png</url>
      <title>Forem: Akromdev</title>
      <link>https://forem.com/akromdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/akromdev"/>
    <language>en</language>
    <item>
      <title>I Built an AI Chatbot That Knows Everything About Me</title>
      <dc:creator>Akromdev</dc:creator>
      <pubDate>Wed, 01 Apr 2026 14:21:59 +0000</pubDate>
      <link>https://forem.com/akromdev/i-built-an-ai-chatbot-that-knows-everything-about-me-4jeb</link>
      <guid>https://forem.com/akromdev/i-built-an-ai-chatbot-that-knows-everything-about-me-4jeb</guid>
      <description>&lt;p&gt;My portfolio site has project pages, work experience entries, and blog posts, all written as MDX files. When someone visits, they usually have a specific question: "Has this person worked with React?" or "What's their most recent project?" The answer is somewhere on the site, but finding it means clicking through pages and scanning project cards.&lt;/p&gt;

&lt;p&gt;I wanted visitors to be able to just ask. Not a FAQ page with canned answers, but something that reads the actual content on the site and answers questions from it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Feed It Everything?
&lt;/h2&gt;

&lt;p&gt;Your first thought might be: take all the content, send it to a language model like GPT-4o or Claude, and let it answer questions. This works for short content. But language models hallucinate. Ask about a technology you never mentioned, and the model might confidently say "yes, they have 3 years of experience with that" because it sounds plausible.&lt;/p&gt;

&lt;p&gt;There's also a scale problem. My site has around 30 content files. Sending all of them as context every time someone asks a question is wasteful, and the more content you include, the more room there is for the model to drift.&lt;/p&gt;




&lt;h2&gt;
  
  
  Search First, Then Answer
&lt;/h2&gt;

&lt;p&gt;Instead of sending everything, what if I first searched my own content to find the pieces relevant to the question, and only sent those to the model? That's the core idea behind RAG (Retrieval-Augmented Generation). The model writes its answer from a small, focused set of context instead of your entire site. Because it only sees what's relevant, it stays grounded in what's actually there.&lt;/p&gt;

&lt;p&gt;To make this work, I needed three things: a way to split my content into searchable pieces, a way to search by meaning (not just keywords), and a language model to write the final answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Splitting Content Into Chunks
&lt;/h2&gt;

&lt;p&gt;My content lives in MDX files: one per project, one per job, one per blog post. Some of these are long. A single project page might describe the tech stack, what I built, and how it works, all in one file. Sending an entire file as context when the user only asked about the tech stack wastes tokens and adds noise.&lt;/p&gt;

&lt;p&gt;So I split each file into smaller chunks at paragraph boundaries, capped at 500 characters:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chunkText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxLen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paragraphs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &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;para&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;paragraphs&lt;/span&gt;&lt;span class="p"&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;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;para&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxLen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
      &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;para&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&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="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;para&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;chunks&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;One thing I learned through testing: raw chunks with no context confused the model. A chunk that says "Built with TypeScript and PostgreSQL" is meaningless without knowing whether it's describing a personal project or a company I worked at. The fix was adding type prefixes. Every chunk starts with &lt;code&gt;[PROJECT]&lt;/code&gt;, &lt;code&gt;[WORK EXPERIENCE]&lt;/code&gt;, &lt;code&gt;[BLOG POST]&lt;/code&gt;, or &lt;code&gt;[PROFILE]&lt;/code&gt;, so the AI immediately knows what kind of content it's looking at. I also added catalog chunks (complete lists of all projects or all work history) so questions like "list all my projects" don't return partial results.&lt;/p&gt;




&lt;h2&gt;
  
  
  Searching by Meaning
&lt;/h2&gt;

&lt;p&gt;Now I have chunks, but how do I find which ones are relevant to a question? Keyword search is the obvious choice, but it's brittle. If someone asks about "React experience" and my project description says "built with NextJS", there's no keyword match, even though NextJS is a React framework.&lt;/p&gt;

&lt;p&gt;This is where embeddings come in. An embedding model takes a piece of text and converts it into a list of numbers that represent its meaning. "React" and "NextJS" produce similar numbers because they're related concepts. "PostgreSQL" and "Redis" end up close together because they're both databases. When someone asks about "React experience", the question gets converted to numbers too, and it naturally lands close to anything frontend-related in my content.&lt;/p&gt;

&lt;p&gt;To convert text into these numbers, you need an embedding model. My first attempt used the HuggingFace Inference API, which worked, but had a problem: 0.5 seconds when the model was warm, 9.4 seconds when it was cold. HuggingFace spins down free-tier models after inactivity, so the chatbot would randomly hang for nearly 10 seconds. I switched to running the same model locally. &lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt; is a popular open-source option, only 22MB, and it produces 384 numbers per piece of text in about 12ms:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@huggingface/transformers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extractor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;feature-extraction&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;Xenova/all-MiniLM-L6-v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;embedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pooling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;normalize&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// 384 numbers&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At build time, I run this on every chunk and save the results to a JSON file. At runtime, I embed the user's question and find the closest chunks by comparing their numbers using cosine similarity (how much two sets of numbers point in the same direction):&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchChunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryEmbedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;embedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;chunk&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;cosineSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;topK&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;If you're working with thousands of chunks, you'd want a vector database like Pinecone or Weaviate to handle the search. For a personal site with around 160 chunks, looping through all of them in memory works fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generating the Answer
&lt;/h2&gt;

&lt;p&gt;At this point I have the top 8 chunks most relevant to the user's question. The last step is sending them to a language model to write a readable answer.&lt;/p&gt;

&lt;p&gt;I went with Groq's free tier running Llama 3.1 8B. The model doesn't know anything about me by default. It only sees whatever chunks I send it. The system prompt tells it how to interpret the content and what the type prefixes mean:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a helpful assistant on a personal website.
Answer questions using only the provided context.

Pay attention to type labels:
- [PROJECT]: Portfolio projects
- [WORK EXPERIENCE]: Employment history
- [BLOG POST]: Articles written
- [PROFILE]: Personal info

Keep answers concise and friendly. Do not make up information.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API call:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.groq.com/openai/v1/chat/completions&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&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;Content-Type&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llama-3.1-8b-instant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;conversationHistory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Context:\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;relevantChunks&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\nQuestion: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temperature controls how creative the model gets. At 0.3 (out of 1.0), it stays close to the most likely answer, which is what you want when accuracy matters. Conversation history (the last 10 messages) goes in with each request so follow-up questions like "tell me more about that project" work without losing context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploying to Vercel
&lt;/h2&gt;

&lt;p&gt;At this point everything worked locally and I was ready to deploy and move on. The chatbot ran as a serverless function through Astro's Vercel adapter, the model was only 22MB, and the embeddings were a static JSON file. Should have been the easy part.&lt;/p&gt;

&lt;p&gt;I deployed and immediately hit Vercel's 250MB size limit on serverless functions. The model is only 22MB, so that wasn't the issue. &lt;code&gt;@huggingface/transformers&lt;/code&gt; depends on &lt;code&gt;onnxruntime-node&lt;/code&gt;, which ships native binaries for every platform. They all get bundled into your function, and that alone pushes you way past 250MB.&lt;/p&gt;

&lt;p&gt;There's a lighter alternative called &lt;code&gt;onnxruntime-web&lt;/code&gt; that uses WebAssembly instead of native binaries, around 11MB. But it's built for browsers. Run it in Node.js and it tries to fetch WASM files from a CDN over HTTPS, which Node.js refuses to do.&lt;/p&gt;

&lt;p&gt;The workaround: swap &lt;code&gt;onnxruntime-node&lt;/code&gt; for &lt;code&gt;onnxruntime-web&lt;/code&gt; with a pnpm override, copy the WASM files to a local directory during the build, and tell the runtime to load them from the filesystem instead of the CDN:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasmDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;onnxEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasmPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`file://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;wasmDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ort-wasm-simd-threaded.wasm`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mjs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`file://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;wasmDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ort-wasm-simd-threaded.mjs`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;onnxEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numThreads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Vercel's &lt;code&gt;includeFiles&lt;/code&gt; bundling the model and WASM into the function, the same local inference that works on my laptop works in production. No embedding API, no cold starts, no cost.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Embedding a query: ~50ms&lt;/li&gt;
&lt;li&gt;Searching 164 chunks: under 1ms&lt;/li&gt;
&lt;li&gt;LLM response: ~400ms&lt;/li&gt;
&lt;li&gt;Total: under 500ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Monthly cost: $0. Groq's free tier covers the LLM, embeddings run inside the serverless function, and chunk data is a static JSON file built at deploy time.&lt;/p&gt;

&lt;p&gt;The whole thing is around 250 lines of TypeScript. There's a chat button on &lt;a href="https://akrom.dev" rel="noopener noreferrer"&gt;my site&lt;/a&gt; if you want to try it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://akrom.dev/blog/building-rag-chatbot" rel="noopener noreferrer"&gt;akrom.dev&lt;/a&gt;. For quick dev tips, join &lt;a href="https://t.me/akromdotdev" rel="noopener noreferrer"&gt;@akromdotdev on Telegram&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>typescript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cursor vs Claude Code: Why I Switched After a $500 Bill</title>
      <dc:creator>Akromdev</dc:creator>
      <pubDate>Tue, 10 Feb 2026 22:26:26 +0000</pubDate>
      <link>https://forem.com/akromdev/cursor-vs-claude-code-why-i-switched-after-a-500-bill-33jp</link>
      <guid>https://forem.com/akromdev/cursor-vs-claude-code-why-i-switched-after-a-500-bill-33jp</guid>
      <description>&lt;h2&gt;
  
  
  How We Got Here
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This section is a brief history of AI coding tools. If you just want the Cursor vs Claude Code comparison, skip to The $500 Problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I still remember the first time I copied code from ChatGPT into my editor. It was late 2022, and the whole thing felt like magic wrapped in duct tape. Open browser, type question, get code, copy, paste, fix imports, repeat. Clunky? Absolutely. But it worked, and that was enough to make it feel revolutionary.&lt;/p&gt;

&lt;p&gt;Then GitHub Copilot showed up in mid-2022, and suddenly we had AI living inside our editors. No more context switching. Just start typing and watch the ghost text appear. It felt like the future, until you actually used it for a while.&lt;/p&gt;

&lt;p&gt;Early Copilot had real problems. It only used OpenAI Codex, with zero model selection until late 2024. It only saw your current file, no project-wide awareness, no understanding of your architecture, no context beyond the code you were staring at.&lt;/p&gt;

&lt;p&gt;The quality wasn't great either. GitHub's own benchmarks showed 43% first-try accuracy, meaning it was wrong more often than it was right. An &lt;a href="https://cyber.nyu.edu/2021/10/15/ccs-researchers-find-github-copilot-generates-vulnerable-code-40-of-the-time/" rel="noopener noreferrer"&gt;NYU study&lt;/a&gt; found that 40% of its generated code had security vulnerabilities. Not reassuring when you're shipping to production.&lt;/p&gt;

&lt;p&gt;The autocomplete would pop up at the weirdest moments, fighting with IntelliSense, inserting code in the wrong place, or leaving you with mismatched brackets to clean up. It broke your flow more often than it helped.&lt;/p&gt;

&lt;p&gt;But we kept using it because, well, what else was there?&lt;/p&gt;

&lt;p&gt;Cursor changed things in 2023-2024. Tab completion that actually worked. Model selection so you could pick what you wanted. Then they acquired Supermaven and suddenly autocomplete was sub-500ms, fast enough that it felt like reading your mind instead of lagging behind your thoughts.&lt;/p&gt;

&lt;p&gt;And then the model arms race kicked into high gear. Sonnet 3.5 was the breakout star, fast, capable, and good enough for most tasks. Sonnet 4 raised the bar even higher. Thinking models arrived and gave us a glimpse of what was possible when you let the AI actually reason through problems. And then Opus dropped, and everything changed.&lt;/p&gt;

&lt;p&gt;Once you've used Opus for a complex refactor or a gnarly architectural decision, going back to Sonnet feels like trading in a sports car for a bicycle. It's not that Sonnet is bad, it's great for most things. But Opus just &lt;em&gt;gets it&lt;/em&gt; in a way that other models don't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The $500 Problem
&lt;/h2&gt;

&lt;p&gt;The difference between Opus and Sonnet isn't subtle. When you're doing complex refactors, making architectural decisions, or debugging something that requires understanding six layers of abstraction, Opus is in a different league.&lt;/p&gt;

&lt;p&gt;It's the difference between "here's a solution that might work" and "here's the solution, and here's why the three alternatives you're probably thinking about won't work in your specific case." But that quality comes at a price. A steep one.&lt;/p&gt;

&lt;p&gt;Opus 4 costs $15 per million input tokens and $75 per million output tokens. Sonnet 4 costs $3 input and $15 output. That's a 5x price difference, and when you're using these tools all day, every day, it adds up fast.&lt;/p&gt;

&lt;p&gt;My typical Sonnet months on Cursor's usage-based billing ran $100-150. Annoying but manageable. My first month using Opus heavily? $477 in 28 days. Luckily my client covered the bills, but I still felt guilty seeing that number.&lt;/p&gt;

&lt;p&gt;I started thinking about tokens instead of code. Should I start a new chat to save context? Is this refactor worth the cost? Maybe there are tricks to reduce token consumption, but I didn't want to become an expert in prompt optimization. I wanted to code and not care about billing.&lt;/p&gt;

&lt;p&gt;So I started looking for alternatives, and found Claude Code Max.&lt;/p&gt;




&lt;h2&gt;
  
  
  Claude Code Max: Pay $100 and Stop Worrying
&lt;/h2&gt;

&lt;p&gt;Claude Code Max is Anthropic's answer to the usage-based billing problem. Pay $100 a month for Max 5x, or $200 for Max 20x, and you get full Opus access with no per-token charges. Just a flat rate and a generous usage cap.&lt;/p&gt;

&lt;p&gt;The limits work on a rolling window system, which sounds complicated but is actually pretty simple in practice. You get a 5-hour rolling window for burst usage. It doesn't reset on a fixed schedule. Instead, it's based on when &lt;em&gt;you&lt;/em&gt; started your session.&lt;/p&gt;

&lt;p&gt;There's also a weekly active hours cap, where "active" means when Claude is processing tokens, not when you're reading output or thinking.&lt;/p&gt;

&lt;p&gt;Max 5x gives you all-day Sonnet access and a generous Opus allocation. In practice, that's more than enough for full-time development. Personally, I haven't hit the hourly limit yet. If you do, it resets in a few hours. Not the worst excuse to step away and make sure you still remember how to code without AI.&lt;/p&gt;

&lt;p&gt;The mental shift was immediate. I stopped thinking about money, optimizing prompts to save tokens, second-guessing whether a question was "worth it." I just worked.&lt;/p&gt;

&lt;p&gt;And the math? ~$500 a month down to $100 is over $4,000 saved per year for essentially the same Opus access.&lt;/p&gt;

&lt;p&gt;But switching tools isn't just about money. If Claude Code was worse than Cursor in every other way, the savings wouldn't matter. So let's break down what each tool actually does better.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Cursor Still Does Better
&lt;/h2&gt;

&lt;p&gt;I'm not here to tell you Cursor is bad. It's not. There are real things it does better than Claude Code, and if you're considering the switch, you should know what you're trading away.&lt;/p&gt;

&lt;p&gt;Tab completion is the big one. Cursor's Fusion model combined with Supermaven is genuinely impressive. Sub-500ms response times with 13K token context windows. It doesn't just predict what you're about to type; it predicts where you're about to navigate next.&lt;/p&gt;

&lt;p&gt;When you're making lots of small edits across multiple files, this alone might be worth staying on Cursor for. Claude Code doesn't even try to compete here.&lt;/p&gt;

&lt;p&gt;Then there's the GUI advantage. Selecting files, folders, code snippets, terminal output — just click and add to chat. In Claude Code, you type &lt;code&gt;@filename&lt;/code&gt; or specify line numbers manually. It's not hard, but it's not as smooth. When you're trying to quickly pull together context from six different files, point-and-click is faster than typing.&lt;/p&gt;

&lt;p&gt;Diff review is another place where Cursor shines. You get inline red-green diffs with accept-reject buttons for each change. It's intuitive, and it makes reviewing AI-generated changes feel natural. Claude Code works in the terminal, which means you're either reviewing diffs in your editor or using &lt;code&gt;git diff&lt;/code&gt;. You can get visual diffs if you use the Zed editor integration, but Zed has its own quirks (more on that later).&lt;/p&gt;

&lt;p&gt;Multi-model fallback is a nice safety net. When Anthropic has an outage (rare, but it happens), Cursor lets you switch to GPT or Gemini and keep working. Claude Code is Claude only. If Anthropic is down, you're done.&lt;/p&gt;

&lt;p&gt;Cursor has background agents that run in the cloud. You can kick off a complex task, switch to something else, and come back when it's done. Claude Code doesn't have anything like this yet.&lt;/p&gt;

&lt;p&gt;I've also noticed Claude Code occasionally slowing down with large conversations. There are known issues where context units above 1,000 can cause typing delays (see &lt;a href="https://github.com/anthropics/claude-code/issues/12222" rel="noopener noreferrer"&gt;issue #12222&lt;/a&gt;). Cursor seemed to handle big feature work more gracefully, though I haven't done enough side-by-side testing to say that definitively.&lt;/p&gt;

&lt;p&gt;And finally, IDE integration depth. Cursor has been building on top of VS Code for years. Chrome DevTools integration, speech-to-text, polished GUI features. It's a mature product. Claude Code is newer and rougher around the edges.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Claude Code Does Better
&lt;/h2&gt;

&lt;p&gt;This might not matter to everyone, but I love that it's CLI-native. I'm in the terminal all day anyway. Cursor's chat panel takes up space inside your editor, competing with your code for screen real estate. Claude Code is just another terminal pane, same place you're already running builds, git, and tests. Reference files with &lt;code&gt;@&lt;/code&gt;, drag and drop images straight in. It fits into the workflow you already have instead of adding a new one on top.&lt;/p&gt;

&lt;p&gt;The CLAUDE.md system is where Claude Code really pulls ahead.&lt;/p&gt;

&lt;p&gt;CLAUDE.md is persistent project memory. It loads every single session automatically, conventions, architecture, common commands, gotchas. Claude never forgets. You write it once, and every conversation starts with that context already loaded. No more explaining the same things over and over.&lt;/p&gt;

&lt;p&gt;Custom slash commands let you define reusable actions for your project. Things like &lt;code&gt;/fix&lt;/code&gt; for targeted debugging, &lt;code&gt;/test-service&lt;/code&gt; to generate tests following your patterns, or &lt;code&gt;/pr-review&lt;/code&gt; to check code against your team's standards.&lt;/p&gt;

&lt;p&gt;Skills go a step further. They're knowledge packages that teach Claude how a specific part of your codebase works, your DAL patterns, your testing conventions, your component architecture. Commands tell Claude what to do. Skills teach Claude how to think about your code.&lt;/p&gt;

&lt;p&gt;Then there are hooks. They let you automate actions based on events. For example, you can run your linter automatically after every edit, so issues get caught before you even look at the code. No manual step, no forgetting.&lt;/p&gt;

&lt;p&gt;There's also an official GitHub Actions integration. Mention &lt;code&gt;@claude&lt;/code&gt; in a PR comment and it reviews your code, suggests fixes, or creates commits following your project's CLAUDE.md standards. Tag it in an issue and it can create a branch with the implementation. Everything you've set up locally carries over to your CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;All of this is version controlled and shared with the team. Cursor has recently added its own rules, commands, and skills system. It's catching up, but Claude Code's ecosystem is more mature, with deeper hierarchies, hooks, and a larger community building on top of it. If you want to see what's possible, check out the &lt;a href="https://docs.claude.com/en/docs/claude-code/skills" rel="noopener noreferrer"&gt;official Claude Code documentation&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenCode &amp;amp; Oh-My-OpenCode
&lt;/h2&gt;

&lt;p&gt;So far I've been comparing Cursor and Claude Code as they come out of the box. But there's an open-source ecosystem that supercharges the whole experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/opencode-ai/opencode" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; is an open-source AI coding agent with over 93,000 GitHub stars. It supports Claude, GPT, Gemini, and local models, so you're not locked into one provider. Even if you only use Claude today, Oh-My-OpenCode makes it worth the switch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/code-yeongyu/oh-my-opencode" rel="noopener noreferrer"&gt;Oh-My-OpenCode&lt;/a&gt; is an orchestration layer on top of OpenCode, basically Oh-My-Zsh for AI coding. Instead of one agent doing everything sequentially, it spins up specialized agents that work in parallel. One agent can research documentation in the background while another explores your codebase and the main agent implements the feature you asked for. All agents run in parallel and work independently.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As of January 2026, Anthropic has been tightening restrictions on third-party tools using Claude subscriptions. OpenCode works for now, but you may need workarounds if things change.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Note on Editors
&lt;/h2&gt;

&lt;p&gt;Claude Code has a VS Code extension, but I found the raw terminal experience more reliable. Image uploading was buggy and the file picker was noticeably slower than the CLI. Not dealbreakers, but enough to make me stick with the terminal.&lt;/p&gt;

&lt;p&gt;I also tried Zed with Claude integration. It's promising but not there yet. No persistent chat history for external agents (&lt;a href="https://github.com/zed-industries/zed/issues/37074" rel="noopener noreferrer"&gt;zed issue #37074&lt;/a&gt;), so conversations vanish when you close them. You can't run Claude's slash commands like &lt;code&gt;/resume&lt;/code&gt; through Zed (&lt;a href="https://github.com/zed-industries/zed/issues/37719" rel="noopener noreferrer"&gt;zed issue #37719&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;My recommendation? Just use Claude Code (with OpenCode and Oh-My-OpenCode) in a terminal pane alongside your editor. Simple, reliable, no integration bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Best of Both Worlds
&lt;/h2&gt;

&lt;p&gt;Here's what I actually do: Cursor Pro ($20/month, usage-based billing turned OFF) plus Claude Code Max ($100/month). Total: $120/month.&lt;/p&gt;

&lt;p&gt;I use Cursor for tab completion and quick inline edits. The Fusion/Supermaven model is still the best in the business for autocomplete, and on the Pro plan with usage-based billing disabled, it's unlimited. For small changes and rapid iteration, it's perfect.&lt;/p&gt;

&lt;p&gt;I use Claude Code for everything else, through OpenCode with Oh-My-OpenCode. Multi-file refactors, architecture decisions, debugging, test generation. Oh-My-OpenCode spins up multiple agents working in parallel, so research, implementation, and verification happen at the same time instead of one after another.&lt;/p&gt;

&lt;p&gt;There's no conflict. Cursor is the IDE, Claude Code is the brain. One handles the keystrokes, the other handles the thinking. They complement each other perfectly.&lt;/p&gt;

&lt;p&gt;$120 a month for the best of both worlds versus ~$500 a month for Cursor with Opus alone. That's a 75% savings.&lt;/p&gt;




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

&lt;p&gt;There's more to Claude Code than I've covered here. The skills system goes deep, hooks and multi-agent workflows open up possibilities I'm still discovering, and CLAUDE.md keeps surprising me with how much it changes the daily workflow.&lt;/p&gt;

&lt;p&gt;I'm thinking about writing a follow-up on getting the most out of Claude Code. How to structure your CLAUDE.md, how custom skills can automate your team's workflow, and how OpenCode with Oh-My-OpenCode can supercharge your setup. Let me know if that's something you'd find useful.&lt;/p&gt;

&lt;p&gt;What's your setup? Still on Cursor? Tried Claude Code? Using something else entirely? I'd love to hear what's working for you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://akrom.dev/blog/cursor-to-claude-code" rel="noopener noreferrer"&gt;akrom.dev&lt;/a&gt;. For quick dev tips, join &lt;a href="https://t.me/akromdotdev" rel="noopener noreferrer"&gt;@akromdotdev on Telegram&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cursor</category>
      <category>claude</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
