gledach / the shelf / whenrun

whenrun · slot 03 · shipped

Run it when the
power is cheap.
Nothing to install.

Watches day-ahead electricity prices and the renewable-share forecast, works out the cheapest or greenest window that still meets your deadline, and starts the job there.

MIT licensed · no account, no API key · 0 runtime dependencies · 128 offline tests

gledach-whenrun prices real output
  today     ▄                          176 EUR/MWh  (1h left)
  tomorrow  ▆▅▅▅▅▅▇█▇▆▅▃▂▁▁▃▅▇██▇▅▅▄     72 to 231 EUR/MWh
            00:00 06:00 12:00 18:00

  len  when                     spot          you pay
  1h   tomorrow 13:15 to 14:15  68.3 EUR/MWh  31.3 ct/kWh delivered
  4h   tomorrow 11:30 to 15:30  86.6 EUR/MWh  33.5 ct/kWh delivered

  Greenest 2h  tomorrow 12:30 to 14:30  101% renewable
German power moved by a factor of three that day. The renewable share passed 100% because exported surplus counts against domestic load.

The situation

The information has been free the whole time.

Power can cost three times more in the evening than it does at midday, and your backup job does not care when it runs.

The auction is public

Day-ahead prices for the next twenty four hours are published every afternoon, free, for anyone who goes and collects them. So is the renewable-share forecast.

The tariff exists

Since January 2025 every German electricity supplier has had to offer a dynamic tariff. The price you pay can follow the auction if you ask it to.

What was missing

Something that acts on both without tying you to one supplier's app. That is the whole of this tool, and it is the first one on this shelf that acts rather than reports.

Running it

There is no npm install, because there is nothing to install.

A fresh clone with Node 22 on it does everything below. Storage is a file on disk. No account, no key, no build step.

git clone https://github.com/gledach/whenrun && cd whenrun
node bin/whenrun.mjs prices     # what power costs today
node bin/whenrun.mjs where      # your location, and every zone
node bin/whenrun.mjs doctor     # config, connectivity, data freshness

It ships set to postcode 82319 Starnberg in bidding zone DE-LU. where prints that and every zone it supports, and cp config/location.default.mjs config/location.local.mjs changes it for good. The local copy is gitignored, which is the right place for your postcode if you would rather it were not in a published repository.

The commands

Command What it does
gledach-whenrun prices Today and tomorrow, with the cheap windows marked.
gledach-whenrun plan When should this run, and what will it cost. Takes --duration, --kw, --by and --objective, or runs every task in your config.
gledach-whenrun run --task=backup A dry run. Add --execute and it waits for the window, then starts the job.
gledach-whenrun daemon --execute All enabled tasks, continuously. --once makes it a single pass for cron or Task Scheduler.
gledach-whenrun report What it actually saved, against the delivered bill rather than the spot price.
gledach-whenrun where Your location and every bidding zone it knows.
gledach-whenrun doctor Config, connectivity and data freshness. Warns while you are still on the shipped tariff defaults.

The command is gledach-whenrun. npm link puts that name on your PATH. The copy block above calls the same file and needs no link. Nothing named whenrun is installed. Three objectives: cheapest minimises price, greenest maximises renewable share, balanced blends the two. A job marked interruptible may be scattered across non-adjacent slots. Otherwise it gets one unbroken block. Your task is told what it was scheduled into through the environment: WHENRUN_WINDOW_START, WHENRUN_WINDOW_END, WHENRUN_SPOT_EUR_MWH, WHENRUN_RENEWABLE_PCT and WHENRUN_SLOTS.

Safety

This tool runs commands unattended.

Which is a thing worth being careful about rather than quiet about. Four rules it holds to, all of them mechanical.

The command comes from your task file

Never from a price feed, never from an argument, never from the network. There is no code path that builds a command out of fetched data.

Two independent locks

A task needs enabled: true and the command needs --execute. A dry run is the default everywhere.

It refuses rather than guesses

Stale price data, an impossible deadline, or a window that closed while the daemon was asleep each produce a refusal with a reason, not a best effort.

The agent surface is read only

An agent can ask when power is cheap. It cannot start anything.

What the savings number means

The spot price is not your bill.

A German bill is roughly spot plus supplier markup plus grid fees plus levies plus taxes plus VAT, and only the first of those moves by the hour. So if spot halves, your bill does not.

gledach-whenrun report real output
saving   0.04 EUR (31.5% of this job's bill)   11% of its energy component

Both numbers, both labelled. The flattering one is always the figure marked as the energy component. report also prints your standing charge for the period, because no amount of load shifting changes it and a savings figure without that context flatters the tool.

Where the numbers come from

Two sources, neither of which needs a key.

doctor cross-checks the two against each other and warns when they disagree by more than a rounding error.

Source Zones Key Resolution
Energy-ChartsFraunhofer ISE DE-LU, AT, FR, NL and more none 15 min
aWATTar DE, AT none 60 min

Price data is CC BY 4.0 from Bundesnetzagentur and SMARD.de by way of Energy-Charts. Verified against live data for DE-LU, AT, FR, CH, NL, ES and PL. A bidding zone is not a country: Germany and Luxembourg share DE-LU, Denmark is split at the Great Belt, and Italy and Norway have several. The renewable-share forecast is published per country and derived from the zone, so you only set one thing.

For agents

Six read-only tools over MCP.

Every result carries a coverage block with a freshness status and a trustEmptyResult flag, so an agent can tell prices are flat from I have not been able to look since Tuesday.

node mcp-server.mjs

get_prices  ·  cheapest_window  ·  greenest_window
list_tasks  ·  plan_task        ·  savings_report

What it cannot see

Stated here rather than buried in a wiki.

Every tool on this shelf ships a written audit of its own blind spots. This is whenrun's, in full.

  • Your actual tariff. It uses the numbers in your tariff file. If those are wrong, every euro it reports is wrong by the same proportion. doctor warns while you are still on the shipped defaults.

  • Whether you are on a dynamic tariff at all. On a fixed tariff it will still tell you when power is cheap, and you will still be charged the same.

  • Your own generation. Solar on your roof changes the answer completely and is not modelled.

  • Grid fees that vary by time. Modelled as flat. Where a time-varying network tariff applies, the cheap window may not be exactly where this says it is.

  • Whether your job really draws what you declared. kw is your estimate, not a measurement.

  • It cannot pause a running command. interruptible shapes which slots are chosen and passes the list to your task in WHENRUN_SLOTS. Honouring the split is your script's job.

Tests: 128, fully offline, no network. A separate live suite checks that the real APIs still answer in the expected shape, and refreshes the captured fixtures. The offline fixtures are captured from the live APIs rather than handwritten, so they test what the API does rather than what someone remembered it doing.