Installation
surface runs on macOS, Linux and Windows. It needs no runtime, no service and no configuration; the binary is the whole install.
Install
Works in both Windows PowerShell 5.1 and PowerShell 7.
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/holistic-ai/surface/main/install.ps1 | iex"
irm and iex are PowerShell aliases and do not exist in cmd.exe, so the
command above hands the job to PowerShell rather than running there.
The crate is surface-cli but the binary it installs is surface.
Compiles SQLite from C, so this one needs a C toolchain — see without SQLite.
Then:
Or do it by hand
Prefer not to run a script? The archives are on the
releases page — six targets,
each with a SHA256SUMS entry.
| Platform | Target | Archive |
|---|---|---|
| macOS, Apple silicon | aarch64-apple-darwin |
.tar.gz |
| macOS, Intel | x86_64-apple-darwin |
.tar.gz |
| Linux, x86-64 | x86_64-unknown-linux-gnu |
.tar.gz |
| Linux, arm64 | aarch64-unknown-linux-gnu |
.tar.gz |
| Linux, x86-64, static | x86_64-unknown-linux-musl |
.tar.gz |
| Windows, x86-64 | x86_64-pc-windows-msvc |
.zip |
tar -xzf surface-v0.1.0-aarch64-apple-darwin.tar.gz
shasum -a 256 -c SHA256SUMS --ignore-missing
install -m 755 surface-v0.1.0-aarch64-apple-darwin/surface ~/.local/bin/surface
The two linux-gnu archives are built on Ubuntu 22.04 and so need glibc 2.35 or
newer: Ubuntu 22.04, Debian 12 (2.36) and anything later. RHEL 9 and its
rebuilds sit just below at 2.34.
The linux-musl archive is statically linked and needs no libc at all, which
makes it the one to take on Alpine, on a distroless image, or on any distribution
below that floor — including RHEL 9. install.sh checks your glibc and picks it
for you, so on x86-64 this resolves itself. On arm64 there is no static build, so
an older arm64 distribution means building from source; the installer says so
rather than installing a binary that cannot start.
Two environment variables, if you need them:
SURFACE_VERSION=v0.1.0 sh install.sh # a specific tag
SURFACE_INSTALL_DIR=~/bin sh install.sh # somewhere else
On Windows the equivalents are $env:SURFACE_VERSION and
$env:SURFACE_INSTALL_DIR; the default there is %LOCALAPPDATA%\Programs\surface,
which the script adds to your user PATH. Set them before the install command in
the same session:
Only the installer is PowerShell-only. Once it has run, surface itself is an
ordinary executable on your PATH and runs from cmd, PowerShell and Windows
Terminal alike — reopen the terminal first, so it picks up the new PATH.
Verifying a download
Checking against SHA256SUMS proves the file arrived intact. It does not prove we
produced it, because the checksums are served from the same release as the
archives — anyone who could substitute one could substitute both.
Every archive therefore carries a Sigstore build provenance attestation, which ties it to this repository, the workflow that built it and the commit it was built from:
That needs the GitHub CLI and, the first time, network access to fetch the signing certificate. It is the check worth running if you are putting surface into a build of your own; for an interactive install the installer's checksum verification is the usual bar.
Or build it yourself:
Details, including the minimum supported Rust version, are in Building from source.
Platform notes
On musl (Alpine, distroless) take the x86_64-unknown-linux-musl archive; there
is no static arm64 build, so arm64 Alpine still means building from source. The
macOS binaries are not notarized yet, so an archive downloaded by a browser
is quarantined — xattr -d com.apple.quarantine surface clears it. Windows on
arm64 runs the x86-64 binary emulated; there is no native build yet. On
Windows use Windows Terminal; the old conhost console does not draw the
box-drawing characters the dashboard is made of.
No Homebrew tap yet.
Development install
For working on surface rather than with it. There is no separate dev toolchain
to install: cargo is the whole thing.
git clone https://github.com/holistic-ai/surface
cd surface
cargo run # scan this machine, open the dashboard
cargo run builds a debug binary, which is slower on a cold transcript read but
otherwise identical. For realistic timings use cargo run --release.
Requirements
| Rust | 1.88 or newer — the MSRV, built on exactly that version in CI |
| C toolchain | For the bundled SQLite. xcode-select --install, build-essential, or the MSVC C++ workload |
| Python | Only to build the docs site — see Writing docs |
No task runner, no code generation, no submodules, no .env to fill in.
Before you open a pull request
The four commands CI runs. All four pass on a clean checkout:
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test # 246 tests
cargo run -- --json --offline # smoke test: must degrade, never fail
The last one matters more than it looks. A CI runner is a machine with no AI tools, no browser profiles and no transcripts — exactly the case where a scan has to produce empty output rather than an error. Empty is a pass; a non-zero exit is not.
If you touched anything that changes what gets built:
cargo test --no-default-features # 206 of the 246 tests; the rest need SQLite
cargo clippy --workspace --all-targets --no-default-features -- -D warnings
uv run mkdocs build --strict # docs, if you edited any
Working against your own data safely
Point the state directory somewhere disposable and the real ledger is never touched:
SURFACE_CONFIG_DIR does the same for configuration, so you can test a config
file without editing the one you use. --offline keeps a debug loop off the
network entirely.
Serving the docs
uv venv
uv pip install -r docs/requirements.txt
uv run mkdocs serve # http://127.0.0.1:8000/surface/
Building the site runs cargo doc through an MkDocs hook, so cargo must be on
PATH. Details in Writing docs.
More on the build itself — the release profile, what each CI job checks, the three classes of test — is in Building from source.
Without SQLite
Browser history (Chromium and Firefox both store it in SQLite) and OpenCode's token history are the two things that need SQLite, and it is the one dependency that needs a C compiler. So it is a default feature rather than a mandatory one:
| Default build | --no-default-features |
|
|---|---|---|
| Size | ~2.06 MB | ~1.20 MB |
| Sites view | yes | not compiled in |
| OpenCode token counts | yes | reported as unreadable |
| Claude Code and Codex usage | yes | yes — plain JSONL, no SQLite involved |
| AI tool detection | yes | yes |
The reduced build says what it cannot see rather than showing empty views: the Sites view reports that it was compiled out, and OpenCode appears as an unreadable source rather than as zero usage. Released binaries are built with the feature on, so installing one never compiles C.
Verify the install
--check is the fastest way to confirm surface can find its own directories.
Typical output:
state dir /Users/you/Library/Application Support/ai.holistic.surface
config dir /Users/you/Library/Application Support/ai.holistic.surface
config defaults (no file)
ledger /Users/you/Library/Application Support/ai.holistic.surface/usage-ledger.json
prices 1284 models, cached 2h ago
sites 30 day lookback
usage 30 day window
Where surface keeps its files
Two files, both in the state directory: the usage ledger
(usage-ledger.json, so the next scan reads only what was appended) and the
cached price table (litellm-prices.json). Nothing is written anywhere else,
ever.
| State | Config | |
|---|---|---|
| macOS | ~/Library/Application Support/ai.holistic.surface |
same as state |
| Linux | ~/.local/share/surface |
~/.config/surface |
| Windows | %LOCALAPPDATA%\holistic\surface\data |
%APPDATA%\holistic\surface\config |
These are whatever the directories crate reports
for this platform, which is why macOS gets a reverse-DNS directory and Linux does
not. surface --check prints the resolved pair, and that output is the
authority — never a guess from this table.
Both are overridable with SURFACE_STATE_DIR and SURFACE_CONFIG_DIR — handy
for a throwaway run that leaves the real profile untouched:
Uninstall
There is nothing else: no service to stop, no launch agent, no registry keys, and nothing on a server to delete because nothing was ever sent.
Troubleshooting
cargo install surface-cli fails compiling libsqlite3-sys
That is the bundled SQLite build, and it needs a C compiler:
- macOS —
xcode-select --install - Debian/Ubuntu —
sudo apt install build-essential - Windows — the "Desktop development with C++" workload for MSVC
Or skip it entirely with cargo install surface-cli --no-default-features, and
see without SQLite for what that costs you.
The Sites view says a browser is unreadable
Not a bug, and deliberately not hidden. Safari's history lives in a directory that needs Full Disk Access, so surface reports it as an unreadable browser rather than counting it as zero AI usage. Grant the permission in System Settings → Privacy & Security → Full Disk Access, to your terminal, or leave it — a known blind spot is more honest than a silent zero.
The Sites view says not compiled in
The binary was built with --no-default-features. Install a released
binary, or rebuild with the sqlite feature — see
without SQLite.
The first run takes twenty seconds
Expected once. The first scan parses every transcript in the window; on a 900 MB corpus that is around 19 seconds. Every run after it reads only the bytes that were appended since, typically under 50 ms. See Core concepts.