Omahub
← All plugins
S

OpenRouter

by SpoilHeap

Remaining OpenRouter credit in the bar, with purchased-versus-spent, key caps, and today/week/month spend in a popup.

Security review

Review recommended · 1 finding

Deterministic scan — not a security guarantee

Low
Risk level
Low
Analyzed commit
b443c42
Scanned
1 month ago

Flagged patterns appear only in documentation files (README / docs) — descriptive examples, not executable code.

  • Docs external_hosts README.md:17

    Downloads or connects to an external HTTP(S) host.

    git clone https://github.com/SpoilHeap/Omarchy.OpenRouter.git ~/.config/omarchy/plugins/io.github.spoilheap.openrouter

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
b443c42
Reviewed
1 month ago

No malicious, obfuscated, or destructive code was found. The plugin handles the OpenRouter API key carefully (stdin-based transfer, mode 600 file, HTTPS only), and the deterministic finding is just the README's `git clone` install snippet, which is documentation and not executed by the plugin. The only noteworthy risk is the intentional `keyCommand` setting, which runs a shell command the user configures.

  • The `keyCommand` setting is passed to `bash -lc` and executes arbitrary shell code; it is user-controlled and documented, but users should only set it to a trusted command.
  • The API key is stored in plaintext on disk at `~/.config/omarchy/openrouter/key` with mode 600; this is expected and reasonably protected, but it is still a secret at rest.
  • By default the plugin sends the API key to OpenRouter's API over HTTPS; if `OPENROUTER_API_BASE` is set by the user to another host, the key could be sent there, so the environment should not be set to untrusted values.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/SpoilHeap/Omarchy.OpenRouter --enable
Widgets #bar #ai

Omarchy.OpenRouter

An Omarchy shell plugin: remaining OpenRouter credit in the bar, and the ledger behind it in a popup: what was purchased against the balance, what this key is capped at, and what has been spent today, this week, and this month.

  $74.75

Bar icon and popup showing account balance, spend today/week/month, and actions to add credits, view activity, or replace the key

Install

git clone https://github.com/SpoilHeap/Omarchy.OpenRouter.git ~/.config/omarchy/plugins/io.github.spoilheap.openrouter
omarchy plugin enable io.github.spoilheap.openrouter
omarchy restart shell

What the figure is

OpenRouter answers "how much is left" from three different endpoints, and which ones a key may read depends on the key:

Endpoint Needs Gives
/api/v1/credits usually a management key the account ledger: credits purchased and credits used
/api/v1/key any inference key this key's spend cap and its own lifetime spend
/api/v1/activity a management key account-wide spend by day, for the last 30 days

All three are asked on every refresh and whichever answers, answers. The hero line names the figure being shown so the panel never leaves it ambiguous:

  • Account balance — purchased minus used, the real remaining credit.
  • Key spend cap — no management key, but this key has a cap, so what is left of the cap is the honest answer.
  • Spend only — no management key and an uncapped key. There is no remaining figure to give, so the panel shows spend and says why.

The meter under the balance shows how much of today's starting balance is left, so it moves day to day instead of sitting pinned near-empty for the rest of a long-funded account's life. Today's spend can't come from /api/v1/activity — that endpoint only reports on UTC days once they're over, and refuses to say anything about the one in progress — so instead the widget records the account's lifetime spend the first time it polls each UTC day, to a small state file (~/.config/omarchy/openrouter/today-spend.json), and every later poll that day is that much spend since. This needs /api/v1/credits, which usually needs a management key; without one the meter falls back to the lifetime purchased-vs-spent ratio instead. The SPEND section's week and month rows add today's live figure on top of whatever /api/v1/activity has already settled for the earlier days in the window — that also needs a management key, and says so when it's missing. A management key is free to create at openrouter.ai/settings/management-keys and, unlike an inference key, cannot itself make chat requests — so it can be pasted into this widget without being able to run up a bill on its own.

Because today's spend is tracked from the moment this widget first records a baseline, whatever was spent earlier that same day — before the baseline existed — isn't in it. The figure is accurate from install onward, not retroactively.

The API key

Checked in this order, first hit wins:

  1. the keyCommand setting, for a secret manager
  2. $OPENROUTER_API_KEY, then $OPENROUTER_KEY
  3. ~/.config/omarchy/openrouter/key
  4. ~/.config/openrouter/key

The easiest way in is the panel itself: with no key found, it shows a field to paste one into. Save it (Enter, or the check icon) and the widget writes it straight to ~/.config/omarchy/openrouter/key and refreshes. Once a key is on file, the same field reappears from the "Replace API key" action if you ever need to swap it.

Editing the file by hand works just as well:

$EDITOR ~/.config/omarchy/openrouter/key
chmod 600 ~/.config/omarchy/openrouter/key

Out of a secret manager instead, with no key on disk:

omarchy bar set io.github.spoilheap.openrouter keyCommand 'pass show openrouter/api-key'

Keys come from openrouter.ai/settings/keys. For the account balance rather than a key cap, that has to be a management key.

The key never reaches a command line at any point: typed input goes over stdin to save-key.sh, which writes it with mode 600; credits.sh likewise hands it to curl through a config file on stdin. Neither appears in ps or in argv. When keyCommand or an environment variable is set, that source takes precedence over the file and the panel says so — a key saved from the UI in that case is written to the file but won't be used until the higher-priority source is unset.

Uninstall

omarchy plugin disable io.github.spoilheap.openrouter
rm -rf ~/.config/omarchy/plugins/io.github.spoilheap.openrouter
omarchy restart shell

That removes the plugin's own checkout, but not the API key: it's written to ~/.config/omarchy/openrouter/, outside the checkout, so a saved key survives a reinstall. To also remove the key and the today's-spend state file:

rm -rf ~/.config/omarchy/openrouter

If the key came from keyCommand or an environment variable instead of the file, clear that too — it isn't touched by either command above.

Interactions

  • Bar icon — left: panel · right: refresh without opening · middle: top-up page
  • Panel — j/k move the cursor, Enter activates it (or refreshes when nothing is selected), r refresh, t top up, a activity, Tab moves to the neighboring bar panel, Esc closes
  • IPC — omarchy-shell io.github.spoilheap.openrouter <open|close|toggle|refresh|status|remaining>
$ omarchy-shell io.github.spoilheap.openrouter status
Account balance: $74.75

Settings

In this widget's entry in ~/.config/omarchy/shell.json, or via omarchy bar set io.github.spoilheap.openrouter <key> <value>:

Key Default What it does
refreshIntervalSec 300 How often OpenRouter is asked
lowBalance 5 Turn the bar figure urgent at or below this many credits
barLabel "Amount" "Icon only" keeps the bar to the icon alone
keyCommand "" Shell command that prints the key

Numbers need --json, or they land in shell.json as strings:

omarchy bar set io.github.spoilheap.openrouter refreshIntervalSec 900 --json
omarchy bar set io.github.spoilheap.openrouter lowBalance 10 --json
omarchy bar set io.github.spoilheap.openrouter barLabel 'Icon only'

A vertical bar has no room for a figure and falls back to the icon on its own, whatever barLabel says.

Files

File What it is
manifest.json plugin declaration and settings schema
Panel.qml the bar button and the popup
Service.qml runs the helper on a timer, holds the result
Model.js formatting — money, ages, section wording
credits.sh resolves the key, asks OpenRouter, prints one JSON object
save-key.sh writes a key pasted into the panel to the key file, over stdin

credits.sh also keeps ~/.config/omarchy/openrouter/today-spend.json, its baseline for today's spend — safe to delete; it's just rebuilt on the next poll.

credits.sh always exits 0 with JSON, so a refused key or an unreachable API is a state the panel renders rather than an error it swallows. It can be run by hand to see exactly what the widget sees:

bash ~/.config/omarchy/plugins/io.github.spoilheap.openrouter/credits.sh | jq .

Editing any file here reloads the plugin. If a change does not take — QML that is already loaded can hold on — force it with omarchy restart shell.