orvanitag/SKILL.md
2026-10-25 10:42:05 +02:00

8.3 KiB

name description
otag Teaches how to tag, search and organize files and folders with otag (OrvaniTagger, https://github.com/randogoth/orvanitag), a todo.txt style tagger that stores +projects, @contexts, #tags and a note per file in plain `.otag` files next to them. Use this skill whenever a user asks to tag, label, categorize, find or list files by tag, bulk-retag, move or copy files together with their tags, sync tags into files (ID3, PDF, Office, ebooks, archives, images, source comments, front matter) or into TagSpaces, or when a folder contains `.otag` or `.otag.toml` files. Install by copying this file to a skill directory, e.g. ~/.claude/skills/otag/SKILL.md.

Tagging files with otag

otag gives every file and folder todo.txt style categories: +project, @context, #tag, plus a free text note. There is no database. Each folder keeps the entries of its files and subfolders in a plain text .otag file, so tags travel with the folder when it is copied, synced or archived. The command is otag, installed e.g. with uv tool install 'orvanitag[all]'.

Rules that matter for agents

  1. Tagging means the .otag sidecar files, never the files themselves. Use add, remove, cp, mv and rm for every tagging request. Run write (including write tagspaces) only when the user explicitly asks to embed tags in files or sync them to TagSpaces, and only for the files they named; never as a side effect, a "helpful" extra step, or because a plugin exists for the file type.
  2. Your shell is not a terminal, so otag ls prints bare paths only, one per line, with no tags. That is meant for piping. To see the tags themselves, read the .otag file in the file's folder (see The .otag format), e.g. grep -F 'song.mp3 --- ' music/.otag, or find . -name .otag to see them all.
  3. Quote every #tag ('#lofi'), because an unquoted # starts a shell comment and the tag silently disappears.
  4. Words without a prefix become the note. otag add f.mp3 '#lofi' first take sets the note to first take. Adding again merges the tags and replaces the note. remove can only remove projects, contexts and tags: to change a note, add a new one; to drop it, edit the .otag line.
  5. Searches match partially by default: #lo finds #lofi and #lowkey. Use -e for whole terms before any bulk change based on a search.
  6. Ask the user first before purge (deletes every .otag in a tree, irreversibly), rm -f / mv -f / cp -f (act on the files themselves) and scan --replace (overwrites entries). write needs an explicit request anyway (rule 1), because it modifies the files' contents.
  7. Commands only accept existing files and folders. Flags go after the command. Errors print one line and exit with 1.

Commands

Every command has a short alias. otag -h and otag <command> -h show all arguments.

Command Does
add (a) FILE WORD... Adds projects, contexts, tags and a note to a file or folder
remove (r) FILE [WORD...] Removes the given projects, contexts and tags; without any, the whole entry
list (ls, l) [TERM...] Lists the tagged files under the current folder that match all terms; no terms list everything
copy (cp) / move (mv) SRC DEST Copies or moves the tags to another path; -f also copies or moves the file
del (rm) FILE Removes the entry; -f also deletes the file
clean (c) [PATH] Removes entries of files that no longer exist
purge (p) [PATH] Deletes all .otag and cache files in a tree (destructive)
scan (s) [PATH] Adds tags embedded in files (and TagSpaces sidecars) to their entries; --replace makes them replace the entries
write (w) PLUGIN FILE Writes a file's entry into the file itself; -c removes otag's embedded tags instead
export (e) [FILE] / import (i) [FILE] Writes all entries of the file's folder tree to a path --- tags text file, and adds the missing tags back from one

clean and scan work recursively; -n stays in the given folder. export collects the tree the export file is in, so export to a file inside that tree.

Searching

A term with a prefix only matches that category (@drums); a term without prefix matches any category and the note. All terms must match; -or matches any of them. Other flags: -e whole terms, --files or -d only files or folders, -p PATH searches another tree (paths are then printed with that prefix), -n skips subfolders, -t shows a table in a terminal. Results are relative to the current folder, shallow ones first, and folders have no trailing slash.

otag ls '#lofi' @drums          # files with both
otag ls -or +album +ep          # files in either project
otag ls -e '#draft' --files     # exactly #draft, files only

Batches through pipes

add, remove, del and write read paths from stdin, one per line, when the file is -. A failing path is reported without stopping the others, and the exit code is 1 at the end. A file literally named - is written ./-.

ls *.mp3 | otag add - +album '#lofi'
find . -name '*.pdf' | otag write pdf -
otag ls -e '#old' | otag add - '#new'      # rename a tag: add the new one ...
otag ls -e '#old' | otag remove - '#old'   # ... then remove the old one

ls dir prints names without dir/, so feed ls dir/* or find dir instead.

The .otag format

One line per entry: the name relative to the folder, ---, then projects, contexts, tags and the note, in that order. Folder names end with /. otag keeps the lines sorted by name and deletes the file once it is empty.

Boombap Loop.wav --- +album @drums #boombap #lofi
song.mp3 --- +album @drums #lofi #snare first take
stems/ --- @archive

Hand edits are fine as long as every line keeps ---; a line without it makes otag stop with an error naming file and line, until it is fixed. Note words starting with +, @ or # are read as tags. A tag is one word: spaces and commas are not possible.

Tags embedded in files

Only use this section when the user explicitly asks for it (rule 1); normal tagging stays in the .otag files. scan reads tags stored inside files; write PLUGIN FILE stores a file's entry in it, replacing what that plugin wrote before, so removed tags disappear on the next write. write -c clears otag's embedded tags; it is never done implicitly. A file without entry can only be cleared, not written.

Plugin Files Stored in
id3 .mp3 .wav .flac .ogg The comment tag (needs the audio extra)
pdf .pdf The keywords field (pdf extra)
office .docx .xlsx .pptx .odt .ods .odp, reading also .doc .xls .ppt The description field
ebook .epub .fb2 .cbz An otag line the readers ignore
frontmatter .md .markdown .txt projects, contexts, tags, note keys of the YAML front matter (markdown extra)
comment Source code, .html, .svg An otag: ... comment line near the top
archive .zip .tar .gz .tgz The archive or gzip comment, a pax header in tar
image .jpg .jpeg .png .gif An own JPEG, PNG or GIF comment
tagspaces Every file and folder The TagSpaces sidecar .ts/<name>.json (.ts/tsm.json for folders); file name tags like photo[a b].jpg are read only

Plugins refuse rather than damage: a comment field already used by another tool is kept, and the error says so. An optional .otag.toml limits plugins for its folder tree (plugins = ["id3", "pdf"], [id3] types = [".mp3"], [id3.map] tags = "genre").

Recipes

  • Tag a file: otag add report.pdf +q3 @work '#finance' final version
  • What is this file tagged with? grep -F 'report.pdf --- ' .otag
  • Move a file and keep its tags: otag mv -f report.pdf archive/. Moving or renaming with plain mv leaves the entry behind, and otag mv can't fix that afterwards, since it needs the source to exist. Instead, re-add the tags from the old .otag line at the new path, then run otag clean to drop the orphaned entry.
  • Import tags from file metadata or TagSpaces: otag scan music/
  • Show otag's tags in TagSpaces (only when asked): otag ls | otag write tagspaces -
  • Back up all tags of a tree: otag export tags.txt, restore with otag import tags.txt