<?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: Shashank Chakraborty</title>
    <description>The latest articles on DEV Community by Shashank Chakraborty (@shashank_chakraborty_6362).</description>
    <link>https://dev.to/shashank_chakraborty_6362</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3704133%2Fd098c3bc-977f-44ec-878c-182cadfbe9d6.jpg</url>
      <title>DEV Community: Shashank Chakraborty</title>
      <link>https://dev.to/shashank_chakraborty_6362</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shashank_chakraborty_6362"/>
    <language>en</language>
    <item>
      <title>Ofrenda is a local altar for long lost repositories which still have unfinished work.</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Mon, 26 Oct 2026 21:13:26 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/ofrenda-is-a-local-altar-for-long-lost-repositories-which-still-have-unfinished-work-1aci</link>
      <guid>https://dev.to/shashank_chakraborty_6362/ofrenda-is-a-local-altar-for-long-lost-repositories-which-still-have-unfinished-work-1aci</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ofrenda
&lt;/h2&gt;

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

&lt;p&gt;Some folders are cemeteries of unfinished work. The last commit is still the inscription on the stone, and nobody has read it in years.&lt;/p&gt;

&lt;p&gt;Ofrenda is a local altar for those repositories. You point it at a folder on your machine. Each git repo that went quiet becomes a niche: the years it was alive, how long it has been dark, and its last commit subject. A longer quiet means a lower flame.&lt;/p&gt;

&lt;p&gt;Ask one to speak. Gemma 3, running on this computer through Ollama, answers in the repository's voice. It may only use the record it was handed: commit subjects, dates, the author name as git wrote it, a short README, and TODO lines. The server drops any commit id the model invents. If nothing it cited was real, the altar refuses the remembrance and leaves the inscription in place.&lt;/p&gt;

&lt;p&gt;The sample grave is &lt;code&gt;hourglass&lt;/code&gt;, a tiny duration parser whose last commit is 14 February 2023. On this machine Gemma said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The parsing of time ceased within these boundaries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It cited all five commits, from "Start a duration parser for the kitchen timer" through "Leave a warning about months before stopping," and the offering was a real file: &lt;code&gt;test_hourglass.py&lt;/code&gt;. It did not invent a person. The ghost is the repository.&lt;/p&gt;

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

&lt;p&gt;The app runs locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ollama pull gemma3:4b
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
python -m app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://127.0.0.1:8765" rel="noopener noreferrer"&gt;http://127.0.0.1:8765&lt;/a&gt; and choose &lt;strong&gt;Sit with the sample&lt;/strong&gt;. Or paste a folder that is a repository, or that contains some.&lt;/p&gt;

&lt;p&gt;If the 4B model is slow on CPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set OFRENDA_MODEL=gemma3:1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="https://github.com/Shashank-blip/Ofrenda" rel="noopener noreferrer"&gt;https://github.com/Shashank-blip/Ofrenda&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Python and FastAPI serve one page. There is no database and no account. Git is read with &lt;code&gt;git log&lt;/code&gt; and a walk for TODO lines. Diffs are not sent. Files named &lt;code&gt;.env&lt;/code&gt;, and names that look like keys, are left out of the packet, so a secret in a note does not become part of the prompt.&lt;/p&gt;

&lt;p&gt;The model call is Ollama at &lt;code&gt;127.0.0.1&lt;/code&gt;, &lt;code&gt;gemma3:4b&lt;/code&gt;, JSON output, and a small context window so a laptop without a discrete GPU can hold it. This machine has Intel Iris Xe graphics. The 4B model still answered.&lt;/p&gt;

&lt;p&gt;The check that matters is small and tested without the model: a citation has to match a SHA that was in the packet. Unknown ids are dropped. An empty citation list is a refusal, not a story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open innovation matters
&lt;/h2&gt;

&lt;p&gt;A closed model would have meant uploading unfinished work: half-written TODOs, private commit subjects, the names of the people who touched the code. The dead, in this sense, should be visited at home.&lt;/p&gt;

&lt;p&gt;Gemma is an open-weight model. Ollama runs it on the machine. After the weights are downloaded, the altar does not need the network to speak. The weights can be swapped with &lt;code&gt;OFRENDA_MODEL&lt;/code&gt;. Nothing about the remembrance is locked inside a hosted service that could disappear and take the voice with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemmachallenge</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Lazyyyyy pardons the notes you were never going to read</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Thu, 22 Oct 2026 22:46:02 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/lazyyyyy-pardons-the-notes-you-were-never-going-to-read-41md</link>
      <guid>https://dev.to/shashank_chakraborty_6362/lazyyyyy-pardons-the-notes-you-were-never-going-to-read-41md</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I do not read the pile. I paste it and close the tab.&lt;/p&gt;

&lt;p&gt;Lazyyyyy is that pile. Messages, newsletters, half-plans, the lease you have been circling. You drop them in a box. You get two lines back, and each note gets one stamp:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pardoned.&lt;/strong&gt; Nothing breaks if you ignore it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two minutes.&lt;/strong&gt; Someone, or a date, is actually waiting, so it writes the reply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already seen.&lt;/strong&gt; You got away with this shape of thing before. It does not make you decide again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The person who gets to be lazier is anyone whose unread pile has become a second job. The sample pile is six notes. A raccoon video, a playlist with no date, a morning-routine newsletter, a lease that needs a yes or no by Friday, the same lease again, and Priya, who is blocked until you say whether the demo is Thursday or Friday. The raccoon can wait. Priya cannot. The second lease is not a new problem.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://lazyyyyy.vercel.app" rel="noopener noreferrer"&gt;lazyyyyy.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Use the sample pile&lt;/strong&gt; and leave the tab open. The first thing that appears under &lt;strong&gt;Already gotten away with&lt;/strong&gt; is two old pardons: a playlist from Oct 11, and a morning-routine newsletter from Oct 3. Those are planted on purpose. Then the page searches them, and Gemma stamps the six notes. A two-minute stamp includes the reply, with a button to copy it.&lt;/p&gt;

&lt;p&gt;The public page calls Gemma through &lt;a href="https://docs.ollama.com/cloud" rel="noopener noreferrer"&gt;Ollama's hosted API&lt;/a&gt;. The vectors come from EmbeddingGemma on a machine I control, because that model is not in Ollama's hosted catalog. If you open a new pile while that machine is asleep, the old pardons are still in Tiger. A fresh stamp needs the tunnel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/Shashank-blip" rel="noopener noreferrer"&gt;
        Shashank-blip
      &lt;/a&gt; / &lt;a href="https://github.com/Shashank-blip/Lazyyyyy" rel="noopener noreferrer"&gt;
        Lazyyyyy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Lazyyyyy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;You paste the messages, notes, and links you are not going to read. Lazyyyyy stamps each one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pardoned.&lt;/strong&gt; Nothing breaks if you ignore it
&lt;strong&gt;Two minutes.&lt;/strong&gt; Someone or a deadline is actually waiting, so it writes the reply
&lt;strong&gt;Already seen.&lt;/strong&gt; Tiger remembers you already got away with this, or the same ask showed up twice in the pile.&lt;/p&gt;
&lt;p&gt;The only thing you are supposed to read is two lines.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this is local&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The notes people avoid are the personal ones: a lease, a person waiting, a chat they do not want in someone else's log. Gemma 4 E4B runs through Ollama on the same machine as the paste. EmbeddingGemma turns each note into a vector here too. Nothing in the pile is sent to a model API.&lt;/p&gt;
&lt;p&gt;Tiger is the memory, not a log bolted on at the end. Every stamp is stored with its text and its…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Shashank-blip/Lazyyyyy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/Shashank-blip/Lazyyyyy" rel="noopener noreferrer"&gt;github.com/Shashank-blip/Lazyyyyy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT. The pile lives in its own Postgres schema, &lt;code&gt;lazyyyyy&lt;/code&gt;, so it can share a Tiger service without touching anything else on it.&lt;/p&gt;

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

&lt;p&gt;Gemma does two jobs, and only those. It splits a paste into separate notes. Then, after search, it stamps each note. The prompt forbids a todo list. A date, money, or a named person waiting on a reply cannot be a pardon. A merge has to point at a real earlier note. If a stamp comes back missing, the pile stops and asks you to try again. Quietly pardoning a deadline would be the lazy version of a bug.&lt;/p&gt;

&lt;p&gt;Between those two calls, each note is embedded with EmbeddingGemma (768 dimensions) and looked up in Tiger. Keyword rank is BM25 through &lt;code&gt;pg_textsearch&lt;/code&gt;. Meaning rank is pgvector cosine distance on a &lt;code&gt;halfvec&lt;/code&gt;. The two lists are fused with reciprocal rank fusion, k = 60, and the top three go into the prompt. Exact words like "Friday" and "Priya" survive the keyword side. "Send the playlist whenever" still matches "the playlist when you can" on the vector side.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  paste[Paste] --&amp;gt; split[Gemma splits the notes]
  split --&amp;gt; embed[EmbeddingGemma]
  embed --&amp;gt; tiger[Tiger hybrid search]
  tiger --&amp;gt; stamp[Gemma stamps verdicts]
  stamp --&amp;gt; lines[Two lines]
  stamp --&amp;gt; memory[Tiger stores the decision]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Vercel hosts the site. It cannot hold Gemma 4 E4B, which is about 6 GB before it even starts. So the deployed app sets OLLAMA_HOST to &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;https://ollama.com&lt;/a&gt; and GEMMA_MODEL to gemma4. The same /api/chat call runs against gemma4:e4b on my laptop when OLLAMA_HOST is local. One environment variable swaps them.&lt;/p&gt;

&lt;p&gt;Ollama also refuses a public hostname. A tunnel to port 11434 returned 403 until the request's Host header was rewritten to localhost:11434. That is the whole of the deployment trick. The model did not get more clever. The header did.&lt;/p&gt;

&lt;p&gt;Why Does Open Innovation Matter?&lt;br&gt;
The notes people avoid are the personal ones. A lease. A person waiting. A chat they do not want in someone else's log.&lt;/p&gt;

&lt;p&gt;An open-weight Gemma is what makes the pardon possible without renting a closed chat product for it. On the laptop, gemma4:e4b and EmbeddingGemma run through Ollama, and the pile does not need a model API at all. On the public demo I serve Gemma through Ollama instead, which is still Gemma, still swappable, and still not a closed assistant with a monthly seat. GEMMA_MODEL and OLLAMA_HOST are the whole provider. A bigger local model, or a smaller one, is a config change.&lt;/p&gt;

&lt;p&gt;Tiger is the part that gets lazier over time. The stamp is stored with the text and the vector. Next time, hybrid search recalls the old decision and Gemma is not asked to re-litigate it. A closed organizer would call that memory a feature on a higher plan. Here it is a table in a schema I control.&lt;/p&gt;

&lt;p&gt;A closed API could sort the same six notes. It would also be the place those notes live, and it would charge me to look at them again tomorrow. The open setup let me keep the judgment, the vectors, and the veto in pieces I can replace.&lt;/p&gt;

&lt;p&gt;Prize Categories&lt;br&gt;
Best Use of Gemma. Gemma splits the pile and writes the stamps. Locally that is gemma4:e4b through Ollama. On the deployed site it is gemma4 served by Ollama. EmbeddingGemma, also Gemma, builds the 768-dimension vectors the search uses.&lt;br&gt;
Best Use of Tiger Data. Every note and every stamp is a row in Tiger with a pgvector embedding. Search is hybrid: BM25 keyword rank plus cosine distance, fused with reciprocal rank fusion. The agent reads and writes that database. It does not keep a side file of "memory."&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>From GitHub Commits to Tasks: Learning Backend Webhooks Through Open Source</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Thu, 22 Oct 2026 19:12:47 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/from-github-commits-to-tasks-learning-backend-webhooks-through-open-source-3m2j</link>
      <guid>https://dev.to/shashank_chakraborty_6362/from-github-commits-to-tasks-learning-backend-webhooks-through-open-source-3m2j</guid>
      <description>&lt;p&gt;One of the best ways to learn backend development is to stop treating it as just writing APIs and start understanding how different systems communicate with each other.&lt;/p&gt;

&lt;p&gt;Recently, while contributing to DevBoard, an open-source Kanban board built specifically for developers, I worked on a feature that helped me explore exactly that: creating tasks from GitHub commit events using webhooks.&lt;/p&gt;

&lt;p&gt;🔗 Project: &lt;a href="https://github.com/anoopcodehack/DevBoard" rel="noopener noreferrer"&gt;https://github.com/anoopcodehack/DevBoard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem&lt;/p&gt;

&lt;p&gt;Developers already spend a large part of their day inside GitHub. Switching between GitHub and a separate task board to keep work organized can introduce unnecessary manual steps.&lt;/p&gt;

&lt;p&gt;What if a relevant GitHub event could trigger task creation in DevBoard automatically?&lt;/p&gt;

&lt;p&gt;That was the idea behind this contribution.&lt;/p&gt;

&lt;p&gt;How webhooks make this possible&lt;/p&gt;

&lt;p&gt;A webhook allows one application to notify another when a particular event occurs.&lt;/p&gt;

&lt;p&gt;The workflow, at a high level, looks like this:&lt;/p&gt;

&lt;p&gt;An event occurs on GitHub. A commit-related event triggers the configured webhook.&lt;/p&gt;

&lt;p&gt;GitHub sends an HTTP request to the application's webhook endpoint, including event data in the payload.&lt;/p&gt;

&lt;p&gt;The backend receives and processes the event, extracting the information it needs from the payload.&lt;/p&gt;

&lt;p&gt;DevBoard creates a task using the relevant information, allowing the event to feed directly into the task-management workflow.&lt;/p&gt;

&lt;p&gt;Instead of repeatedly asking GitHub whether something has changed, the receiving application can react when an event notification arrives.&lt;/p&gt;

&lt;p&gt;That is what makes webhooks such a useful integration mechanism.&lt;/p&gt;

&lt;p&gt;What I learned&lt;/p&gt;

&lt;p&gt;This was more than just connecting two applications.&lt;/p&gt;

&lt;p&gt;It was an opportunity to understand how backend systems handle external events, how HTTP requests can trigger workflows, and how data from one service can be transformed into something useful in another.&lt;/p&gt;

&lt;p&gt;It also made me think more carefully about the details that matter in real integrations: validating incoming requests, handling unexpected payloads, preventing duplicate processing where necessary, and making failures observable.&lt;/p&gt;

&lt;p&gt;These are the kinds of engineering considerations that turn a simple integration into a reliable feature.&lt;/p&gt;

&lt;p&gt;Why I enjoy contributing to open source&lt;/p&gt;

&lt;p&gt;This is one of the reasons I enjoy working on existing projects.&lt;/p&gt;

&lt;p&gt;You get to understand a codebase, work on a real issue, see how a feature fits into an existing application, and learn from the process of contributing through pull requests.&lt;/p&gt;

&lt;p&gt;I've had 6 PRs merged into DevBoard so far, with another contribution in progress. Each one has been an opportunity to improve something while learning along the way.&lt;/p&gt;

&lt;p&gt;The biggest takeaway for me: building backend systems is not only about what happens inside your application. It's also about how your application communicates, reacts, and integrates with the world around it.&lt;/p&gt;

&lt;p&gt;I'm looking forward to learning more, building more, and continuing to contribute.&lt;/p&gt;

&lt;p&gt;If you're interested in developer-focused open source projects, check out DevBoard:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/anoopcodehack/DevBoard" rel="noopener noreferrer"&gt;https://github.com/anoopcodehack/DevBoard&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  OpenSource #BackendDevelopment #Webhooks #GitHub #WebDevelopment #SoftwareEngineering #BuildInPublic
&lt;/h1&gt;

</description>
      <category>opensource</category>
      <category>backend</category>
      <category>github</category>
      <category>webhooks</category>
    </item>
    <item>
      <title>DocuFlow: Turning Invoice PDFs into Structured Financial Data with Python and Docker</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Mon, 19 Oct 2026 22:26:50 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/docuflow-turning-invoice-pdfs-into-structured-financial-data-with-python-and-docker-o2i</link>
      <guid>https://dev.to/shashank_chakraborty_6362/docuflow-turning-invoice-pdfs-into-structured-financial-data-with-python-and-docker-o2i</guid>
      <description>&lt;p&gt;DocuFlow: Turning Invoice PDFs into Structured Financial Data&lt;/p&gt;

&lt;p&gt;Invoices are easy to overlook when they’re just files sitting in a folder.&lt;/p&gt;

&lt;p&gt;But once you need to extract information from hundreds of documents, identify duplicates, store financial records, and analyze spending, things get more interesting.&lt;/p&gt;

&lt;p&gt;That’s the problem I wanted to tackle with DocuFlow — an intelligent, containerized invoice ingestion pipeline built with Python.&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/Shashank0701-byte" rel="noopener noreferrer"&gt;
        Shashank0701-byte
      &lt;/a&gt; / &lt;a href="https://github.com/Shashank0701-byte/docuflow" rel="noopener noreferrer"&gt;
        docuflow
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DocuFlow is a containerized, event-driven pipeline that automates ingestion, OCR, parsing, and analytics for invoices and financial documents.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DocuFlow — Intelligent Invoice Ingestion Pipeline&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/087fd83906413aea53ad1b40b719e743f7a8a4075e564a9738b59cc703da31bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507974686f6e2d332e31312d626c75653f6c6f676f3d707974686f6e266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/087fd83906413aea53ad1b40b719e743f7a8a4075e564a9738b59cc703da31bf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507974686f6e2d332e31312d626c75653f6c6f676f3d707974686f6e266c6f676f436f6c6f723d7768697465" alt="Python"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/dbdd64fcb04f8411badd299a63960c4b2e0d380473359b544b1d4a228f1b0b3a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f636b65722d436f6e7461696e6572697a65642d3234393645443f6c6f676f3d646f636b6572266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/dbdd64fcb04f8411badd299a63960c4b2e0d380473359b544b1d4a228f1b0b3a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f636b65722d436f6e7461696e6572697a65642d3234393645443f6c6f676f3d646f636b6572266c6f676f436f6c6f723d7768697465" alt="Docker"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/271f521c134a4dc106acaf60fd7afa073c549f554cc876c7c7da2273542d966d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f506f7374677265732d44617461626173652d3333363739313f6c6f676f3d706f737467726573716c266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/271f521c134a4dc106acaf60fd7afa073c549f554cc876c7c7da2273542d966d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f506f7374677265732d44617461626173652d3333363739313f6c6f676f3d706f737467726573716c266c6f676f436f6c6f723d7768697465" alt="Postgres"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/27daf5add49e3acd290fb45962897d0a908bb0d529b8271a33fba2c51b73a3a2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53747265616d6c69742d44617368626f6172642d4646344234423f6c6f676f3d73747265616d6c6974266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/27daf5add49e3acd290fb45962897d0a908bb0d529b8271a33fba2c51b73a3a2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53747265616d6c69742d44617368626f6172642d4646344234423f6c6f676f3d73747265616d6c6974266c6f676f436f6c6f723d7768697465" alt="Streamlit"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/5b63f43aa1b7e4eb04322b3e27b56ae58cef2bfe9e13019b2c222a61fa4ba692/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c642d50617373696e672d627269676874677265656e"&gt;&lt;img src="https://camo.githubusercontent.com/5b63f43aa1b7e4eb04322b3e27b56ae58cef2bfe9e13019b2c222a61fa4ba692/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c642d50617373696e672d627269676874677265656e" alt="Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;DocuFlow is a containerized, event-driven pipeline that automates ingestion, OCR, parsing, and analytics for invoices and financial documents. It uses a lightweight microservice pattern so heavy OCR work runs asynchronously and does not block the UI.&lt;/p&gt;
&lt;p&gt;Quick highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hot-folder watcher for real-time ingestion&lt;/li&gt;
&lt;li&gt;Celery + Redis for asynchronous OCR and parsing&lt;/li&gt;
&lt;li&gt;Tesseract + Poppler for PDF/image text extraction&lt;/li&gt;
&lt;li&gt;PostgreSQL for persistent storage&lt;/li&gt;
&lt;li&gt;Streamlit dashboard for visualization and quick analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🏗️ Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The system follows a microservices pattern and is orchestrated with Docker Compose.&lt;/p&gt;
&lt;p&gt;If GitHub renders the diagram you will see the flow below. If it doesn't, a plain ASCII fallback is provided right after.&lt;/p&gt;

  &lt;div class="js-render-enrichment-target"&gt;
    &lt;div class="render-plaintext-hidden"&gt;
      &lt;pre&gt;graph LR
  A[data_raw_hot_folder] --&amp;gt;|New PDF detected| B[watcher_service]
  B --&amp;gt;|Push task| C[redis_broker]
  C --&amp;gt;|Distribute task| D[celery_worker]
  D --&amp;gt;|Extract text| E[tesseract_poppler]
  D --&amp;gt;|Parse &amp;amp; save| F[postgres_db]
  F --&amp;gt;|Query| G[streamlit_dashboard]
&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full"&gt;
    &lt;span&gt;
      &lt;span class="sr-only"&gt;Loading&lt;/span&gt;
&lt;/span&gt;
  &lt;/span&gt;


&lt;p&gt;Plain fallback (always visible):&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;Flow
data/raw (Hot Folder) -&amp;gt; Watcher Service&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Shashank0701-byte/docuflow" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What does it do?
&lt;/h2&gt;

&lt;p&gt;DocuFlow watches a designated folder for new invoice files and processes them through an asynchronous pipeline.&lt;/p&gt;

&lt;p&gt;The workflow looks like this:&lt;/p&gt;

&lt;p&gt;New PDF -&amp;gt; File Watcher -&amp;gt; Redis + Celery -&amp;gt; OCR -&amp;gt; Data Parsing -&amp;gt; PostgreSQL -&amp;gt; Streamlit Dashboard&lt;/p&gt;

&lt;p&gt;The goal is to turn unstructured invoice documents into structured, queryable financial data without making the dashboard wait for every document to finish processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;Here are some of the engineering decisions behind the project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event-driven ingestion:&lt;/strong&gt; A folder watcher detects incoming documents and queues them for processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous processing:&lt;/strong&gt; Celery and Redis separate the ingestion flow from the OCR workload, making the architecture easier to extend with additional workers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OCR and parsing:&lt;/strong&gt; Tesseract and Poppler help extract text from PDFs, while parsing logic identifies fields such as invoice number, date, vendor, and total amount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent storage:&lt;/strong&gt; PostgreSQL stores the extracted records for downstream queries and analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data visibility:&lt;/strong&gt; A Streamlit dashboard presents spending trends, vendor breakdowns, recent invoices, and processing information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Containerized services:&lt;/strong&gt; Docker Compose brings the watcher, worker, database, broker, and dashboard together in a reproducible local environment.&lt;/p&gt;

&lt;p&gt;I also worked on duplicate detection so that the pipeline can avoid reprocessing the same invoice unnecessarily.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted to explore
&lt;/h2&gt;

&lt;p&gt;This project was about more than extracting text from a PDF.&lt;/p&gt;

&lt;p&gt;I wanted to work with the pieces that turn a script into a pipeline: asynchronous jobs, service boundaries, persistence, containerization, and visibility into the processing workflow.&lt;/p&gt;

&lt;p&gt;It also made me think more carefully about what happens when a document is malformed, OCR produces noisy output, or a parser encounters an unexpected invoice format.&lt;/p&gt;

&lt;p&gt;There’s still room to grow — including stronger test coverage, more robust parsing for varied invoice layouts, and better production-grade observability.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;More resilient extraction for inconsistent invoice formats.&lt;/li&gt;
&lt;li&gt;Better automated testing and CI integration.&lt;/li&gt;
&lt;li&gt;Email-based invoice ingestion.&lt;/li&gt;
&lt;li&gt;Smarter parsing for complex financial documents.&lt;/li&gt;
&lt;li&gt;Improved operational visibility and failure recovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DocuFlow is an evolving project, and I’m looking forward to improving it further.&lt;/p&gt;

&lt;p&gt;I’d especially love feedback from developers who have built document-processing pipelines: What would you improve first — the OCR and parsing layer, task reliability, or observability?&lt;/p&gt;

</description>
      <category>python</category>
      <category>docker</category>
      <category>opensource</category>
      <category>database</category>
    </item>
    <item>
      <title>Claude is stuck on a question. Answer it from Discord.</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Sat, 17 Oct 2026 23:29:03 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/claude-is-stuck-on-a-question-answer-it-from-discord-3hf5</link>
      <guid>https://dev.to/shashank_chakraborty_6362/claude-is-stuck-on-a-question-answer-it-from-discord-3hf5</guid>
      <description>&lt;p&gt;Claude Code will happily work for twenty minutes and then stop on a question you could have answered in one line. The terminal is on a desk. You are not.&lt;/p&gt;

&lt;p&gt;A few people have already solved the "I'm not at the keyboard" half of this. &lt;a href="https://dev.to/jagafarm"&gt;jagafarm&lt;/a&gt; shows that a tmux session is what keeps the agent alive when the laptop lid closes, and that attaching from a phone is the whole trick: &lt;a href="https://dev.to/jagafarm/stop-losing-claude-code-sessions-a-tmux-primer-for-mobile-devs-2p48"&gt;Stop losing Claude Code sessions: a tmux primer for mobile devs&lt;/a&gt;. &lt;a href="https://dev.to/coa00"&gt;coa00&lt;/a&gt; and &lt;a href="https://dev.to/yuu1ch13"&gt;yuu1ch13&lt;/a&gt; go further and put Allow and Deny on the lock screen, by blocking inside a &lt;code&gt;PermissionRequest&lt;/code&gt; hook until ntfy.sh comes back with a decision: &lt;a href="https://dev.to/coa00/how-i-built-a-mobile-approval-system-for-claude-code-so-i-can-finally-leave-my-desk-1ida"&gt;a mobile approval system&lt;/a&gt; and &lt;a href="https://dev.to/yuu1ch13/approve-claude-code-permission-requests-from-your-phone-47nd"&gt;Approve Claude Code permission requests from your phone&lt;/a&gt;. &lt;a href="https://dev.to/pederaa"&gt;pederaa&lt;/a&gt; uses the &lt;code&gt;Notification&lt;/code&gt; hook so you at least find out the agent is waiting: &lt;a href="https://dev.to/pederaa/how-to-know-when-a-claude-code-agent-needs-your-approval-without-watching-the-terminal-5ekf"&gt;How to know when a Claude Code agent needs your approval&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Those are the right shapes for yes and no. They are a bad shape for the message Claude actually sends, which is often three questions with descriptions, or a sentence you still have to type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Shashank0701-byte/agent-bridge" rel="noopener noreferrer"&gt;agent-bridge&lt;/a&gt; takes the tmux half from the primer and the notification half from the hook posts, and then does something they deliberately do not: it types your reply into the live pane. The hook does not wait, and it does not return &lt;code&gt;allow&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/Shashank0701-byte" rel="noopener noreferrer"&gt;
        Shashank0701-byte
      &lt;/a&gt; / &lt;a href="https://github.com/Shashank0701-byte/agent-bridge" rel="noopener noreferrer"&gt;
        agent-bridge
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Get a Discord DM when your coding agent is blocked, reply from your phone, and have it typed straight back into the session. Works with Claude Code, Codex, or anything with a CLI.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;agent-bridge&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/Shashank0701-byte/agent-bridge/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/Shashank0701-byte/agent-bridge/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/Shashank0701-byte/agent-bridge/actions/workflows/install.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/Shashank0701-byte/agent-bridge/actions/workflows/install.yml/badge.svg" alt="Install"&gt;&lt;/a&gt;
&lt;a href="https://github.com/Shashank0701-byte/agent-bridge/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Get a Discord DM when your coding agent (Claude Code, Codex, etc.) is blocked
waiting on you, reply from your phone, and have that reply typed straight back
into the session -- no need to be at your keyboard.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It types what you send into a live terminal on your machine, so one Discord
account is all that stands between a stranger and your shell. Two minutes on
&lt;a href="https://github.com/Shashank0701-byte/agent-bridge#security" rel="noopener noreferrer"&gt;Security&lt;/a&gt; before you install is time well spent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The whole thing hinges on one idea: run the agent inside &lt;strong&gt;tmux&lt;/strong&gt;, then treat
the terminal itself as the integration point instead of hooking into each
tool's internals differently. That's what makes it work the same way for
Claude Code, Codex, or anything else you run in a terminal.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt; you AFK                         tmux session "myproject"
   |                                    |
   |  Discord DM  &amp;lt;----- notify.sh &amp;lt;----+  Notification hook fires
   |                                       (Claude Code is&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Shashank0701-byte/agent-bridge" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What shows up on the phone
&lt;/h2&gt;

&lt;p&gt;A decision is the whole card, not a buzz that says "something needs you."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbr7gsrsq7xttfc15grkt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbr7gsrsq7xttfc15grkt.jpeg" alt="A Discord DM titled PromptWall needs a decision, with three numbered questions and an option under each" width="720" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You answer with the codes it printed. &lt;code&gt;1:1 2:1 3:1&lt;/code&gt; means the first option of each question. The bridge turns those numbers back into the labels Claude is waiting for, then confirms what it sent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp5f3ktaixpy0bk8hh1df.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp5f3ktaixpy0bk8hh1df.jpeg" alt="The same DM, with the reply 1:1 2:1 3:1 and the bot answering sent to PromptWall, answered the pending question" width="720" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two projects are two tmux sessions. Each message is tagged with the one it came from. A reply to PromptWall never lands in CodePulse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fub7k40r51kzp4uf4smh5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fub7k40r51kzp4uf4smh5.jpeg" alt="PromptWall and CodePulse both asking for permission, each answered with yes, each confirmed separately" width="800" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A permission prompt is the same idea in one line. &lt;code&gt;pick 2&lt;/code&gt; presses the digit. Ordinary prose is typed in as if you were sitting there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it types, instead of approving inside the hook
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;PermissionRequest&lt;/code&gt; hook can return &lt;code&gt;allow&lt;/code&gt; or &lt;code&gt;deny&lt;/code&gt; and Claude continues. That is what the ntfy projects do, and it is the right tool when the only decision is a button.&lt;/p&gt;

&lt;p&gt;It does not cover the rest of what the pane is showing. A question card wants the option labels. A plan you disagree with wants a sentence. A follow-up wants whatever you would have typed. The terminal already accepts all of those. So the bridge treats the terminal as the API.&lt;/p&gt;

&lt;p&gt;The path is short. Claude's &lt;code&gt;Notification&lt;/code&gt; hook fires because the agent is blocked. A Discord bot, allowlisted to one user id, DMs you. Your reply is loaded into a tmux paste buffer and pasted into the named session, then Enter. &lt;code&gt;send-keys&lt;/code&gt; is the wrong primitive here: tmux parses that text as arguments, so a reply that starts with &lt;code&gt;-&lt;/code&gt; is a flag, and quotes get eaten. A paste buffer is just bytes.&lt;/p&gt;

&lt;p&gt;The hook itself returns immediately. &lt;code&gt;PreToolUse&lt;/code&gt; runs before the question card is drawn. If the hook sat there waiting for your phone, the card would never appear, and you could not answer it from the keyboard either.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that makes "just paste it" unsafe
&lt;/h2&gt;

&lt;p&gt;Claude's modal selectors treat a paste as a choice. Measured on Claude Code 2.1.241, pasting the word &lt;code&gt;hello&lt;/code&gt; into an open selector selected the highlighted option, with no Enter. On a plan-approval dialog, that is the agent starting to edit files because you sent a sentence.&lt;/p&gt;

&lt;p&gt;So a reply never goes in while that widget is up. The bridge sends Escape, checks that the selector footer is gone, and only then pastes. If the widget is still there, nothing is typed and Discord says so. Refusing is recoverable. Approving a plan by accident is not.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pick&lt;/code&gt; is only the digits 1 through 9. The first digit of &lt;code&gt;12&lt;/code&gt; would already have chosen something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;You need three things: a paid Claude plan (the free claude.ai tier does not include Claude Code), a Discord bot that only you can DM, and tmux. On Windows the typing half runs inside WSL. Without WSL you can still get the notifications, and you cannot type back into the pane.&lt;/p&gt;

&lt;p&gt;On macOS, Linux, or a WSL distro you already have:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
curl -fsSL https://gitlab.com/shashankchakraborty712005/agent-bridge/-/raw/main/scripts/get.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>discord</category>
      <category>linux</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>OrderSift turns messy customer messages into orders you can actually act on.</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:29:47 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/ordersift-turns-messy-customer-messages-into-orders-you-can-actually-act-on-5c4k</link>
      <guid>https://dev.to/shashank_chakraborty_6362/ordersift-turns-messy-customer-messages-into-orders-you-can-actually-act-on-5c4k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;OrderSift turns messy customer messages into orders you can actually act on.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run a small food or craft business, your "order system" is probably your phone. Orders arrive as WhatsApp messages, Instagram DMs, texts, and emails, and every one is written differently. Here's a real-looking one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hey Sarah! Can I please get 2 boxes of chocolate fudge cupcakes and 1 red velvet birthday cake with "Happy 30th Dave" written in gold? My nephew has a severe peanut allergy so please make sure completely nut free! We will pick it up on Saturday around 2pm. Call my mobile 555-0199 when ready.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A baker reading that has to pull out the items, the inscription, the pickup time, the phone number, and (most importantly) the allergy. Then they have to notice what &lt;em&gt;isn't&lt;/em&gt; there: the customer never said their name. Miss one detail and you've baked the wrong cake, or worse.&lt;/p&gt;

&lt;p&gt;You paste the message into OrderSift and get back an editable order card:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line items&lt;/strong&gt; with quantities and customizations ("with 'Happy 30th Dave' written in gold").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pickup date, time, and phone&lt;/strong&gt;, pulled out of the prose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allergy warnings&lt;/strong&gt; surfaced in special instructions, so they can't hide in the middle of a paragraph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prices filled in automatically&lt;/strong&gt; from the merchant's catalog. "2 boxes of chocolate fudge cupcakes" matches the 6-pack box SKU, not the single cupcake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A missing-details check&lt;/strong&gt; that flags what you still need (here, the customer's name) and writes a polite follow-up message you can copy straight back into the chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A local order ledger&lt;/strong&gt; with statuses (draft, confirmed, preparing, ready, delivered) and CSV/JSON export for your spreadsheet or accountant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who would pay for it?&lt;/strong&gt; Home bakers, caterers, custom-apparel sellers, and anyone else taking orders through chat apps. They have too many orders to keep in their head, but they're too small for a POS system or Shopify, and they don't want to make customers fill out a form. The time saved is easy to measure: every order that comes in as a message gets typed up by someone. The cost of a mistake is even easier: one missed allergy note costs more than a year of a small subscription.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://order-sift.vercel.app" rel="noopener noreferrer"&gt;https://order-sift.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try one of the built-in sample messages (bakery, catering inquiry, custom hoodies) or paste your own. The badge at the top shows which model is serving the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/Shashank-blip" rel="noopener noreferrer"&gt;
        Shashank-blip
      &lt;/a&gt; / &lt;a href="https://github.com/Shashank-blip/OrderSift" rel="noopener noreferrer"&gt;
        OrderSift
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;OrderSift&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;OrderSift turns messy customer messages into clean, structured orders that a merchant can act on quickly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Turn chaotic chats into organized, valid orders with AI-powered extraction, local-first processing, and zero cloud leakage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OrderSift is built for solo businesses, home bakers, caterers, and micro-merchants who receive orders over WhatsApp, Instagram DMs, email, and SMS. Instead of manually copying details from messy conversations, the app extracts useful order data, validates missing fields, and helps send smart follow-up questions back to the customer.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Most small businesses are still handling orders by hand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reading long chat histories&lt;/li&gt;
&lt;li&gt;extracting item details and quantities&lt;/li&gt;
&lt;li&gt;reconciling delivery dates and contact info&lt;/li&gt;
&lt;li&gt;chasing missing information manually&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates friction, mistakes, and lost revenue. OrderSift helps simplify that workflow with a local-first, privacy-conscious order intake assistant.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What OrderSift does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Extracts structured order data from messy, real-world messages&lt;/li&gt;
&lt;li&gt;Identifies product details, quantities, delivery dates, and customer…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Shashank-blip/OrderSift" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;OrderSift is a Next.js 14 (App Router) + TypeScript app. The AI part is deliberately small and swappable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open-weight models only.&lt;/strong&gt; Extraction runs on open-weight models through a provider chain, all on the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Groq&lt;/strong&gt; serving &lt;strong&gt;&lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;&lt;/strong&gt; (Apache 2.0) for the hosted demo. Groq is fast enough that parsing feels instant, which matters when you have twenty DMs to get through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any OpenAI-compatible endpoint&lt;/strong&gt; (Together, OpenRouter, a self-hosted vLLM box), configured by environment variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; for fully local inference, e.g. &lt;code&gt;qwen2.5&lt;/code&gt; or &lt;code&gt;gpt-oss:20b&lt;/code&gt; on the merchant's own laptop. Ollama's &lt;code&gt;format&lt;/code&gt; parameter takes a JSON schema, so the model is constrained to valid output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rule-based fallback parser&lt;/strong&gt; so the app still produces a rough draft when no model is reachable. It's clearly labeled as a fallback, because a draft with gaps is more honest than a confident guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app doesn't hard-code which Groq models exist. It asks Groq which models the API key can use and picks the best available one from a preference list. I learned this the hard way: my first deploy kept silently falling back to the rule-based parser because a hard-coded model ID had been retired. Now any fallback shows its reason right in the UI (&lt;code&gt;Heuristic fallback — Groq: HTTP 404 ...&lt;/code&gt;), which turned an hour of guessing into a one-line fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Schema first, model second.&lt;/strong&gt; The model gets a system prompt with extraction rules and the full JSON schema for an order. A few of those rules came from watching it get things wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;A name in a greeting ("Hey Sarah!") is usually the merchant, not the customer.&lt;/em&gt; Without this, the model cheerfully assigned the baker's name to the customer.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Keep dates as the customer wrote them.&lt;/em&gt; "Saturday" is more useful to a baker than a guessed calendar date.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Keep the unit ("boxes", "dozen") in the item notes&lt;/em&gt;, so catalog matching can price a box rather than a single cupcake.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model's output then goes through a normalizing step (prices or quantities that come back as strings, missing arrays, stray markdown fences) before deterministic code computes totals and missing fields. I don't let the LLM do arithmetic or decide what's "required".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Catalog matching for prices and allergens.&lt;/strong&gt; Customers never use the merchant's product names. A matcher scores each extracted item against the catalog, handling plurals and words like "boxes", and fills in the SKU name, unit price, and allergen list. It only fills items that don't already have a price, so anything the merchant typed is never overwritten. If nothing matches (say, "blueberry muffins"), the item stays unpriced instead of getting a guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Local-first storage.&lt;/strong&gt; Saved orders live in the browser's IndexedDB via Dexie. There's no account and no server-side order database by default; the merchant's ledger is on their device and exports to CSV/JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Tiger Data as the merchant's back office.&lt;/strong&gt; The catalog and the cloud ledger live in a Tiger Cloud service (PostgreSQL + TimescaleDB). I built it entirely from my editor through the Tiger MCP server, without opening a dashboard: create the service, apply &lt;a href="https://github.com/Shashank-blip/OrderSift/blob/main/scripts/setup-tiger.sql" rel="noopener noreferrer"&gt;&lt;code&gt;scripts/setup-tiger.sql&lt;/code&gt;&lt;/a&gt;, seed products, and inspect rows while debugging. When I realized "2 boxes of cupcakes" was being priced as two single cupcakes, adding the 6-pack SKU was one &lt;code&gt;db_query&lt;/code&gt; call. At runtime, the app talks to Tiger directly through &lt;code&gt;pg&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Catalog matching&lt;/strong&gt; reads &lt;code&gt;merchant_catalog&lt;/code&gt; live, so a merchant changes a price in one place and the next parsed order uses it. If the database is unreachable, the app falls back to a built-in catalog and says so in the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Sync to Tiger Cloud"&lt;/strong&gt; upserts confirmed orders from the browser into &lt;code&gt;customer_orders_sync&lt;/code&gt;, keyed by order number. The local ledger stays the source of truth on the device, and Tiger gives the merchant a copy they can query, back up, or open on a second device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catalog table already has a &lt;code&gt;vector(384)&lt;/code&gt; column. The next step is filling it with embeddings so matching works on meaning ("fudge buns" → cupcakes), not just keywords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Deployment.&lt;/strong&gt; One Dockerfile (multi-stage, standalone Next.js output, non-root user) plus a &lt;code&gt;docker-compose.yml&lt;/code&gt; that points at Ollama on the host, so a merchant can self-host the whole thing. The public demo runs on Vercel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For a side-hustle tool, open weights are the business model, not a nice-to-have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The same model can run anywhere.&lt;/strong&gt; &lt;code&gt;gpt-oss&lt;/code&gt; runs on Groq for the hosted demo, and the same model family runs on a merchant's laptop through Ollama. Switching is an environment variable, not a rewrite. A closed API gives you exactly one place to run it, at that vendor's price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy is a real choice, not a promise.&lt;/strong&gt; Order messages are full of personal data: names, phone numbers, home addresses, and health information like allergies. With the hosted demo, that text goes to an inference provider. With the self-hosted Docker + Ollama setup, it never leaves the merchant's machine. I can only offer that second option because the weights are open. With a closed model, "trust us" is the only privacy policy available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost floor is near zero.&lt;/strong&gt; A baker parsing a couple hundred messages a month shouldn't be paying a per-token bill that grows with their success. Open-weight models on cheap hosted inference (or free local inference) let OrderSift be priced like a small tool, not like a cloud bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No single point of failure.&lt;/strong&gt; When my first hosted model ID disappeared, the fix was to pick another open model from the list. Because multiple providers serve the same open weights, no single provider can deprecate my product out from under me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Best Use of Tiger Data&lt;/strong&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tbtu4ftyasmfm1iobc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tbtu4ftyasmfm1iobc4.png" alt=" " width="800" height="533"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0ec8a4mmhhsrfzdlb0q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0ec8a4mmhhsrfzdlb0q.png" alt=" " width="800" height="545"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1elsbeegz8xobjdfdh2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1elsbeegz8xobjdfdh2.png" alt=" " width="800" height="457"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffu7kamljmjpeqq0xawqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffu7kamljmjpeqq0xawqd.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Comparing AI Models in Cursor: Opus 5.5 vs. Grok 4.7</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Fri, 09 Oct 2026 19:23:21 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/comparing-ai-models-in-cursor-opus-55-vs-grok-47-52e2</link>
      <guid>https://dev.to/shashank_chakraborty_6362/comparing-ai-models-in-cursor-opus-55-vs-grok-47-52e2</guid>
      <description>&lt;p&gt;Objective&lt;/p&gt;

&lt;p&gt;As part of Hacktoberfest, I compared Opus 5.5 and Grok 4.7 in Cursor across three common software development tasks: TypeScript code generation, asynchronous JavaScript debugging, and code explanation.&lt;/p&gt;

&lt;p&gt;Methodology&lt;/p&gt;

&lt;p&gt;I used the same prompt for both models in each experiment and compared their outputs for correctness, readability, reasoning, edge-case handling, and practical usefulness.&lt;/p&gt;

&lt;p&gt;Experiment 1: TypeScript Code Generation&lt;/p&gt;

&lt;p&gt;Task: Reverse a string without using the built-in reverse() method, handle empty input, and provide three test cases.&lt;/p&gt;

&lt;p&gt;Opus 5.5: Used for...of and prepended each Unicode code point to the result. It also explained the limitation involving multi-code-point emoji.&lt;/p&gt;

&lt;p&gt;Grok 4.7: Used a backward index loop and appended characters to the result. The implementation was simple, but UTF-16 indexing can split supplementary Unicode characters.&lt;/p&gt;

&lt;p&gt;Observation: Both solutions met the basic requirements, while Opus paid more attention to Unicode behavior.&lt;/p&gt;

&lt;p&gt;Experiment 2: Asynchronous JavaScript Debugging&lt;/p&gt;

&lt;p&gt;Task: Identify and fix the missing await in a function that fetches users.&lt;/p&gt;

&lt;p&gt;Grok 4.7: Correctly identified the unresolved promise and provided a minimal fix.&lt;/p&gt;

&lt;p&gt;Opus 5.5: Fixed the same issue, added an HTTP response-status check, and demonstrated caller-side error handling.&lt;/p&gt;

&lt;p&gt;Observation: Both models found the main bug. Opus also addressed the fact that fetch() does not automatically reject for HTTP error statuses such as 404 or 500.&lt;/p&gt;

&lt;p&gt;Experiment 3: Code Explanation&lt;/p&gt;

&lt;p&gt;Task: Explain a chained filter() and map() expression, provide an example, analyze complexity, and identify edge cases.&lt;/p&gt;

&lt;p&gt;Grok 4.7: Delivered a focused explanation, correct complexity analysis, and examples of JavaScript type coercion.&lt;/p&gt;

&lt;p&gt;Opus 5.5: Provided a more extensive tutorial, intermediate results, and additional cases involving BigInt, sparse arrays, null, and numeric precision.&lt;/p&gt;

&lt;p&gt;Observation: Both explanations were correct for the intended numeric-array use case. Opus explored a wider range of edge cases, while Grok was more concise.&lt;/p&gt;

&lt;p&gt;Overall Findings&lt;/p&gt;

&lt;p&gt;In these three experiments, both models produced useful solutions and explanations. Opus 5.5 consistently explored more edge cases and provided more detailed reasoning, whereas Grok 4.7 tended toward simpler, more direct responses.&lt;/p&gt;

&lt;p&gt;This was a small, qualitative experiment rather than a standardized benchmark. The results demonstrate how the choice of model can affect the depth and style of AI-assisted development.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Testing the same prompts across models helped me look beyond the first answer and evaluate correctness, assumptions, and robustness. It also reinforced the importance of running tests and reviewing AI-generated code instead of relying on explanations alone.&lt;/p&gt;

&lt;p&gt;Opus 5.5&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgokaubhfzlrhbgue2dbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgokaubhfzlrhbgue2dbv.png" alt=" " width="800" height="687"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ms7fj8c1b8fet5d9g02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ms7fj8c1b8fet5d9g02.png" alt=" " width="800" height="794"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgtvvwgdk0sdobo22wtus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgtvvwgdk0sdobo22wtus.png" alt=" " width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grok 4.7&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmt4p8h47fqjumk5r9kbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmt4p8h47fqjumk5r9kbq.png" alt=" " width="800" height="681"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flb1r8tvrj16tev9ftxgw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flb1r8tvrj16tev9ftxgw.png" alt=" " width="800" height="637"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7i5vstcl6asfrqi23war.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7i5vstcl6asfrqi23war.png" alt=" " width="800" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>TouchGrass AI: I built an outdoor quest app with Ollama and local-first storage</title>
      <dc:creator>Shashank Chakraborty</dc:creator>
      <pubDate>Tue, 06 Oct 2026 21:32:15 +0000</pubDate>
      <link>https://dev.to/shashank_chakraborty_6362/touchgrass-ai-i-built-an-outdoor-quest-app-with-ollama-and-local-first-storage-2c4i</link>
      <guid>https://dev.to/shashank_chakraborty_6362/touchgrass-ai-i-built-an-outdoor-quest-app-with-ollama-and-local-first-storage-2c4i</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  TouchGrass AI: turning an AI prompt into a reason to put my phone away
&lt;/h1&gt;

&lt;p&gt;I built &lt;strong&gt;TouchGrass AI&lt;/strong&gt;, a small outdoor quest companion. You choose how much time you have, what kind of outing sounds good, and how much effort you want. It makes a short quest, then the app gets out of the way so you can go do it.&lt;/p&gt;

&lt;p&gt;The latest test was on my phone: I generated a quest, took a photo for one of its tasks, and the photo stayed available with the saved quest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/Shashank0701-byte/TouchGrass" rel="noopener noreferrer"&gt;TouchGrass AI on GitHub&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;A quest is a handful of simple prompts for a walk, exploring a familiar area, noticing nature, or taking a photo walk. Tasks can ask for photo evidence. If they do, the task stays incomplete until a photo is saved.&lt;/p&gt;

&lt;p&gt;The app keeps quests, progress, and photo evidence in the browser on that device. Photos are stored as blobs in IndexedDB, can be previewed or removed, and never go into the service worker cache. The app shell can load offline after it has been visited, and a saved quest can go with you when the signal does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The UI sends the selected activity, duration, and difficulty to a same-origin server route. That route asks Ollama for structured JSON, then validates the response against the quest rules before showing it. If generation is unavailable or the response is not usable, the route returns a ready-made quest instead.&lt;/p&gt;

&lt;p&gt;For local development I run Ollama on my computer. For the hosted demo I configured Ollama's API with a Gemma model. The API key stays in the server environment; it is not included in browser code.&lt;/p&gt;

&lt;p&gt;The browser stores the quest and its photos locally. In hosted mode, quest-generation settings and the prompt go to Ollama to produce the quest. The app does not ask for or send a location, and the photo evidence is not uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open models matter here
&lt;/h2&gt;

&lt;p&gt;I wanted the AI to help create a personal plan, not become the activity itself. Open models let me keep local inference as a real option, change the model, and inspect the boundary between generation and the rest of the app. The hosted API makes the demo accessible without requiring each visitor to install a model, while the offline part of the experience stays useful after a quest is saved.&lt;/p&gt;

&lt;p&gt;That trade-off is deliberate: generation needs a connection in the hosted setup, but the walk, saved tasks, progress, and photo evidence do not depend on a live AI request. A model can suggest the next small thing to notice; it should not need a permanent place in the user's pocket once the walk starts.&lt;/p&gt;

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

&lt;p&gt;The most important boundary was deciding what belongs on the server and what belongs on the phone. The server needs only the choices required to generate a quest. The browser owns the saved quest and evidence. Keeping those responsibilities separate made offline use and photo privacy easier to reason about.&lt;/p&gt;

&lt;p&gt;I also learned that “works on my laptop” is not the finish line for a camera feature. Testing the deployed app on a phone made the whole loop real: take or choose a photo, save it, see it attached to the task, and continue the quest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build session
&lt;/h2&gt;

&lt;p&gt;A lightly edited transcript of the build: practical decisions, one missing persistence bug, and the moment the quests briefly achieved enlightenment by vanishing into tranquility.&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Codex"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;TouchGrass: when quests vanished into tranquility&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;okay committed and pushed — lets go to the next phase build&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;We moved on to the next phase: keep the quest creation loop useful from first prompt to a saved outing.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;okay generation too and the fallback both working fine but what happens next when i click go back home the quests vanish into tranquility. vanished. not really being saved though ??&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;A peaceful disappearance is charming in poetry and less charming in a quest app. We fixed the home flow so quests are saved locally and show up on the quest shelf after navigation.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;i noticed the icon in this pg is different keep the leaf one i love it&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The leaf stays. The brand mark was made consistent across the app.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;okay whats next and make it&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;We continued with the next phase, adding photo evidence to tasks that ask for it. Photos are saved on-device in IndexedDB, can be reviewed or removed, and are not cached or uploaded.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;okay i guess all done so can we move towards deployment?&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;We prepared Vercel deployment with server-side Ollama credentials, structured quest validation, and a ready-made fallback if generation fails.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;for this one i used ollama's api key and then used a model used in ollama for gemma i believe&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;We updated the hosted setup to call Ollama's API from the server, so the key stays out of browser code. Gemma can be configured as the hosted model.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;worked perfectly in my phone too. the photo clciking is perfect&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The mobile test confirmed the camera/photo-picker flow works, and the evidence remains attached to the saved quest on that device. The phone has now passed its field test; the app may politely ask it to go outside.&lt;/p&gt;

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

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        14 of 14 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/Shashank0701-byte/TouchGrass" rel="noopener noreferrer"&gt;github.com/Shashank0701-byte/TouchGrass&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The hosted demo is deployed on Vercel. &lt;em&gt;(&lt;a href="https://touch-grass-gilt.vercel.app/" rel="noopener noreferrer"&gt;https://touch-grass-gilt.vercel.app/&lt;/a&gt;)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd like to run it locally, clone the repository, install Ollama and the configured model, then follow the README setup steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/okoye_ndidiamaka_5e3b7d30/no-internet-your-web-app-should-still-work-offline-functionality-in-pwas-explained-4egg"&gt;No Internet? Your Web App Should Still Work&lt;/a&gt; helped frame the app-shell versus locally saved data distinction.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/pm_cheng_3f36acecfb9c59f5/image-processing-that-never-leaves-your-device-26l6"&gt;Image processing that never leaves your device&lt;/a&gt; is a useful discussion of making on-device data boundaries observable.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>devchallenge</category>
    </item>
  </channel>
</rss>
