<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Krish Khati — Blog</title>
    <link>https://krish-portfolio-dev.vercel.app/blog</link>
    <description>Notes from a self-taught developer: learning to code, building projects, AI tools and digital marketing.</description>
    <language>en</language>
    <atom:link href="https://krish-portfolio-dev.vercel.app/rss.xml" rel="self" type="application/rss+xml" />
    <lastBuildDate>Sun, 11 Oct 2026 12:14:01 GMT</lastBuildDate>
    <item>
      <title>Publishing My First Game on the Play Store for Free</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/publish-free-android-game</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/publish-free-android-game</guid>
      <pubDate>Fri, 09 Oct 2026 03:30:00 GMT</pubDate>
      <category>Projects</category>
      <description>Learn how I published my first free android game on the Play Store using Vercel, Bubblewrap, and a low‑cost Play Console account. Practical steps inside.</description>
      <content:encoded><![CDATA[<p>Yesterday I finally managed to publish free android game on the Play Store. It started as a simple web app called Memory Match that I built in Google AI Studio just for fun. Being from a small town like Pundri, I don’t have a big budget, so I wanted a completely free path from code to store. I tried a few hosting options, hit the free tier of Vercel, and discovered that the whole pipeline could stay cost‑zero. In the next sections I will walk through each step, the tools I used, and the little hiccups that taught me a lot about mobile publishing.</p>
<h2>publish free android game: turning a web app into an app bundle</h2>
<p>The game itself is a classic memory‑matching card game. I built the logic in Google AI Studio, using a tiny Python backend to shuffle cards and a lightweight HTML/CSS front end. While writing the code I also wrote a separate post about what the project taught me about state management: <a href="/blog/what-a-memory-match-game-taught-me-about-state">what a memory match game taught me about state</a>. That article helped me keep the front end simple, which later made the web‑to‑app conversion smoother. I also referenced my free‑government‑scheme finder when I needed to think about API keys and free tiers: <a href="/blog/free-government-scheme-finder">Building Yojana Saathi</a>. Finally, I looked at my voice‑assistant project for ideas on structuring config files: <a href="/blog/build-python-voice-assistant">How I built JARVIS</a>.</p>
<p>Once the game was playable locally, I pushed the code to a GitHub repository and linked it to Vercel. Vercel’s free plan gave me a custom domain (my‑game.vercel.app) and automatic HTTPS, which was enough for a public demo. The deployment process was a single click – Vercel detected the static site, built it, and gave me a preview URL instantly. I was surprised how fast the site loaded on a 4G connection, which is important for users in rural areas where bandwidth is limited.</p>
<p>With a stable web version in hand, the next challenge was turning it into an Android App Bundle (.aab). I used Bubblewrap, a command‑line tool that wraps a progressive web app (PWA) into a native container. The steps were straightforward: run `bubblewrap init` to generate a manifest, point it at the Vercel URL, adjust the icons, and then `bubblewrap build` to get the .aab file. The biggest surprise was the need to add a service worker for offline support – without it the Play Store rejected the bundle because it didn’t meet the PWA criteria. Adding a tiny service‑worker script solved the problem in minutes.</p>
<h2>Setting up the Play Console and store listing</h2>
<p>Google requires a Play Console account to upload any app. I created a new account using my personal Gmail, paid the one‑time $25 registration fee (the only cost I couldn’t avoid), and then navigated to the &quot;Create Application&quot; screen. I chose the &quot;Games&quot; category, filled in the title &quot;Memory Match&quot;, and uploaded the .aab generated by Bubblewrap. The store listing asked for a short description, a full description, screenshots, and a feature graphic. I reused the screenshots I had taken while testing on Vercel, added a short tagline, and wrote a concise description that highlighted the free nature of the game.</p>
<ul><li>Create a free Vercel account and link your GitHub repo</li><li>Deploy the static site and verify the URL works</li><li>Install Bubblewrap (`npm i -g @bubblewrap/cli`)
Run `bubblewrap init` and point to the Vercel URL
Add icons (192×192, 512×512) and a service worker
Run `bubblewrap build` to generate the .aab file</li><li>Register for a Play Console account (one‑time $25 fee)
Create a new app, choose &quot;Games&quot; category
Fill in store listing details (title, description, screenshots)
Upload the .aab file and submit for review</li></ul>
<p>Google’s review process took about two days. I received a notification that the app was approved and now appears in the Play Store under the &quot;Free&quot; section. The whole journey taught me that even with zero hosting costs, there are still minimal unavoidable fees – the Play Console registration being the main one. The rest of the pipeline – from Google AI Studio to Vercel to Bubblewrap – stayed completely free, which is a big win for a hobbyist developer in a small town.</p>
<h2>Practical takeaway</h2>
<p>If you have a static web game or any PWA and you want to reach Android users without spending on servers, you can follow the same steps: host for free on Vercel, wrap with Bubblewrap, and upload the .aab to the Play Console. The only cost you’ll face is the one‑time Play Console fee, but everything else can be done on free tiers. The process also forces you to think about offline support and proper manifest metadata, which improves the overall quality of the app.

Feel free to explore my other projects for more examples of free‑tier development, or check out the portfolio page for a quick overview of what I’ve built so far: <a href="/projects">my projects</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Why I Built a Portfolio Website Instead of Relying on a Resume</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/how-to-create-portfolio-website</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/how-to-create-portfolio-website</guid>
      <pubDate>Wed, 07 Oct 2026 03:30:00 GMT</pubDate>
      <category>Journey</category>
      <description>I explain why I built a 3‑D animated portfolio website instead of a traditional resume, how I used Claude Code and Supabase, and what I learned.</description>
      <content:encoded><![CDATA[<h2>how to create portfolio website – my personal reason</h2>
<p>Last week I searched for “how to create portfolio website” and realized most of the guides were aimed at designers, not at a developer like me who wants to showcase code, AI experiments, and a few short videos. A plain resume feels old‑school, especially when I can demonstrate a live, interactive site that tells the same story in motion. In this post I explain why I chose to build a 3‑D animated portfolio website instead of relying on a traditional resume.</p>
<p>Resumes have been the standard for decades, but they compress everything into a single page of bullet points. That format hides the process behind a project, the little demos that actually run, and the visual style I care about. When a recruiter asks for a PDF I can send one, but when I’m in a technical interview I want the interviewers to see the code, the video walkthroughs, and the interactive UI without opening a separate zip file. A portfolio site lets me arrange those pieces the way I think about them, and it gives me a place to update everything instantly.</p>
<p>The hero section of my site uses my own photograph, which I ran through Flow AI to add subtle 3‑D rotation and depth. It feels like a small personal animation that catches the eye without being a distraction. The effect took a few minutes of tweaking, but the result is a friendly face that greets anyone who lands on the page. I also added a short tagline that explains I’m an AI‑powered app builder from Pundri, Kaithal, so the visitor gets context right away.</p>
<p>Below the hero I created three main sections: an “About Me” paragraph that talks about my background in Python, AI tools like Claude Code, Gemini and VS Code; a “Projects” gallery where each entry shows a thumbnail, a short video demo, and a link to the GitHub repo; and a “Contact” form that writes directly to my Supabase table. The layout is responsive, so it works on a phone as well as on a laptop, which matters because many interviewers view my site on a small screen during a video call.</p>
<p>I wrote most of the front‑end code with Claude Code, which helped me generate boilerplate components and debug CSS quirks faster than typing everything by hand. For the back‑end I set up a Supabase project and added an admin panel that I can reach from a hidden URL. The panel lets me add new blog posts, upload project screenshots, and edit text without touching the codebase again. This self‑service approach saved me from a long cycle of redeploys every time I wanted to showcase a new experiment.</p>
<p>One of the features I’m most proud of is the blog‑post editor. It works the same way as the post‑automation pipeline I described in my earlier article about generating three posts a week with free AI tools. If you’re curious about that workflow, check out <a href="/blog/automate-blog-posts-free-ai">how I automated my blog: 3 posts a week with free AI</a> for the full details. The editor uses Supabase’s real‑time API, so when I save a draft the changes appear instantly on the public site.</p>
<p>Building the site also reminded me of the early days when I first learned to code with nothing but a laptop in Pundri. That story lives in another post, <a href="/blog/learning-to-code-with-just-a-laptop">Learning to code with just a laptop</a>, where I talk about how curiosity and late‑night debugging got me started. The same perseverance helped me push through the moments when Claude Code produced syntax that didn’t compile or when Supabase rate limits forced me to batch updates.</p>
<ul><li>✅ Immediate visual impact – interviewers see a live demo instead of a static PDF.</li><li>✅ Centralised updates – a single admin panel refreshes the résumé, projects, and blog.</li><li>✅ SEO advantage – search engines can index the site and bring me organic traffic.</li><li>⚠️ Ongoing maintenance – I need to keep dependencies up‑to‑date and watch for broken links.</li><li>⚠️ Performance tuning – 3‑D animations add load time, so I must optimise assets.</li></ul>
<p>During interviews the portfolio site becomes a conversation starter. Instead of saying “I built an AI chatbot”, I can click the project card, play the demo video, and walk the interviewer through the code in real time. It also shows that I’m comfortable with modern deployment pipelines, because the site lives on Vercel and pulls data from Supabase without any manual server management.</p>
<p>The downside is that a website needs regular attention. Every time I add a new library I have to test that the build still works, and the animated hero can cause issues on older browsers. I also spend a few hours each month reviewing analytics to see which sections attract the most clicks, so I can decide where to improve content.</p>
<p>Looking ahead, I plan to add a light‑dark theme toggle, integrate a simple AI chat widget that can answer quick questions about my projects, and write a short guide on how I used Claude Code to scaffold the entire site. Those upgrades will keep the portfolio fresh and give visitors a reason to return.</p>
<blockquote>A portfolio isn’t just a showcase; it’s a living document of what you’re learning right now.</blockquote>
<p>In short, the portfolio website replaced the old‑school resume because it lets me present my work dynamically, update it instantly, and make a stronger impression in technical interviews. If you want to see the site in action, take a look at the live version on <a href="/projects">my projects</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>How I automated my blog: 3 posts a week with free AI</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/automate-blog-posts-free-ai</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/automate-blog-posts-free-ai</guid>
      <pubDate>Mon, 05 Oct 2026 03:30:00 GMT</pubDate>
      <category>AI</category>
      <description>Learn how I set up a free, automated pipeline using GitHub Actions, Supabase, Gemini and Groq to draft three blog posts each week, with practical debugging…</description>
      <content:encoded><![CDATA[<p>I&#39;ve been experimenting with ways to keep my portfolio blog fresh without spending hours each week writing. The search query that finally gave me a direction was “automate blog posts with free ai”, and I built a tiny pipeline that now drafts three posts every Monday, Wednesday and Friday. The whole thing runs on GitHub Actions, stores the generated markdown in Supabase, and calls Google Gemini for the actual content, falling back to Groq when Gemini is overloaded.</p>
<p>Why three posts? For a small personal site, a steady rhythm helps me stay visible in search results and gives me material to share on social media. At the same time, I don&#39;t want to sacrifice quality, so I let the AI write the first draft and then I spend a few minutes editing before I hit publish. The approach also lets me test new prompts and see how the model evolves, which is useful for the other AI‑powered tools I build.</p>
<h2>automate blog posts with free ai – the workflow</h2>
<p>The pipeline is deliberately simple. Every night GitHub Actions triggers a workflow file that calls a small Python script. The script reads a short “topic” entry from a Supabase table, sends it to Gemini with a prompt that asks for a 800‑word blog draft, and writes the returned markdown back into another Supabase table marked as a draft. If Gemini returns an error, the script catches it and retries using the Groq API, which is also free for low‑volume use.</p>
<ul><li>1. Store blog topics in a Supabase table called `draft_topics`.</li><li>2. GitHub Actions runs the `generate_blog.yml` workflow on a schedule.</li><li>3. The Python script fetches a pending topic, calls Gemini (or Groq), and saves the markdown as a draft.</li><li>4. A separate action creates a pull request with the draft file, so I can review it in GitHub.</li></ul>
<pre><code>name: Generate Blog Draft
on:
  schedule:
    - cron: &#39;0 9 * * 1,3,5&#39; # 09:00 UTC Mon, Wed, Fri
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: &#39;3.11&#39;
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
      - name: Run generator
        env:
          SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
          SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
          GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
        run: python generate_blog.py</code></pre>
<p>The workflow file lives in .github/workflows/generate_blog.yml and uses a cron expression `0 9 * * 1,3,5` to fire at 09:00 UTC on Monday, Wednesday and Friday. I chose UTC because it maps nicely to my local morning routine in Haryana (around 14:30 IST). The job spins up a fresh Python container, installs the requirements, and then runs the script. By keeping the container lightweight I stay within GitHub&#39;s free minutes limit, and the run usually finishes in under two minutes.</p>
<p>All generated posts end up as drafts in Supabase, not directly on the live site. I have a tiny admin page that lists drafts, shows a preview, and lets me edit the markdown if I need to tighten the language or add a local example. Once I&#39;m happy, I click “publish” and the post moves to the `published_posts` table, which the Next.js front‑end reads to render the page. This extra step saved me from accidental typos and gave me a chance to add personal touches.</p>
<p>The first few weeks were a crash course in reading logs. My initial run failed because I had typed the wrong Supabase URL, so the script threw a connection error that I only spotted after scrolling through the Action’s output. A second failure came from Gemini’s “high demand” response, which meant the API returned a 429 status; my retry logic wasn&#39;t ready for that, so the workflow stopped. Later I discovered that the Groq model I referenced had been retired, and the script tried to call an endpoint that no longer existed.</p>
<p>One surprise was how sensitive the prompts are to temperature settings. I started with temperature 0.7, which gave me creative but sometimes off‑topic output. Lowering it to 0.3 made the drafts more focused and easier to edit. I also added a short “style guide” section in the prompt that asks the model to keep sentences under 20 words and to avoid jargon, which aligns with my goal of writing in plain, friendly English.</p>
<ul><li>• Wrong Supabase URL – fixed by copying the exact URL from the Supabase project settings.</li><li>• Gemini “high demand” 429 – added exponential back‑off and a fallback to Groq after three attempts.</li><li>• Retired Groq model – updated the model name to `llama2‑70b‑chat` which is currently supported.</li><li>• Missing database columns – added `draft_content` and `status` fields to the `draft_posts` table and adjusted the insert query.</li></ul>
<p>Each error taught me a small but valuable habit. I now check the Action logs line by line before assuming the pipeline worked, and I keep a tiny checklist of required environment variables in the repository README. Reading the error messages carefully helped me understand how each API signals rate limits or missing resources, which is a skill that transfers to any cloud‑based AI integration I work on. Debugging became less about guesswork and more about reproducing the exact request that failed.</p>
<p>The practical takeaway is simple: you can set up a fully automated, three‑posts‑a‑week blog using only free tiers of Gemini, Groq, GitHub Actions, and Supabase, as long as you treat the AI output as a draft rather than the final product. The pipeline saves me a couple of hours each week, and the occasional debugging session reminds me that even “free” services need careful handling.</p>
<p>If you want to see the code in detail, check out my post on <a href="/blog/how-i-use-ai-tools-without-outsourcing-my-thinking">how I use AI tools without outsourcing my thinking</a> and the project page for my open‑source helpers at <a href="/projects">my projects</a>. I also wrote about building a free government‑scheme finder, which uses a similar Supabase‑backed AI flow, here: <a href="/blog/free-government-scheme-finder">free government scheme finder</a>. Feel free to drop a note if you run into any roadblocks—I’m always happy to help a fellow coder.</p>]]></content:encoded>
    </item>
    <item>
      <title>Building Yojana Saathi: a free government scheme finder for India</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/free-government-scheme-finder</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/free-government-scheme-finder</guid>
      <pubDate>Fri, 02 Oct 2026 03:30:00 GMT</pubDate>
      <category>Projects</category>
      <description>I built Yojana Saathi, a free government scheme finder india tool that matches users with eligible schemes using VS Code, Claude, Supabase and Gemini.</description>
      <content:encoded><![CDATA[<p>When I typed &quot;free government scheme finder india&quot; into Google, I found a gap: there are many schemes, but no simple tool that tells a person whether they qualify. That realization sparked Yojana Saathi, a free assistant that helps users discover the schemes they are eligible for. I wanted a portfolio project that solves a real Indian problem, and I could build it step by step in VS Code with Claude Code guiding me in the terminal.</p>
<h2>Why I chose the free government scheme finder india project</h2>
<p>Living in Pundri, Kaithal, I often hear neighbours talk about subsidies, scholarships, or pension plans they never know how to apply for. The information is scattered across different ministries, and the eligibility criteria are hidden in long PDFs. By creating Yojana Saathi I could address a need I see every day, and I could also show future employers that I can turn a social idea into a working web app.</p>
<h2>The tools I used</h2>
<p>I kept the stack simple: VS Code as my editor, Claude Code for code generation and debugging, Supabase for the database and auth, and Gemini for a lightweight language model that parses scheme documents. I also leaned on my existing knowledge from building JARVIS, my voice assistant in Python, which taught me how to combine AI tools with a backend. You can read more about that process in <a href="/blog/build-python-voice-assistant">how I built JARVIS, my own AI assistant in Python</a>.</p>
<h2>Setting up the backend with Supabase</h2>
<p>Supabase gave me a hosted Postgres database and an instant REST API. I created a &quot;schemes&quot; table with fields like name, description, eligibility_criteria, and link. The eligibility criteria are stored as JSON so I can match user inputs (age, income, location, etc.) against them. Claude helped me write the initial migration script, and I tweaked it manually to make sure the JSON format was consistent.</p>
<p>User authentication is optional, but I added email‑based sign‑up so people can save their profile for later. Supabase&#39;s auth hooks let me trigger a webhook that records each search, which later helps me see which schemes are most requested.</p>
<h2>Building the front‑end UI</h2>
<p>I used plain HTML, CSS and a tiny amount of JavaScript. The UI has three parts: a form where users fill in basic details, a results list that shows matching schemes, and a detail view with a link to the official portal. The form validation is simple – just check that required fields are not empty. I reused a component I wrote for the memory‑match game to render the list items, which kept the code DRY.</p>
<p>When a user submits the form, the front‑end calls a Supabase RPC that runs a PostgreSQL function. That function loops through the &quot;schemes&quot; table, evaluates each JSON eligibility rule, and returns only the matching rows. Claude suggested the function structure, and I added a few test cases to make sure edge cases (like missing income data) are handled gracefully.</p>
<h2>Integrating Gemini for smarter searching</h2>
<p>Some schemes have eligibility described in natural language rather than strict numbers. To bridge that gap I called Gemini with the scheme description and the user&#39;s profile, asking it to output a yes/no decision and a short explanation. The model runs in a serverless function, so the latency stays under a second for most queries.</p>
<p>I was careful not to over‑promise the AI’s accuracy; the result is always presented as a suggestion, with a link to the official source so the user can verify the details themselves.</p>
<h2>Challenges I faced</h2>
<ul><li>Cleaning up the eligibility data – many PDFs use different terms for the same concept, so I had to normalise fields like &quot;annual income&quot; and &quot;household size&quot;.</li><li>Balancing AI assistance with manual logic – Gemini is great for vague criteria, but I still needed deterministic SQL for clear cut rules.</li><li>Keeping the project lightweight – I avoided heavy front‑end frameworks to stay within the resources of a free Supabase tier.</li></ul>
<p>Every time I hit a roadblock I asked Claude for a quick code snippet, then tweaked it to fit my specific schema. That workflow reminded me of the process I described in <a href="/blog/how-i-use-ai-tools-without-outsourcing-my-thinking">how I use AI tools without outsourcing my thinking</a>.</p>
<h2>What I learned from building Yojana Saathi</h2>
<p>The biggest lesson is that a useful tool does not need to be flashy. A simple form, a clean database, and a modest AI helper can already provide real value to people who otherwise struggle to find information. I also reinforced my habit of writing small, testable pieces of code before stitching them together – a habit I first discovered while learning to code with just a laptop.</p>
<p>If you’re curious about my other projects, you can see them on <a href="/projects">my projects</a>.</p>
<h2>Practical takeaway</h2>
<p>If you have an idea for a tool that solves a local problem, start with the data you already have, pick the simplest stack that lets you query it, and let AI assist you in the repetitive bits. You don’t need a big team – just a laptop, a willingness to experiment, and the discipline to keep the project focused on one clear outcome.</p>]]></content:encoded>
    </item>
    <item>
      <title>How I built JARVIS, my own AI assistant in Python</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/build-python-voice-assistant</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/build-python-voice-assistant</guid>
      <pubDate>Wed, 30 Sep 2026 03:30:00 GMT</pubDate>
      <category>Projects</category>
      <description>Learn how I built a free python voice assistant named JARVIS using VS Code, Claude, Supabase and Gemini. Step‑by‑step guide for beginners. I built JARVIS, a…</description>
      <content:encoded><![CDATA[<p>I wanted to build python voice assistant that could understand my spoken commands, reply in text or speech, and fetch answers from the web without spending a dime. The idea started when I realized I was juggling multiple tabs and notebooks, and a single assistant could keep everything in one place. This post walks through the exact steps I followed, the free services I chose, and the hiccups I still have to iron out.</p>
<h2>Step-by-step guide to build python voice assistant</h2>
<p>The overall plan was simple: a loop that records my voice, sends it to a speech‑to‑text model, forwards the transcript to a chat model, turns the response back into speech, and finally runs a web search if the answer looks incomplete. I kept the architecture linear so I could add or remove parts later. The first version only handled text chat; voice and search came in the next two iterations.</p>
<h2>Free tools and APIs I used</h2>
<p>All the components have a free tier, which matches my rule of staying cost‑free. I wrote the code in VS Code because the editor is lightweight and works well on my old laptop. Inside VS Code I installed the Claude extension, which helped me generate snippets and debug on the fly. For speech‑to‑text I used the free tier of Gemini’s whisper model, and for text‑to‑speech I relied on the built‑in macOS say command (no API key needed). Web search is powered by Supabase Edge Functions that call the DuckDuckGo Instant Answer API, both of which have generous free limits. I also read about similar projects in my earlier post about a memory‑match game [/blog/what-a-memory-match-game-taught-me-about-state] and about how I use AI tools without outsourcing my thinking [/blog/how-i-use-ai-tools-without-outsourcing-my-thinking]. You can see more of my experiments on my projects page [/projects].</p>
<ul><li>VS Code with Claude extension</li><li>Gemini whisper (free tier) for speech‑to‑text</li><li>macOS say command for text‑to‑speech</li><li>Supabase Edge Functions + DuckDuckGo Instant Answer API for web search</li></ul>
<h2>Writing the chat and voice loops with Claude</h2>
<p>I started by drafting a simple REPL that accepted text input and printed the response from Claude. When the loop worked, I asked Claude to wrap the input in a function that records audio, sends it to Gemini, and returns the transcript. Claude generated the code, I copied it into my file, and then I ran it. The first run failed because the audio library needed ffmpeg, so I installed it and tried again. Each error became a quick prompt to Claude: &quot;Fix the ImportError for ffmpeg&quot;. Within a few hours I had a function called get_voice_input() that returned a clean string.</p>
<pre><code>import subprocess, json, os

def get_voice_input():
    # Record 5 seconds of audio to temp.wav
    subprocess.run([&#39;ffmpeg&#39;, &#39;-y&#39;, &#39;-f&#39;, &#39;avfoundation&#39;, &#39;-i&#39;, &#39;:0&#39;, &#39;-t&#39;, &#39;5&#39;, &#39;temp.wav&#39;], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    # Send to Gemini Whisper (pseudo code)
    with open(&#39;temp.wav&#39;, &#39;rb&#39;) as f:
        audio_bytes = f.read()
    transcript = gemini_whisper_api(audio_bytes)
    return transcript.strip()</code></pre>
<h2>Adding web search with Supabase and Gemini</h2>
<p>Once the chat loop was stable, I noticed many of my questions needed up‑to‑date facts. I added a small check: if the response contains the phrase &quot;I don’t know&quot; or looks like a generic answer, I call a Supabase Edge Function that forwards the query to DuckDuckGo. The function returns a short snippet, which I then feed back to the chat model for a polished reply. This kept the user experience smooth and avoided hard‑coding any API keys in the client script.</p>
<h2>Putting everything together in VS Code</h2>
<p>With the three pieces – voice input, chat response, and optional web search – I stitched them into a single async main loop. I used Python’s asyncio library so the voice recording, API calls, and speech synthesis could happen without blocking each other. Running the script in the integrated terminal showed the assistant responding in real time. I added a few safety prints to see which path (chat only or chat + search) was taken, which helped me debug later when the search API throttled.</p>
<h2>What I’m adding next</h2>
<p>The current version works for simple queries, but I still want better context handling – the assistant should remember the last few interactions. I also plan to replace the macOS say command with a cross‑platform TTS service so I can run JARVIS on Linux. Finally, I want to expose a small web UI so I can trigger the assistant from my phone without opening a terminal.</p>
<h2>Takeaway</h2>
<p>Building JARVIS taught me that a free‑tool stack can get you far if you break the problem into tiny steps and let an AI helper fill the gaps. The hardest part was wiring the pieces together, not writing the individual functions. If you’re starting from scratch, focus on one capability at a time – text chat, then voice, then search – and keep the code modular. You’ll be surprised how quickly a functional assistant emerges.</p>
<p>If you want to keep reading, I also wrote about <a href="/blog/what-a-memory-match-game-taught-me-about-state">What a memory match game taught me about state</a> and <a href="/blog/learning-to-code-with-just-a-laptop">Learning to code with just a laptop</a>. You can see what I am building right now on my <a href="/projects">projects page</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Learning to code with just a laptop</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/learning-to-code-with-just-a-laptop</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/learning-to-code-with-just-a-laptop</guid>
      <pubDate>Tue, 22 Sep 2026 03:30:00 GMT</pubDate>
      <category>Journey</category>
      <description>No coding classes, no tech background in Pundri. Just curiosity, a laptop, and a lot of late nights.</description>
      <content:encoded><![CDATA[<p>I&#39;m a Class 12 student from a small town. There was no coding class to join and nobody around me who worked in tech. What I had was a laptop and a stubborn habit of asking &quot;but how does that actually work?&quot;</p>
<h2>Where I started</h2>
<p>I began with Python because it let me see results fast. Print a line, change a number, run it again. That tight loop of trying something and seeing what happens is what made me stay.</p>
<h2>What actually helped</h2>
<ul><li>Building small things instead of only watching tutorials.</li><li>Reading error messages slowly instead of panicking at them.</li><li>Writing down what I learned so I could explain it to myself later.</li></ul>
<blockquote>I&#39;m not chasing certificates — I&#39;m chasing the ability to build things that actually work.</blockquote>
<p>I&#39;m still early in this journey. This blog is where I&#39;ll keep track of it: what I build, what breaks, and what I figure out along the way.</p>]]></content:encoded>
    </item>
    <item>
      <title>Python, C and C++: what each one taught me</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/python-c-and-cpp-what-each-one-taught-me</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/python-c-and-cpp-what-each-one-taught-me</guid>
      <pubDate>Tue, 15 Sep 2026 03:30:00 GMT</pubDate>
      <category>Learning</category>
      <description>Three languages, three different ways of thinking. Here is what I took from each.</description>
      <content:encoded><![CDATA[<p>Learning more than one language early on turned out to be less about syntax and more about how each one makes you think.</p>
<h2>Python — thinking in ideas</h2>
<p>Python let me focus on the logic. When I had an idea, I could test it in minutes, which made it the best place to learn how loops, functions, and data structures fit together.</p>
<h2>C — thinking in memory</h2>
<p>C removed the safety net. Suddenly I had to care about types, memory, and what the machine is really doing. It was frustrating and it was the most useful thing I learned.</p>
<h2>C++ — thinking in structure</h2>
<p>C++ added classes and objects on top, which pushed me to organise code into pieces that make sense on their own.</p>
<pre><code>def greet(name):
    return f&quot;Hello, {name}!&quot;

print(greet(&quot;world&quot;))</code></pre>
<p>Same idea, three languages, three different ways of getting there.</p>]]></content:encoded>
    </item>
    <item>
      <title>What a memory match game taught me about state</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/what-a-memory-match-game-taught-me-about-state</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/what-a-memory-match-game-taught-me-about-state</guid>
      <pubDate>Tue, 08 Sep 2026 03:30:00 GMT</pubDate>
      <category>Projects</category>
      <description>A simple card game is a great way to learn how state changes and events work together.</description>
      <content:encoded><![CDATA[<p>My Memory Match game looks simple, but it forced me to think carefully about state: which cards are flipped, which are matched, and what is allowed to happen next.</p>
<h2>State first, screen second</h2>
<p>The biggest lesson was to decide what the game needs to remember before drawing anything. Once the state was clear, the screen simply followed it.</p>
<pre><code>const state = {
  flipped: [],
  matched: [],
  locked: false,
};</code></pre>
<h2>Events need rules</h2>
<p>Clicking fast, clicking the same card twice, clicking while two cards are being compared — every one of those is a bug waiting to happen. Writing the rules down made them easy to handle.</p>
<blockquote>Most bugs are just a missing rule about what should happen next.</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>How I use AI tools without outsourcing my thinking</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/how-i-use-ai-tools-without-outsourcing-my-thinking</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/how-i-use-ai-tools-without-outsourcing-my-thinking</guid>
      <pubDate>Sun, 30 Aug 2026 03:30:00 GMT</pubDate>
      <category>AI</category>
      <description>AI can speed me up, but only if I stay the one who understands the code.</description>
      <content:encoded><![CDATA[<p>I use AI tools every day for learning and building. The rule I try to follow is simple: I should be able to explain everything I ship.</p>
<h2>What I use them for</h2>
<ul><li>Explaining a concept a different way when a tutorial did not click.</li><li>Reviewing my code and pointing out things I missed.</li><li>Getting unstuck, then going back to write the solution myself.</li></ul>
<h2>What I try not to do</h2>
<p>Paste an answer I do not understand. If I cannot explain a line, I have not learned it yet — I have just borrowed it.</p>
<blockquote>A tool should make me faster at thinking, not a replacement for it.</blockquote>]]></content:encoded>
    </item>
    <item>
      <title>What Digital Marketing with AI taught me about building for people</title>
      <link>https://krish-portfolio-dev.vercel.app/blog/what-digital-marketing-with-ai-taught-me</link>
      <guid isPermaLink="true">https://krish-portfolio-dev.vercel.app/blog/what-digital-marketing-with-ai-taught-me</guid>
      <pubDate>Tue, 18 Aug 2026 03:30:00 GMT</pubDate>
      <category>Marketing</category>
      <description>Finishing a Digital Marketing with AI certification changed how I think about the things I build.</description>
      <content:encoded><![CDATA[<p>This year I completed a Digital Marketing with AI certification. I expected to learn tools. I also learned to think about who the work is for.</p>
<h2>Good software still needs to be found</h2>
<p>SEO, ads, and social media are really about one thing: helping the right person find something useful. A great project nobody can find does not help anyone.</p>
<h2>What I am taking with me</h2>
<ul><li>Start with the audience, then decide the message.</li><li>Measure what happens instead of guessing.</li><li>Clear writing is a skill worth practising just like code.</li></ul>
<p>Web development and digital marketing feel like two halves of the same idea: build something good, then make sure people can reach it.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
