Initial release: AI Explainer plugin for Obsidian

Generate linked explanation notes from selections using Claude Code,
Gemini, Codex, or a local Ollama model. Includes 23 sample note modes,
inline enhancement actions, batch generation, a config-driven modes
system, cross-platform installers, and vault repair scripts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
algometrix 2026-07-15 02:51:45 -04:00
commit e22cc920b7
26 changed files with 7219 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
node_modules/
main.js
.claude/
data.json
# Personal mode definitions -- see "Note Modes" in README.md
modes.json
modes.config.json
modes.personal.json

85
CLAUDE.md Normal file
View file

@ -0,0 +1,85 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What This Is
An Obsidian plugin ("AI Explainer") that generates detailed explanation notes using Claude Code CLI, Gemini CLI, Codex CLI, or a local Ollama server. Users select text in a note, pick a teaching style (mode), and the plugin spawns the CLI to produce a new note or inline enhancement. Desktop only; supports Windows, macOS, and Linux.
## Build Commands
```bash
npm install # install dependencies
npm run build # type-check (tsc -noEmit) + production bundle
npm run dev # esbuild watch mode (rebuilds on save)
```
The build produces `main.js` (CJS bundle) from the single entry point `main.ts` via esbuild. Use the [Hot-Reload plugin](https://github.com/pjeby/hot-reload) in Obsidian for live dev.
## Project Structure
This is a single-file plugin. All source code lives in `main.ts` (~3800 lines). There is no `src/` directory.
- **main.ts** -- entire plugin: types, UI modals, queue processor, CLI spawning, output post-processing, settings tab
- **modes.json** -- note generation modes loaded as `BUILTIN_MODES` at build time. Gitignored; resolved before every build by `scripts/sync-modes.js` (from `modes.config.json`'s `modesFile` if present, else an existing `modes.json`, else `modes.sample.json`)
- **modes.sample.json** -- committed sample set of general-purpose modes (Explain, Deep Inquiry, Feynman, etc.); the default build input for fresh clones
- **modes.config.json** -- optional, gitignored; `{ "modesFile": "<path>" }` points the build at a personal modes file (conventionally `modes.personal.json`, also gitignored)
- **styles.css** -- modal and queue UI styles
- **manifest.json** -- Obsidian plugin manifest (id: `ai-explainer`)
- **scripts/** -- build helpers and vault fix scripts:
- **sync-modes.js** -- prepares `modes.json` before builds (see above); wired into `npm run build` and `npm run dev`
- **vault-root.js** -- shared vault path resolution for the fix scripts (first non-flag CLI argument, or `OBSIDIAN_VAULT` env var)
- **fix-all.js** -- unified runner that executes all fix scripts below in order
- **fix-callout-fences.js** -- fixes callout code fences missing the `> ` prefix on closing ``` or content lines
- **fix-currency-dollars.js** -- escapes unescaped `$` currency signs that Obsidian misinterprets as LaTeX
- **fix-mermaid-end.js** -- strips extra `end` keywords with no matching block opener (subgraph, or sequence-diagram par/alt/opt/loop/rect/critical/break/box) and inserts zero-width space into "end" inside larger words
- **fix-mermaid-missing-end.js** -- re-inserts missing `end` keywords (indentation-based placement) and puts the closing ``` fence on its own line
- **fix-split-end.js** -- rejoins lines corrupted by an old regex that split words like "Send" into "S\nend"
- **fix-mermaid-parens.js** -- quotes unquoted mermaid node labels containing parentheses or slashes
- **fix-mermaid-quotes.js** -- strips nested double quotes inside already-quoted mermaid labels (inner `"` to `'`)
- **fix-mermaid-list.js** -- fixes "Unsupported markdown: list" errors by converting `N.`/`N)` to `N:` in labels and joining `- item` lines with `<br/>`
All fix scripts share the same interface: `node scripts/<script> <vault-path>` for detect-only, add `--fix` to apply. The vault path can also come from the `OBSIDIAN_VAULT` environment variable.
## Architecture
### Core Flow
1. User selects text, invokes a command (palette or hotkey)
2. A modal (`NoteCreatorModal`, `InlineActionModal`, etc.) lets the user pick a mode/action and configure options
3. The plugin builds a prompt by substituting `{selection}` and `{context}` placeholders in the mode's prompt template, then appends output rules (`getOutputRules()` / `INLINE_OUTPUT_RULES` / `APPEND_OUTPUT_RULES`)
4. The request is enqueued as a `QueueItem` (four types: `note`, `inline`, `append`, `topic-note`)
5. `processQueue()` processes items sequentially, calling `runClaude()` which spawns the CLI via `child_process.spawn`
6. Output goes through five post-processing fixers: `fixCodeBlocks`, `fixMermaidBlocks`, `fixDetailsBlocks`, `fixCalloutCodeFences`, `fixDataviewInlineQueries`
7. Result is written to a new note, appended to an existing note, or inserted/replaced inline
### Two Command Families
- **Note creation** ("Explain selection"): creates a new note from selection, replaces selection with a `[[wiki-link]]`. Uses modes from `modes.json` + custom user modes.
- **Inline actions** ("Enhance selection"): transforms selected text in-place (expand, simplify, add examples, add diagram, summarize, challenge, fix & polish, ELI5, translate to code). Defined as `INLINE_ACTIONS` in main.ts.
### AI Provider Abstraction
Supports four backends via `settings.aiProvider`: `"claude"` (Claude Code CLI, default), `"gemini"` (Gemini CLI), `"codex"` (OpenAI Codex CLI), and `"ollama"` (local Ollama REST API). The `runClaude()` method branches on provider: Claude, Gemini, and Codex spawn CLI processes, while Ollama calls the HTTP streaming API (`/api/generate`) directly via `fetch`. Claude uses `--disallowedTools` to prevent file writes; Gemini uses `--approval-mode plan`; Codex uses `codex exec --sandbox read-only` with the final message captured via `--output-last-message` (its stdout interleaves progress logs).
### Key Classes
- `ClaudeExplainerPlugin` (line ~2260) -- main plugin class, owns the queue, status bar, all commands
- `NoteCreatorModal` -- mode picker for note creation (multi-select, batch generation)
- `InlineActionModal` -- action picker for inline transformations
- `FullNoteActionModal`, `FillNoteModal`, `TopicGeneratorModal`, `FolderGeneratorModal`, `FolderAnalysisModal`, `NoteAnalysisModal` -- specialized generation modals
- `QueueStatusModal` -- live progress viewer with retry/remove
- `ScaleCalculatorModal`, `ScaffoldWorkspaceModal` -- batch generation utilities
- `ClaudeExplainerSettingTab` -- plugin settings UI
### Output Post-Processing
Generated content is cleaned up before writing to the vault. The fixers (lines ~565-650) handle common LLM output issues: wrapped-in-code-fence removal, mermaid block repairs, `<details>` to callout conversion, callout code fence prefix fixing, and dataview inline query escaping.
### Prompt Template Variables
Mode prompts in `modes.json` use `{selection}` and `{context}` as placeholders. These are string-replaced at enqueue time. Output formatting rules are appended after the mode prompt.
## Notes on Output Formatting Rules
The `getOutputRules()` function defines strict formatting requirements for generated notes: YAML frontmatter first, Title Case headings, no em dashes, short paragraphs, mermaid syntax constraints, Obsidian callout syntax (not HTML `<details>`). These rules are critical to Obsidian rendering compatibility.

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 algometrix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

314
README.md Normal file
View file

@ -0,0 +1,314 @@
# AI Explainer (Obsidian Plugin)
Generate detailed, linked explanation notes from any selection in your vault, powered by an AI CLI (Claude Code, Gemini, or Codex) or a fully local Ollama model. Select text, pick a teaching style, and get a new note wired into your knowledge graph. Desktop only. Works on Windows, macOS, and Linux.
## Quick Start
Five steps from zero to your first generated note. Each step links to a detailed section below if you get stuck.
1. **Install Node.js** (version 16 or newer) from [nodejs.org](https://nodejs.org). Verify with `node --version` in a terminal.
2. **Set up one AI backend.** The two easiest options:
- *Cloud (best quality):* `npm install -g @anthropic-ai/claude-code`, then run `claude` once to log in. Needs a Claude account.
- *Local (free, private):* install [Ollama](https://ollama.com) and run `ollama pull llama3`. No account needed.
All options are covered in [Setting Up an AI Backend](#setting-up-an-ai-backend).
3. **Clone and install the plugin:**
```bash
git clone https://github.com/algometrix/ai_explainer.git
cd ai_explainer
```
- Windows: double-click `install.bat` (or run it in a terminal)
- macOS / Linux: run `./install.sh`
The script builds the plugin and finds your vaults automatically. See [Installation](#installation) for the manual route.
4. **Enable it in Obsidian:** Settings → Community plugins → turn off Restricted mode → refresh → toggle **AI Explainer** on. If you chose Ollama in step 2, also open Settings → AI Explainer and set the provider to Ollama.
5. **Generate your first note:** open any note, select a word or phrase, press `Ctrl+P` (macOS: `Cmd+P`), run **"Explain selection with AI"**, and pick the **Explain** mode. A new linked note appears in the same folder.
If the plugin says it cannot find the CLI, see [Troubleshooting](#troubleshooting); it is almost always a PATH issue with a one-line fix.
## Features and When to Use Them
### Explain Selection (note creation)
Select a word or phrase, run **"Explain selection with AI"**, and pick one or more modes. The plugin creates a new note in the same folder, writes the AI output into it, and replaces your selection with a `[[wiki-link]]`. If a note with that name already exists, it links to it instead. You can select several modes at once to generate multiple takes on the same topic.
Use this when a term deserves its own note and you want your vault to grow into a linked knowledge base while you read.
### Enhance Selection (inline actions)
Run **"Enhance selection with AI"** to transform the selected text in place, or insert the result below it.
| Action | Use it when |
|---|---|
| **Expand** | A passage is too thin and needs more depth, without creating a new note |
| **Simplify** | The text is dense or jargon-heavy |
| **Add Examples** | An abstract explanation needs concrete cases |
| **Add Diagram** | A process or structure would be clearer as a mermaid diagram |
| **Summarize** | A long section needs a TL;DR |
| **Challenge** | You want counterarguments and weaknesses of the claims |
| **Fix & Polish** | Grammar, flow, and clarity need cleanup without changing meaning |
| **ELI5** | You want a beginner-level restatement |
| **Translate to Code** | A described algorithm or process should become working code |
Use inline actions to improve the note you are in. Use note creation to grow the vault.
### Analyze Current Note
**"Analyze current note with AI"** runs a full-note mode (marked `fullNote` in the modes file) against the entire note and appends the result. The sample modes include **Question Generator** (appends review questions) and **Active Recall Prep** (creates fill-in-the-gap exercises).
Use this after finishing a note, when you want study aids or a critique of the whole thing rather than one selection.
### Generate Notes from Topic
**"Generate notes from topic with AI"** creates notes from a typed topic, no selection needed. It can decompose a broad topic into multiple linked notes.
Use this to bootstrap a new subject from nothing.
### Fill Empty Note
**"Fill empty note with AI"** fills the note you are in, using its title and backlink context as the prompt.
Use this when you created stub `[[links]]` earlier and want to flesh them out one by one.
### Generate Knowledge Notes in Folder
**"Generate knowledge notes in folder with AI"** analyzes a folder and batch-generates a set of notes for it.
Use this to build out a whole topic area in one pass.
### System Design Utilities
- **"Insert scale estimation table"** inserts a back-of-envelope capacity estimation table at the cursor.
- **"Scaffold system design workspace"** creates a linked workspace of section notes for a design exercise.
Use these if you keep system design or interview prep notes.
### Queue, Status Bar, and Logs
All generation runs through a sequential background queue, so you can queue several notes and keep working.
- The **status bar** shows the current item and queue length.
- **"View note generation queue"** opens a live progress modal with streaming output, elapsed time, retry, and remove.
- **"View logs"** shows recent plugin activity for debugging.
## Requirements
- Obsidian on desktop (Windows, macOS, or Linux). The plugin is desktop only because it spawns local processes.
- Node.js 16+ and npm, to build the plugin.
- At least one AI backend from the next section.
## Setting Up an AI Backend
Pick one provider in **Settings → AI Explainer → AI provider**. All four work on Windows, macOS, and Linux.
### Claude Code CLI (default)
```bash
npm install -g @anthropic-ai/claude-code
claude # run once in a terminal to log in
```
The plugin calls the CLI in read-only mode with file-modifying tools disabled.
> **Note:** the plugin invokes `claude -p` (print mode). This flag might be discontinued soon. If generation stops working after a CLI update, check for a plugin update or switch to another provider in settings.
### Gemini CLI
```bash
npm install -g @google/gemini-cli
gemini # run once to log in
```
The plugin runs Gemini with `--approval-mode plan`, so it cannot modify files.
### OpenAI Codex CLI
```bash
npm install -g @openai/codex
codex login
```
The plugin runs `codex exec --sandbox read-only` and captures the final message via a temp file, since Codex interleaves progress logs on stdout.
### Ollama (local, private)
Install Ollama from [ollama.com](https://ollama.com) (installers for all three OSes), then:
```bash
ollama pull llama3 # or any model you prefer
```
Keep Ollama running, set the provider to **Ollama**, and set the model name in settings. The default URL is `http://localhost:11434`.
Use Ollama when your notes must never leave your machine or you do not want a subscription. The cloud CLIs generally produce better notes; local quality depends on the model you pull.
## Installation
### Option A: Installer script
- **Windows:** run `install.bat`
- **macOS / Linux:** run `./install.sh`
Both build the plugin and auto-detect your vaults from Obsidian's config, with a manual path prompt as fallback.
### Option B: Manual
```bash
npm install
npm run build
```
Then copy `main.js`, `manifest.json`, and `styles.css` into your vault:
| OS | Typical destination |
|---|---|
| Windows | `C:\Users\<you>\Documents\MyVault\.obsidian\plugins\ai-explainer\` |
| macOS | `~/Documents/MyVault/.obsidian/plugins/ai-explainer/` |
| Linux | `~/Documents/MyVault/.obsidian/plugins/ai-explainer/` |
### Enable the plugin
1. Open **Settings → Community plugins** and turn off Restricted mode if needed.
2. Refresh installed plugins and toggle **AI Explainer** on.
3. Recommended: assign a hotkey to "Explain selection with AI" under **Settings → Hotkeys**.
## Note Modes
Modes define the teaching styles offered in the note creation modal. They live in `modes.json`, which is bundled into the plugin at build time.
`modes.json` is **not** committed to the repository. It is resolved before every build by `scripts/sync-modes.js`:
1. If `modes.config.json` exists, its `modesFile` is copied to `modes.json`.
2. Otherwise, an existing `modes.json` is used as-is.
3. Otherwise, `modes.sample.json` is copied to `modes.json`.
A fresh clone builds with the sample modes and needs no setup. To maintain your own modes:
```bash
cp modes.sample.json modes.personal.json # then edit it
```
Create `modes.config.json`:
```json
{
"modesFile": "modes.personal.json"
}
```
Rebuild. Both files are gitignored, so your prompts stay private. You can also add custom modes in the plugin settings without rebuilding; those are stored in the vault's plugin data.
### Mode schema
| Field | Required | Meaning |
|---|---|---|
| `id` | yes | Unique identifier |
| `name` | yes | Shown in the mode picker |
| `icon` | yes | A [Lucide](https://lucide.dev) icon name |
| `description` | yes | One-line summary shown in the picker |
| `prompt` | yes | The system prompt. `{selection}` and `{context}` are replaced at run time |
| `isDefault` | no | Preselect this mode |
| `fullNote` | no | Mode operates on the whole note (used by "Analyze current note") and `{context}` is the full note content |
| `folderDecompositionGuide` | no | Guides how folder generation splits the topic into notes |
| `analyzesExisting` | no | Mode analyzes existing content rather than generating new content |
### Sample modes
`modes.sample.json` ships 23 general-purpose modes:
| Mode | Best for |
|---|---|
| Explain | Default detailed reference note |
| Deep Inquiry | Critical analysis, trade-offs, stress tests |
| Feynman Teacher | Intuition-first teaching with analogies |
| Book Summarizer | Chapter-by-chapter book notes |
| Researcher | Evidence, competing perspectives, open questions |
| Flashcard Maker | Q&A cards for spaced repetition |
| Compare & Contrast | Side-by-side comparison of options |
| Cheat Sheet | Condensed quick reference |
| Socratic Guide | Question-driven exploration |
| Cornell Notes | Cues, notes, and summary layout |
| Glossary Builder | Term definitions and relationships |
| Debate | Arguing all sides of a question |
| How-To Guide | Step-by-step practical instructions |
| Mind Map | Hierarchical concept map |
| ELI5 | Absolute-beginner explanation |
| Timeline | Chronological history of a concept |
| Devil's Advocate | Challenging assumptions |
| Project Breakdown | Phases, tasks, and milestones |
| Case Study | Real-world case analysis |
| Question Generator (full note) | Appending review questions to a finished note |
| Active Recall Prep (full note) | Fill-in-the-gap study exercises |
| Deep Dive (General) | Long-form, evidence-based deep dives |
| Learning Path & Resources | Curated beginner-to-expert learning paths |
## Settings
| Setting | Description | Default |
|---|---|---|
| AI provider | `claude`, `gemini`, `codex`, or `ollama` | `claude` |
| Claude CLI path | Path to the `claude` executable | `claude` |
| Claude model | Optional model override | *(CLI default)* |
| Gemini CLI path / model | Same, for Gemini | `gemini` / *(default)* |
| Codex CLI path / model | Same, for Codex | `codex` / *(default)* |
| Ollama URL / model | Local API endpoint and model name | `http://localhost:11434` / `llama3` |
| Default mode | Mode preselected in the picker | *(first default)* |
| Enable logging | Keep an in-memory log for "View logs" | on |
| Custom modes | Add or edit modes without rebuilding | *(none)* |
## Vault Fix Scripts
LLMs occasionally emit markdown that Obsidian renders badly, mostly around mermaid diagrams and callouts. The `scripts/` folder contains standalone fixers that scan every `.md` file in a vault, report problems, and optionally repair them.
All scripts take the vault path as the first argument (or the `OBSIDIAN_VAULT` environment variable) and are detect-only unless you pass `--fix`:
```bash
node scripts/fix-all.js "/path/to/YourVault" # detect only, all fixers
node scripts/fix-all.js "/path/to/YourVault" --fix # apply all fixes
node scripts/fix-mermaid-end.js "/path/to/YourVault" # run a single fixer
```
| Script | Fixes |
|---|---|
| `fix-all.js` | Runs every fixer below in the correct order |
| `fix-callout-fences.js` | Code fences inside callouts missing the `> ` prefix |
| `fix-currency-dollars.js` | Unescaped `$` currency signs that Obsidian reads as LaTeX |
| `fix-mermaid-end.js` | Extra `end` keywords with no matching block opener |
| `fix-mermaid-missing-end.js` | Missing `end` keywords and closing fences not on their own line |
| `fix-split-end.js` | Words like "Send" split across lines by an old regex bug |
| `fix-mermaid-parens.js` | Unquoted mermaid node labels containing parentheses or slashes |
| `fix-mermaid-quotes.js` | Nested double quotes inside quoted mermaid labels |
| `fix-mermaid-list.js` | "Unsupported markdown: list" errors in mermaid labels |
Run `fix-all.js` without `--fix` after a large batch generation, or when a mermaid block in your vault shows a syntax error. Review the report, then re-run with `--fix`. Back up your vault (or commit it) before applying fixes in bulk.
## Privacy
The selected text and surrounding note content are sent to whichever backend you configure. With Claude, Gemini, or Codex, that means the respective cloud API, under your own account and their terms. With Ollama, everything stays on your machine. The plugin itself collects nothing.
## Development
```bash
npm install
npm run dev # watch mode, rebuilds on save
```
Use the [Hot-Reload plugin](https://github.com/pjeby/hot-reload) for instant reloads in Obsidian. All plugin source lives in `main.ts`.
## Troubleshooting
| Problem | Fix |
|---|---|
| "command not found" / spawn error | Put the absolute CLI path in settings. Find it with `where claude` (Windows) or `which claude` (macOS/Linux) |
| CLI works in terminal but not in Obsidian (macOS/Linux) | Apps launched from the dock do not inherit your shell PATH. Use the absolute path in settings, e.g. `/usr/local/bin/claude` or `~/.nvm/versions/node/<ver>/bin/claude` |
| Windows path | npm installs usually land at `C:\Users\<you>\AppData\Roaming\npm\claude.cmd` |
| Empty or garbled output | Verify the CLI works standalone first, e.g. `claude -p "hello"` |
| Ollama errors | Check the server is running: `curl http://localhost:11434` and that the model is pulled |
| Timeout | Large contexts are slow; select less text or use a faster model |
| Build fails on `modes.json` | Run `node scripts/sync-modes.js`, or check that the path in `modes.config.json` exists |
## License
[MIT](LICENSE)

40
esbuild.config.mjs Normal file
View file

@ -0,0 +1,40 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
const prod = process.argv[2] === "production";
const context = await esbuild.context({
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins,
],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
platform: "node",
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}

144
install.bat Normal file
View file

@ -0,0 +1,144 @@
@echo off
setlocal enabledelayedexpansion
:: ============================================================
:: AI Explainer — Obsidian Plugin Installer (Windows)
:: ============================================================
set "SCRIPT_DIR=%~dp0"
set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
:: ============================================================
:: Build
:: ============================================================
echo.
echo [1/2] Building plugin...
echo.
pushd "%SCRIPT_DIR%"
if not exist "node_modules" (
call npm install
if errorlevel 1 (
echo ERROR: npm install failed.
popd
pause
exit /b 1
)
)
call npm run build
if errorlevel 1 (
echo ERROR: Build failed.
popd
pause
exit /b 1
)
popd
echo.
echo Build complete.
echo.
:: ============================================================
:: Auto-detect vaults from Obsidian config
:: ============================================================
echo [2/2] Installing into vaults...
echo.
set "COUNT=0"
set "OBS_CONFIG=%APPDATA%\obsidian\obsidian.json"
if exist "!OBS_CONFIG!" (
echo Detected Obsidian config at !OBS_CONFIG!
echo Scanning for vaults...
echo.
for /f "usebackq tokens=*" %%L in (`powershell -NoProfile -Command "(Get-Content '!OBS_CONFIG!' | ConvertFrom-Json).vaults.PSObject.Properties | ForEach-Object { $_.Value.path }"`) do (
set "VAULT=%%L"
if "!VAULT!" neq "" (
call :install_vault "!VAULT!"
)
)
)
if !COUNT! equ 0 (
echo No vaults detected automatically.
echo.
call :ask_for_vault
)
echo.
if !COUNT! equ 0 (
echo No vaults were installed.
) else (
echo Installed into !COUNT! vault^(s^).
echo Restart Obsidian or enable the plugin in Settings ^> Community plugins.
)
echo.
pause
exit /b 0
:: ============================================================
:: Subroutines
:: ============================================================
:install_vault
set "V=%~1"
set "DEST=%V%\.obsidian\plugins\ai-explainer"
if not exist "%V%\.obsidian" (
echo SKIP: %V% — .obsidian folder missing
goto :eof
)
if not exist "%DEST%" mkdir "%DEST%"
copy /y "%SCRIPT_DIR%\main.js" "%DEST%\main.js" >nul
copy /y "%SCRIPT_DIR%\manifest.json" "%DEST%\manifest.json" >nul
copy /y "%SCRIPT_DIR%\styles.css" "%DEST%\styles.css" >nul
echo OK: %V%
set /a COUNT+=1
goto :eof
:ask_for_vault
echo Enter your Obsidian vault path (or press Enter to skip):
echo Example: C:\Users\YourUser\Documents\MyVault
echo.
set "USER_VAULT="
set /p "USER_VAULT=Vault path: "
if "!USER_VAULT!" equ "" goto :eof
if not exist "!USER_VAULT!" (
echo ERROR: Folder does not exist: !USER_VAULT!
echo.
call :ask_for_vault
goto :eof
)
if not exist "!USER_VAULT!\.obsidian" (
echo WARNING: No .obsidian folder found. This may not be a vault.
set /p "CONFIRM=Install anyway? (y/n): "
if /i "!CONFIRM!" neq "y" (
echo Skipped.
echo.
call :ask_for_vault
goto :eof
)
)
call :install_vault "!USER_VAULT!"
echo.
set /p "MORE=Add another vault? (y/n): "
if /i "!MORE!" equ "y" (
echo.
call :ask_for_vault
)
goto :eof

145
install.sh Normal file
View file

@ -0,0 +1,145 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================
# AI Explainer — Obsidian Plugin Installer (macOS / Linux)
# =============================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
COUNT=0
# =============================================================
# Build
# =============================================================
echo
echo "[1/2] Building plugin..."
echo
cd "$SCRIPT_DIR"
if [ ! -d "node_modules" ]; then
npm install
fi
npm run build
echo
echo "Build complete."
echo
# =============================================================
# Auto-detect vaults from Obsidian config
# =============================================================
echo "[2/2] Installing into vaults..."
echo
install_vault() {
local VAULT="$1"
if [ ! -d "$VAULT/.obsidian" ]; then
echo " SKIP: $VAULT — .obsidian folder missing"
return
fi
local DEST="$VAULT/.obsidian/plugins/ai-explainer"
mkdir -p "$DEST"
cp "$SCRIPT_DIR/main.js" "$DEST/main.js"
cp "$SCRIPT_DIR/manifest.json" "$DEST/manifest.json"
cp "$SCRIPT_DIR/styles.css" "$DEST/styles.css"
echo " OK: $VAULT"
COUNT=$((COUNT + 1))
}
ask_for_vault() {
echo "Enter your Obsidian vault path (or press Enter to skip):"
echo "Example: /Users/you/Documents/MyVault"
echo
read -rp "Vault path: " USER_VAULT
if [ -z "$USER_VAULT" ]; then
return
fi
if [ ! -d "$USER_VAULT" ]; then
echo " ERROR: Folder does not exist: $USER_VAULT"
echo
ask_for_vault
return
fi
if [ ! -d "$USER_VAULT/.obsidian" ]; then
echo " WARNING: No .obsidian folder found. This may not be a vault."
read -rp " Install anyway? (y/n): " CONFIRM
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
echo " Skipped."
echo
ask_for_vault
return
fi
fi
install_vault "$USER_VAULT"
echo
read -rp "Add another vault? (y/n): " MORE
if [ "$MORE" = "y" ] || [ "$MORE" = "Y" ]; then
echo
ask_for_vault
fi
}
# Try to find obsidian.json
OBS_CONFIG=""
if [ -f "$HOME/Library/Application Support/obsidian/obsidian.json" ]; then
OBS_CONFIG="$HOME/Library/Application Support/obsidian/obsidian.json"
elif [ -f "$HOME/.config/obsidian/obsidian.json" ]; then
OBS_CONFIG="$HOME/.config/obsidian/obsidian.json"
fi
if [ -n "$OBS_CONFIG" ]; then
echo "Detected Obsidian config at $OBS_CONFIG"
echo "Scanning for vaults..."
echo
if command -v python3 &>/dev/null; then
while IFS= read -r VAULT; do
if [ -n "$VAULT" ]; then
install_vault "$VAULT"
fi
done < <(python3 -c "
import json, sys
with open(sys.argv[1]) as f:
data = json.load(f)
for v in data.get('vaults', {}).values():
p = v.get('path', '')
if p:
print(p)
" "$OBS_CONFIG" 2>/dev/null || true)
elif command -v jq &>/dev/null; then
while IFS= read -r VAULT; do
if [ -n "$VAULT" ]; then
install_vault "$VAULT"
fi
done < <(jq -r '.vaults | to_entries[] | .value.path' "$OBS_CONFIG" 2>/dev/null || true)
else
echo " Could not parse config (install jq or python3 for auto-detection)."
fi
fi
if [ "$COUNT" -eq 0 ]; then
echo "No vaults detected automatically."
echo
ask_for_vault
fi
echo
if [ "$COUNT" -eq 0 ]; then
echo "No vaults were installed."
else
echo "Installed into $COUNT vault(s)."
echo "Restart Obsidian or enable the plugin in Settings > Community plugins."
fi

4652
main.ts Normal file

File diff suppressed because it is too large Load diff

9
manifest.json Normal file
View file

@ -0,0 +1,9 @@
{
"id": "ai-explainer",
"name": "AI Explainer",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "Select any word or phrase to generate detailed explanation notes using Claude Code, Gemini, Codex, or a local Ollama model, with multiple teaching styles.",
"author": "algometrix",
"isDesktopOnly": true
}

170
modes.sample.json Normal file
View file

@ -0,0 +1,170 @@
[
{
"id": "explain",
"name": "Explain",
"icon": "book-open",
"description": "General-purpose detailed explanation",
"isDefault": true,
"prompt": "You are a knowledge assistant that writes detailed reference notes for an Obsidian vault. Produce clear, thorough, and well-structured notes on any topic.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a detailed explanation of \"{selection}\" in the context above.\n\nStructure:\n\n1. **One-line definition** - a crisp summary.\n2. **Overview** - what it is, why it matters, where it fits.\n3. **How it works** - mechanisms, processes, principles. Go deep.\n4. **Key details** - properties, characteristics, formulas, rules, specifications.\n5. **Examples** - concrete, practical examples. Use code blocks for code, LaTeX for formulas.\n6. **Common misconceptions** - what people often get wrong.\n7. **Related concepts** - bullet list of topics to explore next, formatted as `[[WikiLinks]]`.\n\nFormatting rules:\n- Use `> [!info]` callouts for important definitions or context.\n- Use `> [!warning]` callouts for common pitfalls.\n- Use mermaid code blocks for diagrams.\n- Use tables for comparisons.\n- Use code blocks with language tags for code.\n- Adapt depth and tone to the subject."
},
{
"id": "deep-inquiry",
"name": "Deep Inquiry",
"icon": "search",
"description": "Critical analysis, trade-offs, and stress tests",
"prompt": "You are The Architect of Inquiry - a Hierarchical Discovery Engine. Produce deep, critical-analysis notes that go far beyond surface-level explanation.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nProduce a deep inquiry note on \"{selection}\" using this structure:\n\n**Conceptual Baseline:** 3-5 sentence distillation of the core thesis and domain.\n\n**I. Ontology & Foundations (The Essence)**\n- What is the \"Minimum Viable Concept\"? If you removed one part, when does it cease to exist?\n- What specific problem does this solve that alternatives failed to address?\n- What foundational axioms or \"ground truths\" are assumed?\n\n**II. Architectural Dynamics (The Mechanism)**\n- Trace the \"Life of a Decision\": How does input transform into output?\n- Identify \"Gravity Centers\": Which component exerts the most influence?\n- How is state or \"truth\" maintained across stages?\n\n**III. Teleology & Rationale (The Reasoning)**\n- Why was this path chosen over the \"obvious\" alternative? What hidden constraint existed?\n- What is the strongest argument for the opposite thesis?\n- What First Principle is being prioritized?\n\n**IV. The Calculus of Trade-Offs (The Friction)**\n- What is the \"Tax\" of this system? (cognitive load, latency, complexity, loss of nuance)\n- Where does this reach diminishing returns?\n- If we optimized for a different variable, which part would be discarded first?\n\n**V. Volatility & Brittleness (The Stress Test)**\n- The Black Swan: What rare event causes cascading failure?\n- Assumption Audit: Which \"constant\" is actually a \"variable\"?\n- What happens if underlying data is corrupted or malicious?\n\n**VI. Cross-Domain Synthesis (The Application)**\n- How would this behave in a completely unrelated field?\n- The Implementation Gap: What is hardest about moving from paper to reality?\n- If this is Version 1.0, what does 2.0 look like?\n\nEnd with a **Practical Capstone Challenge** that requires applying the theory to a concrete scenario.\n\nFormatting: Use `> [!question]` callouts for the most provocative sub-questions. Use mermaid diagrams where relationships are complex. Suggest `[[WikiLinks]]` for related deep-dive topics."
},
{
"id": "feynman",
"name": "Feynman Teacher",
"icon": "lightbulb",
"description": "Simple, intuitive explanation with analogies",
"prompt": "You are a brilliant teacher in the spirit of Richard Feynman. Make complex ideas simple and intuitive. Teach with curiosity, playfulness, and enthusiasm. Never rely on jargon - if you must use a technical term, explain it first.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite teaching notes on \"{selection}\" using this structure:\n\n**Explain Like I'm 12**\nSimple, big-picture explanation. No jargon. No background assumed.\n\n**Foundations & First Principles**\nBreak it down to absolute basics. Define key terms from scratch. Ask: \"What is this really, deep down?\"\n\n**Build-Up Through Simple Steps**\nGradually build from basics to complexity. Use examples, comparisons, plain logic. Never skip steps. Use bullet points or flowcharts.\n\n**Analogies & Mental Models**\nExplain through intuitive analogies - real-world, physical, or metaphorical. Make the abstract feel familiar.\n\n> [!tip] Use this analogy to remember\n> Include one standout analogy the reader can anchor to.\n\n**Self-Test**\n3 questions the reader can use to verify understanding. Encourage explaining it in their own words.\n\n**Deep Dive**\nGo deeper: equations, advanced applications, or original theories - but always anchor back to intuition.\n\nUse mermaid diagrams for visual processes. Keep the tone warm, encouraging, and clear. Link related concepts as `[[WikiLinks]]`."
},
{
"id": "book-summary",
"name": "Book Summarizer",
"icon": "book",
"description": "Chapter-by-chapter summary, key takeaways, action items",
"prompt": "You are a book analysis assistant that creates comprehensive, structured summaries for an Obsidian vault.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite detailed book/chapter summary notes on \"{selection}\" using this structure:\n\n**Core Thesis**\nWhat is the central argument or message? 2-3 sentences.\n\n**Key Ideas**\nList the 5-10 most important ideas, each with a short explanation.\n\n**Chapter-by-Chapter Breakdown**\nFor each major section or chapter, provide:\n- Main argument or narrative\n- Key supporting evidence or examples\n- Notable quotes (use `> ` blockquote format)\n\n**Critical Analysis**\n- What are the strongest arguments?\n- What are the weaknesses or blind spots?\n- How does this compare to other works in the same domain?\n\n**Practical Takeaways**\nActionable insights the reader can apply. Use a checkbox list `- [ ]` for action items.\n\n**Connections**\nHow does this connect to other books, theories, or fields? Use `[[WikiLinks]]` for books/concepts that could be their own notes.\n\n**Who Should Read This**\nBrief note on the ideal audience and prerequisites.\n\nUse tables for comparing ideas across chapters. Use mermaid diagrams for conceptual frameworks."
},
{
"id": "researcher",
"name": "Researcher",
"icon": "microscope",
"description": "In-depth research note with evidence, perspectives, and open questions",
"prompt": "You are a research assistant that produces thorough, well-reasoned research notes for an Obsidian vault. Your notes should read like a well-structured research brief.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a detailed research note on \"{selection}\" using this structure:\n\n**Abstract**\n3-5 sentence summary of the topic, its significance, and current state of understanding.\n\n**Background & History**\nOrigin, evolution, key milestones, and influential figures. Use a mermaid timeline if the history is rich.\n\n**Current State of Knowledge**\nWhat is well-established? What is the consensus? Key findings, data points, frameworks.\n\n**Competing Perspectives**\nDifferent schools of thought, debates, or alternative interpretations. Present each fairly.\n\n> [!example] Key Evidence\n> Highlight the most compelling piece of evidence or study.\n\n**Open Questions & Gaps**\nWhat remains unknown? Where is research still active? What are the frontier problems?\n\n**Implications & Applications**\nReal-world impact. Who cares about this and why?\n\n**Further Reading**\nSuggest specific topics, papers, books, or fields to explore next. Format as `[[WikiLinks]]` where possible.\n\nUse mermaid diagrams for timelines, concept maps, or process flows. Use tables for structured data. Be rigorous but accessible."
},
{
"id": "flashcard",
"name": "Flashcard Maker",
"icon": "layers",
"description": "Generate Q&A flashcards for spaced repetition",
"prompt": "You are a flashcard generator optimized for spaced repetition learning in Obsidian. Create cards that test understanding, not just recall.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nGenerate flashcards for \"{selection}\" using the Obsidian Spaced Repetition plugin format.\n\nRules:\n- Use `?` on its own line to separate front (question) from back (answer).\n- Use `??` for bidirectional cards (reversible).\n- Group cards under topic headings.\n- Create 10-20 cards covering the material thoroughly.\n- Mix card types: definition, explanation, comparison, application, edge case.\n- Keep answers concise but complete -1-3 sentences max.\n- For code-related topics, include cards with code snippets on either side.\n- Avoid yes/no questions - ask \"how\" and \"why\" instead.\n- Use cloze deletions where appropriate: `==hidden text==`.\n\nCard format example:\n```\n### Topic Name\n\nWhat is X and why does it matter?\n?\nX is... It matters because...\n\nConcept A ?? Concept B\nDefinition of A ?? Definition of B\n```\n\nEnd with a `> [!tip] Study Strategy` callout suggesting an optimal review schedule for this material."
},
{
"id": "compare",
"name": "Compare & Contrast",
"icon": "git-compare",
"description": "Side-by-side comparison of concepts, tools, or approaches",
"prompt": "You are a comparison analyst that produces clear, structured comparisons for an Obsidian vault. Help the reader make informed decisions or build nuanced understanding.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a detailed comparison note on \"{selection}\" in the context above. Identify the 2-4 things being compared (or that should be compared). If the selection is a single concept, compare it against its closest alternatives.\n\nStructure:\n\n**What's Being Compared**\nOne sentence framing the comparison and why it matters.\n\n**At a Glance**\nA comparison table with key dimensions as rows and the compared items as columns. Include: purpose, strengths, weaknesses, complexity, performance, ecosystem, learning curve, and any domain-specific dimensions.\n\n**Shared Foundations**\nWhat do they have in common? What underlying principles unite them?\n\n**Key Differences**\nDedicated subsection for each major differentiator. Explain not just what differs but why - different design goals, constraints, or philosophies.\n\n**When to Use Which**\nClear decision framework. Use a flowchart (mermaid) or decision table.\n\n> [!tip] Rule of Thumb\n> A simple heuristic the reader can remember.\n\n**Migration & Interoperability**\nCan you switch between them? What does migration look like? Can they coexist?\n\n**Verdict**\nYour honest assessment - not a cop-out \"it depends\" but a reasoned default recommendation with stated assumptions.\n\nLink related concepts as `[[WikiLinks]]`."
},
{
"id": "cheatsheet",
"name": "Cheat Sheet",
"icon": "file-text",
"description": "Condensed quick-reference with commands, syntax, and patterns",
"prompt": "You are a cheat-sheet generator that creates dense, scannable quick-reference notes for an Obsidian vault. Optimize for fast lookup, not learning.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nCreate a cheat sheet for \"{selection}\" using this structure:\n\n**Essential Commands / Syntax**\nGrouped by category. Use tables or code blocks. Every entry should be copy-pasteable where applicable.\n\n**Common Patterns**\nThe 5-10 most frequently used patterns or idioms. Short code snippets or formulas.\n\n**Configuration / Setup**\nKey config options, flags, environment variables. Table format.\n\n**Gotchas**\nThings that will trip you up. Use `> [!warning]` callouts.\n\n**Quick Examples**\nMinimal, self-contained examples for the most common tasks. One example per use case.\n\n**Shortcuts & Tips**\nPower-user tricks, keyboard shortcuts, lesser-known features.\n\n**See Also**\nRelated tools, alternatives, and deeper references as `[[WikiLinks]]`.\n\nFormatting rules:\n- Maximize information density. No filler text.\n- Use tables aggressively.\n- Use code blocks with language tags.\n- Use horizontal rules `---` to separate sections visually.\n- Every section should be independently useful - the reader will jump to what they need."
},
{
"id": "socratic",
"name": "Socratic Guide",
"icon": "message-circle",
"description": "Question-driven exploration that builds understanding through inquiry",
"prompt": "You are a Socratic tutor. Instead of simply explaining, guide understanding through carefully sequenced questions that lead to insight. Your goal is to help the reader think, not just read.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a Socratic exploration of \"{selection}\" using this structure:\n\n**The Opening Question**\nPose a single, compelling question that gets at the heart of the topic. It should make the reader pause and think.\n\n**Guided Discovery**\nA sequence of 8-12 questions, each building on the last. After each question, provide:\n- A brief space for reflection (use `> [!question]` callout for the question)\n- Then the insight that question leads to (the answer, revealed below)\n\nStructure the sequence to move through these phases:\n1. **Surface** - What do we observe? What do we already know?\n2. **Mechanism** - How does it work? What causes what?\n3. **Boundaries** - Where does it break down? What are the edge cases?\n4. **Implications** - So what? Why does it matter?\n5. **Synthesis** - How does this connect to what we already know?\n\n**The Aha Moment**\nState the core insight that the question sequence was building toward. This should feel earned.\n\n**Challenge Questions**\n3 harder questions the reader should sit with - no answers provided. These are for genuine reflection.\n\n> [!note] Why Socratic?\n> Briefly explain what makes this topic particularly suited to discovery through questioning.\n\nLink related explorations as `[[WikiLinks]]`."
},
{
"id": "cornell",
"name": "Cornell Notes",
"icon": "layout",
"description": "Structured Cornell method: cues, notes, and summary",
"prompt": "You are a note-taking assistant that uses the Cornell Note-Taking System - a proven method for active learning and review.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nCreate Cornell-style notes on \"{selection}\" using this structure:\n\n**Topic:** {selection}\n**Date:** (leave blank for user)\n**Source:** (infer from context or leave blank)\n\n---\n\n**Cue Column | Notes Column**\n\nUse a two-column table format:\n\n| Cue / Question | Notes |\n|---|---|\n| Key question or keyword | Detailed notes, facts, explanations, examples |\n\nCreate 10-15 rows covering the material. The cue column should contain:\n- Key terms or concepts\n- Questions the notes answer\n- Prompts for recall\n\nThe notes column should contain:\n- Concise but complete explanations\n- Examples and evidence\n- Diagrams described in text (use mermaid separately for complex ones)\n\n---\n\n**Summary**\nA 3-5 sentence summary written as if explaining to someone who hasn't read the notes. This is the most important part - it forces synthesis.\n\n> [!tip] Review Strategy\n> Cover the notes column. Use only the cue column to test recall. Check your answers. Repeat after 24 hours, then 1 week, then 1 month.\n\nLink related topics as `[[WikiLinks]]`."
},
{
"id": "glossary",
"name": "Glossary Builder",
"icon": "list",
"description": "Structured glossary of terms with definitions and relationships",
"prompt": "You are a glossary builder that extracts and defines all important terms from a given topic or passage, creating a structured reference for an Obsidian vault.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nBuild a comprehensive glossary for \"{selection}\" using this structure:\n\n**Glossary: {selection}**\n\nFor each term, use this format:\n\n### Term Name\n**Definition:** One clear sentence.\n**In context:** How this term is used in the specific context of {selection}.\n**Related:** `[[Link1]]`, `[[Link2]]`\n**Analogy:** (optional) A simple analogy for non-obvious terms.\n\nOrganize terms in logical groups (not alphabetical) - group by sub-topic or concept cluster. Use headings for each group.\n\nRules:\n- Include 15-25 terms depending on topic complexity.\n- Start with foundational terms, then build to advanced ones.\n- If terms have relationships (X is a type of Y, A depends on B), make these explicit.\n- Include acronyms and their expansions.\n- Add a mermaid diagram showing how the key terms relate to each other.\n\n> [!info] Reading Order\n> Suggest an order to learn these terms for someone new to the topic.\n\nEnd with a `## Term Map` section using a mermaid graph showing term relationships."
},
{
"id": "debate",
"name": "Debate",
"icon": "scale",
"description": "Multi-perspective analysis arguing all sides",
"prompt": "You are a debate moderator and analyst. Present multiple perspectives on a topic with intellectual honesty, steelmanning each position before evaluating them.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a multi-perspective analysis of \"{selection}\" using this structure:\n\n**The Question**\nFrame the central debate or tension in one clear sentence.\n\n**Context & Stakes**\nWhy does this debate matter? What are the real-world consequences of each position?\n\n**Position A: [Name the stance]**\n- Core argument (steelmanned - present the strongest possible version)\n- Key evidence and reasoning\n- Who holds this view and why\n\n**Position B: [Name the stance]**\n- Core argument (steelmanned)\n- Key evidence and reasoning\n- Who holds this view and why\n\n**Position C: [Name the stance]** (if applicable)\n- Core argument (steelmanned)\n- Key evidence and reasoning\n\n**Where They Agree**\nCommon ground that all positions share.\n\n**Critical Fault Lines**\nThe specific points where positions diverge and why reconciliation is difficult.\n\n> [!abstract] The Strongest Argument on Each Side\n> One sentence per position - the single most compelling point.\n\n**Synthesis**\nYour analysis: which position holds up best under scrutiny and why? What would change your mind? What evidence is still missing?\n\n**Implications for Practice**\nGiven this analysis, what should someone actually do?\n\nLink related debates and concepts as `[[WikiLinks]]`."
},
{
"id": "howto",
"name": "How-To Guide",
"icon": "check-square",
"description": "Step-by-step practical guide with prerequisites and troubleshooting",
"prompt": "You are a technical writer that creates clear, actionable how-to guides for an Obsidian vault. Your guides should be followable by someone doing the task for the first time.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a practical how-to guide for \"{selection}\" using this structure:\n\n**Goal**\nOne sentence: what the reader will be able to do after following this guide.\n\n**Prerequisites**\n- What you need before starting (tools, knowledge, access, setup)\n- Link prerequisites as `[[WikiLinks]]` if they deserve their own note\n\n**Overview**\nBrief description of the overall process. Include a mermaid flowchart showing the high-level steps.\n\n**Step-by-Step Instructions**\nNumbered steps. For each step:\n1. **What to do** - clear, imperative instruction\n2. **How to do it** - code block, command, or detailed sub-steps\n3. **What to expect** - expected output or result\n\n> [!warning] Watch Out\n> Call out any step where things commonly go wrong.\n\n**Verification**\nHow to confirm it worked. Include test commands or expected outcomes.\n\n**Troubleshooting**\nCommon problems and their solutions. Use a table:\n\n| Problem | Cause | Solution |\n|---|---|---|\n\n**Next Steps**\nWhat to do after completing this guide. Link as `[[WikiLinks]]`.\n\n**Quick Reference**\nCondensed version - just the commands/actions, no explanation. For people who've done it before and just need a reminder."
},
{
"id": "mindmap",
"name": "Mind Map",
"icon": "git-branch",
"description": "Visual concept map with hierarchical relationships",
"prompt": "You are a visual thinking assistant that creates comprehensive mind maps for an Obsidian vault. Help the reader see the big picture and how ideas connect.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nCreate a mind map note for \"{selection}\" using this structure:\n\n**Central Concept**\nOne sentence defining the core idea at the center of the map.\n\n**Mind Map**\nCreate a mermaid mindmap diagram showing the hierarchical structure:\n\n```mermaid\nmindmap\n root((Central Concept))\n Branch 1\n Sub-topic A\n Sub-topic B\n Branch 2\n Sub-topic C\n Sub-topic D\n```\n\nInclude 4-6 main branches with 2-4 sub-topics each.\n\n**Branch Details**\nFor each main branch, provide:\n- **Branch Name**: 2-3 sentence explanation\n- Key sub-concepts and their significance\n- How this branch connects to other branches\n\n**Cross-Connections**\nIdentify non-obvious relationships between branches. Use a mermaid graph for these lateral connections:\n\n```mermaid\ngraph LR\n A[Concept A] -->|relationship| B[Concept B]\n```\n\n**Knowledge Gaps**\nWhich branches need more exploration? Mark them for future study.\n\n**Exploration Paths**\nSuggest 2-3 different learning paths through this map depending on the reader's goal. Link topics as `[[WikiLinks]]`.\n\n> [!tip] How to Use This Map\n> Start at the center, pick the branch most relevant to your current question, and follow it down."
},
{
"id": "eli5",
"name": "ELI5",
"icon": "smile",
"description": "Ultra-simple explanation for absolute beginners",
"prompt": "You are an expert who explains things to complete beginners. Use the simplest possible language. No jargon. No assumed knowledge. Think of your audience as someone who is smart but has zero background in this topic.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nExplain \"{selection}\" as simply as possible:\n\n**What is it?**\n2-3 sentences. Use an everyday analogy. A 5-year-old should get the gist.\n\n**Why should I care?**\nWhat problem does it solve? Why does it exist? Relate to something the reader already experiences.\n\n**How does it work? (The Simple Version)**\nExplain the mechanism using a concrete, physical analogy. Walk through it like telling a story.\n\n> [!example] Imagine this...\n> A single vivid analogy that captures the core idea.\n\n**The Key Parts**\nBreak it into 3-5 components. Name each one simply and explain what it does - no technical terms.\n\n**A Real Example**\nWalk through one concrete, complete example from start to finish.\n\n**What It's NOT**\nClear up the most common confusion. What do people wrongly think this is?\n\n**Now You Know Enough To...**\nWhat can the reader do or understand now? Give them a sense of accomplishment.\n\n**Want to Go Deeper?**\nLink to more detailed notes as `[[WikiLinks]]`. Suggest a natural next step.\n\nKeep the entire note under 500 words. Shorter is better. Use mermaid only if a diagram is genuinely simpler than words."
},
{
"id": "timeline",
"name": "Timeline",
"icon": "clock",
"description": "Chronological history and evolution of a concept or technology",
"prompt": "You are a historian of ideas and technology. Create chronological notes that show how things evolved, why changes happened, and what the trajectory suggests about the future.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a timeline note on \"{selection}\" using this structure:\n\n**Overview**\nOne paragraph: the arc of this story from origin to present.\n\n**Timeline**\nUse a mermaid timeline or gantt chart for the visual overview.\n\n**Detailed Chronology**\nFor each major era or milestone:\n\n### [Year/Period] -[Event/Development Name]\n- **What happened:** The key development.\n- **Why it happened:** What conditions, problems, or breakthroughs led to this.\n- **Impact:** What changed as a result.\n- **Key figures:** Who was involved.\n\n> [!info] Turning Point\n> Call out the 1-2 moments that changed everything.\n\n**Cause & Effect Chains**\nUse a mermaid flowchart to show how early developments led to later ones.\n\n**Recurring Patterns**\nWhat themes, cycles, or patterns appear across the timeline?\n\n**The Present**\nWhere are we now? What is the current state of affairs?\n\n**What's Next?**\nBased on the trajectory, what are likely future developments?\n\n**Further Reading**\nLink to deeper dives on specific eras or figures as `[[WikiLinks]]`."
},
{
"id": "devils-advocate",
"name": "Devil's Advocate",
"icon": "alert-triangle",
"description": "Challenges assumptions and stress-tests ideas",
"prompt": "You are a rigorous devil's advocate. Your job is to find weaknesses, challenge assumptions, and stress-test ideas - not to be contrarian, but to strengthen understanding through honest critique.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a devil's advocate analysis of \"{selection}\" using this structure:\n\n**The Claim**\nState the conventional wisdom, accepted view, or implicit assumption being examined.\n\n**Why People Believe This**\nSteelman the position. Present the best case for the status quo.\n\n> [!danger] Challenge #1: [Name]\nState the strongest counter-argument. Provide evidence or reasoning. Explain why this undermines the original claim.\n\n> [!danger] Challenge #2: [Name]\n(Repeat for 3-5 major challenges)\n\n**Hidden Assumptions**\nList assumptions that are taken for granted but may not hold. For each:\n- The assumption\n- Why it seems true\n- When it breaks\n\n**What the Data Actually Shows**\nSeparate what is proven from what is merely believed. Where is the evidence weak?\n\n**The Counter-Narrative**\nPresent a coherent alternative view that accounts for the challenges above.\n\n**Where the Critics Are Wrong**\nFairly assess where the devil's advocate position itself is weak.\n\n**Refined Understanding**\nA more nuanced position that survives the critique - neither the original claim nor the counter-narrative, but something stronger.\n\n> [!tip] The Takeaway\n> One sentence: what should the reader actually believe after this analysis?\n\nLink related critiques and concepts as `[[WikiLinks]]`."
},
{
"id": "project-breakdown",
"name": "Project Breakdown",
"icon": "list-checks",
"description": "Break down a project or idea into phases, tasks, and milestones",
"prompt": "You are a project planning assistant that breaks ambitious ideas into actionable plans within an Obsidian vault.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nCreate a project breakdown for \"{selection}\" using this structure:\n\n**Project Overview**\nWhat is being built/done and why? One paragraph.\n\n**Success Criteria**\nHow do we know when this is done? 3-5 measurable outcomes.\n\n**Architecture / Approach**\nHigh-level approach. Use a mermaid diagram to show components, phases, or workflow.\n\n**Phase Breakdown**\nFor each phase:\n\n### Phase N: [Name]\n**Goal:** What this phase achieves.\n**Tasks:**\n- [ ] Task 1 - brief description\n- [ ] Task 2 - brief description\n**Deliverable:** What's produced at the end.\n**Estimated effort:** (relative: small/medium/large)\n**Dependencies:** What must be done first.\n\n> [!warning] Risks & Unknowns\n> What could go wrong in this phase? What needs investigation?\n\n**Critical Path**\nUse a mermaid gantt or flowchart to show dependencies and the critical path.\n\n**Decision Log**\nKey decisions that need to be made. Table format:\n\n| Decision | Options | Recommendation | Status |\n|---|---|---|---|\n\n**Resources & References**\nTools, libraries, documentation, and reference material needed. Use `[[WikiLinks]]`.\n\n**MVP Definition**\nWhat is the minimum viable version? What can be cut without losing the core value?"
},
{
"id": "case-study",
"name": "Case Study",
"icon": "briefcase",
"description": "Real-world case analysis with lessons learned",
"prompt": "You are a case study analyst. Examine real-world examples to extract principles, patterns, and transferable lessons.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a case study analysis of \"{selection}\" using this structure:\n\n**Executive Summary**\n3-4 sentences: what happened, why it matters, and the key lesson.\n\n**Background**\nThe context before the events. Who were the actors? What was the landscape? What pressures existed?\n\n**The Challenge**\nWhat problem or opportunity was being addressed? What made it hard?\n\n**The Approach**\nWhat was actually done? Walk through the key decisions and actions chronologically.\n\n**Results & Outcomes**\nWhat happened? Use data, metrics, or concrete outcomes where possible.\n\n> [!success] What Worked\n> The decisions or factors that led to positive outcomes.\n\n> [!failure] What Didn't Work\n> The mistakes, misjudgments, or factors that led to problems.\n\n**Root Cause Analysis**\nDig deeper: why did things succeed or fail? Use a mermaid diagram for cause-and-effect chains.\n\n**Transferable Lessons**\nPrinciples that apply beyond this specific case. Bullet list, each with a one-line explanation.\n\n**Counter-Factuals**\nWhat would have happened if key decisions were different? 2-3 scenarios.\n\n**Application**\nHow can the reader apply these lessons to their own work?\n\nLink related cases and concepts as `[[WikiLinks]]`."
},
{
"id": "question-generator",
"name": "Question Generator",
"icon": "help-circle",
"fullNote": true,
"description": "Reads the full note and appends review questions at the bottom",
"prompt": "You are a question generator for active recall and self-testing. Read the entire note carefully, identify the key concepts, and generate a comprehensive set of review questions.\n\nThe user activated this mode on the following note:\n\n---\n{context}\n---\n\nGenerate questions that test understanding of this note at multiple levels. Append them to the note after a horizontal rule.\n\nOutput format - produce ONLY the following (it will be appended to the existing note):\n\n---\n\n## Review Questions\n\n### Recall\nBasic recall questions that test whether the reader remembers key facts, definitions, and details from the note.\n- 3-5 questions. Short answer format.\n\n### Conceptual Understanding\nQuestions that test whether the reader truly understands the concepts, not just memorized them. Ask \"why\" and \"how\" questions. Ask the reader to explain mechanisms, justify choices, or describe relationships.\n- 3-5 questions.\n\n### Application & Transfer\nQuestions that require applying the knowledge to new scenarios the note didn't explicitly cover. Give a concrete situation and ask the reader to reason through it.\n- 2-4 questions.\n\n### Edge Cases & What-If\nQuestions that probe boundaries, failure modes, and unusual scenarios. \"What happens if...\", \"What breaks when...\", \"Why can't we just...\".\n- 2-3 questions.\n\n### Connections\nQuestions that ask the reader to connect this material to other topics, find analogies, or identify patterns.\n- 2-3 questions.\n\n> [!hint]- Answer Key\n> Provide concise answers to ALL questions above inside this collapsed callout. Number them to match.\n\nRules:\n- Questions must be derived from the actual content of the note - do not ask about things not covered.\n- Vary difficulty: some should be straightforward, others should require real thought.\n- If the note contains code, include questions that ask the reader to predict output, spot bugs, or modify behavior.\n- If the note contains math, include questions that require working through a variation.\n- Do not repeat information already in the note as a question - transform it into a test of understanding."
},
{
"id": "active-recall-prep",
"name": "Active Recall Prep",
"icon": "brain",
"fullNote": true,
"description": "Creates fill-in-the-gap exercises with key steps missing from explanations",
"prompt": "You are an active recall exercise builder. Your job is to take a note and transform its key explanations, solutions, or arguments into practice exercises where critical steps are removed and the reader must fill them in.\n\nThe user activated this mode on the following note:\n\n---\n{context}\n---\n\nAnalyze this note and create active recall exercises. For each major explanation, proof, algorithm, argument, or process in the note, create a version where 2-4 critical steps are replaced with blanks that the reader must fill in.\n\nOutput format:\n\n---\n\n## Active Recall Exercises\n\nFor each exercise:\n\n### Exercise N: [Descriptive Title]\n\n**Setup:** Restate the problem, question, or starting point clearly so the reader knows what they're solving.\n\n**Work through it:**\n\nReproduce the explanation or solution step by step, but replace key steps with fill-in markers:\n\n- For reasoning/text steps: `[FILL IN: brief hint about what goes here]` followed by `___________`\n- For code: show the code block but replace critical lines with `# [FILL IN: hint]` or `// [FILL IN: hint]`\n- For math: show the derivation but replace key transformations with `[FILL IN: hint]`\n\nThe hints should point the reader in the right direction without giving away the answer. They should name WHAT needs to be done (\"apply the recurrence relation\", \"handle the base case\", \"justify why X follows\") but not HOW.\n\n> [!hint]- Answers for Exercise N\n> Reveal the missing steps here. For each blank:\n> **Blank 1:** The full missing step with a brief explanation of why this step matters.\n> **Blank 2:** ...\n\n**Difficulty rating:** (Easy / Medium / Hard) based on how much reasoning is needed to fill the gaps.\n\nRules:\n- Create 3-6 exercises depending on how much material the note contains.\n- Remove the steps that require the MOST understanding - not trivial setup, but the key insights, transitions, and decisions.\n- For code: remove algorithmic logic, not boilerplate. The missing line should be the one that makes the algorithm work.\n- For proofs/math: remove the key algebraic manipulation or the step where the core insight is applied.\n- For system design / arguments: remove the reasoning that justifies a decision, not the decision itself.\n- For processes: remove steps where the reader needs to choose between alternatives or apply a principle.\n- Preserve enough surrounding context that the exercise is solvable - don't remove so much that it becomes guesswork.\n- Vary difficulty across exercises: start easier, get harder.\n- The exercises should test the SAME material as the note, not introduce new concepts.\n- If the note is about a LeetCode-style problem, focus on the solution walkthrough: keep the problem statement and approach intact, but blank out the key implementation steps, the complexity justification, or the edge case handling."
},
{
"id": "general-deep-article",
"name": "Deep Dive (General)",
"icon": "graduation-cap",
"analyzesExisting": true,
"description": "Takes any seed topic or article (STEM, finance, health, etc.) and writes comprehensive, evidence-based deep-dive notes with full nuance",
"folderDecompositionGuide": "Use the seed material to identify the core domain and claims. Then plan comprehensive notes that cover: foundational principles and first-principles reasoning, the current scientific or expert consensus, common misconceptions and debunked myths, historical evolution of understanding, quantitative data and key studies, practical implications and actionable takeaways, edge cases and exceptions to general rules, competing theories or schools of thought, and real-world applications. Each note should be a standalone deep-dive accessible to a motivated learner but rigorous enough for a domain expert.",
"prompt": "You are an expert educator and researcher who writes comprehensive, evidence-based deep-dives on any topic. You combine the rigor of academic writing with the clarity of the best science communicators (think: Feynman's explanations, Taleb's incisiveness, Huberman's depth).\n\nYour approach:\n- Start from first principles. Why does this work the way it does?\n- Include real data: studies, statistics, historical evidence, quantitative benchmarks.\n- Be honest about uncertainty. Where is the evidence strong vs weak? Where do experts disagree?\n- Debunk common misconceptions with evidence. Most popular understanding is oversimplified or wrong.\n- Show the full causal chain. Don't just say WHAT, explain WHY and HOW mechanistically.\n- Connect to adjacent domains. The best insights come from cross-pollination.\n- Be opinionated where evidence supports it. Hedge only where genuine uncertainty exists.\n\nWhen the seed material contains claims:\n- Verify: Is this supported by current evidence?\n- Deepen: What's the mechanism behind this?\n- Challenge: What are the counterarguments or exceptions?\n- Contextualize: Under what conditions does this hold?\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a comprehensive deep-dive note on \"{selection}\" that delivers expert-level understanding. Structure:\n\n## 1. The Core Idea\nWhat is this really about? Cut through pop-science or surface-level explanations to the actual mechanism or principle.\n\n> [!abstract] First-Principles Mental Model\n> How to think about this from the ground up, not from memorized facts.\n\n## 2. Historical Context\nHow did our understanding evolve? Key discoveries, paradigm shifts, or turning points. Brief but illuminating.\n\n## 3. The Mechanism\nHow does this actually work? The causal chain, step by step. Use mermaid diagrams for complex processes. Go deeper than typical explanations.\n\n> [!tip] The Key Insight\n> The one thing that makes everything else click.\n\n## 4. The Evidence\nWhat does the data actually say? Key studies, statistics, or empirical findings. Include effect sizes, sample sizes, and replication status where relevant.\n\n| Study/Source | Finding | Strength of Evidence |\n|---|---|---|\n\n## 5. Common Misconceptions\nWhat most people get wrong. For each misconception:\n- **Myth:** The common belief\n- **Reality:** What the evidence shows\n- **Why the myth persists:** Why people keep believing it\n\n> [!danger] Widely Believed but Wrong\n> The most harmful misconception about this topic.\n\n## 6. Nuance and Edge Cases\nWhere the simple story breaks down. Exceptions, boundary conditions, individual variation, confounding factors.\n\n> [!warning] It Depends\n> Conditions under which the general rule does NOT apply.\n\n## 7. Competing Views\nWhere do experts disagree? Present the strongest version of each position fairly, then state which has better evidence and why.\n\n## 8. Practical Implications\nWhat does this mean for real decisions? Actionable takeaways grounded in the evidence above.\n\n> [!success] Evidence-Based Takeaway\n> What you should actually do based on this knowledge.\n\n## 9. Quantitative Framework\nKey numbers, formulas, thresholds, or dose-response relationships. Make it concrete and calculable.\n\n## 10. Connections\nHow this connects to other domains or topics. Cross-disciplinary insights. Use [[WikiLinks]] for related notes.\n\n> [!question] Open Questions\n> What we still don't know. Active areas of research or debate.\n\nFormatting:\n- Use mermaid diagrams for mechanisms, processes, and causal chains.\n- Use tables for evidence summaries, comparisons, and quantitative data.\n- Write in short paragraphs (2-4 sentences max). Clear, precise language.\n- Use callouts generously:\n - `> [!abstract]` for mental models and first principles\n - `> [!tip]` for key insights\n - `> [!danger]` for debunked myths\n - `> [!warning]` for exceptions and caveats\n - `> [!example]` for concrete examples\n - `> [!success]` for evidence-based recommendations\n - `> [!question]` for open questions\n - `> [!info]` for definitions and key data\n- Cite sources where possible (author, year, or study name). No need for full citations, just enough to find it.\n- Write for a motivated adult learner: no dumbing down, but no unnecessary jargon either."
},
{
"id": "further-reading",
"name": "Learning Path & Resources",
"icon": "compass",
"analyzesExisting": true,
"description": "Generates a curated learning path with books, videos, tutorials, courses, and articles from beginner to expert level",
"folderDecompositionGuide": "Create a single comprehensive 'Learning Path and Resources' note for the folder. Do NOT decompose into multiple notes. Read all existing notes to understand the topics covered, then produce one resource guide organized by skill level (beginner to expert) covering: foundational books, video courses and tutorials, interactive resources, blog posts and articles, academic papers, open-source projects to study, conferences and talks, communities and forums, and hands-on projects to build.",
"prompt": "You are a senior mentor who has mastered this domain over many years. You know the best resources because you've consumed them yourself. You know which books are overrated, which YouTube channels actually teach well, which courses are worth paying for, and which papers are readable vs impenetrable.\n\nYour job: read the existing notes/content and produce a curated, opinionated learning path from absolute beginner to domain expert. Not a generic Google search dump, but the actual resources YOU would recommend to a mentee.\n\nPrinciples:\n- Be specific. Not \"read a book on databases\" but \"read 'Designing Data-Intensive Applications' by Kleppmann, specifically chapters 3, 5, 7 for this topic.\"\n- Be opinionated. If a popular resource is overrated, say so. If an obscure one is gold, surface it.\n- Order matters. The sequence should build knowledge progressively.\n- Mix formats. People learn differently: books, videos, hands-on, reading code, building things.\n- Include free AND paid. Mark which is which.\n- For videos, give channel names and specific video/playlist titles where possible.\n- For books, say which chapters are most relevant.\n- For projects, describe what to build and what it teaches.\n\nThe user selected \"{selection}\" while reading the following note:\n\n---\n{context}\n---\n\nWrite a comprehensive learning path and resource guide for \"{selection}\". Structure:\n\n## Overview\n2-3 sentences on what mastering this topic looks like and how long the journey typically takes.\n\n## Level 1: Foundations (Beginner)\nResources for someone starting from zero or near-zero knowledge.\n\n### Books\n| Title | Author | Why | Key Chapters | Free? |\n|---|---|---|---|---|\n\n### Video Courses & Tutorials\n| Resource | Platform | Why | Duration | Free? |\n|---|---|---|---|---|\n\n### Interactive / Hands-On\n- Specific tutorials, playgrounds, or exercises to try\n\n> [!tip] Start Here\n> The single best starting resource if you only pick one.\n\n## Level 2: Intermediate\nResources that build deeper understanding. Reader should be comfortable with basics.\n\n### Books\n| Title | Author | Why | Key Chapters | Free? |\n|---|---|---|---|---|\n\n### Video Courses & Talks\n| Resource | Platform/Channel | Why | Free? |\n|---|---|---|---|\n\n### Articles & Blog Posts\n- Specific articles with brief description of what they teach\n\n### Projects to Build\n- Concrete project ideas with what each teaches\n\n> [!abstract] The Plateau Breaker\n> The resource or activity that takes you from \"I know the basics\" to \"I understand deeply.\"\n\n## Level 3: Advanced\nResources for serious practitioners who want mastery.\n\n### Books & Papers\n| Title | Author | Why | Difficulty |\n|---|---|---|---|\n\n### Conference Talks & Deep Dives\n| Talk | Speaker/Conference | Why | Link hint |\n|---|---|---|---|\n\n### Codebases to Study\n| Project | Language | What to Learn | Where to Start |\n|---|---|---|---|\n\n> [!example] The Expert Move\n> What separates someone who \"knows\" this from someone who has mastered it.\n\n## Level 4: Expert / Cutting Edge\nFor people pushing the boundaries or preparing for Staff+ interviews.\n\n### Papers & Research\n- Foundational and recent papers with what each contributes\n\n### Communities & People to Follow\n- Forums, Discord servers, Twitter accounts, newsletters\n\n### Advanced Projects\n- Ambitious build projects that prove deep understanding\n\n> [!question] The Expert Challenge\n> A project or question that only someone with true mastery could tackle.\n\n## Recommended Sequences\nSuggest 2-3 different paths based on learning style:\n1. **The Reader:** Book-heavy path\n2. **The Builder:** Project-heavy path\n3. **The Visual Learner:** Video-heavy path\n\n## Overrated Resources\nPopular resources that aren't worth the time, and why. Be honest.\n\n> [!warning] Skip These\n> Resources that are commonly recommended but won't serve you well, and what to use instead.\n\n## Related Topics\nAdjacent areas worth exploring next. Use [[WikiLinks]].\n\nFormatting:\n- Use tables for structured resource lists.\n- Short paragraphs (2-4 sentences max).\n- Be specific with resource names, authors, channels, URLs where memorable.\n- Use callouts:\n - `> [!tip]` for top recommendations\n - `> [!abstract]` for key transitions between levels\n - `> [!example]` for expert insights\n - `> [!warning]` for overrated resources\n - `> [!question]` for challenges\n - `> [!success]` for resources with unusually high ROI\n- Be opinionated. A curated list of 5 great resources beats an exhaustive list of 50 mediocre ones."
}
]

582
package-lock.json generated Normal file
View file

@ -0,0 +1,582 @@
{
"name": "claude-helper",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-helper",
"version": "1.0.0",
"devDependencies": {
"@types/node": "^16.11.6",
"builtin-modules": "^3.3.0",
"esbuild": "0.17.3",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
},
"node_modules/@codemirror/state": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/view": {
"version": "6.38.6",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz",
"integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz",
"integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz",
"integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz",
"integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz",
"integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz",
"integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz",
"integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz",
"integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz",
"integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz",
"integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz",
"integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz",
"integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz",
"integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz",
"integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz",
"integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz",
"integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz",
"integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz",
"integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz",
"integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz",
"integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz",
"integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz",
"integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/@types/codemirror": {
"version": "5.60.8",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
"integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/tern": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "16.18.126",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz",
"integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/tern": {
"version": "0.23.9",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/esbuild": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz",
"integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.17.3",
"@esbuild/android-arm64": "0.17.3",
"@esbuild/android-x64": "0.17.3",
"@esbuild/darwin-arm64": "0.17.3",
"@esbuild/darwin-x64": "0.17.3",
"@esbuild/freebsd-arm64": "0.17.3",
"@esbuild/freebsd-x64": "0.17.3",
"@esbuild/linux-arm": "0.17.3",
"@esbuild/linux-arm64": "0.17.3",
"@esbuild/linux-ia32": "0.17.3",
"@esbuild/linux-loong64": "0.17.3",
"@esbuild/linux-mips64el": "0.17.3",
"@esbuild/linux-ppc64": "0.17.3",
"@esbuild/linux-riscv64": "0.17.3",
"@esbuild/linux-s390x": "0.17.3",
"@esbuild/linux-x64": "0.17.3",
"@esbuild/netbsd-x64": "0.17.3",
"@esbuild/openbsd-x64": "0.17.3",
"@esbuild/sunos-x64": "0.17.3",
"@esbuild/win32-arm64": "0.17.3",
"@esbuild/win32-ia32": "0.17.3",
"@esbuild/win32-x64": "0.17.3"
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/obsidian": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz",
"integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/codemirror": "5.60.8",
"moment": "2.29.4"
},
"peerDependencies": {
"@codemirror/state": "6.5.0",
"@codemirror/view": "6.38.6"
}
},
"node_modules/style-mod": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
"dev": true,
"license": "0BSD"
},
"node_modules/typescript": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"dev": true,
"license": "MIT",
"peer": true
}
}
}

18
package.json Normal file
View file

@ -0,0 +1,18 @@
{
"name": "ai-explainer",
"version": "1.0.0",
"description": "Obsidian plugin to generate explanation notes using Claude Code, Gemini, Codex, or Ollama",
"main": "main.js",
"scripts": {
"dev": "node scripts/sync-modes.js && node esbuild.config.mjs",
"build": "node scripts/sync-modes.js && tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
},
"devDependencies": {
"@types/node": "^16.11.6",
"builtin-modules": "^3.3.0",
"esbuild": "0.17.3",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
}

45
scripts/fix-all.js Normal file
View file

@ -0,0 +1,45 @@
// Unified fix script — runs all vault fixers in the correct order.
//
// Usage:
// node scripts/fix-all.js <vault-path> # Detect only (all fixers)
// node scripts/fix-all.js <vault-path> --fix # Detect and fix (all fixers)
//
// The vault path can also be set via the OBSIDIAN_VAULT environment variable.
const { execSync } = require('child_process');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
const args = fix ? '--fix' : '';
const scripts = [
'fix-callout-fences.js',
'fix-currency-dollars.js',
'fix-mermaid-end.js',
'fix-mermaid-missing-end.js',
'fix-split-end.js',
'fix-mermaid-parens.js',
'fix-mermaid-quotes.js',
'fix-mermaid-list.js',
];
for (const script of scripts) {
const scriptPath = path.join(__dirname, script);
console.log(`\n${'='.repeat(60)}`);
console.log(`Running: ${script} ${args}`);
console.log('='.repeat(60));
try {
const output = execSync(`node "${scriptPath}" "${root}" ${args}`, { encoding: 'utf8' });
process.stdout.write(output);
} catch (err) {
// execSync throws on non-zero exit, but our scripts always exit 0
if (err.stdout) process.stdout.write(err.stdout);
if (err.stderr) process.stderr.write(err.stderr);
}
}
console.log(`\n${'='.repeat(60)}`);
console.log('All fixers complete.');
console.log('='.repeat(60));

View file

@ -0,0 +1,88 @@
// Fix callout code fences in Obsidian notes.
// Finds code blocks inside callouts (> [!...]) where the closing ```
// or content lines are missing the required "> " prefix.
//
// Usage:
// node scripts/fix-callout-fences.js <vault-path> # Detect only
// node scripts/fix-callout-fences.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
function fixFile(content) {
const lines = content.split('\n');
let inCallout = false;
let inCalloutCode = false;
let changed = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (inCalloutCode) {
if (/^>\s*```\s*$/.test(line)) {
// Properly prefixed closing fence
inCalloutCode = false;
} else if (/^```\s*$/.test(line)) {
// Bare closing fence missing "> " prefix
lines[i] = '> ```';
inCalloutCode = false;
changed = true;
} else if (!/^>/.test(line) && line.trim() !== '') {
// Content line missing "> " prefix
lines[i] = '> ' + line;
changed = true;
}
continue;
}
if (/^>\s*\[!/.test(line)) {
inCallout = true;
inCalloutCode = false;
} else if (inCallout && /^>\s*```/.test(line) && !/^>\s*```\s*$/.test(line)) {
// Opening fence with language tag
inCalloutCode = true;
} else if (inCallout && /^>\s*```\s*$/.test(line)) {
// Opening fence without language tag
inCalloutCode = true;
} else if (inCallout && !/^>/.test(line) && line.trim() !== '') {
inCallout = false;
}
}
return { content: lines.join('\n'), changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
const result = fixFile(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

View file

@ -0,0 +1,69 @@
// Fix unescaped currency dollar signs that Obsidian misinterprets as LaTeX.
// e.g. "$1M portfolio" renders as collapsed LaTeX instead of text.
// Escapes $<digits><letter> and $<digits>,<digits> patterns outside code blocks
// and existing LaTeX expressions.
//
// Usage:
// node scripts/fix-currency-dollars.js <vault-path> # Detect only
// node scripts/fix-currency-dollars.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
function fixFile(content) {
const lines = content.split('\n');
let inCode = false;
let changed = false;
for (let i = 0; i < lines.length; i++) {
if (/^```/.test(lines[i])) { inCode = !inCode; continue; }
if (inCode) continue;
const original = lines[i];
// $<digits><letter> — currency like $1M, $500k, $10B
lines[i] = lines[i].replace(/(?<!\\)\$(\d+[A-Za-z])/g, '\\$$$1');
// $<digits>,<digits> — currency like $1,000 $28,800 (not inside LaTeX {,})
lines[i] = lines[i].replace(/(?<!\\)\$(\d{1,3}(?:,\d{3})+)(?!\})/g, '\\$$$1');
// $<digits>.<digits> <word> — currency like $1.5 million, $0.03 per
lines[i] = lines[i].replace(/(?<!\\)\$(\d+\.\d+\s+[a-zA-Z])/g, '\\$$$1');
if (lines[i] !== original) changed = true;
}
return { content: lines.join('\n'), changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
const result = fixFile(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

View file

@ -0,0 +1,88 @@
// Fix mermaid "end" reserved keyword issues:
// 1. Strip extra "end" keywords that have no matching block opener
// (subgraph in flowcharts; par/alt/opt/loop/rect/critical/break/box in sequence diagrams)
// 2. Insert zero-width space into standalone "end" tokens (e.g. node IDs),
// leaving "end" inside larger words (Vendor, Send, Backend) untouched
// Block-closing "end" lines are preserved.
//
// Usage:
// node scripts/fix-mermaid-end.js <vault-path> # Detect only
// node scripts/fix-mermaid-end.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
const ZWSP = '';
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
// Block constructs that are closed by a bare "end" line. "subgraph" is the
// flowchart opener; the rest open blocks in sequence diagrams. Continuation
// keywords (and, else, option) neither open nor close a block.
const END_BLOCK_OPENER = /^(subgraph|par|alt|opt|loop|rect|critical|break|box)\b/;
function fixMermaidEnd(content) {
let changed = false;
const result = content.replace(/```mermaid\n([\s\S]*?)```/g, (match, inner) => {
// Strip extra "end" keywords that have no matching block opener
let depth = 0;
let stripped = inner.split('\n').filter(line => {
const trimmed = line.trim();
if (END_BLOCK_OPENER.test(trimmed)) { depth++; return true; }
if (trimmed === 'end') { if (depth > 0) { depth--; return true; } return false; }
return true;
}).join('\n');
// Insert ZWSP into standalone "end" tokens (e.g. node IDs) except
// block-closing "end" lines. Use word boundaries so "end" inside larger
// words (Vendor, Send, Backend) is left intact -- inserting ZWSP there
// breaks Mermaid's lexer rather than helping.
const fixed = stripped.split('\n').map(line => {
if (line.trim() === 'end') return line;
return line.replace(/\bend\b/g, 'e' + ZWSP + 'nd');
}).join('\n');
if (fixed !== inner) {
changed = true;
return '```mermaid\n' + fixed + '```';
}
return match;
});
return { content: result, changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
if (!content.includes('```mermaid')) continue;
const result = fixMermaidEnd(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

116
scripts/fix-mermaid-list.js Normal file
View file

@ -0,0 +1,116 @@
// Fix mermaid labels that trigger "Unsupported markdown: list".
// Mermaid's markdown parser treats "1. text" and "1) text" as ordered lists,
// and "- text" / "* text" as unordered lists inside node/edge labels.
//
// Fixes:
// - Numbered list: "1. Serialize" / "1) Serialize" → "1: Serialize"
// - Unordered list: multi-line labels with "- item" lines → <br/>-joined text
//
// Usage:
// node scripts/fix-mermaid-list.js <vault-path> # Detect only
// node scripts/fix-mermaid-list.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
// Escape numbered list patterns: "1. text" or "1) text" → "1: text"
function fixNumberedList(label) {
return label
.replace(/^(\d+)[.)]\s/gm, '$1: ')
.replace(/(<br\/?>)(\d+)[.)]\s/gi, '$1$2: ');
}
// Convert unordered list items to <br/>-joined text
function fixUnorderedList(label) {
if (!/(?:^|\n)\s*[-*] /m.test(label)) return label;
return label
.split(/\n/)
.map(l => l.replace(/^\s*[-*]\s+/, '').trim())
.filter(l => l.length > 0)
.join('<br/>');
}
function fixLabel(label) {
return fixNumberedList(fixUnorderedList(label));
}
function fixMermaidList(content) {
let changed = false;
const result = content.replace(/```mermaid\n([\s\S]*?)```/g, (match, inner) => {
let fixed = inner;
// Fix edge labels: -->|"1. text"| → -->|"1: text"|
fixed = fixed.replace(/\|"([^"]*?)"\|/g, (_m, label) => {
const newLabel = fixLabel(label);
if (newLabel === label) return _m;
return '|"' + newLabel + '"|';
});
// Fix quoted node labels in square brackets: ["..."]
fixed = fixed.replace(/\["([^\]]*?)"\]/g, (_m, label) => {
const newLabel = fixLabel(label);
if (newLabel === label) return _m;
return '["' + newLabel + '"]';
});
// Fix quoted node labels in round brackets: ("...")
fixed = fixed.replace(/\("([^)]*?)"\)/g, (_m, label) => {
const newLabel = fixLabel(label);
if (newLabel === label) return _m;
return '("' + newLabel + '")';
});
// Fix quoted node labels in curly brackets: {"..."}
fixed = fixed.replace(/\{"([^}]*?)"\}/g, (_m, label) => {
const newLabel = fixLabel(label);
if (newLabel === label) return _m;
return '{"' + newLabel + '"}';
});
if (fixed !== inner) {
changed = true;
return '```mermaid\n' + fixed + '```';
}
return match;
});
return { content: result, changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
if (!content.includes('```mermaid')) continue;
const result = fixMermaidList(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

View file

@ -0,0 +1,149 @@
// Re-insert missing "end" keywords in mermaid blocks. An earlier version of
// fix-mermaid-end.js only recognized flowchart "subgraph" as a block opener,
// so it stripped legitimate "end" lines that closed sequence diagram blocks
// (par/alt/opt/loop/rect/critical/break/box). This script restores them.
//
// Placement heuristic: a block closes where indentation returns to the
// opener's level (continuation keywords like else/and/option stay inside).
// Blocks whose content is not indented deeper than the opener close at the
// end of the diagram.
//
// Also ensures the closing ``` sits on its own line — some blocks ended with
// "end```" (no newline), which prevents markdown from closing the fence.
//
// Usage:
// node scripts/fix-mermaid-missing-end.js <vault-path> # Detect only
// node scripts/fix-mermaid-missing-end.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
const SEQUENCE_OPENER = /^(par|alt|opt|loop|rect|critical|break|box)\b/;
const FLOWCHART_OPENER = /^subgraph\b/;
const CONTINUATION = /^(else|and|option)\b/;
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
function diagramOpener(lines) {
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('%%')) continue;
if (/^sequenceDiagram\b/.test(trimmed)) return SEQUENCE_OPENER;
if (/^(flowchart|graph)\b/.test(trimmed)) return FLOWCHART_OPENER;
return null; // other diagram types don't use bare "end"
}
return null;
}
function repairBlock(inner, opener) {
const lines = inner.split('\n');
const out = [];
// stack entries: { indent, indented: null|true|false }
// indented tracks whether the block's content is indented deeper than the
// opener (null until the first content line is seen)
const stack = [];
const closeTop = () => {
const top = stack.pop();
out.push(' '.repeat(top.indent) + 'end');
};
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) { out.push(line); continue; }
const indent = line.length - line.trimStart().length;
if (trimmed === 'end') {
if (stack.length > 0) stack.pop();
out.push(line);
continue;
}
if (CONTINUATION.test(trimmed) && stack.length > 0) {
out.push(line);
continue;
}
// Dedent closes indented-style blocks
while (stack.length > 0 && stack[stack.length - 1].indented === true
&& indent <= stack[stack.length - 1].indent) {
closeTop();
}
if (stack.length > 0) {
const top = stack[stack.length - 1];
if (top.indented === null) top.indented = indent > top.indent;
}
if (opener.test(trimmed)) {
stack.push({ indent, indented: null });
}
out.push(line);
}
// Close anything still open at the end of the diagram (innermost first),
// inserting before trailing blank lines so the closing ``` stays on its own line
const needClose = stack.length > 0;
const trailing = [];
while (out.length > 0 && out[out.length - 1].trim() === '') trailing.push(out.pop());
while (stack.length > 0) closeTop();
if (needClose && trailing.length === 0) trailing.push('');
out.push(...trailing.reverse());
return out.join('\n');
}
function fixMissingEnd(content) {
let changed = false;
const result = content.replace(/```mermaid\n([\s\S]*?)```/g, (match, inner) => {
const opener = diagramOpener(inner.split('\n'));
let fixed = opener ? repairBlock(inner, opener) : inner;
// Closing fence must be on its own line
if (!fixed.endsWith('\n')) fixed += '\n';
const rebuilt = '```mermaid\n' + fixed + '```';
if (rebuilt !== match) {
changed = true;
return rebuilt;
}
return match;
});
return { content: result, changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
if (!content.includes('```mermaid')) continue;
const result = fixMissingEnd(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

View file

@ -0,0 +1,68 @@
// Fix mermaid node labels containing unquoted parentheses or slashes.
// Mermaid misparses special characters in unquoted labels, e.g.
// A[System (Restart/Scale)] → parse error
// A["System (Restart/Scale)"] → correct
//
// Usage:
// node scripts/fix-mermaid-parens.js <vault-path> # Detect only
// node scripts/fix-mermaid-parens.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
function fixMermaidParens(content) {
let changed = false;
const result = content.replace(/```mermaid\n([\s\S]*?)```/g, (match, inner) => {
let fixed = inner;
// Quote unquoted labels in [...] containing ( ) or /
fixed = fixed.replace(/\[([^\]"]*[()\/\\][^\]"]*)\]/g, '["$1"]');
// Quote unquoted labels in (...) containing nested parens
fixed = fixed.replace(/\(([^)"]*[()][^)"]*)\)/g, '("$1")');
if (fixed !== inner) {
changed = true;
return '```mermaid\n' + fixed + '```';
}
return match;
});
return { content: result, changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
if (!content.includes('```mermaid')) continue;
const result = fixMermaidParens(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

View file

@ -0,0 +1,95 @@
// Fix mermaid labels with nested double quotes that break parsing.
// Inner quotes inside an already-quoted label cause premature termination:
// A["0x400000 ("non-PIE) or random (PIE")"] → parse error
// A["0x400000 (non-PIE) or random (PIE)"] → correct
//
// Strips inner double quotes from within quoted mermaid labels by finding
// patterns like ["...("word)..."] where quotes appear inside parentheses.
//
// Usage:
// node scripts/fix-mermaid-quotes.js <vault-path> # Detect only
// node scripts/fix-mermaid-quotes.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
function fixMermaidQuotes(content) {
let changed = false;
const result = content.replace(/```mermaid\n([\s\S]*?)```/g, (match, inner) => {
let fixed = inner;
// Process each line individually to find quoted labels with inner quotes.
// A quoted label looks like: ["content"] or ("content") or {"content"} or |"content"|
// If "content" itself has double quotes, they break parsing.
fixed = fixed.split('\n').map(line => {
// For each quoted-label pattern, find and strip inner quotes
return line
// Square bracket labels: ["..."]
.replace(/\["((?:[^"]|"(?!\]))+)"\]/g, (m, label) => {
if (!label.includes('"')) return m;
return '["' + label.replace(/"/g, "") + '"]';
})
// Round bracket labels: ("...")
.replace(/\("((?:[^"]|"(?!\)))+)"\)/g, (m, label) => {
if (!label.includes('"')) return m;
return '("' + label.replace(/"/g, "") + '")';
})
// Curly bracket labels: {"..."}
.replace(/\{"((?:[^"]|"(?!\}))+)"\}/g, (m, label) => {
if (!label.includes('"')) return m;
return '{"' + label.replace(/"/g, "") + '"}';
})
// Edge labels: |"..."|
.replace(/\|"((?:[^"]|"(?!\|))+)"\|/g, (m, label) => {
if (!label.includes('"')) return m;
return '|"' + label.replace(/"/g, "") + '"|';
});
}).join('\n');
if (fixed !== inner) {
changed = true;
return '```mermaid\n' + fixed + '```';
}
return match;
});
return { content: result, changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
if (!content.includes('```mermaid')) continue;
const result = fixMermaidQuotes(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

78
scripts/fix-split-end.js Normal file
View file

@ -0,0 +1,78 @@
// Fix mermaid lines corrupted by an old regex that split words like "Send"
// into "S\nend" (putting bare "end" on its own line).
// Rejoins "S\nend ..." back to "Send ..." (with zero-width space).
//
// Usage:
// node scripts/fix-split-end.js <vault-path> # Detect only
// node scripts/fix-split-end.js <vault-path> --fix # Detect and fix
const fs = require('fs');
const path = require('path');
const { resolveVaultRoot } = require('./vault-root');
const root = resolveVaultRoot();
const fix = process.argv.includes('--fix');
const ZWSP = '';
function walkDir(dir) {
let results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) results = results.concat(walkDir(full));
else if (entry.name.endsWith('.md')) results.push(full);
}
return results;
}
function fixFile(content) {
let changed = false;
const result = content.replace(/```mermaid\n([\s\S]*?)```/g, (match, inner) => {
const lines = inner.split('\n');
for (let i = 1; i < lines.length; i++) {
// Line starts with "end " followed by word chars (not a bare "end" closing a subgraph)
if (/^end\s+\S/.test(lines[i])) {
const prev = lines[i - 1];
// Previous line ends with a single uppercase letter or a few chars that form part of a word
// e.g. ' O -->|"4. S' or ' N->>E: S' or ' B["S'
if (/[A-Z]\s*$/.test(prev)) {
// Rejoin: remove "end" from current line, append "e<ZWSP>nd" + rest to previous line
const rest = lines[i].substring(3); // strip "end"
lines[i - 1] = prev + 'e' + ZWSP + 'nd' + rest;
lines.splice(i, 1);
i--;
changed = true;
}
}
}
if (changed) {
return '```mermaid\n' + lines.join('\n') + '```';
}
return match;
});
return { content: result, changed };
}
const files = walkDir(root);
let count = 0;
for (const fp of files) {
const content = fs.readFileSync(fp, 'utf8');
if (!content.includes('```mermaid')) continue;
const result = fixFile(content);
if (result.changed) {
const rel = path.relative(root, fp);
console.log((fix ? 'Fixed: ' : 'BROKEN: ') + rel);
if (fix) {
fs.writeFileSync(fp, result.content, 'utf8');
}
count++;
}
}
console.log('\nTotal: ' + count);
if (!fix && count > 0) {
console.log('Run with --fix to apply fixes.');
}

51
scripts/sync-modes.js Normal file
View file

@ -0,0 +1,51 @@
// Prepares modes.json (the build input) before every build.
//
// Resolution order:
// 1. modes.config.json exists -> copy its "modesFile" to modes.json
// 2. modes.json already exists -> leave it as-is
// 3. Neither -> copy modes.sample.json to modes.json
//
// modes.json and modes.config.json are gitignored. Keep your own modes in a
// separate file (e.g. modes.personal.json) and point modes.config.json at it.
const fs = require('fs');
const path = require('path');
const root = path.join(__dirname, '..');
const target = path.join(root, 'modes.json');
const configPath = path.join(root, 'modes.config.json');
const samplePath = path.join(root, 'modes.sample.json');
function copyValidated(source) {
const content = fs.readFileSync(source, 'utf8');
let modes;
try {
modes = JSON.parse(content);
} catch (err) {
console.error(`${source} is not valid JSON: ${err.message}`);
process.exit(1);
}
if (!Array.isArray(modes) || modes.length === 0) {
console.error(`${source} must be a non-empty JSON array of modes.`);
process.exit(1);
}
fs.writeFileSync(target, content);
}
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
if (!config.modesFile) {
console.error('modes.config.json must contain a "modesFile" property.');
process.exit(1);
}
const source = path.resolve(root, config.modesFile);
if (!fs.existsSync(source)) {
console.error(`Modes file from modes.config.json not found: ${source}`);
process.exit(1);
}
copyValidated(source);
console.log(`modes.json updated from ${config.modesFile}`);
} else if (!fs.existsSync(target)) {
copyValidated(samplePath);
console.log('Created modes.json from modes.sample.json. Create modes.config.json to use your own modes file.');
}

27
scripts/vault-root.js Normal file
View file

@ -0,0 +1,27 @@
// Shared vault path resolution for all fix scripts.
// The vault path comes from the first non-flag CLI argument, or from the
// OBSIDIAN_VAULT environment variable if no argument is given.
const fs = require('fs');
const path = require('path');
function resolveVaultRoot() {
const arg = process.argv.slice(2).find(a => !a.startsWith('--'));
const root = arg || process.env.OBSIDIAN_VAULT;
if (!root) {
const script = path.basename(process.argv[1] || 'fix-script.js');
console.error(`Usage: node scripts/${script} <vault-path> [--fix]`);
console.error('Or set the OBSIDIAN_VAULT environment variable to your vault path.');
process.exit(1);
}
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
console.error(`Vault path is not a directory: ${root}`);
process.exit(1);
}
return path.resolve(root);
}
module.exports = { resolveVaultRoot };

134
styles.css Normal file
View file

@ -0,0 +1,134 @@
.claude-helper-modal h2 {
margin-bottom: 12px;
}
.claude-helper-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.claude-helper-mode-btn {
padding: 12px 16px;
border: 1px solid var(--background-modifier-border);
border-radius: 8px;
cursor: pointer;
transition: background-color 0.15s ease;
}
.claude-helper-mode-btn:hover {
background-color: var(--background-modifier-hover);
border-color: var(--interactive-accent);
}
.claude-helper-mode-name {
font-weight: 600;
font-size: 14px;
margin-bottom: 4px;
}
.claude-helper-mode-desc {
font-size: 12px;
color: var(--text-muted);
}
.claude-helper-mode-btn.claude-helper-mode-selected {
background-color: var(--interactive-accent);
border-color: var(--interactive-accent);
color: var(--text-on-accent);
}
.claude-helper-mode-btn.claude-helper-mode-selected .claude-helper-mode-desc {
color: var(--text-on-accent);
opacity: 0.85;
}
.claude-helper-label {
display: block;
font-weight: 600;
font-size: 14px;
margin-bottom: 8px;
}
.claude-helper-config-section {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--background-modifier-border);
}
.claude-helper-title-input {
width: 100% !important;
}
.claude-helper-extra-input {
width: 100% !important;
min-height: 60px;
}
.claude-helper-btn-row {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
}
/* Queue status modal */
.claude-helper-queue-section {
margin-bottom: 16px;
}
.claude-helper-queue-section h3 {
margin-bottom: 8px;
font-size: 14px;
color: var(--text-muted);
}
.claude-helper-queue-item {
display: flex;
align-items: center;
padding: 8px 12px;
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
margin-bottom: 4px;
}
.claude-helper-queue-active {
border-color: var(--interactive-accent);
background-color: var(--background-modifier-hover);
}
.claude-helper-queue-name {
font-weight: 600;
flex: 1;
}
.claude-helper-queue-mode {
font-size: 12px;
color: var(--text-muted);
margin-right: 8px;
}
.claude-helper-queue-remove {
font-size: 12px;
cursor: pointer;
}
.claude-helper-queue-clear {
margin-top: 12px;
width: 100%;
}
.claude-helper-queue-empty {
color: var(--text-muted);
font-style: italic;
}
.claude-helper-spinner {
color: var(--interactive-accent);
animation: claude-helper-pulse 1.5s infinite;
}
@keyframes claude-helper-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}

19
tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["DOM", "ES5", "ES6", "ES7"]
},
"include": ["**/*.ts"]
}

3
versions.json Normal file
View file

@ -0,0 +1,3 @@
{
"1.0.0": "0.15.0"
}