Skip to content

Repository files navigation

opscope

Hollywood called. They want their terminal back.

Sci-fi hacker panels from the movies — except these ones tell you the truth.

Fill your screen with glowing panels, scrolling graphs and blinking status readouts. The difference is that every number on them is real: actual network latency, actual deployments, actual machines on your tailnet, actual agents waiting on you.

opscope — click to watch the 1m54s demo

Eight widgets running at once — every figure live. Watch the 1m54s demo →

Run it

npx opscope is the official way to use this. Nothing to install, no PATH to manage, no version to pick — it fetches the launcher and the binaries and starts whichever widget you name.

npx opscope              # the menu
npx opscope clocks       # or name one and skip it

clocks needs no configuration at all, so it is the one to try first. Other ways to get them →

The launcher

opscope is the front door: it shows every widget, what it does, and a preview without starting its data source. Pick one and it runs; quit it and you are back. It is the launcher, not another widget.

How the launcher works →

The widgets

Widget What it does Needs Docs
latency Continuous latency to a list of hosts: median, jitter, loss and a log-scale graph, so a slow link and an unsteady one look different. ping · Linux, macOS read →
vercel-deployments Vercel deployments over time — activity per hour, build-time drift, and the build log of the one you open, so a failure explains itself instead of only naming a code. A copy page carries the dashboard, preview and PR URLs. curl, a Vercel token read →
tailnet Tailscale peers, and whether each is reached directly or through a relay. Live throughput, full machine info, copyable addresses. tailscale read →
herdr-panes Every agent and process across all workspaces, ordered by which one needs a human. Enter jumps you there. herdr read →
github Pull requests across every org: merge rate, opened-vs-merged per day, review backlog and the contribution calendar — and for one account on a screen of its own, because a queue growing in one of them is invisible in a total the others are also feeding. curl, a GitHub token read →
github-actions GitHub Actions across your personal account and orgs: what is running or queued, which workflows are failing repeatedly, which job and step broke, and whether the pipeline is getting slower. curl, a GitHub token read →
github-prs The pull requests you have to follow up on: checks, reviews, mergeability, and a stack map with the order a stack has to merge in. curl, a GitHub token read →
linear Linear across every team: what is outstanding, the running cycles and their scope creep, issues created against completed, and every project still going. opens a cycle, a team or a project on a screen of its own. curl, a Linear API key read →
agent-usage How much each coding agent on the machine has been used — tokens, sessions, AI-written code — and what is left of each one's rate limit, one tab per agent. the agents' own logins read →
ports What is listening on this machine — the dev servers you have running, which project each was started from, how long it has been up, how much traffic it is carrying, and whether anything outside the box can reach it. k stops the selected one; opens it for a traffic chart, an address to copy, or to publish it over Tailscale or Cloudflare. lsof on macOS · Linux, macOS read →
netwatch Which processes are using the network — total since it started, current rate, up and down, per process — read from the operating system's own counters rather than by capturing packets. ip + ss on Linux, nettop + script on macOS · Linux, macOS read →
link How good the connection is between this machine and whoever is connected to it — round-trip time, jitter and loss for every inbound session, plus achieved delivery rate where the kernel exposes it, read rather than probed. ss on Linux, nettop + script on macOS · Linux, macOS read →
clocks Server clock, countdowns to the next hour / end of office hours / end of day, a pomodoro, and a world clock. read →
matrix Nothing whatsoever. Digital rain, with truecolor fade trails. read →
months A month grid you can page through: today marked, at least two weeks of context either side of it, ISO week numbers, and the zone the dates are reckoned in — clocks owns the time of day, this owns dates. read →

All fifteen widgets run on Linux and macOS.

Each is a single self-contained binary — every library it needs is compiled in and ldd shows only libc, libm and libgcc. Widgets that read host tools declare them and check before launch instead of drawing an empty pane.

Ask the downloaded launcher about this machine at any time:

npx opscope doctor       # or: ./opscope doctor

It reports required and recommended tools separately, names the widgets that use each one, and prints installation advice. It never installs anything. For the package-managed Linux tools needed by the main data sources, the family commands are:

sudo apt install curl iproute2 iputils-ping     # Debian / Ubuntu
sudo dnf install curl iproute iputils           # Fedora / RHEL
sudo pacman -S curl iproute2 iputils            # Arch
apk add curl iproute2 iputils                    # Alpine (normally as root)

Tailscale and Herdr keep their own installers and are reported separately; optional integrations such as Cloudflare tunnels never block a widget that can otherwise run. On macOS, the core tools ship with the operating system; doctor can offer Homebrew packages for optional tools where one exists.

How it started

Literally: "split 10 panels that make my computer look like some sci-fi hacker terminal from the movies."

So there were ten. A radar sweep, a rotating globe, a spectrum analyser, a cipher cracker, a memory scanner, a packet intercept — all beautiful, all fabricated. They looked exactly right and told you nothing.

Then one got wired to /proc and became genuinely useful, and the contrast was impossible to unsee. One by one the fakes came down, each replaced by something that answers a real question. What survived is the aesthetic with the lying removed.

Some of the theatre is still here, unapologetically. matrix computes nothing at all — it just looks good, and it knows it.

Getting them

The official route is npx. If you have Node, this is the whole thing:

npx opscope                 # the menu
npx opscope clocks          # skip the menu; any widget name works
npx opscope@latest clocks   # latest release, or pin with @0.3.0

It fetches the launcher and fifteen widget binaries for this machine into npm's cache and runs whichever you named — or the menu, if you named none. Your PATH is not touched. Only opscope is exposed as a command; the widgets sit inside the package beside it, which is why link never shadows the coreutils command of that name.

Published for Linux x86-64 (glibc 2.35 or newer), macOS Apple Silicon and macOS Intel.

There is no Homebrew formula yet. You can also download three files, or build all sixteen binaries. Both take about a minute.

Download a release

Linux x86-64, macOS Apple Silicon and macOS Intel are built for every tag. This works out which one you want, checks it against its own checksum, and unpacks it — no version to fill in, because it asks which release is current:

R=https://github.com/stealth-factory/opscope/releases
V=$(curl -fsSLI -o /dev/null -w '%{url_effective}' $R/latest | sed 's|.*/||')
case "$(uname -s) $(uname -m)" in
  "Darwin arm64")   A=aarch64-apple-darwin ;;
  "Darwin x86_64")  A=x86_64-apple-darwin ;;
  "Linux x86_64")   A=x86_64-unknown-linux-gnu ;;
  *) echo "no build for $(uname -sm); build from source below" >&2 ;;
esac
T=opscope-$V-$A
curl -fsSLO $R/download/$V/$T.tar.gz
curl -fsSLO $R/download/$V/$T.tar.gz.sha256
shasum -a 256 -c $T.tar.gz.sha256    # sha256sum -c on Linux; either is fine
tar -xzf $T.tar.gz && cd $T

Or take them by hand from the latest release — every tarball has a .sha256 beside it.

The sixteen binaries are right there, beside config.example.json and a copy of the docs. Nothing else is needed to run them, so this folder can live wherever you like. Start them from it — do not copy link onto your PATH, it shadows the coreutils command of that name:

./opscope            # the menu
./opscope link       # skip the menu
./opscope clocks -h  # widget flags go through

On macOS, download with curl rather than a browser. These binaries are not signed or notarised, and a browser marks what it downloads with com.apple.quarantine, which makes Gatekeeper refuse to run them. curl does not set that mark. If you did use a browser, extract the tarball and clear the mark on that extracted opscope-<version>-<arch> directory — not on Downloads, and not on .:

xattr -dr com.apple.quarantine opscope-*-apple-darwin

Or build them

Needs a Rust toolchain and nothing else:

cargo build --release   # launcher + fifteen widgets in ./target/release
./target/release/opscope # the menu, from the build tree

Running them

opscope is the front door — a menu of the fifteen widgets, with a live preview of whichever is highlighted. Name a widget to skip the menu. The same shape works from npx, from an unpacked tarball, and from a build tree:

npx opscope              # pick one and it runs
npx opscope clocks       # or name one and skip the menu
npx opscope clocks -h    # flags after the name belong to the widget

The launcher looks for each widget beside itself, so the launcher and fifteen widget binaries have to stay together — npx keeps them that way for you. An unpacked tarball or a build tree is a setup method, not the usual way to run them, and lives under Getting them.

They are built to sit side by side, but nothing assumes a multiplexer — each is an ordinary terminal program. Tile them however you like, and they degrade rather than break as panes get narrower: columns drop out in priority order, footers wrap instead of truncating, and graphs rescale. A widget in a 30-column strip still says something useful.

Scrolling

Every widget with a scrollable body scrolls the same way. matrix has no body, selection, or content below the viewport and is the sole explicit no-scroll exception.

wheel full widget scroll — the whole pane moves under a pinned title
Ctrl-Y Ctrl-E the same thing from the keyboard, a line at a time, as in vim
move the selection, and the view follows to keep it in sight

The split is the point: the mouse moves the view, the keys move the selection. Turning the wheel never changes which row is selected — not even when it scrolls that row off the screen — and never changes which section has focus. So scrolling to look at something cannot change what opens. Press an arrow and the window comes back to the cursor.

Nothing is ever hidden because a pane is short. Each widget builds its frame at whatever height it needs and the pane shows a window onto it, so a section that will not fit is below the fold rather than dropped — a chart that is not drawn looks exactly like a chart with no data, and only one of those is your problem to fix.

Mouse reporting is on by default and takes drag-to-select away from the terminal while it is. "terminal": {"mouse": false} in your config turns it off; the keys are unaffected. See Configuration.

Configuration

Every widget reads optional settings from the first readable of $OPSCOPE_CONFIG, $XDG_CONFIG_HOME/opscope/config.json (~/.config/opscope/config.json where that is unset), config.json in the working directory, and config.json beside the binary.

Nothing has to be configured. Every key is optional, and a key you leave out is not a gap — the widget uses the default built into it. Five widgets need a token before they can show you something; they are named under Credentials below. Every other widget starts on its own defaults.

There are two ways in, and they write the same file.

The settings screen

Press , in any configurable widget, or in the launcher for the settings every widget shares. It is one screen, owned by opscope-core rather than written fifteen times, so it behaves the same everywhere.

The list shows every key that widget answers to, and for each one the value in force, the default it falls back to, and what the key means. The file being written is named at the top, so there is never a question of which config.json you are editing.

opens the row under the cursor, and what opens depends on the value:

the value what opens
text, or a number a single-line editor
true / false nothing — it moves the value on, default included
one of a fixed set the choices, one to a row
a list of names, or of numbers an entry-at-a-time editor: type to add one, pick one to remove it
a group of named settings the same, one entry per group, each opening its own screen
a model's prices a searchable card of every model, its publisher and its rates

The editors that both take typing and hold a list keep the two apart: you are typing until tab or , and picking after. It is why d puts a d in the box rather than deleting the entry you can see — the box has focus, and a key that means two things at once will eventually do the wrong one.

[d]efault on a row removes the key from your config.json rather than writing today's default into it, so the widget's own default goes on being the answer even when a later release changes it.

Writes are atomic and owner-only. The widget reloads itself as you leave the screen, so nothing needs restarting.

Editing config.json by hand

config.example.json is the reference, and it is generated — not written by hand, not kept current by remembering to. It is built from the same per-widget declarations the settings screen reads, so it carries every section, every key any widget reads, that key's real default, and a comment for each saying what it does:

"ports": {
  "_system_ports_comment": "Extra ports to hide behind `o`, which hides them by default. …",
  "system_ports": [22, 53, 123, 323, 631, 5353],
  "_refresh_comment": "Seconds between listening-socket and traffic refreshes.",
  "refresh": 4.0
}

Copy the sections you want, drop the rest. _comment keys are ignored, so they can stay where they are as a reminder.

So you do not have to read fifteen widget pages to find out what you can set. cargo test fails if a widget reads a key the example does not list, and fails again if the example lists a key no widget reads — the file is neither incomplete nor stale by construction, in both directions. The per-widget pages are for depth: where a number comes from, what it means, why a default is what it is. They are not the inventory.

Section names are the widget's name with hyphens turned to underscores, so vercel-deployments reads vercel_deployments. Getting one wrong is silent — a section no widget reads is simply never read, and the widget goes on using its defaults as though you had written nothing.

terminal is the one section that is not a widget's. It applies to all of them, and , in the launcher edits it:

"terminal": { "mouse": false }

turns off mouse reporting, which is what makes the scroll wheel scroll a widget. It is on by default and costs a real thing while it is: with the terminal reporting, dragging selects nothing, so copying a line off a panel with the mouse stops working. Turn it off if you copy more often than you scroll — Ctrl-Y and Ctrl-E still scroll either way, and so do the arrows.

Credentials

Five widgets define their own token settings: vercel-deployments wants a Vercel token from Account Settings → Tokens, github a classic GitHub PAT with repo and read:org (fine-grained tokens reach only one org each), linear a personal API key from Settings → Security & access, and github-prs and github-actions each hold their own GitHub token rather than borrowing one. Every other widget needs no configuration to start.

Keeping all of this in config.json is what keeps hostnames, ping targets, city lists and tokens out of the source tree: the repo ships generic defaults, and config.json is git-ignored along with .env files and anything else likely to hold a secret.

Asking an assistant to configure one

Each binary carries a plain-Markdown guide for an AI assistant helping you:

opscope latency --configure-help

The guide explains real data sources, safe inspection, credentials, and what must be asked rather than guessed. It is documentation, not a skill and not permission to make changes.

Requirements

  • Nothing to install to run them — the binaries carry what they link against, SQLite included. Node, if you use npx; a Rust toolchain only if you build rather than download or install
  • Linux x86-64 or macOS, Apple Silicon or Intel. A Linux arm64 build is not produced yet; that machine builds from source
  • A terminal with 24-bit colour
  • Per-widget, the external tools the table above names: curl, ss, ping, tailscale, herdr, ip, and the macOS system tools lsof, ps, nettop, ifconfig and script. cloudflared is an optional integration, not a hard requirement. Each widget needs only its own; one that cannot work without its tool says so rather than drawing an empty pane; and none needs root

Documentation

Every widget has a page of its own — what it shows, where each number comes from, every key it answers to, and the settings it reads when it is configurable. That README lives beside the widget's code, help, plain-Markdown AI configuration guide, and — when the widget has settings — its settings declaration. They are linked from the table above and indexed in docs/.

Seven pages are about the repository rather than a widget:

Making a widget how one folder owns a widget's code, help, preview, settings and AI configuration guide — and everything else it takes to add one
Model prices published API list prices per million tokens, with sources and as-of dates
Design conventions the rules every widget holds to, and why each was paid for
Internals opscope-core, the chart helpers, and what cargo test checks that a compiler cannot
Port decisions what the Rust port changed from the Python and why — the answer to most questions beginning why does this key do that
Building Herdr panels resize semantics, focus, and the layout mistakes worth skipping
Releasing how a version is decided, what merging the release PR sets off, and what to do when it goes wrong

Bundled skill

skills/herdr/ carries the Herdr control skill, so an agent working in this repo can drive panes, tabs and workspaces without going looking for it — copy it to ~/.claude/skills/herdr/ to install. It is Herdr's own file, not covered by this repository's licence, and herdr --skill regenerates it after an upgrade.

What is being worked on

Planned widgets, open questions and the state of the Rust port are tracked in Linear: https://linear.app/stealth-company/project/opscope-e829b47d84b8/issues. The link needs access to the workspace; the issues are the canonical list either way, so a feature that looks missing may already be filed there with a reason.

Contributing

cargo test from the root is the gate: each widget's own tests, plus widgets/tests/check.rs, which reads the sources and fails on the things a compiler cannot see — a poller that dies without saying why, a key hinted but unanswered, a setting read but undocumented, a colour below WCAG AA on a selected row. Internals explains what each check is defending. Follow Making a widget for the ordered path from an empty folder through the build-tree launcher smoke test and PR handoff.

License

GNU AGPL-3.0. You may use, modify and share these widgets freely; if you distribute a modified version — or run one as a network service — you must make your source available under the same license. Commercial licenses are available; write to email@wiiiimm.codes.

About

Hollywood called. They want their terminal back. Sci-fi terminal widgets that show only real data — latency, deployments, tailnet peers, GitHub, agents and clocks. Fifteen single-binary Rust widgets, nothing to install alongside them.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages