- Scheme 71.1%
- Shell 13.3%
- C 11.2%
- Makefile 4.1%
- Common Lisp 0.3%
| .builds | ||
| .jerboa | ||
| docs | ||
| jtop | ||
| scripts | ||
| support | ||
| tests | ||
| vendor/termbox2 | ||
| .gitignore | ||
| .jerbuild | ||
| AGENTS.md | ||
| jpkg.lock | ||
| jpkg.policy.sexp | ||
| jpkg.sexp | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| SECURITY.md | ||
jtop
A cross-platform terminal process monitor written in Jerboa. Inspired by
top, atop, and mactop.
Runs on Linux, FreeBSD, and macOS (including Apple Silicon).
Quick start
make run # build + run ./jtop-bin
make run ARGS='--once' # single-frame snapshot (smoke test)
make run ARGS='--sort mem' # sort processes by %MEM
make run ARGS='--with-power' # macOS: sample powermetrics (needs sudo)
make binary # native binary -> ./jtop-bin
make test # import check + --once smoke test
make security # static release/security checks
make verify # security, native audit, and tests
make release-evidence # dist/release-evidence bundle
make install # install ./jtop-bin to ~/.local/bin/jtop
Release evidence also records SBOM/provenance, repeated native binary reproducibility, and explicit live TUI smoke status.
Requires jerbuild, a C compiler, and ncurses. jerbuild bundles Chez Scheme
and the Jerboa standard library; no Jerboa source checkout is required for a
normal build. make auto-detects ../jerboa/dist/jerbuild when present, or
uses jerbuild from PATH.
Binary
make binary produces ./jtop-bin, a native binary that bundles the Chez
kernel, boot files, and termbox shim. It dynamically links the system ncurses
library.
Keys (live mode)
| Key | Action |
|---|---|
q |
quit |
c / m / p |
sort by %CPU / %MEM / PID |
+ / = / - |
faster / slower refresh |
↑ / ↓ |
move selection |
g / G |
jump to first / last process |
space |
pause / resume (cache stays on screen) |
/ |
start filter prompt (substring of cmd) |
enter |
apply filter (empty clears) |
esc |
cancel filter input |
k |
kill selected process (y to confirm) |
? / h |
toggle help screen |
Display
jtop Apple M4 Pro cores=14 (E:4 P:10) up 8d 08:16 load 1.85 1.56 1.46
CPU 7.3% [███████ ]
MEM 27.1% 13.0G/48.0G [█████████████████ ]
SWP 79.9% 4.0G/ 5.0G [████████████████████████████████████████ ]
I/O disk r:0K/s w:0K/s net in:12K/s out:3K/s
PID USER *%CPU %MEM RSS S TIME COMMAND
359 root 16.9 0.0 15.7M Ss 73:19.75 /usr/libexec/opendirectoryd
39733 user 13.8 1.1 523.7M S+ 2:54.64 claude
...
The * marker before a column header indicates the active sort key.
Platform notes
| Platform | CPU% | Per-core | Memory | Swap | I/O rate | GPU/Power |
|---|---|---|---|---|---|---|
| Linux | /proc/stat | yes | yes | yes | yes | — |
| FreeBSD | sysctl | yes | yes | yes | net only | — |
| macOS | top -l | aggregate | vm_stat | yes | net only | needs sudo |
On macOS, per-core breakdown is suppressed (the data source processor_info()
isn't available without C bindings; powermetrics requires sudo). Use
--with-power to enable Apple-Silicon GPU% / power readouts via
powermetrics -i 200 -n 1 (root).
Security and Privacy
jtop is network-free, but it displays local process details. Treat screenshots,
logs, and --once output as sensitive because they can include users, PIDs,
command names, resource counters, host model, and optional power telemetry.
Collectors never resolve through PATH or a shell. A native runner accepts
only reviewed absolute, root-owned, non-group/world-writable executables,
passes direct argv with a minimal clean environment, drains stdout/stderr
concurrently, caps output, applies a monotonic deadline, and explicitly reaps
every child. The k action invokes kill(2) directly after UI confirmation.
--with-power is disabled by default because macOS powermetrics may require
sudo.
Native boundaries are the termbox2 shim and the bounded process runner in
jtop/tui-ffi.ss, jtop/jtop_tui_shim.c, jtop/process.ss, and
jtop/jtop_process_runner.c. The production path is the native jerbuild
binary, where all shim symbols are registered from generated C. Runtime
shared-object loading is disabled. Plain/--once process fields escape C0/C1,
ESC, line separators, backslash, and Unicode bidi controls before output.
Layout
jerboa-top/
├── Makefile
├── .jerbuild # native binary build definition
├── docs/ # threat model and release evidence docs
├── scripts/security-check.sh # release/security policy gate
├── support/import-check.ss # import and FFI symbol smoke check
├── jtop/
│ ├── main.ss # interpreter entry (sets library-directories)
│ ├── main-binary.ss # static-binary entry (no dlopen, FFI via Sforeign_symbol)
│ ├── platform.ss # OS detection + shared structs
│ ├── util.ss # direct command + output-escaping utilities
│ ├── process.ss # native runner FFI
│ ├── jtop_process_runner.c # direct argv, clean env, caps/deadline/reaping
│ ├── collector.ss # platform dispatcher
│ ├── collector-linux.ss # /proc parsing
│ ├── collector-freebsd.ss # sysctl + ps
│ ├── collector-macos.ss # top/ps/vm_stat/sysctl + powermetrics
│ ├── history.ss # ring-buffer sparkline histories
│ ├── tui-ffi.ss # Chez FFI bindings for termbox2 (via shim)
│ ├── tui-widgets.ss # bars, sparklines, columns, panels
│ ├── render.ss # frame layout + ui-state struct
│ └── jtop_tui_shim.c # C shim wrapping termbox2 for FFI
├── vendor/
│ └── termbox2/ # single-header TUI library (vendored)
└── README.md
TUI architecture
jtop uses termbox2 for input/output.
A thin C shim (jtop_tui_shim.c) wraps the termbox API into plain functions
that Chez can call via (foreign-procedure ...). The shim is linked into the
native binary and registered with Sforeign_symbol from generated C main.
(jtop tui-ffi) does not call load-shared-object; import-only and --once
paths do not need an external TUI shared object.
--once skips termbox entirely and emits a plain-text snapshot — useful for
scripts, smoke tests, and pipelines where there's no TTY.
The --once path proves collector/render startup only; production release still
needs real TTY evidence on the target operating systems.