The Ultimate Command-Line File Tagging System
Transform chaos into order with intelligent file organization
Features • Installation • MCP clients • Quick Start • Commands • Examples
Folder hierarchies force you to put a file in one place. Tags let a file belong to everything it actually is.
# One command to tag — one command to find
tm add report.pdf --tags work client-a q1 2024 final
tm search --tags work q1 # instantly surface itTagManager stores tags in a lightweight JSON sidecar (~/file_tags.json) — no database, no daemon required — and ships a full command set for search, bulk operations, smart filtering, an interactive network graph, and a file-system watcher that tags new files automatically.
| Category | What you get |
|---|---|
| Core | Add/remove/search tags, fuzzy path search, list & tree views |
| Bulk | Mass-tag with glob patterns, retag, bulk-remove — all with --dry-run |
| Auto-tag | Extension-based tag suggestions (60+ file types built-in) |
| Aliases | Normalize tag variants: py → python, js → javascript |
| Presets | Save named tag bundles and apply them in one flag |
| Filters | Duplicate sets, orphans, similarity (Jaccard), clusters, isolated files |
| Stats | Tag frequency, co-occurrence, ASCII bar charts & histograms |
| Visualizer | Interactive 2D/3D network graph in your browser — click to open files |
| Watch mode | Auto-tag files the moment they land in a directory |
| Move tracking | tm mv keeps tag records in sync when you rename files |
| Config | Full dot-notation config system with export/import |
| Thin GUI | tm gui — local browser UI for tags + search (/gui); dry-run preview page at /preview |
| Shell completion | Typer/bash/zsh tab-complete tags, presets, aliases; Fish: completions/tm.fish |
pip install tagmanager-cliCommands are available as both tm (short) and tagmanager (long form).
Watch mode requires watchdog:
pip install tagmanager-cli[watch]
# or separately
pip install watchdoggit clone https://github.com/davidtbilisi/TagManager.git
cd TagManager
pip install -e .To expose TagManager to Cursor, Claude Desktop, ChatGPT (where local stdio is supported), OpenAI Codex, or any MCP client over stdio, install the optional SDK:
pip install "tagmanager-cli[mcp]"
# or, from a clone:
pip install -e ".[mcp]"See MCP clients for wiring it into your tools.
CI and hook examples (GitHub Actions, uvx, sample pre-commit): tasks/recipes/README.md.
PyPI releases: a v* tag triggers .github/workflows/release.yml (build + publish). After bumping version and release_notes.md, run ./publish.sh release (gh under the hood) — see docs/RELEASE_CI.md.
TagManager ships a Model Context Protocol server (tagmanager/mcp_stdio.py) that exposes tools such as listing tags, searching files by tag, reading tags for a path, and adding tags (default dry run). You can run it manually as:
tm-mcp # console script, if installed
# or
tm mcp # same server via the main CLI- Install the MCP extra (see Optional: MCP).
- Open the TagManager repository folder as your workspace in Cursor (the config below relies on
${workspaceFolder}). - This repo includes
.cursor/mcp.json, which registers a server namedtagmanagerusing:python -m tagmanager.mcp_stdioPYTHONPATH=${workspaceFolder}so a local clone works without a separatepip installof the package into that interpreter.
- In Cursor, open Settings → MCP (or the MCP panel), then refresh or restart Cursor so the project server is loaded.
- If the server fails to start, point
commandin.cursor/mcp.jsonat the same Python executable you used forpip install(on Windows you can usepywithargslike["-3", "-m", "tagmanager.mcp_stdio"]).
Claude Desktop reads its own MCP config file (not .cursor/mcp.json). In the app, use Settings → Developer → Edit Config to open it (or edit the path below manually).
| OS | File |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
- Install the MCP extra in the Python environment you will reference below.
- Quit Claude Desktop before editing the file.
- Add or merge a
mcpServersentry. Example when you develop from a clone (replace the path with your real repo directory):
{
"mcpServers": {
"tagmanager": {
"command": "python",
"args": ["-m", "tagmanager.mcp_stdio"],
"env": {
"PYTHONPATH": "/absolute/path/to/TagManager"
}
}
}
}If tagmanager-cli[mcp] is installed in that Python environment and imports resolve globally, you can omit env and use only "command": "python" and "args": ["-m", "tagmanager.mcp_stdio"]. Alternatively, if the tm-mcp script is on PATH for the same environment:
{
"mcpServers": {
"tagmanager": {
"command": "tm-mcp",
"args": []
}
}
}- Save the file and start Claude Desktop again; enable the tagmanager MCP server in the app if prompted. Use the developer logs or the tools / hammer control in the chat UI to confirm the server loaded.
TagManager’s MCP server speaks stdio (a local subprocess). How that maps to ChatGPT depends on which product you use.
ChatGPT custom apps (developer mode) connect to an MCP server over a remote URL (for example SSE or streamable HTTP), not by spawning your local python -m tagmanager.mcp_stdio directly. This repository does not ship an HTTP MCP bridge.
To use TagManager from web ChatGPT you would need a trusted network path from OpenAI’s infrastructure to your tools (for example a small stdio → HTTP gateway you control, or a hosted deployment), then register that URL under Settings → Apps per your workspace rules. Plan and admin requirements change over time; start from OpenAI’s docs: Developer mode and MCP apps, ChatGPT developer mode.
Some ChatGPT Desktop builds load MCP servers from a JSON file on disk, similar to Claude Desktop. Community-reported locations (confirm in your app version under Settings / developer options):
| OS | File (may vary by release) |
|---|---|
| macOS | ~/Library/Application Support/ChatGPT/chatgpt_mcp_config.json |
| Windows | %APPDATA%\OpenAI\ChatGPT\chatgpt_mcp_config.json |
If that file exists and expects the same mcpServers shape as Claude, reuse the JSON example from the Claude Desktop section (same command / args / env as there). Fully quit and restart the desktop app after edits.
OpenAI Codex documents stdio MCP servers in ~/.codex/config.toml (or a trusted project’s .codex/config.toml). Example for a local clone:
[mcp_servers.tagmanager]
command = "python"
args = ["-m", "tagmanager.mcp_stdio"]
[mcp_servers.tagmanager.env]
PYTHONPATH = "/absolute/path/to/TagManager"Use the same Python you used for pip install "tagmanager-cli[mcp]". Then use Codex’s MCP UI or codex mcp to verify the server starts.
Further reading: Cursor MCP, Connecting local MCP servers (Claude Desktop), OpenAI Codex MCP.
# Tag a file (auto-tags by extension too)
tm add main.py --tags backend core
# Tag everything matching a glob
tm bulk add "src/**/*.py" --tags python
# Find files
tm search --tags backend core # either tag
tm search --tags backend --match-all core # both tags
# Visual overview
tm ls --tree
tm tags --cloud
tm stats --chart
# Interactive network graph (opens in browser)
tm graph
# Watch a directory — auto-tag as files arrive
tm watch ~/Downloads --tags inbox
# Portable export (paths relative to project root)
tm export -o tags.json --relative-to .
# Thin browser GUI (localhost; opens browser — same tag DB as CLI)
tm gui
# tm gui --no-browser --port 8844
# Optional path jail: TAGMANAGER_GUI_ROOT=C:\your\projectThe web UI is a single page served over HTTP on your machine. It uses the same tag file as tm add, tm search, etc.—nothing is stored in the cloud.
- Run
tm guiin a terminal. By default the server listens on127.0.0.1:8844, your browser opens tohttp://127.0.0.1:8844/gui, and the UI loads. - Stop the server with Ctrl+C in that terminal (the process exits; refresh the tab afterward and it will fail to connect—that is expected).
- Without auto-opening a browser:
tm gui --no-browser(same host/port; open/guiyourself). - Custom bind or port:
tm gui --host 127.0.0.1 --port 8844(defaults are loopback + 8844).
A second page on the same server: http://127.0.0.1:8844/preview (adjust host/port if you changed them). Nothing on /preview writes the database by itself—it always calls the API with dry_run: true.
- Saved tag database — on load (and Refresh from server), fetches
GET /api/v1/tags: a sortable table of every saved path and its tags, plus a chip list of all distinct tag names, with a client-side filter (matches path or tag text). - Preview add tags — enter a file path, optional tags to add, and the same no auto-tag / no aliases / no content toggles as
/gui. Preview merge shows before and after tag sets (after includes extension auto-tags and alias resolution when those options are off). - Preview remove / clear / drop — pick a mode and (for “one tag”) a tag name; the server returns a JSON payload describing what would happen.
The main /gui page links here; the terminal banner also prints the preview URL when the server starts.
JSON-only HTTP (no HTML page): tm serve or tm server on port 8765 by default—the same API routes work (/api/v1/...). Use that when you only need scripts or curl, not the browser form.
File — one path at a time (a file path your OS can resolve):
| Control | What it does |
|---|---|
| Path | Absolute or relative path to the file. After Load tags, the server may echo back a normalized absolute path. |
| Dry-run | When checked, Add tags and remove actions do not write the database; you get a preview-style success message. Uncheck to apply changes. |
| Load tags | GET current tags for that path (same data as tm path for that file). |
| Tags to add | Comma-separated list, same idea as tm add … --tags a,b. Then Add tags. |
| No auto-tag / No aliases / No content rules | Match tm add’s --no-auto, --no-aliases, and skipping content-based rules—useful when you want only the tags you typed. |
| Remove one tag… | Prompts for a tag name; removes that tag from the file (like removing one chip). |
| Clear all tags on file | Empties the tag list but keeps the path in the database (tm remove --path … --all-tags). Confirms before running. |
| Remove file from DB | Drops the path entirely from the tag store (tm remove --path … without --all-tags). Confirms before running—this is stronger than “clear tags”. |
Search — same tag logic as the CLI for a multi-tag query:
| Control | What it does |
|---|---|
| Tags | Comma-separated tag names to search for. |
| Match all (AND) | Checked: file must have every listed tag. Unchecked: file needs any listed tag (OR), same spirit as tm search without --match-all. |
| Search | Lists matching file paths below the button (paths may be truncated per your tm ls / list display config). |
Status text (green/red) under the form shows the last result or error. If TAGMANAGER_GUI_ROOT is set to an absolute directory, paths outside that tree are rejected for GUI operations (safety rail for shared machines).
- Default binding is loopback only. There is no login; anyone who can open the URL on your machine can use the UI if they reach the port.
- Do not use
--host 0.0.0.0on untrusted networks unless you understand you are exposing the API.
Mutations go through tagmanager/app/gui_handlers.py (same load_tags / save_tags stack as the CLI). HTML assets: tagmanager/app/thin_gui.html, tagmanager/app/preview_page.html. REST-style discovery: open http://127.0.0.1:8844/ or …/api while the server is running for a short JSON list of routes (includes GET /preview, GET /api/v1/files/tags?path= for scripts).
tm add <file> --tags <tag> [<tag>...] # basic
tm add <file> --tags python --preset webproject # combine with preset
tm add <file> --no-auto # skip extension-based auto-tags
tm add <file> --no-aliases # skip alias resolutiontm search --tags python web # files with EITHER tag
tm search --tags python --match-all # files with ALL listed tags
tm search --path /projects/ # by path fragment
tm search --tags python --exact # exact tag match (no fuzzy)tm ls # flat table
tm ls --tree # directory tree with inline tagstm tags # list all tags
tm tags --search py # filter tags by name
tm tags --cloud # frequency cloud
tm tags --where python # which files carry this tagtm stats # summary
tm stats --chart # ASCII bar charts
tm stats --tag python # deep-dive on one tag (co-occurrence, file types)Opens a self-contained HTML graph in your default browser.
tm graph # tag co-occurrence graph, 2D
tm graph --3d # start in 3D (toggle button in UI too)
tm graph --mode file # file similarity graph (Jaccard)
tm graph --mode mixed # bipartite file↔tag graph
tm graph --export gexf # also export tag_network.gexf (Gephi)
tm graph --export graphml # for Cytoscape / yEd
tm graph --min-weight 2 # only show edges with ≥2 co-occurrences
tm graph --output ~/graph.html # save to a specific pathIn the browser UI:
- Sidebar with 10 live filters (search, tag/extension multi-select, weight & degree sliders, cluster highlight, color-by)
- 2D / 3D toggle — WebGL via Three.js, handles thousands of nodes
- Click any file node → opens it in your OS file explorer
- Download GEXF / GraphML buttons (embedded, no server needed)
Monitors a directory with watchdog and tags files as they arrive.
tm watch # watch current dir
tm watch ~/Downloads # watch a specific dir
tm watch . --tags inbox # always add "inbox" to every new file
tm watch . --preset webproject # apply a saved preset
tm watch . --no-auto # skip extension auto-tagging
tm watch . --clean-on-delete # remove tag entry when file is deleted
tm watch . --no-recursive # top-level only
tm watch . --ignore "*.log" # extra ignore patterns
tm watch . --plain # plain text output (no Rich live display)Rich live display shows a colour-coded event log (✚ created, → moved, ✖ deleted) with resolved tags. Press Ctrl+C to stop.
tm filter duplicates # files with identical tag sets
tm filter orphans # files with no tags
tm filter similar <file> # files similar to this one (Jaccard)
tm filter clusters # group files by shared tag
tm filter isolated # files that share few tags with otherstm bulk add "*.py" --tags python # tag by glob
tm bulk remove --tag deprecated # remove a tag from all files
tm bulk retag --from js --to javascript # rename a tag everywhere
# All bulk commands support --dry-run
# With journal on (TAGMANAGER_JOURNAL=1 or journal.enabled), retag is undoable: tm undo
# Aliases (tm alias) normalize names at tag time; retag rewrites stored tags in the DB# Aliases — normalize tag variants
tm alias add py python
tm alias list
tm alias remove py
# Presets — named tag bundles
tm preset save webproject --tags python django web
tm preset apply webproject app.py
tm preset list
# Move tracking — keep the DB in sync
tm mv old/path.py new/path.py
tm clean # remove entries for deleted files
tm clean --dry-run # previewtm config list # all settings
tm config set display.emojis false
tm config set search.fuzzy_threshold 0.8
tm config export --file settings.json
tm config import team_settings.json
tm config reset # back to defaultsTag co-occurrence network — 42 nodes, 180 edges
Sidebar filters: search · tag multiselect · min-weight slider · degree range
color-by cluster · highlight cluster · show/hide node types
2D ↔ 3D toggle | Download GEXF | Download GraphML
Click file node → opens in Finder/Explorer
Watching: /home/user/Downloads
Recursive: True | Auto-tag: True | Clean-on-delete: False
Always add tags: ['inbox']
Press Ctrl+C to stop.
[14:02:11] ✚ created ✓ report_q1.pdf [inbox, pdf, document]
[14:02:45] ✚ created ✓ script.py [inbox, python]
[14:03:10] → moved ✓ script.py → processed/script.py
☁️ Tag Cloud
★ python(15) ◆ web(8) ● documentation(5) • config(3) · backup(1)
└── 📁 projects/
├── 📁 backend/
│ └── 📄 api.py 🏷️ [python, web, api, core]
└── 📁 docs/
└── 📄 README.md 🏷️ [docs, markdown]
tagmanager/
├── cli.py # Typer CLI — all commands registered here
└── app/
├── add/ # tm add
├── bulk/ # tm bulk
├── filter/ # tm filter
├── search/ # tm search
├── stats/ # tm stats
├── graph/ # tm graph (HTML generator, GEXF/GraphML export)
├── watch/ # tm watch (watchdog integration)
├── alias/ # tm alias
├── preset/ # tm preset
├── autotag/ # extension → tag mappings
├── move/ # tm mv / tm clean
├── visualization/ # tree, cloud, ASCII charts
├── config/ # tm config
└── helpers.py # load_tags() / save_tags() — atomic JSON I/O
Storage: ~/file_tags.json (path configurable). Plain JSON — easy to inspect, back up, or version-control.
pytest tests/ -v # full suite (406 tests)
pytest tests/ --cov=tagmanager # with coverage
pytest tests/test_graph_service.py -v # graph module only
pytest tests/test_watch_service.py -v # watch module only406 tests across 21 test files. Watch mode integration tests require pip install watchdog.
- Fork the repo
- Create a feature branch
- Add tests
- Open a PR
Bug reports and feature requests: GitHub Issues
MIT — see LICENSE.
⭐ Star this repo if TagManager helps you stay organized!
Made by David Chincharashvili • Built with Typer and Rich