Skip to content

FEATURES — capability × strategy matrix

Strategies: wrap:<tool> (external binary via adapter) · lib:<pypi> · custom (pure Python) · degrade-if-missing · stretch. Tiers: MVP (must ship before flagship), v1 (shipped in v0.1.0 after MVP), v0.2.0 (shipped on feat/v0.2.0, specs 15–21), stretch (attempted last / cut candidates). File types: pdf md jpg jpeg png ico txt html json xml csv docx odt epub rtf xlsx (xlsm) eml mbox (mbx).

Capability Command Strategy Types Tier
File conversion carrel convert wrap:pandoc (md/html/txt + docx/odt/epub/rtf read and write), wrap:weasyprint (html→pdf), wrap:poppler pdftotext (pdf→txt/md), lib:Pillow + wrap:imagemagick (jpg/png/ico), custom (json↔csv↔xml via stdlib), lib:openpyxl via carrel[office] (xlsx→csv/json) all MVP (+ office in v0.2.0)
Text recognition (OCR) carrel ocr wrap:ocrmypdf (pdf), wrap:tesseract (images) pdf jpg jpeg png MVP
Diff / compare carrel diff custom (text unified diff, structural JSON/CSV diff), wrap:pdftotext (pdf text diff), lib:Pillow (image pixel/percent diff) all MVP
General editing carrel edit wrap:qpdf + lib:pypdf (pdf merge/split/rotate/extract-pages), lib:Pillow (image resize/rotate/crop/strip), custom (text/md find-replace, json set/del via dotted path) all MVP
Context engineering carrel pack custom (tree+content dump, include/exclude globs, ignore-file aware, chunking, char/token estimates; md/xml/json output formats) all text-ish; binaries summarized via inspect MVP
Query-driven & version-control-aware packing carrel pack --query / --top / --since / --changed / --dedupe-content / --tokenizer exact / --outline custom over FTS5 ranking (relevance order + per-file score); wrap:git for changed-file lists; !pattern negation in ignore files; lib:tiktoken (o200k_base) via carrel[tokens]; ast-based outline for .py, heading outline for .md all indexed types (--query); all (--since, --outline) v0.2.0
Object/metadata inspection carrel inspect wrap:exiftool (degrade→lib:Pillow EXIF + lib:pypdf metadata), custom (magic bytes, structure summaries for json/xml/csv/html, docx core props, epub OPF, xlsx sheets) all MVP (+ office in v0.2.0)
Indexing & search carrel index / carrel search custom (SQLite FTS5; per-type text extractors reuse convert/ocr paths); index --status reports stale rows all MVP
Catalog: schema migrations, export/import, status carrel catalog export/import/status, carrel index --status custom (PRAGMA user_version + ordered MIGRATIONS; deterministic JSON document of tags + notes; merge or --replace) — v0.2.0
Thumbnails carrel thumb wrap:pdftoppm (pdf), lib:Pillow (images), wrap:imagemagick fallback; html thumb = render pdf→ppm pdf + images (+html via pdf) MVP
Folder watch carrel watch (--recursive --existing --stable --poll --done-dir --error-dir --log --print-service) lib:watchdog (inotify, or PollingObserver with --poll for /mnt/c and shares), custom rule→action mapping via core/actions.py; settle-wait, filing away, JSON log, systemd/schtasks service printer all MVP (+ v0.4.0 flags)
Batch runner carrel batch (--jobs --manifest --resume --dry-run --fail-fast --json-lines) custom (core/actions.py shell actions per file, ThreadPool parallelism, JSON-lines manifest; walks like index) all v0.4.0
Field extraction carrel fields (--profile --date-order --set --save) custom (core/money.py + core/dates.py + label heuristics over textextract; core/patterns.py for invoice/PO/IBAN/account; confidence per field) all text-bearing v0.4.0
Inbox intake carrel intake INBOX --to DEST (--apply --watch --by ym\|period --fiscal-start --template --tag) custom pipeline over fields + refs + rename + fsops + index + meta + tag; scans OCRed into a searchable copy with the original kept under _originals/ (D-014); dry-run default all v0.4.0
Template rename carrel rename --template '{date}_{vendor}_{ref}{ext}' custom (fields/meta/refs placeholders, slugified, collision-safe; desk rows follow via core/fsops.py, which organize --apply now uses too) all v0.4.0
Doctor / env probe carrel doctor custom (re-probes adapters, prints capability table + apt hints; shows via CARREL_BIN_* for pinned binaries and the extra to install for gated commands) — MVP
Whole desk over MCP carrel mcp custom stdlib JSON-RPC 2.0 on stdio: 14 tools (search, pack, inspect, tag, note, index, convert, diff, redact, doctor, meta, fields, mail, refs) delegating to the command impl functions + resource templates carrel://file/{path}, carrel://search/{query} all v1 (3 tools) → v0.2.0 (10 tools + resources) → v0.4.0 (14)
Install ergonomics carrel completion bash/zsh/fish; extras tui/office/tokens/all; CARREL_BIN_<NAME> override; git adapter lib:click completions (in-process); pyproject extras (D-007); env-var override (D-008) — v0.2.0
Dedupe carrel dedupe custom (BLAKE2 content hash groups; --near perceptual dHash for images, custom impl, no numpy) all v1
File/folder organization carrel organize custom (rules: by type/date/exif-date; dry-run default) all v1
Audiobook (TTS) carrel audiobook wrap:espeak-ng → wrap:ffmpeg (mp3/ogg, chapters from md headings); adapter prefers piper/edge-tts if present; documents narrate via textextract (pandoc) txt md pdf docx odt epub rtf v1 (+ documents in v0.2.0)
Redaction carrel redact custom (pattern/regex redaction for txt/md/html/json/csv/xml with the built-in kinds from core/patterns.py — PII plus invoice/po/iban/routing/…; label-driven kinds keep the label); pdf: true redaction by rasterize→blackbox→rebuild (text layer destroyed by design; documented) text types + pdf v1
Signatures carrel sign lib:pypdf+lib:reportlab (visible PDF stamp), custom (sha256 MANIFEST + wrap:gpg detached sig, verify mode) pdf + any (manifest) v1
Notes/comments (annotations) carrel note lib:pypdf (PDF text annotations, list/add), custom (sidecar notes in index DB for any file) pdf + all (sidecar) v1
Tagging carrel tag custom (tags in index DB; add/rm/ls/find) all v1
Typed metadata fields carrel meta set/get/ls/rm/find/export, search --meta custom (schema v2 meta table; kinds str/num/date/bool inferred, canonical storage; typed find conditions = != > >= < <= ~ ?; CSV/JSON export; travels with catalog) all v0.4.0
Email files .eml/.mbox everywhere: inspect, convert (eml → md/txt/html/pdf, mbox → md/txt), index/search --type eml, pack, diff, organize → mail/, refs, the Read guard custom (stdlib email + mailbox via core/mail.py; shape sniff only for unmapped extensions, D-012) eml mbox v0.4.0
Mail tools carrel mail attachments/split/threads/pst custom (attachments with sha256, mbox → dated .eml files, Message-ID/In-Reply-To/References threads); wrap:readpst (pst-utils) for Outlook .pst/.ost (-e one .eml per message, -r one mbox per folder) eml mbox pst v0.4.0
Reference numbers carrel refs (--tag, --link, --pattern NAME=REGEX) custom (core/patterns.py: label-driven invoice/po/order/check/account/tracking/ticket + iban/routing/ein/vat/isbn/gtin/doi/ups/usps with check-digit validators; text via textextract; ref:<kind>:<value> tags cross-link files) all v0.4.0
Form building carrel form custom (JSON spec → HTML form; → PDF form via weasyprint for print-fill), lib:pypdf (fill existing AcroForm PDF, list fields) html pdf json v1
Image extraction carrel extract-images wrap:pdfimages (pdf), wrap:icotool (ico frames), custom (html <img> local refs) pdf ico html v1
Soft proofing (ICC) carrel proof wrap:imagemagick -profile with system ICC profiles (probed present); reports ΔE summary via Pillow jpg jpeg png pdf(raster) v1
Color management carrel color lib:Pillow+ImageCms (profile convert/assign, palette extraction, contrast check) images v1
Agent workflows & loops marketplace plugin carrel-agent custom (slash commands + agents + a watch-loop skill that pairs carrel watch with claude -p) — v1
Drift gates & CI matrix scripts/sync_reference.py (generated docs/REFERENCE.md, --check in CI); docs/COOKBOOK.md pulls every recipe/snippet in at build time; Linux 3.12–3.14 with coverage floor, test-minimal (macos) required, test-minimal (windows) advisory custom — v0.2.0
Plugin surface generated from --help 7 plugins (carrel-inspect, carrel-convert, carrel-organize, carrel-documents, carrel-watch, carrel-agent, carrel-guard) custom; every command a slash command, usage blocks generated by scripts/sync_plugins.py --check; hooks PreToolUse(Read) → text conversion, SessionStart → capability summary, PostToolUse(Write\|Edit) → reindex — v0.2.0
Source-tree indexing carrel index (--no-source, --no-gitignore) custom (FileType.CODE from SOURCE_EXTENSIONS; .gitignore walk shared with pack via core/ignore.py) source + config types post-v0.2.0
Invented: desk TUI carrel desk lib:textual via carrel[tui] (flagship; drives core library) all v1 (flagship)
Invented: recipes carrel run <recipe.yaml> custom mini-pipeline runner — stretch
Cryptographic PDF signing (PAdES) — stretch (needs pyHanko + key mgmt) pdf stretch

Office & ebook formats (spec 18, shipped)

Detection is by bytes ({\rtf prefix; zip containers probed for mimetype / [Content_Types].xml), so a mis-named file is still handled — carrel inspect mystery.bin on a renamed docx reports type: docx. One core.textextract branch feeds index, search, pack, diff and audiobook for every row. Word-processor and ebook reads/writes go through pandoc (degrade-if-missing → exit 3 with sudo apt install pandoc); xlsx reads use openpyxl from the office extra (uv tool install 'carrel[office]'; exit 3 with that hint when absent). Spreadsheet writing is out of scope for this release.

Format Detect Text (extract_text) convert from convert to inspect detail
docx .docx; zip with [Content_Types].xml + word/ wrap:pandoc -t plain md html txt pdf (pandoc → weasyprint) epub from md html txt (pandoc), from epub paragraphs, words, title/author/created (docProps/core.xml)
odt .odt; zip mimetype = application/vnd.oasis.opendocument.text wrap:pandoc md html txt pdf from md html txt words
epub .epub; zip mimetype = application/epub+zip wrap:pandoc md html txt pdf docx from docx title, creator, language, spine_items (OPF), words
rtf .rtf; {\rtf header wrap:pandoc md html txt pdf — words
xlsx .xlsx .xlsm; zip with [Content_Types].xml + xl/ lib:openpyxl (# sheet heading + CSV rows) csv (--sheet NAME\|N\|all) json ({sheet: [row objects]}) — sheets: [{name, rows, cols}]

Proof (executed 2026-09-04, TEST_REPORT.md): sample.md → docx → md keeps the fixture sentinel melodious cartography; sample.xlsx --to csv --sheet 2 yields the Loans sheet; inspect sample.xlsx lists both sheets with rows: 4, cols: 3.

Email (spec 28, shipped)

One core.textextract branch (core/mail.py, stdlib only) feeds every command. Detection is by extension (.eml, .mbox, .mbx) or, for files without a mapped extension, by shape — an RFC 5322 header block or an mbox From separator — so a .txt that happens to start with From: stays text (D-012).

Format Detect Text (extract_text) convert to inspect detail mail
eml .eml; header block (unmapped ext) key headers, blank line, plain body (html flattened when no plain part), attachment: lines md html txt pdf (weasyprint) from/to/cc, date (ISO), subject, message id, in-reply-to, references, parts, has_html, attachments attachments, threads
mbox .mbox .mbx; From line + header block (unmapped ext) # <subject> block per message md txt messages, first/last date, top senders attachments, split, threads
pst/ost — (input to mail pst only) — eml or mbox per folder via readpst — pst

Outlook .msg item files are cut (D-011): no pure-Python writer exists, so the support-matrix test could not hold a generated fixture; export to .pst or save as .eml instead.

Explicit scope notes

  • Bulk moves refuse to rename files git is tracking. rename --apply, organize --apply, intake --apply and watch --done-dir/--error-dir exit 2 when the move would touch a tracked path, naming the repository and what it found; each takes --allow-tracked (--force is a deprecated alias that warns, D-022). Untracked files inside a repository are fine, so ~/Downloads under a dotfiles repo keeps working — the guard asks "is it tracked?", not "is it in a repo?", because the second question refuses cases with no way out and teaches the override reflex it exists to prevent. The dry-run default is never guarded, nor is a destination that does not exist yet. Without the git binary the question cannot be answered, so carrel exits 3 with git's install hint rather than guessing. This exists because a rename --apply aimed at carrel's own checkout renamed 21 tracked files (spec 29, D-017). Git tracking is the only hazard carrel detects: unambiguous to check and catastrophic to break.
  • PDF redaction is true redaction (rasterization destroys the text layer) — documented tradeoff; searchability restorable via carrel ocr afterwards.
  • html thumbnails go through weasyprint→pdf→pdftoppm; if weasyprint missing, degrade with hint.
  • Near-dupe uses a dependency-free dHash (no numpy/imagehash) to keep install light.
  • Token counts in pack default to a chars/3.6 heuristic labeled tokens_est; --tokenizer exact counts with tiktoken o200k_base (labeled tokens, the header names the tokenizer) and needs the tokens extra — no network, and the heavy dependency stays opt-in.
  • pack --query ranks indexed files, and since spec 22 carrel index covers plain-text source and config files (.py, .rs, .toml, .yaml, Makefile, …) as type code, so query-driven packing works on source trees as well as document trees. --no-source indexes documents only. What is still never walked: hidden entries (.git, .carrel, dotfiles), .gitignored paths (--no-gitignore opts out), and binary types with no extractable text.
  • Relevance scores are bm25 from FTS5: lower is better, and on small documents they are tiny (≈ -1.5e-06 in the proofs), so the human score column can print -0.000 while --json carries the full value.
  • Spreadsheets are read-only (xlsx → csv/json); writing xlsx and reading legacy .xls/.doc are out of scope.

Cuts (running log — updated through the build)

  • recipes runner: stretch, cut if time is short (cookbook shell scripts cover the use cases).
  • PAdES cryptographic PDF signing: cut to stretch; visible stamp + gpg manifest signing ship instead (rationale: key management UX exceeds session scope).
  • v0.4.0: Outlook .msg (would need the extract-msg package and a hand-made OLE fixture) — cut; mail pst via readpst covers Outlook exports. Also deferred from the v0.4.0 brainstorm: ledger reconciliation (recon), folder roll-ups (ledger), embedded metadata write-back (meta embed), watch rules files, snapshot diffs (changes), mail drafting and sending.
  • v0.2.0: unwired adapter entries removed (gs, pngquant, jq, mlr, rg, fd, sqlite3, inotifywait, claude) — none was referenced by any command, yet doctor advertised them. Re-add each together with the command that uses it (spec 19).