
Obsidian Vault Management
- 276 installs
- 6 repo stars
- Updated July 22, 2026
- julianobarbosa/claude-code-skills
obsidian-vault-management is a Claude Code skill that organizes, refactors, links, and maintains an Obsidian vault so engineering notes, ADRs, runbooks, and project docs stay searchable and consistent for developers mana
About
obsidian-vault-management is a documentation skill from julianobarbosa/claude-code-skills that helps developers organize, refactor, link, and maintain Obsidian vaults used for engineering knowledge. The skill supports consistent structure for ADRs, runbooks, project notes, and cross-linked reference material so agents and humans can search and navigate technical docs reliably. Developers reach for obsidian-vault-management when vaults grow messy—duplicate pages, broken wikilinks, inconsistent tags, or scattered runbooks—and they need systematic cleanup without manual reorganization. It fits teams that treat Obsidian as the source of truth for internal engineering documentation alongside code repositories.
- Vault organization patterns
- Link and tag hygiene
- Note refactoring
- Knowledge base upkeep
- Agent-friendly note workflows
Obsidian Vault Management by the numbers
- 276 all-time installs (skills.sh)
- +3 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #931 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/julianobarbosa/claude-code-skills --skill obsidian-vault-managementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 276 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 22, 2026 |
| Repository | julianobarbosa/claude-code-skills ↗ |
How do you refactor a messy Obsidian engineering vault?
Organize, refactor, link, and maintain an Obsidian vault so engineering notes, ADRs, runbooks, and project docs stay searchable and consistent over time.
Who is it for?
Developers and platform engineers who store ADRs, runbooks, and technical notes in Obsidian and need systematic vault cleanup and linking.
Skip if: Teams using Confluence, Notion, or wiki-only workflows with no Obsidian markdown vault to maintain.
When should I use this skill?
A developer asks to reorganize an Obsidian vault, fix broken links, standardize tags, or refactor engineering documentation structure.
What you get
Restructured vault folders, repaired wikilinks, consistent tags, and cross-linked ADRs, runbooks, and project notes.
- restructured vault layout
- standardized link graph
- tag and metadata conventions
Files
Obsidian Vault Management
Vault Structure (PARA-Based)
This vault uses a PARA-like organization:
| Folder | Purpose |
|---|---|
00 - Maps of Content | Index notes linking related topics |
01 - Projects | Active project notes |
02 - Areas | Ongoing responsibilities |
03 - Resources | Reference materials |
04 - Permanent | Evergreen/zettelkasten notes |
05 - Fleeting | Quick capture notes |
06 - Daily | Daily notes (YYYY/MM/YYYYMMDD.md) |
07 - Archives | Completed/inactive content |
08 - books | Book notes and clippings |
99 - Meta | Templates, settings |
Clippings | Web clips and imports |
Quick Reference
Linking Syntax
[[Note Name]] # Basic wikilink
[[Note Name|Display Text]] # Aliased link
[[Note Name#Heading]] # Link to heading
[[Note Name#^block-id]] # Link to block
![[Note Name]] # Embed note
![[image.png]] # Embed image
![[Note Name#Heading]] # Embed sectionFrontmatter Template
---
created: {{date:YYYY-MM-DDTHH:mm}}
updated: {{date:YYYY-MM-DDTHH:mm}}
title: "Note Title"
type: note
status: draft
tags:
- tag1
- tag2
aliases:
- "Alternate Name"
cssclasses:
- custom-class
---Callouts
> [!note] Title
> Content
> [!warning] Important
> Warning content
> [!tip] Helpful tip
> Tip content
> [!info]+ Collapsible (open by default)
> Content
> [!danger]- Collapsed by default
> ContentAvailable callout types: note, abstract, info, todo, tip, success, question, warning, failure, danger, bug, example, quote
Creating Notes
Daily Note
Create in 06 - Daily/YYYY/MM/ with filename YYYYMMDD.md:
---
created: 2025-12-09T09:00
updated: 2025-12-09T09:00
title: "20251209"
type: daily-note
status: true
tags:
- daily
- journal
- 2025
- 2025-12
aliases:
- "2025-12-09"
date_formatted: 2025-12-09
topics:
- "[[daily]]"
- "[[journal]]"
related:
- "[[2025-12-08]]"
- "[[2025-12-10]]"
cssclasses:
- daily
---
# Daily Note - 2025-12-09
### Tasks
- [ ] Task 1
### Journal
...
### Navigation
<< [[2025-12-08]] | **Today** | [[2025-12-10]] >>Zettelkasten Note
Create in 04 - Permanent/:
---
created: {{date}}
type: zettelkasten
tags:
- permanent
- topic
---
# Note Title
## Main Insight
**Key Idea**: [Main point]
## Connections
- [[Related Note 1]]
- [[Related Note 2]]
## References
- Source citationDataview Queries
For dataview query syntax, see references/dataview.md.
Quick examples:
LIST FROM "06 - Daily" WHERE file.cday = date(today) SORT file.ctime DESCTABLE status, tags FROM "01 - Projects" WHERE status != "completed"Templates
Templates location: 99 - Meta/00 - Templates/
For Templater syntax, see references/templater.md.
Common Templater variables:
<% tp.file.title %> # Current file name
<% tp.date.now("YYYY-MM-DD") %> # Current date
<% tp.file.cursor(1) %> # Cursor position
<% tp.system.prompt("Question") %> # User input promptInstalled Plugins
| Plugin | Purpose |
|---|---|
| Dataview | Query and display data from notes |
| Templater | Advanced templates with scripting |
| Auto Note Mover | Auto-organize notes by tags |
| Periodic Notes | Daily/weekly/monthly notes |
| Kanban | Kanban boards in markdown |
| Tag Wrangler | Bulk tag management |
| Table Editor | Markdown table editing |
| Advanced URI | Deep links to notes |
| Local REST API | External API access |
File Operations
Creating a Note
1. Determine appropriate folder based on note type 2. Add proper frontmatter 3. Use consistent naming conventions 4. Include relevant tags for auto-organization
Best Practices
- Use descriptive filenames (avoid special characters except hyphens)
- Always include
createdandupdatedtimestamps - Tag notes for discoverability
- Link to related notes bidirectionally
- Use callouts for important information
- Include navigation links in daily notes
Advanced Features
- Dataview queries: references/dataview.md
- Templater scripting: references/templater.md
- Canvas diagrams: references/canvas.md
- Plugin configurations: references/plugins.md
---
Gotchas
- Bulk operations outside Obsidian don't trigger link-rebuilding — external scripts must signal a
:Reindexor wait for the next vault open. - Dataview cache is per-vault and per-session — CLI script-based DQL queries may see stale state if Obsidian was last open with different filters.
- Templater scripts via CLI run in a different context than the UI — many
tp.system.*functions return nil (no UI to prompt against). - PARA folder moves break tag-based queries — if your queries hardcode folder paths, refactor them to use tags or properties before reorganizing.
- Daily note rotation: changing the daily-note format mid-vault leaves old notes orphaned — they don't auto-migrate to the new format; a rename pass is needed.
Obsidian Canvas Reference
Canvas is a visual tool for spatially organizing notes, images, and other content.
File Format
Canvas files use .canvas extension and store JSON data:
{
"nodes": [],
"edges": []
}Node Types
Text Node
{
"id": "unique-id",
"type": "text",
"x": 0,
"y": 0,
"width": 250,
"height": 100,
"text": "Your text content here"
}File Node (Embed Note)
{
"id": "unique-id",
"type": "file",
"file": "path/to/note.md",
"x": 300,
"y": 0,
"width": 400,
"height": 300
}Link Node (Web Embed)
{
"id": "unique-id",
"type": "link",
"url": "https://example.com",
"x": 0,
"y": 200,
"width": 400,
"height": 300
}Group Node
{
"id": "unique-id",
"type": "group",
"x": -50,
"y": -50,
"width": 600,
"height": 400,
"label": "Group Name"
}Edges (Connections)
{
"id": "edge-id",
"fromNode": "source-node-id",
"fromSide": "right",
"toNode": "target-node-id",
"toSide": "left",
"color": "1",
"label": "Connection label"
}Side Options
toprightbottomleft
Color Options
Canvas uses numbered colors 1-6:
- 1: Red
- 2: Orange
- 3: Yellow
- 4: Green
- 5: Cyan
- 6: Purple
Node Colors
Add color property to any node:
{
"id": "node-id",
"type": "text",
"color": "1",
"text": "Red text card"
}Complete Canvas Example
{
"nodes": [
{
"id": "1",
"type": "text",
"x": 0,
"y": 0,
"width": 200,
"height": 100,
"text": "# Main Idea\n\nCore concept description"
},
{
"id": "2",
"type": "file",
"file": "04 - Permanent/concept-note.md",
"x": 300,
"y": 0,
"width": 300,
"height": 200
},
{
"id": "3",
"type": "text",
"x": 0,
"y": 150,
"width": 200,
"height": 80,
"text": "Related thought",
"color": "4"
},
{
"id": "group1",
"type": "group",
"x": -20,
"y": -20,
"width": 640,
"height": 280,
"label": "Knowledge Cluster"
}
],
"edges": [
{
"id": "e1",
"fromNode": "1",
"fromSide": "right",
"toNode": "2",
"toSide": "left",
"label": "expands on"
},
{
"id": "e2",
"fromNode": "1",
"fromSide": "bottom",
"toNode": "3",
"toSide": "top",
"color": "4"
}
]
}Creating Canvas Programmatically
Basic Structure
import json
canvas = {
"nodes": [],
"edges": []
}
# Add nodes
canvas["nodes"].append({
"id": "node1",
"type": "text",
"x": 0,
"y": 0,
"width": 250,
"height": 100,
"text": "Content here"
})
# Save
with open("my-canvas.canvas", "w") as f:
json.dump(canvas, f, indent=2)Grid Layout Helper
def create_grid_canvas(items, cols=3, card_width=250, card_height=100, gap=50):
nodes = []
for i, item in enumerate(items):
row = i // cols
col = i % cols
nodes.append({
"id": f"node-{i}",
"type": "text",
"x": col * (card_width + gap),
"y": row * (card_height + gap),
"width": card_width,
"height": card_height,
"text": item
})
return {"nodes": nodes, "edges": []}Best Practices
1. Use groups to organize related nodes 2. Color-code by category or status 3. Keep text concise in text nodes 4. Embed notes for detailed content 5. Label edges for relationship clarity 6. Maintain spacing for readability
Canvas Use Cases
- Mind mapping: Brainstorm and connect ideas
- Project planning: Visual task boards
- Knowledge graphs: Visualize note relationships
- Mood boards: Collect visual inspiration
- Architecture diagrams: System design
- Flowcharts: Process documentation
Dataview Query Reference
Query Types
LIST Query
LIST
FROM "folder"
WHERE condition
SORT field ASC/DESC
LIMIT 10TABLE Query
TABLE col1, col2, col3
FROM "folder"
WHERE condition
SORT field DESCTASK Query
TASK
FROM "folder"
WHERE !completed
GROUP BY file.linkCALENDAR Query
CALENDAR file.cday
FROM "06 - Daily"Source Selection (FROM)
FROM "folder" # Specific folder
FROM #tag # Notes with tag
FROM [[Note]] # Notes linking to Note
FROM outgoing([[Note]]) # Notes linked from Note
FROM "folder" AND #tag # Combine with AND
FROM "folder" OR "other" # Combine with OR
FROM -"excluded" # Exclude folderField Access
Implicit Fields (automatic)
| Field | Description |
|---|---|
file.name | Filename without extension |
file.path | Full path |
file.folder | Parent folder |
file.link | Link to file |
file.size | File size in bytes |
file.ctime | Creation time |
file.cday | Creation date |
file.mtime | Modified time |
file.mday | Modified date |
file.tags | All tags |
file.etags | Explicit tags only |
file.inlinks | Incoming links |
file.outlinks | Outgoing links |
file.tasks | All tasks |
file.lists | All list items |
Frontmatter Fields
Access YAML frontmatter directly:
TABLE status, created, tags
FROM "01 - Projects"Inline Fields
Define inline fields in notes:
Key:: Value
[Key:: Value]
(Key:: Value)Access in queries: WHERE key = "value"
WHERE Conditions
WHERE file.name = "exact"
WHERE contains(file.name, "partial")
WHERE startswith(file.name, "prefix")
WHERE endswith(file.name, "suffix")
WHERE file.cday = date(today)
WHERE file.cday >= date(today) - dur(7 days)
WHERE contains(tags, "#important")
WHERE status != "completed"
WHERE !completed # For tasks
WHERE length(file.tags) > 2
WHERE any(file.tags, (t) => startswith(t, "#project"))SORT Options
SORT file.name ASC
SORT file.mtime DESC
SORT status ASC, priority DESC # Multiple fieldsGROUP BY
TABLE rows.file.link
FROM "folder"
GROUP BY statusDate Functions
date(today) # Today
date(now) # Current datetime
date(tomorrow) # Tomorrow
date(yesterday) # Yesterday
date("2025-12-09") # Specific date
dur(7 days) # Duration
dur(1 week) # Duration
file.cday.year # Extract year
file.cday.month # Extract month
file.cday.day # Extract dayString Functions
contains(string, "search")
startswith(string, "prefix")
endswith(string, "suffix")
replace(string, "old", "new")
lower(string)
upper(string)
length(string)
split(string, ",")
join(list, ", ")List Functions
length(list)
contains(list, item)
all(list, condition)
any(list, condition)
filter(list, condition)
map(list, function)
sort(list)
reverse(list)
flat(nested-list)Inline Queries
Use prefix = for inline queries (configured in vault):
Today is `= date(today)`
File count: `= length(filter(file.tasks, (t) => !t.completed))`DataviewJS
For complex queries, use dataviewjs code blocks:
const pages = dv.pages('"06 - Daily"')
.where(p => p.file.cday >= dv.date('2025-01-01'))
.sort(p => p.file.cday, 'desc');
dv.table(
["Date", "Tasks"],
pages.map(p => [p.file.link, p.file.tasks.length])
);Common Patterns
Recent Daily Notes
LIST
FROM "06 - Daily"
WHERE file.cday >= date(today) - dur(7 days)
SORT file.cday DESCIncomplete Tasks by Project
TASK
FROM "01 - Projects"
WHERE !completed
GROUP BY file.linkNotes Modified Today
TABLE file.mtime as "Modified"
WHERE file.mday = date(today)
SORT file.mtime DESCTag Overview
TABLE length(rows) as "Count"
FROM "vault"
FLATTEN file.tags as tag
GROUP BY tag
SORT length(rows) DESCNotes Created This Month
LIST
WHERE file.cday.year = date(today).year
WHERE file.cday.month = date(today).month
SORT file.cday DESCInstalled Plugins Reference
Dataview
Purpose: Query notes like a database
Config file: .obsidian/plugins/dataview/data.json
Key Settings:
- Inline query prefix:
= - Inline JS prefix:
$= - Date format:
MMMM dd, yyyy - Task completion tracking: enabled
See dataview.md for query syntax.
Templater
Purpose: Advanced templates with scripting
Config file: .obsidian/plugins/templater-obsidian/data.json
Key Settings:
- Templates folder:
99 - Meta/00 - Templates - Trigger on file creation: enabled
- Auto jump to cursor: enabled
See templater.md for syntax.
Auto Note Mover
Purpose: Automatically move notes based on tags
Config file: .obsidian/plugins/auto-note-mover/data.json
Current Rules:
| Tag | Destination Folder |
|---|---|
| #aks-a | 08 - books/azure/aks |
| #pwsh-a | 08 - books/PowerShell |
| #argocd-a | 08 - books/argocd |
| #fabric-a | 08 - books/fabric |
| #git-a | 08 - books/git |
Periodic Notes
Purpose: Daily, weekly, monthly note management
Config file: .obsidian/plugins/periodic-notes/data.json
Typical Settings:
- Daily notes folder:
06 - Daily/{{date:YYYY}}/{{date:MM}} - Daily note format:
{{date:YYYYMMDD}} - Template: daily note template
Kanban
Purpose: Kanban boards in markdown
Usage: Create a note with kanban content:
---
kanban-plugin: basic
---
## To Do
- [ ] Task 1
- [ ] Task 2
## In Progress
- [ ] Active task
## Done
- [x] Completed taskTag Wrangler
Purpose: Bulk tag management
Features:
- Rename tags across vault
- Merge duplicate tags
- Delete unused tags
- Tag suggestions
Table Editor
Purpose: Easy markdown table editing
Features:
- Visual table editor
- Add/remove rows/columns
- Sort columns
- Align text
Advanced URI
Purpose: Deep links to notes and actions
URI Format:
obsidian://advanced-uri?vault=VAULT_NAME&filepath=path/to/note.md
obsidian://advanced-uri?vault=VAULT_NAME&daily=true
obsidian://advanced-uri?vault=VAULT_NAME&search=queryLocal REST API
Purpose: External API access to vault
Config file: .obsidian/plugins/obsidian-local-rest-api/data.json
Endpoints (when enabled):
GET /vault- List all filesGET /vault/{path}- Get file contentPUT /vault/{path}- Create/update fileDELETE /vault/{path}- Delete filePOST /search- Search vault
MetaEdit
Purpose: Edit frontmatter properties
Features:
- Quick property editing
- Auto-complete for values
- Kanban integration
Note Refactor
Purpose: Split and refactor notes
Features:
- Extract selection to new note
- Split by heading
- Automatic link updates
Update Time on Edit
Purpose: Auto-update timestamps
Config file: .obsidian/plugins/update-time-on-edit/data.json
Updates updated field in frontmatter on file save.
Frontmatter Tag Suggest
Purpose: Tag auto-complete in frontmatter
Features:
- Suggests existing tags
- Works in YAML frontmatter
- Reduces typos
Icon Folder
Purpose: Custom folder icons
Features:
- Assign icons to folders
- Custom icon packs
- Visual organization
List Callouts
Purpose: Callout styling for lists
Usage:
- [!] Important item
- [?] Question
- [i] InformationSmart Typography
Purpose: Auto-replace typography
Features:
- Smart quotes
- Em/en dashes
- Ellipsis
- Arrows
Style Settings
Purpose: Theme customization
Features:
- CSS variable controls
- Per-theme settings
- Live preview
URL Into Selection
Purpose: Paste URLs as links
Features:
- Select text, paste URL
- Creates
[selected text](url)
Copilot (AI)
Purpose: AI-powered assistance
Features:
- Chat with notes
- Summarization
- Question answering
- Custom prompts stored in
copilot-custom-prompts/
Plugin Management Tips
1. Disable unused plugins to improve performance 2. Check for updates regularly 3. Backup config before major changes 4. Test new plugins in a test vault first 5. Review settings after updates
Templater Reference
Configuration
Templates folder: 99 - Meta/00 - Templates/
Basic Syntax
All Templater commands use <% %> delimiters:
<% tp.module.function() %>Output result: <% tp.module.function() %> Execute without output: <%* code %> Whitespace control: <%- %> or <% -%>
File Module (tp.file)
<% tp.file.title %> # Filename without extension
<% tp.file.path() %> # Full path
<% tp.file.path(true) %> # Relative path
<% tp.file.folder() %> # Parent folder name
<% tp.file.folder(true) %> # Full folder path
<% tp.file.content %> # File content
<% tp.file.selection() %> # Selected text
<% tp.file.cursor(1) %> # Cursor position (numbered)
<% tp.file.cursor_append("text") %> # Append at cursor
<%* tp.file.create_new(template, filename, folder) %> # Create file
<%* tp.file.move("/new/path") %> # Move file
<%* tp.file.rename("new-name") %> # Rename file
<%* await tp.file.exists("path") %> # Check if existsDate Module (tp.date)
<% tp.date.now() %> # Current datetime
<% tp.date.now("YYYY-MM-DD") %> # Formatted date
<% tp.date.now("HH:mm") %> # Time only
<% tp.date.now("YYYY-MM-DD", 7) %> # 7 days from now
<% tp.date.now("YYYY-MM-DD", -1) %> # Yesterday
<% tp.date.now("dddd") %> # Day name
<% tp.date.now("MMMM") %> # Month name
<% tp.date.yesterday("YYYY-MM-DD") %> # Yesterday
<% tp.date.tomorrow("YYYY-MM-DD") %> # Tomorrow
<% tp.date.weekday("YYYY-MM-DD", 0) %> # This week's SundayDate Format Tokens
| Token | Output |
|---|---|
| YYYY | 2025 |
| YY | 25 |
| MMMM | December |
| MMM | Dec |
| MM | 12 |
| M | 12 |
| dddd | Monday |
| ddd | Mon |
| DD | 09 |
| D | 9 |
| HH | 14 (24h) |
| hh | 02 (12h) |
| mm | 30 |
| ss | 45 |
| A | PM |
| a | pm |
System Module (tp.system)
<% tp.system.clipboard() %> # Clipboard content
<% tp.system.prompt("Question") %> # User input
<% tp.system.prompt("Question", "default") %> # With default
<% tp.system.suggester(options, values) %> # Dropdown selectionSuggester Examples
<%*
const options = ["Option 1", "Option 2", "Option 3"];
const selected = await tp.system.suggester(options, options);
tR += selected;
%>
<%*
const types = {"Project": "01 - Projects", "Area": "02 - Areas"};
const selected = await tp.system.suggester(Object.keys(types), Object.values(types));
%>Frontmatter Module (tp.frontmatter)
<% tp.frontmatter.title %> # Frontmatter field
<% tp.frontmatter["field-name"] %> # Field with hyphen
<% tp.frontmatter.tags %> # Array fieldWeb Module (tp.web)
<% tp.web.daily_quote() %> # Random quote
<% tp.web.random_picture() %> # Random image URL
<% tp.web.random_picture("200x200") %> # Sized imageUser Scripts
Create scripts in user scripts folder and call:
<% tp.user.my_script() %>
<% tp.user.my_script(arg1, arg2) %>JavaScript Execution
Full JavaScript support:
<%*
const today = tp.date.now("YYYY-MM-DD");
const title = tp.file.title;
if (title.startsWith("Daily")) {
tR += "# Daily Note\n";
} else {
tR += "# " + title + "\n";
}
%>Variables
tR: Template result string (append to output)tp: Templater object with all modules
Template Examples
Daily Note Template
---
created: <% tp.date.now("YYYY-MM-DDTHH:mm") %>
updated: <% tp.date.now("YYYY-MM-DDTHH:mm") %>
title: "<% tp.file.title %>"
type: daily-note
tags:
- daily
- <% tp.date.now("YYYY") %>
- <% tp.date.now("YYYY-MM") %>
aliases:
- "<% tp.date.now("YYYY-MM-DD") %>"
---
# Daily Note - <% tp.date.now("YYYY-MM-DD") %>
## Tasks
- [ ] <% tp.file.cursor(1) %>
## Journal
## Navigation
<< [[<% tp.date.now("YYYY-MM-DD", -1) %>]] | **Today** | [[<% tp.date.now("YYYY-MM-DD", 1) %>]] >>New Note Template
---
created: <% tp.date.now("YYYY-MM-DDTHH:mm") %>
updated: <% tp.date.now("YYYY-MM-DDTHH:mm") %>
title: "<% tp.file.title %>"
type: <%* const types = ["note", "project", "reference", "zettelkasten"]; tR += await tp.system.suggester(types, types); %>
status: draft
tags:
- <% tp.file.cursor(2) %>
---
# <% tp.file.title %>
<% tp.file.cursor(1) %>Meeting Notes Template
---
created: <% tp.date.now("YYYY-MM-DDTHH:mm") %>
type: meeting
attendees:
-
project: "[[<% await tp.system.prompt("Project") %>]]"
---
# Meeting: <% await tp.system.prompt("Meeting Topic") %>
**Date**: <% tp.date.now("YYYY-MM-DD HH:mm") %>
**Attendees**:
## Agenda
1. <% tp.file.cursor(1) %>
## Notes
## Action Items
- [ ]
## Follow-upBook Notes Template
---
created: <% tp.date.now("YYYY-MM-DDTHH:mm") %>
type: book
title: "<% await tp.system.prompt("Book Title") %>"
author: "<% await tp.system.prompt("Author") %>"
status: reading
rating: /5
---
# <% tp.frontmatter.title %>
**Author**: <% tp.frontmatter.author %>
**Started**: <% tp.date.now("YYYY-MM-DD") %>
## Summary
## Key Takeaways
1.
2.
3.
## Quotes
## Notes
<% tp.file.cursor(1) %>#!/usr/bin/env python3
"""
Create a daily note with proper frontmatter and structure.
Usage: python create-daily-note.py [YYYY-MM-DD]
If no date provided, uses today's date.
"""
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
def create_daily_note(date_str: str = None, vault_path: str = "."):
"""Create a daily note for the specified date."""
if date_str:
target_date = datetime.strptime(date_str, "%Y-%m-%d")
else:
target_date = datetime.now()
# Calculate dates
yesterday = target_date - timedelta(days=1)
tomorrow = target_date + timedelta(days=1)
review_date = target_date + timedelta(days=7)
# Format dates
date_formatted = target_date.strftime("%Y-%m-%d")
filename = target_date.strftime("%Y%m%d")
year = target_date.strftime("%Y")
month = target_date.strftime("%m")
year_month = target_date.strftime("%Y-%m")
created = target_date.strftime("%Y-%m-%dT09:00")
yesterday_str = yesterday.strftime("%Y-%m-%d")
tomorrow_str = tomorrow.strftime("%Y-%m-%d")
review_str = review_date.strftime("%Y-%m-%d")
# Create directory structure
daily_dir = Path(vault_path) / "06 - Daily" / year / month
daily_dir.mkdir(parents=True, exist_ok=True)
# Note path
note_path = daily_dir / f"{filename}.md"
if note_path.exists():
print(f"Daily note already exists: {note_path}")
return str(note_path)
# Generate content
content = f'''---
created: {created}
updated: {created}
toc: true
title: "{filename}"
template-type: Note
template-version: "1.8"
type: daily-note
status: true
weather: ""
location: ""
mood: ""
energy: /10
score: ""
tags:
- daily
- journal
- {year}
- {year_month}
aliases:
- "{date_formatted}"
date_formatted: {date_formatted}
topics:
- "[[daily]]"
- "[[journal]]"
- "[[{year}]]"
- "[[{year_month}]]"
related:
- "[[{yesterday_str}]]"
- "[[{tomorrow_str}]]"
cssclasses:
- daily
review_date: {review_str}
tasks_completed: 0
tasks_total: 0
sleep_hours: ""
exercise: false
---
# Daily Note - {date_formatted}
### Journal
#### TIME
...
***
### Tasks
- [ ] Task 1
- [ ] Task 2
- [ ] Task 3
# errors
# reference
```dataview
LIST
FROM "Clippings"
WHERE file.cday = this.file.cday
SORT file.ctime DESC
```
# navigate
<< [[{yesterday_str}]] | **Today** | [[{tomorrow_str}]] >>
'''
# Write file
note_path.write_text(content)
print(f"Created daily note: {note_path}")
return str(note_path)
if __name__ == "__main__":
date_arg = sys.argv[1] if len(sys.argv) > 1 else None
vault_path = os.environ.get("VAULT_PATH", ".")
create_daily_note(date_arg, vault_path)
#!/usr/bin/env python3
"""
Find orphan notes (notes with no incoming or outgoing links).
Usage: python find-orphan-notes.py [vault_path]
"""
import os
import re
import sys
from pathlib import Path
from collections import defaultdict
def extract_links(content: str) -> set:
"""Extract wikilinks from note content."""
# Match [[link]] or [[link|alias]]
pattern = r'\[\[([^\]|]+)(?:\|[^\]]+)?\]\]'
matches = re.findall(pattern, content)
return set(matches)
def find_orphan_notes(vault_path: str = "."):
"""Find notes with no links to or from other notes."""
vault = Path(vault_path)
# Exclude folders
exclude_folders = {'.obsidian', '.git', '.claude', 'node_modules', '99 - Meta'}
# Track links
outgoing_links = defaultdict(set) # note -> set of linked notes
incoming_links = defaultdict(set) # note -> set of notes linking to it
all_notes = set()
# Find all markdown files
for md_file in vault.rglob("*.md"):
# Skip excluded folders
if any(ex in md_file.parts for ex in exclude_folders):
continue
note_name = md_file.stem
all_notes.add(note_name)
try:
content = md_file.read_text(encoding='utf-8')
links = extract_links(content)
outgoing_links[note_name] = links
for link in links:
# Handle links with path (folder/note)
link_name = Path(link).stem if '/' in link else link
incoming_links[link_name].add(note_name)
except Exception as e:
print(f"Error reading {md_file}: {e}", file=sys.stderr)
# Find orphans
orphans = []
for note in all_notes:
has_outgoing = bool(outgoing_links.get(note, set()) & all_notes)
has_incoming = bool(incoming_links.get(note, set()))
if not has_outgoing and not has_incoming:
orphans.append(note)
# Output results
print(f"Total notes: {len(all_notes)}")
print(f"Orphan notes: {len(orphans)}")
print()
if orphans:
print("Orphan notes (no incoming or outgoing links):")
for orphan in sorted(orphans):
print(f" - {orphan}")
else:
print("No orphan notes found!")
return orphans
if __name__ == "__main__":
vault_path = sys.argv[1] if len(sys.argv) > 1 else "."
find_orphan_notes(vault_path)
#!/usr/bin/env python3
"""
Generate an overview of all tags used in the vault.
Usage: python tag-overview.py [vault_path] [--output FILE]
"""
import os
import re
import sys
import argparse
from pathlib import Path
from collections import defaultdict
import yaml
def extract_frontmatter_tags(content: str) -> list:
"""Extract tags from YAML frontmatter."""
tags = []
# Match frontmatter
fm_match = re.match(r'^---\s*\n(.*?)\n---', content, re.DOTALL)
if fm_match:
try:
fm = yaml.safe_load(fm_match.group(1))
if fm and 'tags' in fm:
fm_tags = fm['tags']
if isinstance(fm_tags, list):
tags.extend(fm_tags)
elif isinstance(fm_tags, str):
tags.append(fm_tags)
except:
pass
return tags
def extract_inline_tags(content: str) -> list:
"""Extract inline #tags from content."""
# Match #tag but not in code blocks or links
pattern = r'(?<![`\[])#([a-zA-Z0-9_-]+(?:/[a-zA-Z0-9_-]+)*)'
return re.findall(pattern, content)
def generate_tag_overview(vault_path: str = ".", output_file: str = None):
"""Generate tag usage statistics."""
vault = Path(vault_path)
# Exclude folders
exclude_folders = {'.obsidian', '.git', '.claude', 'node_modules'}
# Track tags
tag_usage = defaultdict(list) # tag -> list of notes using it
# Find all markdown files
for md_file in vault.rglob("*.md"):
# Skip excluded folders
if any(ex in md_file.parts for ex in exclude_folders):
continue
try:
content = md_file.read_text(encoding='utf-8')
# Get all tags
all_tags = set()
all_tags.update(extract_frontmatter_tags(content))
all_tags.update(extract_inline_tags(content))
# Normalize tags (remove # prefix if present)
for tag in all_tags:
clean_tag = tag.lstrip('#')
if clean_tag:
tag_usage[clean_tag].append(md_file.stem)
except Exception as e:
print(f"Error reading {md_file}: {e}", file=sys.stderr)
# Sort by usage count
sorted_tags = sorted(tag_usage.items(), key=lambda x: len(x[1]), reverse=True)
# Generate output
output_lines = []
output_lines.append("# Tag Overview")
output_lines.append("")
output_lines.append(f"Total unique tags: {len(sorted_tags)}")
output_lines.append("")
output_lines.append("## Tags by Usage")
output_lines.append("")
output_lines.append("| Tag | Count |")
output_lines.append("|-----|-------|")
for tag, notes in sorted_tags:
output_lines.append(f"| #{tag} | {len(notes)} |")
output_lines.append("")
output_lines.append("## Tag Details")
output_lines.append("")
for tag, notes in sorted_tags[:20]: # Top 20 detailed
output_lines.append(f"### #{tag} ({len(notes)} notes)")
output_lines.append("")
for note in sorted(notes)[:10]: # First 10 notes
output_lines.append(f"- [[{note}]]")
if len(notes) > 10:
output_lines.append(f"- ... and {len(notes) - 10} more")
output_lines.append("")
output = "\n".join(output_lines)
if output_file:
Path(output_file).write_text(output)
print(f"Tag overview saved to: {output_file}")
else:
print(output)
return dict(tag_usage)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Generate tag overview for Obsidian vault')
parser.add_argument('vault_path', nargs='?', default='.', help='Path to vault')
parser.add_argument('--output', '-o', help='Output file path')
args = parser.parse_args()
generate_tag_overview(args.vault_path, args.output)
Related skills
FAQ
What does obsidian-vault-management help with?
obsidian-vault-management helps developers organize, refactor, link, and maintain Obsidian vaults containing engineering notes, ADRs, runbooks, and project docs so content stays searchable and consistently structured.
Can obsidian-vault-management fix broken Obsidian links?
obsidian-vault-management supports repairing and standardizing wikilinks and cross-references across an engineering vault, reducing broken navigation between ADRs, runbooks, and project pages.