Configuration
Every setting is optional. surface works with nothing but the binary, and a missing config file is the normal case rather than an error — so this page is really a list of the four things worth changing.
Where the file goes
surface.toml, in the config directory:
| Path | |
|---|---|
| macOS | ~/Library/Application Support/ai.holistic.surface/surface.toml |
| Linux | ~/.config/surface/surface.toml |
| Windows | %APPDATA%\holistic\surface\config\surface.toml |
surface --check prints the resolved directory on this machine, which beats
guessing:
Precedence
Later wins:
- Built-in defaults
surface.tomlSURFACE_*environment variables- CLI flags (
--offline)
The whole file
Copy this and delete what you do not need — every value shown is the default.
# surface configuration — every setting is optional.
#
# surface works with no config file at all; this documents the defaults. Copy it
# to the path `surface --check` reports as the config directory, as `surface.toml`.
#
# macOS ~/Library/Application Support/ai.holistic.surface/surface.toml
# Linux ~/.config/surface/surface.toml
# Windows %APPDATA%\holistic\surface\config\surface.toml
# ----------------------------------------------------------------- AI sites
#
# Read browser history for visits to known AI domains. Only the domain, a visit
# count and a last-seen date are read: the domain filter runs inside SQLite, so
# no URL, path, query string or page title is ever loaded — not even for the AI
# domains themselves, let alone the rest of your browsing.
[web]
# Set false to skip browser history entirely. The Sites view then says it is
# switched off rather than showing an empty list.
scan_history = true
# How recently a domain must have been visited to appear. Visit counts are the
# browser's lifetime totals; this only decides which domains are listed.
history_lookback_days = 30
# Extra domains to treat as AI services — an internal model gateway, say.
# Matched on the registrable host, so "models.corp.example" also covers
# "www.models.corp.example" but never "notmodels.corp.example".
extra_ai_domains = []
# --------------------------------------------------------------- token usage
#
# Read the transcripts Claude Code, Codex and OpenCode already write, to count
# tokens per day, tool and model. Message content is never read.
[usage]
# Set false to skip transcripts entirely.
scan = true
# How many days of daily totals to keep. Older days are pruned from the ledger,
# along with their deduplication keys.
window_days = 30
# ---------------------------------------------------------------------- cost
#
# surface prices tokens at API rates. If you pay a flat subscription instead,
# tell it what you actually pay and the Cost view will compare the two.
[cost]
# Monthly spend per tool, in USD. Keys are tool ids as they appear in the Usage
# view: claude_code, codex, opencode, gemini_cli, ...
#
# Without an entry here a tool gets no subscription row at all — surface reads no
# account state, so it cannot know your plan and will not guess one.
[cost.subscriptions]
# claude_code = 100.0
# codex = 30.0
# ------------------------------------------------------------ env overrides
#
# Environment variables win over this file, and are handy for one-off runs:
#
# SURFACE_STATE_DIR where the ledger and price cache live
# SURFACE_CONFIG_DIR where this file is looked for
# SURFACE_SCAN_HISTORY=false same as [web] scan_history
# SURFACE_HISTORY_LOOKBACK_DAYS same as [web] history_lookback_days
# SURFACE_SCAN_USAGE=false same as [usage] scan
# SURFACE_USAGE_WINDOW_DAYS same as [usage] window_days
[web]
Browser history, for known AI domains only.
| Key | Default | Does |
|---|---|---|
scan_history |
true |
Read browser history at all |
history_lookback_days |
30 |
How recently a domain must have been visited to be listed |
extra_ai_domains |
[] |
Extra domains to treat as AI services |
[web]
scan_history = true
history_lookback_days = 90
extra_ai_domains = ["models.corp.example", "llm-gateway.internal"]
extra_ai_domains is matched on the registrable host, so
models.corp.example also covers www.models.corp.example but never
notmodels.corp.example. An internal model gateway is the usual reason to set
it.
Setting scan_history = false makes the Sites view report
that it is switched off, rather than showing an empty list.
The lookback filters domains, not visit counts
Visit counts are the browser's lifetime totals. history_lookback_days only
decides which domains make the list.
[usage]
Token accounting from the transcripts your tools already write.
| Key | Default | Does |
|---|---|---|
scan |
true |
Read transcripts at all |
window_days |
30 |
How many days of daily totals to keep |
window_days is also the retention policy: days that fall outside it are pruned
from the ledger, along with their deduplication keys. Widening it does not
recover days already pruned — those transcripts will be re-read from scratch,
which costs a cold scan once.
Both day counts are clamped to 1–3650, not rejected. A nonsensical window is a typo, and refusing to run over a typo is worse than running over a sane value. An unknown key, on the other hand, is an error at startup: a misspelled setting that silently does nothing is the worse failure.
[cost]
surface prices tokens at API list rates. If you pay a flat subscription instead, tell it what you actually pay and the Cost view will compare the two.
Keys are tool ids as they appear in the Usage view — claude_code, codex,
opencode, gemini_cli, … — and values are monthly USD.
A tool with no entry gets no subscription row at all. surface reads no account
state, so it cannot know your plan and will not guess one. A configured figure is
used as given; a list-price fallback is labelled est wherever it is shown.
Environment variables
Handy for a one-off run, and what the test suite uses to stay out of a real profile.
| Variable | Overrides |
|---|---|
SURFACE_STATE_DIR |
Where the ledger and price cache live |
SURFACE_CONFIG_DIR |
Where surface.toml is looked for |
SURFACE_SCAN_HISTORY |
[web] scan_history |
SURFACE_HISTORY_LOOKBACK_DAYS |
[web] history_lookback_days |
SURFACE_SCAN_USAGE |
[usage] scan |
SURFACE_USAGE_WINDOW_DAYS |
[usage] window_days |
Booleans accept 1/true/yes/on and 0/false/no/off; anything else is
ignored rather than treated as false.
$env:SURFACE_SCAN_HISTORY = 'false'
$env:SURFACE_STATE_DIR = (New-Item -ItemType Directory -Path "$env:TEMP\surface-scratch" -Force).FullName
surface --json
Those two assignments last for the current PowerShell session. Use
[Environment]::SetEnvironmentVariable('SURFACE_SCAN_HISTORY','false','User')
to make one stick, or Remove-Item Env:SURFACE_SCAN_HISTORY to undo it now.
Recipes
Tools only — no history, no transcripts
The scan then reports which AI tools are installed and nothing else. Tool detection has no off switch: it is the cheapest section and the one the tool exists for.
A quarter of history instead of a month
Expect one slow scan while the extra transcripts are read, then the usual ~50 ms.