orvanitag/SKILL.md

101 lines
8.3 KiB
Markdown
Raw Normal View History

2026-10-25 10:42:05 +02:00
---
name: otag
description: >-
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.
```sh
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 `./-`.
```sh
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`