Three files configure glimpse for your project. .glimpseignore excludes
paths, .glimpse-baseline.json records images already handled, and the
optional .github/workflows/glimpse.yml runs checks or automatic
optimization in GitHub Actions.
Init
Run it from the project root:
cpx glimpse init
Init creates the ignore file and asks whether to accept your current
images unchanged. This baseline seed defaults to No and makes no API
calls unless selected. Seeding means checks and automatic optimization
skip those images until their contents change. Review .glimpseignore
before seeding.
When run interactively in a Git repository without an existing workflow, init then asks:
Which GitHub Actions workflow would you like?
[0] Check and optimize
[1] Check only
[2] No workflow
Check and optimize is selected by default. It checks images after a PR
merges into any branch and opens an optimization PR against that branch.
GLIMPSE_TOKEN is optional. A personal token provides higher limits. See
automatic optimization for setup.
Check only runs the existing check on PRs and pushes to main, and
fails on flagged images. Its token is optional.
Use flags to select a workflow without the workflow prompt:
cpx glimpse init --workflow # check and optimize
cpx glimpse init --workflow-mode=optimize # check and optimize
cpx glimpse init --workflow-mode=check # check only
cpx glimpse init --workflow-mode=check --no-interaction
An explicit mode overrides --workflow's default. Only check and
optimize are accepted. Plain non-interactive runs and closed stdin never
select a workflow implicitly. --update-baseline explicitly seeds the
baseline; without it, non-interactive init creates an empty baseline.
Existing files are kept. --force recreates .glimpseignore; to also
replace the workflow, select a workflow with --workflow or
--workflow-mode. For example:
cpx glimpse init --workflow-mode=check --force
--force alone never replaces an existing workflow or baseline. Use
--update-baseline to refresh the baseline. A kept custom workflow gets
neutral setup guidance because init does not assume what it does.
The default for --workflow has changed from check only to check and
optimize. Existing workflow files are not migrated. Scripts that need the
old behavior should use --workflow-mode=check.
Check
check is the CI gate. It scans a directory (or a single file),
estimates the best-format saving for every image, and lists only the
ones that would benefit from optimization. Without --fix, it sends
image measurements without uploading image bytes.
cpx glimpse check . # fail if any image could save 10% or more
cpx glimpse check assets/ --threshold=25 # only fail on big wins
cpx glimpse check . --json # machine-readable report
+---------------------+---------+-----------+--------+---------+
| File | Current | Estimated | Format | Saved % |
+---------------------+---------+-----------+--------+---------+
| art/social-card.png | 1.2 MB | ~180 KB | AVIF | 85.0% |
| art/icon.png | 96 KB | ~34 KB | WEBP | 64.6% |
+---------------------+---------+-----------+--------+---------+
2 of 14 images need optimization (threshold: 10%).
An image needs optimization when the format that saves the most (the
same pick analyze makes) would save at least --threshold percent
(default 10, decimals allowed). The exit code is 1 when any image is
over the threshold, so a CI pipeline fails until the images are
optimized.
Files that cannot be checked (unreadable or unrecognized) are listed separately and also fail the run: a passing check means every image was checked and passed, not just that none were flagged. A directory with no images passes.
Fix flagged images
Use --fix to optimize the flagged images in place:
cpx glimpse check . --fix
cpx glimpse check assets/ --fix --quality=85 # optional re-encode quality
--quality is optional and requires --fix. When omitted, optimization
uses the existing optimize default. An explicit quality changes only
optimization, not the estimates used to select images.
Checking finishes before any file is changed. If a file cannot be checked,
the command fails without optimizing anything. Otherwise, it uploads only
the flagged images for optimization and preserves their formats and filenames.
It respects the same threshold, ignore rules, and baseline as ordinary check.
The command returns 0 when optimization succeeds or nothing needs fixing.
It returns 1 on failure. If optimization fails partway through, it stops
and keeps the earlier successful changes. Successful writes update an
existing baseline; the command does not create one. Run cpx glimpse init
first if you want subsequent directory scans to skip optimized images.
With --fix --json, the report adds a fixed list of successfully optimized
filenames. files and needs_optimization describe the original check
results. failed contains checking or optimization errors. Without --fix,
the existing check output and exit codes stay the same.
Skip an image
Restore one image from Git and accept it unchanged in the baseline:
cpx glimpse skip assets/photo.png
Run this from the repository root. By default it restores the version in
HEAD, so you can undo an optimization you have not committed yet.
If you already committed the optimization, choose a commit containing the
original:
cpx glimpse skip assets/photo.png --from=abc1234
Only that image is restored. The command creates or updates
.glimpse-baseline.json with via: "skip", without calling the API.
Commit the image and baseline together. The Git index is left unchanged.
check and directory analyze scans skip the image while its bytes match.
Editing the image makes it eligible again.
There are no backups. If the original is not in the selected Git commit, the command fails without changing the image or baseline. Symlinks and paths outside the repository are refused.
On automatic optimization PRs,
you can also add an inline glimpse skip comment on the image.
.glimpseignore
A .glimpseignore file at the project root excludes paths from scans,
using gitignore syntax:
# generated assets, not worth optimizing
vendor/
public/build/
*.gif
!logo.gif
The file is read from the directory you run glimpse from, and patterns
are relative to it: with the rules above, cpx glimpse check . and
cpx glimpse check public/ both skip public/build/. Negated patterns
(!logo.gif) re-include files, with the same caveat as git: a file
inside an ignored directory cannot be re-included.
check <dir> and analyze <dir> honor the file, and the transform
commands honor it too by never recording an ignored file into the
baseline.
.glimpse-baseline.json
Adopting glimpse in an existing project should not require converting
every image on day one. The baseline records files you have already
dealt with, so check <dir> and analyze <dir> skip them and only new
or changed images get flagged. It is the same idea as phpstan's
baseline: accept the current state, gate everything from here on.
Generate it by scanning the directory:
cpx glimpse analyze assets/ --update-baseline
Or let cpx glimpse init set it up together with the other files.
The file lists each image with its size, content hash, and the
operation that put it there, under a _readme header that explains the
format to anyone reading it in review:
{
"_readme": [
"Images already handled by glimpse: check and analyze skip a file while its size and xxh128 content hash still match its entry.",
"..."
],
"files": {
"photos/hero.png": {
"size": 482133,
"xxh128": "5f3d0a9c1b2e4d6f8a7c9e0b1d2f3a4c",
"via": "analyze"
},
"photos/hero.webp": {
"size": 98342,
"xxh128": "9a1c3e5f7b2d4a6c8e0f1a3b5c7d9e0f",
"via": "optimize"
}
}
}
How entries match
An image is skipped only while its content still matches the recorded
entry. Replace a baselined file with a new version and it re-enters the
scan automatically. Where .glimpseignore says "never look at these
paths", the baseline says "these exact file contents are already
handled".
The via field
via separates files you accepted as-is from files glimpse actually
processed. analyze means the file was taken as-is by
--update-baseline. A command name (convert, resize, thumbnail)
means glimpse produced or processed the file. optimize means the
optimizer chain ran, through the optimize command or a transform's
--optimize flag. When you work through a bulk-accepted baseline
later, the analyze entries are the ones still waiting for real
treatment.
Where the baseline applies
Every command reads the baseline (and .glimpseignore) from the
current working directory, the way composer and phpstan resolve their
config. Run glimpse from the project root and the baseline there
governs the run, with entries keyed relative to it (scanning assets/
matches keys like assets/hero.png). There is no upward search: run
glimpse from anywhere else and the baseline simply does not apply.
--update-baseline writes to the current directory and requires the
scanned directory to be inside it.
The baseline only applies to directory scans. Naming a file explicitly
(cpx glimpse analyze photo.png) always analyzes it. Commit the file so CI
and your teammates share the same starting point.
The transforms keep it current
convert and optimize record the written output and its source (an
in-place conversion that replaced the source also cleans up its old
entry), while resize and thumbnail record only the file they
produce. Writing to stdout or to a path outside the working directory
records nothing. A baseline problem never fails a transform that
already succeeded; it degrades to a warning on STDERR. The transforms
never create the baseline file; that is --update-baseline's job.
Re-running analyze <dir> --update-baseline refreshes changed entries
and prunes deleted files.
Concurrent writes are safe
A run that will write takes an exclusive lock on the file first, and
the content lands in a temporary file swapped atomically into place. A
second concurrent writer fails fast with a clear error
(analyze --update-baseline) or degrades to a STDERR warning (the
transforms) instead of silently overwriting entries. Readers never see
a half-written file, and a failed write leaves the previous baseline
untouched.