# Obsidian Workflows and Automation Patterns

This reference contains ready-to-use automation recipes and workflow patterns for common Obsidian use cases.

---

## Pattern 1: Auto-Generated Daily Notes

**Goal**: Daily note is created every morning with weather, priorities, and template.

**Setup**:
1. Enable Periodic Notes (core plugin)
2. Install Templater
3. Create template: `Templates/Daily.md`
4. Configure folder template in Templater: `Journal/` -> `Templates/Daily.md`

**Template** (`Templates/Daily.md`):
```markdown
---
date: <% tp.date.now("YYYY-MM-DD") %>
day: <% tp.date.now("dddd") %>
mood: <% tp.system.suggester("Mood", ["great", "good", "neutral", "tired", "bad"]) %>
---

# <% tp.date.now("YYYY-MM-DD") %> — <% tp.date.now("dddd") %>

## Morning Priorities
1. 
2. 
3. 

## Schedule
- 

## Notes
<% tp.system.clipboard() %>

## Tasks
```tasks
not done
due today
```

## End of Day Review
- What went well:
- What to improve:
- Tomorrow's focus:
```

**Cron (Hermes)**:
```bash
hermes cronjob create \
  --schedule "0 6 * * *" \
  --command "xdg-open 'obsidian://daily?vault=MyVault'"
```

---

## Pattern 2: Meeting Notes Pipeline

**Goal**: One command/hotkey creates a meeting note with prompts for attendees and topic.

**Setup**:
1. Install QuickAdd
2. Create "New Meeting" choice
3. Create template: `Templates/Meeting.md`
4. Set capture folder: `Meetings/`

**QuickAdd Configuration**:
- Choice type: Template
- Template: `Templates/Meeting.md`
- File name format: `{{DATE:YYYY-MM-DD}} — {{VALUE:Topic}}`
- Create in folder: `Meetings/`
- Open: Yes

**Template** (`Templates/Meeting.md`):
```markdown
---
date: <% tp.date.now() %>
type: meeting
topic: {{VALUE:Topic}}
attendees: {{VALUE:Attendees}}
---

# {{DATE:YYYY-MM-DD}} — {{VALUE:Topic}}

**Attendees:** {{VALUE:Attendees}}
**Date:** {{DATE:YYYY-MM-DD HH:mm}}

## Agenda
1. 
2. 
3. 

## Notes

## Action Items
- [ ] 

## Next Meeting
- 
```

**CLI alternative**:
```bash
obsidian create name="$(date +%F) — Team Sync" template=Meeting folder="Meetings/"
```

---

## Pattern 3: Project Dashboard

**Goal**: Central dashboard that auto-updates with all active projects.

**Setup**:
1. Create `Projects/Dashboard.md`
2. Use Dataview to aggregate

**Dashboard**:
```markdown
# Project Dashboard

## Active Projects
```dataview
TABLE status, deadline, priority, file.mtime as "Last Updated"
FROM #project
WHERE status != "done" AND status != "cancelled"
SORT priority DESC, deadline ASC
```

## Completed This Month
```dataview
LIST
FROM #project
WHERE status = "done" AND completed >= date(today) - dur(30 days)
SORT completed DESC
```

## Overdue Tasks
```tasks
not done
due before today
path includes Projects/
sort by due
```

## Recently Updated
```dataview
TABLE file.mtime as "Modified"
FROM #project
SORT file.mtime DESC
LIMIT 5
```
```

**Project note template** (`Templates/Project.md`):
```markdown
---
title: <% tp.file.title %>
status: active
deadline: <% tp.date.now("YYYY-MM-DD", 30) %>
priority: medium
tags: [project]
---

# <% tp.file.title %>

## Objective

## Tasks
- [ ] 

## Notes

## Resources
```

---

## Pattern 4: Zettelkasten / Literature Notes

**Goal**: Capture literature notes with auto-generated IDs and backlinks.

**Setup**:
1. Install Templater
2. Create `Templates/Literature.md`
3. Set folder: `Literature/`
4. Configure QuickAdd for quick capture

**Template**:
```markdown
---
id: <% tp.date.now("YYYYMMDDHHmm") %>
title: <% tp.file.title %>
author: <% tp.system.prompt("Author") %>
source: <% tp.system.prompt("Source") %>
date: <% tp.date.now() %>
tags: [literature, <% tp.system.suggester("Type", ["book", "paper", "article", "video"]) %>]
---

# <% tp.file.title %>

**Author:** <% tp.system.prompt("Author") %>
**Source:** <% tp.system.prompt("Source") %>
**Date:** <% tp.date.now() %>

## Key Ideas
1. 
2. 
3. 

## My Thoughts

## Connections
- 

## Quotes
> 
```

**Literature index** (Dataview):
```dataview
TABLE author, source, date
FROM #literature
SORT date DESC
```

---

## Pattern 5: Inbox Processing

**Goal**: Quick capture throughout the day, process in batches.

**Setup**:
1. Create `Inbox.md`
2. QuickAdd "Quick Capture" -> append to Inbox
3. Daily processing session

**QuickAdd Capture**:
```
Choice: Capture
Target: Inbox.md
Format: `- [ ] {{VALUE}} {{DATE:YYYY-MM-DD HH:mm}}`
Position: Append
```

**Inbox.md**:
```markdown
# Inbox

## Today
```tasks
not done
path includes Inbox
```

## Quick Notes
```dataview
LIST
FROM "Inbox"
WHERE file.name != "Inbox"
SORT file.ctime DESC
LIMIT 10
```

## Unprocessed
- [ ] Review and file items below:
```

**Processing session** (using CLI):
```bash
# List items in inbox
obsidian read Inbox

# Move processed items to archive
obsidian move "Inbox.md" "Archive/Inbox-$(date +%F).md"
```

---

## Pattern 6: TTRPG Campaign Vault

**Goal**: Manage D&D campaign with auto-generated stat blocks, session notes, and indexes.

**Structure**:
```
Campaign/
├── _Index.md
├── Templates/
│   ├── NPC.md
│   ├── Monster.md
│   ├── Session.md
│   └── Location.md
├── NPCs/
│   ├── _Index.md
│   └── ...
├── Monsters/
├── Locations/
├── Sessions/
├── Items/
└── Worldbuilding/
```

**Scaffolding script** (`scripts/scaffold.py`):
```python
#!/usr/bin/env python3
"""Scaffold campaign content from 5e.tools data."""
import json, requests, os, re

VAULT = os.path.expanduser("~/obsidian-vault/Campaign")

def slugify(name):
    return re.sub(r'[^\w\s-]', '', name).strip().replace(' ', '-')

def create_npc(name, race, class_, notes):
    path = f"{VAULT}/NPCs/{slugify(name)}.md"
    content = f"""---
name: {name}
race: {race}
class: {class_}
type: npc
tags: [npc, {race.lower()}]
---

# {name}

**Race:** {race}
**Class:** {class_}

## Notes
{notes}

## Appearances
- 
"""
    with open(path, 'w') as f:
        f.write(content)
    return path

def update_index(folder, tag):
    index_path = f"{VAULT}/{folder}/_Index.md"
    notes = []
    for f in os.listdir(f"{VAULT}/{folder}"):
        if f.endswith('.md') and f != '_Index.md':
            notes.append(f"- [[{f[:-3]}]]")
    with open(index_path, 'w') as f:
        f.write(f"# {folder} Index\n\n")
        f.write('\n'.join(sorted(notes)))

# Usage
if __name__ == '__main__':
    create_npc("Captain Blackwood", "Human", "Fighter", "Pirate captain")
    update_index("NPCs", "npc")
```

**Session template**:
```markdown
---
date: <% tp.date.now() %>
session_number: <% tp.system.prompt("Session #") %>
tags: [session]
---

# Session <% tp.system.prompt("Session #") %> — <% tp.system.prompt("Title") %>

## Summary

## Players Present
- 

## Key Events
1. 
2. 
3. 

## NPCs Met
- 

## Locations Visited
- 

## Loot & Rewards
- 

## Next Session Hooks
- 
```

---

## Pattern 7: Git-Backed Vault

**Goal**: Version control for the entire vault with automatic commits.

**Setup**:
1. Install Obsidian Git plugin
2. Initialize git repo in vault root
3. Configure remote
4. Set auto-commit intervals

**Git config**:
```bash
cd ~/obsidian-vault
git init
git remote add origin git@github.com:user/vault.git
```

**Obsidian Git settings**:
- Auto commit: On
- Commit interval: 10 minutes
- Push on commit: On
- Backup on quit: On
- Commit message: `vault backup: {{date}}`

**Cron backup** (Hermes):
```bash
# Daily push at midnight
hermes cronjob create \
  --schedule "0 0 * * *" \
  --command "cd ~/obsidian-vault && git add -A && git commit -m 'daily backup $(date +%F)' && git push"

# Weekly full sync
hermes cronjob create \
  --schedule "0 2 * * 0" \
  --command "cd ~/obsidian-vault && git fetch && git merge origin/main"
```

---

## Pattern 8: External Data Pipeline

**Goal**: Pull external data (RSS, APIs, emails) into Obsidian automatically.

**RSS to Literature Notes**:
```bash
#!/bin/bash
# rss-to-obsidian.sh
FEED_URL="https://example.com/feed.xml"
VAULT="~/obsidian-vault"

# Fetch and parse RSS
items=$(curl -s "$FEED_URL" | xmlstarlet sel -t -m "//item" -v "title" -o "|" -v "link" -o "|" -v "pubDate" -n)

while IFS='|' read -r title link date; do
    filename="$(echo "$title" | tr ' ' '-' | tr -cd '[:alnum:]-').md"
    cat > "$VAULT/Literature/$filename" <<EOF
---
title: $title
source: $link
date: $(date -d "$date" +%Y-%m-%d)
tags: [rss, literature]
---

# $title

**Source:** [$title]($link)
**Date:** $date

## Notes

EOF
done <<< "$items"
```

**n8n Webhook -> Obsidian REST API**:
```
n8n workflow:
1. Webhook trigger (e.g., from form submission)
2. HTTP Request -> POST /vault/Form-Submissions.md
3. Append captured data to note
```

---

## Pattern 9: Knowledge Graph Analysis

**Goal**: Analyze vault structure, find orphans, measure connectivity.

**Script** (`scripts/vault-analyze.py`):
```python
#!/usr/bin/env python3
import os, re, yaml, json
from collections import defaultdict

VAULT = os.path.expanduser("~/obsidian-vault")

def get_md_files():
    for root, dirs, files in os.walk(VAULT):
        for f in files:
            if f.endswith('.md'):
                yield os.path.join(root, f)

def extract_links(content):
    # [[Link]] or [[Link|Display]]
    return re.findall(r'\[\[([^\]|]+)(?:\|[^\]]+)?\]\]', content)

def extract_tags(content):
    return re.findall(r'#([a-zA-Z0-9_\-/]+)', content)

# Build graph
files = list(get_md_files())
links = defaultdict(list)
backlinks = defaultdict(list)
tags = defaultdict(list)

for f in files:
    rel = os.path.relpath(f, VAULT)[:-3]
    with open(f) as fh:
        content = fh.read()
    for link in extract_links(content):
        links[rel].append(link)
        backlinks[link].append(rel)
    for tag in extract_tags(content):
        tags[tag].append(rel)

# Find orphans (no backlinks)
orphans = [f for f in files if os.path.relpath(f, VAULT)[:-3] not in backlinks]

# Find broken links
all_basenames = {os.path.relpath(f, VAULT)[:-3] for f in files}
broken = []
for source, targets in links.items():
    for t in targets:
        if t not in all_basenames:
            broken.append((source, t))

# Report
print(f"Total notes: {len(files)}")
print(f"Total links: {sum(len(v) for v in links.values())}")
print(f"Orphaned notes: {len(orphans)}")
for o in orphans:
    print(f"  - {o}")
print(f"Broken links: {len(broken)}")
for s, t in broken:
    print(f"  - {s} -> {t}")
print(f"Total tags: {len(tags)}")
for tag, files in sorted(tags.items(), key=lambda x: -len(x[1]))[:10]:
    print(f"  #{tag}: {len(files)} notes")
```

---

## Pattern 10: Multi-Device Sync Strategy

**Goal**: Keep vault in sync across Linux server, MacBook, and iOS.

**Option A: iCloud (Mac/iOS native)**
- Vault in iCloud Drive/Obsidian
- Linux: Use pyicloud script for periodic sync
- Pros: Native on Apple devices
- Cons: Linux requires credential management

**Option B: Obsidian Sync (Official)**
- Subscribe to Obsidian Sync
- Linux: Use `obsidian-headless` for sync
- Pros: E2E encrypted, handles conflicts
- Cons: Subscription required

**Option C: Syncthing (Free)**
- Install Syncthing on all devices
- Share vault folder
- Pros: Free, P2P, handles conflicts
- Cons: Setup complexity

**Option D: Git + Obsidian Git**
- Git repo with remote (GitHub/GitLab)
- Auto-commit and push on all devices
- Pros: Full history, free, works everywhere
- Cons: Manual conflict resolution

**Recommended for this user**: Option A (iCloud) for primary, with pyicloud sync script on Linux. Git as backup.

---

*These patterns are starting points. Adapt them to your specific vault structure and workflow.*
