<?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: ninefyi</title>
    <description>The latest articles on DEV Community by ninefyi (@ninefyi).</description>
    <link>https://dev.to/ninefyi</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%2F162444%2F7a46fdde-3721-44ab-a098-43e6e16cfd1c.png</url>
      <title>DEV Community: ninefyi</title>
      <link>https://dev.to/ninefyi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ninefyi"/>
    <language>en</language>
    <item>
      <title>I built a chat digest that checks its own work, then tried to trick it</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:30:03 +0000</pubDate>
      <link>https://dev.to/ninefyi/i-built-a-chat-digest-that-checks-its-own-work-then-tried-to-trick-it-40lk</link>
      <guid>https://dev.to/ninefyi/i-built-a-chat-digest-that-checks-its-own-work-then-tried-to-trick-it-40lk</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my 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;
  
  
  "0 flagged" tells you nothing
&lt;/h2&gt;

&lt;p&gt;I built a tool that reads a group-chat export and writes a short digest: what was decided and what remains an open question. A local model writes the digest. Then a second pass re-reads the chat and checks every line the first pass wrote.&lt;/p&gt;

&lt;p&gt;On my first test run, the second pass flagged nothing on two of the three days. That sounds good. It is also exactly what a checker that cannot find anything would report. A quiet checker is not evidence of a correct digest.&lt;/p&gt;

&lt;p&gt;So I did what I should have done first: I wrote wrong items on purpose and watched whether the checker caught them.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;nightshift&lt;/strong&gt; reads a LINE chat export, hides private details, and works through the chat one day at a time with a local open model (&lt;a href="https://ollama.com/library/gemma3" rel="noopener noreferrer"&gt;Gemma 3 12B&lt;/a&gt; through Ollama). It leaves a Markdown digest of decisions, open questions and news, grouped by day.&lt;/p&gt;

&lt;p&gt;It is built to be slow and careful, not fast:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hide first.&lt;/strong&gt; Before the model sees anything, emails, links, phone numbers, long numbers and voucher-style codes become placeholders, and a list of names I choose is blanked out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resumable.&lt;/strong&gt; Each finished day is saved straight away. Stop the run, let the laptop sleep, and run the same command again; it carries on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No quote, no item. **&lt;/strong&gt; Every line must carry a few words copied from one message. If it's not in that day's chat, drop the line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A second look.&lt;/strong&gt; Each surviving line goes back to the model with the whole day and one question: is this really what the messages say? Any number in the line must also appear in the chat, which needs no model at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You decide. **&lt;/strong&gt; A small review page shows each line beside its quote and what the second pass said. I mark it keep, edit or wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything runs on my own machine. Everything shown here comes from a &lt;strong&gt;fabricated&lt;/strong&gt; Thai group chat about a trip to Chiang Mai, written for the repo. Nobody in it is real.&lt;/p&gt;

&lt;p&gt;I built this in a pair-programming session with Claude Code. It wrote most of the code, the tests and the 22 probe items below; I ran the demo and the probe on my own MacBook, and the demo and probe numbers are what my machine printed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2Fuazy34c54efwb7iigodp.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%2Fuazy34c54efwb7iigodp.png" alt="IMAGE 1, review page totals: the top of the review page after judging all 14 items: " width="797" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3hmmpar6g9jmed02wtx.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%2Fp3hmmpar6g9jmed02wtx.png" alt="the digest: the finished digest headed " width="799" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5yrs868bwp9or44je55p.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%2F5yrs868bwp9or44je55p.png" alt="IMAGE 3, the digest: the finished digest headed " width="800" height="804"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All three come from the fabricated chat. The names in them are made up.&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/ninefyi" rel="noopener noreferrer"&gt;
        ninefyi
      &lt;/a&gt; / &lt;a href="https://github.com/ninefyi/hf26challenge-week3" rel="noopener noreferrer"&gt;
        hf26challenge-week3
      &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;nightshift: an overnight digest of your LINE chat&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Drop a LINE chat export in before bed. A local open model (&lt;a href="https://ollama.com/library/gemma3" rel="nofollow noopener noreferrer"&gt;Gemma 3 12B&lt;/a&gt; through &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;) works through it one day at a time while you sleep, and leaves a Markdown digest of decisions, open questions and news. &lt;strong&gt;Nothing leaves your machine.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19" rel="nofollow"&gt;Hacktoberfest Open-Source AI Challenge: Week 3, "Slow and Steady"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Status: work in progress.&lt;/strong&gt; The code, the review page and the tests are in place; the real night run and its evaluation (see &lt;a href="https://github.com/ninefyi/hf26challenge-week3/EVAL.md" rel="noopener noreferrer"&gt;EVAL.md&lt;/a&gt;) have not been done yet, so there are no accuracy numbers here.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;LINE .txt export ─► parse ─► one chunk per day ─► Gemma 3 12B (Ollama, local)
   ─► items: decision / question / point, each with a quote
   ─► quote check (drop any item whose quote is not in the chat)
   ─► second pass: numbers&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/ninefyi/hf26challenge-week3" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. To try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma3:12b
uv &lt;span class="nb"&gt;sync
&lt;/span&gt;uv run python run.py demo/demo_chat.txt
uv run python app.py        &lt;span class="c"&gt;# review page&lt;/span&gt;
uv run python probe.py      &lt;span class="c"&gt;# the planted-error test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Parsing a real export.&lt;/strong&gt; I wrote the parser for the format I expected, with tabs between fields. On a real LINE export, it found zero messages: the real file separates time, name and text with plain spaces, and names can contain spaces and emoji. The fix infers each name from the file itself: a name is the longest run of leading words that all of that person's lines share. After that, the real export parsed correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redaction, and a leak the tests caught.&lt;/strong&gt; One rule treats the Thai word for "password" as a keyword and redacts the value after it. My first version let that keyword swallow part of a glued code, leaving a few characters visible. A unit test caught it, and I limited the keyword to Thai letters. A second gap: an email-like string without a &lt;code&gt;.com&lt;/code&gt; ending slipped through until I made the ending optional. Redaction is a safety net, not a guarantee. It matches patterns, and a Thai nickname is not a pattern, so names go in a list I write myself, and a &lt;code&gt;--preview&lt;/code&gt; option shows exactly what the model will read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A review page that lost my clicks.&lt;/strong&gt; Each click on keep, edit or wrong rewrites one small file. Gradio runs every click in its own thread, so two quick clicks could both read the old file and the later write would erase the earlier verdict. A unit test that clicked from many threads at once failed every time; a lock around the change and an atomic file write fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resumable by design.&lt;/strong&gt; Each day becomes one small JSON file, written under a temporary name and then renamed, so a crash cannot leave a half-written result that looks finished. A result is reused only if it came from the same text, model and settings. Changing the redaction list, or turning the second pass off, quietly redoes the affected days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second pass.&lt;/strong&gt; Two checks, cheapest first. First, every number in an item must appear in that day's messages. Second, the model gets the whole day plus the item and its quote, and must answer &lt;em&gt;supported&lt;/em&gt;, &lt;em&gt;unsupported&lt;/em&gt; or &lt;em&gt;unclear&lt;/em&gt; with a reason. I told it to be strict: an item is unsupported if it adds a name, date, number or who-did-what the messages do not state; if a question it calls open is answered later that day; or if a decision it reports was never agreed. An unsupported item stays out of the digest unless I put it back, so nothing is deleted behind my back.&lt;/p&gt;

&lt;p&gt;The obvious weakness is that the same model is grading its own work. I did not want to assume the check helps, so I measured it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The demo run.&lt;/strong&gt; On the fabricated chat (20 messages over three days), the model wrote 14 items in total. The quote check dropped none, and the second pass flagged one on the third day. The three days took 39.4, 14.3, and 22.1 seconds, about 76 seconds total on a 16 GB M1 Pro; the first day includes loading the model. The second pass took 41.3 of those 75.8 seconds, more than half. I ran the first day alone, stopped, and ran the command again. It reported &lt;code&gt;done 2, already done 1&lt;/code&gt;: it skipped the finished day and did only the other two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Judging the 14 items.&lt;/strong&gt; I read every item against the chat on the review page: &lt;strong&gt;14 keep, 0 edit, 0 wrong. **&lt;/strong&gt; The one item the second pass flagged, I judged correct, so that was a false alarm: 1 of 14. It missed no wrong item, but there were none to miss, so this run says nothing about what the second pass misses. That is the gap the next test fills. Reading the finished digest again, two lines on the first day sit uneasily together: one lists "who will drive" as unclear, and another refers to "the driver". In the chat, one person says right away that he can drive. I judged both keep and I stand by that, but a reader looking at the digest will see the tension, so I am pointing it out. A stricter reader may call the first line misleading. Two more caveats: the chat is 20 messages I wrote, so it is easy, and the digest items came out mostly in English, although I asked for the chat's language (the quotes stay Thai).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trying to trick it.&lt;/strong&gt; &lt;code&gt;probe.py&lt;/code&gt; feeds the second pass 22 items whose answers I know: 7 correct, and 15 wrong on purpose in seven ways. All of them are made from the fabricated chat. A test checks that every quote really is in the chat, and that each wrong item can only be caught the way its label says.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wrong items flagged&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13 of 15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caught by the number check alone (no model)&lt;/td&gt;
&lt;td&gt;4 of 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caught by the model's re-read&lt;/td&gt;
&lt;td&gt;9 of 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct items wrongly flagged&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 of 7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Set that next to the demo run, where the second pass flagged 1 of 14 correct items. "Zero false alarms" on the probe was a little rosy; both samples are tiny.&lt;/p&gt;

&lt;p&gt;It caught every example I tried of: a number that is not in the chat, a number from the chat used in the wrong place (a deposit and a per-person amount swapped), the wrong person, an invented detail, an overclaim ("everyone paid" when one person had), and a question reported as open that was answered a few lines later. For some of these kinds I only had one example, so that is a hint, not a rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it missed.&lt;/strong&gt; The same kind of error, twice. One item said everyone agreed to split the fuel cost; in the chat, one person asked, and nobody replied. Another said the group agreed to leave at 7 a.m.; in the chat it was a request nobody had answered yet. The second pass called both supported. Its reason for the second one: the quote matches a message in the chat. It checked that the sentence was said. It did not check that anyone agreed. Of three items of this "never actually agreed" kind, it caught one, so it is not that it cannot do it; it does it unreliably. This is also the kind of mistake that makes a digest dangerous, because "we decided X" reads as settled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I did not do.&lt;/strong&gt; I planned to judge at least 20 digest items from a real chat against a bar I wrote down first. I did not run that. The only real chat I have is a work conversation with other people's details, and I wasn't willing to publish results from it. So the accuracy bar in the repo's &lt;code&gt;EVAL.md&lt;/code&gt; (at least 70% correct, at most 10% wrong) is untested on real chats. On the fabricated chat, the same bar would be met: 14 of 14, but that says nothing about real chats, and nothing here is an accuracy rate for them. What I can stand behind is narrower: on 22 planted items, 13 of the 15 wrong ones were flagged, and none of the 7 right ones was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;A chat export is full of other people's messages, which is exactly what you do not send to someone else's server. An open-weight model let me run the whole pipeline on my own laptop with no account and no per-token cost. It also made the careful parts affordable. A second model call for every item would bill on a hosted service; here it is only time, and time is what "slow and steady" is about.&lt;/p&gt;

&lt;p&gt;Local is not an excuse to be careless, though, so the chat is redacted before even my own model reads it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every result above comes from a fabricated chat of 20 messages and 14 items, and from 22 probes written by hand. Neither is a benchmark. The chat produced no wrong items, so it couldn't show what the checks miss; only the probes could.&lt;/li&gt;
&lt;li&gt;The digest items came out mostly in English even though the prompt asked for the language of the chat. I have not fixed that.&lt;/li&gt;
&lt;li&gt;The probes were written after the tool existed, so they test the mistakes we thought of, not what real chats contain.&lt;/li&gt;
&lt;li&gt;The second pass is the same model checking its own work. It missed "agreed" claims twice, and I have not tuned the prompt. Tuning it against these same probes would make the numbers meaningless, so the next step needs new probes it has never seen.&lt;/li&gt;
&lt;li&gt;The number check is strict: it flags a figure the model worked out itself even when the arithmetic is right.&lt;/li&gt;
&lt;li&gt;Redaction catches patterns, not people.&lt;/li&gt;
&lt;li&gt;I have not run this overnight. A small chat finishes in minutes.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Write fresh probes for the "agreed" errors, measure the current prompt on them, then tighten the prompt and measure again, reporting both numbers. Try a second, different model as the checker so it does not share the first one's blind spots. And run the tool on a real chat with the participants' consent, judge at least 20 items, and report against the bar I set before looking.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>python</category>
    </item>
    <item>
      <title>tripkit: Gemma writes the caption, but it guessed the wrong country</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Wed, 14 Oct 2026 11:50:08 +0000</pubDate>
      <link>https://dev.to/ninefyi/tripkit-gemma-writes-the-caption-but-it-guessed-the-wrong-country-1md4</link>
      <guid>https://dev.to/ninefyi/tripkit-gemma-writes-the-caption-but-it-guessed-the-wrong-country-1md4</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;tripkit is a small web page. You give it a photo, and optionally the place and a short note. It gives back a title, the place, a one-sentence caption and at least three hashtags, in Thai by default, ready to paste into your own social account. A local Gemma 3 12B does the writing, so the photo stays on the laptop.&lt;/p&gt;

&lt;p&gt;The theme asks for something someone would pay for. I will say this up front: &lt;strong&gt;I do not know that anyone would.&lt;/strong&gt; It is free to use, I did not ask anyone for a price, and nobody but me has tried it. My guess for a future payer is someone who posts places for income, such as a travel page admin or a homestay owner, but that is a guess and I have not asked one.&lt;/p&gt;

&lt;p&gt;What I can report is the part I tested: what the model gets right and wrong when you ask it to caption a photo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2F71w82ltfmt6pjuv2h0pz.jpg" 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%2F71w82ltfmt6pjuv2h0pz.jpg" alt="The page with a public photo: the AI guess is wrong, the typed place and the post kit are below" width="616" height="1620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the page with a public photo of the Santichaiprakarn Throne Hall in Bangkok. I typed the place and the note "ตอนเย็น" ("in the evening"), and the result is below them. I also pressed the guess button first: it said &lt;strong&gt;พระที่นั่งอรุณราชโอรส&lt;/strong&gt;, a different building, with a warning that it may be wrong, and it did not put that into the Place box. (Photo by Dominic Nelson, &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noopener noreferrer"&gt;CC BY-SA 4.0&lt;/a&gt;, via &lt;a href="https://commons.wikimedia.org/wiki/File:Bangkok_-_Santichaiprakarn_Throne_Hall_at_golden_hour_Jan_2024.jpg" rel="noopener noreferrer"&gt;Wikimedia Commons&lt;/a&gt;, resized; the screenshot is shared under the same license.)&lt;/p&gt;

&lt;p&gt;The one photo I evaluated is a group photo taken at an event, with about fourteen people in it. I am not showing it, because I do not have their consent. This is what the page returned for it, with the Place left empty and a note naming the event, "ถ่ายรูปหมู่ที่งาน Microsoft AI Tour" ("group photo at the Microsoft AI Tour"). My coding assistant wrote that note from the sign in the photo, and I left it unchanged:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;ทีม Microsoft AI Tour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Place&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(empty, with a warning "no Place given")&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caption&lt;/td&gt;
&lt;td&gt;รวมตัวถ่ายรูปหมู่ที่งาน Microsoft AI Tour!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hashtags&lt;/td&gt;
&lt;td&gt;#MicrosoftAITour #ถ่ายรูปหมู่ #AI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I marked it "post as is". Most of it is that note handed back. What the photo added was the word "ทีม" (team), which is the model's assumption about those people, and the tag &lt;code&gt;#AI&lt;/code&gt; is generic.&lt;/p&gt;

&lt;p&gt;The page also has a button, "not sure where this is? let the AI guess". For this photo it answered &lt;strong&gt;Impact Arena, Muang Thong Thani&lt;/strong&gt;, a venue in Thailand, and labelled its confidence "high". The photo was taken at &lt;strong&gt;Singapore Expo&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/ninefyi/hf26challenge-week2" rel="noopener noreferrer"&gt;https://github.com/ninefyi/hf26challenge-week2&lt;/a&gt; (MIT). The page (&lt;code&gt;app.py&lt;/code&gt;), the generator (&lt;code&gt;tripkit/generate.py&lt;/code&gt;), the rules a result must meet (&lt;code&gt;tripkit/validate.py&lt;/code&gt;), the evaluation script (&lt;code&gt;eval.py&lt;/code&gt;) and 29 unit tests. The model runs through Ollama, which is not part of the repo.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;1. The first design was wrong.&lt;/strong&gt; I had one caption box, and I asked Gemma to copy the place name out of it. I wrote a check that the place must appear word for word in the caption, to stop it inventing places. On six public photos from Wikimedia Commons the model returned the &lt;em&gt;whole caption&lt;/em&gt; as the place: "พระที่นั่งสันติไชยปราการ กรุงเทพ ตอนเย็น" ("... Bangkok, in the evening"), and for two photos whose caption named no place at all it returned the description as if it were a place name. The check passed every time, because a sentence is always inside itself. Only one of six was correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Two fields.&lt;/strong&gt; Now there is a Place box and a Note box. The Place on the result is whatever you typed, passed through unchanged, and it is empty with a warning if you typed nothing. The model never writes it. An invented place name can no longer reach the result, by construction and not by hope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The guess is a suggestion, not an answer.&lt;/strong&gt; I still wanted help for photos where you do not remember the place, so there is a separate button. It shows the guess marked "AI guess, may be wrong". It never fills the Place box by itself and never reaches the title, caption or hashtags until you press "use this". On the same six public photos the guess was clearly right once, wrong with the label "high" three times, in the right area once (it said Maya Bay for a photo of Phi Phi Lay; the bay is on that island, but I did not check that the photo shows it), and declined once. The Singapore Expo guess above is a fourth wrong "high". So the page does not show the model's confidence at all. In my runs it was "high" whether the guess was right or wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Smaller things Gemma did.&lt;/strong&gt; Four of six titles came back in English although I asked for Thai. After I told it to write everything in the language I chose, one of six still did. Once it ended a caption with "ค่ะ", a particle that assumes the speaker is a woman. The prompt now forbids it. Every result is checked for at least three hashtags, each starting with &lt;code&gt;#&lt;/code&gt;, and a caption on a single line of at most 120 characters, with up to two retries. No result needed a retry in any of my runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Photos are cleaned before the model sees them.&lt;/strong&gt; Each image is rotated, shrunk to 1024 pixels and re-encoded without EXIF, so GPS data never reaches the model or my evaluation log. (Gradio keeps its own temporary copy of the uploaded original, so this is not a guarantee that the file is never stored on the machine.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Time.&lt;/strong&gt; On a 16 GB M1 Pro, a result takes about 14 seconds and a guess 8 to 19 seconds more. My one-photo run took 33 seconds in total, and the six-photo run averaged 27 seconds per photo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I did not do, and what that means.&lt;/strong&gt; I planned an evaluation of 20 of my own photos, judged by a first user, with a bar I wrote down before running anything: at least 70% accepted without edits and zero invented places. I then cut it to one photo, judged by me, because I wanted to submit early. With one photo the rate can only be 0% or 100%, so the bar was &lt;strong&gt;not tested&lt;/strong&gt;, and my script reports it as "NOT TESTED" rather than "met". I also did not try a Thai-specific model. The one I looked at, iApp's OpenThai 2.0 (27B), has a 17 GB file in the smallest form its page lists, which does not fit this machine's 16 GB. A search for "thai" on Ollama's site turned up no Thai-focused model, and I did not import one myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;Trip photos have faces in them, and often GPS coordinates. Running Gemma on my own machine means none of that goes to a server I do not control, and a result costs nothing, which is the only way a free tool can exist. Being able to read and change what the model is allowed to do mattered too: the Place box, the EXIF stripping, the hashtag rules and the "never show confidence" decision are all things I could decide because nothing was hidden behind an API.&lt;/p&gt;

&lt;p&gt;Where closed might have done better: place recognition. A hosted model with more landmark knowledge may well have known Singapore Expo. I did not test one, so I cannot say.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built this with Claude Code. It wrote a large share of the code and the first drafts of this post, so the post is marked as AI-assisted. I made the product decisions: two fields, the guess as a suggestion, a free tool, and cutting the evaluation to one photo. I am not embedding the session transcript.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;Me. I was the Traveller and the judge for the one photo. I did not ask for a price, so I have no answer to "would they pay", and nothing to report about what a user would ask for next.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 12B does the image reading and the writing, locally, and the post reports where it fails.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>python</category>
    </item>
    <item>
      <title>Voice2Note: I spoke to a ring for four hours, and the phone misheard me</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Sat, 10 Oct 2026 08:27:53 +0000</pubDate>
      <link>https://dev.to/ninefyi/voice2note-i-spoke-to-a-ring-for-four-hours-and-the-phone-misheard-me-24lb</link>
      <guid>https://dev.to/ninefyi/voice2note-i-spoke-to-a-ring-for-four-hours-and-the-phone-misheard-me-24lb</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;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I wore a &lt;a href="https://repebble.com" rel="noopener noreferrer"&gt;Pebble Index 01&lt;/a&gt;, a ring with one button and a microphone, and spent about four hours outside talking to it instead of taking out my phone. Hold the button, say a thing, let go. At home, a local Gemma turns the notes into a short journal, and I check it against my own words before it counts.&lt;/p&gt;

&lt;p&gt;The plan was a hands-free field journal: screen in my pocket, say what I see. I will be straight about the result. I recorded three notes in four hours, and what I said was errands and one remark about the air (I was walking in a public park), not birds and trees. It felt convenient: pressing the record button was very easy. The pipeline works end to end, and what it does to speech is the interesting part, and not all of it is flattering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2Fj43acff8uy9c069r87s0.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%2Fj43acff8uy9c069r87s0.png" alt="Review Screen" width="800" height="684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhml72sj3byy801pl5gx.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%2Fkhml72sj3byy801pl5gx.png" alt="Confirm and export Journal" width="800" height="967"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The three notes, as the phone transcribed them, and what I actually said (I checked each one by listening to the saved audio):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Phone transcript&lt;/th&gt;
&lt;th&gt;What I said&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;08:42&lt;/td&gt;
&lt;td&gt;Next thing I have to return the book at national &lt;strong&gt;rivalry&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Next thing I have to return the book at national &lt;strong&gt;library&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09:41&lt;/td&gt;
&lt;td&gt;This evening I will bring my son to go to &lt;strong&gt;kit&lt;/strong&gt; gym today.&lt;/td&gt;
&lt;td&gt;This evening I will bring my son to go to &lt;strong&gt;kids&lt;/strong&gt; gym today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14:37&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Today&lt;/strong&gt; we have Air pollution outside.&lt;/td&gt;
&lt;td&gt;We have Air pollution outside.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three had an error. By my count, that is 3 wrong words out of 29 (about 10%) and zero notes exactly right.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/ninefyi/hf26challenge-week1" rel="noopener noreferrer"&gt;https://github.com/ninefyi/hf26challenge-week1&lt;/a&gt; (MIT). Everything below is in that repo: the receiver, the mailbox, the pull command, the quote check, the review screen, a stats command and 35 tests. The ring, its app and its on-phone transcription are Pebble's, not mine.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ring ─► Pebble app (transcribes on the phone) ─► webhook ─► Mailbox on Render (web service + Postgres)
   ─► `pull` at home saves the notes on my laptop, then tells the Mailbox to delete them
   ─► Gemma 3 12B via Ollama ─► review screen ─► journal.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. Find out what the ring sends.&lt;/strong&gt; There is no documentation, so I set up a logger and pressed the ring. The Pebble app POSTs &lt;code&gt;multipart/form-data&lt;/code&gt;: an &lt;code&gt;audio&lt;/code&gt; part (m4a, 16 kHz mono), a &lt;code&gt;transcription&lt;/code&gt; made on the phone, and &lt;code&gt;recordedAt&lt;/code&gt; in epoch milliseconds. I keep the audio for every note, as ground truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The phone is not a reliable stenographer.&lt;/strong&gt; My test phrase "Offline test one" arrived as "Off-line tail one". On the real day, one word per note went wrong (table above). The model never hears the audio, only the transcript, so it cannot know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A walk takes you out of your home Wi-Fi.&lt;/strong&gt; My first receiver listened on my laptop, which cannot work when the phone is elsewhere. So I put a small Mailbox on Render's free plan: a web service plus Postgres. It accepts the webhook behind a secret token, and &lt;code&gt;pull&lt;/code&gt; collects notes by hand, saves them locally, and only then deletes them from the Mailbox.&lt;/p&gt;

&lt;p&gt;What I measured: the free service sleeps after 15 minutes. Of the three notes I used, two arrived while the service was asleep, each stored 10 to 14 seconds after the service started waking. So the Pebble app waited or retried long enough. That is two cases, not a guarantee, and I never tested a long stretch away from home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Two things went wrong, and one I still cannot fully explain.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Render stopped picking up my commits. Several never went live until I pressed Manual Deploy, and I only noticed because the log lines were still in the old format. I had also renamed the GitHub repo around then, which may be the reason; I did not find out. I now return the running commit on &lt;code&gt;/health&lt;/code&gt; so a stale deploy is visible.&lt;/li&gt;
&lt;li&gt;My first &lt;code&gt;pull&lt;/code&gt; said 0 notes while the database held six. I never proved why. The most likely cause: I pasted four commands at once, and &lt;code&gt;read -s PULL_TOKEN&lt;/code&gt; swallowed the next line as the token. Run one at a time, it worked and saved all six notes, with their audio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Gemma at home, twice.&lt;/strong&gt; &lt;code&gt;gemma3:12b&lt;/code&gt; through Ollama reads each note and writes a short paragraph. Each observation has to carry a quote that appears verbatim in the note, or it is dropped, which blocks invented facts. I ran the same three notes two ways.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;From the phone's transcript (23 seconds):&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The air pollution was noticeable outside today. Next, I need to return a book at National Rivalry. This evening, I will bring my son to Kit Gym.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;From what I actually said (28 seconds):&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I needed to return a book at the National Library. There was air pollution outside. This evening, I will bring my son to kids gym.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I take from it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It repeated the phone's mistakes and made them look sure.&lt;/strong&gt; "National Rivalry" and "Kit Gym" came out capitalised like proper nouns. The quote check passed them, because it only looks at the transcript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better input fixed the nouns, not the structure.&lt;/strong&gt; Both runs put the air-pollution remark (14:37) ahead of the evening plan (09:41). The model reordered my day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It still colours things.&lt;/strong&gt; "Noticeable" and "I needed" are the model's words, not mine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It did not invent a species, a place or a task&lt;/strong&gt; in either run, which is what the quote check is for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My first version did worse. It wrote "lovely to see" and "I realized", and filed "Hello hello hello" as a sighting. I fixed those with a stricter prompt and a filler filter. That is as far as I got.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. I confirm before it counts.&lt;/strong&gt; The review screen shows my own words, the draft paragraph and an editable table. Untick a row to drop it. The saved journal says "Reviewed and confirmed by me" only after I press the button. I confirmed the journal exactly as Gemma drafted it: 0 of 3 rows changed, 0 sentences rewritten. That means the confirmed version still has the faults I listed above, the reordered day and the "I needed". I chose to publish the unedited version and show the faults, rather than tidy them away. The review screen is where I would have fixed them; I decided not to, and I want that on the record.&lt;/p&gt;

&lt;p&gt;The confirmed journal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I needed to return a book at the National Library. There was air pollution outside. This evening, I will bring my son to kids gym.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;todo:&lt;/strong&gt; returning a book (national library) · &lt;strong&gt;todo:&lt;/strong&gt; kids gym · &lt;strong&gt;sighting:&lt;/strong&gt; air pollution&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;The model that reads my voice notes is an open-weight model running on my own laptop. A hosted model would have heard what I said and, on a real walk, where I said it.&lt;/p&gt;

&lt;p&gt;The honest trade-off: my notes pass through a Mailbox I run on Render before they reach my laptop, and are deleted once pulled. That is weaker than "nothing leaves my machine", and I chose it because the ring cannot reach my laptop when I am out.&lt;/p&gt;

&lt;p&gt;Open also meant I could read the ring's webhook format myself, decide how the model may behave (the quote check), and pick the model size. Cost: Ollama and Render's free plan were free; the ring was already mine.&lt;/p&gt;

&lt;p&gt;Where closed would have been easier: a hosted API would have been quicker and needed no Mailbox. And a better transcriber might not have heard "library" as "rivalry". I did not test one, so I do not know.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built this with Claude Code. It wrote a large share of the code and the first drafts of this post, which is why the post is marked as AI-assisted. I am not embedding the session transcript.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 12B does the extraction and the paragraph, locally, and I have shown where it fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render:&lt;/strong&gt; the Mailbox is a Render web service plus Postgres on the free plan. I have reported what the free plan did to me: sleeping, a database that expires after 30 days, and auto-deploy that stopped working for me.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>iot</category>
    </item>
    <item>
      <title>Img2text for Thai billing using Gemma.</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Sun, 04 Oct 2026 13:42:34 +0000</pubDate>
      <link>https://dev.to/ninefyi/i-taught-a-local-gemma-to-read-a-rental-meter-sheet-1led</link>
      <guid>https://dev.to/ninefyi/i-taught-a-local-gemma-to-read-a-rental-meter-sheet-1led</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;On the 25th of every month, the housekeeper at a rental building I'll call apartment_a walks past 16 rooms with a clipboard, reads the electricity and water meters, and writes the numbers by hand on a printed table: last month's reading, this month's reading, and then, in blue ink, "units = baht".&lt;/p&gt;

&lt;p&gt;That sheet reaches someone close to me as a photo, and she has to turn it into what each room owes for electricity and water. Until now, that meant reading 2 rows of handwriting and doing the sums herself, every month.&lt;/p&gt;

&lt;p&gt;So I built a small app for her. You drop in the photo, a model reads the handwriting, and you get a table to check against the photo. Then you download a CSV with one row per room: electric usage and cost (units × 7 baht), water usage and cost (units × 28 baht), a totals row, and a note on any row that needs a second look.&lt;/p&gt;

&lt;p&gt;Everything runs on one laptop. The model is Gemma 3 12B through Ollama, and the photo never leaves the machine.&lt;/p&gt;

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

&lt;p&gt;A 49-second walk-through: drop in the photo, let Gemma read it (the real wait is about three and a half minutes, sped up here), check the table, fix a wrong digit on purpose and watch the warning appear and clear, then download the CSV.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ninefyi/hacktoberfest2026/raw/main/demo/demo.gif.mp4" rel="noopener noreferrer"&gt;Demo Click Here&lt;/a&gt;&lt;br&gt;
The demo uses a fabricated sheet in the same template. The real sheet is not in the repository.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/ninefyi/hacktoberfest2026" rel="noopener noreferrer"&gt;https://github.com/ninefyi/hacktoberfest2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core is three small files: &lt;code&gt;billsplit/extract.py&lt;/code&gt; (reading the photo), &lt;code&gt;billsplit/calc.py&lt;/code&gt; (usage, cost, and checks) and &lt;code&gt;billsplit/app.py&lt;/code&gt; (the table and download).&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The first attempt failed.&lt;/strong&gt; I gave Gemma the whole photo and asked for the table as JSON. It mixed up the columns, invented digits, and dropped the water half completely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked was cropping.&lt;/strong&gt; The printed template is the same every month, so I describe it once in a small layout file (where the columns and rows sit). The app then cuts the photo into pieces of five rows. Each piece is the room-number column stitched next to either the electric block or the water block. On a piece like that, the model reads cleanly. On my fabricated demo sheet it got all 64 readings right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A tilted photo almost put numbers on the wrong tenant.&lt;/strong&gt; My first version assumed row n of the crop was room n. In the photo, the table drifts, and from room 207 down every row held its neighbour's numbers. A wrong number on the wrong row is the worst thing this app could do, so now the model has to read the printed room label on each row, and values are matched by label, never by position. If a label cannot be found, the row is left empty and flagged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sheet checks itself.&lt;/strong&gt; The housekeeper already writes "units = baht" next to each reading. The app recomputes both from the readings and flags a row when they disagree, when a reading goes down, or when usage looks too high.&lt;/p&gt;

&lt;p&gt;**The honest results on the real sheet. Handwriting is hard. On the real apartment_a sheet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Water: 15 of 16 rooms read correctly. The model didn't find the last room and flagged it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Electric: 9 of 16 rooms were exactly right. Of the 7 wrong, 6 were flagged by the checks above. One was not: the model read 2309 → 2320 as 9309 → 9320, a plausible usage, so nothing looked off.&lt;br&gt;
That last one is why the table is not optional. The model does the typing, she checks against the photo, and the flags tell her where to look first.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Things I tried that did not help.&lt;/strong&gt; I added a second reading pass with a different crop and zoom, hoping to catch the errors where the model disagreed with itself. It caught nothing new, doubled the time, and added false alarms. I left it out. A more promising idea, which I have not built: next month, flag any "previous reading" that does not match last month's "current" reading.&lt;/p&gt;

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

&lt;p&gt;This sheet has tenants' room numbers and how much power and water each household uses. I did not want to upload a monthly stream of that to a cloud service just to avoid typing. An open-weights model that runs on a laptop made that choice unnecessary. It also costs nothing per photo, so it still makes sense for a building with 16 rooms.&lt;/p&gt;

&lt;p&gt;It also let me look inside: when the model failed, I could change the crops, the prompt, and the checks until I understood why, instead of hoping a closed API would improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built this in one day with Claude Code running on my laptop, and it was less "write me an app" than a back-and-forth.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;It started with questions, not code.&lt;/strong&gt;&lt;/em&gt; Before any code, Claude interviewed me about the plan: who the friend was, what the real problem was, which prize to aim for, and how much time I had. The first idea we settled on (explain official paperwork) was wrong, and the questions are what surfaced the real one: the meter sheet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;My samples broke its assumption.&lt;/em&gt;&lt;/strong&gt; It had assumed I would give it photos of meters. When I dropped in three handwritten sheets, it said so and changed the design instead of forcing the old plan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Failing in the open.&lt;/em&gt;&lt;/strong&gt; The first model attempts were bad (the 4B model invented digits; the 12B mixed up columns on a full page). Claude tried crops, a layout file and room-label matching, and told me each time which results it could verify and which it could not. When its "fix" for the drifting rows did not work, it reported that plainly (the model had been copying the room list I gave it) instead of declaring success.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;It scored itself against the photo.&lt;/em&gt;&lt;/strong&gt; I got honest numbers (water 15 of 16 rooms, electric 9 of 16, one error unflagged) because Claude compared the output with my sheet row by row rather than saying "looks good".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;It built the demo.&lt;/em&gt;&lt;/strong&gt; It generated a fabricated sheet in the same template, scored the app against known answers (64 of 64 readings), and recorded the demo video by driving the real app in Chrome.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;I made the decisions.&lt;/em&gt;&lt;/strong&gt; I changed the product three times (payment forms, then Excel, then CSV), and each time the agent re-planned and cleaned out the old code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Gemma: the whole app runs locally on Gemma 3 12 B.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>gemma</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Hacktoberfest 23 Contributor and Maintainer</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Tue, 24 Oct 2023 08:24:18 +0000</pubDate>
      <link>https://dev.to/ninefyi/hacktoberfest-23-contributor-and-maintainer-8bp</link>
      <guid>https://dev.to/ninefyi/hacktoberfest-23-contributor-and-maintainer-8bp</guid>
      <description>&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;Hey there! I'm an old-school developer. I'm an admin of MongoDB Thailand User Group. I joined many Hacktoberfest events. I love to contribute my knowledge when I am available to Thai developers. Please check out my GitHub profile - &lt;a href="https://github.com/ninefyi"&gt;ninefyi&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  As a contributor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Highs and Lows
&lt;/h3&gt;

&lt;p&gt;Highs: This year, it is easy to find Thai open-source repos. Many Thai developers create awesome repos, which I can find in &lt;a href="https://open.source.in.th/"&gt;one&lt;/a&gt; place.&lt;/p&gt;

&lt;p&gt;Lows: It is not easy to manage the time when you intend to contribute something to the communities.&lt;/p&gt;




&lt;h2&gt;
  
  
  As a maintainer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Project
&lt;/h3&gt;

&lt;p&gt;The project is the MongoDB tutorial website for Thai developers because sometimes it is difficult for anyone who can understand some technical content. That is why I created it.&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://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mongodbthailand"&gt;
        mongodbthailand
      &lt;/a&gt; / &lt;a href="https://github.com/mongodbthailand/mongodbthailand.github.io"&gt;
        mongodbthailand.github.io
      &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;h1 id="user-content-mongodbthailandcom-website"&gt;&lt;a class="heading-link" href="https://github.com/mongodbthailand/mongodbthailand.github.io#mongodbthailandcom-website"&gt;mongodbthailand.com Website&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;โปรเจกต์นี้สำหรับเก็บซอร์สโค้ดของเว็บไซต์ &lt;a href="https://mongodbthailand.com" rel="nofollow"&gt;MongoDB Thailand&lt;/a&gt; เว็บไซต์รวบรวมข้อมูลและคำแนะนำการใช้งาน MongoDB
สำหรับคอมมูนิตี้ชาวไทย&lt;/p&gt;
&lt;h2 id="user-content-hacktoberfest-2023"&gt;&lt;a class="heading-link" href="https://github.com/mongodbthailand/mongodbthailand.github.io#hacktoberfest-2023"&gt;Hacktoberfest 2023&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;ปัจจุบันโครงการนี้เปิดรับ MR เพื่อเพิ่มคอนเทนต์ที่น่าสนใจ สามารถติดตามรายละเอียดเพิ่มเติมได้ที่
&lt;a href="https://mongodbthailand.com/hacktoberfest-2023" rel="nofollow"&gt;หน้ารายละเอียดกิจกรรม Hacktoberfest 2023&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="user-content-สำหรับนักพัฒนาและผู้ร่วมแก้ไข"&gt;&lt;a class="heading-link" href="https://github.com/mongodbthailand/mongodbthailand.github.io#%E0%B8%AA%E0%B8%B3%E0%B8%AB%E0%B8%A3%E0%B8%B1%E0%B8%9A%E0%B8%99%E0%B8%B1%E0%B8%81%E0%B8%9E%E0%B8%B1%E0%B8%92%E0%B8%99%E0%B8%B2%E0%B9%81%E0%B8%A5%E0%B8%B0%E0%B8%9C%E0%B8%B9%E0%B9%89%E0%B8%A3%E0%B9%88%E0%B8%A7%E0%B8%A1%E0%B9%81%E0%B8%81%E0%B9%89%E0%B9%84%E0%B8%82"&gt;สำหรับนักพัฒนาและผู้ร่วมแก้ไข&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;เพื่อให้โปรเจกต์นี้มีรูปแบบการย่อหน้าที่ตรงกัน กรุณาลง Extension &lt;a href="https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig" rel="nofollow"&gt;EditorConfig&lt;/a&gt; ในโปรแกรมแก้ไขโค้ดของท่าน&lt;/p&gt;
&lt;h2 id="user-content--project-structure"&gt;&lt;a class="heading-link" href="https://github.com/mongodbthailand/mongodbthailand.github.io#-project-structure"&gt;🚀 Project Structure&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Inside of your Astro + Starlight project, you'll see the following folders and files:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;
├── public/
├── src/
│   ├── assets/
│   ├── content/
│   │   ├── docs/
│   │   └── config.ts
│   └── env.d.ts
├── astro.config.mjs
├── package.json
├── tailwind.config.cjs
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Starlight looks for &lt;code&gt;.md&lt;/code&gt; or &lt;code&gt;.mdx&lt;/code&gt; files in the &lt;code&gt;src/content/docs/&lt;/code&gt; directory. Each file is exposed as a route based on its file name.&lt;/p&gt;
&lt;p&gt;Images can be added to &lt;code&gt;src/assets/&lt;/code&gt; and embedded in Markdown with a relative link.&lt;/p&gt;
&lt;p&gt;Static assets, like favicons, can be placed in the &lt;code&gt;public/&lt;/code&gt; directory.&lt;/p&gt;
&lt;h2 id="user-content--commands"&gt;&lt;a class="heading-link" href="https://github.com/mongodbthailand/mongodbthailand.github.io#-commands"&gt;🧞 Commands&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All commands are run from the root of the project, from a terminal:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Installs dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm run dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starts local dev server at &lt;code&gt;localhost:4321&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm run&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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/mongodbthailand/mongodbthailand.github.io"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Highs and Lows
&lt;/h3&gt;

&lt;p&gt;Highs: I can find many Thai contributors who can help and use MongoDB this year. I hope they enjoy it and make our repo invaluable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Low: I still need more contributors to share their knowledge with other people through the website.
&lt;/h2&gt;

&lt;p&gt;Thank you!!!&lt;/p&gt;

</description>
      <category>hack23maintainer</category>
      <category>hack23contributor</category>
      <category>hacktoberfest23</category>
    </item>
    <item>
      <title>GitHub Actions for MongoDB Atlas App Services</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Tue, 23 May 2023 09:11:59 +0000</pubDate>
      <link>https://dev.to/ninefyi/github-actions-for-mongodb-atlas-app-services-2mhg</link>
      <guid>https://dev.to/ninefyi/github-actions-for-mongodb-atlas-app-services-2mhg</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;I created GitHub Actions for MongoDB Atlas App Services. There are two jobs: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;build Astro to generate static files&lt;/li&gt;
&lt;li&gt;copy static files and push them to MongoDB Atlas App Services.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DIY Deployments&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://frontend-app-hqqpl.mongodbstitch.com"&gt;Click&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;There are two parts: GitHub Codespaces and GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Codespaces
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open GitHub Codespaces link to GitHub&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gmVwBKs1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/246n8iyis5if4cc6bnft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gmVwBKs1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/246n8iyis5if4cc6bnft.png" alt="Setting up GitHub Codespaces" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the terminal inside GitHub Codespaces and run below command&lt;br&gt;
&lt;code&gt;npm create astro@latest -- --template framework-svelte&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update svelte.config.js&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9sJ467eD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kcpz83g187iy1l0h20xt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9sJ467eD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kcpz83g187iy1l0h20xt.png" alt="Update svelte config" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update package.json&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BwQQy-4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxv9vcjdqdir7qp1hib7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BwQQy-4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxv9vcjdqdir7qp1hib7.png" alt="Update package.json" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm run dev&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;you can open browser after run no 5.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ba9c_gLl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s1ejzi0cykq74gmyhc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ba9c_gLl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s1ejzi0cykq74gmyhc0.png" alt="website" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create a GitHub Actions workflow.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c7wOd7bZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovwd27ditk64e5hencnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c7wOd7bZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovwd27ditk64e5hencnp.png" alt="gha" width="800" height="535"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-mdb-atlas-app-service&lt;/span&gt;
&lt;span class="na"&gt;run-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }} deploys an app on the Atlas App Service&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-astro-svelte&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;18'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm i&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./fe&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./fe&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mkdir -p ./data/files&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cp -R ./fe/dist/* ./data/files&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifact&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./data&lt;/span&gt;
  &lt;span class="na"&gt;deploy-to-aas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-astro-svelte&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;18'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install -g mongodb-realm-cli&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mkdir -p build-artifact&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifact&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifact&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;realm-cli login --api-key ${{ secrets.API_KEY }} --private-api-key ${{ secrets.PRIVATE_API_KEY }} -y&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;realm-cli pull --remote ${{ vars.APP_ID }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cp -R ./build-artifact/* ./${{ vars.APP_NAME }}/hosting&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;realm-cli push --remote ${{ vars.APP_ID }} -s -c -y&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./${{ vars.APP_NAME }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After create dev and GitHub Actions on GitHub Codespaces, we will prepare MongoDB Atlas App Services.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Sign-in on &lt;a href="https://cloud.mongodb.com/"&gt;https://cloud.mongodb.com/&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Data services and "Create"&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yjRI0aMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apcpu8y5rjy19gyyrm3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yjRI0aMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apcpu8y5rjy19gyyrm3d.png" alt="mdb1" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a paid MongoDB cluster.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SfzvR2Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wwg5bw5byw2ckjnxhkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SfzvR2Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wwg5bw5byw2ckjnxhkb.png" alt="mdb2" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create an app services&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z9h6ZGlQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xcu6xc6rrbnatm7tdw9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z9h6ZGlQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xcu6xc6rrbnatm7tdw9f.png" alt="mdb3" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter required information and "Create".&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tZ5-gEcX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/po6qyx7hgejyud0j9tzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tZ5-gEcX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/po6qyx7hgejyud0j9tzo.png" alt="mdb4" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable hosting.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JK_IsSA5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r075wecq59vyjjsbqqyw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JK_IsSA5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r075wecq59vyjjsbqqyw.png" alt="mdb5" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy APP ID.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lL0qIjQu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1z4se9u1okom8yeof4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lL0qIjQu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1z4se9u1okom8yeof4u.png" alt="mdb6" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create API KEY for GitHub Actions.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TnvH97FT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25g8qki5pf3nzvgbqknl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TnvH97FT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25g8qki5pf3nzvgbqknl.png" alt="mdb10" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter required information.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fwj-MBDb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2z9zzhrelliu6xbmlduh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fwj-MBDb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2z9zzhrelliu6xbmlduh.png" alt="mdb11" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy and Save the "Public and Private API key"&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rVscIb43--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9eel2n0txnlkrt8inbcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rVscIb43--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9eel2n0txnlkrt8inbcn.png" alt="mdb12" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After prepare MongoDB Atlas App Services, we will create secrets &amp;amp; variables on GitHub settings.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to "New repository variable"&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--afN8hSRb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aysz6pfyvgrq1qnfb7h6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--afN8hSRb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aysz6pfyvgrq1qnfb7h6.png" alt="mdb7" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create APP_NAME variable.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rW8xtNcn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6p5v2skd0wuz2hgqtjo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rW8xtNcn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6p5v2skd0wuz2hgqtjo.png" alt="mdb8" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create APP_ID variable&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fS_gdjvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6efmcm4mytbkz0jhcbd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fS_gdjvp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6efmcm4mytbkz0jhcbd3.png" alt="mdb9" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create APP&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h1gKREdE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0cmj980ewh9489ctfsn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h1gKREdE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0cmj980ewh9489ctfsn.png" alt="mdb13" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to "New repository secret"&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZUsOxius--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eolfgvbeu066losjrtl6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZUsOxius--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eolfgvbeu066losjrtl6.png" alt="gha1" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create API_KEY secret.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UYe4NxIR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/treauk80j8zkhk9am0bm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UYe4NxIR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/treauk80j8zkhk9am0bm.png" alt="gha2" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create PRIVATE_API_KEY secret.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UBCuVzat--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lezigj8f148x3vregtli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UBCuVzat--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lezigj8f148x3vregtli.png" alt="gha3" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run workflow manually for deployment.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JDzCn4pf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5aj0gwe0wjbflr5g3gi3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JDzCn4pf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5aj0gwe0wjbflr5g3gi3.png" alt="gha4" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If workflow runs passed, you can see files in hosting and can see the result on the website.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FMvSMzDJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/la4pcjjvzihkdlja8vza.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FMvSMzDJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/la4pcjjvzihkdlja8vza.png" alt="gh5" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;10.&lt;br&gt;
 Website&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ba9c_gLl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s1ejzi0cykq74gmyhc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ba9c_gLl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s1ejzi0cykq74gmyhc0.png" alt="website" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;This application runs on MongoDB Atlas App Services (Hosting). It is a service for static files. I will show how to use GitHub Actions to create CI/CD pipeline for Atlas App Service deployment. I also demonstrate how to use GitHub Codespaces for testing deployment to run Astro.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ninefyi/astro-gha-mdb-hosting"&gt;GitHub repo&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MIT&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background (What made you decide to build this particular app? What inspired you?)
&lt;/h2&gt;

&lt;p&gt;My inspiration is the less content about MongoDB Atlas App Services and how to use GitHub Codespaces and GitHub Actions to create an automated deployment frontend to MongoDB Atlas App Services.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)
&lt;/h3&gt;

&lt;p&gt;I used GitHub Codespaces for development and GitHub Actions to deploy static files to MongoDB Atlas App Services. I learned how to test my front-end website using GitHub Codespaces. It is a free tier, and I do not need to install any software on my laptop. I learned how to use secrets and variables to hide my key from MongoDB Atlas for GitHub Actions. It looked seamless between GitHub Codespaces and GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/kkoziarski/react-vite-github-codespaces-5529"&gt;Expose port 3000 for GitHub Codespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/docs/atlas/app-services/cli/"&gt;Realm-Cli&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>githubhack23</category>
      <category>atlasappservices</category>
      <category>mongodbatlas</category>
      <category>astro</category>
    </item>
    <item>
      <title>Pulumi Challenge 03</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Sun, 13 Nov 2022 14:56:46 +0000</pubDate>
      <link>https://dev.to/ninefyi/pulumi-challenge-03-4a1o</link>
      <guid>https://dev.to/ninefyi/pulumi-challenge-03-4a1o</guid>
      <description>&lt;p&gt;I did the &lt;a href="https://www.pulumi.com/challenge/deployments/"&gt;Pulumi Challenge 03&lt;/a&gt;, and I published my code on &lt;a href="https://github.com/ninefyi/pulumi-challenge-03"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What did I learn from Pulumi Challenge 03?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is easy to use Pulumi deployment because it will link to GitHub. When you push code to GitHub, Pulumi deployment will automatically build your code.&lt;/li&gt;
&lt;li&gt;I learnt how to use AWS CLI to get a session token from an MFA account using the below command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Display MFA devices
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam list-mfa-devices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a temporary session token
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ws sts get-session-token &lt;span class="nt"&gt;--serial-number&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;SerialNumber] &lt;span class="nt"&gt;--token-code&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Token from MFA device] &lt;span class="nt"&gt;--duration-seconds&lt;/span&gt; 86400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  After I push the code to GitHub, the Pulumi deployment will run immediately.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3qfSWeA2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlquzieqg57mt1g17r7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3qfSWeA2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlquzieqg57mt1g17r7k.png" alt="Pulumi deployment" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SvdRGH1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zstpckmkegsgp54362tg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SvdRGH1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zstpckmkegsgp54362tg.png" alt="Python Lambda" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pulumi is the one tool that I should learn :).&lt;/p&gt;

</description>
      <category>pulumi</category>
      <category>aws</category>
      <category>challenge</category>
    </item>
    <item>
      <title>Pulumi Challenge 01</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Sat, 24 Sep 2022 09:13:03 +0000</pubDate>
      <link>https://dev.to/ninefyi/pulumi-challenge-01-22e</link>
      <guid>https://dev.to/ninefyi/pulumi-challenge-01-22e</guid>
      <description>&lt;p&gt;I attended the &lt;a href="https://www.pulumi.com/challenge/startup-in-a-box/" rel="new"&gt;[Pulumi Challenge 01]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is exciting because I love programming for Infrastructure as Code (IaC) and learning about any services in any cloud platform, such as AWS and Azure GCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I learned from Pulumi challenge 01.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How to create a Pulumi account.&lt;/li&gt;
&lt;li&gt;How to create a new AWS account.&lt;/li&gt;
&lt;li&gt;How to create a new Checkly account.&lt;/li&gt;
&lt;li&gt;How to install Pulumi CLI on my MacBook Pro using:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;pulumi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;How to install AWS CLI on my MacBook Pro using:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;awscli 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;How to create Access Key and Secret from AWS account.&lt;/li&gt;
&lt;li&gt;How to create API Key from Checkly.&lt;/li&gt;
&lt;li&gt;How to find Account ID from Checkly.&lt;/li&gt;
&lt;li&gt;How to link Checkly key to Pulumi by using the below command.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;checkly:apiKey &amp;lt;YOUR API KEY&amp;gt; &lt;span class="nt"&gt;--secret&lt;/span&gt;
pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;checkly:accountId &amp;lt;YOUR ACCOUNT ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;How to set up an AWS profile using:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;How to solve the problem of running the website because there are limits on the country that can display only from these countries which are ("US", "CA", "GB", "DE").&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  You can find the code below inside cdn-website &amp;gt; index.ts
&lt;/h3&gt;

&lt;p&gt;I live in Singapore, so I can't open the website because AWS Cloudfront allows only specific regions. I can fix it by adding my country code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;restrictions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;geoRestriction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;restrictionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;whitelist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  After
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;restrictions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;geoRestriction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;restrictionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;whitelist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can deploy all resources and pulumi projects using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also clean up all resources and projects using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi destroy &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pulumi stack &lt;span class="nb"&gt;rm&lt;/span&gt; &amp;lt;your environment&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please feel free to see my video recording for more information.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/IC9fnZyZKkM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can also find my repo which includes everything you need for this challenge &lt;a href="https://github.com/ninefyi/pulumi-challenge-01"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep learning!!!&lt;/p&gt;

</description>
      <category>pulumi</category>
      <category>aws</category>
      <category>challenge</category>
      <category>startup</category>
    </item>
    <item>
      <title>[Azure Trial Hackathon - SonarQube community branch plugin on AKS]</title>
      <dc:creator>ninefyi</dc:creator>
      <pubDate>Tue, 08 Mar 2022 14:33:14 +0000</pubDate>
      <link>https://dev.to/ninefyi/azure-trial-hackathon-sonarqube-community-branch-plugin-on-aks-2oh3</link>
      <guid>https://dev.to/ninefyi/azure-trial-hackathon-sonarqube-community-branch-plugin-on-aks-2oh3</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;My project is for the Developers who need to deploy the SonarQube which is the code quality tool on the Kubernetes cluster. This tool is useful for the software company to analyze the code before deploying the code on a production/staging environment. It would be helpful for Devs who need to set up like me :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category: Computing Captains
&lt;/h3&gt;

&lt;h3&gt;
  
  
  My Github Repository: &lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ninefyi"&gt;
        ninefyi
      &lt;/a&gt; / &lt;a href="https://github.com/ninefyi/azuretrialhack-2022"&gt;
        azuretrialhack-2022
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      https://dev.to/ninefyi/azure-trial-hackathon-sonarqube-community-branch-plugin-on-aks-4pjb-temp-slug-8928180/edit
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;h1 id="user-content-my-project-is-for-the-microsoft-azure-trial-hackathon-on-devto"&gt;&lt;a class="heading-link" href="https://github.com/ninefyi/azuretrialhack-2022#my-project-is-for-the-microsoft-azure-trial-hackathon-on-devto"&gt;My project is for The Microsoft Azure Trial Hackathon on Dev.to&lt;/a&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/ninefyi/azure-trial-hackathon-sonarqube-community-branch-plugin-on-aks-4pjb-temp-slug-8928180" rel="nofollow"&gt;Dev.to&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mc1arke/sonarqube-community-branch-plugin"&gt;Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" rel="nofollow"&gt;Azure-CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="user-content-instruction"&gt;&lt;a class="heading-link" href="https://github.com/ninefyi/azuretrialhack-2022#instruction"&gt;Instruction&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;az login&lt;/li&gt;
&lt;li&gt;az account set -s "[Subscription name]"&lt;/li&gt;
&lt;li&gt;az group create --name rg-aks-trial --location eastasia&lt;/li&gt;
&lt;li&gt;az network public-ip create -g rg-aks-trial -n pip-aks-trial --dns-name aks-trial --allocation-method Static --sku Standard&lt;/li&gt;
&lt;li&gt;az aks create --resource-group rg-aks-trial --name aks-trial --node-count 3 --node-vm-size Standard_B2s&lt;/li&gt;
&lt;li&gt;az aks install-cli&lt;/li&gt;
&lt;li&gt;az aks get-credentials --name aks-trial -g rg-aks-trial --admin&lt;/li&gt;
&lt;li&gt;az aks show -g rg-aks-trial -n aks-trial --query "identity.principalId" -o tsv&lt;/li&gt;
&lt;li&gt;az account show --query "id" -o tsv&lt;/li&gt;
&lt;li&gt;az role assignment create --assignee "answer_no_8" --role "Network Contributor" --scope subscriptions/answer_no_9/resourceGroups/rg-aks-trial&lt;/li&gt;
&lt;li&gt;az network public-ip list -g rg-aks-trial -o tsv --query "[].ipAddress"&lt;/li&gt;
&lt;li&gt;kubectl config use-context aks-trial-admin&lt;/li&gt;
&lt;li&gt;kubectl create namespace sonarqube&lt;/li&gt;
&lt;li&gt;kubectl apply -f ./aks/postgres&lt;/li&gt;
&lt;li&gt;kubectl apply -f ./aks/sonarqube&lt;/li&gt;
&lt;li&gt;kubectl label namespace sonarqube cert-manager.io/disable-validation=true&lt;/li&gt;
&lt;li&gt;helm repo add ingress-nginx &lt;a href="https://kubernetes.github.io/ingress-nginx" rel="nofollow"&gt;https://kubernetes.github.io/ingress-nginx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;helm repo add jetstack &lt;a href="https://charts.jetstack.io" rel="nofollow"&gt;https://charts.jetstack.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;helm repo update&lt;/li&gt;
&lt;li&gt;helm install nginx-ingress ingress-nginx/ingress-nginx \
--version 4.0.13 \
--namespace sonarqube \…&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/ninefyi/azuretrialhack-2022"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/h3&gt;

&lt;h3&gt;
  
  
  Software deployment on AKS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Website: SonarQube Community Editon&lt;/li&gt;
&lt;li&gt;Database: Postgres&lt;/li&gt;
&lt;li&gt;SSL: Letsencrypt&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Azure resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Azure Kubernetes Services&lt;/li&gt;
&lt;li&gt;Azure Public IP Address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vjHJ1zZR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vjHJ1zZR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/1.png" alt="alt_text" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s-AFWKQ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s-AFWKQ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/2.png" alt="alt_text" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ym1fdTqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ym1fdTqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/3.png" alt="alt_text" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q5Hv87_9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q5Hv87_9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/ninefyi/azuretrialhack-2022/main/screenshots/4.png" alt="alt_text" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DEV usernames: &lt;a href="https://dev.to/ninefyi"&gt;ninefyi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azuretrialhack</category>
      <category>microsoft</category>
      <category>azure</category>
    </item>
  </channel>
</rss>
