iwantit is a config-driven CLI that turns a URL, text, or screenshot into a clean,
repeatable media workflow. It is designed to be automation-friendly: it reads/writes JSON,
works well in pipelines, and keeps side effects (downloads) isolated to explicit steps.
This repo currently focuses on:
- Music search + downloads via Prowlarr (and Redacted enrichment)
- Movies/TV dispatch to Radarr/Sonarr
- Books via Prowlarr
- Strong defaults for query cleanup, media type detection, and scoring
Get productive first; customize later.
- Install and initialize:
uv tool install .
iwantit initIf your shell does not see iwantit yet:
uv tool update-shell- Run a safe test:
iwantit run --text "Pink Floyd - Dark Side of the Moon"- Allow side effects when you're ready:
iwantit run --text "Bernard Badie - Bones [2011]" --confirmIntegration points (all configurable in ~/.config/iwantit/config.yaml):
- Prowlarr search + grab (music/books)
- Goodreads Want to Read ingestion (CSV bootstrap + incremental RSS)
- Radarr and Sonarr dispatch (movie/tv)
- Web search providers (Kagi/Brave) for release verification
- Optional tracker enrichment (Redacted)
- Custom HTTP or external command steps
Local service names and endpoints for this workspace are documented in
docs/local_media_services.md.
- OCR uses
tesseract. Install it if you want to pass screenshots via--image.
iwantit --help
iwantit help overview
iwantit help config
iwantit help config --verbose
iwantit help json
iwantit help safety
iwantit help exit-codes
iwantit help errors
iwantit doctorKey automation notes:
- URL-like input is auto-detected even from stdin/JSON.
- Output arrays are compact by default; use
--fullfor full items. - Exit code
20meansdecision.status=needs_choice. --dry-runskips side effects;--confirmallows them.iwantit run --batch file.jsonl --jobs 4runs batches in parallel.run_idandlogsare included in output JSON for tracing.
Batch inputs can be a JSON array, JSONL, or plain lines:
iwantit run --batch inputs.jsonl --jobs 4Import a complete Goodreads export as a non-downloading baseline:
iwantit shelf sync goodreads --csv docs/goodreads_library_export.csv --limit 0Queue the existing shelf only when you explicitly want a full backfill:
iwantit shelf sync goodreads --csv docs/goodreads_library_export.csv --backfill --dry-runPoll the configured RSS shelf and process due ebook/audiobook legs:
iwantit shelf sync goodreads --confirm
iwantit shelf status
iwantit shelf retryCSV imports, incremental identity, per-format completion, retries, and the systemd
timer are documented in docs/goodreads_automation.md.
Validate configuration and connectivity:
iwantit doctorRun an offline end-to-end test harness with stubbed services:
python3 scripts/functional_test.pyOptions:
--verboseprints progress--keep-tempkeeps the generated config directory
Optional structured logs and human-readable reports:
logging:
path: ~/.local/state/iwantit/run.log.jsonl
report:
enabled: trueCommon error codes are documented via:
iwantit help errorsExternal steps can be discovered via plugins/ or IWANTIT_PLUGIN_PATH:
plugins:
paths:
- /path/to/iwantit-pluginsPlugin format (plugin.yaml):
name: my-plugin
version: 0.1.0
steps:
my_step:
command: ["python3", "-m", "my_plugin.steps.do_thing"]web_search:
provider: kagi
providers:
kagi:
api_key: ${ENV:KAGI_SEARCH_API_KEY}
prowlarr:
url: http://localhost:9696
api_key: CHANGE_ME
arr:
radarr:
url: http://localhost:7878
api_key: CHANGE_ME
root_folder: /media/movies
quality_profile_id: 1
endpoint: /api/v3/movie
sonarr:
url: http://localhost:8989
api_key: CHANGE_ME
root_folder: /media/tv
quality_profile_id: 1
endpoint: /api/v3/series
redacted:
url: https://redacted.sh
api_key: CHANGE_ME
goodreads:
shelf_url: https://www.goodreads.com/review/list/151049665-travis?shelf=to-read
formats: [ebook, audiobook]
batch_limit: 10
inventory:
required: true
sources:
ebook:
- {type: calibre_ssh, host: root@192.168.1.222, database: /mnt/user/visualmedia/Calibre/metadata.db}
- {type: ssh, host: root@192.168.1.222, path: /mnt/user/visualmedia}
audiobook:
- {type: audiobookshelf_ssh, host: root@192.168.1.222, database: /mnt/user/appdata/audiobookshelf/config/absdatabase.sqlite}
- {type: ssh, host: root@192.168.1.222, path: /mnt/user/audiobooks}The pipeline runs in two phases:
-
Pre-steps (normalize + identify)
- OCR (for screenshots)
- URL fetching (pulls title/description)
- Web search cleanup (fix typos, normalize artist/title/year)
- Media type detection
- Track-to-album resolution (if input looks like a song)
-
Workflow steps (by media type)
- Music/Books: Prowlarr search → filtering → ranking → decision → grab
- Movies/TV: dispatch to Radarr/Sonarr
All steps are configured in YAML. You can replace any step with your own command.
- End-to-end overview
- Pre-steps detail
- Music workflow
- Book workflow
- Movie and TV workflow
- Decision and dispatch logic
Sources: docs/diagrams/flow-overview.mmd and docs/diagrams/rendered/flow-overview.svg.
Sources: docs/diagrams/pre-steps.mmd and docs/diagrams/rendered/pre-steps.svg.
Sources: docs/diagrams/music-workflow.mmd and docs/diagrams/rendered/music-workflow.svg.
Sources: docs/diagrams/book-workflow.mmd and docs/diagrams/rendered/book-workflow.svg.
Sources: docs/diagrams/movie-tv-workflow.mmd and docs/diagrams/rendered/movie-tv-workflow.svg.
Sources: docs/diagrams/decision-and-dispatch.mmd and docs/diagrams/rendered/decision-and-dispatch.svg.
Install uv first (see https://astral.sh/uv).
From the repo root:
uv run python -m iwantit --helpuv tool install .. means "install from this local project". This keeps dependencies isolated and exposes iwantit
on your PATH. If your shell doesn't see it yet, run:
uv tool update-shelluv tool install git+https://github.com/<you>/iwantit.gituv tool install iwantituv venv
source .venv/bin/activate
uv pip install -e .uv build
uv publishUnit tests:
python3 -m unittest discover -s testsFunctional smoke tests (offline, stubbed providers):
python3 scripts/functional_test.py --verboseiwantit init [--force]
iwantit run [--text|--url|--image|--json|--stdin] [--workflow name] [--dry-run] [--confirm] [--book-format ebook|audiobook|both]
iwantit step <step-name> [--text|--url|--image|--json|--stdin] [--book-format ebook|audiobook|both]
iwantit choose [--json|--stdin] [--interactive] [--select <idx|substring>]
iwantit shelf sync goodreads [--csv export.csv] [--backfill] [--dry-run|--confirm]
iwantit shelf status
iwantit shelf retry [--include-choices] [--include-uncertain]
iwantit shelf resolve <goodreads-book-id> --book-format ebook|audiobook --choice N --confirm
iwantit list workflows|steps
iwantit validateNotes:
iwantit runreturns exit code 20 whendecision.status = needs_choice.iwantit choosecan emit--choice Nflags for reruns.- URLs are auto-detected (CLI args, stdin, or JSON input) and treated as
--url. - CLI output is compacted by default; use
--fullto emit full JSON. - Progress messages are written to stderr; use
--quietto suppress. - Dispatch steps are skipped unless you pass
--confirm.
Every run returns a JSON object:
{
"request": { "input": "...", "input_type": "text|url|image", "query": "...", "media_type": "music|movie|tv|book" },
"work": { "candidates": [], "selected": {} },
"decision": { "status": "selected|needs_choice|error" },
"search": { "kagi": {}, "prowlarr": {} },
"dispatch": { "prowlarr": {}, "radarr": {} },
"tags": {},
"error": { "message": "...", "step": "...", "type": "..." }
}Config lives at ~/.config/iwantit/config.yaml (override with IWANTIT_CONFIG).
Secrets live at ~/.config/iwantit/secrets.yaml (override with IWANTIT_SECRETS).
ocr -> fetch_url -> identify -> identify_web_search -> extract_release_preferences
-> determine_media_type -> resolve_track_release
For URL inputs, fetch_url grabs <title> and meta description, then uses that
as the query. It also has a YouTube oEmbed fallback to avoid the generic "YouTube" title.
steps:
fetch_url:
builtin: fetch_url
timeout: 15
retries: 1
retry_backoff_seconds: 0.5
headers:
User-Agent: Mozilla/5.0 ...
Accept-Language: en-US,en;q=0.9Uses a web search provider (Kagi by default) to normalize artist/title/year. It does not blindly use the top result: it builds a consensus from the top N.
steps:
identify_web_search:
provider: kagi
result_limit: 10
min_confirmations: 2
min_match_ratio: 0.4Uses keywords from web results plus a fallback heuristic (Artist - Title → music, etc.).
steps:
determine_media_type:
provider: kagi
min_score: 2
fallback: trueIf the query looks like a track, it attempts to find the album/EP:
- Try to extract album from web snippets (e.g., "from the album ...")
- If that fails, run Redacted advanced search using
artistname + filelist - Rewrite the query to search the album/EP first
steps:
resolve_track_release:
release_priority: ["Album", "EP", "Single", "Live album"]Music and book workflows use Prowlarr for search + grab.
prowlarr:
url: http://localhost:9696
api_key: ${ENV:PROWLARR_API_KEY}
search:
indexer_ids:
music: [] # limit to music trackers
book: []
categories:
music: [3000, 3010, 3040]
book: [7000, 7020, 7040, 3030]Prowlarr grabs can select a download client by name or numeric ID. Name-based routing is preferred because Prowlarr owns the final client IDs.
prowlarr:
download_clients:
music: Music
book:
ebook: Books
audiobook: Audiobooks
default: Books
download_client_rules:
- client_name: Music
categories: [3010, 3040, 3050, 3060]
- client_name: Movies
categories: [3020]
category_prefixes: [2] # matches 2xxx
- client_name: TV
category_prefixes: [5] # matches 5xxx
- client_name: Audiobooks
categories: [3030]
- client_name: Books
category_prefixes: [7] # matches 7xxxPrefix matching is thousands-based by default (3 → 3000-3999). You can use
prefix_mode: "hundreds" in a rule if you prefer. If the selected client is a
name, IWantIt resolves it through Prowlarr's /api/v1/downloadclient endpoint
before posting the grab request.
The pipeline applies two filters to Prowlarr results:
steps:
filter_candidates:
allow_missing_categories: false
category_prefixes:
music: [30]
filter_match:
min_match_ratio: 0.4
min_token_matches: 2book_decide filters book results when the user specifies a format:
- Query keywords like
audiobook,ebook,epub,m4bare detected. - You can also pass
--pref book_format=ebookor--pref book_format=audiobook. - The default can be set in config via
book.default_format(ebook, audiobook, or both).
When book.default_format or --book-format is both, IWantIt tries to select
one top-ranked ebook and one top-ranked audiobook, then sends both to Prowlarr on
confirm. If a single format is specified, candidates are filtered to that type;
otherwise, all results remain and the decision step handles selection.
When a candidate is from Redacted, redacted_enrich pulls group/torrent metadata
using the Redacted JSON API. This is used for ranking and for track/album resolution.
Set the API key in secrets:
redacted:
api_key: YOUR_KEYrank_releases scores candidates using quality_rules.
Default music preferences:
- FLAC > V0 > 320
- WEB > CD > SACD > vinyl
- Rejects 24-bit and 5.1/surround
quality_rules:
music:
release_priority: [deluxe, studio, anniversary, live, bootleg]
reject:
- "(?i)\\b24[- ]?bit\\b"
- "(?i)\\b5\\.1\\b"
score:
- { match: "(?i)\\bflac\\b", score: 120 }
- { match: "(?i)\\bweb\\b", score: 60 }
- { match: "(?i)\\bcd\\b", score: 40 }
- { match: "(?i)\\bvinyl\\b", score: -10 }Any step can be replaced with a command:
steps:
my_step:
command: ["python3", "-m", "my.module"]The command receives the JSON payload on stdin and must return JSON to stdout.
Most steps support:
steps:
identify_web_search:
cache: { enabled: true, ttl_seconds: 3600 }
timeout: 15
retries: 2Global defaults can be set with timeouts and retries in config.
- Prowlarr and Redacted have rate limits. The Redacted API allows ~10 requests/10s with API keys.
- Always respect tracker rules and terms of service.
- This tool is meant for personal automation; you control which indexers and download clients are used.





