A neofetch-style system info display compiled to WebAssembly (WASI), running inside a browser terminal. Built with Zig — the same binary works natively on Linux and as a WASM module in the browser.
| Field | Linux | Browser |
|---|---|---|
| OS | /etc/os-release |
Browser name (Brave, Chrome, Firefox…) |
| RAM | sysinfo syscall |
WASM module memory |
| Lang | $LANG env var |
navigator.language |
| Res | xrandr |
screen.width x screen.height |
| CPU | /proc/cpuinfo |
navigator.hardwareConcurrency |
| Host | gethostname() |
location.hostname |
| GPU | glxinfo -B |
WEBGL_debug_renderer_info |
The project compiles a single Zig codebase to two targets:
zig build # Native Linux binary
zig build -Dtarget=wasm32-wasi # WASM module for the browser
In the browser, the WASM module runs inside a Web Worker with a custom WASI implementation. System info that requires browser APIs (resolution, GPU, language) is captured from the main thread and injected into the WASM module via extern "env" function imports.
terminal.js (main thread)
│ captures: screen, GPU, navigator.*
│ postMessage →
└─ wasi_worker.js (Web Worker)
│ implements: WASI syscalls + env imports
└─ zigfetch.wasm
│ calls: getBrowserName(), getResolution(), getGpu()...
└─ prints via fd_write → postMessage → xterm.js
src/
├── main.zig # Entry point and render loop
├── info/
│ ├── os.zig # Linux OS name from /etc/os-release
│ ├── ram.zig # RAM via sysinfo (Linux) or @wasmMemorySize
│ ├── browser.zig # extern "env" imports from JS host
│ └── system.zig # Linux: language, resolution, GPU, hostname
└── render/
└── logo.zig # Color palette renderer
public/scripts/
├── terminal.js # xterm.js shell, captures browser info
└── wasi_worker.js # WASI runtime implementation in JS
xrandr— screen resolutionglxinfo(mesa-utils) — GPU renderer
SharedArrayBuffersupport (requires COOP/COEP headers)- Chromium-based browser recommended for
WEBGL_debug_renderer_info
# Native Linux binary
zig build -Doptimize=ReleaseSmall
# WASM module for the browser
zig build -Doptimize=ReleaseSmall -Dtarget=wasm32-wasi
# Run locally
./zig-out/bin/zigfetch
# Test WASM with wasmer
wasmer zig-out/bin/zigfetch.wasmThe site must be served with these HTTP headers for SharedArrayBuffer to work:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Without these headers the terminal will display an error instead of loading.
| Browser | OS Name | GPU Renderer | RAM |
|---|---|---|---|
| Chrome / Edge | ✅ | ✅ | performance.memory (approx) |
| Brave | ✅ | ✅ (unblocked) | ❌ blocked by anti-fingerprint |
| Firefox | ✅ | ❌ blocked? | ❌ blocked? |
| Safari | ✅ | ❌ blocked? | ❌ blocked? |
GPU info and RAM availability depend on the browser's privacy settings. Fields show graceful fallbacks when unavailable.
- Capture in
terminal.js(if the API is main-thread only):
const myValue = someMainThreadAPI();
worker.postMessage({ ..., myValue });- Expose in
wasi_worker.js:
const { myValue } = e.data;
// in env:
getMyValueLen: () => myValue.length,
getMyValue: (ptr) => { /* write to WASM memory */ },- Declare extern in
src/info/browser.zig:
extern "env" fn getMyValueLen() u32;
extern "env" fn getMyValue(ptr: [*]u8) void;
pub fn getMyValue(allocator: std.mem.Allocator) []const u8 { ... }- Add Linux fallback in
src/info/system.zigand wire both inmain.zig.
MIT

