vault backup: 2026-02-26 21:10:47
This commit is contained in:
@@ -54,16 +54,6 @@ Create a comprehensive synthesis of the week's work.
|
||||
|
||||
Best for: Weekly reviews, pattern recognition
|
||||
|
||||
### 💬 git-commit-msg
|
||||
|
||||
AI-generated conventional commit messages from staged changes.
|
||||
|
||||
```
|
||||
/git-commit-msg
|
||||
```
|
||||
|
||||
Best for: Creating meaningful commit messages, maintaining clean git history
|
||||
|
||||
## Creating Custom Commands
|
||||
|
||||
1. Create a new `.md` file in this directory
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
---
|
||||
name: pragmatic-review
|
||||
description:
|
||||
'Interactive pragmatic code review focusing on YAGNI and KISS principles'
|
||||
version: 1.0.0
|
||||
argument-hint:
|
||||
'[--auto] [--ci] [--deep (6-pass)] [--branch branch-name] [--base base-branch]'
|
||||
allowed-tools:
|
||||
[
|
||||
Read,
|
||||
Grep,
|
||||
Glob,
|
||||
Bash(test:*),
|
||||
Bash(git:*),
|
||||
Bash(echo:*),
|
||||
Bash(head:*),
|
||||
Bash(wc:*),
|
||||
Bash(tr:*),
|
||||
]
|
||||
---
|
||||
|
||||
# Pragmatic Code Review: YAGNI & KISS Focus
|
||||
|
||||
You will perform an interactive code review with laser focus on **YAGNI** (You
|
||||
Aren't Gonna Need It) and **KISS** (Keep It Simple, Stupid) principles.
|
||||
|
||||
## Review Modes
|
||||
|
||||
**Default mode**: Fast YAGNI/KISS-focused review
|
||||
|
||||
- Scans for over-engineering, unused abstractions, unnecessary complexity
|
||||
- Quick security and performance checks (OWASP basics, obvious N+1 queries)
|
||||
- Self-reflection to validate findings with evidence
|
||||
|
||||
**Deep mode** (`--deep` flag): Multi-pass comprehensive review
|
||||
|
||||
- Pass 1: Security (OWASP Top 10, input validation, auth issues)
|
||||
- Pass 2: Architecture (SOLID principles, separation of concerns)
|
||||
- Pass 3: Logic (edge cases, error handling, correctness)
|
||||
- Pass 4: Performance (algorithm complexity, resource leaks)
|
||||
- Pass 5: YAGNI/KISS (over-engineering, unnecessary abstractions)
|
||||
- Pass 6: Maintainability (readability, tests, documentation)
|
||||
- Self-reflection after all passes
|
||||
|
||||
Use `--deep` when:
|
||||
|
||||
- Security-critical changes (auth, payment, data handling)
|
||||
- Core architecture modifications
|
||||
- Complex logic changes with many edge cases
|
||||
- Performance-sensitive code paths
|
||||
|
||||
Use default mode when:
|
||||
|
||||
- Feature additions
|
||||
- Bug fixes
|
||||
- Refactoring
|
||||
- Documentation changes
|
||||
|
||||
**CI mode** (`--ci` flag): Non-interactive mode for GitHub Actions
|
||||
|
||||
- Skips ALL interactive prompts
|
||||
- Auto-selects: all branch changes vs base branch
|
||||
- Uses `$GITHUB_BASE_REF` environment variable if available
|
||||
- Outputs all findings at once as markdown (summary view)
|
||||
|
||||
## Step 1: Determine Review Scope
|
||||
|
||||
### Check Current Git State
|
||||
|
||||
First, verify we're in a git repository by running:
|
||||
|
||||
- `test -d .git` to check if .git directory exists
|
||||
|
||||
If not in a git repository, ask the user to specify files to review manually.
|
||||
|
||||
If in a git repository, gather information:
|
||||
|
||||
#### Current branch:
|
||||
Run: `git rev-parse --abbrev-ref HEAD`
|
||||
|
||||
#### Default branch detection:
|
||||
1. Try: `git rev-parse --verify main`
|
||||
2. If that fails, try: `git rev-parse --verify master`
|
||||
3. If that fails, try: `git rev-parse --verify develop`
|
||||
|
||||
If user specified `--base [branch]` in arguments, use that instead.
|
||||
|
||||
#### Working directory status:
|
||||
Run: `git status --short | head -20`
|
||||
|
||||
### Present Options to User
|
||||
|
||||
**If `--ci` flag is present:** Skip all interactive prompts and auto-select
|
||||
option 2: Review all changes on current branch vs base.
|
||||
|
||||
Unless `--auto` or `--ci` flag is present, ask the user:
|
||||
|
||||
```
|
||||
📋 CODE REVIEW SCOPE SELECTION
|
||||
════════════════════════════════
|
||||
|
||||
What would you like to review?
|
||||
|
||||
1️⃣ Current uncommitted changes
|
||||
2️⃣ All changes on current branch (compared to [detected default branch])
|
||||
3️⃣ Specific files or directory
|
||||
4️⃣ Last N commits
|
||||
5️⃣ Staged changes only
|
||||
|
||||
Please enter your choice (1-5):
|
||||
```
|
||||
|
||||
## Step 2: YAGNI/KISS Analysis Framework
|
||||
|
||||
For each file identified, analyze for these patterns:
|
||||
|
||||
### YAGNI Detection Patterns
|
||||
|
||||
1. **Unused abstractions**
|
||||
- Interfaces/protocols with single implementations
|
||||
- Abstract base classes with one concrete subclass
|
||||
- Generic types that are always the same
|
||||
|
||||
2. **Premature flexibility**
|
||||
- Configuration for things that never change
|
||||
- Plugin systems with no plugins
|
||||
- Feature flags that are always on/off
|
||||
|
||||
3. **Over-engineering indicators**
|
||||
- Factory classes for simple objects
|
||||
- Builder patterns for objects with 2-3 fields
|
||||
- Event systems with single listeners
|
||||
|
||||
4. **Speculative code**
|
||||
- "TODO: might need this" comments
|
||||
- Commented-out code "just in case"
|
||||
- Unreachable code paths
|
||||
- Methods that are never called
|
||||
|
||||
5. **The GenericButton Anti-Pattern**
|
||||
- Components with 8+ optional parameters serving different use cases
|
||||
- So many props that using it is as complex as writing from scratch
|
||||
|
||||
6. **Premature Abstraction - Rule of Three**
|
||||
- Abstraction created at 1st or 2nd duplication (wait for 3rd!)
|
||||
- Reference: Martin Fowler - "Tolerate duplication twice, refactor on the third"
|
||||
|
||||
### KISS Violation Patterns
|
||||
|
||||
1. **Verbose implementations**
|
||||
- Can be reduced by >50% lines
|
||||
- Reimplements standard library functions
|
||||
- Complex regex when simple string operations work
|
||||
|
||||
2. **Abstraction addiction**
|
||||
- More than 3 levels of inheritance/wrapping
|
||||
- Interfaces between every layer
|
||||
|
||||
3. **Clever code**
|
||||
- Needs extensive comments to explain
|
||||
- Uses obscure language features unnecessarily
|
||||
- One-liners that should be 5 clear lines
|
||||
|
||||
4. **Catch-Log-Exit Anti-Pattern**
|
||||
- Catching exceptions just to log and exit
|
||||
- Replaces actual error with a guess about what went wrong
|
||||
|
||||
```typescript
|
||||
// TERRIBLE: replaces actual error with a guess
|
||||
try {
|
||||
await createNewBranch({ branchName, cwd })
|
||||
} catch (error) {
|
||||
console.error('Error: Not in a git repository') // Maybe wrong!
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// CORRECT: let it throw naturally
|
||||
await createNewBranch({ branchName, cwd })
|
||||
```
|
||||
|
||||
### Security Patterns to Check
|
||||
|
||||
Even in a YAGNI/KISS review, flag critical security issues:
|
||||
|
||||
1. **SQL Injection**
|
||||
- String concatenation in SQL queries
|
||||
- Missing parameterized queries
|
||||
|
||||
2. **Authentication/Authorization**
|
||||
- Hardcoded secrets
|
||||
- Weak defaults: `SECRET = os.getenv('KEY', 'default')`
|
||||
- JWT without expiration
|
||||
|
||||
3. **Unvalidated External Inputs**
|
||||
- URL parameters used directly without validation
|
||||
- API response data trusted without schema validation
|
||||
|
||||
### Performance Patterns to Check
|
||||
|
||||
Flag obvious performance issues:
|
||||
|
||||
1. **N+1 Query Problems**
|
||||
- Loops that make database calls
|
||||
- Missing eager loading
|
||||
|
||||
2. **Inefficient Algorithms**
|
||||
- O(n²) where O(n) or O(n log n) would work
|
||||
- Unnecessary nested loops
|
||||
|
||||
## Step 3: Perform Analysis
|
||||
|
||||
**Check for `--deep` flag**: If present, use Multi-Pass Deep Mode with 6
|
||||
sequential passes. Otherwise, use Fast YAGNI/KISS Mode.
|
||||
|
||||
**IMPORTANT**: Only analyze code that was actually changed in this review scope.
|
||||
Do not flag pre-existing issues.
|
||||
|
||||
## Step 3.5: Self-Review Pass
|
||||
|
||||
**Before presenting findings, validate each issue:**
|
||||
|
||||
1. **Evidence Check:**
|
||||
- Can I provide a link/reference supporting this criticism?
|
||||
- Have I explained WHY this matters?
|
||||
|
||||
2. **Severity Validation:**
|
||||
- Is this rating accurate (High/Medium/Low)?
|
||||
- Would this issue actually cause problems?
|
||||
|
||||
3. **YAGNI-Specific Checks:**
|
||||
- If flagging duplication: Is this the 3rd+ occurrence?
|
||||
- Can this be refactored later when we have more information?
|
||||
|
||||
**Remove or downgrade any issues that fail these checks.**
|
||||
|
||||
## Step 4: Interactive Review Process
|
||||
|
||||
### Issue Severity Prefixes
|
||||
|
||||
Use these prefixes to communicate priority:
|
||||
|
||||
| Prefix | Meaning | Action Required |
|
||||
| ------------- | ---------------------------------- | --------------------- |
|
||||
| `issue:` | Bug, correctness problem | Must fix before merge |
|
||||
| `nit:` | Minor improvement, style | Optional, don't block |
|
||||
| `thought:` | Design consideration | Discuss, may defer |
|
||||
| `suggestion:` | Specific improvement with code | Consider seriously |
|
||||
|
||||
### Interactive Walkthrough
|
||||
|
||||
For each issue, present:
|
||||
|
||||
```
|
||||
═══════════════════════════════════════
|
||||
Issue [current] of [total]
|
||||
═══════════════════════════════════════
|
||||
|
||||
📁 File: [filename]
|
||||
📍 Lines: [start-end]
|
||||
🏷️ Type: [YAGNI | KISS | Both]
|
||||
🎯 Severity: [High | Medium | Low]
|
||||
|
||||
CURRENT CODE:
|
||||
[show actual code snippet]
|
||||
|
||||
ISSUE DETECTED: [Specific description]
|
||||
|
||||
WHY THIS MATTERS: [Explain the real cost/problem]
|
||||
|
||||
SUGGESTED SIMPLIFICATION:
|
||||
[Show the simpler alternative code]
|
||||
|
||||
═══════════════════════════════════════
|
||||
|
||||
What would you like to do?
|
||||
1. ✅ Accept - Add to fix list
|
||||
2. ❌ Skip - Keep current code
|
||||
3. 💬 Discuss - Mark for team review
|
||||
4. 👀 Context - See more surrounding code
|
||||
5. ⏹️ Stop - End review here
|
||||
```
|
||||
|
||||
## Step 5: Core Review Rules
|
||||
|
||||
### ALWAYS Flag These YAGNI Issues:
|
||||
|
||||
1. **Interfaces with single implementation**
|
||||
2. **Unused code** - functions/methods with zero callers
|
||||
3. **Speculative database fields** - columns always NULL
|
||||
4. **Premature optimization** - caching before measuring
|
||||
|
||||
### ALWAYS Flag These KISS Violations:
|
||||
|
||||
1. **Standard library reimplementation**
|
||||
2. **Excessive abstraction layers**
|
||||
3. **Configuration over convention** - 100 lines config for 50 lines code
|
||||
|
||||
### DON'T Flag These:
|
||||
|
||||
1. **Necessary complexity** - error handling, security measures
|
||||
2. **Domain complexity** - business rules that ARE complex
|
||||
3. **Team conventions** - agreed-upon patterns
|
||||
|
||||
## Step 6: Final Summary
|
||||
|
||||
```
|
||||
📝 PRAGMATIC REVIEW COMPLETE
|
||||
═══════════════════════════════
|
||||
|
||||
Review Statistics:
|
||||
• Files reviewed: [X]
|
||||
• Lines changed: [Y]
|
||||
|
||||
Issues Found: [Y total]
|
||||
• Critical (blocking): [count]
|
||||
• High priority: [count]
|
||||
• Medium: [count]
|
||||
• Low: [count]
|
||||
|
||||
COMPLEXITY REDUCTION POTENTIAL:
|
||||
• Lines removable: ~[total] (-X%)
|
||||
• Unnecessary abstractions: [count]
|
||||
|
||||
TOP 3 QUICK WINS:
|
||||
1. [Biggest impact, easiest change]
|
||||
2. [Second biggest impact]
|
||||
3. [Third biggest impact]
|
||||
|
||||
RECOMMENDATION: [Clear ship/don't ship with reasoning]
|
||||
|
||||
═══════════════════════════════
|
||||
```
|
||||
|
||||
## Command Parameters Reference
|
||||
|
||||
- `--auto` : Skip interactive prompts, use defaults (uncommitted changes)
|
||||
- `--ci` : CI mode - skip ALL prompts, review branch vs base
|
||||
- `--deep` : Enable 6-pass comprehensive review
|
||||
- `--branch [name]` : Review specific branch
|
||||
- `--base [branch]` : Compare against this base branch
|
||||
|
||||
Examples:
|
||||
|
||||
- `/pragmatic-review` - Interactive mode
|
||||
- `/pragmatic-review --auto` - Review current changes automatically
|
||||
- `/pragmatic-review --ci` - CI mode for GitHub Actions
|
||||
- `/pragmatic-review --deep` - Comprehensive 6-pass review
|
||||
|
||||
## Core Philosophy
|
||||
|
||||
When in doubt, remember:
|
||||
|
||||
1. **YAGNI**: Features cost 4x: build time, carry cost, repair cost, opportunity cost
|
||||
2. **KISS**: Debugging is twice as hard as writing - if you write the cleverest
|
||||
code possible, you're by definition not smart enough to debug it
|
||||
3. **Rule of Three**: Tolerate duplication twice, refactor on the third
|
||||
4. **Pragmatic**: Ship working software today, perfect it tomorrow
|
||||
|
||||
Your role is to be the champion of simplicity. Every line deleted is a victory.
|
||||
|
||||
## References
|
||||
|
||||
- Martin Fowler - YAGNI: https://martinfowler.com/bliki/Yagni.html
|
||||
- KISS principle: https://en.wikipedia.org/wiki/KISS_principle
|
||||
- OWASP Top 10: https://owasp.org/www-project-top-ten/
|
||||
- Addy Osmani - "Avoid Large Pull Requests"
|
||||
- Jeff Atwood - "Curly's Law: Do One Thing"
|
||||
@@ -32,8 +32,9 @@ capabilities.
|
||||
- Check if already on latest version:
|
||||
|
||||
```bash
|
||||
CURRENT=$(grep '"version"' package.json | sed 's/.*: "\(.*\)".*/\1/')
|
||||
LATEST=$(curl -s https://raw.githubusercontent.com/heyitsnoah/claudesidian/main/package.json | grep '"version"' | sed 's/.*: "\(.*\)".*/\1/')
|
||||
# Use cut instead of sed to avoid zsh parentheses escaping issues
|
||||
CURRENT=$(grep '"version"' package.json | head -1 | cut -d'"' -f4)
|
||||
LATEST=$(curl -s https://raw.githubusercontent.com/heyitsnoah/claudesidian/main/package.json | grep '"version"' | head -1 | cut -d'"' -f4)
|
||||
|
||||
if [ "$CURRENT" = "$LATEST" ]; then
|
||||
echo "✅ You're already on the latest version ($CURRENT)"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
# Skill discovery hook - suggests relevant skills when user mentions "skill"
|
||||
# Exit 0 stdout → added as context for Claude
|
||||
# No dependencies - pure bash
|
||||
|
||||
# Read all stdin
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract just the prompt field value (pure bash, no jq)
|
||||
# Input format: {"prompt": "user text here", ...}
|
||||
# Pattern handles escaped quotes: matches [^\\"] or \\. sequences
|
||||
# Use -n and /p so non-matching input produces empty output (not original input)
|
||||
PROMPT=$(echo "$INPUT" | sed -nE 's/.*"prompt"[[:space:]]*:[[:space:]]*"(([^\\"]|\\.)*)".*/\1/p')
|
||||
|
||||
# Match: skill, skills (case-insensitive, word boundary) in prompt only
|
||||
if echo "$PROMPT" | grep -iqE '\bskills?\b'; then
|
||||
# Get skill names from .claude/skills/ directory
|
||||
SKILLS_DIR="${CLAUDE_PROJECT_DIR:-.}/.claude/skills"
|
||||
|
||||
if [ -d "$SKILLS_DIR" ]; then
|
||||
# Build list of skills with descriptions
|
||||
output=""
|
||||
for d in "$SKILLS_DIR"/*/; do
|
||||
[ -d "$d" ] || continue
|
||||
name=$(basename "$d")
|
||||
skill_file="$d/SKILL.md"
|
||||
if [ -f "$skill_file" ]; then
|
||||
# Extract description from YAML frontmatter
|
||||
desc=$(grep -m1 '^description:' "$skill_file" | sed 's/^description: *//')
|
||||
output="$output$name: $desc"$'\n'
|
||||
else
|
||||
output="$output$name"$'\n'
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$output" ]; then
|
||||
echo "<skill-discovery>"
|
||||
echo "The user mentioned 'skill'. Available skills in this project:"
|
||||
echo ""
|
||||
echo "$output" | sort
|
||||
echo "If relevant to the user's request, read the SKILL.md file to load the skill instructions."
|
||||
echo "</skill-discovery>"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always exit 0 - never block user prompts
|
||||
exit 0
|
||||
+28
-7
@@ -8,16 +8,37 @@
|
||||
"Edit(.obsidian/plugins/quickadd/data.json)",
|
||||
"Edit(04_Archive/Projects/Airport/Chengdu/Office Test Env.md)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(cat > .replace_urls.py << 'PYEOF'\n#!/usr/bin/env python3\nimport re\n\n# Read the mapping\nmappings = []\nwith open('.url_mapping.txt', 'r') as f:\n for line in f:\n if '|' in line:\n url, filename = line.strip().split('|', 1)\n mappings.append((url, filename))\n\n# Read the markdown file\nnote_path = \"00_Inbox/Clippings/2026/01/ZeroLuawesome-nanobanana-pro 🚀 An awesome list of curated Nano Banana pro prompts and examples. Your go-to resource for mastering prompt engineering and exploring the creative potential of the Nano banana pro(Nano banana 2) AI image model..md\"\n\nwith open(note_path, 'r', encoding='utf-8') as f:\n content = f.read()\n\n# Perform replacements\nreplacements_made = 0\nfor url, filename in mappings:\n # Escape special regex characters in URL\n escaped_url = re.escape(url)\n # Replace markdown image syntax:  -> ![[path/filename]]\n pattern = r'!\\[([^\\]]*)\\]\\(' + escaped_url + r'\\)'\n replacement = f'![[05_Attachments/nanobanana-pro/{filename}]]'\n new_content, count = re.subn(pattern, replacement, content)\n if count > 0:\n content = new_content\n replacements_made += count\n print(f\"✓ Replaced {count}x: {filename}\")\n\n# Write back\nwith open(note_path, 'w', encoding='utf-8') as f:\n f.write(content)\n\nprint(f\"\\nTotal replacements: {replacements_made}\")\nPYEOF\npython3 .replace_urls.py)",
|
||||
"Bash(cat > .fix_links.py << 'PYEOF'\n#!/usr/bin/env python3\nimport re\n\nnote_path = \"00_Inbox/Clippings/2026/01/ZeroLuawesome-nanobanana-pro 🚀 An awesome list of curated Nano Banana pro prompts and examples. Your go-to resource for mastering prompt engineering and exploring the creative potential of the Nano banana pro(Nano banana 2) AI image model..md\"\n\nwith open(note_path, 'r', encoding='utf-8') as f:\n content = f.read()\n\n# Pattern: [![[wikilink]]](url) -> ![[wikilink]]\n# Remove the clickable link wrapper around wikilinks\npattern = r'\\[(!\\[\\[05_Attachments/nanobanana-pro/[^\\]]+\\]\\])\\]\\([^)]+\\)'\nreplacement = r'\\1'\nnew_content, count = re.subn(pattern, replacement, content)\n\nprint(f\"Fixed {count} clickable link wrappers\")\n\nwith open(note_path, 'w', encoding='utf-8') as f:\n f.write(new_content)\nPYEOF\npython3 .fix_links.py)",
|
||||
"Bash(ls -lh 05_Attachments/nanobanana-pro/ | awk '{if ($5 ~ /^[0-9]+$/ && $5 < 1024) print $9, $5}' | head -20)",
|
||||
"Bash(find:*)",
|
||||
"Bash(find 05_Attachments/nanobanana-pro/ -type f \\( -size 0 -o -size -100c \\) | sort > .failed_images.txt && head -10 .failed_images.txt)",
|
||||
"Bash(cat > .fix_conflicts.py << 'EOF'\n#!/usr/bin/env python3\nimport re\n\n# Read the file\nwith open\\('.obsidian/plugins/claudian/main.js', 'r'\\) as f:\n content = f.read\\(\\)\n\n# Fix first conflict \\(line ~2076\\) - use origin/main version \\(simpler path\\)\npattern1 = r'<<<<<<< HEAD\\\\n// node_modules/@modelcontextprotocol/sdk/node_modules/ajv/node_modules/json-schema-traverse/index\\\\.js\\\\nvar require_json_schema_traverse = __commonJS\\\\\\(\\\\{\\\\n \"node_modules/@modelcontextprotocol/sdk/node_modules/ajv/node_modules/json-schema-traverse/index\\\\.js\"\\\\\\(exports, module2\\\\\\) \\\\{\\\\n=======\\\\n// node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse/index\\\\.js\\\\nvar require_json_schema_traverse = __commonJS\\\\\\(\\\\{\\\\n \"node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse/index\\\\.js\"\\\\\\(exports, module2\\\\\\) \\\\{\\\\n>>>>>>> origin/main'\n\nreplacement1 = '''// node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse/index.js\nvar require_json_schema_traverse = __commonJS\\({\n \"node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse/index.js\"\\(exports, module2\\) {'''\n\ncontent = re.sub\\(pattern1, replacement1, content\\)\n\n# Fix second conflict \\(line ~8693\\) - use origin/main version \\(simpler path\\)\npattern2 = r'<<<<<<< HEAD\\\\n// node_modules/ajv-formats/node_modules/ajv/node_modules/json-schema-traverse/index\\\\.js\\\\nvar require_json_schema_traverse2 = __commonJS\\\\\\(\\\\{\\\\n \"node_modules/ajv-formats/node_modules/ajv/node_modules/json-schema-traverse/index\\\\.js\"\\\\\\(exports, module2\\\\\\) \\\\{\\\\n=======\\\\n// node_modules/ajv-formats/node_modules/json-schema-traverse/index\\\\.js\\\\nvar require_json_schema_traverse2 = __commonJS\\\\\\(\\\\{\\\\n \"node_modules/ajv-formats/node_modules/json-schema-traverse/index\\\\.js\"\\\\\\(exports, module2\\\\\\) \\\\{\\\\n>>>>>>> origin/main'\n\nreplacement2 = '''// node_modules/ajv-formats/node_modules/json-schema-traverse/index.js\nvar require_json_schema_traverse2 = __commonJS\\({\n \"node_modules/ajv-formats/node_modules/json-schema-traverse/index.js\"\\(exports, module2\\) {'''\n\ncontent = re.sub\\(pattern2, replacement2, content\\)\n\n# Write back\nwith open\\('.obsidian/plugins/claudian/main.js', 'w'\\) as f:\n f.write\\(content\\)\n\nprint\\(\"✓ Fixed both conflicts in main.js\"\\)\nEOF\npython3 .fix_conflicts.py)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nMerge branch ''origin/main'' into main\n\nResolved conflicts in:\n- .claude/settings.json \\(kept local permissions\\)\n- .obsidian/community-plugins.json \\(merged both plugin lists\\)\n- .obsidian/plugins/obsidian-excalidraw-plugin/manifest.json \\(kept newer version 2.20.2\\)\n- .obsidian/plugins/claudian/main.js \\(used simpler dependency paths\\)\n\nCo-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
|
||||
"Bash(git add:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
},
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "[ -f FIRST_RUN ] && echo '{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"\\n\\n# 🚀 Welcome to Claudesidian!\\n\\n**This appears to be your first time using this vault.**\\n\\n## Quick Start\\n\\nRun the setup wizard:\\n\\n⬇\\n/init-bootstrap\\n⬆\\n\\n## What this will do:\\n\\n✅ Set up your personalized configuration\\n✅ Disconnect from the original repository\\n✅ Help you import any existing Obsidian vault\\n✅ Configure your preferred workflow\\n✅ Create your PARA folder structure\\n\\nThe setup wizard will guide you through everything!\\n\\n\"}}' || true"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "npm run check-updates --silent 2>/dev/null || true"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/skill-discovery.sh",
|
||||
"timeout": 5000,
|
||||
"type": "command"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: defuddle
|
||||
description: Extract clean markdown content from web pages using Defuddle CLI, removing clutter and navigation to save tokens. Use instead of WebFetch when the user provides a URL to read or analyze, for online documentation, articles, blog posts, or any standard web page.
|
||||
---
|
||||
|
||||
# Defuddle
|
||||
|
||||
Use Defuddle CLI to extract clean readable content from web pages. Prefer over WebFetch for standard web pages — it removes navigation, ads, and clutter, reducing token usage.
|
||||
|
||||
If not installed: `npm install -g defuddle-cli`
|
||||
|
||||
## Usage
|
||||
|
||||
Always use `--md` for markdown output:
|
||||
|
||||
```bash
|
||||
defuddle parse <url> --md
|
||||
```
|
||||
|
||||
Save to file:
|
||||
|
||||
```bash
|
||||
defuddle parse <url> --md -o content.md
|
||||
```
|
||||
|
||||
Extract specific metadata:
|
||||
|
||||
```bash
|
||||
defuddle parse <url> -p title
|
||||
defuddle parse <url> -p description
|
||||
defuddle parse <url> -p domain
|
||||
```
|
||||
|
||||
## Output formats
|
||||
|
||||
| Flag | Format |
|
||||
|------|--------|
|
||||
| `--md` | Markdown (default choice) |
|
||||
| `--json` | JSON with both HTML and markdown |
|
||||
| (none) | HTML |
|
||||
| `-p <name>` | Specific metadata property |
|
||||
@@ -0,0 +1,656 @@
|
||||
---
|
||||
name: json-canvas
|
||||
description: Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.
|
||||
---
|
||||
|
||||
# JSON Canvas Skill
|
||||
|
||||
This skill enables skills-compatible agents to create and edit valid JSON Canvas files (`.canvas`) used in Obsidian and other applications.
|
||||
|
||||
## Overview
|
||||
|
||||
JSON Canvas is an open file format for infinite canvas data. Canvas files use the `.canvas` extension and contain valid JSON following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/).
|
||||
|
||||
## File Structure
|
||||
|
||||
A canvas file contains two top-level arrays:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [],
|
||||
"edges": []
|
||||
}
|
||||
```
|
||||
|
||||
- `nodes` (optional): Array of node objects
|
||||
- `edges` (optional): Array of edge objects connecting nodes
|
||||
|
||||
## Nodes
|
||||
|
||||
Nodes are objects placed on the canvas. There are four node types:
|
||||
- `text` - Text content with Markdown
|
||||
- `file` - Reference to files/attachments
|
||||
- `link` - External URL
|
||||
- `group` - Visual container for other nodes
|
||||
|
||||
### Z-Index Ordering
|
||||
|
||||
Nodes are ordered by z-index in the array:
|
||||
- First node = bottom layer (displayed below others)
|
||||
- Last node = top layer (displayed above others)
|
||||
|
||||
### Generic Node Attributes
|
||||
|
||||
All nodes share these attributes:
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `id` | Yes | string | Unique identifier for the node |
|
||||
| `type` | Yes | string | Node type: `text`, `file`, `link`, or `group` |
|
||||
| `x` | Yes | integer | X position in pixels |
|
||||
| `y` | Yes | integer | Y position in pixels |
|
||||
| `width` | Yes | integer | Width in pixels |
|
||||
| `height` | Yes | integer | Height in pixels |
|
||||
| `color` | No | canvasColor | Node color (see Color section) |
|
||||
|
||||
### Text Nodes
|
||||
|
||||
Text nodes contain Markdown content.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "6f0ad84f44ce9c17",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"text": "# Hello World\n\nThis is **Markdown** content."
|
||||
}
|
||||
```
|
||||
|
||||
#### Newline Escaping (Common Pitfall)
|
||||
|
||||
In JSON, newline characters inside strings **must** be represented as `\n`. Do **not** use the literal sequence `\\n` in a `.canvas` file—Obsidian will render it as the characters `\` and `n` instead of a line break.
|
||||
|
||||
Examples:
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "Line 1\nLine 2" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "Line 1\\nLine 2" }
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `text` | Yes | string | Plain text with Markdown syntax |
|
||||
|
||||
### File Nodes
|
||||
|
||||
File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4e5f67890",
|
||||
"type": "file",
|
||||
"x": 500,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"file": "Attachments/diagram.png"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "b2c3d4e5f6789012",
|
||||
"type": "file",
|
||||
"x": 500,
|
||||
"y": 400,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"file": "Notes/Project Overview.md",
|
||||
"subpath": "#Implementation"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `file` | Yes | string | Path to file within the system |
|
||||
| `subpath` | No | string | Link to heading or block (starts with `#`) |
|
||||
|
||||
### Link Nodes
|
||||
|
||||
Link nodes display external URLs.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "c3d4e5f678901234",
|
||||
"type": "link",
|
||||
"x": 1000,
|
||||
"y": 0,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"url": "https://obsidian.md"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `url` | Yes | string | External URL |
|
||||
|
||||
### Group Nodes
|
||||
|
||||
Group nodes are visual containers for organizing other nodes.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "d4e5f6789012345a",
|
||||
"type": "group",
|
||||
"x": -50,
|
||||
"y": -50,
|
||||
"width": 1000,
|
||||
"height": 600,
|
||||
"label": "Project Overview",
|
||||
"color": "4"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "e5f67890123456ab",
|
||||
"type": "group",
|
||||
"x": 0,
|
||||
"y": 700,
|
||||
"width": 800,
|
||||
"height": 500,
|
||||
"label": "Resources",
|
||||
"background": "Attachments/background.png",
|
||||
"backgroundStyle": "cover"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `label` | No | string | Text label for the group |
|
||||
| `background` | No | string | Path to background image |
|
||||
| `backgroundStyle` | No | string | Background rendering style |
|
||||
|
||||
#### Background Styles
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `cover` | Fills entire width and height of node |
|
||||
| `ratio` | Maintains aspect ratio of background image |
|
||||
| `repeat` | Repeats image as pattern in both directions |
|
||||
|
||||
## Edges
|
||||
|
||||
Edges are lines connecting nodes.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "f67890123456789a",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"toNode": "a1b2c3d4e5f67890"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "0123456789abcdef",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"fromSide": "right",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b2c3d4e5f6789012",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "1",
|
||||
"label": "leads to"
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Default | Description |
|
||||
|-----------|----------|------|---------|-------------|
|
||||
| `id` | Yes | string | - | Unique identifier for the edge |
|
||||
| `fromNode` | Yes | string | - | Node ID where connection starts |
|
||||
| `fromSide` | No | string | - | Side where edge starts |
|
||||
| `fromEnd` | No | string | `none` | Shape at edge start |
|
||||
| `toNode` | Yes | string | - | Node ID where connection ends |
|
||||
| `toSide` | No | string | - | Side where edge ends |
|
||||
| `toEnd` | No | string | `arrow` | Shape at edge end |
|
||||
| `color` | No | canvasColor | - | Line color |
|
||||
| `label` | No | string | - | Text label for the edge |
|
||||
|
||||
### Side Values
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `top` | Top edge of node |
|
||||
| `right` | Right edge of node |
|
||||
| `bottom` | Bottom edge of node |
|
||||
| `left` | Left edge of node |
|
||||
|
||||
### End Shapes
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `none` | No endpoint shape |
|
||||
| `arrow` | Arrow endpoint |
|
||||
|
||||
## Colors
|
||||
|
||||
The `canvasColor` type can be specified in two ways:
|
||||
|
||||
### Hex Colors
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "#FF0000"
|
||||
}
|
||||
```
|
||||
|
||||
### Preset Colors
|
||||
|
||||
```json
|
||||
{
|
||||
"color": "1"
|
||||
}
|
||||
```
|
||||
|
||||
| Preset | Color |
|
||||
|--------|-------|
|
||||
| `"1"` | Red |
|
||||
| `"2"` | Orange |
|
||||
| `"3"` | Yellow |
|
||||
| `"4"` | Green |
|
||||
| `"5"` | Cyan |
|
||||
| `"6"` | Purple |
|
||||
|
||||
Note: Specific color values for presets are intentionally undefined, allowing applications to use their own brand colors.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Simple Canvas with Text and Connections
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "8a9b0c1d2e3f4a5b",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 150,
|
||||
"text": "# Main Idea\n\nThis is the central concept."
|
||||
},
|
||||
{
|
||||
"id": "1a2b3c4d5e6f7a8b",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": -100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point A\n\nDetails here."
|
||||
},
|
||||
{
|
||||
"id": "2b3c4d5e6f7a8b9c",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 100,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"text": "## Supporting Point B\n\nMore details."
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "3c4d5e6f7a8b9c0d",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1a2b3c4d5e6f7a8b",
|
||||
"toSide": "left"
|
||||
},
|
||||
{
|
||||
"id": "4d5e6f7a8b9c0d1e",
|
||||
"fromNode": "8a9b0c1d2e3f4a5b",
|
||||
"fromSide": "right",
|
||||
"toNode": "2b3c4d5e6f7a8b9c",
|
||||
"toSide": "left"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Project Board with Groups
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "5e6f7a8b9c0d1e2f",
|
||||
"type": "group",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "To Do",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "6f7a8b9c0d1e2f3a",
|
||||
"type": "group",
|
||||
"x": 350,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "In Progress",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "7a8b9c0d1e2f3a4b",
|
||||
"type": "group",
|
||||
"x": 700,
|
||||
"y": 0,
|
||||
"width": 300,
|
||||
"height": 500,
|
||||
"label": "Done",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "8b9c0d1e2f3a4b5c",
|
||||
"type": "text",
|
||||
"x": 20,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 1\n\nImplement feature X"
|
||||
},
|
||||
{
|
||||
"id": "9c0d1e2f3a4b5c6d",
|
||||
"type": "text",
|
||||
"x": 370,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 2\n\nReview PR #123",
|
||||
"color": "2"
|
||||
},
|
||||
{
|
||||
"id": "0d1e2f3a4b5c6d7e",
|
||||
"type": "text",
|
||||
"x": 720,
|
||||
"y": 50,
|
||||
"width": 260,
|
||||
"height": 80,
|
||||
"text": "## Task 3\n\n~~Setup CI/CD~~"
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
```
|
||||
|
||||
### Research Canvas with Files and Links
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "1e2f3a4b5c6d7e8f",
|
||||
"type": "text",
|
||||
"x": 300,
|
||||
"y": 200,
|
||||
"width": 400,
|
||||
"height": 200,
|
||||
"text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?",
|
||||
"color": "5"
|
||||
},
|
||||
{
|
||||
"id": "2f3a4b5c6d7e8f9a",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Literature/Paper A.pdf"
|
||||
},
|
||||
{
|
||||
"id": "3a4b5c6d7e8f9a0b",
|
||||
"type": "file",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 250,
|
||||
"height": 150,
|
||||
"file": "Notes/Meeting Notes.md",
|
||||
"subpath": "#Key Insights"
|
||||
},
|
||||
{
|
||||
"id": "4b5c6d7e8f9a0b1c",
|
||||
"type": "link",
|
||||
"x": 0,
|
||||
"y": 400,
|
||||
"width": 250,
|
||||
"height": 100,
|
||||
"url": "https://example.com/research"
|
||||
},
|
||||
{
|
||||
"id": "5c6d7e8f9a0b1c2d",
|
||||
"type": "file",
|
||||
"x": 750,
|
||||
"y": 150,
|
||||
"width": 300,
|
||||
"height": 250,
|
||||
"file": "Attachments/diagram.png"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "6d7e8f9a0b1c2d3e",
|
||||
"fromNode": "2f3a4b5c6d7e8f9a",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "supports"
|
||||
},
|
||||
{
|
||||
"id": "7e8f9a0b1c2d3e4f",
|
||||
"fromNode": "3a4b5c6d7e8f9a0b",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"label": "informs"
|
||||
},
|
||||
{
|
||||
"id": "8f9a0b1c2d3e4f5a",
|
||||
"fromNode": "4b5c6d7e8f9a0b1c",
|
||||
"fromSide": "right",
|
||||
"toNode": "1e2f3a4b5c6d7e8f",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow",
|
||||
"color": "6"
|
||||
},
|
||||
{
|
||||
"id": "9a0b1c2d3e4f5a6b",
|
||||
"fromNode": "1e2f3a4b5c6d7e8f",
|
||||
"fromSide": "right",
|
||||
"toNode": "5c6d7e8f9a0b1c2d",
|
||||
"toSide": "left",
|
||||
"label": "visualized by"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Flowchart
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "a0b1c2d3e4f5a6b7",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 0,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**Start**",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "b1c2d3e4f5a6b7c8",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 100,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Step 1:\nGather data"
|
||||
},
|
||||
{
|
||||
"id": "c2d3e4f5a6b7c8d9",
|
||||
"type": "text",
|
||||
"x": 200,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"text": "**Decision**\n\nIs data valid?",
|
||||
"color": "3"
|
||||
},
|
||||
{
|
||||
"id": "d3e4f5a6b7c8d9e0",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Process data"
|
||||
},
|
||||
{
|
||||
"id": "e4f5a6b7c8d9e0f1",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 200,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "Request new data",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "f5a6b7c8d9e0f1a2",
|
||||
"type": "text",
|
||||
"x": 400,
|
||||
"y": 320,
|
||||
"width": 150,
|
||||
"height": 60,
|
||||
"text": "**End**",
|
||||
"color": "4"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "a6b7c8d9e0f1a2b3",
|
||||
"fromNode": "a0b1c2d3e4f5a6b7",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "b7c8d9e0f1a2b3c4",
|
||||
"fromNode": "b1c2d3e4f5a6b7c8",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "c2d3e4f5a6b7c8d9",
|
||||
"toSide": "top"
|
||||
},
|
||||
{
|
||||
"id": "c8d9e0f1a2b3c4d5",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "right",
|
||||
"toNode": "d3e4f5a6b7c8d9e0",
|
||||
"toSide": "left",
|
||||
"label": "Yes",
|
||||
"color": "4"
|
||||
},
|
||||
{
|
||||
"id": "d9e0f1a2b3c4d5e6",
|
||||
"fromNode": "c2d3e4f5a6b7c8d9",
|
||||
"fromSide": "left",
|
||||
"toNode": "e4f5a6b7c8d9e0f1",
|
||||
"toSide": "right",
|
||||
"label": "No",
|
||||
"color": "1"
|
||||
},
|
||||
{
|
||||
"id": "e0f1a2b3c4d5e6f7",
|
||||
"fromNode": "e4f5a6b7c8d9e0f1",
|
||||
"fromSide": "top",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b1c2d3e4f5a6b7c8",
|
||||
"toSide": "left",
|
||||
"toEnd": "arrow"
|
||||
},
|
||||
{
|
||||
"id": "f1a2b3c4d5e6f7a8",
|
||||
"fromNode": "d3e4f5a6b7c8d9e0",
|
||||
"fromSide": "bottom",
|
||||
"toNode": "f5a6b7c8d9e0f1a2",
|
||||
"toSide": "top"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## ID Generation
|
||||
|
||||
Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
|
||||
|
||||
```json
|
||||
"id": "6f0ad84f44ce9c17"
|
||||
"id": "a3b2c1d0e9f8g7h6"
|
||||
"id": "1234567890abcdef"
|
||||
```
|
||||
|
||||
This format is a 16-character lowercase hex string (64-bit random value).
|
||||
|
||||
## Layout Guidelines
|
||||
|
||||
### Positioning
|
||||
|
||||
- Coordinates can be negative (canvas extends infinitely)
|
||||
- `x` increases to the right
|
||||
- `y` increases downward
|
||||
- Position refers to top-left corner of node
|
||||
|
||||
### Recommended Sizes
|
||||
|
||||
| Node Type | Suggested Width | Suggested Height |
|
||||
|-----------|-----------------|------------------|
|
||||
| Small text | 200-300 | 80-150 |
|
||||
| Medium text | 300-450 | 150-300 |
|
||||
| Large text | 400-600 | 300-500 |
|
||||
| File preview | 300-500 | 200-400 |
|
||||
| Link preview | 250-400 | 100-200 |
|
||||
| Group | Varies | Varies |
|
||||
|
||||
### Spacing
|
||||
|
||||
- Leave 20-50px padding inside groups
|
||||
- Space nodes 50-100px apart for readability
|
||||
- Align nodes to grid (multiples of 10 or 20) for cleaner layouts
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. All `id` values must be unique across nodes and edges
|
||||
2. `fromNode` and `toNode` must reference existing node IDs
|
||||
3. Required fields must be present for each node type
|
||||
4. `type` must be one of: `text`, `file`, `link`, `group`
|
||||
5. `backgroundStyle` must be one of: `cover`, `ratio`, `repeat`
|
||||
6. `fromSide`, `toSide` must be one of: `top`, `right`, `bottom`, `left`
|
||||
7. `fromEnd`, `toEnd` must be one of: `none`, `arrow`
|
||||
8. Color presets must be `"1"` through `"6"` or valid hex color
|
||||
|
||||
## References
|
||||
|
||||
- [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/)
|
||||
- [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas)
|
||||
@@ -0,0 +1,651 @@
|
||||
---
|
||||
name: obsidian-bases
|
||||
description: Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
|
||||
---
|
||||
|
||||
# Obsidian Bases Skill
|
||||
|
||||
This skill enables skills-compatible agents to create and edit valid Obsidian Bases (`.base` files) including views, filters, formulas, and all related configurations.
|
||||
|
||||
## Overview
|
||||
|
||||
Obsidian Bases are YAML-based files that define dynamic views of notes in an Obsidian vault. A Base file can contain multiple views, global filters, formulas, property configurations, and custom summaries.
|
||||
|
||||
## File Format
|
||||
|
||||
Base files use the `.base` extension and contain valid YAML. They can also be embedded in Markdown code blocks.
|
||||
|
||||
## Complete Schema
|
||||
|
||||
```yaml
|
||||
# Global filters apply to ALL views in the base
|
||||
filters:
|
||||
# Can be a single filter string
|
||||
# OR a recursive filter object with and/or/not
|
||||
and: []
|
||||
or: []
|
||||
not: []
|
||||
|
||||
# Define formula properties that can be used across all views
|
||||
formulas:
|
||||
formula_name: 'expression'
|
||||
|
||||
# Configure display names and settings for properties
|
||||
properties:
|
||||
property_name:
|
||||
displayName: "Display Name"
|
||||
formula.formula_name:
|
||||
displayName: "Formula Display Name"
|
||||
file.ext:
|
||||
displayName: "Extension"
|
||||
|
||||
# Define custom summary formulas
|
||||
summaries:
|
||||
custom_summary_name: 'values.mean().round(3)'
|
||||
|
||||
# Define one or more views
|
||||
views:
|
||||
- type: table | cards | list | map
|
||||
name: "View Name"
|
||||
limit: 10 # Optional: limit results
|
||||
groupBy: # Optional: group results
|
||||
property: property_name
|
||||
direction: ASC | DESC
|
||||
filters: # View-specific filters
|
||||
and: []
|
||||
order: # Properties to display in order
|
||||
- file.name
|
||||
- property_name
|
||||
- formula.formula_name
|
||||
summaries: # Map properties to summary formulas
|
||||
property_name: Average
|
||||
```
|
||||
|
||||
## Filter Syntax
|
||||
|
||||
Filters narrow down results. They can be applied globally or per-view.
|
||||
|
||||
### Filter Structure
|
||||
|
||||
```yaml
|
||||
# Single filter
|
||||
filters: 'status == "done"'
|
||||
|
||||
# AND - all conditions must be true
|
||||
filters:
|
||||
and:
|
||||
- 'status == "done"'
|
||||
- 'priority > 3'
|
||||
|
||||
# OR - any condition can be true
|
||||
filters:
|
||||
or:
|
||||
- 'file.hasTag("book")'
|
||||
- 'file.hasTag("article")'
|
||||
|
||||
# NOT - exclude matching items
|
||||
filters:
|
||||
not:
|
||||
- 'file.hasTag("archived")'
|
||||
|
||||
# Nested filters
|
||||
filters:
|
||||
or:
|
||||
- file.hasTag("tag")
|
||||
- and:
|
||||
- file.hasTag("book")
|
||||
- file.hasLink("Textbook")
|
||||
- not:
|
||||
- file.hasTag("book")
|
||||
- file.inFolder("Required Reading")
|
||||
```
|
||||
|
||||
### Filter Operators
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `==` | equals |
|
||||
| `!=` | not equal |
|
||||
| `>` | greater than |
|
||||
| `<` | less than |
|
||||
| `>=` | greater than or equal |
|
||||
| `<=` | less than or equal |
|
||||
| `&&` | logical and |
|
||||
| `\|\|` | logical or |
|
||||
| <code>!</code> | logical not |
|
||||
|
||||
## Properties
|
||||
|
||||
### Three Types of Properties
|
||||
|
||||
1. **Note properties** - From frontmatter: `note.author` or just `author`
|
||||
2. **File properties** - File metadata: `file.name`, `file.mtime`, etc.
|
||||
3. **Formula properties** - Computed values: `formula.my_formula`
|
||||
|
||||
### File Properties Reference
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `file.name` | String | File name |
|
||||
| `file.basename` | String | File name without extension |
|
||||
| `file.path` | String | Full path to file |
|
||||
| `file.folder` | String | Parent folder path |
|
||||
| `file.ext` | String | File extension |
|
||||
| `file.size` | Number | File size in bytes |
|
||||
| `file.ctime` | Date | Created time |
|
||||
| `file.mtime` | Date | Modified time |
|
||||
| `file.tags` | List | All tags in file |
|
||||
| `file.links` | List | Internal links in file |
|
||||
| `file.backlinks` | List | Files linking to this file |
|
||||
| `file.embeds` | List | Embeds in the note |
|
||||
| `file.properties` | Object | All frontmatter properties |
|
||||
|
||||
### The `this` Keyword
|
||||
|
||||
- In main content area: refers to the base file itself
|
||||
- When embedded: refers to the embedding file
|
||||
- In sidebar: refers to the active file in main content
|
||||
|
||||
## Formula Syntax
|
||||
|
||||
Formulas compute values from properties. Defined in the `formulas` section.
|
||||
|
||||
```yaml
|
||||
formulas:
|
||||
# Simple arithmetic
|
||||
total: "price * quantity"
|
||||
|
||||
# Conditional logic
|
||||
status_icon: 'if(done, "✅", "⏳")'
|
||||
|
||||
# String formatting
|
||||
formatted_price: 'if(price, price.toFixed(2) + " dollars")'
|
||||
|
||||
# Date formatting
|
||||
created: 'file.ctime.format("YYYY-MM-DD")'
|
||||
|
||||
# Calculate days since created (use .days for Duration)
|
||||
days_old: '(now() - file.ctime).days'
|
||||
|
||||
# Calculate days until due date
|
||||
days_until_due: 'if(due_date, (date(due_date) - today()).days, "")'
|
||||
```
|
||||
|
||||
## Functions Reference
|
||||
|
||||
### Global Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `date()` | `date(string): date` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
|
||||
| `duration()` | `duration(string): duration` | Parse duration string |
|
||||
| `now()` | `now(): date` | Current date and time |
|
||||
| `today()` | `today(): date` | Current date (time = 00:00:00) |
|
||||
| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
|
||||
| `min()` | `min(n1, n2, ...): number` | Smallest number |
|
||||
| `max()` | `max(n1, n2, ...): number` | Largest number |
|
||||
| `number()` | `number(any): number` | Convert to number |
|
||||
| `link()` | `link(path, display?): Link` | Create a link |
|
||||
| `list()` | `list(element): List` | Wrap in list if not already |
|
||||
| `file()` | `file(path): file` | Get file object |
|
||||
| `image()` | `image(path): image` | Create image for rendering |
|
||||
| `icon()` | `icon(name): icon` | Lucide icon by name |
|
||||
| `html()` | `html(string): html` | Render as HTML |
|
||||
| `escapeHTML()` | `escapeHTML(string): string` | Escape HTML characters |
|
||||
|
||||
### Any Type Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `isTruthy()` | `any.isTruthy(): boolean` | Coerce to boolean |
|
||||
| `isType()` | `any.isType(type): boolean` | Check type |
|
||||
| `toString()` | `any.toString(): string` | Convert to string |
|
||||
|
||||
### Date Functions & Fields
|
||||
|
||||
**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `date()` | `date.date(): date` | Remove time portion |
|
||||
| `format()` | `date.format(string): string` | Format with Moment.js pattern |
|
||||
| `time()` | `date.time(): string` | Get time as string |
|
||||
| `relative()` | `date.relative(): string` | Human-readable relative time |
|
||||
| `isEmpty()` | `date.isEmpty(): boolean` | Always false for dates |
|
||||
|
||||
### Duration Type
|
||||
|
||||
When subtracting two dates, the result is a **Duration** type (not a number). Duration has its own properties and methods.
|
||||
|
||||
**Duration Fields:**
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `duration.days` | Number | Total days in duration |
|
||||
| `duration.hours` | Number | Total hours in duration |
|
||||
| `duration.minutes` | Number | Total minutes in duration |
|
||||
| `duration.seconds` | Number | Total seconds in duration |
|
||||
| `duration.milliseconds` | Number | Total milliseconds in duration |
|
||||
|
||||
**IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. You must access a numeric field first (like `.days`), then apply number functions.
|
||||
|
||||
```yaml
|
||||
# CORRECT: Calculate days between dates
|
||||
"(date(due_date) - today()).days" # Returns number of days
|
||||
"(now() - file.ctime).days" # Days since created
|
||||
|
||||
# CORRECT: Round the numeric result if needed
|
||||
"(date(due_date) - today()).days.round(0)" # Rounded days
|
||||
"(now() - file.ctime).hours.round(0)" # Rounded hours
|
||||
|
||||
# WRONG - will cause error:
|
||||
# "((date(due) - today()) / 86400000).round(0)" # Duration doesn't support division then round
|
||||
```
|
||||
|
||||
### Date Arithmetic
|
||||
|
||||
```yaml
|
||||
# Duration units: y/year/years, M/month/months, d/day/days,
|
||||
# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
|
||||
|
||||
# Add/subtract durations
|
||||
"date + \"1M\"" # Add 1 month
|
||||
"date - \"2h\"" # Subtract 2 hours
|
||||
"now() + \"1 day\"" # Tomorrow
|
||||
"today() + \"7d\"" # A week from today
|
||||
|
||||
# Subtract dates returns Duration type
|
||||
"now() - file.ctime" # Returns Duration
|
||||
"(now() - file.ctime).days" # Get days as number
|
||||
"(now() - file.ctime).hours" # Get hours as number
|
||||
|
||||
# Complex duration arithmetic
|
||||
"now() + (duration('1d') * 2)"
|
||||
```
|
||||
|
||||
### String Functions
|
||||
|
||||
**Field:** `string.length`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `contains()` | `string.contains(value): boolean` | Check substring |
|
||||
| `containsAll()` | `string.containsAll(...values): boolean` | All substrings present |
|
||||
| `containsAny()` | `string.containsAny(...values): boolean` | Any substring present |
|
||||
| `startsWith()` | `string.startsWith(query): boolean` | Starts with query |
|
||||
| `endsWith()` | `string.endsWith(query): boolean` | Ends with query |
|
||||
| `isEmpty()` | `string.isEmpty(): boolean` | Empty or not present |
|
||||
| `lower()` | `string.lower(): string` | To lowercase |
|
||||
| `title()` | `string.title(): string` | To Title Case |
|
||||
| `trim()` | `string.trim(): string` | Remove whitespace |
|
||||
| `replace()` | `string.replace(pattern, replacement): string` | Replace pattern |
|
||||
| `repeat()` | `string.repeat(count): string` | Repeat string |
|
||||
| `reverse()` | `string.reverse(): string` | Reverse string |
|
||||
| `slice()` | `string.slice(start, end?): string` | Substring |
|
||||
| `split()` | `string.split(separator, n?): list` | Split to list |
|
||||
|
||||
### Number Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `abs()` | `number.abs(): number` | Absolute value |
|
||||
| `ceil()` | `number.ceil(): number` | Round up |
|
||||
| `floor()` | `number.floor(): number` | Round down |
|
||||
| `round()` | `number.round(digits?): number` | Round to digits |
|
||||
| `toFixed()` | `number.toFixed(precision): string` | Fixed-point notation |
|
||||
| `isEmpty()` | `number.isEmpty(): boolean` | Not present |
|
||||
|
||||
### List Functions
|
||||
|
||||
**Field:** `list.length`
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `contains()` | `list.contains(value): boolean` | Element exists |
|
||||
| `containsAll()` | `list.containsAll(...values): boolean` | All elements exist |
|
||||
| `containsAny()` | `list.containsAny(...values): boolean` | Any element exists |
|
||||
| `filter()` | `list.filter(expression): list` | Filter by condition (uses `value`, `index`) |
|
||||
| `map()` | `list.map(expression): list` | Transform elements (uses `value`, `index`) |
|
||||
| `reduce()` | `list.reduce(expression, initial): any` | Reduce to single value (uses `value`, `index`, `acc`) |
|
||||
| `flat()` | `list.flat(): list` | Flatten nested lists |
|
||||
| `join()` | `list.join(separator): string` | Join to string |
|
||||
| `reverse()` | `list.reverse(): list` | Reverse order |
|
||||
| `slice()` | `list.slice(start, end?): list` | Sublist |
|
||||
| `sort()` | `list.sort(): list` | Sort ascending |
|
||||
| `unique()` | `list.unique(): list` | Remove duplicates |
|
||||
| `isEmpty()` | `list.isEmpty(): boolean` | No elements |
|
||||
|
||||
### File Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `asLink()` | `file.asLink(display?): Link` | Convert to link |
|
||||
| `hasLink()` | `file.hasLink(otherFile): boolean` | Has link to file |
|
||||
| `hasTag()` | `file.hasTag(...tags): boolean` | Has any of the tags |
|
||||
| `hasProperty()` | `file.hasProperty(name): boolean` | Has property |
|
||||
| `inFolder()` | `file.inFolder(folder): boolean` | In folder or subfolder |
|
||||
|
||||
### Link Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `asFile()` | `link.asFile(): file` | Get file object |
|
||||
| `linksTo()` | `link.linksTo(file): boolean` | Links to file |
|
||||
|
||||
### Object Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `isEmpty()` | `object.isEmpty(): boolean` | No properties |
|
||||
| `keys()` | `object.keys(): list` | List of keys |
|
||||
| `values()` | `object.values(): list` | List of values |
|
||||
|
||||
### Regular Expression Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `matches()` | `regexp.matches(string): boolean` | Test if matches |
|
||||
|
||||
## View Types
|
||||
|
||||
### Table View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: table
|
||||
name: "My Table"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- due_date
|
||||
summaries:
|
||||
price: Sum
|
||||
count: Average
|
||||
```
|
||||
|
||||
### Cards View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: cards
|
||||
name: "Gallery"
|
||||
order:
|
||||
- file.name
|
||||
- cover_image
|
||||
- description
|
||||
```
|
||||
|
||||
### List View
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: list
|
||||
name: "Simple List"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
```
|
||||
|
||||
### Map View
|
||||
|
||||
Requires latitude/longitude properties and the Maps community plugin.
|
||||
|
||||
```yaml
|
||||
views:
|
||||
- type: map
|
||||
name: "Locations"
|
||||
# Map-specific settings for lat/lng properties
|
||||
```
|
||||
|
||||
## Default Summary Formulas
|
||||
|
||||
| Name | Input Type | Description |
|
||||
|------|------------|-------------|
|
||||
| `Average` | Number | Mathematical mean |
|
||||
| `Min` | Number | Smallest number |
|
||||
| `Max` | Number | Largest number |
|
||||
| `Sum` | Number | Sum of all numbers |
|
||||
| `Range` | Number | Max - Min |
|
||||
| `Median` | Number | Mathematical median |
|
||||
| `Stddev` | Number | Standard deviation |
|
||||
| `Earliest` | Date | Earliest date |
|
||||
| `Latest` | Date | Latest date |
|
||||
| `Range` | Date | Latest - Earliest |
|
||||
| `Checked` | Boolean | Count of true values |
|
||||
| `Unchecked` | Boolean | Count of false values |
|
||||
| `Empty` | Any | Count of empty values |
|
||||
| `Filled` | Any | Count of non-empty values |
|
||||
| `Unique` | Any | Count of unique values |
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Task Tracker Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.hasTag("task")
|
||||
- 'file.ext == "md"'
|
||||
|
||||
formulas:
|
||||
days_until_due: 'if(due, (date(due) - today()).days, "")'
|
||||
is_overdue: 'if(due, date(due) < today() && status != "done", false)'
|
||||
priority_label: 'if(priority == 1, "🔴 High", if(priority == 2, "🟡 Medium", "🟢 Low"))'
|
||||
|
||||
properties:
|
||||
status:
|
||||
displayName: Status
|
||||
formula.days_until_due:
|
||||
displayName: "Days Until Due"
|
||||
formula.priority_label:
|
||||
displayName: Priority
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "Active Tasks"
|
||||
filters:
|
||||
and:
|
||||
- 'status != "done"'
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- formula.priority_label
|
||||
- due
|
||||
- formula.days_until_due
|
||||
groupBy:
|
||||
property: status
|
||||
direction: ASC
|
||||
summaries:
|
||||
formula.days_until_due: Average
|
||||
|
||||
- type: table
|
||||
name: "Completed"
|
||||
filters:
|
||||
and:
|
||||
- 'status == "done"'
|
||||
order:
|
||||
- file.name
|
||||
- completed_date
|
||||
```
|
||||
|
||||
### Reading List Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
or:
|
||||
- file.hasTag("book")
|
||||
- file.hasTag("article")
|
||||
|
||||
formulas:
|
||||
reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
|
||||
status_icon: 'if(status == "reading", "📖", if(status == "done", "✅", "📚"))'
|
||||
year_read: 'if(finished_date, date(finished_date).year, "")'
|
||||
|
||||
properties:
|
||||
author:
|
||||
displayName: Author
|
||||
formula.status_icon:
|
||||
displayName: ""
|
||||
formula.reading_time:
|
||||
displayName: "Est. Time"
|
||||
|
||||
views:
|
||||
- type: cards
|
||||
name: "Library"
|
||||
order:
|
||||
- cover
|
||||
- file.name
|
||||
- author
|
||||
- formula.status_icon
|
||||
filters:
|
||||
not:
|
||||
- 'status == "dropped"'
|
||||
|
||||
- type: table
|
||||
name: "Reading List"
|
||||
filters:
|
||||
and:
|
||||
- 'status == "to-read"'
|
||||
order:
|
||||
- file.name
|
||||
- author
|
||||
- pages
|
||||
- formula.reading_time
|
||||
```
|
||||
|
||||
### Project Notes Base
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Projects")
|
||||
- 'file.ext == "md"'
|
||||
|
||||
formulas:
|
||||
last_updated: 'file.mtime.relative()'
|
||||
link_count: 'file.links.length'
|
||||
|
||||
summaries:
|
||||
avgLinks: 'values.filter(value.isType("number")).mean().round(1)'
|
||||
|
||||
properties:
|
||||
formula.last_updated:
|
||||
displayName: "Updated"
|
||||
formula.link_count:
|
||||
displayName: "Links"
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "All Projects"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
- formula.last_updated
|
||||
- formula.link_count
|
||||
summaries:
|
||||
formula.link_count: avgLinks
|
||||
groupBy:
|
||||
property: status
|
||||
direction: ASC
|
||||
|
||||
- type: list
|
||||
name: "Quick List"
|
||||
order:
|
||||
- file.name
|
||||
- status
|
||||
```
|
||||
|
||||
### Daily Notes Index
|
||||
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Daily Notes")
|
||||
- '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'
|
||||
|
||||
formulas:
|
||||
word_estimate: '(file.size / 5).round(0)'
|
||||
day_of_week: 'date(file.basename).format("dddd")'
|
||||
|
||||
properties:
|
||||
formula.day_of_week:
|
||||
displayName: "Day"
|
||||
formula.word_estimate:
|
||||
displayName: "~Words"
|
||||
|
||||
views:
|
||||
- type: table
|
||||
name: "Recent Notes"
|
||||
limit: 30
|
||||
order:
|
||||
- file.name
|
||||
- formula.day_of_week
|
||||
- formula.word_estimate
|
||||
- file.mtime
|
||||
```
|
||||
|
||||
## Embedding Bases
|
||||
|
||||
Embed in Markdown files:
|
||||
|
||||
```markdown
|
||||
![[MyBase.base]]
|
||||
|
||||
<!-- Specific view -->
|
||||
![[MyBase.base#View Name]]
|
||||
```
|
||||
|
||||
## YAML Quoting Rules
|
||||
|
||||
- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
|
||||
- Use double quotes for simple strings: `"My View Name"`
|
||||
- Escape nested quotes properly in complex expressions
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Filter by Tag
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.hasTag("project")
|
||||
```
|
||||
|
||||
### Filter by Folder
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- file.inFolder("Notes")
|
||||
```
|
||||
|
||||
### Filter by Date Range
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- 'file.mtime > now() - "7d"'
|
||||
```
|
||||
|
||||
### Filter by Property Value
|
||||
```yaml
|
||||
filters:
|
||||
and:
|
||||
- 'status == "active"'
|
||||
- 'priority >= 3'
|
||||
```
|
||||
|
||||
### Combine Multiple Conditions
|
||||
```yaml
|
||||
filters:
|
||||
or:
|
||||
- and:
|
||||
- file.hasTag("important")
|
||||
- 'status != "done"'
|
||||
- and:
|
||||
- 'priority == 1'
|
||||
- 'due != ""'
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Bases Syntax](https://help.obsidian.md/bases/syntax)
|
||||
- [Functions](https://help.obsidian.md/bases/functions)
|
||||
- [Views](https://help.obsidian.md/bases/views)
|
||||
- [Formulas](https://help.obsidian.md/formulas)
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
name: obsidian-cli
|
||||
description: Interact with Obsidian vaults using the Obsidian CLI to read, create, search, and manage notes, tasks, properties, and more. Also supports plugin and theme development with commands to reload plugins, run JavaScript, capture errors, take screenshots, and inspect the DOM. Use when the user asks to interact with their Obsidian vault, manage notes, search vault content, perform vault operations from the command line, or develop and debug Obsidian plugins and themes.
|
||||
---
|
||||
|
||||
# Obsidian CLI
|
||||
|
||||
Use the `obsidian` CLI to interact with a running Obsidian instance. Requires Obsidian to be open.
|
||||
|
||||
## Command reference
|
||||
|
||||
Run `obsidian help` to see all available commands. This is always up to date. Full docs: https://help.obsidian.md/cli
|
||||
|
||||
## Syntax
|
||||
|
||||
**Parameters** take a value with `=`. Quote values with spaces:
|
||||
|
||||
```bash
|
||||
obsidian create name="My Note" content="Hello world"
|
||||
```
|
||||
|
||||
**Flags** are boolean switches with no value:
|
||||
|
||||
```bash
|
||||
obsidian create name="My Note" silent overwrite
|
||||
```
|
||||
|
||||
For multiline content use `\n` for newline and `\t` for tab.
|
||||
|
||||
## File targeting
|
||||
|
||||
Many commands accept `file` or `path` to target a file. Without either, the active file is used.
|
||||
|
||||
- `file=<name>` — resolves like a wikilink (name only, no path or extension needed)
|
||||
- `path=<path>` — exact path from vault root, e.g. `folder/note.md`
|
||||
|
||||
## Vault targeting
|
||||
|
||||
Commands target the most recently focused vault by default. Use `vault=<name>` as the first parameter to target a specific vault:
|
||||
|
||||
```bash
|
||||
obsidian vault="My Vault" search query="test"
|
||||
```
|
||||
|
||||
## Common patterns
|
||||
|
||||
```bash
|
||||
obsidian read file="My Note"
|
||||
obsidian create name="New Note" content="# Hello" template="Template" silent
|
||||
obsidian append file="My Note" content="New line"
|
||||
obsidian search query="search term" limit=10
|
||||
obsidian daily:read
|
||||
obsidian daily:append content="- [ ] New task"
|
||||
obsidian property:set name="status" value="done" file="My Note"
|
||||
obsidian tasks daily todo
|
||||
obsidian tags sort=count counts
|
||||
obsidian backlinks file="My Note"
|
||||
```
|
||||
|
||||
Use `--copy` on any command to copy output to clipboard. Use `silent` to prevent files from opening. Use `total` on list commands to get a count.
|
||||
|
||||
## Plugin development
|
||||
|
||||
Reload a plugin after code changes — essential for the develop/test cycle:
|
||||
|
||||
```bash
|
||||
obsidian plugin:reload id=my-plugin
|
||||
```
|
||||
|
||||
Run JavaScript in the app context:
|
||||
|
||||
```bash
|
||||
obsidian eval code="app.vault.getFiles().length"
|
||||
```
|
||||
|
||||
Check for errors and console output:
|
||||
|
||||
```bash
|
||||
obsidian dev:errors
|
||||
obsidian dev:console
|
||||
obsidian dev:console level=error
|
||||
```
|
||||
|
||||
Take a screenshot for visual testing:
|
||||
|
||||
```bash
|
||||
obsidian dev:screenshot path=screenshot.png
|
||||
```
|
||||
|
||||
Inspect DOM and CSS:
|
||||
|
||||
```bash
|
||||
obsidian dev:dom selector=".workspace-leaf" text
|
||||
obsidian dev:css selector=".workspace-leaf" prop=background-color
|
||||
```
|
||||
|
||||
Toggle mobile emulation:
|
||||
|
||||
```bash
|
||||
obsidian dev:mobile on
|
||||
```
|
||||
|
||||
Run `obsidian help` to see additional developer commands including CDP and debugger controls.
|
||||
@@ -0,0 +1,620 @@
|
||||
---
|
||||
name: obsidian-markdown
|
||||
description: Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
|
||||
---
|
||||
|
||||
# Obsidian Flavored Markdown Skill
|
||||
|
||||
This skill enables skills-compatible agents to create and edit valid Obsidian Flavored Markdown, including all Obsidian-specific syntax extensions.
|
||||
|
||||
## Overview
|
||||
|
||||
Obsidian uses a combination of Markdown flavors:
|
||||
- [CommonMark](https://commonmark.org/)
|
||||
- [GitHub Flavored Markdown](https://github.github.com/gfm/)
|
||||
- [LaTeX](https://www.latex-project.org/) for math
|
||||
- Obsidian-specific extensions (wikilinks, callouts, embeds, etc.)
|
||||
|
||||
## Basic Formatting
|
||||
|
||||
### Paragraphs and Line Breaks
|
||||
|
||||
```markdown
|
||||
This is a paragraph.
|
||||
|
||||
This is another paragraph (blank line between creates separate paragraphs).
|
||||
|
||||
For a line break within a paragraph, add two spaces at the end
|
||||
or use Shift+Enter.
|
||||
```
|
||||
|
||||
### Headings
|
||||
|
||||
```markdown
|
||||
# Heading 1
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
#### Heading 4
|
||||
##### Heading 5
|
||||
###### Heading 6
|
||||
```
|
||||
|
||||
### Text Formatting
|
||||
|
||||
| Style | Syntax | Example | Output |
|
||||
|-------|--------|---------|--------|
|
||||
| Bold | `**text**` or `__text__` | `**Bold**` | **Bold** |
|
||||
| Italic | `*text*` or `_text_` | `*Italic*` | *Italic* |
|
||||
| Bold + Italic | `***text***` | `***Both***` | ***Both*** |
|
||||
| Strikethrough | `~~text~~` | `~~Striked~~` | ~~Striked~~ |
|
||||
| Highlight | `==text==` | `==Highlighted==` | ==Highlighted== |
|
||||
| Inline code | `` `code` `` | `` `code` `` | `code` |
|
||||
|
||||
### Escaping Formatting
|
||||
|
||||
Use backslash to escape special characters:
|
||||
```markdown
|
||||
\*This won't be italic\*
|
||||
\#This won't be a heading
|
||||
1\. This won't be a list item
|
||||
```
|
||||
|
||||
Common characters to escape: `\*`, `\_`, `\#`, `` \` ``, `\|`, `\~`
|
||||
|
||||
## Internal Links (Wikilinks)
|
||||
|
||||
### Basic Links
|
||||
|
||||
```markdown
|
||||
[[Note Name]]
|
||||
[[Note Name.md]]
|
||||
[[Note Name|Display Text]]
|
||||
```
|
||||
|
||||
### Link to Headings
|
||||
|
||||
```markdown
|
||||
[[Note Name#Heading]]
|
||||
[[Note Name#Heading|Custom Text]]
|
||||
[[#Heading in same note]]
|
||||
[[##Search all headings in vault]]
|
||||
```
|
||||
|
||||
### Link to Blocks
|
||||
|
||||
```markdown
|
||||
[[Note Name#^block-id]]
|
||||
[[Note Name#^block-id|Custom Text]]
|
||||
```
|
||||
|
||||
Define a block ID by adding `^block-id` at the end of a paragraph:
|
||||
```markdown
|
||||
This is a paragraph that can be linked to. ^my-block-id
|
||||
```
|
||||
|
||||
For lists and quotes, add the block ID on a separate line:
|
||||
```markdown
|
||||
> This is a quote
|
||||
> With multiple lines
|
||||
|
||||
^quote-id
|
||||
```
|
||||
|
||||
### Search Links
|
||||
|
||||
```markdown
|
||||
[[##heading]] Search for headings containing "heading"
|
||||
[[^^block]] Search for blocks containing "block"
|
||||
```
|
||||
|
||||
## Markdown-Style Links
|
||||
|
||||
```markdown
|
||||
[Display Text](Note%20Name.md)
|
||||
[Display Text](Note%20Name.md#Heading)
|
||||
[Display Text](https://example.com)
|
||||
[Note](obsidian://open?vault=VaultName&file=Note.md)
|
||||
```
|
||||
|
||||
Note: Spaces must be URL-encoded as `%20` in Markdown links.
|
||||
|
||||
## Embeds
|
||||
|
||||
### Embed Notes
|
||||
|
||||
```markdown
|
||||
![[Note Name]]
|
||||
![[Note Name#Heading]]
|
||||
![[Note Name#^block-id]]
|
||||
```
|
||||
|
||||
### Embed Images
|
||||
|
||||
```markdown
|
||||
![[image.png]]
|
||||
![[image.png|640x480]] Width x Height
|
||||
![[image.png|300]] Width only (maintains aspect ratio)
|
||||
```
|
||||
|
||||
### External Images
|
||||
|
||||
```markdown
|
||||

|
||||

|
||||
```
|
||||
|
||||
### Embed Audio
|
||||
|
||||
```markdown
|
||||
![[audio.mp3]]
|
||||
![[audio.ogg]]
|
||||
```
|
||||
|
||||
### Embed PDF
|
||||
|
||||
```markdown
|
||||
![[document.pdf]]
|
||||
![[document.pdf#page=3]]
|
||||
![[document.pdf#height=400]]
|
||||
```
|
||||
|
||||
### Embed Lists
|
||||
|
||||
```markdown
|
||||
![[Note#^list-id]]
|
||||
```
|
||||
|
||||
Where the list has been defined with a block ID:
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
|
||||
^list-id
|
||||
```
|
||||
|
||||
### Embed Search Results
|
||||
|
||||
````markdown
|
||||
```query
|
||||
tag:#project status:done
|
||||
```
|
||||
````
|
||||
|
||||
## Callouts
|
||||
|
||||
### Basic Callout
|
||||
|
||||
```markdown
|
||||
> [!note]
|
||||
> This is a note callout.
|
||||
|
||||
> [!info] Custom Title
|
||||
> This callout has a custom title.
|
||||
|
||||
> [!tip] Title Only
|
||||
```
|
||||
|
||||
### Foldable Callouts
|
||||
|
||||
```markdown
|
||||
> [!faq]- Collapsed by default
|
||||
> This content is hidden until expanded.
|
||||
|
||||
> [!faq]+ Expanded by default
|
||||
> This content is visible but can be collapsed.
|
||||
```
|
||||
|
||||
### Nested Callouts
|
||||
|
||||
```markdown
|
||||
> [!question] Outer callout
|
||||
> > [!note] Inner callout
|
||||
> > Nested content
|
||||
```
|
||||
|
||||
### Supported Callout Types
|
||||
|
||||
| Type | Aliases | Description |
|
||||
|------|---------|-------------|
|
||||
| `note` | - | Blue, pencil icon |
|
||||
| `abstract` | `summary`, `tldr` | Teal, clipboard icon |
|
||||
| `info` | - | Blue, info icon |
|
||||
| `todo` | - | Blue, checkbox icon |
|
||||
| `tip` | `hint`, `important` | Cyan, flame icon |
|
||||
| `success` | `check`, `done` | Green, checkmark icon |
|
||||
| `question` | `help`, `faq` | Yellow, question mark |
|
||||
| `warning` | `caution`, `attention` | Orange, warning icon |
|
||||
| `failure` | `fail`, `missing` | Red, X icon |
|
||||
| `danger` | `error` | Red, zap icon |
|
||||
| `bug` | - | Red, bug icon |
|
||||
| `example` | - | Purple, list icon |
|
||||
| `quote` | `cite` | Gray, quote icon |
|
||||
|
||||
### Custom Callouts (CSS)
|
||||
|
||||
```css
|
||||
.callout[data-callout="custom-type"] {
|
||||
--callout-color: 255, 0, 0;
|
||||
--callout-icon: lucide-alert-circle;
|
||||
}
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
||||
### Unordered Lists
|
||||
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Nested item
|
||||
- Another nested
|
||||
- Item 3
|
||||
|
||||
* Also works with asterisks
|
||||
+ Or plus signs
|
||||
```
|
||||
|
||||
### Ordered Lists
|
||||
|
||||
```markdown
|
||||
1. First item
|
||||
2. Second item
|
||||
1. Nested numbered
|
||||
2. Another nested
|
||||
3. Third item
|
||||
|
||||
1) Alternative syntax
|
||||
2) With parentheses
|
||||
```
|
||||
|
||||
### Task Lists
|
||||
|
||||
```markdown
|
||||
- [ ] Incomplete task
|
||||
- [x] Completed task
|
||||
- [ ] Task with sub-tasks
|
||||
- [ ] Subtask 1
|
||||
- [x] Subtask 2
|
||||
```
|
||||
|
||||
## Quotes
|
||||
|
||||
```markdown
|
||||
> This is a blockquote.
|
||||
> It can span multiple lines.
|
||||
>
|
||||
> And include multiple paragraphs.
|
||||
>
|
||||
> > Nested quotes work too.
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
### Inline Code
|
||||
|
||||
```markdown
|
||||
Use `backticks` for inline code.
|
||||
Use double backticks for ``code with a ` backtick inside``.
|
||||
```
|
||||
|
||||
### Code Blocks
|
||||
|
||||
````markdown
|
||||
```
|
||||
Plain code block
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Syntax highlighted code block
|
||||
function hello() {
|
||||
console.log("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# Python example
|
||||
def greet(name):
|
||||
print(f"Hello, {name}!")
|
||||
```
|
||||
````
|
||||
|
||||
### Nesting Code Blocks
|
||||
|
||||
Use more backticks or tildes for the outer block:
|
||||
|
||||
`````markdown
|
||||
````markdown
|
||||
Here's how to create a code block:
|
||||
```js
|
||||
console.log("Hello")
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
## Tables
|
||||
|
||||
```markdown
|
||||
| Header 1 | Header 2 | Header 3 |
|
||||
|----------|----------|----------|
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
| Cell 4 | Cell 5 | Cell 6 |
|
||||
```
|
||||
|
||||
### Alignment
|
||||
|
||||
```markdown
|
||||
| Left | Center | Right |
|
||||
|:---------|:--------:|---------:|
|
||||
| Left | Center | Right |
|
||||
```
|
||||
|
||||
### Using Pipes in Tables
|
||||
|
||||
Escape pipes with backslash:
|
||||
```markdown
|
||||
| Column 1 | Column 2 |
|
||||
|----------|----------|
|
||||
| [[Link\|Display]] | ![[Image\|100]] |
|
||||
```
|
||||
|
||||
## Math (LaTeX)
|
||||
|
||||
### Inline Math
|
||||
|
||||
```markdown
|
||||
This is inline math: $e^{i\pi} + 1 = 0$
|
||||
```
|
||||
|
||||
### Block Math
|
||||
|
||||
```markdown
|
||||
$$
|
||||
\begin{vmatrix}
|
||||
a & b \\
|
||||
c & d
|
||||
\end{vmatrix} = ad - bc
|
||||
$$
|
||||
```
|
||||
|
||||
### Common Math Syntax
|
||||
|
||||
```markdown
|
||||
$x^2$ Superscript
|
||||
$x_i$ Subscript
|
||||
$\frac{a}{b}$ Fraction
|
||||
$\sqrt{x}$ Square root
|
||||
$\sum_{i=1}^{n}$ Summation
|
||||
$\int_a^b$ Integral
|
||||
$\alpha, \beta$ Greek letters
|
||||
```
|
||||
|
||||
## Diagrams (Mermaid)
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Do this]
|
||||
B -->|No| D[Do that]
|
||||
C --> E[End]
|
||||
D --> E
|
||||
```
|
||||
````
|
||||
|
||||
### Sequence Diagrams
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hello Bob
|
||||
Bob-->>Alice: Hi Alice
|
||||
```
|
||||
````
|
||||
|
||||
### Linking in Diagrams
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Biology]
|
||||
B[Chemistry]
|
||||
A --> B
|
||||
class A,B internal-link;
|
||||
```
|
||||
````
|
||||
|
||||
## Footnotes
|
||||
|
||||
```markdown
|
||||
This sentence has a footnote[^1].
|
||||
|
||||
[^1]: This is the footnote content.
|
||||
|
||||
You can also use named footnotes[^note].
|
||||
|
||||
[^note]: Named footnotes still appear as numbers.
|
||||
|
||||
Inline footnotes are also supported.^[This is an inline footnote.]
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
```markdown
|
||||
This is visible %%but this is hidden%% text.
|
||||
|
||||
%%
|
||||
This entire block is hidden.
|
||||
It won't appear in reading view.
|
||||
%%
|
||||
```
|
||||
|
||||
## Horizontal Rules
|
||||
|
||||
```markdown
|
||||
---
|
||||
***
|
||||
___
|
||||
- - -
|
||||
* * *
|
||||
```
|
||||
|
||||
## Properties (Frontmatter)
|
||||
|
||||
Properties use YAML frontmatter at the start of a note:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: My Note Title
|
||||
date: 2024-01-15
|
||||
tags:
|
||||
- project
|
||||
- important
|
||||
aliases:
|
||||
- My Note
|
||||
- Alternative Name
|
||||
cssclasses:
|
||||
- custom-class
|
||||
status: in-progress
|
||||
rating: 4.5
|
||||
completed: false
|
||||
due: 2024-02-01T14:30:00
|
||||
---
|
||||
```
|
||||
|
||||
### Property Types
|
||||
|
||||
| Type | Example |
|
||||
|------|---------|
|
||||
| Text | `title: My Title` |
|
||||
| Number | `rating: 4.5` |
|
||||
| Checkbox | `completed: true` |
|
||||
| Date | `date: 2024-01-15` |
|
||||
| Date & Time | `due: 2024-01-15T14:30:00` |
|
||||
| List | `tags: [one, two]` or YAML list |
|
||||
| Links | `related: "[[Other Note]]"` |
|
||||
|
||||
### Default Properties
|
||||
|
||||
- `tags` - Note tags
|
||||
- `aliases` - Alternative names for the note
|
||||
- `cssclasses` - CSS classes applied to the note
|
||||
|
||||
## Tags
|
||||
|
||||
```markdown
|
||||
#tag
|
||||
#nested/tag
|
||||
#tag-with-dashes
|
||||
#tag_with_underscores
|
||||
|
||||
In frontmatter:
|
||||
---
|
||||
tags:
|
||||
- tag1
|
||||
- nested/tag2
|
||||
---
|
||||
```
|
||||
|
||||
Tags can contain:
|
||||
- Letters (any language)
|
||||
- Numbers (not as first character)
|
||||
- Underscores `_`
|
||||
- Hyphens `-`
|
||||
- Forward slashes `/` (for nesting)
|
||||
|
||||
## HTML Content
|
||||
|
||||
Obsidian supports HTML within Markdown:
|
||||
|
||||
```markdown
|
||||
<div class="custom-container">
|
||||
<span style="color: red;">Colored text</span>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
Hidden content here.
|
||||
</details>
|
||||
|
||||
<kbd>Ctrl</kbd> + <kbd>C</kbd>
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
````markdown
|
||||
---
|
||||
title: Project Alpha
|
||||
date: 2024-01-15
|
||||
tags:
|
||||
- project
|
||||
- active
|
||||
status: in-progress
|
||||
priority: high
|
||||
---
|
||||
|
||||
# Project Alpha
|
||||
|
||||
## Overview
|
||||
|
||||
This project aims to [[improve workflow]] using modern techniques.
|
||||
|
||||
> [!important] Key Deadline
|
||||
> The first milestone is due on ==January 30th==.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Initial planning
|
||||
- [x] Resource allocation
|
||||
- [ ] Development phase
|
||||
- [ ] Backend implementation
|
||||
- [ ] Frontend design
|
||||
- [ ] Testing
|
||||
- [ ] Deployment
|
||||
|
||||
## Technical Notes
|
||||
|
||||
The main algorithm uses the formula $O(n \log n)$ for sorting.
|
||||
|
||||
```python
|
||||
def process_data(items):
|
||||
return sorted(items, key=lambda x: x.priority)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Input] --> B[Process]
|
||||
B --> C[Output]
|
||||
B --> D[Cache]
|
||||
```
|
||||
|
||||
## Related Documents
|
||||
|
||||
- ![[Meeting Notes 2024-01-10#Decisions]]
|
||||
- [[Budget Allocation|Budget]]
|
||||
- [[Team Members]]
|
||||
|
||||
## References
|
||||
|
||||
For more details, see the official documentation[^1].
|
||||
|
||||
[^1]: https://example.com/docs
|
||||
|
||||
%%
|
||||
Internal notes:
|
||||
- Review with team on Friday
|
||||
- Consider alternative approaches
|
||||
%%
|
||||
````
|
||||
|
||||
## References
|
||||
|
||||
- [Basic formatting syntax](https://help.obsidian.md/syntax)
|
||||
- [Advanced formatting syntax](https://help.obsidian.md/advanced-syntax)
|
||||
- [Obsidian Flavored Markdown](https://help.obsidian.md/obsidian-flavored-markdown)
|
||||
- [Internal links](https://help.obsidian.md/links)
|
||||
- [Embed files](https://help.obsidian.md/embeds)
|
||||
- [Callouts](https://help.obsidian.md/callouts)
|
||||
- [Properties](https://help.obsidian.md/properties)
|
||||
Reference in New Issue
Block a user