Obsidian-wiki graph-colorize
git clone https://github.com/Ar9av/obsidian-wiki
T=$(mktemp -d) && git clone --depth=1 https://github.com/Ar9av/obsidian-wiki "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.skills/graph-colorize" ~/.claude/skills/ar9av-obsidian-wiki-graph-colorize && rm -rf "$T"
.skills/graph-colorize/SKILL.mdGraph Colorize — Color-code the Obsidian Graph View
You are rewriting
$OBSIDIAN_VAULT_PATH/.obsidian/graph.json so Obsidian's graph view tints nodes by tag, folder, or visibility.
Obsidian stores graph settings in
<vault>/.obsidian/graph.json. The colorGroups array is a list of {query, color} pairs; the first matching query wins per node. Queries use Obsidian's search syntax: tag:#foo, path:"concepts", file:foo, etc. Color is {"a": 1, "rgb": <packed-int>} where the int is (R << 16) | (G << 8) | B.
Before You Start
- Read
, or fall back to~/.obsidian-wiki/config
in this repo, to get.env
.OBSIDIAN_VAULT_PATH - Confirm
exists. If it doesn't, the vault has never been opened in Obsidian — tell the user to open the vault once in Obsidian, then re-run.$OBSIDIAN_VAULT_PATH/.obsidian/ - Warn the user if Obsidian is likely open: Obsidian overwrites
on close. Tell them to close the vault first, or be ready to reload (Cmd/Ctrl+R) and not touch the graph settings until they reload.graph.json
Step 1: Pick a Mode
Infer the mode from the user's phrasing. If ambiguous, default to by-tag.
| User intent | Mode |
|---|---|
| "color by tag", "color my graph", "make it colorful" (default) | |
| "color by folder", "color by category", "color by directory" | |
| "highlight visibility", "show internal/pii in graph", "visibility colors" | |
User provides explicit mapping (, or JSON blob) | |
| "combine tag and visibility" / "both" | (visibility first, then tag) |
Step 2: Build the colorGroups
Array
colorGroupsPalette (10 distinct, colorblind-friendly colors)
Use in order. If more groups than colors, cycle and add a lightness shift by dividing brightness ~20% via a second pass — or just cap at 10 and tell the user the remaining tags share the "other" color.
| # | Hex | rgb (packed int) | Role |
|---|---|---|---|
| 0 | | | blue |
| 1 | | | orange |
| 2 | | | red |
| 3 | | | teal |
| 4 | | | green |
| 5 | | | yellow |
| 6 | | | purple |
| 7 | | | pink |
| 8 | | | brown |
| 9 | | | gray |
Every color is wrapped as
{"a": 1, "rgb": <int>}.
Mode: by-tag
by-tag- Glob
excluding$VAULT_PATH/**/*.md
,_archives/
,_raw/
,.obsidian/
,node_modules/
,index.md
,log.md
._insights.md - Parse frontmatter
from each page. Count usage per tag.tags - Drop
tags from the frequency list — they are reserved system tags, handled only invisibility/*
orby-visibility
mode.combined - Take the top 10 tags by usage. If there are fewer than 10 unique tags, use all of them.
- For each tag
at indexT
: emiti
.{"query": "tag:#T", "color": palette[i]} - Optionally, append a final catch-all entry for untagged pages at the end:
— skip if color slot 9 is already taken by a real tag.{"query": "-[\"tag\":]", "color": palette[9]}
Mode: by-category
by-categoryUse the seven vault top-level folders in this fixed order so colors are stable across runs:
| Folder | Color index |
|---|---|
| 0 (blue) |
| 1 (orange) |
| 2 (red) |
| 3 (teal) |
| 4 (green) |
| 5 (yellow) |
| 6 (purple) |
Emit one entry per folder that exists AND contains at least one
.md file. Each entry is:
{"query": "path:\"<folder>\"", "color": {"a": 1, "rgb": <int>}}
Mode: by-visibility
by-visibilityEmit exactly three entries, in this order (first-match wins, so most restrictive comes first):
→visibility/pii
(red, rgb 14767961)#E15759
→visibility/internal
(orange, rgb 15896107)#F28E2B
→visibility/public
(green, rgb 5873999)#59A14F
{"query": "tag:#visibility/pii", "color": {"a": 1, "rgb": 14767961}}
Pages with no
visibility/ tag remain Obsidian's default color — do not add a catch-all.
Mode: combined
combinedEmit
by-visibility entries first, then by-tag entries. Visibility wins on conflict because it appears first in the list.
Mode: custom
customIf the user gave explicit mappings, honor them literally. Convert any hex they give (e.g.
#FF00FF) to packed int using int(hex_without_hash, 16). Wrap each as {"a": 1, "rgb": <int>}.
Step 3: Merge into graph.json (Do Not Clobber)
-
Read the existing
. If it doesn't exist, start from this minimal default:$VAULT_PATH/.obsidian/graph.json{ "collapse-filter": true, "search": "", "showTags": false, "showAttachments": false, "hideUnresolved": false, "showOrphans": true, "collapse-color-groups": false, "colorGroups": [], "collapse-display": true, "showArrow": false, "textFadeMultiplier": 0, "nodeSizeMultiplier": 1, "lineSizeMultiplier": 1, "collapse-forces": true, "centerStrength": 0.518713248970312, "repelStrength": 10, "linkStrength": 1, "linkDistance": 250, "scale": 1, "close": true } -
Back up first: copy the existing file to
before writing. If a backup from the same minute exists, reuse it — don't pile up duplicates..obsidian/graph.json.backup-<YYYYMMDD-HHMM> -
Replace only the
field with your new array. Leave every other field untouched. This preserves the user's zoom, physics, filter, search, and display preferences.colorGroups -
Write the file back with the same JSON style as the original (usually compact single-line or 2-space indent — preserve what's there).
Step 4: Report and Log
Print a summary like:
Graph colorized → .obsidian/graph.json Mode: by-tag Groups: 7 color assignments Palette: blue, orange, red, teal, green, yellow, purple Backup: .obsidian/graph.json.backup-20260424-1432 Reload Obsidian (Cmd/Ctrl+R) to see the new colors. If Obsidian is currently open, close it first OR reload immediately — Obsidian overwrites graph.json on close and can erase these changes.
Append to
$VAULT_PATH/log.md:
- [TIMESTAMP] GRAPH_COLORIZE mode=<mode> groups=<N> backup=graph.json.backup-<stamp>
Edge Cases
- No tags in vault in
mode → fall back toby-tag
and tell the user.by-category - User wants to undo → restore from the latest
and note that ingraph.json.backup-*
.log.md - User wants to clear all color groups → set
, back up, log ascolorGroups: []
.GRAPH_COLORIZE mode=clear
missing → the vault hasn't been opened in Obsidian yet. Tell the user to open it once, then re-run. Don't create.obsidian/
yourself — Obsidian populates many files there on first open..obsidian/- Query syntax gotchas: folder paths with spaces need quoting (
); tags with nested slashes work literally (path:"my folder"
); don't URL-encode.tag:#visibility/internal - Obsidian open during edit: surface the risk — Obsidian reads graph.json at startup and rewrites it on close. If the user is editing live, tell them to close Obsidian first or run the reload (Cmd/Ctrl+R) immediately and avoid opening graph settings before they do.
Notes
- This is a pure config edit — no page content changes, no frontmatter writes.
- Re-running is safe: each run creates a new backup, only
is rewritten.colorGroups - If the user has manually curated color groups they want to keep, offer
mode or ask before overwriting.combined - The palette here matches
'swiki-export
community colors, so the Obsidian graph and the exported visualization look consistent.graph.html