LLM Wiki
lewislulu/llm-wiki-skill
Build and maintain a Karpathy-style LLM knowledge base — a self-compiling Obsidian markdown wiki where an Agent ingests raw sources, compiles cross-linked concept/entity/summary pages, answers…
Archives links a user sends into a profile's Obsidian-style vault with a save script and replies with a one-word confirmation instead of a summary.
$ npx skills add dodo-reach/hermes-link-curator --skill obsidian -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install dodo-reach/hermes-link-curator obsidian --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/dodo-reach/hermes-link-curator.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skill-obsidian .claude/skills/obsidian && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "obsidian" agent skill from https://github.com/dodo-reach/hermes-link-curator/tree/main/skill-obsidian into .claude/skills/obsidian/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "obsidian", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/dodo-reach/hermes-link-curator/tree/main/skill-obsidianType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add dodo-reach/hermes-link-curator --skill obsidian -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install dodo-reach/hermes-link-curator obsidian --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dodo-reach/hermes-link-curator.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skill-obsidian .agents/skills/obsidian && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "obsidian" agent skill from https://github.com/dodo-reach/hermes-link-curator/tree/main/skill-obsidian into .agents/skills/obsidian/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "obsidian", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add dodo-reach/hermes-link-curator --skill obsidian -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install dodo-reach/hermes-link-curator obsidian --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dodo-reach/hermes-link-curator.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skill-obsidian .cursor/skills/obsidian && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "obsidian" agent skill from https://github.com/dodo-reach/hermes-link-curator/tree/main/skill-obsidian into .cursor/skills/obsidian/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "obsidian", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/dodo-reach/hermes-link-curator.git --path skill-obsidian--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add dodo-reach/hermes-link-curator --skill obsidian -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install dodo-reach/hermes-link-curator obsidian --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dodo-reach/hermes-link-curator.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skill-obsidian .gemini/skills/obsidian && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "obsidian" agent skill from https://github.com/dodo-reach/hermes-link-curator/tree/main/skill-obsidian into .gemini/skills/obsidian/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "obsidian", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install dodo-reach/hermes-link-curator obsidianInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add dodo-reach/hermes-link-curator --skill obsidian -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/dodo-reach/hermes-link-curator.git skills-src && mkdir -p .github/skills && cp -r skills-src/skill-obsidian .github/skills/obsidian && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "obsidian" agent skill from https://github.com/dodo-reach/hermes-link-curator/tree/main/skill-obsidian into .github/skills/obsidian/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "obsidian", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add dodo-reach/hermes-link-curator --skill obsidian -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install dodo-reach/hermes-link-curator obsidian --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dodo-reach/hermes-link-curator.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skill-obsidian .opencode/skills/obsidian && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "obsidian" agent skill from https://github.com/dodo-reach/hermes-link-curator/tree/main/skill-obsidian into .opencode/skills/obsidian/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "obsidian", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
obsidianArchives links a user sends into a profile's Obsidian-style vault with a save script and replies with a one-word confirmation instead of a summary.
The agent acts as a librarian: when you send a URL or ask to save something, it files the entry immediately, does not summarize it unless asked, and answers only with a short confirmation or the error. Entries have a title, the URL and a type such as github, x-post, article, tool, video, paper or other, with a separator line between entries so the dashboard can split them.
Saving goes through `save_entry.py`, which reads the index first, inserts the new entry after the first separator, appends to a daily note named by date, and runs `validate.py`. A note describes an earlier bug that merged same-day entries in one chunk and explains how `rebuild_index.py` repairs affected vaults. For X posts that hit login walls, an optional local browser-session service can fetch the content. The excerpt is cut off in that section.
5 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 5bd4d5a. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 2 files in scripts/ (Python), which the agent can run.
Shell commands in SKILL.md call:
python3curlvaultFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
x.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Link Curator Obsidian Vault loads about 2.8k tokens when it runs. Until then it costs about 13 tokens; SKILL.md has 768 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from dodo-reach/hermes-link-curator at commit 5bd4d5a, republished under its MIT licence (© dodo-reach). 768 words, ~2,796 tokens.
.claude/skills/obsidian/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.When the user sends a URL or asks to save/archive something: archive it immediately, do not summarize. Your job is that of a librarian: receive → process → file. Not a chatbot to give impressions.
Do NOT respond with a summary of the content unless the user explicitly asks for one. Archive first, then say only "Salvato." (or the error, if any).
<profile-dir>/vault/
### [Title]
- **URL**: https://...
- **Type**: `github` | `x-post` | `article` | `tool` | `video` | `paper` | `other`
- **Tags**: #tag1 #tag2 #tag3
- **Added**: YYYY-MM-DD
- **Summary**: What is this? Why does it matter? What do you do with it?
---
[next entry]--- between every entry. No --- = merged entry in dashboard. Parse splits on \n---\n.
Use save_entry.py — atomic read+patch, never overwrites INDEX.md.
python3 <profile-dir>/skills/note-taking/obsidian/scripts/save_entry.py \
--url "https://..." \
--title "Entry Title" \
--type "article" \
--tags "ai dev-tools" \
--added "2026-06-04" \
--summary "What it is and why it matters."The script:
--- separator--- separator (each entry gets its own chunk)vault/YYYY-MM-DD.md⚠️ Prepend bug (fixed): Old versions of
save_entry.pyinserted new entries BETWEEN the header and the first---. When two entries from the same day were saved consecutively, they ended up in the same chunk. The dashboard parser usesre.findall(r'\*\*URL\*\*', chunk)[0]— only the first URL per chunk was read. Symptoms: entry appears in INDEX.md but not in dashboard (or dashboard shows fewer entries than vault count). Fix was to changelines[:sep_idx]→lines[:sep_idx+1]so insertion happens AFTER the separator, not before. Runrebuild_index.pyto fix retroactively affected vaults.
Validate after save is automatic via the script. Manual validate (if needed):
cd <profile-dir>/dashboard && python3 validate.pyManual workflow (only if script unavailable): fetch → append to daily → read INDEX first, then patch to prepend. Never use write_file on INDEX.md.
web_extract and browser_navigate may return login walls on X. If camofox is installed and the content is important, use its local REST API:
CAMOFOX_USER_ID="${CAMOFOX_USER_ID:-link-curator}"
TAB=$(curl -s -X POST http://localhost:9377/tabs \
-H "Content-Type: application/json" \
-d "{\"userId\":\"$CAMOFOX_USER_ID\",\"sessionKey\":\"link-curator\",\"url\":\"https://x.com/USERNAME/status/POST_ID\"}")
TAB_ID=$(echo "$TAB" | python3 -c "import sys,json; print(json.load(sys.stdin)['tabId'])")
sleep 3
curl -s "http://localhost:9377/tabs/$TAB_ID/snapshot?userId=$CAMOFOX_USER_ID" > /tmp/snap.json
python3 -c "import sys,json; d=json.load(open('/tmp/snap.json')); print(d.get('snapshot','')[:6000])"
curl -s -X DELETE "http://localhost:9377/tabs/$TAB_ID?userId=$CAMOFOX_USER_ID"Content is in .snapshot, NOT .accessibilityTree.content. jq is not available — always use python3 for JSON parsing.
If camofox fails: web_search fallback, then save URL + [content unavailable] in Summary.
Quick probe before long fetch — when the URL is an X post and you need content for the Summary:
curl -s --max-time 8 "https://x.com/USER/status/ID" — if it returns HTML login page, X is blockingweb_search or save with [content unavailable]When the user says "this link is about X" or provides the summary framing directly, trust it and stop digging. Do not spend extra tool calls trying to extract more context from the page — the user already told you what matters. Supplement with whatever minimal snapshot data is readily available, then save. Examples:
"é un video che spiega che codex può lanciare, pinnare e gestire worktrees" → use that framing, add minimal post metadata (author, engagement) from snapshot, done."save this, it's about local LLMs" → accept the user's framing, don't try to fetch deeper content.Only fall back to [content unavailable] if camofox itself fails AND no user context was provided.
# By content
grep -ri "keyword" <profile-dir>/vault/ --include="*.md"
# By date
ls <profile-dir>/vault/2026-05-*.mdThe validate script lives in the dashboard directory, NOT in the profile vault. In a standard install the dashboard is at ~/.hermes/profiles/<profile>/dashboard/ and the vault is at ~/.hermes/profiles/<profile>/vault/.
Always check the actual path before running:
# Find validate.py
find ~ -name "validate.py" 2>/dev/null | grep -i dashboardNever hardcode a path you haven't verified. The cd must target the dashboard directory, not the vault.
Keep tags consistent across the vault. When saving new entries, follow these rules:
#open-source — never use #open alone#dev-tools — for developer tooling entries (CLI, agents, utility scripts). Use #dev-tools consistently, not #tooling or #tools (except for entries that are genuinely about design/general tools)#local-ai — for local inference, Apple Silicon, on-device ML#local and #local-ai → always use #local-aiDo NOT use **Status**: 'unread' or any other status markers. The vault stores processed entries only. For items to revisit later, use a bookmarking tool outside the vault (read-later app, pin board, etc.).
## Dashboard (port 8090) — separate process
The link-curator dashboard runs at `http://localhost:8090`. It is a standalone FastAPI app, not part of the official Hermes dashboard (`hermes dashboard`, default port 9119). The two are independent and can run side-by-side. See the `link-curator-dashboard` skill for full maintenance and restart procedures.
## Architecture rules (critical — never violate)
**Entry blocks do NOT contain `---`. The separator is added at write time.**
`build_entry_block()` returns text ending in `\n` (no `---`). Both `append_to_daily()` and `prepend_to_index()` add `\n---\n` or `\n---` as a separator when writing. If you put `---` inside `build_entry_block`, you get double `---` on prepend, which corrupts the dashboard parse.
**Never use `write_file` on INDEX.md.** The only safe operations are `read_file` first, then `patch`. Even for "quick edits" — read first, patch, never overwrite. The script enforces this; manual workflow must follow it too.
**INDEX.md headers differ by profile.** The `prepend_to_index` function finds the first `---` separator regardless of what header text precedes it:
- Default: `# Index` followed by `---`
- Custom profile headers are also fine as long as the first separator is `---`
Do NOT look for a specific header string — find the first `---` and insert after it.
**Validate after every save.** Run `validate.py` from the dashboard directory, not the vault.
## validate.py — common fixes
**Empty entry error ("Missing or malformed ### title line")**:
Usually caused by a double `---` separator creating an empty chunk. Fix: remove the duplicate `---` between two consecutive entries in INDEX.md, leaving only one.
**Title uses em-dash (—) instead of hyphen-minus (-)**:
The parser accepts both in titles, but the `validate.py` script warns on em-dashes. More importantly, some edge cases in the title regex (`^###\s+[^\n—]+?\s+—\s+`) can fail to parse a title that has an em-dash mid-string. **Always use hyphen-minus (`-`) as the title separator in vault entries.** Example: `### Claude Code + Screen Recording Workflow - UI Bug Fixing` not `—`.
**Title validation fails for `# Index` header**:
The title check `r'^###\s+\S'` rejects `# Heading` (single `#`). Chunk 5 (`# Index`) was a valid non-entry header — the URL filter already correctly skips non-entry blocks. Working title regex: `r'^#{1,3}\s+\S'` accepts h1–h3. The URL filter is the primary guard; the title regex is secondary.
---
## INDEX.md Health Check — when to run + manual verification
Run `validate.py` and do a manual chunk split when:
- Entries saved via `save_entry.py` appear in INDEX.md but not in the dashboard
- Dashboard shows fewer entries than vault count suggests
- You made manual edits to INDEX.md (patch, rebuild, etc.)
### validate.py output thresholds
| Result | Meaning |
|--------|---------|
| `Has errors: 0` | Vault is parse-safe. Dashboard will show all entries. |
| `Has errors: >0` | Structural corruption — entries may be missing from dashboard |
| `Fully valid: N` | Entries that pass all field checks |
| `Has warnings: M` | Non-fatal (em-dashes in titles, missing optional fields) — dashboard still works |
**Always run validate.py after any INDEX.md edit.** It's the only way to catch double-`---` chunks and merged entries before the user sees wrong counts.
### Manual chunk analysis (Python one-liner for ad-hoc checks)
```python
content = open('<profile-dir>/vault/INDEX.md').read()
chunks = content.split('\n---\n')
for i, chunk in enumerate(chunks):
titles = [l for l in chunk.split('\n') if l.strip().startswith('###')]
if len(titles) > 1:
print(f"⚠️ Chunk {i}: MERGED {len(titles)} entries")
if '\n---\n' in chunk:
print(f"⚠️ Chunk {i}: double separator")
print(f"✅ {len(chunks)} chunks total, {sum(1 for c in chunks if '**URL**' in c)} with URLs")This catches the prepend bug (two entries in same chunk) and orphaned --- separators (empty chunks) faster than re-reading by eye.
Root cause: write_file on INDEX.md without reading first wipes all entries. If only recent dates appear in dashboard (e.g. June only, May gone), INDEX.md was overwritten.
Fix — run the rebuild script:
cd <profile-dir>/skills/note-taking/obsidian/scripts
python3 rebuild_index.pyPrevention: ALWAYS use save_entry.py for new saves. Never write_file directly on INDEX.md.
grep -c '^### ' <profile-dir>/vault/INDEX.md
# must equal sum of grep -c '^### ' vault/2026-05-*.mdIf counts don't match: extract missing entries from INDEX and patch into the daily file.
write_file on INDEX.md without reading first = overwrite destroys all entries. Always read_file first, then patch.
camofox — for browser-session fetching on sites that block simple extraction© dodo-reach, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 2 other files (scripts) in skill-obsidian of dodo-reach/hermes-link-curator.
Open the folder on GitHubat commit 5bd4d5a
Link Curator Obsidian Vault next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Link Curator Obsidian Vault this skilldodo-reach/hermes-link-curator | 142 | — | ~2.8k | Automated safety check: Pass | MIT | |
| LLM Wikilewislulu/llm-wiki-skill | 655 | — | ~3.7k | Automated safety check: Pass | None | |
| Obsidian CLIpablo-mano/Obsidian-CLI-skill | 450 | — | ~3.2k | Automated safety check: Pass | None | |
| Obsidian Project Knowledge BaseGalaxy-Dawn/claude-scholar | 5.7k | — | ~551 | Automated safety check: Pass | MIT | |
| LLM WikiCharlesHoskinson/sevenlayer | 116 | — | ~1.5k | Automated safety check: Pass | Custom licence | |
| Save to Obsidian VaultAgriciDaniel/claude-obsidian | 15k | — | ~1.3k | Automated safety check: Pass | MIT |
lewislulu/llm-wiki-skill
Build and maintain a Karpathy-style LLM knowledge base — a self-compiling Obsidian markdown wiki where an Agent ingests raw sources, compiles cross-linked concept/entity/summary pages, answers…
pablo-mano/Obsidian-CLI-skill
Lets the agent read, write, search and tidy an Obsidian vault directly through the official Obsidian command-line interface.
Galaxy-Dawn/claude-scholar
Maintains a project-scoped Obsidian research knowledge base: bootstrapping its folders, routing notes, updating hub, plan and index notes, and linting.
CharlesHoskinson/sevenlayer
Builds and maintains a persistent, interlinked Obsidian-compatible markdown knowledge wiki in a git repo via three operations — ingest a source into linked pages, answer a question from the…
AgriciDaniel/claude-obsidian
Files a specific answer, decision or insight you point to in a conversation as one reviewed note in your Obsidian vault, and only when you ask for it.
imraywang/rayskills
Sets up, audits or incrementally upgrades a local Obsidian vault for knowledge work and content production, without overwriting or moving existing notes.
dodo-reach/hermes-link-curator
Maintains and debugs the FastAPI-based link-curator web dashboard on port 8090, a separate process from the official Hermes dashboard.
Works with
Categories
Archives links a user sends into a profile's Obsidian-style vault with a save script and replies with a one-word confirmation instead of a summary. The agent acts as a librarian: when you send a URL or ask to save something, it files the entry immediately, does not summarize it unless asked, and answers only with a short confirmation or the error. Entries have a title, the URL and a type such as github, x-post, article, tool, video, paper or other, with a separator line between entries so the dashboard can split them.
Link Curator Obsidian Vault fits situations like: saving links to a curated vault without writing summaries; fixing an index where saved entries do not show up in the dashboard; fetching an X post that normal page extraction cannot read.
Run `npx skills add dodo-reach/hermes-link-curator --skill obsidian -a claude-code`. Or copy the skill folder (skill-obsidian in dodo-reach/hermes-link-curator) into .claude/skills/obsidian in your project. Claude Code loads it when a task matches its description.
Run `npx skills add dodo-reach/hermes-link-curator --skill obsidian -a codex`. Or copy the skill folder (skill-obsidian in dodo-reach/hermes-link-curator) into .agents/skills/obsidian in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add dodo-reach/hermes-link-curator --skill obsidian -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/obsidian, .gemini/skills/obsidian, .github/skills/obsidian and .opencode/skills/obsidian in your project.
Going by SKILL.md and its folder, Link Curator Obsidian Vault needs Python for the scripts in its folder and the command-line tools its instructions call (python3, curl and vault). Our summary lists: Python 3 to run the save, validate and rebuild scripts; A profile folder containing the vault and dashboard.
SKILL.md names 1 domain. In commands or code: x.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Link Curator Obsidian Vault is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 2.8k tokens (SKILL.md is roughly 11k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Link Curator Obsidian Vault: LLM Wiki (lewislulu/llm-wiki-skill, 655 stars), Obsidian CLI (pablo-mano/Obsidian-CLI-skill, 450 stars), Obsidian Project Knowledge Base (Galaxy-Dawn/claude-scholar, 5.7k stars) and LLM Wiki (CharlesHoskinson/sevenlayer, 116 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
dodo-reach (a GitHub user) maintains it in dodo-reach/hermes-link-curator, which has 142 GitHub stars. The repository holds 2 skills in this directory. The repository was last updated on June 6, 2026.
Source: dodo-reach/hermes-link-curator on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.