<?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: lalit sonawane</title>
    <description>The latest articles on DEV Community by lalit sonawane (@lalit_sonawane_4767fbfb55).</description>
    <link>https://dev.to/lalit_sonawane_4767fbfb55</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%2F3645070%2F04469d15-466b-4a76-aa3b-077ff7df5002.png</url>
      <title>DEV Community: lalit sonawane</title>
      <link>https://dev.to/lalit_sonawane_4767fbfb55</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lalit_sonawane_4767fbfb55"/>
    <language>en</language>
    <item>
      <title>PaperKeep</title>
      <dc:creator>lalit sonawane</dc:creator>
      <pubDate>Wed, 14 Oct 2026 11:50:00 +0000</pubDate>
      <link>https://dev.to/lalit_sonawane_4767fbfb55/paperkeep-4a45</link>
      <guid>https://dev.to/lalit_sonawane_4767fbfb55/paperkeep-4a45</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;Paperkeep is a receipt desk for freelancers and the bookkeepers who close their months.&lt;/p&gt;

&lt;p&gt;You drop a PDF or a photo of an invoice. A small open-weight model, running on the laptop, pulls out the vendor, the date, the invoice number, the tax, and the total. You tag it with a client, fix anything that looks wrong, and export a CSV. Meals default to half deductible. A coworking pass lands in Office. A software subscription lands in Software. Those rules are a chart of accounts you can edit, not a generic "business expense" bucket.&lt;/p&gt;

&lt;p&gt;The person who would pay is a freelance bookkeeper in filing week. They already have the tedious job: a shoebox of client invoices, a spreadsheet, and a hard rule that those invoices do not go to a chatbot. Forty receipts for one client, then another client after lunch. A tool that reads the page locally and hands back a CSV is worth an evening of their rate. A freelancer with one messy month would pay less, once, for the same reason: tax time is a weekend they would rather not spend retyping totals.&lt;/p&gt;

&lt;p&gt;The sample set includes a GST tax invoice on purpose. It has a taxable value, a CGST line, an SGST line, and an invoice total, and every one of those looks like money. The booked total has to be 11,210, and the tax has to be the two GST lines added together, 1,710. That is the actual problem. Not "find a number."&lt;/p&gt;

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

&lt;p&gt;There is no public URL, and that is the product. Paperkeep binds to &lt;code&gt;side-hustle-sigma.vercel.app&lt;/code&gt;. Receipt text is scored in that process. The ledger is a JSON file on the machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sonawanelalit574/side-hustle.git
&lt;span class="nb"&gt;cd &lt;/span&gt;side-hustle
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run setup
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;a href="http://127.0.0.1:5173" rel="noopener noreferrer"&gt;http://127.0.0.1:5173&lt;/a&gt; and choose &lt;strong&gt;File the sample month&lt;/strong&gt;. You should get four receipts: a software subscription in USD, a coworking invoice in INR with two GSTINs, a flight, and a cafe receipt booked at 50%. &lt;strong&gt;Read a sample PDF&lt;/strong&gt; runs the same GST invoice through the PDF text extractor before the model sees it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run setup&lt;/code&gt; downloads the int8 weights once, about 180 MB. After that, scoring a receipt does not need the network. &lt;code&gt;npm run check&lt;/code&gt; asserts vendor, date, tax, total, and category on the four samples.&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/sonawanelalit574" rel="noopener noreferrer"&gt;
        sonawanelalit574
      &lt;/a&gt; / &lt;a href="https://github.com/sonawanelalit574/side-hustle" rel="noopener noreferrer"&gt;
        side-hustle
      &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;Paperkeep&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A receipt desk for freelancers and the bookkeepers who close their months. Drop a PDF or a photo of an invoice. An open-weight model reads the vendor, date, tax, and total on the laptop. You get a ledger, a client tag, and a CSV.&lt;/p&gt;

&lt;p&gt;Nothing in that path is a hosted model API. The extractor is &lt;a href="https://huggingface.co/urchade/gliner_small-v2.1" rel="nofollow noopener noreferrer"&gt;GLiNER small v2.1&lt;/a&gt; (Apache-2.0), running as int8 ONNX through &lt;a href="https://github.com/lmoe/gliner-onnx.js" rel="noopener noreferrer"&gt;&lt;code&gt;@lmoe/gliner-onnx&lt;/code&gt;&lt;/a&gt;. Photos go through Tesseract. PDFs are read locally. The API binds to &lt;code&gt;127.0.0.1&lt;/code&gt;. The ledger is &lt;code&gt;data/ledger.json&lt;/code&gt; on this machine.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run it&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install
npm run setup
npm run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://127.0.0.1:5173" rel="nofollow noopener noreferrer"&gt;http://127.0.0.1:5173&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setup&lt;/code&gt; downloads the int8 weights once (about 180 MB) plus the tokenizer. The default cache is &lt;code&gt;D:\paperkeep-models&lt;/code&gt; when that drive exists, otherwise &lt;code&gt;.models&lt;/code&gt; in this folder. Override with &lt;code&gt;PAPERKEEP_MODEL_DIR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;File the sample month, or drop &lt;code&gt;public/samples/jaipur-yard.pdf&lt;/code&gt;. That one is a GST tax invoice: CGST and SGST should…&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/sonawanelalit574/side-hustle" 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/sonawanelalit574/side-hustle" rel="noopener noreferrer"&gt;https://github.com/sonawanelalit574/side-hustle&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The open model is &lt;a href="https://huggingface.co/urchade/gliner_small-v2.1" rel="noopener noreferrer"&gt;GLiNER small v2.1&lt;/a&gt; (Apache-2.0). It is a zero-shot named-entity model: you hand it a list of labels, and it marks spans. I run the int8 ONNX export from &lt;code&gt;onnx-community/gliner_small-v2.1&lt;/code&gt; through &lt;a href="https://github.com/lmoe/gliner-onnx.js" rel="noopener noreferrer"&gt;&lt;code&gt;@lmoe/gliner-onnx&lt;/code&gt;&lt;/a&gt;, on CPU, with ONNX Runtime. No GPU is required.&lt;/p&gt;

&lt;p&gt;The labels live in &lt;code&gt;server/labels.ts&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;company name&lt;/li&gt;
&lt;li&gt;invoice date&lt;/li&gt;
&lt;li&gt;invoice number&lt;/li&gt;
&lt;li&gt;subtotal amount&lt;/li&gt;
&lt;li&gt;tax amount&lt;/li&gt;
&lt;li&gt;total amount&lt;/li&gt;
&lt;li&gt;currency&lt;/li&gt;
&lt;li&gt;payment method&lt;/li&gt;
&lt;li&gt;gstin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Changing that list retargets extraction. Swapping the ONNX file (&lt;code&gt;PAPERKEEP_ONNX_FILE&lt;/code&gt;, or a different folder via &lt;code&gt;PAPERKEEP_MODEL_DIR&lt;/code&gt;) swaps the checkpoint. A larger GLiNER model can drop in without a new vendor.&lt;/p&gt;

&lt;p&gt;Around the model, the desk is deliberately boring. PDFs are read locally with &lt;a href="https://github.com/unjs/unpdf" rel="noopener noreferrer"&gt;unpdf&lt;/a&gt; (PDF.js). Photos go through &lt;a href="https://github.com/naptha/tesseract.js" rel="noopener noreferrer"&gt;Tesseract.js&lt;/a&gt;. The UI is a small React app. The API is Express on localhost. Filed receipts sit in &lt;code&gt;data/ledger.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;GLiNER proposes spans. A second pass checks the arithmetic: subtotal plus tax should equal the total. If the model's total and the line that literally says "Invoice total" disagree, the line that makes the math work wins, and the receipt is marked so you can see which one was booked. On the GST sample, CGST and SGST are both tax spans, so they are summed instead of treated as two competing totals.&lt;/p&gt;

&lt;p&gt;Categories are not the model's job. Hints in the chart of accounts map "cowork" to Office and "latte" to Meals. You can edit those hints in the desk. They are stored next to the ledger.&lt;/p&gt;

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

&lt;p&gt;A closed document API can read an invoice. It fails this job in three places that decide whether anyone would pay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The document cannot leave.&lt;/strong&gt; A bookkeeper's invoices have client names, amounts, and GSTINs. "We don't train on your data" is still a copy of the file on someone else's server. Paperkeep scores the text in-process. After the weights are on disk, that step does not call out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second hundred receipts have to be free.&lt;/strong&gt; Filing week is not a demo of five uploads. Per-page pricing can move, and a side hustle priced like a bookkeeper's tool cannot absorb a vendor's new rate. Local weights cost the same on receipt one and receipt two hundred. The business works because inference is not a line item.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The niche is a list of labels, not a contract.&lt;/strong&gt; A general invoice prompt is bad at one person's chart of accounts. This coworking space is Office. Meals are half. This client's GSTINs matter. In Paperkeep that is &lt;code&gt;server/labels.ts&lt;/code&gt; and &lt;code&gt;data/categories.json&lt;/code&gt;. GLiNER was built to take labels it was not trained on. I did not have to ask a platform to support GST invoices, and I can point the same desk at a different checkpoint when a bigger open model is worth the disk.&lt;/p&gt;

&lt;p&gt;The part that is actually hard is which number is the total. Subtotal, tax, and amount due all look like money. An open extractor lets me keep the spans, the scores, and the arithmetic check on the machine, and show all three next to the source text. A chat API can do the same reading only if I ship it the document.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Build for friend</title>
      <dc:creator>lalit sonawane</dc:creator>
      <pubDate>Wed, 14 Oct 2026 11:06:08 +0000</pubDate>
      <link>https://dev.to/lalit_sonawane_4767fbfb55/build-for-friend-4kbj</link>
      <guid>https://dev.to/lalit_sonawane_4767fbfb55/build-for-friend-4kbj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I cook for one person. Their allergies are specific, their week is usually a mess, and a normal meal app is a bad place for both of those facts.&lt;br&gt;
Their Table plans five dinners for that one person. I type their name, what they cannot eat, and a note about this week in their words: exam week, nothing heavy after 9, a guest on Friday. An open-weight model reads that note and picks from a kitchen list. A checker on the laptop throws the plan out if a dish does not clear their card.&lt;br&gt;
The card starts empty. The sample profile in the app is labeled as an example, because their allergy list is not something I want in a blog post. Before I cook, I replace the name with theirs.&lt;br&gt;
Coconut is its own switch. It is not treated as a tree nut.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;There is no hosted demo on purpose. A public URL would put their card on a server, which is the thing this project refuses to do.&lt;br&gt;
Clone it and run it on the laptop you cook from:&lt;br&gt;
git clone &lt;a href="https://github.com/sonawanelalit574/Build_for_friend" rel="noopener noreferrer"&gt;https://github.com/sonawanelalit574/Build_for_friend&lt;/a&gt;&lt;br&gt;
cd Build_for_friend&lt;br&gt;
python -m http.server 5173&lt;br&gt;
Open buildforfriend.vercel.app. The first plan downloads the weights into that browser. After that, a new week can be planned with the wifi off. Copy for them, or print the grocery list.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sonawanelalit574/Build_for_friend" rel="noopener noreferrer"&gt;https://github.com/sonawanelalit574/Build_for_friend&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sonawanelalit574" rel="noopener noreferrer"&gt;
        sonawanelalit574
      &lt;/a&gt; / &lt;a href="https://github.com/sonawanelalit574/Build_for_friend" rel="noopener noreferrer"&gt;
        Build_for_friend
      &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;Their Table&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A week of dinners for one person you cook for. An open-weight model runs in the browser and picks the meals. A small checker on the laptop throws the plan out if it is not safe for them.&lt;/p&gt;
&lt;p&gt;Their allergies, the note about their week, and the plan stay in this browser. There is no account and no API key. After the weights download once, a new week can be planned with the wifi off.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Put your person in&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The example card is not a real roommate. Open the app, replace the name, and write this week the way they would say it. The model is there to read that note. The checker is there so a small model cannot talk you into a dish they cannot eat.&lt;/p&gt;
&lt;p&gt;Coconut is its own switch. It is not treated as a tree nut.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run it&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;No install. From this folder:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&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/sonawanelalit574/Build_for_friend" 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;The open model is Gemma 3 270M Instruct, the ONNX build at &lt;code&gt;onnx-community/gemma-3-270m-it-ONNX&lt;/code&gt;, running in the browser through Transformers.js. WebGPU uses the q4f16 weights. If the GPU build comes back empty, the same prompt runs again on the CPU build. Decoding is greedy. Sampling on this small Gemma build can return nothing.&lt;br&gt;
Qwen 2.5 0.5B Instruct is one click away, under Apache 2.0. The model box also takes any other Transformers.js text-generation id, so the checker does not have to change when the weights do.&lt;br&gt;
The model is not allowed to invent a recipe. The kitchen list lives in &lt;code&gt;src/recipes.js&lt;/code&gt;. Before the model speaks, &lt;code&gt;src/guard.js&lt;/code&gt; removes anything that fails their diet, allergens, onion and garlic, cook time, or the foods they asked me not to make. The prompt in &lt;code&gt;src/prompts.js&lt;/code&gt; only includes what survived. The model has to come back with recipe ids and a short note about their week.&lt;br&gt;
Ingredients on the card are copied from the file, not from the model's words. If an id is invented, repeated, or missing, that reply is discarded and the model is asked again, up to three times. The raw prompt and the raw reply stay on the page under "How this plan was made." A grocery list is added up in code, scaled for how many people are eating.&lt;br&gt;
&lt;code&gt;node --test test/plan.test.mjs&lt;/code&gt; covers the filter, the parser, the retry, and the grocery math. It does not download a model.&lt;/p&gt;

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

&lt;p&gt;A closed API would write a smoother note. It would also want their allergy card, and the sentence about why Friday has to be nicer, on someone else's server. That note is the whole point of the plan. It does not need to leave the laptop, and once the weights are cached it costs nothing to make another week.&lt;br&gt;
A closed model is also a sealed box. Here I can read the checker, change the prompt, and swap Gemma for Qwen when I want a different voice. If the model mentions peanuts in the letter, the page says so. The dishes were still checked. I can read the words before I send them.&lt;br&gt;
The small model is worse at prose than a hosted one. That is acceptable, because it is not the source of the ingredients. It only chooses among dishes the checker has already cleared, and I can see when it fails.&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>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Touch grass</title>
      <dc:creator>lalit sonawane</dc:creator>
      <pubDate>Wed, 14 Oct 2026 10:17:03 +0000</pubDate>
      <link>https://dev.to/lalit_sonawane_4767fbfb55/touch-grass-kdj</link>
      <guid>https://dev.to/lalit_sonawane_4767fbfb55/touch-grass-kdj</guid>
      <description>&lt;p&gt;Open touch-grass-murex.vercel.app. On the same Wi-Fi, node server.mjs --lan lets a phone open the card at the door.&lt;/p&gt;

&lt;p&gt;I used it on October 13 for a 25-minute garden card in Phoenix. The card said to plant tomatoes, peppers, lettuce, peas, garlic, and brassicas into cool soil, water deeply, and shade the seedlings. It mentioned palo verde still holding a yellow cast, Gambel’s quail near wash vegetation, and soil finally cool enough to work in the morning. The frost line was 63 days until the average first fall frost, December 15. That is the start of the desert vegetable year, not the end of it.&lt;/p&gt;

&lt;p&gt;Code&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sonawanelalit574" rel="noopener noreferrer"&gt;
        sonawanelalit574
      &lt;/a&gt; / &lt;a href="https://github.com/sonawanelalit574/Touch-Grass" rel="noopener noreferrer"&gt;
        Touch-Grass
      &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;Touch Grass&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A field station for the next half hour. It writes one card — a garden job, a walk, or a sit — and then the phone is supposed to go in a pocket.&lt;/p&gt;
&lt;p&gt;The card is not a feed. You read it at the door. Pocket mode is a clock and one sentence. When you come back, you keep a single line about what you noticed. That line stays in &lt;code&gt;data/journal.json&lt;/code&gt; on this computer.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why open&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The sentence on the card is written by &lt;strong&gt;Qwen2.5 1.5B Instruct&lt;/strong&gt;, an open-weight model (Apache 2.0), running in &lt;strong&gt;llama.cpp&lt;/strong&gt; (MIT) on the laptop. There is no model API in the loop.&lt;/p&gt;
&lt;p&gt;That matters for this, specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It still works with the network unplugged&lt;/strong&gt;, after the one-time download. &lt;code&gt;llama-server&lt;/code&gt; is started with &lt;code&gt;--offline&lt;/code&gt;. The page loads from this machine. Frost math, sunset, and the journal never leave it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Place and&lt;/strong&gt;…&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/sonawanelalit574/Touch-Grass" 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/sonawanelalit574/Touch-Grass" rel="noopener noreferrer"&gt;https://github.com/sonawanelalit574/Touch-Grass&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Qwen weights and the llama.cpp binaries are not in the repo. setup.ps1 downloads them, about 1 GB for the model.&lt;/p&gt;

&lt;p&gt;How I Built It&lt;br&gt;
The sentence on the card is written by Qwen2.5 1.5B Instruct, an open-weight model under Apache 2.0, running in llama.cpp (MIT) on the laptop. server.mjs starts llama-server with --offline, a 4096-token context, and the GPU layers forced off, because this machine’s GPU is a 2 GB GeForce MX330. Inference is CPU-only. A card comes back in roughly 10 to 30 seconds once the weights are warm.&lt;/p&gt;

&lt;p&gt;The model does not invent the season. lib/facts.mjs builds a brief from bundled climatic averages, a daylight calculation, and a month-by-month phenology file for six climates: continental, subtropical, desert, mediterranean, marine, and tropical. The brief says what may be sown, what must not be sown, and which birds, leaves, and soil cues the card is allowed to mention. Mushrooms in those notes are for looking, not eating. Walks stay on public paths.&lt;/p&gt;

&lt;p&gt;Qwen only phrases that brief, and it has to fit a constraint if you wrote one. If the draft is too thin, names the city instead of the job, or turns a walk into a planting list, the station says so and writes the card from the frost calendar instead. The card is labeled either way: “Written on this machine · Qwen2.5 1.5B,” or “Written from the local frost calendar.”&lt;/p&gt;

&lt;p&gt;The voice is prompts/ranger.txt. There is an editor on the page. The next card uses the file you saved. To swap models, drop another .gguf in models/, set TOUCH_GRASS_MODEL, and restart.&lt;/p&gt;

&lt;p&gt;Why Does Open Innovation Matter?&lt;br&gt;
A closed garden chatbot is a bad fit for this, for three concrete reasons.&lt;/p&gt;

&lt;p&gt;It would need the network at the moment you are trying to leave the house. After the weights are on disk, Touch Grass does not. The page, the frost math, the sunset, and the journal all stay on the machine. llama-server is started with --offline. An outing costs nothing.&lt;/p&gt;

&lt;p&gt;It would also want the note. “Balcony pots only, and it is windy” is a small fact about a home. I did not want that, or a list of what I noticed on the walk, sitting on a server I do not control. The journal is a JSON file in the project folder.&lt;/p&gt;

&lt;p&gt;And a general model flattens the calendar. Ask one what to plant in October and it often says wait for spring. That is wrong in Phoenix, where the vegetable year starts as the soil cools, and wrong in Minneapolis, where the average first frost has already passed and the job is garlic if the soil still crumbles, not tomatoes. Those two briefs are calculated in lib/facts.mjs before the model speaks. I can read them, edit the ranger’s voice, and replace the weights. None of that is a setting inside someone else’s prompt.&lt;/p&gt;

&lt;p&gt;My Agent Session&lt;br&gt;
I built this in a Cursor agent session on the laptop. I do not have a DevRelay recording to embed. The repo is the record: the frost checks in scripts/check-facts.mjs, the voice file, and the server that refuses to call out once the model is loaded.&lt;/p&gt;

&lt;p&gt;Prize Categories&lt;br&gt;
I am not entering a partner category. The project runs locally on llama.cpp and Qwen2.5, with no hosted runtime, fine-tune, or partner API.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Second plate</title>
      <dc:creator>lalit sonawane</dc:creator>
      <pubDate>Tue, 13 Oct 2026 06:35:52 +0000</pubDate>
      <link>https://dev.to/lalit_sonawane_4767fbfb55/second-plate-gfn</link>
      <guid>https://dev.to/lalit_sonawane_4767fbfb55/second-plate-gfn</guid>
      <description>&lt;p&gt;I’ll fill the official template from the project as it actually runs, and check the challenge page so the prize section only lists categories this build can enter.&lt;/p&gt;

&lt;p&gt;Paste this into a DEV post and add the tags devchallenge, weekendchallenge, and hf26challenge. The challenge page says that window closed on October 5, so this is the finished write-up, not a live entry.&lt;/p&gt;

&lt;p&gt;This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend&lt;/p&gt;

&lt;p&gt;What I Built&lt;br&gt;
Second Plate is a dinner planner for the person I share a kitchen with. On a heavy week, dinner has to come from whatever is already in the house, reheat the next day, and stay inside a short list of allergies and foods they will not eat. I did not want that list sitting in a chat box on a server I do not control.&lt;/p&gt;

&lt;p&gt;The page holds one person: their name, allergies, foods they will not eat, foods they love, and a note about how they are eating right now. I type what is in the kitchen and how many minutes I have. An open-weight model names one dish and writes the steps. A checker I can read then scans the draft. If the model mentions a forbidden food, or a food that is not in the kitchen, the steps are hidden and it has to try again.&lt;/p&gt;

&lt;p&gt;The repository starts with a sample roommate so the app can be tried before it is handed over: peanuts, shellfish, and lactose, and no cilantro or mushrooms. Peanut oil counts as peanuts. Before I rely on a plan, I replace that profile with the real person. I have not handed it over yet, so I am not going to invent what they said.&lt;/p&gt;

&lt;p&gt;Demo&lt;br&gt;
It runs on the laptop. There is no hosted demo, because the point is that their food rules never leave the machine.&lt;/p&gt;

&lt;p&gt;npm install&lt;br&gt;
npm start&lt;br&gt;
Open &lt;a href="http://127.0.0.1:4317" rel="noopener noreferrer"&gt;http://127.0.0.1:4317&lt;/a&gt;&lt;br&gt;
hosting link:&lt;br&gt;
hackoberfesttest1.vercel.app&lt;/p&gt;

&lt;p&gt;The first plan downloads the model. After the weights are cached, starting the app with SECOND_PLATE_OFFLINE=1 keeps it from fetching them again. A plan on this CPU takes about a minute. The green line under the recipe is the checker, not the model's opinion.&lt;/p&gt;

&lt;p&gt;On the sample profile, "rice fried in peanut oil" is refused. "Lemon tomato eggs with rice" is cleared. From eggs, rice, onion, tomato, lemon, and chili, the model planned Tomato Rice Salad, and the page stamped it as checked on this laptop against peanuts, shellfish, dairy, cilantro, and mushrooms.&lt;/p&gt;

&lt;p&gt;Code&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sonawanelalit574" rel="noopener noreferrer"&gt;
        sonawanelalit574
      &lt;/a&gt; / &lt;a href="https://github.com/sonawanelalit574/Hackoberfest_test1" rel="noopener noreferrer"&gt;
        Hackoberfest_test1
      &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;Second Plate&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A dinner planner for one person you cook for. Their allergies, the foods they won't eat, and the notes about how they eat this month stay in a file on this laptop. &lt;a href="https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct" rel="nofollow noopener noreferrer"&gt;Qwen2.5-1.5B-Instruct&lt;/a&gt;, an open-weight model under Apache 2.0, writes the meal. A checker in &lt;code&gt;server/safety.js&lt;/code&gt; reads the same food list and hides the recipe if the model slips.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run it&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install
npm start&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://127.0.0.1:4317" rel="nofollow noopener noreferrer"&gt;http://127.0.0.1:4317&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The server listens on &lt;code&gt;127.0.0.1&lt;/code&gt; only. The first plan downloads the quantized model (a few hundred megabytes) into &lt;code&gt;.cache&lt;/code&gt;. Later plans use that copy.&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;SECOND_PLATE_OFFLINE&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;1&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
npm start&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;With that set, Transformers.js will not fetch weights again. Planning still works from the cache.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Hand it to someone&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Open the page and replace the sample roommate with the real person: name, allergies, foods they won't eat, foods they love, and how they eat right now.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sonawanelalit574/Hackoberfest_test1" 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/sonawanelalit574/Hackoberfest_test1" rel="noopener noreferrer"&gt;https://github.com/sonawanelalit574/Hackoberfest_test1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How I Built It&lt;br&gt;
The open model is Qwen2.5-1.5B-Instruct, Apache 2.0, quantized to q4 and run on the CPU through Transformers.js and ONNX Runtime. The server listens only on 127.0.0.1. The profile, the cook's instructions, and the meal history are files next to the app.&lt;/p&gt;

&lt;p&gt;The model does not get the last word. server/safety.js expands an allergy into its family: one shellfish blocks prawns and crab, and lactose blocks ghee, paneer, and butter. A won't-eat word stays literal, with a few obvious aliases, so cilantro also blocks dhaniya and fresh coriander, and it leaves coriander powder alone. The same list is shown to the model and enforced in code. Plant milk, eggplant, and the phrase "peanut-free" are tested so the checker does not become silly.&lt;/p&gt;

&lt;p&gt;I tried SmolLM2-360M-Instruct first. It is open and small, and it kept inventing coconut milk and noodles that were not in the kitchen. The runtime takes any Transformers.js text-generation model id, so I switched the default to Qwen2.5-1.5B-Instruct without rewriting the checker. The page also has a box for the cook's job description, which is prompts/cook.txt. The next plan follows whatever is saved there.&lt;/p&gt;

&lt;p&gt;The interface is one page. No account, no CDN, no API key.&lt;/p&gt;

&lt;p&gt;Why Does Open Innovation Matter?&lt;br&gt;
A closed API would have been the wrong default for this kitchen.&lt;/p&gt;

&lt;p&gt;Their allergies and the shape of their week would have left the house with every dinner plan. Here the weights run on this CPU, and the profile stays in a JSON file. After the one-time download, a meal costs electricity, not a request to someone else's server, and there is no usage cap at 11pm.&lt;/p&gt;

&lt;p&gt;I can also change the cook. A hosted chat product would have made the model, the tone, and the failure mode someone else's decision. Because the model is a file and the checker is plain code, I could throw out the small model that invented noodles, keep the same food rules, and put a stronger open model in its place. When I learn a new exception they care about, I can change the checker and the tests, and the next plan uses that rule.&lt;/p&gt;

&lt;p&gt;My Agent Session&lt;br&gt;
I built this with a coding agent in Cursor. I have not saved that session with DevRelay, so there is no agent-session embed yet.&lt;/p&gt;

&lt;p&gt;No prize-category section: the project does not use Render, TabPFN, Tinker, Arduino, DigitalOcean, Gemma, or the other partner tools listed on the challenge page. It is still eligible for the overall prize if the post is accepted.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
