A dedicated, always-findable home for the browser tabs you can't afford to lose. macOS only.
Not a general browser: a minimal app (Tauri v2) that renders a curated, declarative set
of "keeper" tabs from a config.toml config, and refuses to let new-tab navigation
pollute that set — handing every such intent off to your macOS default browser instead.
Important tabs (mail, calendar, dashboards) get buried in a sea of browser windows. Firefox pinned tabs are the closest workaround, but the pinned window itself gets lost and keeping it clean is constant manual work. curator gives keeper tabs a distinct, stable home that lives outside the window-pile and never accumulates cruft — curation is file-driven, everything else is ephemeral.
config.tomlis the source of truth — each[[window]]block opens one window, containing loose[[window.tab]]entries and/or[[window.group]]sections of[[window.group.tab]]s. No in-app pin/unpin; you curate by editing the file (hot-reloaded on save).- Multiple windows — each
[[window]]opens its own window with its own tab list. All open at launch; ⌘W (or the red button) closes a window and the Window menu reopens it. Closing the last open window quits curator. - Keeper tabs are home bases — wander within a session, then snap any tab back to its canonical URL with the sidebar's ⌂ home button (or by re-clicking the active tab); every tab also resets on restart.
- New-tab intents escape —
target="_blank",window.open, cmd/middle-click all shell out toopen, routing to your macOS default browser instead of opening in curator. - Sessions persist, and are shareable — log into a site once in-app and it stays. By
default every tab shares one login store, so signing into a provider covers its related
services (Gmail, Calendar, …). Set a tab's (or a window's)
session = "name"to give it a separate account; reuse the same name elsewhere to share that login. Logins follow thesessionname, so renaming a window or editing a URL never signs you out. - Page-first chrome — the active page fills the content area under an overlay title bar;
the traffic lights float over the top of the sidebar, which doubles as a window-move drag
handle (drag the banner or an empty area to move the window; toggle with
sidebar_drag). load_on_openkeeps a tab live — mark a chat/serviceload_on_openand it loads at launch, stays live in the background, fires native banners, and rolls its unread count up to the dock badge. Tabs without it are lazy and stay quiet until you open them. That one per-tab flag is the only knob — no per-window modes.- Dock badge aggregates across windows — the badge total sums unread across every window's loaded tabs.
- Window menu — close a window (⌘W); reopen any closed window from the Window menu. Closing the last open window quits curator.
In Claude Code, run /curator:install — it checks prerequisites (offering to install
any that are missing), builds curator from source into ~/.curator, installs curator.app
to /Applications, and seeds your config.
Or install from a terminal:
curl -fsSL https://raw.githubusercontent.com/Lockyc/curator/main/install.sh | bashRe-running either path updates curator (git pull + rebuild). The steps below describe the
manual / contributor flow.
-
Copy the sample config into place:
mkdir -p ~/.config/curator cp examples/config.toml ~/.config/curator/config.toml
It lives under
~/.config/so it slots into a dotfiles workflow — your curated tab set becomes versioned, portable config. -
Run it (requires Rust + the Tauri CLI; the installer backstops this via
cargo install tauri-cli):just run # or: cargo tauri devjust runloads the repo'sexamples/config.toml(via theCURATOR_CONFIGenv var) so iterating never touches your real~/.config/curator/config.toml. PointCURATOR_CONFIGat any file to test another config.just buildproduces a.appbundle;just deploybuilds and installs/updates it in/Applications(quitting the running copy and relaunching).just testruns the Rust tests. The app icon source issrc-tauri/icons/icon.svg— re-runcargo tauri icon src-tauri/icons/icon.svgafter editing it. -
Edit
~/.config/curator/config.tomland save — the sidebar hot-reloads, no restart. A malformed file keeps the last-good config running and shows an error banner instead of crashing. The Config menu has Edit Config / Reveal Config in Finder so you needn't memorise the path; the Tabs menu has Reload Tab (⌘R) and Reset All Tabs to snap every open tab back to its canonical URL.
curator opens one window per [[window]] block. A window's tabs may be loose (ungrouped) or
organised into groups; loose tabs render first in a headerless section, then each group:
# App-global options
# dark_mode = true # force dark appearance; omit = follow system
# allow_insecure = ["192.168.1.1"] # accept self-signed TLS for these hosts
# session = "personal" # app-wide default login store (bottom of the session chain)
# density = "compact" # "comfortable" (default) or "compact" (condensed chrome)
# sidebar_drag = false # drag the sidebar chrome to move the window (default true)
[[window]]
title = "Keepers" # required; must be unique across windows
# width = 1500 # optional; default 1500 × 1000
# height = 1000
# open_on_launch = "Grafana" # true/false/"Tab Title"
# Loose (ungrouped) tab — renders first, in a headerless section above the groups.
[[window.tab]]
title = "Start"
url = "https://duckduckgo.com/"
[[window.group]]
name = "Dashboards"
[[window.group.tab]]
title = "Grafana"
url = "https://play.grafana.org/"
load_on_open = true # load at launch + keep live
reload_every = 5 # auto-refresh every 5 minutes
[[window]]
title = "Comms"
[[window.group]]
name = "Chat"
[[window.group.tab]]
title = "Mattermost"
url = "https://community.mattermost.com/"
load_on_open = true # kept live → fires banners + unread in the background| Field | Type | Default | Meaning |
|---|---|---|---|
title |
string | required | Window title; must be unique across all windows. |
width/height |
int | 1500/1000 |
First-run window size in logical pixels. After that, curator remembers each window's size + position across launches, so this is only the initial default (move/resize a window and it reopens where you left it). |
open_on_launch |
bool | tab title string | false |
true opens the first tab; a string opens the named tab; false = blank screen. |
colour |
#rgb / #rrggbb hex |
none | Accent colour for this window — colours the title bar (nav pill + window name), giving each window a distinct identity. |
session |
string | none | Default login store for this window's tabs (overridden per tab). See sessions below. |
Each tab (loose [[window.tab]] or grouped [[window.group.tab]]) requires title and url.
Tab titles must be unique window-wide (across loose + grouped); group names unique within a
window. Optional:
| Field | Type | Default | Meaning |
|---|---|---|---|
load_on_open |
bool | false |
Load when the window opens and keep the tab live in the background, so it fires native banners and reports unread even when it isn't the active tab. |
reload_every |
positive int | none | Auto-refresh the canonical URL every N minutes. |
session |
string | none | Login store for this tab. Tabs sharing a value share a login (even across windows); distinct values are isolated accounts. Falls back to the window's session, then the app-wide top-level session, then the shared default. A blank or whitespace-only value is treated as unset and falls through the chain. |
| Field | Type | Default | Meaning |
|---|---|---|---|
dark_mode |
bool | false |
Force dark appearance so sites honouring prefers-color-scheme render dark. |
allow_insecure |
list of hosts | [] |
Accept self-signed/invalid TLS certs for these hosts. Applied at launch (restart to change). |
session |
string | none | App-wide default login store — the bottom of the session chain (tab → window → this → built-in default). |
density |
string | comfortable |
Chrome sizing: comfortable or compact (type + spacing scaled down proportionally for denser tab lists). Hot-reloads. |
sidebar_drag |
bool | true |
Whether the sidebar chrome is a window-move drag handle (drag the banner/empty list area to move the window). false turns it off. Hot-reloads. |
format_on_save |
bool | false |
Reformat config.toml in curator's house style on a clean hot-reload (same formatting as curator fmt). Leaves the file untouched if a reload fails to parse. |
Run curator validate [path] to check a config without launching: it prints the resolved
window/tab tree (each tab's cascaded session) and any non-fatal warnings (e.g. a URL repeated
within a window), exiting non-zero on a parse/validation error. A bad config never crashes the
app either — it keeps the last-good config running behind an error banner.
Tabs are lazy by default: a webview is created on first activation and kept warm for the session. Each row shows a green dot when its tab is loaded — click it to unload (free that webview's memory); the tab reloads on next click. A navigation pill at the top of the sidebar drives the active tab: ◀ back and ▶ forward through in-page history, and ⌂ home to snap back to its canonical URL.
See examples/config.toml for a two-window starting-point example.
A few setups the model makes easy. All four compose freely — one window can mix them.
Point several tabs at the same url and give each a distinct session. curator keeps the
logins fully isolated, so you get parallel accounts of one web app — Matrix/Element
homeservers, Slack workspaces, separate Google logins — with no incognito juggling. Mark them
load_on_open and every account stays live and notifies at once.
[[window]]
title = "Matrix"
[[window.group]]
name = "Accounts"
[[window.group.tab]]
title = "Work"
url = "https://app.element.io/"
session = "element-work"
load_on_open = true
[[window.group.tab]]
title = "Personal"
url = "https://app.element.io/"
session = "element-personal"
load_on_open = trueKeep alert and status pages load_on_open so they stay live, fire native banners on new
activity even when curator isn't focused, and roll their unread up to the dock badge. Leave
everything else lazy so only the pages you want live cost you a banner.
[[window.group]]
name = "Alerts"
[[window.group.tab]]
title = "ntfy"
url = "https://ntfy.example.com/alerts"
load_on_open = true
[[window.group.tab]]
title = "Status"
url = "https://status.example.com/"
load_on_open = trueCollect the sprawl of admin UIs — hypervisor, NAS, DNS, reverse proxy, registrar — into named
groups in one window. Give the window a colour so it's instantly recognisable, and let most
tabs stay lazy and quiet; only the dashboards you watch get load_on_open.
[[window]]
title = "Infra"
colour = "#0f8a8a"
[[window.group]]
name = "Network"
[[window.group.tab]]
title = "UniFi"
url = "https://unifi.example.com/"
[[window.group.tab]]
title = "Proxmox"
url = "https://pve.example.com/"
[[window.group]]
name = "DNS & Domains"
[[window.group.tab]]
title = "Cloudflare"
url = "https://dash.cloudflare.com/"List the hosts whose invalid/self-signed TLS you trust in the app-global allow_insecure so
their admin pages load without the browser security wall. Scope it tightly — only the hosts
you actually run.
allow_insecure = ["10.0.0.1", "nas.local"]