agentsclimarketplace

Export vault note

Skill psenger/ai-agent-skills/skills/export-vault-note

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.From its SKILL.md

Install
npx -y skills add psenger/ai-agent-skills --skill export-vault-note

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things 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.
  • runs commandsInstructs the agent to run 3 commands, including `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(): ` and 2 more.

SKILL.md

6.4 KB, ~1.5k tokens by cl100k_base, 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: ![alt text](path/to/image.png)

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)
  • formatzip or tar
  • output_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

What ships with it: 3 files

5.8 KB alongside SKILL.md

.workspace/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.