Setup guides
Everything in Refill that needs setting up, step by step. Pick a section on the left.
Accounts
What you get. Refill finds the Claude Code, Codex, Copilot and Gemini logins already on your Mac, and you can add more. Cursor stays one login. You can rename any of them.
What Refill finds on its own
- Claude Code: the default
~/.claudeand every~/.claude-*and~/.claude_*folder. Each folder is one login. - Codex CLI:
~/.codex(this keeps the account idcodex:default), the folder inCODEX_HOMEwhen that is different, and every~/.codex-*and~/.codex_*folder. Read offline from each home'ssessionslogs. It updates when you use that home. Turn every Codex home off with Settings → Accounts → Codex CLI. - The Copilot, Cursor and Gemini sections below cover those tools.
Add a second Claude account
- Open Settings → Accounts. Under Add a Claude account, type a name such as
work, then click Add. Use letters, digits,-and_. - Terminal opens with a separate Claude profile in
~/.claude-work. Type/loginand finish signing in. - Type
/exit, close the window, and click Refresh in Refill. The account appears under Detected.
From a clone of the repo you can do the same in a terminal. It sets CLAUDE_CONFIG_DIR and starts Claude Code.
scripts/add-claude-account.sh workTo use that account later, run Claude Code with its folder:
alias claude-work='CLAUDE_CONFIG_DIR=$HOME/.claude-work claude'Add a second Codex account
- Under Codex CLI, type a name such as
work, then click Add. - Terminal opens with
CODEX_HOMEset to~/.codex-work, runscodex login, then starts Codex once so a session log exists. - Quit Codex, close the window, and click Refresh.
scripts/add-codex-account.sh workalias codex-work='CODEX_HOME=$HOME/.codex-work codex'The default ~/.codex stays codex:default even when CODEX_HOME points at it, so history and hidden accounts from older Refill builds still match. A ~/.codex-* folder with no sessions is hidden until you log in, unless you list it under Extra Codex folders. The Add button writes that path for you.
Folders outside the automatic names
Put one path per line in Extra Claude folders, Extra Codex folders, or Extra Gemini folders. ~ is fine.
Rename an account
Click ⋯ next to an account (or right-click it) and choose Rename…. The name is stored for that account id and shown in the menu, the notch, the dashboard, widgets, history and notifications. Leave it blank and save to go back to the email, then the folder name.
Hide, show or remove an account
- Hide from Refill skips that account. It is not checked and never alerts. This works for Claude, Codex, Copilot, Gemini and Cursor. Bring one back under Hidden → Show. There is no separate mute: hide is how you silence one account.
- Move profile to Trash… is offered for extra Claude, Codex and Gemini profile folders. It signs that profile out on this Mac and can be undone from the Trash. The default
~/.claude,~/.codexand~/.geminiare never offered. Copilot and Cursor have no profile folder to trash.
If it fails
Not signed in. Run claude and type /login.: that profile has no login. Do the Claude steps above.Login expired. Run claude once to renew it.: run Claude Code once in that profile. Or turn on Settings → Accounts → Renew expired logins so Refill renews it. Leave it off if Claude Code runs all day.Rate limited. Next try at …: Refill pauses that account after an HTTP 429 and retries by itself.No Codex sessions yet: use that Codex home once. Numbers update only when Codex writes a session log.
GitHub Copilot
What you get. Monthly premium-request and chat quota for every GitHub login that has a Copilot seat.
Steps
- Install the GitHub CLI and sign in with each account that has Copilot.
gh auth loginadds another login.gh auth statuslists them. - Refill reads each
github.comlogin withgh auth token --user <login>. The first login it tracks stayscopilot:default, so switching the activeghuser does not reshuffle history. Other logins arecopilot:<login>. - Check Settings → Accounts → More providers → GitHub Copilot is on. Click Refresh.
brew install ghgh auth loginIf gh has no github.com login, Refill falls back to the single editor token in ~/.config/github-copilot (apps.json or hosts.json) as copilot:default. Enterprise hosts are ignored. The Copilot editor itself is one login. Usage resets monthly. The endpoint is unofficial and can change.
If it fails
No GitHub token (run gh auth login): sign in with the command above.No GitHub token for <login>: that login has no token. Rungh auth loginagain for it.HTTP 401or403 (token rejected or no Copilot seat): that GitHub account has no Copilot seat. Check withgh auth status. Hide the row if you do not want it listed.
Cursor
What you get. Monthly usage for the Cursor plan signed in on this Mac, with no extra login.
Steps
- Install the Cursor app and sign in to it.
- Check Settings → Accounts → More providers → Cursor is on. Click Refresh.
Refill reads the sign-in from Cursor's local database, ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb, in read-only mode. It then asks cursor.com for your usage. The billing cycle end is the reset.
Cursor stores one login per Mac user. Refill does not invent a second Cursor account. A second person needs their own macOS user, or you switch the login inside Cursor (that replaces the one Refill shows, id cursor:default). You can still rename or hide it.
If it fails
Not signed in to Cursor: open Cursor and sign in.HTTP 401or403 (session expired, reopen Cursor): open Cursor so it renews its session.- Cursor missing from the list: Refill skips it when the database file does not exist.
Gemini CLI
What you get. Per-model quota for every Gemini CLI login, with Pro and Flash tracked separately.
Steps
- Install Gemini CLI, run it once and sign in with Google. The default login is
~/.gemini/oauth_creds.json, and that account staysgemini:default. - Check Settings → Accounts → More providers → Gemini CLI is on. Click Refresh.
npm install -g @google/gemini-cligeminiAdd another Gemini account
Gemini CLI treats GEMINI_CLI_HOME as a home directory and writes creds to $GEMINI_CLI_HOME/.gemini/oauth_creds.json, not to the home itself.
- Under Add a Gemini account, type a name such as
work, then click Add. - Terminal opens with
GEMINI_CLI_HOMEset to~/.gemini-accounts/work. Sign in, then exit. - Click Refresh. The login lives at
~/.gemini-accounts/work/.gemini/oauth_creds.json.
scripts/add-gemini-account.sh workalias gemini-work='GEMINI_CLI_HOME=$HOME/.gemini-accounts/work gemini'A folder you already use can be listed under Extra Gemini folders. Point it at the directory that contains oauth_creds.json, or at a GEMINI_CLI_HOME whose .gemini child contains that file. ~/.gemini-* and ~/.gemini_* folders in your home directory are picked up on their own when they hold creds.
When the saved token expires, Refill refreshes it in memory and never writes it back. It needs the installed CLI to find the public OAuth client, or you can set GEMINI_OAUTH_CLIENT_ID and GEMINI_OAUTH_CLIENT_SECRET. The client is shared. Each account keeps its own token.
If it fails
No Gemini CLI login: rungeminiand sign in.Gemini token refresh failed (run gemini once): rungeminionce so it renews its own login, then refresh.Quota request failed: Google rejected the request. Try again later.
ntfy
What you get. Push notifications on your phone. Free, no account.
Steps
- Install the ntfy app on your phone (iOS and Android).
- Open Refill, then Settings → Integrations → Add → ntfy (phone push). Refill fills in a random topic like
refill-1a2b3c4d. - In the ntfy app, tap + and subscribe to the same topic on the same server.
A topic on ntfy.sh works like a password. Anyone who knows the name can read and publish to it, so keep the random one.
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| Server | https://ntfy.sh, or the URL of your own ntfy server. Leave it empty for ntfy.sh. |
| Topic | The topic you subscribed to in the app. Required. |
| Access token (optional) | A tk_… token, only if your server protects the topic. Sent as a Bearer token. |
Send a test
Click Send test. Refill shows OK 200 when the service accepted the request.
What Refill sends
A JSON POST to the server root for tests, warnings and empty tanks, and for a reset when the Mac is awake. Priority is 4 for a reset and 3 for everything else. Tags are zap, warning, battery or droplet (test).
{
"message": "If you see this, the pipes work. Drip approves.",
"priority": 3,
"tags": ["droplet"],
"title": "Testing, testing",
"topic": "refill-1a2b3c4d"
}Resets while the Mac sleeps or is off
With ntfy enabled and Send on → Refill on, Refill also posts a delayed message for each window that has been used and has a reset time. ntfy holds it and delivers it about 30 seconds after the reset, using the At header, even if the Mac is asleep or powered off. The message id is stable for that account and window (refill- plus a short hash of the account id and window). Publishing the same id again replaces the pending push, so a changed reset time does not leave a duplicate. Cancelling deletes that id.
The access token stays in ~/.config/refill/integrations.json on the Mac. Refill's note of what it scheduled is ~/.config/refill/ntfy-schedule.json (mode 0600) and contains no token. The delayed body is the account's display name and the window, for example Work: 5h session is full again. The title is Refilled.
ntfy.sh accepts a delay from 10 seconds up to 3 days. A weekly reset further out is scheduled once the Mac is awake inside that window. A limit that starts while the Mac is off is scheduled the next time Refill sees it.
If the Mac is awake at the reset, Refill sends the normal ntfy message immediately and deletes the delayed one, so you get one push. If the Mac slept through delivery, the delayed push already went out, and the alert after wake does not send a second ntfy message. The local notification on wake still appears.
If Also mute phone and chat pushes is on and the delivery hour falls inside quiet hours, that reset is not scheduled.
An external script that posted the same ids is no longer needed. If you installed the LaunchAgent local.refill.ntfy-schedule, remove it so it does not publish a second copy:
launchctl bootout gui/$(id -u)/local.refill.ntfy-schedule
rm -f ~/Library/LaunchAgents/local.refill.ntfy-schedule.plistIf it fails
HTTP 403: the topic is protected. Add an access token.OK 200but no push: the topic in the app differs from the one in Refill. Compare them character by character.Missing fields: the Topic field is empty.
Pushover
What you get. Push notifications on iPhone, Android and desktop through Pushover.
Steps
- Create an account at pushover.net and install the Pushover app on your phone. The app is paid after a trial, so check their pricing.
- Copy Your User Key from the top of the Pushover dashboard.
- Go to pushover.net/apps/build, name the application
Refilland create it. Copy the API Token/Key. - Open Refill, then Settings → Integrations → Add → Pushover (phone push).
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| App token | The API token of the application you just created. |
| User key | Your user key. Not the app token. |
Send a test
Click Send test. Refill shows OK 200 when the service accepted the request.
What Refill sends
token=APP_TOKEN&user=USER_KEY&title=Testing%2C%20testing&message=If%20you%20see%20this%2C%20the%20pipes%20work.%20Drip%20approves.If it fails
HTTP 400withapplication token is invalid: the app token is wrong, or you pasted the user key into it.HTTP 400withuser identifier is invalid: the user key is wrong, or the two values are swapped.OK 200but no alert: the Pushover app has no device registered. Open it and sign in.
Telegram
What you get. Messages from your own Telegram bot, in a private chat or a group.
Steps
- In Telegram, open @BotFather and send
/newbot. Pick a name and a username ending inbot. BotFather replies with a token like123456:ABC…. - Open your new bot, press Start and send it any message, for example
hi. - Open
https://api.telegram.org/bot<token>/getUpdatesin a browser, with your token in place of<token>. Find"chat":{"id":123456789. That number is your chat ID. - Open Refill, then Settings → Integrations → Add → Telegram bot.
For a group, add the bot to the group, send a message there, and read the ID from getUpdates. Group IDs are negative, like -1001234567890. See the Bot API docs.
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| Bot token | The token from BotFather. |
| Chat ID | The number from getUpdates, including the minus sign for groups. |
Send a test
Click Send test. Refill shows OK 200 when the service accepted the request.
What Refill sends
{
"chat_id": "123456789",
"text": "Testing, testing\nIf you see this, the pipes work. Drip approves."
}If it fails
getUpdatesreturns an emptyresult: message the bot first, then reload.HTTP 401: the token is wrong. Copy it again from BotFather.HTTP 400 chat not found: wrong chat ID, or you never pressed Start.HTTP 403: you blocked the bot, or it was removed from the group.
Discord
What you get. An embed in a Discord channel, colored by event.
Steps
- In Discord, open Server Settings → Integrations → Webhooks and click New Webhook. You need the Manage Webhooks permission. See Discord's guide.
- Name it, choose the channel, and click Copy Webhook URL.
- Open Refill, then Settings → Integrations → Add → Discord.
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| Webhook URL | https://discord.com/api/webhooks/…. Treat it like a password. |
Send a test
Click Send test. Discord answers with an empty success, so Refill shows OK 204.
What Refill sends
The embed color is the event color as a number: 13172557 (green, reset and test), 16758087 (amber, warning), 16732220 (red, empty).
{
"username": "Refill",
"embeds": [{
"title": "Testing, testing",
"description": "If you see this, the pipes work. Drip approves.",
"color": 13172557
}]
}If it fails
HTTP 404: the webhook was deleted. Create a new one.HTTP 429: Discord is rate limiting the webhook. Wait a minute.Missing fields: the URL is empty or not a fullhttps://address.
Slack
What you get. A message in a Slack channel through an incoming webhook.
Steps
- Go to api.slack.com/apps, click Create New App, choose From scratch, name it
Refilland pick your workspace. - Open Incoming Webhooks and switch Activate Incoming Webhooks on.
- Click Add New Webhook to Workspace, choose a channel and allow it. Copy the URL. Details are in Slack's docs.
- Open Refill, then Settings → Integrations → Add → Slack.
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| Webhook URL | https://hooks.slack.com/services/…. It contains a secret, so never commit it. |
Send a test
Click Send test. Refill shows OK 200 when the service accepted the request.
What Refill sends
{
"text": "*Testing, testing*\nIf you see this, the pipes work. Drip approves."
}If it fails
HTTP 404 no_service: the webhook was revoked or the URL is incomplete. Slack revokes URLs it finds in public repositories.HTTP 404 channel_not_foundorchannel_is_archived: add the webhook again and pick a live channel.
Home Assistant
What you get. Any light Home Assistant can control turns the color of the event.
Steps
- Make sure your Mac can open Home Assistant, for example
http://homeassistant.local:8123. - In Home Assistant, go to Settings → Automations & Scenes → Create automation → Create new automation. Open the three-dot menu and choose Edit in YAML.
- Paste the automation below. Replace
light.living_roomwith your light. Find its ID under Settings → Devices & services → Entities. Save. - Open Refill, then Settings → Integrations → Add → Home Assistant (any lights).
alias: Refill light
description: Color a light with the Refill event color
mode: restart
triggers:
- trigger: webhook
webhook_id: refill
allowed_methods:
- POST
local_only: true
actions:
- action: light.turn_on
target:
entity_id: light.living_room
data:
rgb_color: "{{ trigger.json.rgb }}"
brightness: 255
flash: longThis works with any light brand Home Assistant supports: Hue, IKEA, LIFX, Nanoleaf, Zigbee, Tuya and more. The light must support color. For a white-only bulb, drop rgb_color. On older Home Assistant, use platform: webhook and service: light.turn_on. See the webhook trigger docs.
The webhook ID acts like a password. If others share your network, pick a longer ID than refill in both places. To react differently per event, branch on trigger.json.kind (reset, warning, empty, test).
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| HA URL | http://homeassistant.local:8123, or its IP. No trailing slash. |
| Webhook ID | refill, the same as webhook_id in the automation. |
Send a test
Click Send test. The light should turn lime green. Home Assistant answers 200 even when no automation matches, so a missing reaction means checking the automation.
What Refill sends
Event colors: reset and test #C8FF4D (200, 255, 77), warning #FFB547 (255, 181, 71), empty #FF503C (255, 80, 60). r, g, b and utilization are strings. rgb is a list of numbers.
{
"account": "Refill",
"b": "77",
"color": "#C8FF4D",
"g": "255",
"kind": "test",
"message": "If you see this, the pipes work. Drip approves.",
"r": "200",
"rgb": [200, 255, 77],
"title": "Testing, testing",
"utilization": "100",
"window": "5h session"
}If it fails
- Nothing happens: open the automation, then Traces from the three-dot menu. No trace means the webhook ID differs.
- Trace shows an error on
rgb_color: the entity is not a color light. - Connection errors: the URL is wrong, or the Mac is not on the same network.
local_only: truerejects requests from outside your network.
Philips Hue
What you get. Your Hue lights flash green, amber or red straight from the bridge, no cloud.
Steps
- Find the bridge IP. Check the bridge details in the Hue app, your router, or discovery.meethue.com.
- Press the round link button on the bridge. Within 30 seconds, run the command below. It prints
[{"success":{"username":"…"}}]. Copy the username. If you getlink button not pressed, press the button and run it again. - List your rooms with the second command. Each key (
1,2…) is a group ID with aname. Use0for all lights. - Open Refill, then Settings → Integrations → Add → Philips Hue.
curl -X POST http://BRIDGE_IP/api -d '{"devicetype":"refill#mac"}'curl http://BRIDGE_IP/api/USERNAME/groupsRefill uses the local Hue API v1 over plain HTTP. Hue has marked it legacy, but bridges still answer it. See the Hue getting started guide.
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| Bridge IP | For example 192.168.1.20. Give the bridge a fixed address in your router. |
| API username | The username from step 2. |
| Group / room ID | A group ID from step 3. Empty or 0 means all lights. |
Send a test
Click Send test. The lights turn green and breathe for 15 seconds. Hue answers 200 even for errors, so judge by the lights, not the status.
What Refill sends
Color is CIE xy: reset and test [0.3, 0.6], warning [0.55, 0.41], empty [0.675, 0.322]. Warning uses alert: select (one pulse). Everything else uses lselect (15 seconds).
{
"alert": "lselect",
"bri": 254,
"on": true,
"xy": [0.3, 0.6]
}If it fails
OK 200but dark lights: the bridge repliedunauthorized user(wrong username) orresource not available(wrong group). Repeat the group command with curl to see the reply.- Timeout: the IP changed, or the Mac is on another network or VLAN.
Missing fields: Bridge IP or API username is empty.
WLED
What you get. A WLED strip flashes the event color, or runs a preset you made for resets.
Steps
- Find the host. Try
wled.local, or use the IP from your router or the WLED app. - Optional: open the WLED web page, set a color and effect you like, open Presets, save it to a slot and note its number. The ID is the slot number. See the JSON API docs.
- Open Refill, then Settings → Integrations → Add → WLED strip.
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| Host | wled.local or an IP. Refill adds http:// for you. |
| Preset ID for reset (optional) | For example 3. Leave empty to use the plain green breathing effect. |
Send a test
Click Send test. The strip turns lime and breathes. A test never runs your preset, only a real reset does.
What Refill sends
Warning breathes amber (effect 2). Empty is solid red (effect 0). A reset with a preset ID sends the preset instead.
{
"bri": 255,
"on": true,
"seg": [{ "col": [[200, 255, 77]], "fx": 2 }]
}{ "on": true, "ps": 3 }If it fails
- Cannot resolve
wled.local: use the IP address. Not every network supports.localnames. - Preset does nothing: the slot is empty. Save the preset again and check the number.
- Timeout: the strip is off the Wi-Fi or on another network.
Custom webhook
What you get. Call any HTTP endpoint with a body you shape yourself.
Steps
- Open Refill, then Settings → Integrations → Add → Custom webhook. Method starts as
POST. - Enter the URL. Add headers if the service needs them. Write a body template, or leave it empty to send the event JSON.
- Click Send test.
Fill in Refill
| Field in Refill | What to enter |
|---|---|
| URL | A full https:// or http:// address. Placeholders do not work here. |
| Method | POST by default. GET sends no body. |
| Headers (Key: Value per line) | For example Authorization: Bearer abc. Content-Type: application/json is set for you. |
| Body template (empty = event JSON) | Text with placeholders from the table. Empty sends the event JSON. |
Placeholders
Refill replaces these in the body as plain text. It does not escape quotes, so keep them out of names.
| Placeholder | Value | Example |
|---|---|---|
{{kind}} | Event type | reset, warning, empty, test |
{{title}} | Headline | Testing, testing |
{{message}} | Message text | If you see this, the pipes work. Drip approves. |
{{account}} | Account name | Refill |
{{window}} | Window label | 5h session |
{{utilization}} | Percent used, whole number | 100 |
{{color}} | Event color as hex | #C8FF4D |
{{r}} {{g}} {{b}} | Event color, 0 to 255 each | 200 255 77 |
{{json}} | The whole event as JSON | see below |
Event colors: reset and test #C8FF4D (200, 255, 77), warning #FFB547 (255, 181, 71), empty #FF503C (255, 80, 60).
{"accountId":"test","accountName":"Refill","detectedAt":"2026-10-05T09:30:00Z","kind":"test","message":"If you see this, the pipes work. Drip approves.","provider":"test","reason":"test","title":"Testing, testing","utilization":100,"window":"five_hour","windowLabel":"5h session"}Example: IFTTT or Zapier
Create an IFTTT Webhooks trigger, or a Zapier Catch Hook, and use its URL. IFTTT expects value1 to value3.
{"value1":"{{kind}}","value2":"{{message}}","value3":"{{color}}"}Example: a light controller
Works for a Node-RED flow, an ESPHome or Shelly-style device, or your own server that takes JSON. Adjust the URL and field names to what yours expects.
{"on":true,"rgb":[{{r}},{{g}},{{b}}],"label":"{{kind}}"}If it fails
HTTP 400or422: the body is not valid JSON for that service. Test the same body with curl.HTTP 401or403: add or fix theAuthorizationheader.- A
GETdoes nothing useful: Refill sends no body and does not fill the URL. UsePOST.
Shell hook
What you get. Run your own script on every event: reset, warning, empty and test.
Steps
- Open
~/.config/refill/on-reset. Refill creates a sample there on first launch. Create the file yourself if it is missing. - Start it with a shebang such as
#!/bin/zsh. Refill runs the file directly, not through a shell. - Make it executable, then check Settings → General → Hook script is on.
- Click Send test in the same Settings tab to run it.
chmod +x ~/.config/refill/on-resetRefill runs from the Dock, so your shell's PATH is not set. Use full paths such as /opt/homebrew/bin/… for tools you install with Homebrew.
What your script gets
The event JSON arrives on stdin, one line. These variables are set too.
| Variable | Value |
|---|---|
REFILL_KIND | reset, warning, empty or test |
REFILL_TITLE, REFILL_MESSAGE | Drip's headline and message |
REFILL_COLOR | Event color as hex, such as #C8FF4D |
REFILL_PROVIDER | claude, codex, copilot, cursor, gemini or test |
REFILL_ACCOUNT_ID, REFILL_ACCOUNT | Stable account ID and display name |
REFILL_WINDOW, REFILL_WINDOW_LABEL | Window key (five_hour) and label (5h session) |
REFILL_UTILIZATION | Percent used, whole number |
REFILL_RESETS_AT | ISO 8601 time of the next reset, or empty |
REFILL_REASON | scheduled, observed, threshold or test |
Example
#!/bin/zsh
case "$REFILL_KIND" in
reset)
/usr/bin/say "$REFILL_ACCOUNT is back"
# Pick up where you left off
cd ~/project && /opt/homebrew/bin/claude -p "continue the plan in TODO.md" >> ~/.config/refill/hook.log 2>&1 &
;;
warning)
/usr/bin/osascript -e "display notification \"$REFILL_UTILIZATION% used\" with title \"$REFILL_ACCOUNT\""
;;
empty)
/usr/bin/curl -s -d "$REFILL_ACCOUNT is empty" https://ntfy.sh/my-private-topic
;;
esacListen from other apps
Refill also posts a macOS distributed notification named cz.stepanblaha.refill.reset (or .warning, .empty, .test). The object is the account ID and the user info holds the same REFILL_* values. In Hammerspoon:
hs.distributednotifications.new(function(name, object, info)
hs.alert.show(info.REFILL_MESSAGE)
end, "cz.stepanblaha.refill.reset"):start()If it fails
- Nothing runs: the file is not executable, the toggle is off, or the shebang is missing.
- Command not found: use full paths. Add
exec >> ~/.config/refill/hook.log 2>&1at the top to log output. Refill drops stderr. - Hooks run during quiet hours too. Only sound is muted.
Shortcuts, URLs and CLI
What you get. Ask Refill from Shortcuts, Siri, a link or a terminal.
Shortcuts and Siri
Open the Shortcuts app, create a shortcut, and search for Refill. Refill must be running.
| Action | Returns |
|---|---|
| Get Remaining Usage | Percent left as a number, or -1 if unknown. Choose Provider (Claude, Codex, Any), Window (Session 5h, Week) and optional account email. |
| Get Refill Status | The full status as JSON text. |
| Next Refill Time | A date. Choose a Provider. |
| Refresh Usage | Re-polls now. |
| Send Test Signal | Fires a test to every enabled integration. |
Siri phrases: "How much Claude is left in Refill", "When does Refill refill", "Refresh Refill" and "Test Refill signal".
URL scheme
| URL | Does |
|---|---|
refill://refresh | Re-poll now |
refill://test | Send a test signal |
refill://open, dashboard, settings, history, onboarding | Open that window |
refill://status?x-success=URL&x-error=URL | Opens x-success with remaining and json added, or x-error with errorMessage if there is no data |
open refill://testCommand line
scripts/refill in the repo reads ~/.config/refill/status.json and needs python3. Copy it somewhere on your PATH.
refill status # table of accounts and windows
refill left claude # percent left in the 5h session
refill json # raw status.json
refill open test # any refill:// routeFiles: ~/.config/refill/status.json is the live status and events.jsonl holds one JSON line per event.
Local dashboard
What you get. A web page with your tanks, for a browser tab or your phone.
Steps
- Open Settings → General → Dashboard and click Open. It loads
http://127.0.0.1:7788and only this Mac can reach it. - For your phone, turn on Visible on Wi-Fi. Open the address shown in Settings (your Mac name plus
.localand:7788) in your phone's browser. Add it to the Home Screen if you like. - If macOS asks to allow incoming connections, allow it.
The page is read-only and has no password. Anyone on the same Wi-Fi can open it while Visible on Wi-Fi is on, so use it on networks you trust.
Change the port
The default is 7788. Change it in Settings → General → Dashboard → Port and press Return; the dashboard restarts on the new port.
JSON endpoints
/statusis the same JSON asstatus.json./eventslists recent events.- Both allow cross-origin reads, so a page or a Home Assistant sensor can fetch them.
If it fails
- Phone cannot connect: same Wi-Fi? Some guest and office networks block devices from each other. Try the Mac's IP address instead of the
.localname. - Port in use: pick another with the command above.
Widgets
What you get. Small, medium and large desktop widgets with the tanks and Drip.
Steps
- Keep Refill in
/Applicationsand open it once. - Right-click the desktop and choose Edit Widgets (or open Notification Center and scroll to Edit Widgets).
- Search for Refill, pick a size and add it.
The app writes a snapshot that the widget reads, so keep Refill running. The widget refreshes every 15 minutes, and right after the next known reset.
If it fails
- Refill is missing from the gallery: quit and reopen Refill, then try again. macOS finds widgets after the app has launched.
- Empty widget: open Refill so it can write fresh data. Check that your accounts show up in the menu bar.
Quiet hours and thresholds
What you get. Decide when Refill stays silent and when it warns you.
Quiet hours
- Open Settings → General → Quiet hours and switch it on.
- Choose From and to hours. The default is 22:00 to 08:00 and it can cross midnight.
- Optional: turn on Also mute phone and chat pushes.
| Output | During quiet hours |
|---|---|
| Sound | Muted |
| Notification and notch | Still shown |
| Shell hook | Still runs |
| Home Assistant, Hue, WLED, custom webhook | Still fire |
| ntfy, Pushover, Telegram, Discord, Slack | Fire, unless you turn on the mute option. A delayed ntfy reset whose delivery hour is inside quiet hours is not scheduled when that option is on. |
Warning thresholds
Under Settings → General → Usage, Warn at takes used percentages separated by commas. The default is 80, 95. Values must be above 0 and below 100. An empty alert always goes out at 100%. Check every sets how often Refill polls, from 1 to 60 minutes (default 5).
Each integration has its own Send on switches for Refill (reset), Warning and Empty. Turn off Warning on a chat channel if you only want resets there.
Back to topTroubleshooting
What you get. Quick checks for when something stays quiet.
Integration status messages
| Message | Meaning |
|---|---|
OK 200 | The service accepted the request. Some services use another 2xx code. |
HTTP 4xx … | The service refused it. The first 80 characters of its reply follow. |
Missing fields | A required field is empty or the URL is not a full address. |
| Any other text | A network error, often a timeout after 10 seconds. |
Checks
- Use Send test on the integration first. It tests one service. Settings → General → Send test fires every output.
- Check the integration is switched on and the right Send on toggles are enabled.
- Look at
~/.config/refill/events.jsonlto see which events fired. - Settings live in
~/.config/refill.integrations.jsonholds your tokens and is readable only by you. Do not share it. - A reset is detected when a known reset time passes after use, or when a poll sees the time jump forward. The menu, the notch and most integrations need Refill running. An ntfy reset that Refill already scheduled still arrives if the Mac is asleep or off. See the ntfy guide.
Updates and installs
Refill checks GitHub Releases daily. Use Settings → General → About → Check now. Replacing the app keeps ~/.config/refill. Refill is not notarized, so on first launch use System Settings → Privacy & Security → Open Anyway, or install with Homebrew or the one-line installer described in the README.
Still stuck? Open an issue and leave tokens out.
Back to top