<?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: Jasmin Virdi</title>
    <description>The latest articles on DEV Community by Jasmin Virdi (@jasmin).</description>
    <link>https://dev.to/jasmin</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%2F322836%2Fde35ee13-9df1-4b90-9734-9f29aafe4ef4.jpeg</url>
      <title>DEV Community: Jasmin Virdi</title>
      <link>https://dev.to/jasmin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jasmin"/>
    <language>en</language>
    <item>
      <title>Next in Building TinyAgent series is Streaming!</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Mon, 01 Jun 2026 08:27:35 +0000</pubDate>
      <link>https://dev.to/jasmin/next-in-building-tinyagent-series-is-streaming-1b4g</link>
      <guid>https://dev.to/jasmin/next-in-building-tinyagent-series-is-streaming-1b4g</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh" class="crayons-story__hidden-navigation-link"&gt;Streaming an LLM response, in 4 GIFs&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Perceived speed vs actual latency&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/jasmin" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F322836%2Fde35ee13-9df1-4b90-9734-9f29aafe4ef4.jpeg" alt="jasmin profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jasmin" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jasmin Virdi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jasmin Virdi
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3786280" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jasmin" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F322836%2Fde35ee13-9df1-4b90-9734-9f29aafe4ef4.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jasmin Virdi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 31&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh" id="article-link-3786280"&gt;
          Streaming an LLM response, in 4 GIFs
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;32&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              13&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Streaming an LLM response, in 4 GIFs</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Sun, 31 May 2026 00:16:15 +0000</pubDate>
      <link>https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh</link>
      <guid>https://dev.to/jasmin/streaming-an-llm-response-in-4-gifs-16dh</guid>
      <description>&lt;p&gt;We have watched tokens stream in from an LLM before where they appeared one at a time, like the model was typing. If you used the Anthropic SDK's .stream() method, it just worked and you probably never saw what was on the wire.&lt;/p&gt;

&lt;p&gt;This post will majorly focus on how a stream response works and how bugs are handled by SDK behind the hood.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h2&gt;
  
  
  1. Why Streaming exists
&lt;/h2&gt;

&lt;p&gt;To enable the streaming option we would need to make one change in the post request that is a single field &lt;code&gt;"stream": true&lt;/code&gt; and it will change the response experience.&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%2Fx3uf2767uk1nqgkt88ma.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%2Fx3uf2767uk1nqgkt88ma.gif" alt="non-streaming vs streaming, side by side" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the pointers we take from the gif.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The left side shows no streaming as the cursor blinks for 4 seconds then the whole response lands at once.&lt;/li&gt;
&lt;li&gt;The right side shows the streaming where the first word shows up in about 300 milliseconds. Words flow in as the model generates them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both the sides have &lt;strong&gt;same model, same prompt, same total time&lt;/strong&gt; it is just the right side started giving response almost 4 seconds earlier. The 4 seconds wait time for a full reply feels broken. A streamed reply that finishes in four seconds feels fast. &lt;strong&gt;&lt;em&gt;Streaming doesn't make the model faster it makes the wait disappear.&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;

&lt;/p&gt;
&lt;/div&gt;






&lt;div class="crayons-card c-embed"&gt;

  
&lt;h2&gt;
  
  
  2. What's on the wire
&lt;/h2&gt;

&lt;p&gt;When you set &lt;code&gt;stream: true&lt;/code&gt;, the API stops sending a single JSON blob. It opens a persistent HTTP connection and pushes events down the line as the model generates them. &lt;strong&gt;The format is Server-Sent Events (SSE) a web standard.&lt;/strong&gt; Any SSE debugger will read this stream.&lt;/p&gt;

&lt;p&gt;Here's what comes through:&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%2Fmuhve5nr669vvwuhmmae.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%2Fmuhve5nr669vvwuhmmae.gif" alt="raw SSE chunks streaming in with delta.text and stop_reason highlighted" width="800" height="547"&gt;&lt;/a&gt;&lt;br&gt;
A few things to notice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The text lives in &lt;code&gt;delta.text&lt;/code&gt;&lt;/strong&gt;, nested inside &lt;code&gt;content_block_delta&lt;/code&gt; events. Those are the events we should look after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;stop_reason&lt;/code&gt; moved.&lt;/strong&gt; &lt;a href="https://dev.to/jasmin/an-llm-api-call-in-4-gifs-33b1"&gt;In post 1&lt;/a&gt;, we saw it right there in the response JSON. Here, it arrives at the very end inside a &lt;code&gt;message_delta&lt;/code&gt; event, just before &lt;code&gt;message_stop&lt;/code&gt;. If the loop bails out as soon as the text stops arriving we will never see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chunks don't line up with tokens or words.&lt;/strong&gt; You might get &lt;code&gt;"Hello"&lt;/code&gt; in one chunk and &lt;code&gt;" world"&lt;/code&gt; in the next, or both in one. The network decides where the cuts happens and it is not the model, not the API.&lt;/p&gt;

&lt;p&gt;That's what the SDK has been hiding from you.&lt;br&gt;

&lt;/p&gt;
&lt;/div&gt;






&lt;div class="crayons-card c-embed"&gt;

  
&lt;h2&gt;
  
  
  3. Reading the stream
&lt;/h2&gt;

&lt;p&gt;Streaming sounds complicated until we write the loop. It's just reading bytes, buffering them, splitting on blank lines, and parsing JSON.&lt;/p&gt;

&lt;p&gt;Here's the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The response body is a &lt;code&gt;ReadableStream&lt;/code&gt; which can be iterated with &lt;code&gt;for await&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Each iteration gives us bytes which we can decode to string.&lt;/li&gt;
&lt;li&gt;Buffer the string. A chunk might end mid-message.&lt;/li&gt;
&lt;li&gt;Split the buffer on &lt;code&gt;\n\n&lt;/code&gt; — that's the SSE message separator.&lt;/li&gt;
&lt;li&gt;Keep the last item in the buffer. It might be incomplete.&lt;/li&gt;
&lt;li&gt;For each complete message, find the &lt;code&gt;data:&lt;/code&gt; line, strip the prefix, and parse the JSON.&lt;/li&gt;
&lt;li&gt;If the type is &lt;code&gt;content_block_delta&lt;/code&gt;, print &lt;code&gt;delta.text&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it's &lt;code&gt;message_delta&lt;/code&gt;, you've got your &lt;code&gt;stop_reason&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&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%2F613lapdgc9r2kbnvlf0s.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%2F613lapdgc9r2kbnvlf0s.gif" alt="code on left highlights line by line, output appears on right" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the complete sample code you can use to try out:&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;prompt&lt;/span&gt; &lt;span class="o"&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;argv&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;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Count to 10, slowly.&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;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.anthropic.com/v1/messages&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;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&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;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anthropic-version&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;2023-06-01&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;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="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;claude-opus-4-5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stream&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="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="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="nx"&gt;prompt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&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;buffer&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="k"&gt;await &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&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="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="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&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;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;messages&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;dataLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&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="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data: &lt;/span&gt;&lt;span class="dl"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dataLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&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;data&lt;/span&gt; &lt;span class="o"&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;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataLine&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;6&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;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;content_block_delta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;delta&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;text_delta&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&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;delta&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="k"&gt;if &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;message_delta&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n\n[stop_reason: &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;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stop_reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]\n`&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The way it is working is that when the chunk ends in the middle of a message &lt;code&gt;split("\n\n")&lt;/code&gt; leaves an incomplete fragment as the last item. &lt;code&gt;pop()&lt;/code&gt; pulls it back into the buffer so the next chunk can finish it. Without this line, every split message crashes the parser. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;data.delta.type === "text_delta"&lt;/code&gt; this check matters because content_block_delta can carry other delta types too: &lt;code&gt;input_json_delta&lt;/code&gt; for tool arguments, &lt;code&gt;thinking_delta&lt;/code&gt; for extended thinking, &lt;code&gt;signature_delta&lt;/code&gt; for verification. For now we only care about text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;You can find the full implementation &lt;a href="https://github.com/Jasmin2895/TinyAgent/tree/main/streaming" rel="noopener noreferrer"&gt;here on GitHub as well&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;


&lt;/div&gt;







&lt;div class="crayons-card c-embed"&gt;

  
&lt;h2&gt;
  
  
  4. Three bugs
&lt;/h2&gt;

&lt;p&gt;The code above works on a good day. Here's what breaks it on a bad one.&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%2Ffupcfg6s8117ot9rxh3n.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%2Ffupcfg6s8117ot9rxh3n.gif" alt="three bugs — ghost stream, silent truncation, split packet" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ghost stream.&lt;/strong&gt; The issue is user navigates away with the stream keeps running and tokens keep arriving with nobody to read them. In order to fix this pass an &lt;code&gt;AbortController&lt;/code&gt; signal to &lt;code&gt;fetch&lt;/code&gt; and call &lt;code&gt;abort()&lt;/code&gt; when you're done.&lt;/p&gt;

&lt;p&gt;The fix is an &lt;code&gt;AbortController&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&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;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="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// later, when the user navigates away:&lt;/span&gt;
&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&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;The silent truncation.&lt;/strong&gt; The API can send an &lt;code&gt;error&lt;/code&gt; event mid stream during overload. If the loop only handles &lt;code&gt;content_block_delta&lt;/code&gt;, the error gets skipped and you end up with a truncated response and no exception. The fix is to handle &lt;code&gt;data.type === "error"&lt;/code&gt; explicitly.&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;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;error&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Stream error: &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;error&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="s2"&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;&lt;strong&gt;The split packet.&lt;/strong&gt; A single SSE message can arrive in two TCP packets. Without buffering, &lt;code&gt;JSON.parse&lt;/code&gt; throws on the half. This is what &lt;code&gt;buffer = messages.pop() ?? ""&lt;/code&gt; fixes, it holds the incomplete piece until the next chunk completes it.&lt;/p&gt;


&lt;/div&gt;



&lt;h3&gt;
  
  
  stop_reason, in a stream
&lt;/h3&gt;

&lt;p&gt;In post 1, &lt;code&gt;stop_reason&lt;/code&gt; was right there in the response JSON. In a stream, it's the same four values &lt;code&gt;end_turn&lt;/code&gt;, &lt;code&gt;max_tokens&lt;/code&gt;, &lt;code&gt;tool_use&lt;/code&gt;, &lt;code&gt;stop_sequence&lt;/code&gt; but they arrive inside a &lt;code&gt;message_delta&lt;/code&gt; event near the end of the stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn",...}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same rule from post 1 applies: if you ignore &lt;code&gt;stop_reason&lt;/code&gt;, you'll ship a bug. A &lt;code&gt;max_tokens&lt;/code&gt; cutoff in a streamed response looks exactly like a normal end of stream. You won't know the model was cut off unless you read this event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three things to try before the next post
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Run the streaming code. Then change &lt;code&gt;"stream": true&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; and run it again. Notice how long you wait before seeing anything. That gap is what your users feel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Add &lt;code&gt;console.error(chunk.length)&lt;/code&gt; inside the &lt;code&gt;for await&lt;/code&gt; loop, before any parsing. Run the code and watch the numbers. You'll see chunks of wildly different sizes it could be 8 bytes here, 400 bytes there. The network decides, not the model. Tokens and chunks are not the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Start a stream, then disconnect your wifi mid response. Watch what happens. The loop hangs, then eventually throws but only if we have added error handling. This sets up the error handling post later in the series.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;TinyAgent can now stream a response. Tokens land as they arrive. &lt;code&gt;stop_reason&lt;/code&gt; shows up at the end. It still has no memory though every call starts blank.&lt;/p&gt;

&lt;p&gt;In the upcoming post series we will capture another important details. 😁&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy Coding! 👩‍💻&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>An LLM API call, in 4 GIFs</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Tue, 26 May 2026 20:52:22 +0000</pubDate>
      <link>https://dev.to/jasmin/an-llm-api-call-in-4-gifs-33b1</link>
      <guid>https://dev.to/jasmin/an-llm-api-call-in-4-gifs-33b1</guid>
      <description>&lt;p&gt;This is the first post of series &lt;strong&gt;Building TinyAgent&lt;/strong&gt; where we are going to build a small agent from scratch in Node.js with no frameworks just the API calls.&lt;/p&gt;

&lt;p&gt;But before we write an agent, we need to understand what actually happens when you call an LLM. If you've only ever used a SDK, you've probably never seen the raw request and understand how it works. Six lines of code, an API key, and it just works but you have no idea what happened when request was dispatched and response was printed on the screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The request
&lt;/h2&gt;

&lt;p&gt;Here is the sample API call with each and every section explained in detail.&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%2Fi5h0kmjhzvsegaoovqb0.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%2Fi5h0kmjhzvsegaoovqb0.gif" alt="A curl command typing itself into a terminal and being sent to the API" width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few things worth noticing in the API call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API is stateless&lt;/strong&gt;: Every new API call does not remember previous call context. If you want a chatbot that "remembers" earlier messages, you hold the messages array and resend the whole thing every time. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;max_tokens&lt;/code&gt; is a hard stop, not a target.&lt;/strong&gt; If you hit the target the response stops mid sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API call pattern is universal.&lt;/strong&gt; Different URL, Authorization: Bearer instead of x-api-key, the system prompt lives inside messages rather than at the top level. But it's the same POST, the same JSON, the same {model, messages, max_tokens}. Once you understand the shape, switching providers is just a find-and-replace.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The response
&lt;/h2&gt;

&lt;p&gt;The API answers with a JSON blob. There are ~10 fields in it, but only four actually matter:&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%2F95h1fbgewrurfpmda1x2.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%2F95h1fbgewrurfpmda1x2.gif" alt="The response JSON streams in and four key fields highlight in sequence" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The one which is mostly skipped is: &lt;strong&gt;&lt;code&gt;stop_reason&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It tells you &lt;em&gt;why&lt;/em&gt; the model stopped, and in real systems and there could be possible reasons behind it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;end_turn      → finished naturally, you're done
max_tokens    → hit your ceiling, response is truncated
tool_use      → model wants to call a tool (next post!)
stop_sequence → matched one of your stop strings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you only check the text and ignore &lt;code&gt;stop_reason&lt;/code&gt;, you will ship a bug at some point. The response looks fine right up until it doesn't.&lt;/p&gt;

&lt;p&gt;The other field worth burning in: &lt;strong&gt;&lt;code&gt;usage&lt;/code&gt;&lt;/strong&gt;. It shows you how many tokens went in and came out. You want this number in your logs from day one not after you get a surprise bill. 🤯&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Tokens
&lt;/h2&gt;

&lt;p&gt;I keep saying "24 input tokens." Here's what that means:&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%2Fbgkooeh4lu2qwe52dya0.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%2Fbgkooeh4lu2qwe52dya0.gif" alt="Different inputs shattering into colored token chips: English, rare words, code, JSON" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Things that surprise people and is worth noting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Words don't equal tokens.&lt;/strong&gt; "Unbelievable" is one word but four tokens. The tokenizer splits on common substrings, not spaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code costs more than it looks&lt;/strong&gt; &lt;code&gt;def add(a, b):&lt;/code&gt; is 8 tokens. Every bracket and comma is its own token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON is expensive.&lt;/strong&gt; &lt;code&gt;{"a":1}&lt;/code&gt; is 7 tokens. If your tool schemas are bloated, they're quietly eating into your budget on every single request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-English costs more&lt;/strong&gt; Japanese, Hindi, Arabic tend to run 2–4× the token count of the same content in English. If you're building for a global audience, this changes your cost math a lot.&lt;/p&gt;

&lt;p&gt;Rule of thumb for English prose: ~1 token ≈ 4 characters ≈ 0.75 words. For everything else, run it through the tokenizer yourself before assuming.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. The bill
&lt;/h2&gt;

&lt;p&gt;Two meters run on every call. They are priced &lt;em&gt;differently&lt;/em&gt;&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%2Fts7uergcm8aaisqsxn0r.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%2Fts7uergcm8aaisqsxn0r.gif" alt="Input and output bars filling at very different rates, showing cost asymmetry" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Output tokens cost roughly 3–5× more than input tokens. That's the one number to internalize about LLM pricing.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost = (input_tokens  / 1,000,000) × input_price
     + (output_tokens / 1,000,000) × output_price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three things that follow from the asymmetry:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Long prompts are cheap. Long responses are expensive.&lt;/strong&gt; Stuffing 50 KB of context into a system prompt is fine. Asking for 50 KB of output is roughly 5× more expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Thinking" tokens count as output.&lt;/strong&gt; Reasoning models bill their internal thought at the output rate, even though you don't see it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool schemas eat input on every call.&lt;/strong&gt; They get resent with every request, just like the system prompt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At $0.006 per call, 100k calls a day is $600/month from one small feature. Add usage logging now, not when you get the alert. 🚨&lt;/p&gt;
&lt;h2&gt;
  
  
  5. The whole thing in 20 lines
&lt;/h2&gt;

&lt;p&gt;Here is the complete code of the API call we have discussed above:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Jasmin2895" rel="noopener noreferrer"&gt;
        Jasmin2895
      &lt;/a&gt; / &lt;a href="https://github.com/Jasmin2895/TinyAgent" rel="noopener noreferrer"&gt;
        TinyAgent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;No dependencies and no install setup it is just Node file with API key.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three things to try before the next post
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run it and watch the numbers&lt;/strong&gt; Make ten calls, change the prompt length, see how usage moves. You'll build a real instinct for cost faster this way than reading any doc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set &lt;code&gt;max_tokens: 20&lt;/code&gt; and ask for something long.&lt;/strong&gt; Watch it cut off. Check stop_reason. This is a bug you'll hit in production eventually better to meet it on purpose right now&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build a multi-turn chat by hand.&lt;/strong&gt; Keep a messages array, push each user message and each model reply onto it, and resend the whole thing every turn. Once you do this, you'll immediately understand why long conversations get expensive you're paying for the full history on every call.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In the upcoming post series we will expand the ability of the TinyAgent to actually handle lot of things than just responding.&lt;/p&gt;

&lt;p&gt;Happy Coding! 👩‍💻&lt;/p&gt;




</description>
      <category>llm</category>
      <category>javascript</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Taught Two AIs What Not to Say About Their Humans</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Sun, 26 Apr 2026 18:28:05 +0000</pubDate>
      <link>https://dev.to/jasmin/i-taught-two-ais-what-not-to-say-about-their-humans-2148</link>
      <guid>https://dev.to/jasmin/i-taught-two-ais-what-not-to-say-about-their-humans-2148</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/openclaw-2026-04-16"&gt;OpenClaw Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While brainstorming ideas for this hackathon and going thorugh OpenClaw features like &lt;strong&gt;persona files&lt;/strong&gt; as part of who the agent is gave me an idea of using this feature to build multi agent system where two agents representing two different humans, talking to each other where the information with each other is limited and controlled by a markdown file which acts as a privacy contract.&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%2Ffxh00bsj8gkfgh81per1.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%2Ffxh00bsj8gkfgh81per1.png" alt="Initial View" width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Clawmate is two AI agents, each representing a different human, talking through a shared file. Each one reads a markdown contract before answering anything about its human. &lt;/p&gt;

&lt;p&gt;Alice 🦞 is mine. Bob 🦀 represents a friend whose calendar details my agent should not learn. They share a file at &lt;code&gt;~/clawmate-shared/backchannel.json&lt;/code&gt;. Alice writes a query into it. Bob reads, applies his contract, writes a filtered answer back.&lt;/p&gt;

&lt;p&gt;The interesting catch here is what they choose to say about their humans and how they are communicating with each other.&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%2Ffnca3cjy0ylyneesofar.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%2Ffnca3cjy0ylyneesofar.png" alt="ideation" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture Explanation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two workspaces, two agents&lt;/strong&gt; - Alice in &lt;code&gt;~/.openclaw/workspace/&lt;/code&gt;, Bob in &lt;code&gt;~/.openclaw/workspace/bob/&lt;/code&gt;. Each has its own Telegram bot, routed independently with &lt;code&gt;openclaw agents bind --agent bob --bind telegram:bob&lt;/code&gt; so messages to Bob's bot reach Bob and not Alice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One shared file&lt;/strong&gt; that is &lt;code&gt;~/clawmate-shared/backchannel.json&lt;/code&gt; is the "conversation" between the two agents. They don't message each other on Telegram they just read and write into this JSON file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two privacy contracts&lt;/strong&gt; - Each agent has an &lt;code&gt;IDENTITY.md&lt;/code&gt; that lists what it will and won't share about its human. The agent reads the file as binding.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bob's agent markdown contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Privacy contract&lt;/span&gt;

&lt;span class="gu"&gt;### Never share&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Event names or descriptions
&lt;span class="p"&gt;-&lt;/span&gt; Message contents from anyone
&lt;span class="p"&gt;-&lt;/span&gt; Names of people Bob is meeting with
&lt;span class="p"&gt;-&lt;/span&gt; Locations Bob will be at

&lt;span class="gu"&gt;### Do share&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Whether Bob is busy or free in a time range
&lt;span class="p"&gt;-&lt;/span&gt; Whether Bob can be reached for an emergency

&lt;span class="gu"&gt;### When in doubt&lt;/span&gt;
Refuse, name the rule, offer what you can give.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Used OpenClaw
&lt;/h2&gt;

&lt;p&gt;The best part about building this is OpenClaw's design choice to make persona files. The features I used here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two agent workspaces&lt;/strong&gt; with separate session stores, so Alice and Bob don't share memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram channel bindings&lt;/strong&gt; with &lt;code&gt;agents bind&lt;/code&gt; to route distinct bots to distinct agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filesystem&lt;/strong&gt; tools so each agent can read/write the shared backchannel and its own calendar&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;code&gt;IDENTITY.md&lt;/code&gt;&lt;/strong&gt; persona layer as the actual enforcement mechanism for the privacy contract&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;custom Clawmate skill describing&lt;/strong&gt; the send-query/respond-to-query protocol&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I gave Bob a mock calendar with deliberate privacy traps:&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;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-26"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dinner with emma"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-27"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15:30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"therapy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-28"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"19:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"22:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"concert"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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;Asking about Bob calendar but due to &lt;strong&gt;contract the limited information is shared by Bob's agent&lt;/strong&gt;.&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%2Fmek0xs37fx50hzixctkt.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%2Fmek0xs37fx50hzixctkt.png" alt="bob calendar info" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent to Agent Loop between Bob and Alice Agent
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Alice writes the query &lt;/li&gt;
&lt;/ul&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%2Fsa1dhnz8q86bfxi6uqhx.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%2Fsa1dhnz8q86bfxi6uqhx.png" alt="alice messages" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bob reads, filters, responds.&lt;/li&gt;
&lt;/ul&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%2Fhmv7kz0gcbc37k0nc0uo.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%2Fhmv7kz0gcbc37k0nc0uo.png" alt="bob messages" width="800" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The shared file as ground truth.&lt;/li&gt;
&lt;/ul&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%2Fhw4yzuqj1p07avfo9oci.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%2Fhw4yzuqj1p07avfo9oci.png" alt="messages saved" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The conversation between Alice and Bob's agent is a JSON file changing over time. Their entire message exchange along with query and answer is present in the file. The word "concert" is not there. The privacy contract is the gap between what Bob read and what Bob wrote.&lt;/p&gt;

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

&lt;p&gt;The thing first when building this project was that OpenClaw treats persona files differently than I expected. On most agent platforms I've used, IDENTITY.md would be styling but OpenClaw deals it differently. I used it as a persona file OpenClaw reads as the agent's identity, not as styling. For Clawmate, it's where Bob's privacy contract lives a plain language list of what limited information will be shared by defining set of rules.&lt;/p&gt;

&lt;p&gt;The workspace model was quietly doing the same kind of work for separation. Alice and Bob really did have separate brains, separate persona files, separate session stores, separate sandboxes without me wiring up two parallel stacks. One config, two agents, no shared state I had to defend against. &lt;/p&gt;

&lt;p&gt;I used this command &lt;code&gt;agents bind --agent bob --bind telegram:bob&lt;/code&gt; to route two Telegram bots to two distinct agents which was pretty easy and quick. Another exciting part was how unobtrusive the filesystem tools were. Bob wrote structured JSON to the backchannel file, in the right schema, in response to a Telegram prompt, with no wrapper code from generated from some helper or application.&lt;/p&gt;

&lt;p&gt;OpenClaw made the parts I expected to be hard disappear, which let me let me focus on the part that actually mattered that is  designing the protocol and the contract between two agents, instead of fighting the platform to support them.&lt;/p&gt;

&lt;h2&gt;
  
  
  ClawCon Michigan
&lt;/h2&gt;

&lt;p&gt;I didn't attend ClawCon Michigan. Would definitely love to attend in future. 👩‍💻&lt;/p&gt;

&lt;p&gt;Thanks Dev and OpenClaw team for organising this amazing hackathon.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>VibeBurst: Chrome extension to redesign website vibe!</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Wed, 04 Mar 2026 08:23:16 +0000</pubDate>
      <link>https://dev.to/jasmin/-4coj</link>
      <guid>https://dev.to/jasmin/-4coj</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j" class="crayons-story__hidden-navigation-link"&gt;VibeBurst: I Built a Chrome Extension That Lets AI Redesign Any Website's Vibe&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;DEV Weekend Challenge: Community&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/jasmin" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F322836%2Fde35ee13-9df1-4b90-9734-9f29aafe4ef4.jpeg" alt="jasmin profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jasmin" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jasmin Virdi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jasmin Virdi
                
              
              &lt;div id="story-author-preview-content-3299336" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jasmin" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F322836%2Fde35ee13-9df1-4b90-9734-9f29aafe4ef4.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jasmin Virdi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 1&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j" id="article-link-3299336"&gt;
          VibeBurst: I Built a Chrome Extension That Lets AI Redesign Any Website's Vibe
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/weekendchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;weekendchallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              9&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>VibeBurst: I Built a Chrome Extension That Lets AI Redesign Any Website's Vibe</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Sun, 01 Mar 2026 23:03:37 +0000</pubDate>
      <link>https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j</link>
      <guid>https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-2j5j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you've been staring at GitHub for 6 hours straight and everything just looks... the same? Every site, same vibe, same boring UI. Dark mode if you're lucky. Maybe someone made a theme extension for that one specific site. Cool.&lt;/p&gt;

&lt;p&gt;But what if you could just throw a whole aesthetic onto any site? Like, cyberpunk GitHub at 2am because why not. Glassmorphism on your docs. Brutalist Hacker News — which honestly already looks like that anyway.&lt;/p&gt;

&lt;p&gt;Yeah, that's what I built.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;VibeBurst&lt;/strong&gt; is a Chrome extension. You click it, pick a theme, and AI restyles the entire page with custom CSS. Not some generic stylesheet that breaks everything — it actually reads the page and generates CSS that makes sense for that specific site.&lt;/p&gt;

&lt;p&gt;The four themes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Cyberpunk&lt;/strong&gt; — dark backgrounds, neon cyan and magenta glows, monospace fonts&lt;/li&gt;
&lt;li&gt;🔮 &lt;strong&gt;Glassmorphism&lt;/strong&gt; — frosted glass aesthetic, soft purple and teal, airy and light&lt;/li&gt;
&lt;li&gt;🧱 &lt;strong&gt;Brutalist&lt;/strong&gt; — maximum contrast, pure black and white, bold red accents, zero ornamentation&lt;/li&gt;
&lt;li&gt;🌅 &lt;strong&gt;Retro Wave&lt;/strong&gt; — warm 80s sunset gradients, neon pink and sky blue, synthwave nostalgia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hit &lt;strong&gt;Regenerate&lt;/strong&gt; to get a fresh take. Hit &lt;strong&gt;Reset&lt;/strong&gt; to go back to normal. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's is simple demo and screenshots of how this works on different websites.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/e8rb3N1Uwvo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Same View in different views: &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%2Fybktwb9ywx141hg59oqz.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%2Fybktwb9ywx141hg59oqz.png" alt="Retro Wave Theme" width="800" height="456"&gt;&lt;/a&gt;&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%2Fy6n9khy39t6eo4p1mtzn.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%2Fy6n9khy39t6eo4p1mtzn.png" alt="Glassmorphism" width="800" height="456"&gt;&lt;/a&gt;&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%2Fyxsijes9b3kerhjh773x.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%2Fyxsijes9b3kerhjh773x.png" alt="Cyberpunk" width="800" height="454"&gt;&lt;/a&gt;&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%2F7vtk4xj6hita2rvj7de9.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%2F7vtk4xj6hita2rvj7de9.png" alt="Brutalist" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Github repository link - &lt;a href="https://github.com/Jasmin2895/VibeBurst" rel="noopener noreferrer"&gt;https://github.com/Jasmin2895/VibeBurst&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Stack: Plasmo + TypeScript + React for the extension, Groq API with llama-3.3-70b-versatile for generating CSS. Nothing fancy.&lt;br&gt;
The AI part was honestly the easy bit. The hard part? Making the CSS not destroy the page.&lt;/p&gt;

&lt;p&gt;Early versions were a mess. * selector wrecks everything. Touch width/height and the layout's gone. Bad contrast and you can't read anything. Took a lot of trial and error — ended up building a sanitizeCSS function to strip out anything that could break layouts before injecting.&lt;/p&gt;

&lt;p&gt;Then there's the fact that most sites barely use semantic HTML. It's all divs and class names. Targeting header or nav does almost nothing on sites like Google. So I had to scan the site's actual stylesheets, find every class setting a background-color, and catch the JS-injected ones too.&lt;/p&gt;

&lt;p&gt;Biggest thing that clicked: don't let the AI pick colors for class selectors. Let it style body, h1, button — the semantic stuff. Then grab the body background-color and apply it to all site-specific classes programmatically. That's when things finally stopped looking patchy.&lt;/p&gt;

&lt;p&gt;No JS touched. Page works the same. Just looks different.&lt;br&gt;
Weekend project that taught me CSS specificity is painful and theming the web is harder than it should be. 😅&lt;/p&gt;

&lt;p&gt;It was an overall great learning experience. Thanks Dev Team for organising this short weekend challenge! 👩‍💻&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>VibeBurst: I Built a Chrome Extension That Lets AI Redesign Any Website's Vibe</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Sun, 01 Mar 2026 23:03:37 +0000</pubDate>
      <link>https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-ek8</link>
      <guid>https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-ek8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you've been staring at GitHub for 6 hours straight and everything just looks... the same? Every site, same vibe, same boring UI. Dark mode if you're lucky. Maybe someone made a theme extension for that one specific site. Cool.&lt;/p&gt;

&lt;p&gt;But what if you could just throw a whole aesthetic onto any site? Like, cyberpunk GitHub at 2am because why not. Glassmorphism on your docs. Brutalist Hacker News — which honestly already looks like that anyway.&lt;/p&gt;

&lt;p&gt;Yeah, that's what I built.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;VibeBurst&lt;/strong&gt; is a Chrome extension. You click it, pick a theme, and AI restyles the entire page with custom CSS. Not some generic stylesheet that breaks everything — it actually reads the page and generates CSS that makes sense for that specific site.&lt;/p&gt;

&lt;p&gt;The four themes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Cyberpunk&lt;/strong&gt; — dark backgrounds, neon cyan and magenta glows, monospace fonts&lt;/li&gt;
&lt;li&gt;🔮 &lt;strong&gt;Glassmorphism&lt;/strong&gt; — frosted glass aesthetic, soft purple and teal, airy and light&lt;/li&gt;
&lt;li&gt;🧱 &lt;strong&gt;Brutalist&lt;/strong&gt; — maximum contrast, pure black and white, bold red accents, zero ornamentation&lt;/li&gt;
&lt;li&gt;🌅 &lt;strong&gt;Retro Wave&lt;/strong&gt; — warm 80s sunset gradients, neon pink and sky blue, synthwave nostalgia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hit &lt;strong&gt;Regenerate&lt;/strong&gt; to get a fresh take. Hit &lt;strong&gt;Reset&lt;/strong&gt; to go back to normal. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's is simple demo and screenshots of how this works on different websites.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/e8rb3N1Uwvo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Same View in different views: &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%2Fybktwb9ywx141hg59oqz.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%2Fybktwb9ywx141hg59oqz.png" alt="Retro Wave Theme" width="800" height="456"&gt;&lt;/a&gt;&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%2Fy6n9khy39t6eo4p1mtzn.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%2Fy6n9khy39t6eo4p1mtzn.png" alt="Glassmorphism" width="800" height="456"&gt;&lt;/a&gt;&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%2Fyxsijes9b3kerhjh773x.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%2Fyxsijes9b3kerhjh773x.png" alt="Cyberpunk" width="800" height="454"&gt;&lt;/a&gt;&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%2F7vtk4xj6hita2rvj7de9.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%2F7vtk4xj6hita2rvj7de9.png" alt="Brutalist" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Github repository link - &lt;a href="https://github.com/Jasmin2895/VibeBurst" rel="noopener noreferrer"&gt;https://github.com/Jasmin2895/VibeBurst&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Stack: Plasmo + TypeScript + React for the extension, Groq API with llama-3.3-70b-versatile for generating CSS. Nothing fancy.&lt;br&gt;
The AI part was honestly the easy bit. The hard part? Making the CSS not destroy the page.&lt;/p&gt;

&lt;p&gt;Early versions were a mess. * selector wrecks everything. Touch width/height and the layout's gone. Bad contrast and you can't read anything. Took a lot of trial and error — ended up building a sanitizeCSS function to strip out anything that could break layouts before injecting.&lt;/p&gt;

&lt;p&gt;Then there's the fact that most sites barely use semantic HTML. It's all divs and class names. Targeting header or nav does almost nothing on sites like Google. So I had to scan the site's actual stylesheets, find every class setting a background-color, and catch the JS-injected ones too.&lt;/p&gt;

&lt;p&gt;Biggest thing that clicked: don't let the AI pick colors for class selectors. Let it style body, h1, button — the semantic stuff. Then grab the body background-color and apply it to all site-specific classes programmatically. That's when things finally stopped looking patchy.&lt;/p&gt;

&lt;p&gt;No JS touched. Page works the same. Just looks different.&lt;br&gt;
Weekend project that taught me CSS specificity is painful and theming the web is harder than it should be. 😅&lt;/p&gt;

&lt;p&gt;It was an overall great learning experience. Thanks Dev Team for organising this short weekend challenge! 👩‍💻&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>VibeBurst: I Built a Chrome Extension That Lets AI Redesign Any Website's Vibe</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Sun, 01 Mar 2026 23:03:37 +0000</pubDate>
      <link>https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-hg8</link>
      <guid>https://dev.to/jasmin/vibeburst-i-built-a-chrome-extension-that-lets-ai-redesign-any-websites-vibe-hg8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you've been staring at GitHub for 6 hours straight and everything just looks... the same? Every site, same vibe, same boring UI. Dark mode if you're lucky. Maybe someone made a theme extension for that one specific site. Cool.&lt;/p&gt;

&lt;p&gt;But what if you could just throw a whole aesthetic onto any site? Like, cyberpunk GitHub at 2am because why not. Glassmorphism on your docs. Brutalist Hacker News — which honestly already looks like that anyway.&lt;/p&gt;

&lt;p&gt;Yeah, that's what I built.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;VibeBurst&lt;/strong&gt; is a Chrome extension. You click it, pick a theme, and AI restyles the entire page with custom CSS. Not some generic stylesheet that breaks everything — it actually reads the page and generates CSS that makes sense for that specific site.&lt;/p&gt;

&lt;p&gt;The four themes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Cyberpunk&lt;/strong&gt; — dark backgrounds, neon cyan and magenta glows, monospace fonts&lt;/li&gt;
&lt;li&gt;🔮 &lt;strong&gt;Glassmorphism&lt;/strong&gt; — frosted glass aesthetic, soft purple and teal, airy and light&lt;/li&gt;
&lt;li&gt;🧱 &lt;strong&gt;Brutalist&lt;/strong&gt; — maximum contrast, pure black and white, bold red accents, zero ornamentation&lt;/li&gt;
&lt;li&gt;🌅 &lt;strong&gt;Retro Wave&lt;/strong&gt; — warm 80s sunset gradients, neon pink and sky blue, synthwave nostalgia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hit &lt;strong&gt;Regenerate&lt;/strong&gt; to get a fresh take. Hit &lt;strong&gt;Reset&lt;/strong&gt; to go back to normal. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's is simple demo and screenshots of how this works on different websites.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/e8rb3N1Uwvo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Same View in different views: &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%2Fybktwb9ywx141hg59oqz.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%2Fybktwb9ywx141hg59oqz.png" alt="Retro Wave Theme" width="800" height="456"&gt;&lt;/a&gt;&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%2Fy6n9khy39t6eo4p1mtzn.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%2Fy6n9khy39t6eo4p1mtzn.png" alt="Glassmorphism" width="800" height="456"&gt;&lt;/a&gt;&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%2Fyxsijes9b3kerhjh773x.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%2Fyxsijes9b3kerhjh773x.png" alt="Cyberpunk" width="800" height="454"&gt;&lt;/a&gt;&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%2F7vtk4xj6hita2rvj7de9.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%2F7vtk4xj6hita2rvj7de9.png" alt="Brutalist" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Github repository link - &lt;a href="https://github.com/Jasmin2895/VibeBurst" rel="noopener noreferrer"&gt;https://github.com/Jasmin2895/VibeBurst&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Stack: Plasmo + TypeScript + React for the extension, Groq API with llama-3.3-70b-versatile for generating CSS. Nothing fancy.&lt;br&gt;
The AI part was honestly the easy bit. The hard part? Making the CSS not destroy the page.&lt;/p&gt;

&lt;p&gt;Early versions were a mess. * selector wrecks everything. Touch width/height and the layout's gone. Bad contrast and you can't read anything. Took a lot of trial and error — ended up building a sanitizeCSS function to strip out anything that could break layouts before injecting.&lt;/p&gt;

&lt;p&gt;Then there's the fact that most sites barely use semantic HTML. It's all divs and class names. Targeting header or nav does almost nothing on sites like Google. So I had to scan the site's actual stylesheets, find every class setting a background-color, and catch the JS-injected ones too.&lt;/p&gt;

&lt;p&gt;Biggest thing that clicked: don't let the AI pick colors for class selectors. Let it style body, h1, button — the semantic stuff. Then grab the body background-color and apply it to all site-specific classes programmatically. That's when things finally stopped looking patchy.&lt;/p&gt;

&lt;p&gt;No JS touched. Page works the same. Just looks different.&lt;br&gt;
Weekend project that taught me CSS specificity is painful and theming the web is harder than it should be. 😅&lt;/p&gt;

&lt;p&gt;It was an overall great learning experience. Thanks Dev Team for organising this short weekend challenge! 👩‍💻&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Rekall — A Context Recovery CLI That Uses GitHub Copilot as Its AI Brain</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Mon, 26 Jan 2026 17:21:05 +0000</pubDate>
      <link>https://dev.to/jasmin/rekall-a-context-recovery-cli-that-uses-github-copilot-as-its-ai-brain-5bm</link>
      <guid>https://dev.to/jasmin/rekall-a-context-recovery-cli-that-uses-github-copilot-as-its-ai-brain-5bm</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Rekall&lt;/strong&gt; is a zero-config CLI that helps developers recover context after meetings, breaks, and context switches. Instead of piecing together git status, git log, and git stash list every time you sit back down — you type one word: &lt;strong&gt;&lt;code&gt;rekall&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rekall collects your entire project state — branch, commits, uncommitted changes, stashes, TODOs, FIXMEs — and sends it to GitHub Copilot CLI as a structured prompt. Copilot acts as the AI analysis engine, synthesizing raw data into a concise summary, suggested next step, and blockers.&lt;/p&gt;

&lt;p&gt;Most cases we use Copilot to write code. Rekall uses Copilot to power the product — four commands, each with its own Copilot touchpoint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;rekall context&lt;/strong&gt; — Copilot synthesizes your project state into summary, next step, and blockers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rekall pr &lt;/strong&gt; — Copilot analyzes PR changes with risk assessment and review questions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rekall explain&lt;/strong&gt; — Copilot explains your recent commits in natural language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rekall suggest&lt;/strong&gt; — Copilot generates commit messages or branch names from your changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rekall onboard&lt;/strong&gt; - A quick cheat sheet to understand the project structure and what's new.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool is built in TypeScript and works with or without Copilot installed — gracefully falling back to intelligent rule-based analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/Jasmin2895/rekall" rel="noopener noreferrer"&gt;https://github.com/Jasmin2895/rekall&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;npm package link&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/rekall-cli" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/rekall-cli&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;npm&lt;/strong&gt;: &lt;code&gt;npm install -g rekall-cli&lt;/code&gt; &lt;br&gt;
&lt;strong&gt;Try instantly&lt;/strong&gt;: &lt;code&gt;npx rekall-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rekall with Github Copilot CLI&lt;/strong&gt;&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%2Fex0v3pj2wn28d1ts2au9.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%2Fex0v3pj2wn28d1ts2au9.png" alt="rekall with cli" width="800" height="286"&gt;&lt;/a&gt;&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%2Fcfggn27ywrpsaszszmjh.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%2Fcfggn27ywrpsaszszmjh.png" alt="rekall pr with cli" width="800" height="492"&gt;&lt;/a&gt;&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%2Fhlilnwyh3udaky6x3bf9.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%2Fhlilnwyh3udaky6x3bf9.png" alt="rekall without cli" width="800" height="641"&gt;&lt;/a&gt;&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%2Feouhzci91an34j7z5riw.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%2Feouhzci91an34j7z5riw.png" alt="rekall pr without cli" width="800" height="429"&gt;&lt;/a&gt;&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%2Fsa6g37f5nmrx7259g9ry.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%2Fsa6g37f5nmrx7259g9ry.png" alt="rekall onboard" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rekall without Github Copilot CLI&lt;/strong&gt;&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%2F0lb7izcq9807eia42kq6.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%2F0lb7izcq9807eia42kq6.png" alt=" " width="800" height="647"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rekall explain              &lt;span class="c"&gt;# Copilot explains your recent work&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rekall suggest              &lt;span class="c"&gt;# AI commit message suggestions&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rekall &lt;span class="nb"&gt;pr &lt;/span&gt;1                 &lt;span class="c"&gt;# Give summary overview of the PR&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rekall onboard              &lt;span class="c"&gt;# Quick cheat sheet for project&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rekall suggest &lt;span class="s2"&gt;"add dark mode"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; branch  &lt;span class="c"&gt;# Branch name suggestions&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;rekall &lt;span class="nt"&gt;--format&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.analysis'&lt;/span&gt;         &lt;span class="c"&gt;# JSON for scripting&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Quick Test&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;any-git-repo
npx rekall-cli
npx rekall-cli explain
npx rekall-cli suggest &lt;span class="s2"&gt;"your feature idea"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Requirements&lt;/em&gt;&lt;/strong&gt;: Node.js 18+, Git. Optional: gh CLI for PR mode, gh extension install github/gh-copilot for AI-powered analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;

&lt;p&gt;Rekall doesn't just use Copilot CLI during development — it uses Copilot CLI &lt;strong&gt;as the product itself&lt;/strong&gt;. Every command pipes collected project data into gh copilot and parses what comes back. That's the entire architecture.&lt;/p&gt;

&lt;p&gt;When you run rekall, your git state and code issues get packed into a prompt and sent to Copilot CLI via execute command. The response is parsed into sections — SUMMARY, NEXT_STEP, BLOCKERS — and rendered as clean terminal output. All four commands follow this same pipeline.&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%2Fdr4tcjiohernkjdkr1vn.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%2Fdr4tcjiohernkjdkr1vn.png" alt="app flow" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If Copilot isn't installed, Rekall doesn't break. It falls back to a rule-based engine that prioritizes FIXMEs, scores PR risk by file type, and generates commit messages from conventional patterns. It works — but Copilot's output is noticeably better. The tool tells you which mode you're in so there's no guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building With Copilot CLI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most useful part of Copilot CLI during development was testing my own prompts. I'd run &lt;code&gt;gh copilot -p&lt;/code&gt; with a rough prompt explain with different prompt structures directly in the terminal, see what came back, tweak the wording, and repeat — before writing any parsing code. That saved me from a lot of trial-and-error.&lt;/p&gt;

&lt;p&gt;It also caught a shell escaping issue I would've missed. I was passing raw project data (file paths, commit messages) into the prompt string, and Copilot helped me think through what characters could break the shell command. That's how the escaping logic ended up covering backslashes, quotes, dollar signs, and backticks.&lt;/p&gt;

&lt;p&gt;My initial error handling was too optimistic, only checking if Copilot was "not installed." During testing, I hit a variety of edge cases: API quota limits (402), authentication errors, and generic "no quota" messages. Each time, my tool (Rekall) tried to parse the error as a valid response.&lt;/p&gt;

&lt;p&gt;Using the CLI, I mapped out every failure mode gh copilot could return. This allowed me to build a comprehensive pattern list that catches these exceptions silently and lets the tool fallback gracefully.&lt;/p&gt;

&lt;p&gt;Looking back, the thing that worked best was treating Copilot CLI as a conversation partner I could test ideas against — right there in the terminal, no context switching needed.&lt;/p&gt;

&lt;p&gt;It was an overall great learning experience thanks to Dev and Github Team for organising it. 👩‍💻&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Thu, 03 Jul 2025 15:15:56 +0000</pubDate>
      <link>https://dev.to/jasmin/-577h</link>
      <guid>https://dev.to/jasmin/-577h</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jasmin/quizbit-turn-any-article-into-an-engaging-slack-quiz-5679" class="crayons-story__hidden-navigation-link"&gt;Quizbit: Turn Any Article Into an Engaging Slack Quiz.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/jasmin" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F322836%2Fde35ee13-9df1-4b90-9734-9f29aafe4ef4.jpeg" alt="jasmin profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jasmin" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jasmin Virdi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jasmin Virdi
                
              
              &lt;div id="story-author-preview-content-2644939" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jasmin" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F322836%2Fde35ee13-9df1-4b90-9734-9f29aafe4ef4.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jasmin Virdi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/jasmin/quizbit-turn-any-article-into-an-engaging-slack-quiz-5679" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 1 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jasmin/quizbit-turn-any-article-into-an-engaging-slack-quiz-5679" id="article-link-2644939"&gt;
          Quizbit: Turn Any Article Into an Engaging Slack Quiz.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/runnerhchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;runnerhchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/machinelearning"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;machinelearning&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/jasmin/quizbit-turn-any-article-into-an-engaging-slack-quiz-5679" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;19&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/jasmin/quizbit-turn-any-article-into-an-engaging-slack-quiz-5679#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>runnerhchallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Quizbit: Turn Any Article Into an Engaging Slack Quiz.</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Tue, 01 Jul 2025 22:38:58 +0000</pubDate>
      <link>https://dev.to/jasmin/quizbit-turn-any-article-into-an-engaging-slack-quiz-5679</link>
      <guid>https://dev.to/jasmin/quizbit-turn-any-article-into-an-engaging-slack-quiz-5679</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/runnerh"&gt;Runner H "AI Agent Prompting" Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built an AI-powered &lt;strong&gt;"Quiz Master"&lt;/strong&gt; for Slack using a Runner H workflow. This automation takes any online article and instantly generates an engaging, five-question quiz based on its content. The Quiz Master then posts the quiz directly to a designated Slack channel, one question at a time, creating a fun and interactive learning experience for the team. After a brief pause, it reveals the correct answers with explanations, making it a seamless way to test and reinforce key takeaways from shared reading materials. This solves the problem of &lt;strong&gt;"did they actually read it?"&lt;/strong&gt; by turning passive information consumption into an active and &lt;em&gt;&lt;strong&gt;gamified group activity.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here is the demo of setting up a Sample Slack Quiz using an article.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/RjnQVcDoYng"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here is the final output view in Slack.&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%2Fhf5z71mpp3w0movr6knz.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%2Fhf5z71mpp3w0movr6knz.png" alt="Quiz Question"&gt;&lt;/a&gt;&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%2Fm5aazjicmvjndiaod68t.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%2Fm5aazjicmvjndiaod68t.png" alt="Answer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Runner H
&lt;/h2&gt;

&lt;p&gt;This entire workflow is powered by a single, carefully crafted Runner H prompt. The AI agent handles the content analysis, question generation, formatting, and timed message delivery.&lt;/p&gt;

&lt;p&gt;Here is the exact prompt I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Based on the article at {source_url}, generate a 5-question quiz that tests the main insights and key data points from the text.

For each question, provide the question in bold using Slack’s mrkdwn format, followed by four answer choices, each prefixed with a number emoji (e.g., :one:, :two:). Structure the quiz so each question and its answer options are sent as a separate message to {slack_channel}. Ask the channel Id and source_url of article from user to post.

After 30 seconds, send a follow-up in the thread of last message listing the correct answers and providing brief explanations for each.

Ensure each message is clearly formatted for Slack and suitable for a gamified experience. If Zapier’s Slack integration supports Block Kit elements, include interactive buttons for answer selection; otherwise, present answer options as plain text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can view the Runner H session here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://hcompany.ai/surfer-2" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fassets%2F7eFHjmJeoNnvAhCSfhNTx0E8Njc.jpg" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://hcompany.ai/surfer-2" rel="noopener noreferrer" class="c-link"&gt;
            Surfer 2: The Next Generation of Cross-Platform Computer-Use Agents - H Company
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            We are a frontier AI research company that designs, builds, and deploys cost-efficient agentic AI systems directly into enterprises’ core workflows and processes.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fframerusercontent.com%2Fimages%2FqVilPhQQTJzvgGDNLjrYwa5xQ.png"&gt;
          hcompany.ai
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Use Case &amp;amp; Impact
&lt;/h2&gt;

&lt;p&gt;This AI Quiz Master has several powerful real-world applications:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corporate Training &amp;amp; HR&lt;/strong&gt;: HR and training teams can use this to quickly assess comprehension of training materials, new company policies, or onboarding documents. It makes mandatory reading more engaging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content &amp;amp; Marketing Teams&lt;/strong&gt;: After sharing an important industry report or a new company blog post, marketing teams can use the quiz to reinforce key findings and ensure everyone is aligned on the core message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Educational Settings&lt;/strong&gt;: Teachers and study groups can use it to create quick quizzes from articles, research papers, or news items to facilitate discussion and test knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General Team Engagement&lt;/strong&gt;: It serves as a great tool for team-building. You can run a weekly quiz on a fun or interesting article to foster a more interactive and connected team environment in Slack.&lt;/p&gt;

&lt;p&gt;The primary impact is efficiency and engagement. It saves managers and team leads significant time by automating the creation of learning materials. More importantly, it transforms passive reading into an active, collaborative, and fun experience, which significantly boosts information retention and team participation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Social Love
&lt;/h3&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1940430429494083615-784" src="https://platform.twitter.com/embed/Tweet.html?id=1940430429494083615"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1940430429494083615-784');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1940430429494083615&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Huge thanks to Dev and Runner H Team for this hackathon! I really enjoyed participating. It was a refreshing and different kind of challenge, focusing on the creative art of AI prompting rather than just lines of code. &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>runnerhchallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Subscription Intelligence Hub - Your financial emails Knowledge Hub!</title>
      <dc:creator>Jasmin Virdi</dc:creator>
      <pubDate>Wed, 04 Jun 2025 22:33:41 +0000</pubDate>
      <link>https://dev.to/jasmin/subscription-intelligence-hub-3gcm</link>
      <guid>https://dev.to/jasmin/subscription-intelligence-hub-3gcm</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;We often have come across this problem where we sign up for stuff and later wonder, "&lt;strong&gt;Why am I paying for this again?&lt;/strong&gt;" and also we have so many bills paid via different payments apps and email is the only place where we get notifications from all these app payments. I built the Subscription &amp;amp; Purchase Intelligence Hub to fix that!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Just forward your financial emails such as e-receipts (even PDFs!) and related email chats to one Hub address.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;AI figures out the financials: It uses OpenAI to grab the vendor, product, price, and category from your receipts. It also converts receipts uploaded in any currency into USD. &lt;br&gt;
It gets the "vibe": For your discussion emails, it uses NLP to find key comments and even tells you if the sentiment was good (👍), bad (👎), or so-so (😐).&lt;br&gt;
Everything connects: The Hub links your spending to these contextual insights automatically.&lt;br&gt;
Your personal view: Pop in the email you forwarded from (no complicated sign-ups!) and see your finances with the "why" clearly laid out on a simple dashboard.&lt;br&gt;
Basically, it helps you actually understand your spending, not just see the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;You can access the app live app &lt;strong&gt;&lt;a href="https://subscription-intelligence-hub.vercel.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to use the app.
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Forward all your bills and reciepts to &lt;strong&gt;&lt;code&gt;postmtest06@gmail.com&lt;/code&gt;&lt;/strong&gt; to analyse.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the &lt;a href="https://subscription-intelligence-hub.vercel.app/" rel="noopener noreferrer"&gt;&lt;strong&gt;Dashboard page&lt;/strong&gt;&lt;/a&gt; &lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2F37gy1h4pkcn4zdf65sy5.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%2F37gy1h4pkcn4zdf65sy5.png" alt="signup-page" width="800" height="830"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You will see your financial summary categorised by month spending and spending by category/vendor&lt;/li&gt;
&lt;/ol&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%2Foj7nghrf487slq6ac2q2.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%2Foj7nghrf487slq6ac2q2.png" alt="spending-charts" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we also show the &lt;strong&gt;Financial Items&lt;/strong&gt; containing context of financial discussion or info derived from email.&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%2Fidiy3eb7b70ws0azzjv5.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%2Fidiy3eb7b70ws0azzjv5.png" alt="financial-item-breakup" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Detailed financial card item open view.&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%2Flz1k7cz5ttcpevpm6yrs.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%2Flz1k7cz5ttcpevpm6yrs.png" alt="financial-item" width="800" height="1041"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the Item is recurring event like monthly or yearly subscription we show the next payable date.
There is an &lt;strong&gt;Add Renewal to calendar&lt;/strong&gt; button which generates downloadable &lt;code&gt;.ics&lt;/code&gt; file which can be used to create event in calendar.&lt;/li&gt;
&lt;/ol&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%2Fbrtdbygaiv7blgmka8wg.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%2Fbrtdbygaiv7blgmka8wg.png" alt="add-to-calendar" width="800" height="1186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For reminders I have setup a slack channel where i post regular updates of expenses. This was a feature to showcase that if this is managed by team they can handle or post regular updates of the expenses of teammates in slack channel for subscriptions renewals.
&lt;a href="https://test-projects-talk.slack.com/archives/C08UN6D9EP8" rel="noopener noreferrer"&gt;Link&lt;/a&gt; to slack channel&lt;/li&gt;
&lt;/ol&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%2Fokmjts97dseb7q2bgqjw.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%2Fokmjts97dseb7q2bgqjw.png" alt="slack-channel" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;Link to the &lt;a href="https://github.com/Jasmin2895/subscription-intelligence-hub" rel="noopener noreferrer"&gt;&lt;strong&gt;Github repo&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I really wanted to make financial tracking less about just numbers and more about the story – the 'why' behind what we spend. My goal was to turn that &lt;strong&gt;&lt;em&gt;email clutter into actual understanding&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I created a Node.js/Express backend with a PostgreSQL database, and a React frontend. The first, and most crucial, step was email ingestion. This is where &lt;strong&gt;&lt;em&gt;Postmark truly shone for me&lt;/em&gt;&lt;/strong&gt;. Setting up the inbound webhook was incredibly straightforward. It just worked, consistently delivering emails as well-structured JSON – even PDF attachments. OpenAI API does the heavy lifting on parsing financial details from all sorts of emails and PDFs. The natural NLP library helps dig out key contextual sentences from discussions and figures out the sentiment. &lt;/p&gt;

&lt;p&gt;Here is the backend process preview.&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%2Fwocdcpzt5yxp10okf8ob.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%2Fwocdcpzt5yxp10okf8ob.png" alt="backend-flow" width="800" height="279"&gt;&lt;/a&gt;&lt;br&gt;
It was a fun challenge to combine these tools to turn messy email data into clear, actionable insights!&lt;/p&gt;

&lt;p&gt;I have also thought about enhancing this in future by adding Google calendar integration where the user can directly add reminder in their calendar instead of downloading ics file.&lt;/p&gt;

&lt;p&gt;Thank you to PostMark and Dev.to team for bringing this challenge to us.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
  </channel>
</rss>
