This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
It's the last day of the month. You bill by the hour, you have three clients, and you're rebuilding the week from your browser history, trying to remember whether that hour on Tuesday was Acme's login bug or "just checking something" for Nimbus.
lowkey does that remembering for you, on your own laptop. A small open model reads which windows you had open, decides which client each stretch of time was for, and writes the invoice line. You approve it or you don't. Nothing about your screen leaves the machine.
What I Built
The tools that already fix this want one of two things: your money or your screen.
The money part got real in August. After Harvest was sold to Bending Spoons, one user's bill went from $12 a month to about $116, and a UK consultancy's went from $130 to $2,110 (Silicon UK). The screen part is the automatic trackers: they work by watching everything you do. People don't like being watched, even locally. When Microsoft brought back Recall, Signal and Brave both shipped ways to block it by default.
lowkey is a timesheet that writes itself without either.
- Every 5 seconds it notes which window is in front: the app's name and the window's title. No screenshots, no URLs, no keystrokes.
- Every 5 minutes, Gemma 4 E2B, running on my laptop's GPU through Ollama, reads that slot of titles and returns the client and one line of what got done.
- You review the lines like a feed, bill it or nah, and send a PDF invoice.
There are two faces. The island is a notch that hides in the edge of the screen as a thin line in your current client's colour. Hover and it opens: your client as a little blob creature, the session timer, what you've earned today, and your last hour drawn as a wave.
The dashboard at 127.0.0.1:7777 is your day in colour blocks, one card per work session with its invoice line, a why? on every card that shows the windows behind it, and the invoice.
Who would pay for it? Freelancers and small studios who bill hourly, and most of all the ones under NDA, who can't paste client work into a cloud AI at all. They already pay for time tracking; the Harvest story shows what happens when that price is someone else's decision. lowkey is MIT-licensed and free to run. What I'd sell is the boring part: a one-click installer, a signed Mac build, and invoice templates.
Demo
Try it in your browser, nothing to install: lowkey.nirajlabs.tech. Hit try the live demo and you get the real dashboard on a demo week: review the cards, bill a session, open the invoice.
The 1:39 film, with sound:
Or run the demo week on your own machine. It tracks nothing:
ollama pull gemma4:e2b git clone https://github.com/nirajkhetre/lowkey && cd lowkey pip install -r requirements.txt python -m lowkey --demo That's the whole install: a 4.6 GB model and one pip package (PySide6, for the island). The tracker, the labeller and the dashboard are Python's standard library.
Code
nirajkhetre / lowkey
A Dynamic Island for your laptop that writes your timesheet with a local open model. Nothing leaves your machine.
The 1:39 demo: the island, the local model writing your timesheet, review, and the bill. Sound on. Also on YouTube.
Try it live at lowkey.nirajlabs.tech: the real dashboard, running in your browser on a demo week.
A Dynamic Island for your laptop that writes your timesheet.
It watches what you work on, lets an open model running on your own GPU turn it into invoice-ready lines,
and never sends a byte of your screen activity anywhere.
Demo video · Quick start · What you get · How it works · Privacy · Configuration · Troubleshooting
Why
If you bill by the hour, your timesheet is a guess. You forget to start the timer, you jump between three clients before lunch, and at the end of the month you rebuild the week from browser history.
The tools that fix this want either your money (subscriptions that keep…
How I Built It
foreground window, every 5 s → a JSONL file on disk you can read (idle 5 min+, "go dark", never_track apps: nothing is written) → 5-minute slots → the top 8 windows by time → Gemma 4 E2B on Ollama: {client, task, billable}, client constrained to an enum of your clients → same-client slots merge into a session → a second pass writes one invoice line → plain code: how much of the session your client hints actually back up → you: bill it / nah / move it to another client → lowkey learns a hint → invoice, PDF or CSV ctypes reads the foreground window on Windows, PySide6 paints the island, and the dashboard is one HTML file. The server binds to 127.0.0.1 and refuses pages from anywhere else. Gemma is called through Ollama with a JSON schema, temperature 0 and its thinking turned off.
A small model is a confident witness
I asked Gemma for a confidence score with every label. On a test week of 155 slots, it said 1.0 on 152 of them.
So the number on each card isn't the model's. Plain code computes it: the share of the session's time whose window titles contain that client's hints (repo names, domains, project names). Under 60%, the card says "check me". The model writes the line; code decides how much to trust it.
Thinking was the other surprise. Gemma 4 thinks out loud by default, and my first labelling call took 9.5 s. With thinking off and a schema, a label takes under half a second on an RTX 4050 laptop GPU.
How long should a slot be?
I started with 15-minute slots, because that's how I'd fill in a timesheet by hand. But a slot gets one client, so if you spend 9 minutes on Acme and 6 on Nimbus, all 15 go to Acme. Shorter slots fix that and give the model less to read, so I measured both on a test week I built so that every window's true client is known from its title:
| Slot | Slots | Gemma picks the slot's main client | Minutes billed to the right client | Same, keyword rules | Gemma per label |
|---|---|---|---|---|---|
| 15 min | 155 | 98.1% | 91.7% | 91.3% | 487 ms |
| 10 min | 228 | 95.2% | 91.7% | 91.7% | 459 ms |
| 5 min | 440 | 95.7% | 94.6% | 94.1% | 460 ms |
At 5 minutes Gemma has less context and gets a few more slots wrong, but far less time gets swept into the wrong client, so more of the money lands in the right place. That's the default now; reads every in the settings changes it. Anyone can re-run the table with python -m lowkey.eval.
Two honest notes on that table. At 15 minutes, Gemma's three misses were all slots split almost exactly in half between work and personal: 50/50, 47/53 and 49/51. And look at the rules column: plain keyword matching is nearly as good at every length, because the synthetic titles contain client names. The test week is easy on purpose. The model earns its place on real titles that don't name the client, and in the writing, which rules can't do.
My own days show why this matters. Over 503 active minutes lowkey recorded on my laptop on Oct 16 and 17, across 13 apps, this is how much of my time wasn't the main thing in its slot:
| Slot | My slots | Time that wasn't the slot's main app |
|---|---|---|
| 15 min | 42 | 29.9% |
| 10 min | 63 | 28.2% |
| 5 min | 116 | 21.0% |
Switching apps isn't always switching clients, but whenever it is, a one-label-per-slot timesheet can't be right about all of that time, however good the model is. 5-minute slots cut it by about 30%. I switch apps a lot.
Teaching it to write a day
Above the numbers, the dashboard shows the day in a sentence, written by Gemma from the day's invoice lines, never from raw window titles. My first prompt asked for "how the day went, like a friend who just read it". It filed a report instead:
the most time went to nimbus labs with 130 minutes spent modifying handlers.py and documenting the v2 spec.
A sharper second version asked it to lead with the day's most concrete win, and for one day Gemma handed a line straight back:
11:15, 147 min, Acme Studio: Fixed OAuth redirect in acme-website PR; modified auth.ts & login.tsx.
The third hides the times from it and says "your own words: never copy a line". It mostly works:
you nailed the auth logic and redirect fixes for acme-website. then you spent time previewing products at chai & co and working on nimbus-api tasks.
Mostly. All five test days open with "you nailed", and one of them says only "you nailed the login.", which is half of login.tsx.
Learning from a fix, carefully
When you move a session to the right client, Gemma picks one word from that session's windows that should have told it. Given masala-landing - Figma filed under Chai & Co, it picked masala-landing. Given invoice-gen - main.py - Visual Studio Code under Nimbus Labs, invoice-gen.
Then plain code checks the word before it's learned: it must appear in those windows, can't be generic (chrome, figma, untitled…) and can't already be another client's hint. It needs the check. Given a slot of Reddit, a lofi stream on YouTube and a WhatsApp chat titled "Mom", filed under Chai & Co, Gemma answered Chai & Co, a word that appears in none of those titles. When nothing qualifies, lowkey learns nothing rather than guess. When it does learn, it says "learned: masala-landing means Chai & Co from now on", with undo.
Making the model visible
After a day of using my own build, it felt like a normal app: the model's lines looked exactly like anything I'd typed. So now a small sparkle marks everything Gemma wrote and nothing else. A pill in the header says what it's doing while it does it ("reading 5 min of your windows…"), then how long that took, and the island's edge lights up in the same colours. New lines write themselves into the page. Edit one and its card says your words instead.
A demo with no server
A side hustle needs a try-before-you-install, and a privacy tool can't ask for your window titles to give you one. So lowkey.nirajlabs.tech runs lowkey's own Python inside the browser tab with Pyodide: the same server code as the app, unzipped into the page, answering the dashboard's /api/... calls directly. The site is static, on GitHub Pages, with nowhere to send anything. Your browser can't run Gemma, so the demo replays the lines Gemma 4 already wrote for the demo week, and its header says so: "gemma 4's labels, replayed".
Two bugs worth telling
- My taskbar is at the top. The first island sat on pixel row 0, under a taskbar that is also "always on top". It now hangs from the work area, or optionally re-raises itself above the taskbar.
- YouTube fullscreen. The island is supposed to step aside over fullscreen video, and it didn't, because my check skipped maximized windows and Chrome still reports a window as maximized after it goes fullscreen. The difference turned out to be geometry. Maximized, Chrome's window was
[-9, 31, 1929, 1089]on a1920×1080screen, hanging over the edges by its invisible borders; fullscreen, it's exactly[0, 0, 1920, 1080]. Now a window counts as fullscreen only when it fits the screen exactly.
What the checks can't see
- "% backed" only knows your hints. A Zoom call or an untitled Notepad window can't back anything, so honest work there shows up as "check me".
- Half-and-half slots are a coin flip, for Gemma and for me.
- The day sentence isn't checked by code. It's written from your invoice lines, never the raw windows, but nothing checks it against them, so a wrong word can reach the screen. It wears the sparkle so you know whose words they are.
- lowkey never records URLs, so a browser tab is only as specific as its title.
- Daily use is on Windows 11. The macOS and Linux trackers pass tests against recorded command output, but haven't run on real hardware yet.
Why Does Open Innovation Matter?
For lowkey it isn't a preference. It couldn't be built on a closed API.
- The data is the most private thing on the computer. Window titles are client names, repo names, DM previews and the name of the clinic you booked. Sending them to a cloud model every few minutes, all day, isn't a privacy-policy question. With an open-weight model on my GPU they have nowhere to go, and you don't have to trust me: the code is open and the server only listens on
127.0.0.1. - Always-on has to be free. At 5-minute slots that's 12 slots an hour, and the slot still filling up is re-read every minute: up to about 60 model calls an hour, every working hour. On my own GPU each one costs under half a second and nothing else.
- I can see and steer every step. Ollama lets me force a JSON schema, make the client an enum so the model can't invent one, turn thinking off when it costs 9 seconds, and swap the model with one environment variable (
LOWKEY_MODEL=qwen3:4b). - Nobody can reprice it. The weights are on my disk. No acquisition turns this into a usage-based bill.
Where a closed model would be better: the writing. A frontier model would write a livelier day sentence without opening every day with "you nailed", and it would need fewer guards. It would also need my window titles, a key, a server and a bill.
My First User
Nobody has paid for lowkey yet, and I won't dress up a guess as a customer.
If you bill by the hour, you're who I built it for. Open the live demo, bill a session, open the invoice, and tell me in the comments what it got wrong and what you'd pay for the one-click installer. The first real answers go in this section.
Prize Categories
- Best Use of Gemma: Gemma 4 E2B labels every 5-minute slot, writes each invoice line and the day's sentence, sorts personal time, guesses the client for time you log by hand, and picks what to learn from your fixes, all locally through Ollama with a JSON schema, a client enum and thinking turned off.
- .tech Domain (get.tech): the project lives at lowkey.nirajlabs.tech, on the nirajlabs.tech domain I claimed through Hacktoberfest: a landing page, and a live demo that runs lowkey's real code in the browser.



