<?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: Milan Lazarevic</title>
    <description>The latest articles on DEV Community by Milan Lazarevic (@mrlaki5).</description>
    <link>https://dev.to/mrlaki5</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%2F746352%2Fc1c36600-98c3-4d56-bc99-42ac3c29eaeb.jpeg</url>
      <title>DEV Community: Milan Lazarevic</title>
      <link>https://dev.to/mrlaki5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mrlaki5"/>
    <language>en</language>
    <item>
      <title>far-sight: I Built My Friend a Local AI That Catches Their Cat on the Kitchen Counter</title>
      <dc:creator>Milan Lazarevic</dc:creator>
      <pubDate>Sun, 04 Oct 2026 15:29:16 +0000</pubDate>
      <link>https://dev.to/mrlaki5/far-sight-i-built-my-friend-a-local-ai-that-catches-their-cat-on-the-kitchen-counter-1o44</link>
      <guid>https://dev.to/mrlaki5/far-sight-i-built-my-friend-a-local-ai-that-catches-their-cat-on-the-kitchen-counter-1o44</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;My friend has a cat who believes the kitchen counter is theirs. Every evening my friend comes home to paw prints by the sink and the occasional knocked-over glass, with no idea when it happened or how often. They didn't want a cloud pet cam streaming their apartment to someone else's server, and they didn't want to scrub through hours of footage either.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;far-sight&lt;/strong&gt;, a small Linux program that watches a webcam and keeps asking a local vision-language model one plain-English question: &lt;em&gt;"Does this image show a cat on the kitchen counter?"&lt;/em&gt; When the answer is yes, it saves a photo of that exact moment, with the capture time in the filename and stamped onto the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;far-sight &lt;span class="nt"&gt;-m&lt;/span&gt; SmolVLM-500M-Instruct-Q8_0.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mmproj&lt;/span&gt; mmproj-SmolVLM-500M-Instruct-Q8_0.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"a cat on the kitchen counter"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once it's set up in their kitchen, my friend will come home to a folder of timestamped photos and an &lt;code&gt;events.csv&lt;/code&gt; log that shows when the cat was up there, how often, and the evidence.&lt;/p&gt;
&lt;h3&gt;
  
  
  Not just for cats
&lt;/h3&gt;

&lt;p&gt;Nothing in far-sight is cat-specific. The event is just a sentence passed to &lt;code&gt;-p&lt;/code&gt;, so the same binary covers anything you can describe and point a camera at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Other pets:&lt;/strong&gt; &lt;code&gt;"a dog lying on the couch"&lt;/code&gt;, &lt;code&gt;"a cat scratching the sofa"&lt;/code&gt;. Find out what happens while you're out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Front door and driveway:&lt;/strong&gt; &lt;code&gt;"a package on the doorstep"&lt;/code&gt;, &lt;code&gt;"a car parked in the driveway"&lt;/code&gt;. Get a timestamped record of deliveries and arrivals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Garden and wildlife:&lt;/strong&gt; &lt;code&gt;"a bird at the feeder"&lt;/code&gt;, &lt;code&gt;"a deer in the garden"&lt;/code&gt;. A nature log without watching hours of footage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workshop:&lt;/strong&gt; &lt;code&gt;"a tangled mess of plastic on the 3D printer bed"&lt;/code&gt;. Catch a failed print before it wastes the whole spool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hands-free photos:&lt;/strong&gt; &lt;code&gt;"a person waving at the camera"&lt;/code&gt;. Wave and it takes the picture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Around the house:&lt;/strong&gt; &lt;code&gt;"the garage door is open"&lt;/code&gt;, &lt;code&gt;"someone sitting at my desk"&lt;/code&gt;. Know when something changed, and when.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;--on-match&lt;/code&gt; hook turns a detection into an action. It can send a desktop notification, push the photo to a phone, publish to MQTT for Home Assistant, or run any script you like, so far-sight works as a general "tell me when X happens" building block. Swapping SmolVLM for a bigger open model such as Qwen2.5-VL or Gemma 3 is just a different file, for when a prompt needs more visual understanding than a 500M model has.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I built far-sight over the challenge weekend and haven't had the chance to set it up at my friend's place yet, so I don't have footage of the actual culprit on the actual counter. Instead, I tested it with a stock clip of a cat on a kitchen counter, played into a virtual webcam (&lt;code&gt;v4l2loopback&lt;/code&gt; + &lt;code&gt;ffmpeg&lt;/code&gt;). far-sight can't tell the difference: it opens &lt;code&gt;/dev/video10&lt;/code&gt; exactly like a real camera, so this is the same binary and the same pipeline that will run in my friend's kitchen, just with a different video source. The clip starts on an empty counter, then the cat shows up, walks along the counter and leaves.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;far-sight &lt;span class="nt"&gt;-m&lt;/span&gt; SmolVLM-500M-Instruct-Q8_0.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mmproj&lt;/span&gt; mmproj-SmolVLM-500M-Instruct-Q8_0.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; /dev/video10 &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"a cat on the kitchen counter"&lt;/span&gt; &lt;span class="nt"&gt;--cooldown&lt;/span&gt; 15s &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--on-match&lt;/span&gt; &lt;span class="s1"&gt;'notify-send "far-sight" "Cat on the counter at $FARSIGHT_TIME"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's the log from one pass of the clip, running on a laptop CPU:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2026-10-04 17:06:37.645] camera /dev/video10 opened at 1280x720 YUYV
[2026-10-04 17:06:37.785] watching for "a cat on the kitchen counter" every 1000ms (saving to captures)
[2026-10-04 17:06:37.816] p_yes=0.002 no streak=0 (796 ms)
[2026-10-04 17:06:38.819] static scene (diff 0.29), skipped
[2026-10-04 17:06:39.821] static scene (diff 0.41), skipped
[2026-10-04 17:06:40.785] p_yes=0.987 MATCH streak=1 (786 ms)
[2026-10-04 17:06:41.786] p_yes=0.998 MATCH streak=2 (722 ms)
2026-10-04 17:06:41.786 captures/2026-10-04_17-06-41.786.jpg 0.998
[2026-10-04 17:06:42.787] p_yes=0.997 MATCH streak=1 (719 ms)
...
[2026-10-04 17:06:50.795] p_yes=0.989 MATCH streak=7 (749 ms)
[2026-10-04 17:06:51.796] p_yes=0.002 no streak=0 (765 ms)
[2026-10-04 17:06:52.800] static scene (diff 0.33), skipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The empty counter scores 0.002, and once nothing is moving the model isn't run at all. The cat arrives, two matches in a row fire the event, and the photo is saved within about 0.7 s of each check. The cat keeps posing for another nine seconds, but the cooldown means my friend will get one photo per visit, not ten.&lt;/p&gt;

&lt;p&gt;The saved photo has the capture time burned into the corner:&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%2Fjderc1sj4kxjpgftmqvp.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%2Fjderc1sj4kxjpgftmqvp.jpg" alt="The cat caught on the kitchen counter, with the capture time 2026-10-04 17:06:41.786 stamped in the corner" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;events.csv&lt;/code&gt; keeps the running record of every visit. Here are both passes of the clip:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;captured_at,image,score,prompt
2026-10-04T17:06:41.786+02:00,"2026-10-04_17-06-41.786.jpg",0.998,"a cat on the kitchen counter"
2026-10-04T17:07:01.807+02:00,"2026-10-04_17-07-01.807.jpg",0.998,"a cat on the kitchen counter"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Try it yourself.&lt;/strong&gt; The whole demo is one script in the repo, &lt;a href="https://github.com/MrLaki5/far-sight/tree/main/examples/cat-on-counter" rel="noopener noreferrer"&gt;&lt;code&gt;examples/cat-on-counter&lt;/code&gt;&lt;/a&gt;. It downloads the model and the clip, sets up the video and runs far-sight:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;modprobe v4l2loopback &lt;span class="nv"&gt;video_nr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="nv"&gt;card_label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"far-sight demo"&lt;/span&gt; &lt;span class="nv"&gt;exclusive_caps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
./examples/cat-on-counter/run-demo.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Demo footage: &lt;a href="https://www.pexels.com/video/ginger-cat-walks-on-sunny-kitchen-counter-35674403/" rel="noopener noreferrer"&gt;"Ginger Cat Walks on Sunny Kitchen Counter"&lt;/a&gt; by &lt;a href="https://www.pexels.com/@alexmoliski/" rel="noopener noreferrer"&gt;Alex Moliski&lt;/a&gt; from Pexels, used under the &lt;a href="https://www.pexels.com/license/" rel="noopener noreferrer"&gt;Pexels License&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MrLaki5" rel="noopener noreferrer"&gt;
        MrLaki5
      &lt;/a&gt; / &lt;a href="https://github.com/MrLaki5/far-sight" rel="noopener noreferrer"&gt;
        far-sight
      &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;far-sight&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Watches a webcam and asks a small local vision-language model whether something you describe is happening, such as "a person waving" or "the garage door is open". When it is, far-sight saves a JPEG of that moment with the capture time in the filename and burned into the image, and appends a row to &lt;code&gt;events.csv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is a single C++ binary for Linux. It links llama.cpp (including its &lt;code&gt;libmtmd&lt;/code&gt; multimodal library) statically and captures from the camera through V4L2 directly, so it needs no OpenCV or other runtime dependencies.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/MrLaki5/far-sight/docs/demo-capture.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FMrLaki5%2Ffar-sight%2FHEAD%2Fdocs%2Fdemo-capture.jpg" alt="far-sight catching a cat on the kitchen counter, with the capture time stamped in the corner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Frame from the &lt;a href="https://github.com/MrLaki5/far-sight/examples/cat-on-counter/" rel="noopener noreferrer"&gt;cat-on-counter example&lt;/a&gt;. Footage: &lt;a href="https://www.pexels.com/video/ginger-cat-walks-on-sunny-kitchen-counter-35674403/" rel="nofollow noopener noreferrer"&gt;"Ginger Cat Walks on Sunny Kitchen Counter"&lt;/a&gt; by &lt;a href="https://www.pexels.com/@alexmoliski/" rel="nofollow noopener noreferrer"&gt;Alex Moliski&lt;/a&gt; from Pexels.&lt;/em&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;cmake -S &lt;span class="pl-c1"&gt;.&lt;/span&gt; -B build &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cmake --build build -j       &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; build&lt;/span&gt;
./scripts/download-model.sh                         &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; get SmolVLM-500M (~540 MB)&lt;/span&gt;
./build/far-sight -m models/SmolVLM-500M-Instruct-Q8_0.gguf \
    --mmproj models/mmproj-SmolVLM-500M-Instruct-Q8_0.gguf \
    -p &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;a cat on the kitchen counter&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;               &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; watch /dev/video0&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Photos…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/MrLaki5/far-sight" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/MrLaki5/far-sight#quick-start" rel="noopener noreferrer"&gt;README&lt;/a&gt; has a three-command quick start for a real webcam, and &lt;a href="https://github.com/MrLaki5/far-sight/tree/main/examples/cat-on-counter" rel="noopener noreferrer"&gt;&lt;code&gt;examples/cat-on-counter&lt;/code&gt;&lt;/a&gt; reproduces the demo above without one. The project is Apache-2.0.&lt;/p&gt;

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

&lt;p&gt;The AI at the core is entirely open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model: &lt;a href="https://huggingface.co/HuggingFaceTB/SmolVLM-500M-Instruct" rel="noopener noreferrer"&gt;SmolVLM-500M-Instruct&lt;/a&gt;&lt;/strong&gt;, an open-weight vision-language model (Apache 2.0), in the Q8_0 GGUF build from ggml-org. The model and its vision projector come to about 540 MB, small enough to run on a laptop CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime: &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;&lt;/strong&gt; and its multimodal library &lt;code&gt;libmtmd&lt;/code&gt;, statically linked into a single C++ binary. There's no Python, no server and no OpenCV. CMake fetches llama.cpp at a pinned release tag; the build is CPU by default, and &lt;code&gt;-DGGML_CUDA=ON&lt;/code&gt; or &lt;code&gt;-DGGML_VULKAN=ON&lt;/code&gt; turns on GPU support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each check goes through five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grab the freshest frame.&lt;/strong&gt; far-sight reads the camera through V4L2 directly and decodes MJPEG with stb_image. Frames that piled up while the model was busy are thrown away, and the time comes from the driver's buffer timestamp, so the saved time is when the photo was taken, not when the model finished.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip boring frames.&lt;/strong&gt; A 64×48 grayscale thumbnail is compared with the last frame the model saw. If almost nothing changed, the model doesn't run, so a quiet afternoon in an empty kitchen costs almost no compute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask the model, but read its mind instead of its words.&lt;/strong&gt; Instead of generating an answer and parsing text, far-sight reads the model's logits for the first answer token and computes &lt;code&gt;P(yes) / (P(yes) + P(no))&lt;/code&gt;. That takes one forward pass and gives a confidence score between 0 and 1 that I can put a threshold on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debounce.&lt;/strong&gt; It fires only after N matches in a row and then waits out a cooldown, so one visit to the counter means one photo, not thirty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save the moment.&lt;/strong&gt; The JPEG gets the capture time burned in with a tiny built-in 5×7 pixel font, a row goes into &lt;code&gt;events.csv&lt;/code&gt;, and an optional &lt;code&gt;--on-match&lt;/code&gt; shell hook gets the image path and time as environment variables. That hook can show a desktop notification or push the photo to a phone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On a laptop CPU each check takes about 0.7–0.8 s at 512 px, fast enough to check every second. It runs as a systemd user service, so it starts on login and stays out of the way.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The camera stays in the home.&lt;/strong&gt; This is a webcam pointed at someone's kitchen. With open weights and local inference, no frame ever leaves the machine: there's no account, no upload and no privacy policy to trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's free to run all day.&lt;/strong&gt; Checking once a second is 86,400 checks a day. With a hosted vision API that becomes a monthly bill; locally it's a bit of electricity, and the motion gate skips most of the checks anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could get inside the model.&lt;/strong&gt; The yes/no confidence score comes from reading the raw logits for the "yes" and "no" tokens. That only works because I run the model myself; hosted APIs usually give back text, not the model's full output distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It keeps working.&lt;/strong&gt; There's no rate limit, no API deprecation and no outage on someone else's side. When the Wi-Fi drops, the cat still gets caught. A better open model later, such as Qwen2.5-VL or Gemma 3, is a file swap, not a rewrite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's open all the way down.&lt;/strong&gt; llama.cpp is MIT, SmolVLM is Apache 2.0, stb is public domain, and far-sight itself is Apache 2.0.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I built far-sight in a single session pairing with Claude Code as my coding agent. It read llama.cpp's current &lt;code&gt;libmtmd&lt;/code&gt; headers so the code targets the real API, wrote the C++ and CMake, built it, and tested it against my real webcam, then against a cat clip played through a virtual camera to produce the demo above. The AI inside the product is fully open-source; the coding agent was the one closed piece of the workflow.&lt;/p&gt;

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