Profile
Back to NewsBack
GitHub Trending 38 min
Reader Mode
nixfred/infomarchy: Omarchy plugin: your wallpaper becomes a live, clickable information desk with every running AI agent (Claude Code, Codex, Grok, Ollama), 7-day heatmap, rate limits, recent prompts, machine stats. Theme-driven.

nixfred/infomarchy: Omarchy plugin: your wallpaper becomes a live, clickable information desk with every running AI agent (Claude Code, Codex, Grok, Ollama), 7-day heatmap, rate limits, recent prompts, machine stats. Theme-driven.

12 hours ago

Infomarchy

Your wallpaper, promoted to information desk.
Every AI agent running on your machine, what it's doing, what it cost you, and how the box is holding up —
drawn live on the Omarchy desktop in your current theme, one glance away, one click to jump in.

Omarchy plugin Quickshell bun Hyprland MIT

Infomarchy with sanitized demo data on an empty 1080p Omarchy desktop: live AI sessions, 7-day heatmap, recent tasks, usage limits, local AI, and machine stats

The public preview is the real plugin rendered on an empty Omarchy desktop using Infomarchy's explicit, transient demo-data mode. It contains no live prompt, hostname, username, network, path, process, or session data.

Want to see the plain desktop? After configuring the shortcuts, press SUPER + I to hide the Infomarchy cards and reveal your wallpaper. Press SUPER + I again to bring the dashboard back.

Why

You run Claude Code in three terminals, Codex in a fourth, Grok is poking at a repo somewhere, Ollama is warming a model, and your weekly limit is quietly at 86%. The only way to know any of that is to go look — tab through windows, read titles, run nvidia-smi, open a dashboard.

Infomarchy puts all of it on the one surface you always have open and never use: the wallpaper. It's not a widget in the bar and not another window to manage. It's the desk itself, and it's always current.

What you get

🟡 Live AI sessions — who is working right now

Live AI sessions card

One card per running agent — Claude Code, Codex, Cursor, Grok, Grok Bot, Gemini, Hermes, opencode, aider, Ollama chats — detected straight from /proc, no agent-side hooks, nothing to configure. Each card shows the project, working directory, how long it's been up, the pid, the workspace it lives on, and the terminal's own title. Its two-line current topic is a short synopsis derived from several exact-session requests—not the last prompt copied onto the card. A loaded local Ollama model may refine the wording; summaries are cached by session/content and Infomarchy never auto-loads a model. The dot pulses while the agent is thinking.

Zombies. A session nobody is attached to (background, or no window and nothing to attach to) that is not busy and has had no prompt for six hours gets a STALE · idle Nh tag on its card. Right-click it: a background Claude session offers STOP SESSION (claude stop — graceful, the conversation stays resumable), anything else stale offers END PROCESS (SIGTERM, only after the helper re-verifies the pid still belongs to the process the card described). Both need a second confirming click within four seconds; nothing is ever stopped automatically. Claude Code's own registry (claude agents --json, consulted only when its daemon is already running) supplies exact session ids, display names and live busy/blocked state for every running Claude, and marks background sessions — the ones started with --bg or living under the daemon — whose cards open a terminal attached to the running session (claude attach ). Agents hosted inside Herdr, Boomux, or tmux remain visible. Their large session card reports the host and its bounded identity: Herdr workspace/tab/pane, Boomux workspace/shell plus exact shell/run IDs in the snapshot, or tmux session:window.pane. Infomarchy reads only those documented identity variables from the agent environment; unrelated environment values are never serialized. An attached tmux pane is matched to its client terminal, and a Herdr-hosted agent to the terminal running the Herdr client (the agent descends from herdr server, a daemon, so plain process ancestry never reaches the window). Clicking the card focuses that terminal and then jumps inside the multiplexer: tmux select-window / select-pane / switch-client for tmux, workspace.focus / tab.focus / pane.focus over Herdr's socket API for Herdr (its CLI only exposes a directional pane focus), via the bundled herdr-focus.ts, each id validated and the socket taken from the agent's own environment. For Boomux the window is matched through the boomux __attach client process, or the terminal title Boomux sets (boomux:shell:: | workspace - name); the jump focuses that window and runs boomux open --workspace , which shows the Workspace layer and re-focuses the existing terminal (verified against Boomux 1.9.7: no duplicate window; a bare open neither moves focus nor duplicates). A shell created from inside Herdr inherits Herdr's variables, so Boomux is resolved first and the inherited Herdr host is dropped. A host with no client window at all shows no client window found rather than guessing one. Remote-only processes on another machine are outside local /proc and are not fabricated.

Cursor. Both ways of running it get a card. cursor-agent in a terminal is an ordinary session. The IDE is the interesting one: when the conversation lives in Cursor rather than a terminal, the process that actually runs the agent is cursor-agent worker — one per open workspace, each bound to a single directory with its own pid, cwd and counters. It reads like a service and is not one, so it is not excluded the way Codex's app-server is; excluding it would leave a machine driving Cursor entirely from the IDE, which is most of them, with no Cursor session at all. Those cards are marked background and dimmed like Claude's --bg sessions, because you drive them from the window and not a terminal, and clicking one focuses Cursor. The genuine one-shot management commands (login, update, mcp, models, plugin, …) are excluded. The subcommand is matched across the whole argument list rather than at argv[1], because the IDE puts it after several flags.

A worker lives as long as its Cursor window, not as long as a conversation — so unlike a terminal agent you close when you are done, a Cursor card can sit on the desk for days. That is why the STALE tag and quiet grouping matter more here than for anything else; liveness is the process, and decay is the last prompt.

History comes from Cursor's own transcripts at ~/.cursor/projects//agent-transcripts//.jsonl, in the same role/message shape Claude Code writes; CURSOR_HOME overrides the root. Two quirks are handled rather than guessed at. The directory name is the working directory with every / replaced by -, which is ambiguous as soon as a path segment contains a dash of its own, so it is resolved against the filesystem — home-you-repos-four-monorepo becomes ~/repos/four-monorepo, not ~/repos/four/monorepo — and an unresolvable name yields no project rather than a fabricated path. And there is no timestamp field: the only clock is a tag the client injects into each user turn, parsed explicitly because Date.parse reads that string inconsistently and drops the offset on some builds, with the file's modification time as the fallback.

Which chat a worker is on comes from the newest transcript under its own directory. That has to be read rather than inferred: the generic inference only accepts a prompt within half an hour of launch, which fits a terminal agent prompted right after starting and not a worker that outlives any one conversation.

Cursor is also the one provider with a real busy signal instead of a terminal-title guess: a transcript ends with {"type":"turn_ended"} once the agent has finished, so a trailing assistant turn without it means it is still working. For a worker the title could not have helped anyway — it belongs to the IDE window, which is shared by every workspace. Known limitations: Cursor's hooks and rules inject their own turns as role: "user", wrapped in , timestamped, and positioned exactly where a typed prompt goes — there is no field that separates them, so they appear in RECENT TASKS alongside what you actually asked, and filtering them would mean a content heuristic that could drop real prompts. All of a machine's workers also share the one Cursor window, so clicking any of their cards focuses Cursor but cannot switch it to that workspace. And Cursor publishes no local token or quota data, so it contributes no USAGE row.

Grok Bot. The xAI desktop app runs every bot in its roster inside one Electron process, so /proc shows a single agent no matter how many bots you have. Infomarchy reads the app's own local roster — ~/.config/Grok Bot/sand-client-persistence, one plain-JSON file per state slice, each named by the base32 of its key — and gives each bot its own card: its name, the last line it wrote (markdown flattened, secrets redacted like any other prompt), and whether it is waiting on your answer or holding replies you have not read. Bots you hid from the sidebar get no card, and transcripts are never opened. Since the bots share one process, its CPU/RAM/GPU counters are attributed once, to the bot the app currently has open; the other cards show — rather than repeating the same process on every card. Clicking any of them focuses the Grok Bot window.

Grouping quiet sessions. A roster that size costs a card per bot, and ten of them alone trip the dense layout (> 8 sessions), shrinking every Claude and Codex card on the desk to pay for bots nobody has touched in weeks. So a provider's quiet sessions group into one card that names a few of them with their idle times — still clickable, right-click still opens that individual bot in the inspector — and the chip expands the rest. On the desk this was written for that took SESSIONS from fifteen cards to six and turned dense mode off, giving the surviving cards their working directory, host, git and pid lines back. The three attention states are deliberately not treated alike: waiting (an agent blocked on your answer) and blocked (a conflict, crash or failure) are requests, a request does not expire, and neither ever groups at any age. done — ready for review, or a bot holding unread replies — is a notification, and one nobody has looked at for a month has stopped being news, so it groups once past the window (an hour by default) and the card says N to review. Nothing is hidden by this: NEXT ACTIONS and the notifications are built collector-side from the ungrouped list and still carry every signal. A group of one is refused, and groups are drawn at the end of the list because a group is taller than its neighbours and a Flow row is as tall as its tallest card. The rule is keyed by provider rather than hardcoded to Grok Bot — any app that fans one process out into a dozen sessions gets the same treatment — and Grok Bot is the only one that does that today, so it is the only one on by default. The toggle is on the cards rather than in the module strip, which lists the panes below it: grouping belongs to a provider, and a provider is named on every one of its cards, so the name carries a disclosure caret and clicking it — on any card, including the group itself — turns grouping on and off. It is the same faint ▴ / ▾ WHAT CHANGED uses for its own rows, meaning the same thing: ▴ while a provider's cards are spread out, ▾ once they are grouped. The caret stays textFaint until the pointer arrives, so a glance still reads as cards, and SESSIONS names the control and reports the quiet count in both states. Persisted per provider in dashboard.json as sessionGroups, with the window in sessionQuietMinutes (0 groups every idle session whatever its age).

Each card also attributes live CPU, resident RAM, process-tree size, and—when nvidia-smi exposes compute PIDs—GPU memory to that agent. The detailed totals repeat in the inspector; unavailable counters display —.

Only the full per-session cards appear in Live AI Sessions; there is no duplicate compact workspace-card strip. Each large card includes its workspace number. In the session inspector, workspace buttons 1–10 can move that exact agent window silently; the current workspace is highlighted and disabled.

Window thumbnails are opt-in: right-click a large session card, toggle PREVIEWS OFF/ON in its inspector, then hover a live card. Infomarchy captures only that exact address after a short delay, downsizes it to 160×90, applies a heavy blur, and displays a 320×180 still. The raw capture moves through bounded in-memory streams from grim to ImageMagick without touching disk; the blurred result is written through an exclusive no-follow descriptor inside a random private temporary directory.

Cards also show the repository branch, clean/changed state, ahead/behind counts, and merge conflicts. A Needs You strip calls out agents that appear blocked, waiting for input, or finished for review, plus repositories being shared by multiple live agents.

Active Needs You signals get a faint breathing outline (the module chip glows too if the card is removed). Click the signal to focus it, 10M to snooze it for ten minutes, or × to dismiss that signal for the lifetime of its process. Snoozes and dismissals persist between the wallpaper and fullscreen overlay.

The card's alert controls send deduplicated Omarchy notifications when an agent is blocked, waiting for an answer, ready for review, or ends — and, when the terminal title says so, when it has crashed (Infomarchy reads titles and /proc; it has no exit-status feed, so a silent segfault reports as an ended session). A signal that clears and later fires again is a new episode and notifies again. Alerts are enabled globally by default; each currently active provider can be muted independently, and QUIET 22–08 suppresses overnight delivery. Event fingerprints persist for seven days, so restarting the shell never replays old alerts. A disappeared session must be absent from two consecutive polls before Infomarchy reports that it ended. Clicking an alert opens the fullscreen desk.

Click a Needs You signal → jump straight to that agent's terminal. From the fullscreen overlay, Infomarchy closes itself after focusing the session.

Click a card → Infomarchy focuses the terminal window hosting that agent. It walks the process tree up to the Hyprland client, so it works through kitty, alacritty, ghostty, tmux, whatever.

Right-click a card → inspect it in place. The centered inspector shows its window, shortened session identity, workspace, uptime, pid, and repository state. From there you can focus the existing window or open a fresh terminal in the project directory; paths are passed as process arguments, never evaluated as shell text.

🟢 Usage & limits — what it's costing you

Usage and limits card

Above the limit meters sits a 7-day trend: one line per provider, tokens processed per day, three gridlines, hover any day for the exact figures. The TOKENS / ≈ $ VALUE chip switches the same lines to an estimated API value — what those tokens would have cost at published API prices — and each provider row shows today's and lifetime estimates, the share of lifetime tokens that were cache reads, and the session count. Prices come from a pinned, attributed LiteLLM snapshot (pricing.json, see THIRD_PARTY_NOTICES.md); models missing from it are shown as unpriced rather than guessed, and the estimate is never your subscription bill. All of it is read from the omarchy.agents usage cache — no new scanning.

Session (5-hour) and weekly (7-day) rate-limit meters with time-to-reset, today's prompt count and token volume, per subscription. Meters turn yellow past 60% and red past 85%, in your theme's yellow and red.

Provider chips filter the card interactively. Toggle PERCENT / FORECAST to project each recognized 5-hour or 7-day meter to reset from its elapsed-window pace; young or malformed windows say learning instead of showing a misleading number.

Infomarchy reuses the cache that Omarchy's own omarchy.agents bar widget maintains — enable that widget once and this card lights up. No extra logins, no API keys. Grok token totals are read from its local session files. Two optional outbound refreshes, both off by default, are described under Usage collection and refresh.

🟢 Local AI

Ollama up/down, every loaded model with its VRAM, GPU utilisation / memory / temperature, and lifetime totals per provider. Arrow controls select any locally installed model and show its parameter count, quantization, and disk size. LOAD pins the selected model in memory; each loaded row has its own UNLOAD action. Models at least 8 GiB—or larger than currently available accelerator/system memory—require a second CONFIRM click.

Model changes go through a bounded stdin-framed helper. It validates the model name against Ollama's live /api/tags or /api/ps inventory before using the documented empty /api/generate request with keep_alive: -1 (load) or 0 (unload). Infomarchy never pulls, deletes, or auto-loads a model.

🟢 FLEET — other machines' AI agents

One row per host named in INFOMARCHY_FLEET_HOSTS (a comma-separated list of ssh aliases — the same aliases you'd already use typing ssh yourself; user, identity file and proxy jump stay in ~/.ssh/config, never in this variable). Each host gets a status dot, provider chips for whatever it's running, and a relative "checked Ns ago" time. Invisible until you configure at least one host — the same "no tag until you run it" rule every other provider already follows.

Detection is one bounded, read-only ps call over ssh -o BatchMode=yes per host per refresh (30 s), matched against the identical providerOf() regex table local detection already uses — a remote Hermes, Claude, Codex, or anything else PROVIDERS recognises is identified exactly the way a local process is, just seen over a different channel. An unreachable host reads unreachable, never fabricated. INFOMARCHY_SKIP_FLEET=1 disables it from the collector's environment.

Per-session rows. When a host runs Infomarchy itself (and bun), its row expands into one line per remote session: project, whether it's working, and a NEEDS YOU tag when that agent is idle and waiting on you — the same attention state the local session cards use, derived on the far end by the same code. The card glows like the Needs You inbox does when any remote session is waiting. Clicking a session opens a terminal, sshes to that host and jumps straight into its tmux window and pane. A host that can't answer the richer probe keeps the plain ps row above unchanged, and is re-asked every 10 minutes instead of every refresh, so a fleet of ordinary ssh boxes costs nothing extra. Only session-level facts cross the machine boundary — provider, project basename, attention state, busy/idle, and the tmux address the jump needs. Window titles, prompt text, paths and git state stay on the host that produced them; INFOMARCHY_SKIP_FLEET_SESSIONS=1 turns the whole layer off and leaves the ps rows. See FLEET sessions.

When a host is running Hermes, USAGE & LIMITS gains a Hermes row too — no second API key to manage, since everything comes from files Hermes already keeps on that host. Token counts and the per-model breakdown come from Hermes's own local billing ledger (~/.hermes/state.db, read with sqlite3 -readonly); the dollar figures and a MONTHLY limit bar come from Hermes's cached snapshot of OpenRouter's own key-usage API (~/.hermes/workspace/openrouter_key_usage.json) when present. That split matters: the local ledger is only as old as Hermes's current session-tracking window, not real lifetime spend — verified live, where it undercounted true lifetime cost by roughly 30× — so the ledger's own per-row estimate is a fallback only, used when the key-usage file isn't there. The card's status line says which source produced the number you're looking at. Token counts and the per-model breakdown are model-agnostic — nothing is keyed to Deepseek or any other specific model, so switching Hermes's model shows up correctly on its own. The dollar figure currently assumes that model is still billed through OpenRouter, though: it's an account-level total, not scoped per model, so a model billed through a different provider entirely would need its own fix to be counted (see docs/fleet-remote-hosts.md).

🟡 Activity · last 7 days — when you actually work

7-day hourly activity heatmap

An hour-by-hour heatmap of prompts across every provider, newest day at the bottom, with a red tick at now. The dominant provider colours each cell; intensity is volume. Cells are local wall-clock hours, so on the two DST nights a year one hour is doubled up (fall) or absent (spring). Hover a cell for the exact breakdown ("Tue 18 Aug 16:00 · 8 prompts (Claude 6, Codex 2)"). Click an hour to filter Recent Tasks to that hour; click a provider in the legend to combine a provider filter. The selected cell and provider stay outlined, and clicking either again—or clear—removes that part of the filter. The header carries today/week counts per provider.

🟢 GitHub · last 7 days — what actually landed

Beside ACTIVITY is the identical grid fed from GitHub: commits, PRs, reviews, issues, comments and everything else (releases, forks, stars, branch creates) as other, each cell coloured by its dominant kind, the same red tick at now. Hover a cell for the breakdown plus the repositories involved ("Fri 4 Sep 23:00 · 9 events · commits 7 · PRs 2 · infomarchy, blip"). The header carries today/week counts per kind. There is no list to filter here, so a click pins a cell (its breakdown stays in the status line) and clicking a kind in the legend recolours the grid to that kind alone; clear or the overlay's A key resets all activity filters. In the overlay the module answers to key 4 (ACTIVITY is 3; the modules after it shift by one and 0 reaches the tenth).

Data comes through the already-authenticated GitHub CLI (gh), nothing else: commits from search/commits by author date (one row per commit, default branches only — a push to a feature branch shows once it lands), everything else from your own events feed, private repositories included. GitHub caps a search at 1000 rows and 30 calls a minute, so the week is filled in incrementally — one step a minute until the oldest day is covered (the status line says filling in older days meanwhile), then a five-minute refresh. Rows are cached in a private state file written by the wallpaper collector and read by the overlay, so a restart or a dropped connection shows the cached grid rather than an empty card — marked stale once fetches have been failing for fifteen minutes, with retries backing off to five minutes. Every six hours the week is walked again so a commit merged days after it was authored still lands in its hour. Switching gh accounts starts the store over. Without gh, or before gh auth login, the card says exactly that. INFOMARCHY_SKIP_GITHUB=1 in the collector's environment disables the fetch entirely. The enabled heatmap cards share the row and stack at smaller widths; hide any card using the module strip.

Gitea · last 7 days

GITEA uses the same seven-day heatmap, theme colors, hover details, pinned cells, and kind filters as GITHUB. It appears beside GITHUB and can be hidden with the GITEA module chip. Existing numbered module shortcuts keep their assignments; A clears all activity filters.

Configure a server with tea login add. Infomarchy reads ${XDG_CONFIG_HOME:-~/.config}/tea/config.yml, using the default login or the sole login. With multiple accounts, set INFOMARCHY_GITEA_LOGIN in the shell's environment to the exact tea login name. No dashboard-specific copy of the token is stored. HTTP and HTTPS servers, custom ports, and subdirectory installations are supported; HTTPS is the default for an address without a scheme. Requests never follow redirects with credentials.

Alternatively, supply both GITEA_HOST (the server base URL, without /api/v1) and GITEA_TOKEN. A host alone selects its matching tea login; a token alone is rejected so it cannot be sent to the wrong server. INFOMARCHY_SKIP_GITEA=1 disables the feed. Tokens must allow reading the authenticated user and their activity feed, including repository access for private activity.

The feed counts your own pushes, PR events, reviews, issue events, comments, and other supported activity. A push is one event, even if it contains several commits. This differs from GitHub's commit-search count. Gitea's activity retention and permissions determine the available history. Servers must provide /api/v1/users/{username}/activities/feeds.

The wallpaper collector refreshes every five minutes and fills older pages incrementally once a minute; the overlay reads the same private cache. Each attempt reads at most two pages, requests time out after four seconds, and the cache holds up to 6,000 recent events. Only timestamps, event IDs, kinds, and repository names are retained; commit messages, issue bodies, and credentials are discarded. Changing the configured account starts a new cache. Failed fetches back off and show cached data as stale after fifteen minutes. The whole window is reconciled every six hours.

⚪ Recent tasks — what got asked

The newest prompts across all providers — time ago, provider tag, project, and the prompt itself — so the question "what was I doing an hour ago?" has an answer on the wall. The list keeps up to 80 rows in a scrollable history, with a search box that matches prompt text, project, or provider (filtered searches can show up to 200 matches). Prompts whose exact agent session is still running stay bright and clickable; click one to jump to its terminal. Supported closed sessions are dimmed but remain interactive: hover for RESUME, then click to reopen that exact Claude, Codex, Grok, or OpenCode session in a terminal at its project directory.

Right-click a prompt for its action drawer: copy, pin/unpin, open the project, and review up to five recent prompts from the same session. Pins persist and sort above ordinary recency without changing the underlying history. Wheel and touchpad deltas are handled directly by the row beneath the pointer, and the wider scrollbar track can be clicked or dragged.

🔵 Operations intelligence — what changed, what needs you, what is healthy

Three compact cards sit beneath the live sessions:

  • What Changed fingerprints each active repository and highlights it until you inspect the newest state. It summarizes staged, untracked, test, addition/deletion, and commit data; expand a row to copy changed paths or open the project.
  • Next Actions turns terminal state into a short reason and an exact control: Answer, Resolve, Review, Resume, or Open Project. Permission/approval prompts, conflicts, failures, questions, and completed work no longer share one vague warning.
  • Project Health combines live agent count, branch, clean/dirty state, ahead/behind and conflicts, the last commit, and the newest GitHub Actions result when authenticated gh is available. Click a repository to filter sessions, prompts, changes, and action signals across the whole dashboard; click the project chip at the top to clear it.
All three cards are independently removable. Drag their headers left or right to reorder them; they snap into place and the order persists. The layout compacts automatically when one or two cards are hidden.

🟢🟡🔵 Machine — the boring numbers, in the corner where they belong

Machine stats card

| Meter | Colour | Detail | |---|---|---| | CPU | theme blue | % busy, 1-min load, hottest thermal zone | | RAM | theme green | used / total, % | | Disk | theme yellow | used / total per mount (btrfs subvolume twins collapsed) | | Wi-Fi | theme green | SSID, signal in dBm (bar = link quality), IPv4 | | WAN | theme cyan | cached external IPv4/IPv6 | | ↓ ↑ throughput | green | real-time bits/s (Kb/Mb/Gb) on the default route interface, wired or wireless | | ⇄ latency | green / yellow / red | live ping to Cloudflare 1.1.1.1 — red on timeout | | Battery | — | % and charging state, hidden on desktops |

Any meter goes red when it's genuinely in trouble (RAM > 90%, disk > 90%, CPU > 85%, ping dead).

The three right-column cards—Usage, Local AI, and Machine—also have draggable headers. Drag one far enough up or down to swap it with its neighbor; the card snaps into place and the order persists across overlay and shell restarts. Every section can still be removed and restored from the module strip.

⌨️ Two surfaces, one dashboard

The wallpaper is interactive wherever no window covers it (double-click or right-click the empty desk opens Omarchy's wallpaper switcher, as stock does). After configuring the shortcuts, press SUPER + I to hide the wallpaper dashboard and see the clean desktop; press it again to restore the cards. When you're buried in terminals, SUPER + D shows the desktop on top of everything — the wallpaper exactly as the desk paints it, with the dashboard when SUPER+I has it visible and the plain photo when it doesn't; Esc or a click on the backdrop dismisses it.

The module strip doubles as a keyboard command strip in the overlay: 1–9 toggle modules, J/K (or arrows) select a live session, Enter focuses it, A clears activity filters, and Esc closes. The selected session gets a bright outline.

Local development apps (optional)

Enable APPS in the module strip to register existing development commands and control their systemd user services: stable ports, HTTP readiness, checkout and branch, Open, Start/Stop, Restart, logs and configuration editing while an app is stopped. App package scripts stay unchanged. The helper uses the existing Bun runtime; no extra daemon or agent configuration is required. See Development apps for setup and CLI usage.

Web Mode

Web Mode makes the Infomarchy desk available in a browser on your phone, tablet, or another computer. It runs with the desktop plugin, so the computer and Omarchy shell must stay running. Open SETTINGS from the desk's module strip to manage access. The strip's WEB chip opens SETTINGS while WEB is off, and turns WEB off while it is on. omarchy-shell infomarchy toggleWeb turns WEB on only after the check SETTINGS runs for the chosen mode passes, and shows the reason in SETTINGS when it does not.

The page follows the live Omarchy theme and wallpaper. Wide screens use two columns. Narrow screens stack cards and offer UP/DOWN ordering. Module chips show or hide sections, and zoom is remembered for the current browser tab. Web section visibility and narrow-screen order are independent of the desktop layout but shared by web viewers. A successful refresh updates the page and theme every five seconds while preserving scroll position.

Web Mode displays sessions, recent tasks, activity, usage, local AI status, and machine telemetry. USAGE includes per-model meters and TOKENS · 7 days, with unavailable token counts omitted. MEDIA CONTROLS and the $ VALUE chart are absent. Browser controls change presentation. Desktop actions such as focusing sessions and loading models remain on the desktop.

Access and viewer credentials

| Mode | Reachability | Transport | Default port | | --- | --- | --- | --- | | PRIVATE HTTPS | Connected Tailscale devices permitted by your tailnet policy | HTTPS through Tailscale Serve to a loopback backend | 8788 | | MANUAL HTTPS | A configured private IPv4 interface or loopback, with the source allow list | Direct HTTPS using your existing certificate and private key | 8789 |

The modes are mutually exclusive. Selecting a different mode turns WEB off. Enable it again after reviewing the new setup. Private HTTPS supports viewing away from home through Tailscale. Public internet exposure and Funnel are outside the supported setup.

Pre-release builds of Web Mode also offered LAN HTTP. It was removed because it sent the dashboard and the viewer token over the network unencrypted. A saved LAN setting loads with WEB off and no mode selected, and WEB stays off until you choose PRIVATE HTTPS or MANUAL HTTPS. Nothing switches to another mode on its own. Viewer links from those builds are replaced once, the first time the desk reads them, because they may have crossed the network unencrypted. Labels and the allow list are kept. Share a new link with each viewer.

Each viewer link contains a bearer token: someone with the link and network access can use it. A viewer link is revealed only by COPY URL, SHOW QR, or bun web-server.ts url --reveal run in a terminal, and only once the listener is ready. Without --reveal, url prints the token id and the last four characters of the token. status, tokens and startup print token ids and four-character suffixes only. The QR helper hands the link to the desk encoded as a QR matrix, so treat its output like the link. Keep links and QR images out of public screenshots, logs, commits, and chat. Tokens are individually revocable. Turning WEB off stops access but keeps them for the next start.

Privacy follows the desktop

The browser shows PRIVACY ON/OFF · controlled on desktop. Use the desktop privacy chip or SUPER+SHIFT+I to change it: one press enables privacy, and three presses within two seconds disable it. Privacy is off until the desk saves it on. A missing, unreadable, or malformed setting reads as off in the browser exactly as it does on the desk, so the two never disagree.

With privacy on, the server omits WAN/LAN addresses, Wi-Fi SSID, and user/host identity, shortens home mounts, and sends recent prompts only through their first four words plus the mask. Full values are absent from the HTML and JSON, including hidden elements. Session topics are dropped, because a topic is keywords lifted from your prompts, and the desk drops them the same way. Project names and prompts of four words or fewer stay visible. GitHub login remains excluded at either setting. The JSON view also excludes desktop action arguments, session working directories, previews, and extra provider fields.

Turning desktop privacy off lets connected viewers receive the permitted full values. Changes apply to subsequent responses, normally at the next successful five-second refresh. Previously received or saved data cannot be retracted, and a disconnected page can retain its old content. Desktop source data and full-text COPY EXCERPT are preserved.

Set up Web Mode

Install and enable Infomarchy first using Install. Open the desk with SUPER+D, then SETTINGS. Choose the desktop privacy setting you want before sharing a viewer link.

Web helpers require Bun, flock (util-linux) and timeout (coreutils). SHOW QR uses qrencode. COPY URL uses wl-copy --sensitive from wl-clipboard, which asks clipboard managers not to keep the link in history. On Omarchy/Arch, install the optional viewer tools with sudo pacman -S --needed qrencode wl-clipboard. Tailscale process cleanup requires Python 3. The optional CA recipe requires OpenSSL, and its download helper uses Python 3.

Private HTTPS: through Tailscale

  1. Install and connect Tailscale on the desktop. On Omarchy versions that ship it, the built-in installer can be run with:
omarchy-install-service-tailscale

Follow its sign-in prompts. The installed Omarchy script starts the service, grants your local user Tailscale operator access, and adds a Tailscale admin-console web app and bar integration. If that installer is unavailable, use the official Tailscale installation guide. Infomarchy detects Tailscale but does not install it or sign in for you.

  1. Enable the tailnet prerequisites. In the Tailscale admin console's DNS page, enable MagicDNS, then enable HTTPS Certificates. Review the certificate-name disclosure shown there: certificate hostnames appear in the public Certificate Transparency ledger. See Tailscale's HTTPS setup. Infomarchy uses Serve to manage HTTPS, so you do not need to create certificate files yourself.
  2. Connect the viewing device. Install the Tailscale app on your phone or other device, sign in to the intended tailnet, and connect it. The Tailscale web app's device-enrollment QR flow can help with phone setup. Complete any device approval and ensure tailnet policy permits this device to reach the desktop on TCP 8788.
  3. Configure Infomarchy. Select PRIVATE HTTPS. CHECK PREREQUISITES inspects the installed CLI, connection, DNS name, and existing Serve configuration. Follow any message it displays, then click CONFIGURE & ENABLE. Wait for STARTING… to become WEB ON. The first real setup attempt may discover a missing certificate or permission prerequisite that the inspection could not confirm.
  4. Recover directly if setup fails. Read the message beside WEB FAILED, fix the reported prerequisite, and click RETRY SETUP. For example, if HTTPS certificates were disabled, enable them in the admin console and retry. CHECK PREREQUISITES only checks. It does not restart failed setup. There is no need to flip WEB off and on.
  5. Open the page using the selected viewer's COPY URL or SHOW QR, as described below. Keep Tailscale connected on both devices. Use the copied HTTPS hostname and port, including the viewer credential. A bare hostname or IP address is not the dashboard link.
Infomarchy owns a foreground Serve mapping on 8788, forwarding to its backend on 127.0.0.1:8787. The backend listens on loopback only, so there is no firewall rule to open for it and no background Serve mapping to create. If 8788 already belongs to another Serve or Funnel mapping, setup refuses to overwrite it. Resolve that specific conflict yourself. Unrelated services are preserved. Turning WEB off, stopping the listener, or removing the plugin removes its owned mapping while leaving Tailscale and unrelated services running.

If setup reports local permissions, make sure the user running Omarchy is allowed to manage Serve. The Omarchy installer configures operator access. If it reports a missing/stopped/signed-out client or an unsupported CLI, correct that condition and retry. For more detail, SETUP GUIDE opens Tailscale Serve documentation. Failed HTTPS setup never falls back to plain HTTP or public access.

Manual HTTPS: bring an existing certificate

This expert option uses certificate files you maintain. Starting without a CA? Follow Private LAN HTTPS without DNS or Tailscale below. Infomarchy binds the HTTPS listener and checks the certificate. You manage issuance, installation, DNS, client trust and renewal. The plugin does not create a CA, obtain certificates, change trust stores, or change DNS/firewall rules.

  1. Choose the hostname or private IPv4 address and network. To avoid DNS, enter the desktop’s private IPv4 address as both HOSTNAME / IPv4 and BIND IPv4, and use a certificate with that exact IP SAN. Otherwise, arrange for that hostname to resolve to your desktop's private LAN/VPN IPv4 address on each viewing device. Use that specific interface address for BIND IPv4. The safe default is 127.0.0.1, which permits local viewing only. Wildcard and public bind addresses are refused. infomarchy.localhost with loopback is useful for local testing without LAN DNS changes. Ports must be between 1024 and 65535, and the default is 8789.
  2. Prepare the certificate files. Use a PEM certificate chain with the server/leaf certificate first, followed by its intermediates, and an unencrypted PEM private key that matches the leaf. A hostname must be covered by DNS subject alternative names, and a literal address must match an IP subject alternative name (a DNS SAN containing IP text does not count). The files and their parent directories must be readable by the desktop user and protected against other users writing them. Use actual absolute paths without symlinks. The key must be owned by the desktop user or root and have mode 0600 or 0400. Infomarchy does not elevate privileges to read it. An existing certificate's signed hostname coverage cannot be changed by entering another hostname here.
  3. Obtain its SHA-256 fingerprint. For example:
openssl x509 -in /absolute/path/to/server-chain.pem -noout -fingerprint -sha256

Enter the hex fingerprint after the = sign, with or without colons. This identifies the exact leaf certificate, not its public key alone. Confirm it is the certificate you intend to serve.

  1. Configure the desk. Select MANUAL HTTPS, fill in hostname, bind address, port, certificate-chain path, private-key path and fingerprint, then SAVE CERTIFICATE SETTINGS. Saving changes turns Manual HTTPS off. After saving finishes, CHECK CERTIFICATE verifies file safety, the fingerprint, validity dates, SAN hostname/IP identity, matching key and supplied chain signatures. It does not change files, install trust, or start a listener. Click CONFIGURE & ENABLE, wait for WEB ON, then use COPY URL or SHOW QR. Startup checks the files again. A failure offers RETRY SETUP and never falls back to HTTP.
  2. Set up viewing clients. A certificate from a CA already trusted by that browser requires no additional CA installation. For a private CA or self-signed certificate, configure trust deliberately on each client. The fingerprint entered on the desk does not install browser trust. Keep the existing source allow list and any firewall rules limited to your trusted networks. VPN ranges outside loopback/RFC1918 need an explicit allowed CIDR. Tokens and desktop-owned privacy work exactly as in the other modes.
If the desktop’s IP changes, update the address and use a certificate covering the new IP, including its new fingerprint. Reserve the LAN address in DHCP for repeat use. .localhost names always refer to the viewing device itself, so they cannot be used to reach the desktop from a phone.

  1. Handle renewal. Install the renewed certificate/key and update the leaf fingerprint, then save and re-enable HTTPS. Certificate files are loaded at startup rather than automatically replaced in a running listener. Expiry stops disclosure and the listener shuts down within 30 seconds. Client trust and certificate-chain validation are still the client's responsibility.
For background, see Bun's TLS support and Mozilla's explanation of browser certificate trust.

Private LAN HTTPS without DNS or Tailscale

You can create your own CA and a certificate for the desktop's private IPv4 address using OpenSSL, then supply those files to Manual HTTPS. This is an operator-run setup. Infomarchy does not issue or renew certificates. The desktop and phone must be on a reachable trusted LAN. Reserve the desktop's address in DHCP if possible. These commands require Bash and OpenSSL, and the optional download helper requires Python 3.

Create the files. Replace 192.168.1.50 with the desktop's actual private IPv4 address (ip -4 addr shows interface addresses). Run this block once in a terminal. It creates a new directory and refuses to overwrite an existing setup. The CA lasts one year and the server certificate lasts 90 days. Both private keys remain protected by filesystem permissions. Keep the CA key private and securely backed up, since it can sign certificates trusted by your clients.

(
set -eu
umask 077
infomarchy_ip=192.168.1.50
infomarchy_pki="${XDG_STATE_HOME:-$HOME/.local/state}/infomarchy/pki/private-lan"
mkdir -p "$(dirname "$infomarchy_pki")"
mkdir -m 700 "$infomarchy_pki"
cd "$infomarchy_pki"

openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout ca.key -out ca.crt -days 365 -subj '/CN=Infomarchy private LAN CA' \ -addext 'basicConstraints=critical,CA:TRUE,pathlen:0' \ -addext 'keyUsage=critical,keyCertSign,cRLSign' \ -addext "nameConstraints=critical,permitted;IP:$infomarchy_ip/255.255.255.255,permitted;DNS:infomarchy.invalid" openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout server.key -out server.csr -subj '/CN=Infomarchy LAN dashboard' cat > server.ext <<EOF basicConstraints=critical,CA:FALSE keyUsage=critical,digitalSignature extendedKeyUsage=serverAuth subjectAltName=IP:$infomarchy_ip EOF openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out server.pem -days 90 -extfile server.ext cat server.pem ca.crt > server-chain.pem openssl verify -CAfile ca.crt -verify_ip "$infomarchy_ip" server.pem openssl x509 -in server.pem -noout -fingerprint -sha256 pwd )

The CA's IP constraint permits the chosen address, and its DNS constraint permits only infomarchy.invalid and subdomains. The leaf contains only the chosen IP SAN. See OpenSSL's extension syntax. Keep this CA dedicated to this setup. Do not share ca.key or server.key, or serve the certificate directory over HTTP.

Configure Manual HTTPS. Use your desktop IP for both HOSTNAME / IPv4 and BIND IPv4, port 8789, and the absolute paths to server-chain.pem and server.key in the directory printed above. Enter the server certificate fingerprint printed by OpenSSL. Save, check the certificate, then enable WEB.

Allow incoming connections. With UFW, the following example permits only one phone to reach the listener. Substitute your Wi-Fi interface, phone IP and desktop IP:

sudo ufw allow in on wlo1 proto tcp from 192.168.1.60 to 192.168.1.50 port 8789 comment infomarchy-manual

Use equivalent scoped rules for other firewalls. A successful request from the desktop itself does not test incoming firewall access. Do not configure router port forwarding. Guest Wi-Fi/client isolation may still prevent access.

Install the public CA on the phone. Transfer only ca.crt by USB or another trusted transfer method. On Android, open Settings and find Encryption & credentials → Install a certificate → CA certificate, then select the file. Menu names vary by device. See Google's certificate instructions. Install it as a CA certificate, not a Wi-Fi or client certificate. Other viewing devices need their own browser/OS trust setup. The dashboard fingerprint does not install client trust.

For a temporary LAN download instead of USB, copy only the public CA into a new, separate directory and serve that directory in a foreground terminal:

infomarchy_public=$(mktemp -d)
cp "${XDG_STATE_HOME:-$HOME/.local/state}/infomarchy/pki/private-lan/ca.crt" "$infomarchy_public/infomarchy-ca.crt"
python3 -m http.server 8790 --bind 192.168.1.50 --directory "$infomarchy_public"

Substitute the desktop IP. Temporarily allow TCP 8790 with the same phone/interface/address restriction as 8789, then download http://192.168.1.50:8790/infomarchy-ca.crt on the phone. Before trusting a CA transferred over HTTP, compare its SHA-256 fingerprint in the phone's certificate details with openssl x509 -in /absolute/path/to/ca.crt -noout -fingerprint -sha256 on the desktop. Use USB if the phone cannot show it. This CA fingerprint is separate from the server fingerprint entered in Infomarchy.

After transferring, press Ctrl+C, remove the temporary directory with rm -r -- "$infomarchy_public", and remove the download firewall rule:

sudo ufw delete allow in on wlo1 proto tcp from 192.168.1.60 to 192.168.1.50 port 8790

Open the dashboard. Once the CA is installed and WEB is on, use SHOW QR on the desktop and open the result in Chrome on Android. The CA download address is not the dashboard address. A long timeout usually calls for checking the address, listener, firewall and Wi-Fi isolation, and a certificate error calls for checking CA trust, IP SAN, dates and the device clock. Do not bypass certificate errors.

Maintain or retire the setup. Renew the leaf before 90 days, signing a new CSR with the protected CA and the same I

... (README truncated for length)

Chat with me