Flintstack › CLI reference
CLI reference
Two binaries. flintd is the daemon and takes only startup flags;
flint is the client and does everything else. Both accept --help
on any subcommand and print the same text that is reproduced here.
Global flags
These work on every flint subcommand and are read before the configuration
file, so a flag always wins over a file value.
| Flag | Default | Meaning |
|---|---|---|
| --socket PATH | /run/flint/flintd.sock | where the daemon listens locally |
| --config PATH | /etc/flint/flint.toml | configuration file; missing file is not an error for the client |
| --timeout SEC | 5 | how long to wait for the daemon before falling through |
| --json | off | machine-readable output on stdout, one object per line |
| -v, --verbose | off | repeatable; twice prints every key input |
| -q, --quiet | off | errors only; overrides verbose |
flintd
Startup flags only - there is no runtime admin socket, by choice. Everything mutable
lives in the configuration file and is re-read on SIGHUP.
| Flag | Default | Meaning |
|---|---|---|
| --root DIR | /var/lib/flint | store root; taken exclusively with a lock file |
| --listen ADDR | disabled | bind the shared cache, conventionally on port 7914 |
| --token-file PATH | none | shared secret for --listen; required with it |
| --workers N | cores, max 16 | store writers; reads are not limited by this |
| --no-evict | off | never delete; the store will fill and then refuse writes |
| --dry-run | off | parse the config, check the store, exit 0 without serving |
flint wrap
The only command that belongs in a build. Everything after -- is the real
command line, executed verbatim on a miss.
flint wrap [--no-store] [--label TEXT] -- cc -c -O2 -o obj/parse.o src/parse.c
--no-store looks entries up but never writes them, which is what you want
on a developer laptop that should consume a shared cache without polluting it.
--label attaches a free-form string to stored entries so that
flint stats --by-label can attribute hits later; it is not part of the key.
The wrapper is deliberately boring about failure. If the daemon is unreachable, if the key cannot be computed, or if an input disappears mid-run, it runs the real command and exits with the real command's status. The only case where it fails on its own is a malformed invocation, which it reports before starting anything.
flint stats
flint stats [--window DAYS] [--by-label] [--json]
Counters are kept in memory and flushed to the store every 30 seconds, so a hard kill loses at most half a minute of accounting. The rolling window defaults to seven days; values older than the window are aggregated into a single bucket and lose their label breakdown.
flint prune
flint prune [--older-than DURATION] [--to SIZE] [--label TEXT] [--yes]
Eviction normally happens on its own, driven by max_size. Use
prune when you need it to happen now - most often after a toolchain change
that invalidated a large slice of the store in one go:
$ flint prune --older-than 30d --yes
scanned 18402 entries, removed 2611, freed 3.8 GiB in 4.2 s
Without --yes the command prints exactly the same summary prefixed with
would remove and touches nothing.
flint get and flint put
Low-level access by key, for debugging and for scripts that cache something the wrapper does not understand - a linked archive, a generated header, a test fixture.
flint put --key 9f3c1b2e8a7d4056 --file dist/libparse.a
flint get --key 9f3c1b2e8a7d4056 --out /tmp/libparse.a
Keys here are opaque 16-hex-digit strings and are not validated against anything. If you invent your own namespace, make sure it cannot collide with a wrapper key; prefixing your keys with a fixed byte is the cheap way out.
flint key
Prints the key the wrapper would compute, and with --explain every component
that went into it. This is the first thing to run when a build misses and you cannot see why.
$ flint key --explain -- cc -c -O2 -o obj/lex.o src/lex.c
toolchain cc 13.2.0 x86_64-linux (digest 4e1a…)
argv -c -O2 -o <OUT> <IN> (output path normalized away)
inputs src/lex.c 1f7b…, include/tok.h 93aa…
env CFLAGS="-O2 -pipe", SOURCE_DATE_EPOCH=1760000000
key 9f3c1b2e8a7d4056
Diffing two --explain outputs explains almost every surprise miss. The usual
culprits are an absolute path that leaked into the argument vector and a build timestamp that
was never on the allowlist in the first place.
flint doctor
Environment check: binary version, daemon reachability, filesystem semantics under the store root, lock behaviour, clock offset and backend health. It writes nothing and is safe on a busy builder. The install page shows a clean run.
Exit codes
| Code | Means |
|---|---|
| 0 | success; for wrap, the wrapped command also exited 0 |
| 1 | the wrapped command failed - its status is passed through unchanged |
| 2 | usage error: unknown flag, missing --, bad duration |
| 3 | daemon unreachable and --require-daemon was given |
| 4 | store unusable: root lock held, no space, or a failed semantics check |
| 5 | authentication rejected by a remote backend |
Note that a plain cache miss is not an error condition and never shows up here. If you
want CI to fail when the cache is cold, test the hit rate from flint stats --json
instead of hoping for a non-zero status.
Last updated: 2026-09-28