Export vault note
Production-ready AI agent skills for Claude Code, Codex, Cursor, and the Agent Skills Open Standard
npx -y skills add psenger/ai-agent-skills --skill export-vault-noteAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 9 stars9 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Exports a single Obsidian vault note and all its linked images into a portable zip or tar.gz archive, preserving vault-root-relative paths so the archive unpacks correctly anywhere. Use only when the user explicitly invokes /export-vault-note.
SKILL.md
6.4 KB, as published. Nobody here has run it
Export Vault Note
This skill runs inside an Obsidian vault. It bundles a single Markdown note and every image it links into a portable archive placed at a location of the user's choosing (defaulting to one directory above the vault root).
Handles two image link styles:
- Wiki-style:
![[image.png]]or![[subfolder/image.png]] - Inline Markdown:

Example:
/export-vault-note projects/blog/my-post.md zip ~/Desktop
Step 1 — Parse Arguments
Read $ARGUMENTS. Extract:
note_path— vault-relative path to the Markdown file (e.g.ai/notes/my-note.md)format—ziportaroutput_dir— absolute or relative path where the archive should be saved
If note_path is missing, ask: "Which note do you want to export? Provide the vault-relative path, e.g. ai/notes/my-note.md."
If format is not provided, ask: "Do you want a zip or tar archive?"
If output_dir is not provided, tell the user: "The archive will be saved to one directory above the vault root by default. Is that OK, or do you want a different location?" Wait for confirmation or a new path before continuing.
Step 2 — Locate the Vault Root
Walk up the directory tree from the current working directory looking for a .obsidian folder. If the note path given is absolute, also try walking up from its parent directory.
python3 -c "
import sys
from pathlib import Path
for start in sys.argv[1:]:
p = Path(start).resolve()
for candidate in [p] + list(p.parents):
if (candidate / '.obsidian').is_dir():
print(candidate)
raise SystemExit(0)
print('')
" "." "<absolute-note-parent-if-known>"
If the output is empty (no .obsidian found), ask: "I couldn't detect the vault root automatically. What is the absolute path to your vault root?"
Step 3 — Run the Export
Write the script below to /tmp/export_vault_note.py and run it. The script is embedded directly so the skill works regardless of install location.
cat > /tmp/export_vault_note.py << 'PYEOF'
import sys
import re
import zipfile
import tarfile
from pathlib import Path
def find_vault_wide(vault_root, name):
for p in vault_root.rglob(name):
return p
return None
def collect_images(vault_root, note_path):
content = note_path.read_text(encoding="utf-8")
note_dir = note_path.parent
images = []
warnings = []
seen = set()
wiki = re.findall(r"!\[\[([^\]|#]+)", content)
inline = re.findall(r"!\[[^\]]*\]\(([^)#\s]+)", content)
for ref in wiki + inline:
ref = ref.strip()
if not ref or ref in seen:
continue
seen.add(ref)
if re.match(r"https?://", ref):
warnings.append(f"Skipping external image: {ref}")
continue
abs_path = None
if "/" in ref or "\\" in ref:
for candidate in [vault_root / ref, note_dir / ref]:
if candidate.exists():
abs_path = candidate
break
else:
found = find_vault_wide(vault_root, ref)
if found:
abs_path = found
print(f"Resolved bare link '{ref}' -> {abs_path.relative_to(vault_root)}")
if abs_path is None:
warnings.append(f"Warning: image not found, skipping: {ref}")
continue
try:
vault_rel = str(abs_path.relative_to(vault_root))
images.append((abs_path, vault_rel))
except ValueError:
warnings.append(f"Warning: image outside vault, skipping: {abs_path}")
return images, warnings
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <vault_root> <note_rel_path> [zip|tar] [output_dir]", file=sys.stderr)
sys.exit(1)
vault_root = Path(sys.argv[1]).resolve()
note_rel = sys.argv[2]
fmt = sys.argv[3] if len(sys.argv) > 3 else "zip"
output_dir = Path(sys.argv[4]).resolve() if len(sys.argv) > 4 else vault_root.parent
if fmt not in ("zip", "tar"):
print(f"Error: format must be 'zip' or 'tar', got '{fmt}'", file=sys.stderr)
sys.exit(1)
if not vault_root.is_dir():
print(f"Error: vault root not found: {vault_root}", file=sys.stderr)
sys.exit(1)
note_path = vault_root / note_rel
if not note_path.exists():
print(f"Error: note not found: {note_path}", file=sys.stderr)
sys.exit(1)
if not output_dir.exists():
output_dir.mkdir(parents=True)
images, warnings = collect_images(vault_root, note_path)
stem = note_path.stem
if fmt == "tar":
archive_path = output_dir / f"{stem}.tar.gz"
with tarfile.open(archive_path, "w:gz") as tar:
tar.add(note_path, arcname=note_rel)
for abs_path, vault_rel in images:
tar.add(abs_path, arcname=vault_rel)
else:
archive_path = output_dir / f"{stem}.zip"
with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zf:
zf.write(note_path, note_rel)
for abs_path, vault_rel in images:
zf.write(abs_path, vault_rel)
print(f"Archive: {archive_path}")
print(f"Note: {note_rel}")
for _, vault_rel in images:
print(f"Image: {vault_rel}")
for w in warnings:
print(w)
if __name__ == "__main__":
main()
PYEOF
python3 /tmp/export_vault_note.py "<vault_root>" "<note_rel_path>" "<format>" "<output_dir>"
Substitute all four values. If the user accepted the default output location, resolve and pass vault_root.parent explicitly.
Step 4 — Report Results
Parse the script output and report:
- Archive created: full path
- Note inside archive: vault-relative path
- Images included: one line each
- Warnings: skipped images, resolved bare links — display each prominently so the user can investigate if needed