Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Starf An activity monitor for Apple silicon that never invents a number e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Licence & updates
Licensing
Updates
Elyra
Architecture

Architecture

Shape

Rust reads the kernel and computes every rate and total. Svelte draws, filters, sorts and arranges. The boundary is a serialised snapshot, pushed once per interval.

src-tauri/src/
  sys/              one module per kernel interface
    process.rs        sysctl(KERN_PROC_ALL), per-process counters
    ffi.rs            kinfo_proc field offsets, with a runtime guard
    host.rs           host CPU ticks, VM statistics, interface traffic
    cpu.rs            per-logical-processor ticks, P/E identification
    threads.rs        per-thread counters for one process
    gpu.rs            IOAccelerator devices and clients
    power.rs          AppleSmartBattery and the adapter
    network.rs        per-process traffic, from nettop on its own thread
    reports.rs        KERN_PROCARGS2, and Apple's tools run with a deadline
    applications.rs   Dock presence from Info.plist, cached
    diagnostics.rs    open files, memory regions, Mach ports, resource counters
    iokit.rs          the IOKit calls the GPU and CPU collectors share
    corefoundation.rs the CF readers those need, each type-checked
    machine.rs        static facts, read once
    timebase.rs       Mach ticks to nanoseconds, with the Rosetta correction
  sample.rs         successive readings turned into rates and histories
  tree.rs           process ancestry and subtree totals
  model.rs          the shapes the interface receives
  diagnostics.rs    the per-process pages, collected on request
  menubar.rs        the status item, its pins, and the popover window
  license.rs        offline key verification and the reminder's timing
  update.rs         the one network request, and version comparison
  lib.rs            commands, and the collector thread

src/
  lib/
    state.svelte.ts   the single source the interface reads from
    query.ts          filtering, sorting, column layout per metric
    tree.ts           building and collapsing the display forest
    export.ts         CSV and JSON
    format.ts         every formatter; missing values render as an em dash
    *.svelte          the views

Data flow

A collector thread samples on its own cadence, sequentially: a slow sample delays the next one rather than overlapping with it, so every rate describes measured elapsed time.

collector thread ──► Sampler::sample() ──► Snapshot ──► emit("snapshot") ──► state.svelte.ts
                          │                                                        │
                          ├── sys::* readers                                       ├── query.ts   filter, sort
                          ├── rate computation                                     ├── tree.ts    forest, collapse
                          ├── tree::annotate  ancestry and Σ totals                └── views
                          └── history, 15 minutes

Histories live in the backend as well as the frontend. The frontend fetches the full series once at startup through the history command and appends from each snapshot, so a reload does not start from an empty chart.

Two things do not ride the snapshot stream:

  • Per-thread collection is opt-in, runs at most every two seconds, and is switched on and off with watch_threads. A snapshot carries a fresh flag so the intervening turns do not append the same reading twice.
  • Diagnostics are request-and-response through the diagnostics command. Only the page being looked at is collected, and each carries its own capture time so switching pages never presents an older snapshot as a new one.

Commands

Command Does
machine static facts: chip, processors, memory, performance levels
history the retained series, once at startup
set_interval, set_paused, refresh_now sampling cadence
watch_threads starts or stops per-thread collection for one process
diagnostics collects one diagnostics page
terminate sends SIGTERM or SIGKILL after re-checking identity
save_text opens the save panel and writes what the interface built
set_menu_bar, set_menu_bar_metric the status item
pin_process, unpin_process, pinned, hide_popover menu bar pins and their popover
license_status, activate_license, deactivate_license, license_reminded the licence, checked on this machine
update_status, set_update_checks, skip_version, check_updates the update check, off until asked for
report, cancel_report one diagnostics report, and stopping it

The menu bar popover is the same bundle in a second window, mounted on #popover. It subscribes to the same snapshot stream rather than sampling anything of its own.

The webview has no filesystem access of its own. Export hands over text and a suggested name; the only path ever written is the one the save panel returned.

Reading structures libc does not carry

Three kernel structures Starf needs are absent from the libc crate: kinfo_proc, the per-descriptor proc_pidfdinfo structures, and proc_regionwithpathinfo. Each is read by byte offset, and the offsets were produced by compiling offsetof against the installed SDK headers rather than copied from memory or a blog post. They are named constants with the structure and field in the name, so a future check has something to verify against.

kinfo_proc additionally carries a runtime guard: verify_layout locates Starf's own record at every enumeration and compares PID, parent, owner and start time against values already known. A failure disables the process list with an explanation instead of printing whatever the bytes contain.

Performance

Measured on an M4 Max with about 1,250 processes:

Collector Cost per sample
Per-processor ticks 0.03 ms
Open files, one process 0.3 ms
Battery and adapter 0.44 ms
GPU devices and 46 clients 3.0 ms
Memory map, one process (1,300 mappings) 10.6 ms

The interface keeps two large payloads in $state.raw rather than deep reactive state — the snapshot and the histories are replaced wholesale each second, and proxying a thousand process objects would cost more than the sampling does. The process table renders only the rows on screen.

Subtree totals are sent only for the processes that have descendants — 68 of 1,249 on this machine. A leaf's subtree is itself, which the interface synthesises.

Why this stack

GPUI was considered first and rejected for a concrete reason: it compiles its shaders with xcrun metal at build time, and this machine has only the Command Line Tools, so nothing would build until a full Xcode was installed. On top of that, GPUI has no table or chart primitives, and this is a table-and-chart application.

Tauri 2 with Svelte 5 builds with what is already here, and puts the interesting work — the syscalls, the rate arithmetic, the aggregation — in Rust where it is testable, while the dense tables and charts are drawn by the technology best at them.