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
- Tagging means the
.otagsidecar files, never the files themselves. Useadd,remove,cp,mvandrmfor every tagging request. Runwrite(includingwrite 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. - Your shell is not a terminal, so
otag lsprints bare paths only, one per line, with no tags. That is meant for piping. To see the tags themselves, read the.otagfile in the file's folder (see The .otag format), e.g.grep -F 'song.mp3 --- ' music/.otag, orfind . -name .otagto see them all. - Quote every
#tag('#lofi'), because an unquoted#starts a shell comment and the tag silently disappears. - Words without a prefix become the note.
otag add f.mp3 '#lofi' first takesets the note tofirst take. Adding again merges the tags and replaces the note.removecan only remove projects, contexts and tags: to change a note,adda new one; to drop it, edit the.otagline. - Searches match partially by default:
#lofinds#lofiand#lowkey. Use-efor whole terms before any bulk change based on a search. - Ask the user first before
purge(deletes every.otagin a tree, irreversibly),rm -f/mv -f/cp -f(act on the files themselves) andscan --replace(overwrites entries).writeneeds an explicit request anyway (rule 1), because it modifies the files' contents. - 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 plainmvleaves the entry behind, andotag mvcan't fix that afterwards, since it needs the source to exist. Instead, re-add the tags from the old.otagline at the new path, then runotag cleanto 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 withotag import tags.txt