You are browsing as a guest. Sign up (or log in) to start making projects!

xerneas

@xerneas

Joined June 2nd, 2026

  • 27Devlogs
  • 7Projects
  • 4Ships
  • 45Votes
Open comments for this post

10h 18m 41s logged

BIG BROWSER DOESN’T WANT YOU TO KNOW THIS :yay:

real talk:

every terminal web browser you’ve heard of is faking it :miku-spin: . browsh, carbonyl, all of them, they boot up a full headless chrome in the background and just screenshot it into your terminal. it’s chrome wearing a terminal costume.

so i’m building the real thing. you type a URL, and my code fetches the page, parses the HTML, and lays it out as text in your shell, with a custom engine no chromium. no webkit. no html library doing the hard part. it’s called newt.

and it already works?? :dance_catgirl: (kinda) it’s reading the wikipedia article on salamanders right now: headings get a rule under them, paragraphs wrap to my terminal, links and refs work.

my biggest bug so far: whitespace (grrr). my first render had a space before every comma and printed refs as “[ 2 ]” instead of “[2]”. turns out a link and the comma after it are separate chunks of text in HTML, and i was gluing them back with spaces that were never there. the fix is genuinely the hard part of a browser: layout has to remember whether real whitespace existed between two chunks. so now every word knows if it has a space in front of it, and it only wraps at real spaces (also why “lizard-like” doesn’t snap in half). So dumb

next:

teaching it to read CSS so pages look like themselves. no color, clicking links, or scroll yet. This is gonna be amazing

1
0
37
Open comments for this post

10h 34m 34s logged

hackfetch COLLAB?? :yay:

so my last two devlogs teased two features. shipping both today.

-leaderboard

last devlog i said “compete with your friends on hackatime hours is coming soon.” i thought i was gonna have to build a whole nest server for it. turns out /api/v1/leaderboard/daily and /api/v1/leaderboard/weekly already exist and are public on hackatime. just a single call!! :dance_catgirl:

so hackfetch -leaderboard daily (or weekly) now shows the top 10:

✦ daily leaderboard Last 24 hours

🥇  1. soaphia14                11h 12m
🥈  2. AnastKara               10h 32m
🥉  3. RoSerMar                10h 6m
      4. euledone                 10h
      5. TheShovel               10h
      6. Dylhouse                 9h 1m
      7. Dexrn                         8h 59m
      8. HexFud                     8h 31m
      9. zeddash                   8h 29m
     10. Trtotomotoro        8h 26m

every username is an OSC 8 clickable link to that person’s hackatime profile. modern terminals (iterm2, kitty, wezterm, alacritty, gnome-terminal) render it as an actual hyperlink you can click. terminals without OSC 8 just show the plain text so it doesnt look broken.

if you’re on the board it highlights your row in orange and adds (you) next to your name.

json addon

the other thing. someone else competing in stardance messaged me the other day about integrating hackatime into their own project and needed help figuring out the api (YAY). helping them made me realize hackfetch is doing a bunch of work (auth, caching, streak reconstruction, per-day bucketing) that other tools would love to piggyback on. so now:

$ hackfetch -json
{
  "version": "2",
  "fetched_at": "2026-07-25T17:00: 20.900829+05:30",
  "system": {
    "user": "xerneas",
    "host": "Macbook-Pro",
    "os": "macOS 26.5.2",
    ...
  },
  "hackatime": {
    "slack": "@U0B8FHHRQPJ",
    "streak": 12,
    "today_seconds": 15820,
    "week_seconds": 237501,
    "top_project": { "name": "hackfetch", "
    "top_language": { "name": "Go", "seconds": 97418 },
    "week_chart": [36967, 31651, 33841, 284
    "machines": 3
  }
}

week_chart is 7 numbers = daily seconds oldest to newest same as the sparkline. top_language skips the unknown/other buckets. everything lines up with what the normal fetch would show, just parseable. :miku-spin:

ship

landed in v2.1.0.

brew update && brew upgrade hackfetch

or arch:

yay -Syu hackfetch-bin

then try:

hackfetch -leaderboard weekly
hackfetch -json | jq

For a general install use:
curl -fsSL https://raw.githubusercontent.com/xerneas3318/hackfetch/main/install.sh | sh

3
0
26
Ship

HACKFETCH V2.0

it's been a few weeks since ship #1 and hackfetch is a completely different tool now. faster, smaller, quieter, actually correct, and now lives in places besides your terminal window. v2.0.0 is out.

what changed:

- fits in your tmux status bar with one line of config. same story for lualine. hackfetch -status prints a compact one-liner meant to be shelled out to every minute.
- fits everywhere else too. now on the AUR (yay -S hackfetch-bin) alongside the existing brew tap, curl installer, powershell installer, and go install. seven install channels total.
- runs in ~20ms on repeat calls thanks to an on-disk cache. tmux polling every minute is basically free now. was ~700ms cold before.
- runs in ~0.7s cold thanks to a parallel API fetch. was ~1.7s before.
- shows a smooth truecolor gradient on any modern terminal. auto-detects $COLORTERM. falls back to 256-color on older ones.
- -doctor walks a colored checklist of your setup and tells you exactly what's broken. green if fine, orange if not, non-zero exit if you wanna script it.
- new pizza logo because hack club runs on pizza. seven logos total.
- streak is finally correct after silently saying "1 day" for a month. /summaries is gone from hackatime so hackfetch now reconstructs streak from every coding session in your history via /heartbeats/spans. real streak: 9 days for me right now.
- 7-day sparkline baked into the fetch (with -v) and available as -status --sparkline for the tmux bar.
- default fetch got leaner. slack handle, machines count, and the sparkline moved behind -v so the normal fetch stays focused on hours + project + language.
- loading spinner on the api fetch so you know something is happening. and then the cache made it show up almost never.

one install:

Linux / Mac:
curl -fsSL https://raw.githubusercontent.com/xerneas3318/hackfetch/main/install.sh | sh

Windows:
irm https://raw.githubusercontent.com/xerneas3318/hackfetch/main/install.ps1 | iex

Arch family:
yay -S hackfetch-bin

macOS / Linuxbrew:
brew install xerneas3318/tap/hackfetch

For more install methods check out the github page:
https://github.com/xerneas3318/hackfetch

teasing the next big thing: hackfetch -leaderboard is coming next, using the public leaderboard endpoints i found in hackatime's routes file. compete with friends on daily and weekly hours right from your terminal. private friend groups on a hackclub nest box after that.

if you try it, thanks. send me some feedback

  • 7 devlogs
  • 61h
Try project → See source code →
Open comments for this post

8h 38m 34s logged

my streak was lying to me for a month :dance_catgirl:

went to add a simple 7-day sparkline. by the end of the night i had rewritten how hackfetch talks to hackatime and my streak field is finally accurate for the first time in weeks.

the plan was get the status from the last 7 days. draw ▁▂▃▄▅▆▇█ bars :miku-spin:

except the endpoint i needed straight up 404s. /users/current/summaries doesnt exist on the current hackatime deployment.

Which means every single time i ran hackfetch it was quietly making a 30-day range call that always failed, then falling back to a placeholder that said “if today has activity, streak = 1”. thats it. no matter how long ive actually been on a streak, the fetch would just show 1 day. 🤦

my streak has said 1 day for the last 2 weeks even though ive been coding literally every day. never noticed because 1 day looks kinda plausible (i added 1 day as a fallback so that it didn’t show an error a couple weeks ago and forgot :shrug-1-67: )

the fix:

went digging in hackatimes rails routes on github. turns out theres a whole /api/v1/ namespace i was never using. and one of the endpoints is /api/v1/users/:id/heartbeats/spans. one call, ~150kb of json, every coding session ive ever logged with start_time, end_time, duration. 2000+ spans.

which is exactly what i need for both features. bucket the timestamps by local calendar day, count unique days for streak, sum durations per day for the sparkline. one request covers both. and while i was already in the api layer i noticed fetchUser was making a whole separate call for my slack handle, which is silly because that data is already in the weekly stats response we cache. so i piggybacked it. weve gone from 4 api calls per fetch to 3.

the sparkline:

7-day chart ▄▄▇▅▄▅▃ 65h 28m

oldest on the left, newest on the right. that tall bar in the middle is a big lock-in session from earlier in the week. the two shorter ones on the right are today (still coding) and yesterday (took a break).

opt in for -status --sparkline if you want it in your tmux bar too:

2h 9m today · streak 9 · ▄▄▇▅▄▅▃ 65h 28m

teasing a backend:

I’m planning on building a whole leaderboard server on a free hack club nest box so people can compete on their hackatime hours. while i was poking around the routes file for the streak fix i noticed something.

/api/v1/leaderboard/daily and /api/v1/leaderboard/weekly already exist. public. no auth needed. no server needed. no cron sync. hackatime literally already has it directly.

so hackfetch -leaderboard is coming soon (private friend groups and personal competitions can come later)

last devlog before my next ship

after the ship the devlogs are going to be huge.

stay tuned :P

0
0
35
Open comments for this post

10h 27m 16s logged

modern-lm: modernizing the architecture (RMSNorm, RoPE, SwiGLU)

Starting from my GPT-2 124M reproduction and dragging the architecture from 2019 to 2026, one component at a time. The plan is a ladder: swap one thing, understand exactly what it does, keep going. This devlog covers the three architecture swaps.

What changed:

  • LayerNorm to RMSNorm, and dropped every bias. RMSNorm skips the mean-subtraction and only rescales by root-mean-square times a learned gain. The residual stream’s problem is magnitude drift, not offset, so re-centering was doing nothing. Half the ops, one fewer param vector per norm, and it sets up Muon later (which wants a model that is purely matrices plus a few gains).
  • Learned position embeddings to RoPE. Deleted the wpe table entirely. Instead of adding a position vector, RoPE rotates the query and key feature pairs by a position-dependent angle, so attention scores end up depending on the relative distance between tokens, not their absolute index.
  • GELU MLP to SwiGLU. Replaced the Linear to GELU to Linear block with a gated FFN: one branch computes features, a second branch (through SiLU) decides how much of each passes, then they multiply and project down. Went with 4x width, so it’s a bigger model, not a param-matched swap.

Deliberate: I care more about a stronger model here than a clean ablation, and I can spend the compute.

What was actually hard:

The swaps are each ~20 to 40 lines. The trap was RoPE. It’s the classic silent bug: get the pairing convention wrong between the cos/sin cache and the apply function and the model still trains fine, it just quietly underperforms and you never find out. So I wrote a unit test instead of trusting the loss curve: shift the whole sequence by k positions and assert the attention scores between the same token pairs are unchanged. Rotation invariance either holds or it doesn’t (like an odometer).

Next

  • QK-norm, untied embeddings + zero-init prWSD schedule.
  • Then the actual point: benchmark the modernized model against the untouched GPT-2 baseline (Row 0) at a fixed
    token budget on FineWeb-Edu, and see which
  • Then scale the frozen recipe to ~500M.
0
0
24
Open comments for this post

9h 46m logged

caching or i owe hackclub money :dance_catgirl:

so heres what my day looked like: nvim was calling hackfetch -status on every redraw for my lualine. tmux was calling it every 60 seconds for the status bar. i had 3 tmux sessions open. and every time i actually opened a new terminal, hackfetch fired again on shell start.

quick napkin math: nvim redraws are basically continuous when im typing, tmux was hitting the api 3x/minute across sessions, plus every terminal open = another fetch. we’re talking hundreds of calls to hackatime in a normal work session. from ONE user.
me :menhera-point:

hackatime is a free hack club service and i was starting to feel bad lol. :mikustare:

the fix: cache the response
~/.cache/hackfetch/last.json stores the last snapshot with a timestamp. every fetch checks that file first. if its fresh enough, skip the network entirely and just render from disk.

TTL is different depending on the caller:
hackfetch (one-shot): 30 seconds. long enough that a normal re-run inside a devlog attempt is instant, short enough that you dont get badly stale numbers.
hackfetch -status: 60 seconds
tmux polls roughly this often anyway so its basically free now.

hackfetch -watch: ignores the cache, always fresh (thats the whole point of watch mode).

opt out with -no-cache or HACKFETCH_CACHE_TTL=0 if you want. also HACKFETCH_CACHE_TTL=300 to be even more polite.

So yea tmux is now basically free. :agadance:

but the real win writing this made me realize the cache does more than save milliseconds:

  • every fetch was a full TLS handshake, wifi radio spin up, etc. now most fetches touch the disk once and nothing else. macbook fan appreciates it.

  • offline. if your wifi drops mid coding session you keep working from the cache instead of showing a spinner forever. tmux bar just keeps saying what it said 40 seconds ago.

  • battery again but for the hackatime server. hackfetch stops asking the api 5-10x more than they need to. multiply by everyone who has it in their tmux config and thats a real thing.

  • atomic write via .tmp + rename so nothing ever reads a half-written file. cache is user-only (0600), because it does contain your top project name.

bonus: -doctor :agasp:

while i was fixing the polite-to-the-api stuff i added -doctor, which walks a checklist and tells you whats broken:

hackfetch doctor
✓ wakatime config exists       /Users/xerneas/.wakatime.cfg
✓ api_key present              loaded from config
✓ wakatime-cli found           /Users/xerneas/.wakatime/wakatime-cli
✓ hackatime api reachable      today: 28m
✓ terminal supports truecolor  $COLORTERM=truecolor
✓ browser opener available     /usr/bin/open

green if its working, orange if its not, and non-zero exit if anything failed so you can script it. this was going to be a small self-diagnosis feature and it caught a real bug in the api client on the very first run i did while writing it lol (a string field where hackatime returns an int, so one of the fields was silently missing from every fetch since v1.0). fixed that too.

ship :miku-spin:

both landed in v1.10.0. update as usual:

brew update
brew upgrade hackfetch

or arch:

yay -Syu hackfetch-bin

then run hackfetch -doctor first, and if anything is broken it’ll tell you exactly what.

2
0
85
Open comments for this post

8h 25m 29s logged

hackfetch is now TMUX + NVIM COMPATIBLE :party-11ty:

new thing today: hackfetch will just show up wherever you already look. tmux status bar. nvim statusline. any tool that can shell out to a command and put text on a screen.

The addition was simple:

hackfetch -status
prints one line:

2h 11m today · hackfetch (45m) · streak 12

just the numbers, for tools that already have their own rendering. hackfetch is a data source now, not just a display.

in tmux

two lines in ~/.tmux.conf:

set -g status-interval 60

thats it. every tmux session, every window, refreshing once a minute. i dropped it in a pink Catppuccin capsule to the left of my existing date capsule and now i cant stop glancing at it lol.

in neovim (lualine)

same story, five lines of lua:

  sections = {
    lualine_z = {
      function()
        return vim.fn.system("hackfetch -status"):gsub("\n", "")
      end
    }
  }
}

shells out on every redraw. works with airline too, or any custom statusline that lets you plug in a function.

the fun part

making hackfetch call safe for “something tmux runs every 60s forever” meant fixing three assumptions the normal fetch was making. this was way harder than it should have been ngl.

setup cant block. if you dont have ~/.wakatime.cfg, normal hackfetch throws you into an interactive setup wizard (ask me how i know). in status mode that would freeze your tmux bar until you notice and Ctrl+C it. so status mode is fully non interactive now.

no config → print nothing → tmux shows nothing → no deadlock 🙏

api errors cant leak into your bar. hackatime offline? old behavior would dump HTTP 500 timeout into your status area which looks INSANE. now status mode exits silently instead so your bar stays clean

every call has to be cheap. hackfetch’s parallel fetch alr seconds, and the shared HTTP client keeps connections alive across the 4 API calls it fires, so tmux polling every 60s is easy on the API. a proper local file cache is on the list for later but honestly not
needed for now (i’m lazy)

ship

landed in v1.9.0. if you already have it:

brew update
brew upgrade hackfetch

or arch family:

yay -Syu hackfetch-bin

(for more installs for ur specific os check out the github pls)

then drop the tmux or lualine snippet above into your dotfiles and its just there in every terminal. no plugin. no config. no daemon. just a flag.

0
0
42
Open comments for this post

8h 43m 39s logged

Hackfetch Gets Real Colors

New thing today: hackfetch now emits 24-bit color when your terminal supports it, and spreads gradients smoothly across each row instead of cycling through the palette one character at a time.

Before and after

Old behavior: for a per-char scheme like rainbow, each character got exactly one palette entry, wrapping around every N characters. On a 30-column row with a 3-color palette, that looked like ten cycles of red, green, blue, red, green, blue… basically RGB static.

New behavior: the palette spreads across the row once, and characters between palette entries get interpolated in-between colors. Same 3-color palette on a 30-column row now shows red, olive, green, teal, blue, magenta, back to red.

Why the rebuild

Terminals speak two color modes:

  • 256-color (\x1b[38;5;Nm): pick from a fixed palette of 256 preset RGB values.
  • 24-bit / truecolor (\x1b[38;2;R;G;Bm): specify any of ~16 million RGB values directly.

The old code always used 256-color. That meant every character had to snap to one of the palette entries. No in-betweens possible.

The new code checks $COLORTERM (set to truecolor by iTerm2, kitty, WezTerm, Alacritty, GNOME Terminal, and most modern terminals) and emits 24-bit escapes when supported. When it’s not, it falls back to the old cycled-palette behavior automatically.

You can force it either way with HACKFETCH_TRUECOLOR=1 or =0.

Where you’ll notice it

The built-in rainbow scheme has 23 palette stops, so the difference on that one is small. You just see a smoother color distribution and less banding.

The change is dramatic on:

  • custom themes with few stops (try 3 colors in your ~/.config/hackfetch/colors.json for the wildest gradient)
  • the stardance palette (11 stops, per-char), where sparkles now flow through the spectrum in order instead of landing on random-looking colors
  • any per-line scheme (pride, trans, bi, pan, sunset, ocean, forest) since they now emit exact RGB values instead of nearest-256-color

Bonus: the SVG, PNG, and JPG export paths use the smooth interpolation too, so exported cards look better regardless of your terminal.

Ship

Landed in v1.8.0.

brew update
brew upgrade hackfetch

Or on the Arch family:
yay -Syu hackfetch-bin
 
Try hackfetch rocket forest and just watch.

1
1
814
Open comments for this post

9h 58m 35s logged

One More Way to Install (and Pizza)

Two small things today: hackfetch is now on the AUR, and there’s a new logo.

hackfetch on the AUR

A reviewer on my last ship said they had a hard time getting hackfetch running on CachyOS (an Arch-based Linux distro). Instead of trying to reproduce their exact issue, I figured the cleanest fix was to publish hackfetch through the package manager Arch users already trust.
 
So hackfetch is now up as hackfetch-bin on the AUR: aur.archlinux.org/packages/hackfetch-bin
 
If you’re on Arch, CachyOS, Manjaro, EndeavourOS, Garuda, or any other Arch-based distro:

yay -S hackfetch-bin

Or with paru:
paru -S hackfetch-bin

The package pulls the pre-built binary straight from the GitHub release, so there’s no waiting on a source build. Works on both x86_64 and aarch64.
 
Add it up and hackfetch is now installable one command from:

  • brew (macOS and Linuxbrew)
  • apt / dnf / yum / pacman / zypper / apk (all through the curl installer)
  • the AUR (Arch family)
  • go install (any platform with Go)
  • the PowerShell installer (Windows)

This thing is getting kind of hard to NOT install.

Pizza logo

Hack Club runs on pizza. Now hackfetch does too.
 
hackfetch pizza
 
It’s a triangular slice viewed from the tip, HACK CLUB baked into the middle, and a rolled crust at the wide edge.
 
The tricky part was the crust. I went through three drafts trying to make it curve at the bottom without breaking the connection to the slice’s diagonal sides (turns out you can’t cleanly connect diagonal characters to horizontal ones in a monospace grid). Landed on a two-row rolled crust where the diagonals keep going and the interior fills with a double horizontal band.
 
Pairs surprisingly well with sunset:
 
hackfetch pizza sunset
 
Turns your terminal into a golden-hour pizza box.

Ship

Both landed in v1.7.3. If you’re on Arch-family:
 
yay -S hackfetch-bin
 
Elsewhere:
 
brew update
brew upgrade hackfetch
 
Now try hackfetch pizza and tell me if the crust looks right.

0
0
23
Ship

# GatOS
A personal operating system that runs in your browser and is, I will be honest, mostly about a cat.

live: [xerneas3318.github.io/GatOS](https://xerneas3318.github.io/GatOS/)
code: [github.com/xerneas3318/GatOS](https://github.com/xerneas3318/GatOS)

It is a little fake desktop. It has a top bar with a live clock, a background, and windows you can actually drag around, open, and close like a real OS. I built it from scratch over the last few weeks as my Stardance webOS project.

## What is on it
- **welcome.exe**: a short intro window (with a photo of my friend's cat, obviously)
- **dev.log**: a notes-style reader with a sidebar of build entries, so you can read how the thing got made
- **catnip**: a full cookie-clicker game where the cookie is the cat. Tap the cat for treats, buy auto-petters and laser pointers and a "cat loaf mine" that earns treats while you are away. It saves your progress to your browser.

## Built with
Plain HTML, CSS, and JavaScript. No framework, no build step. The whole color palette lives in one tiny tokens file thanks to CSS variables, so the entire theme changes from one place. Inter for the UI, Space Mono for the code and clock.

## What I learned
Along the way I learned how to make an element draggable, how to keep windows stacking in the right order, how `localStorage` works, and how to split a project into a file structure a human can actually read instead of two giant cursed files.

The cat, as always, remains. The cat is eternal. The cat now lives on the internet over HTTPS.

Try project → See source code →
Open comments for this post

4h 24m 22s logged

the files are files now

opened this project after like a week away and genuinely could not find anything. one style.css, one script.js, both hundreds of lines of everything everywhere. i wrote every line of it and i still had to ctrl+f to find the clock. it was a junk drawer with a cat on top.

so this whole session was cleaning. no new features, nothing shiny, just four hours of moving code around like a little file gremlin.

what got split

styles:

  • one file for the top bar
  • one for windows
  • one for the desktop
  • one per app
    same deal for the js, the clock, the dragging, the window stacking, the icons all live in their own files now. the two games moved into an apps/ folder so catnip stops rubbing up against the core window code (it’s a cat. it rubs on things.)

the one smart thing

the tokens file from last time made this way less painful than it deserved to be, since all the colors were already in one place, nothing broke visually while i shuffled everything. past me did one 1 smart thing and present me is grateful.

weirdest part is opening a file and already knowing what’s in it before it loads. revolutionary technology.🤯

next time

  • github pages: this thing needs a real url so it stops living exclusively on my laptop
    the cat remains. the cat is eternal. the cat’s code is just alphabetized now.
0
0
19
Open comments for this post

3h 11m 20s logged

Hackfetch Is Now Actually Fast

Last log I added a loading spinner so you’d have something to look at while hackfetch was hitting the Hackatime API. This week I made it so you barely have time to see the spinner at all (undoing all my work lol)

The problem

hackfetch was making four separate calls to Hackatime for every fetch:

/users/current/heartbeats/most_recent (for your slack handle)
/users/current/summaries (for your streak)
/users/current/statusbar/today (for today's coding time)
/users/current/stats/last_7_days (for weekly stats)

Doing them one after another meant the total wait was the sum of all four. Turns out that’s about 1.7 seconds on a normal connection. Not the end of the world, but very noticeable when you just want to open a terminal.

The fix: run them all at the same time

Now hackfetch fires all four calls in parallel using goroutines, waits for the slowest one to come back, and then builds the fetch from the responses. Total wait drops from “sum of all four” to “just the slowest one.”

  • Before: ~1.7 seconds
  • After: ~0.7 seconds

60% faster. Every single fetch. Every single time.

One tiny bonus optimization

While I was in there, I also switched to a single shared HTTP client with a bigger connection pool. The default Go setup caps you at 2 open connections per host, which is fine when you’re calling one endpoint at a time but hurts when you’re firing four at once. Now the parallel calls can share connections instead of stacking up on each other.

Nothing about the fetch itself changed. Same info, same layout, same colors. Just faster.

Ship

Shipped in v1.7.2. If you have hackfetch installed:

brew update
brew upgrade hackfetch
time hackfetch

Watch the spinner barely have time to appear.

0
0
19
Open comments for this post

12h 34m 59s logged

Loading Spinner and Splitting Up main.go

Thanks so much to everyone who took the time to comment, rate, and drop feedback on my latest ship. All of it was genuinely so helpful and I’ve been spending the last couple days fixing things up a bit. I totally forgot to devlog so I’m actually losing a couple hours since it’s been over ten hours since my last one :sob

A loading spinner while Hackatime fetches

One reviewer pointed out that hackfetch takes a couple of seconds when it’s pulling your Hackatime stats for the first time, and there was no signal that anything was happening. Now there is.

⠋ fetching Hackatime...

The spinner cycles through 10 braille frames and the whole line clears out the instant the fetch finishes. Same fetch, way less awkward pause.
Two small details that make it feel right:

  • It only shows when stderr is actually a terminal, so it doesn’t spam control codes into your log files if you redirect output.
  • It prints one frame immediately, so fast fetches still flash something instead of nothing.

hackfetch runs it around the API calls automatically. No config, no opt-in. It’s just there.

Splitting up main.go

Another reviewer said the 1.5k-line file was too much. They were completely right. hackfetch had grown into one giant main.go that held the logos, the color schemes, the Hackatime API client, the terminal render, the SVG exporter, the PNG rasterizer, everything.

Refactored today into files that each do one thing:

  • logos.go: the ASCII logos
  • colors.go: color schemes and the ANSI → hex table
  • config.go: reads ~/.wakatime.cfg
  • hackatime.go: the API client, caching, and language inference
  • sysinfo.go: OS, shell, editor, terminal
  • setup.go: the -setup flow
  • render.go: terminal render, watch loop, and layout
  • export.go: SVG, PNG, and JPG exporters
  • main.go: just flag parsing and the entry point (~100 lines instead of 1,600)

Same binary, same behavior, zero user-visible change. But next time I want to add a new logo or a new color scheme, I know exactly which file to open. Also way friendlier for anyone who wants to hack on it.

Ship

Both landed in v1.7.1. If you already have it:

brew update
brew upgrade hackfetch

Or the curl installer will pull v1.7.1 the next time you run it.

Try it.

0
0
25
Open comments for this post

1h 17m 8s logged

GPT-2 124M Reproduction

Reproduced GPT-2 124M (the old 2019 model) from scratch, trained on ~10B tokens across 2x RTX 5090 in ~7 hours. Final val loss ~3.10.

Dataset

  • FineWeb-Edu sample-10BT, ~10B tokens
  • GPT-2 BPE (tiktoken), vocab 50257 padded to 50304
  • 100 uint16 shards (99 train, 1 val), ~19GB
  • Streaming download + multiprocessing tokenizer — switching to imap_unordered with a bigger chunksize took tokenizing from ~2 hours to ~6 minutes

Architecture

GPT-2 124M: embd=768, block_size=1024, weight-tied embeddings (~124M params)

  • 12 blocks, each with causal self-attention (12 heads, flash attention) + MLP (Linear → GELU → Linear, 4x)
  • Pre-LayerNorm + residuals on both sublayers

Training

  • bf16 + torch.compile, AdamW, OneCycleLR cosine with warmup, grad clip 1.0
  • 0.5M-token effective batch (524,288), held fixed via gradient accumulation
  • DDP across 2x RTX 5090 on RunPod, 19,073 steps (~1 epoch), ~7 hours
  • Checkpoint every 200 steps + auto-resume babysit script

Results

  • Val loss ~3.10
  • Coherent, topical English continuations. It’s a base model, so it continues a prompt rather than answering it .
  • Shipped an inference script + Gradio demo, uploaded to Hugging Face

What was actually hard

The architecture was basically the char transformer from last devlog, just wider (124M vs ~10M) with BPE instead of 65 chars. The model was the easy part.

The real work was scale engineering: sharding 10B tokens to stream off disk, gradient accumulation to hit a 0.5M-token batch on cards that OOM at batch 8, multi-GPU gradient sync with DDP, mixed precision, and checkpoint/resume so a 7-hour cloud run survives interruptions. That’s what turns a transformer into a trained LM.

0
0
6
Open comments for this post

26m 33s logged

NEW CAT MINI GAME

first thing i did was get rid of the terminal look. it was cool for about a week, but staring at glowing teal on near-black was starting to feel like my OS was yelling at me. so i rebuilt the entire theme to look like a warm peach background, light grey panels, one orange accent doing all the heavy lifting. it’s calm now. it’s a website you could actually read at 2am without your retinas filing a complaint.

did it the right way this time with CSS variables, so the whole palette lives in like eight lines at the top of the file. want a different accent? change one value. past me would have found-and-replaced forty hex codes and missed six of them.

also swapped the font to Inter for that clean note-app feel, kept Space Mono around for code and the clock because some things are sacred.

oh, and there’s a new app:

catnip.

it’s a cookie-clicker but the cookie is my friend’s cat. you tap the cat, treats go up, you buy auto-petters and laser pointers and eventually a “cat loaf mine” that prints treats while you’re not even looking. it saves your progress. i have lost dozens minutes of my life to it already and i wrote it.

so the cat remains. the cat is eternal.

0
0
12
Open comments for this post

47m 16s logged

Devlog #2

Last time I said the plan is to store meaning, not the waveform, the way face recognition stores an embedding instead of the photo. That left a question I skipped over: how do you actually train a compressor to do that?

The naive version is to run the EEG through an encoder, get an embedding, and store just that. But it has two problems. First, I’d be throwing the raw signal away for good, and a brain recording is expensive and reusable, you might want to ask it a different question later. Second, and bigger: what target do I train the embedding toward? An autoencoder left to itself just learns whatever compresses easiest, which lands me right back at fidelity.

The insight I landed on: I don’t have to invent the meaning space. It already exists. Image and text models like CLIP have already learned a space where a picture of an accordion and the word “accordion” sit in the same spot. So instead of hoping my compressor discovers meaning on its own, I borrow a model that already has it and use it as a judge.

Here’s the shape. The codec compresses and rebuilds the EEG like a normal codec. Then I take the rebuilt signal, push it through a frozen model that maps it into that shared image-text space, and score how close it lands to the embedding of the image the person actually saw. If the reconstruction drifts away from the right meaning, that score punishes it. The judge never changes, it only grades.

The important part is that the gradient has to flow through the frozen judge back into the compressor. The judge is frozen but not detached: it’s a fixed grader whose opinion shapes what the codec learns to keep. That one detail is easy to get wrong, and it’s the whole trick.

So the loss becomes three things added together: spend few bits, rebuild the waveform roughly, and stay decodable in the judge’s space. That third term is the entire idea. It’s what makes the codec hold onto the accordion instead of the loudest wiggle in the signal.

And because the judge lives in a text-image space, search comes for free. Type a word, it becomes a point in the same space, and I rank the stored recordings by how close they land. I never trained on text.

0
0
11
Ship ✨ Blessed

Every fetch tool tells you your OS. Mine tells you your streak.

hackfetch is a Hack Club themed terminal fetch tool. Inspired by neofetch (originally bash, now archived) and fastfetch (its C successor), written in pure Go. Instead of just showing your kernel and CPU, it pulls live stats from your Hackatime account: today's hours, weekly total, current streak, top project, top language. Right in your terminal.

Six Hack Club themed ASCII logos (including my GOAT orpheus). Fourteen color schemes (pride, trans, bi, pan included). Live watch mode that refreshes while you code. And as of today, you can export the whole thing as a PNG, JPG, or SVG card and post it anywhere.

One line to install:

Linux / Mac:
curl -fsSL https://raw.githubusercontent.com/xerneas3318/hackfetch/main/install.sh | sh

Windows:
irm https://raw.githubusercontent.com/xerneas3318/hackfetch/main/install.ps1 | iex

Tell me how it was! Follow and like for updates.

  • 7 devlogs
  • 17h
  • 17.78x multiplier
  • 370 Stardust
Try project → See source code →
Open comments for this post

4h 15m 40s logged

New feature today: hackfetch can turn itself into a shareable image.

The goal was simple: your terminal fetch should be more than a terminal thing. If it looks cool on your screen, you should be able to post it in a Slack channel or drop it in a devlog without ever having to take a screenshot.

hackfetch -export card.svg

Run that and instead of printing to your terminal, hackfetch writes an SVG file that looks exactly like the fetch on your screen: logo on the left, stats on the right, rounded corners, monospace font, every color preserved.

Getting the general SVG output was the easy part. Draw a rectangle, put text inside it, done.

The hard part was preserving the colors.

Terminals speak ANSI 256-color codes. SVGs speak hex color strings. They don’t line up one-to-one, so a naive export just drops every color and produces a black-and-white version of the fetch. Not very shareable.

The fix: a lookup table from ANSI 256-color codes to hex values, plus a rendering path that emits every styled character as its own SVG element with the correct fill color.

That’s actually a lot of tspans. Schemes like per-char, where every single glyph has its own gradient stop, can produce hundreds of colored elements per fetch. But it works. The SVG renders smoothly in any browser and looks identical to what shows up in your terminal.

Use It Anywhere

Some things you can do with the exported card:

  • open it in a browser to see it
  • drop it into a Slack channel or a devlog
  • convert it to PNG with rsvg-convert if you need a raster image

hackfetch -export card.svg trans

Open it.

Post it.

Ship it.

0
0
18
Open comments for this post

50m 16s logged

the window moves now.

you can grab welcome.exe by its title bar and fling it around the desktop, hit the little red dot to close it, and click xerneasOS up top to summon it back. it’s a real window now. it even has a title bar.

spent a while wrestling with the drag math turns out you have to keep track of the cursor’s offset from the window when the drag starts. otherwise the window instantly teleports so its corner snaps to your mouse. not exactly the intended user experience.

also did some housekeeping: ripped all the CSS into style.css and the JavaScript into script.js so my HTML isn’t one giant cursed wall of inline styles anymore. windows are now a reusable .window component, so making future apps should be way easier.oh, and there’s a live clock in the top bar that updates every second. new Date() my beloved.

the cat remains. the cat is eternal.

next time: a real wallpaper, maybe an actual second app, and slowly making this feel less like “one draggable window about a cat” and more like an operating system (that’s still about a cat)

0
0
30
Open comments for this post

2h 1m 10s logged

Live Hackfetch

New milestone today: hackfetch can now stay open and update itself while you code.

The goal was simple: run hackfetch once, leave it in the corner of your terminal, and watch the numbers move in real time as you work.

hackfetch -watch

Your today-hours tick up. Your top project changes when you switch repos. Your streak rolls over at midnight. All without running the command a second time.

The static render was already in place from the regular one-shot fetch. The new part was redrawing without scrolling.

That sounds simple. It is not.

In a terminal, every time you print something, it appends to the bottom of the screen. By default, refreshing the fetch would just print a second copy of itself underneath the first, then a third, then a fourth. Within a minute you’d have a wall of identical fetches scrolling away into the past.

The trick is ANSI escape codes. There’s a sequence that means “move the cursor up N lines” and one that means “clear the rest of the screen from here down.” Together they let you erase the previous fetch and redraw a new one in the same spot. Same screen, fresh data, no scroll.

Why every 30 seconds? Two reasons:

Hackatime aggregates heartbeats in 1-minute buckets internally, so polling faster than that doesn’t tell you anything new 30 seconds is fast enough to feel “live” without hammering the API for no reason

Ctrl+C to quit. Resizing the terminal doesn’t break it. Pair it with a gradient color scheme and the whole thing turns into a tiny dashboard for your coding session:

hackfetch -watch sunset

1
0
30
Loading more…

Followers

Loading…