Extract and diff app.css across Obsidian versions. Downloads Obsidian's ASAR
bundle directly from GitHub releases - no Docker or Node.js needed.
Keybinds are Vim-inspired so terminal users will feel right at home. This project follows a loose interpretation of the UNIX software philosophy.
Do one thing and do it well, in this case extracting and comparing app.css
between Obsidian versions.
This project uses British English in its documentation, following the discipline of ASD-STE100 Simplified Technical English via the SimpleEnglish skill.
go install github.com/bladeacer/ocd@latest
ocdDownload from releases (linux/windows/darwin, amd64/arm64).
Or if you enjoy the CLI way.
curl -LO https://github.com/bladeacer/ocd/releases/latest/download/ocd_linux_amd64.tar.gz
tar xzf ocd_linux_amd64.tar.gz
sudo mv ocd /usr/local/bin/git clone https://github.com/bladeacer/ocd
cd ocd
make build
./ocdocd interactBrowse versions - fetches RSS changelog, Docker Hub tags, and Electron-Chromium mappings asynchronously. Loading messages rotate and show elapsed time.
| Key | Action |
|---|---|
↑ ↓ |
Navigate rows |
← → |
Scroll columns |
enter |
Select version for CSS extraction |
/ |
Enter search mode - type to filter table live |
m |
Toggle mobile versions |
e |
Toggle early access / insider versions |
f |
Show only versions with Docker images |
s |
Toggle sort priority: extracted CSS first → Docker found → N/A → missing |
? |
Toggle help overlay |
Esc |
Close search / help overlay |
q / ctrl+c |
Quit |
ocd diff -p # interactive picker (two-step selection)
ocd diff 1.12.6 1.12.7 # direct CLI
ocd diff --tldr 1.12.6 1.12.7 # TLDR analysis (CSS heuristics) + TOML export
ocd diff --tldr --tldr-format json 1.12.6 1.12.7 # TLDR as JSONocd stat 1.12.7 # show CSS stats + export to TOML
ocd stat 1.12.7 --format json # export as JSON
ocd stat 1.12.7 --output ~/reports # custom output directoryAnalyze a single version's app.css for selector count, CSS variables,
color usage, and specificity - useful for tracking CSS bloat over time.
The diff viewer opens with scrollable, colorized output. Active hunk lines are highlighted with a blue background.
| Key | Action |
|---|---|
↑ ↓ j k |
Scroll one line |
pgup pgdn |
Scroll one page |
{ h / } l |
Jump prev/next diff hunk |
gg / G |
Jump to top / bottom of diff |
zz / zt / zb |
Center / top / bottom current hunk |
n / N |
Next / previous (search match when searching, else hunk) |
/ |
Search within diff - highlights matching pattern |
v |
Toggle vertical split/unified view |
y |
Yank current hunk to clipboard |
Y |
Yank entire diff to clipboard |
yy |
Yank current hunk line content |
e |
Export TLDR analysis to TOML |
o |
Open diff viewer ($OCD_DIFF_PAGER / $EDITOR / delta / less -R) |
? |
Toggle help overlay |
Esc |
Close search / help overlay |
q / ctrl+c |
Quit diff viewer |
Picker mode (when called with -p):
| Key | Action |
|---|---|
↑ ↓ |
Navigate versions |
enter |
Select version |
/ |
Search/filter versions |
m |
Toggle mobile versions |
q / ctrl+c |
Cancel |
ocd extract 1.12.7
ocd extract 1.12.7 --refresh # download again, ignoring the cacheA version that is already cached is not downloaded again. The command says
so and points to --refresh and ocd clean <version>.
ocd clean
ocd clean 1.12.7 # remove a single versionocd check 1.12.7 ./my-theme.css
ocd check 1.12.7 ~/.config/obsidian/themes/flexcyon # a theme folder
ocd check 1.12.7 ./my-theme.css --no-selectors
ocd check 1.12.7 ./my-theme.css --format json --output ~/reports
ocd check 1.12.7 ./my-theme.css --silent --output ~/reports
ocd check 1.12.7 ./my-theme.css --compat-mode strict
ocd check 1.12.7 ./my-theme.css --compat-mode strict --compat-sweepExtract the CSS variables from a target Obsidian version and compare them against a local theme file. The report lists variables that are missing from the theme (present in the target) and variables that the theme defines that are not in the target.
The theme may be a single stylesheet or a folder. For a folder, every CSS file
below it is read, with theme.css first. Only CSS custom properties are
compared.
A selector report is printed too, with the exact match count and the count of
target selectors a theme selector reaches. Use --no-selectors to skip it.
SCSS sources are not read. A Sass file defines its palette as $variables
that are injected at build time, and Obsidian never sees them, so comparing
them gives results that look authoritative but are not. Build the theme first
and point at the folder.
When --compat-mode is set to strict or relaxed, the command also runs a
compatibility check on the theme's CSS variables against the target version.
The check searches the versions already cached, so run ocd origin or add
--compat-sweep to cache every public desktop version first. A check on a
thin cache gives a wrong answer, because a variable that looks new may only
be missing from the cache. In strict mode, an incompatible result yields a
non-zero exit code.
Defaults for the --format and --output flags on diff, stat, and
check may be set in a configuration file. See .ocd.toml
for a sample configuration file with all valid options. When no output
directory is configured, file export defaults to the current working
directory.
Full configuration reference: docs/CONFIG.md.
Files are TOML. Resolution order, from highest to lowest priority, is:
- Direct command-line flag value.
- Per-project config:
.ocd.tomlin the current working directory. - Global config:
$XDG_CONFIG_HOME/ocd/config.toml, falling back to~/.config/ocd/config.tomlwhen$XDG_CONFIG_HOMEis unset.
Only fields that are explicitly set in a config file participate in the merge, so a global config can set a default without overriding a per-project value.
Example global config:
stat_format = "json"
stat_dir = "~/reports"
tldr_dir = "~/reports"
origin_format = "toml"
origin_dir = "~/reports"Example per-project config:
[tldr]
tldr = true
tldr_format = "yaml"| Command | Description |
|---|---|
interact |
TUI browser with async loading, search, filters, CSS status column |
extract <ver> |
Download + extract app.css from GitHub releases |
diff [a] [b] |
Interactive picker or direct diff with colored viewer |
stat <ver> |
CSS composition stats (selectors, variables, colors) for a single version |
check <ver> <theme> |
Compare a theme's CSS variables against a target version |
| `origin <sel | var>` |
clean |
Wipe .obsidian_cache/ metadata and extracted CSS |
cwd:~$ ./ocd stat 1.12.7
____ __________ 1.12.7
/ __ \/ ____/ __ \ 20644 LOC, +1439 selectors, +1049 variables
/ / / / / / / / / 66 !important, hex:252 hsl:38 rgb:249
/ /_/ / /___/ /_/ / specificity mean 16.4 median 10.0 mode 10.0
\____/\____/_____/
Exported: /home/data/Desktop/projects/ocd/ocd-stat-1.12.7.tomladditions_loc = 20644
average_specificity = 16.42807505211954
css_variables_added = ["--undo-button-bg-color-hover", "--link-outline", "..."]
deletions_loc = 0
important_count = 66
selectors_added = [".messageBar", ".messageBar .closeButton", "#editorUndoBar", "..."]
total_selectors_analyzed = 1439
version_a = "1.12.7"
version_b = ""
[color_counts]
hex = 252
hsl = 38
rgb = 249cwd:~$ ./ocd diff --tldr 1.11.5 1.12.7
____ __________ 1.11.5 -> 1.12.7 (minor)
/ __ \/ ____/ __ \ 507 insertions(+), 258 deletions(-)
/ / / / / / / / / +249 LOC, +24 selectors, -15 selectors
/ /_/ / /___/ /_/ / +26 variables, -6 variables, ~2 changed
\____/\____/_____/ 3 !important, hex:13 rgb:7
specificity mean 18.7 median 20.0 mode 20.0
Exported: /home/data/Desktop/projects/ocd/ocd-tldr-1.11.5-1.12.7.tomladditions_loc = 507
average_specificity = 18.71794871794872
css_variables_added = ["--background-modifier-form-field-hover", "--metadata-property-corner-shape-focus", "..."]
css_variables_removed = ["--nav-item-weight-active", "--corner-smoothing", "..."]
deletions_loc = 258
important_count = 3
selectors_added = [".file-browser-description", ".modal-view-options-toolbar", "..."]
selectors_removed = [".download-attachments", ".download-attachments .download-attachment-item", "..."]
semver_bump = "minor"
total_selectors_analyzed = 39
version_a = "1.11.5"
version_b = "1.12.7"
[color_counts]
hex = 13
rgb = 7
[[css_variables_changed]]
name = "--view-bottom-fade-mask"
old_value = "linear-gradient(to top, rgba(0, 0, 0, var(--view-bottom-fade-opacity)) 0%, #000000 1px)"
new_value = "linear-gradient(to top, #000 0%, #000 1px)"
[[css_variables_changed]]
name = "--hidden-nav-offset"
old_value = "calc(var(--view-header-height) + var(--header-top-offset))"
new_value = "calc(var(--view-header-height) + var(--view-header-top-offset))"ocd origin .messageBar # find earliest version with this selector
ocd origin -- --my-var # find earliest version with this variable
ocd origin ".my-selector" --format json --output ~/reports # export result
ocd origin ".my-selector" --refresh # force refresh metadata and CSS cache
ocd origin --cache-days 30 -- .my-selector # keep cached CSS for 30 daysSearch app.css across public desktop Obsidian versions (from the
RSS changelog) and report the earliest version where the given selector or
CSS variable was first introduced. Targets starting with -- are treated as
CSS variables; use -- as a separator to pass them as arguments.
The command caches app.css for every public desktop version on demand, so
the answer covers the whole public history instead of only the versions that
are already cached. The first run downloads every release, which takes some
time and about 880 MB. Later runs reuse the cache.
Cached CSS is reused for 14 days. After that it is downloaded again. Use
--cache-days or cache_days in config to change the age, --refresh to
download everything again now, or ocd clean <version> to drop a single
entry. Versions with no GitHub release are reported as unavailable and
skipped.
Results are printed to stdout and exported to the current working directory
by default. Use --output or origin_dir in config to specify a target
directory. Use --format or origin_format in config to choose between
TOML (default), JSON, and YAML.
- Fetches version list from Obsidian's RSS changelog
- Cross-references with Docker Hub availability and Electron-to-Chromium mappings
- Extracts
app.cssdirectly fromobsidian-{version}.asar.gzon GitHub releases - Parses the Electron ASAR archive in pure Go - no external tools needed
make build # build binary
make test # run unit tests
make cover # tests + coverage report
make cover-html # tests + HTML coverage report in browser
make fmt # go fmt + go vet
make lint # golangci-lint
make release-test # goreleaser snapshot (no upload)
make watch # rebuild and rerun the app on save (needs air)
make clean # remove binary and cachemake watch uses air and the file
.air.toml. Install air first:
go install github.com/air-verse/air@latestTests cover RSS electron fill, Docker tag parsing, ASAR extraction, CSS diff, cache operations and other details.
See docs/changelogs/index.md for the full history of changes.
This project's documentation is written in British English following the discipline of ASD-STE100 Simplified Technical English, via the SimpleEnglish skill.
AI Assistance is used when working on the codebase.
MIT License.