Maintain and debug the link-curator web dashboard (port 8090). Separate process from the official Hermes dashboard.
npx skills add https://github.com/dodo-reach/hermes-link-curator --skill link-curator-dashboard
The link-curator dashboard is a FastAPI app running on port 8090, separate from the official Hermes dashboard (hermes dashboard, default port 9119). It reads from the vault at <profile-dir>/vault/ and exposes a read-only web UI.
# Find all dashboard processes
lsof -nP -iTCP:8090 -sTCP:LISTEN
# Check the command
ps -p <pid> -o pid,command
# Check working directory (macOS)
lsof -a -p <pid> -d cwd
# Check working directory (Linux)
readlink /proc/<pid>/cwd
Current layout (verify with ps aux | grep "8090"):
uvicorn main:app --port 8090 from <profile-dir>/dashboard/hermes dashboard command, default port 9119). They are independent and can run side-by-side.| Component | Path |
|-----------|------|
| Dashboard app | <profile-dir>/dashboard/ |
| Main app entry | <profile-dir>/dashboard/main.py |
| Vault parser | <profile-dir>/dashboard/archive.py |
| Templates | <profile-dir>/dashboard/templates/ |
| Vault | <profile-dir>/vault/ |
| Endpoint | Purpose |
|----------|---------|
| GET / | Dashboard UI |
| GET /health | Health check + entry count |
| GET /stats | Tag counts, date distribution |
| GET /reload-cache | Force-clear and reload vault cache |
| GET /calendar | Calendar view |
| GET /search?q=... | Search entries |
| GET /tag/{tag} | Entries for a specific tag |
| GET /day/YYYY-MM-DD | Entries for a specific day |
| GET /day-json/YYYY-MM-DD | JSON feed for a day |
| GET /graph | Force-directed graph view (HTML + D3.js) |
| GET /graph-json | Graph dataset as {nodes, links} JSON |
archive.py uses mtime-based invalidation — no lru_cache, no manual invalidation needed:
get_all_entries() checks the mtime of INDEX.mdThis means the dashboard always reflects the current vault state without any manual intervention. The only time you need to act is when the file can't be read (permissions, disk error) — in that case the last valid cache is served and a warning is logged.
/reload-cache still exists for edge cases (corrupted mtime, network mounts with broken stat) — it's a manual force-refresh that clears the cache and re-reads immediately.
The dashboard has no watchdog and dies silently (e.g. after a system reboot). Unlike the official Hermes dashboard which can be supervised by hermes gateway install, the link-curator dashboard must be manually restarted when the process exits. See "Dashboard went offline — restart procedure" below.
cd <profile-dir>/dashboard
./start.sh 8090
For a background process:
cd <profile-dir>/dashboard
mkdir -p ../logs
nohup ./start.sh 8090 > ../logs/dashboard.log 2>&1 &
After starting, verify readiness:
sleep 2 && curl -s --max-time 3 http://localhost:8090/health
Expected: {"status":"healthy","total_entries":N,...}
If health fails, check the background process output:
tail -50 <profile-dir>/logs/dashboard.log
cd <profile-dir>/dashboard && python3 validate.py
> Pitfall: The profile root at <profile-dir>/ also has a validate.py — it is a DIFFERENT file. Running python3 validate.py from the wrong directory gives wrong results or errors. See references/validate-path.md for the correct path, common wrong paths, and exit code meanings.
--- separator trapWhen removing a malformed entry from INDEX.md (e.g. one with a wrong header level like # Index instead of ### Title), you may leave behind a dangling --- separator. Combined with the next entry's ---, this creates a double separator pattern (---\n\n---) which produces an empty entry chunk. The validator reports it as ERROR: Missing or malformed ### title line at chunk N.
Fix: After removing a bad entry, check the surrounding context in both INDEX.md and the relevant daily note file (e.g. vault/2026-MM-DD.md). Ensure only ONE --- separates entries, not two. The validator will catch this (Has errors: 1, ERROR: Missing or malformed ### title line) — re-check and patch the orphaned separator.
Why patch misses it: The malformed entry sits in a section of the file where many entries share identical string patterns (e.g. - Summary: ... lines). patch reports Found N matches for old_string and refuses to guess. Use Python direct file manipulation instead:
python3 - <<'EOF'
path = "<profile-dir>/vault/INDEX.md"
with open(path) as f:
lines = f.readlines()
# Find the target line and insert/delete as needed
for i, line in enumerate(lines):
if line.startswith("### TARGET ENTRY"):
# remove: del lines[i-1:i+6] (separator + entry)
# insert: lines.insert(i, new_entry_text)
break
with open(path, "w") as f:
f.writelines(lines)
EOF
Use this sequence before assuming server-side problems:
curl http://localhost:8090/health — get total_entries countcurl http://localhost:8090/day-json/YYYY-MM-DD — check specific days; returns JSON array of entriescurl http://localhost:8090/ | grep 'entry-card'` — count entry cards in raw HTMLCtrl+Shift+R or incognito windowobsidian skill → INDEX.md Health Check section for the Python chunk analysis one-liner that catches merged entries and double---- separators in seconds.Quick health check (always run after INDEX.md edits):
cd <profile-dir>/dashboard && python3 validate.py
Expected: Has errors: 0, Fully valid: N matching vault entry count.
Key signal: total_entries from /health matches vault count → server is fine, client cache is the issue.
The /graph endpoint renders a D3.js force-directed graph over the vault. Data
is built by get_graph_data() in archive.py and exposed via /graph-json.
Data shape (tag-graph, two node kinds):
nodes: {id, label, kind: "tag"|"entry", count, type?, url?}links: {source: tag_id, target: entry_id} — bipartite, no entry↔entry edgesWhy tag-graph instead of entry-graph: with 100+ entries, an entry↔entry
similarity graph becomes a hairball. Tag hubs collapse shared topics into a
readable cluster, the way Obsidian's native graph view does. Entry count is
O(entries × avg_tags), link count is O(tag_appearances).
Filtering rule: tags with count < 2 are dropped (reduces noise from
one-off tags) and entries that share zero active tags are omitted as orphans.
With 103 entries this produces ~78 tag nodes + ~101 entry nodes + ~400 links.
Front-end interactions (D3 v7 via CDN, no npm install):
See references/graph-view.md for the full D3 template, force tuning, color
map, and a reusable starter at templates/force-graph.html (copy + change the
data endpoint to reuse for a different graph).
For any new page that needs the same nav + footer as the rest of the dashboard:
main.py (HTML response) + matching -json for datacurrent_page string in the template contexttemplates/base.html (use {% if current_page == '<name>' %})base.html if it needs stylesarchive.py if it parses the vaultCtrl+Shift+R or incognito. The dashboard is read-heavy and browsers aggressively cache it.write_file directly on INDEX.md without reading it first, wiping all previous entries. If only June entries show, May is gone. Fix: rebuild from daily notes using scripts/rebuild_index.py (see below). Prevention: use save_entry.py from the obsidian skill for all new saves — it does atomic read+patch, never full overwrite.If INDEX.md was overwritten and entries are missing, rebuild from the daily notes:
cd <profile-dir>/skills/note-taking/obsidian/scripts
python3 rebuild_index.py
Then validate:
cd <profile-dir>/dashboard && python3 validate.py
curl http://localhost:8090/reload-cache
obsidian — vault entry format, save workflow, validate.pycamofox — for browser-session fetching on sites that block simple extractionreferences/validate-path.md — validate.py location, exit codes, what it checksreferences/proc_pid_working_dir.md — how to resolve "which process is actually running on this port"references/vault-dashboard-discrepancy.md — troubleshooting missing entries: always verify INDEX.md Added field vs dashboard groupingreferences/graph-view.md — D3 force-directed graph: data shape, force tuning, color map, interaction patternsComprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas
Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in my downloads\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.
Picks random winners from lists, spreadsheets, or Google Sheets for giveaways, raffles, and contests. Ensures fair, unbiased selection with transparency.
Query openFDA API for drugs, devices, adverse events, recalls, regulatory submissions (510k, PMA), substance identification (UNII), for FDA regulatory data analysis and safety research.
MATLAB and GNU Octave numerical computing for matrix operations, data analysis, visualization, and scientific computing. Use when writing MATLAB/Octave scripts for linear algebra, signal processing, image processing, differential equations, optimization, statistics, or creating scientific visualizations. Also use when the user needs help with MATLAB syntax, functions, or wants to convert between MATLAB and Python code. Scripts can be executed with MATLAB or the open-source GNU Octave interpreter.
UMAP dimensionality reduction. Fast nonlinear manifold learning for 2D/3D visualization, clustering preprocessing (HDBSCAN), supervised/parametric UMAP, for high-dimensional data.
Creating interactive data visualisations using d3.js. This skill should be used when creating custom charts, graphs, network diagrams, geographic visualisations, or any complex SVG-based data visualisation that requires fine-grained control over visual elements, transitions, or interactions. Use this for bespoke visualisations beyond standard charting libraries, whether in React, Vue, Svelte, vanilla JavaScript, or any other environment.
Access AlphaFold 200M+ AI-predicted protein structures. Retrieve structures by UniProt ID, download PDB/mmCIF files, analyze confidence metrics (pLDDT, PAE), for drug discovery and structural biology.
Take dodo-reach/link-curator-dashboard from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm.
Without those the skill loads but fails at the first command.