qbcl
A Common Lisp terminal application for controlling qBittorrent,
inspired by pyrocore's tools for rTorrent.
Overview
qbcl brings pyrocore's powerful command-line torrent management philosophy to qBittorrent,
communicating via qBittorrent's WebAPI (REST/JSON) instead of rTorrent's XMLRPC/SCGI protocol.
Tools
| qbcl command |
pyrocore equivalent |
Description |
qbcl (default) |
rtcontrol |
Filter, display, and act on torrents |
qbcl lstor |
lstor |
Inspect .torrent files |
Key Features
- Filter expressions — pyrocore-style filtering:
name=*linux*, ratio=+1.0, state=downloading
- Output formats — configurable field lists and named presets
- Actions — start, stop, delete, recheck, tag, categorize matching torrents
- JSON output — machine-readable output for scripting
- Multiple connections — named connection profiles in config
Quick Start
# Initialize config (creates ~/.config/qbcl/config.lisp)
qbcl config init
# Edit the config with your qBittorrent URL and credentials, then:
# List all torrents
qbcl
# Filter by name (glob)
qbcl '*ubuntu*'
# Seeds with good ratio
qbcl ratio=+1.0
# Large downloads, sorted by size descending
qbcl size=+1g -s -size
# Stop well-seeded torrents
qbcl ratio=+2.0 --stop
# Show all available fields
qbcl --help-fields
# Inspect a .torrent file
qbcl lstor some-file.torrent
Usage
qbcl [OPTIONS] [FILTER...] [ACTION]
qbcl lstor [OPTIONS] <file.torrent>...
qbcl config <subcommand>
qbcl help
qbcl version
Options
| Flag |
Short |
Description |
--help |
-h |
Show help text |
--help-fields |
|
List all torrent fields with descriptions |
--version |
|
Show version |
--output-format FORMAT |
-o |
Output preset name or comma-separated field list |
--sort-fields FIELDS |
-s |
Sort by fields; prefix with - for descending |
--reverse-sort |
-r |
Reverse the sort order |
--column-headers |
-c |
Print column headers above output |
--json |
|
Output results as JSON |
--select N-M |
-/ |
Select a subset of results (e.g. 1-10) |
--dry-run |
-n |
Show what would happen without making changes |
--verbose |
-v |
Verbose logging |
--debug |
|
Debug mode |
--quiet |
-q |
Suppress informational output |
--interactive |
-i |
Prompt before applying each action |
--yes |
|
Answer yes to all prompts |
--url URL |
|
qBittorrent WebAPI URL |
--username USER |
|
Username for authentication |
--password PASS |
|
Password for authentication |
@NAME |
|
Use named connection profile from config |
Actions
| Flag |
Description |
--start |
Resume matching torrents |
--stop |
Pause matching torrents |
--delete |
Remove torrents from client (keep data) |
--cull |
Remove torrents and delete data |
--hash-check / -H |
Force hash recheck |
--reannounce |
Reannounce to trackers |
--tag TAGS |
Add or remove tags (+tag to add, -tag to remove) |
--category CAT |
Set category |
Output Format
Pass a preset name or a comma-separated field list to -o:
qbcl -o detail # named preset
qbcl -o name,size,ratio,state # custom field list
qbcl -o name,size,ratio -c # with column headers
| Preset |
Fields |
default |
name, size, progress, ratio, dlspeed, upspeed, state |
detail |
+ category, tags, tracker, save-path, added-on |
short |
name, progress, state |
files |
name, size, progress, save-path |
action |
hash, name, state |
transfer |
name, size, downloaded, uploaded, ratio |
Run qbcl --help-fields for all available field names.
Config Subcommands
qbcl config init # Write default config to ~/.config/qbcl/config.lisp
qbcl config show # Print current config
qbcl config path # Print path to config file
Examples
Listing and filtering
# List all torrents (default format)
qbcl
# Name contains "linux" (glob, case-insensitive)
qbcl '*linux*'
# Exact state match
qbcl state=downloading
qbcl state=uploading
qbcl state=pausedUP
# Numeric comparisons
qbcl ratio=+1.0 # ratio > 1.0
qbcl size=+1g # size > 1 GB
qbcl size=-500m # size < 500 MB
qbcl eta=+1h # ETA more than 1 hour away
qbcl dlspeed=+1m # downloading faster than 1 MB/s
# Regex match
qbcl 'name=/^linux/' # name starts with "linux"
# Negation
qbcl 'state=!error' # everything except errored
# Filter by category or tag
qbcl category=movies
qbcl tags=keep
# AND (implicit — space-separated filters)
qbcl state=downloading dlspeed=+500k
# OR (explicit)
qbcl 'ratio=+2.0 OR category=archive'
Output and sorting
# Custom column list
qbcl -o name,size,ratio,state
# Named preset with column headers
qbcl -o detail -c
# Sort by name
qbcl -s name
# Sort by size, largest first
qbcl -s -size
# Sort by ratio descending, show top 10
qbcl -s -ratio -/ 1-10
# JSON output for scripting
qbcl state=downloading --json
# Quiet (no headers/info, just data rows)
qbcl -q '*ubuntu*'
Actions
# Stop all torrents with ratio above 2.0 (prompts for confirmation)
qbcl ratio=+2.0 -i --stop
# Start all paused torrents
qbcl state=pausedDL --start
# Remove errored torrents (dry run first)
qbcl state=error -n --delete
qbcl state=error --delete
# Remove torrents AND their data
qbcl category=trash --cull
# Recheck all torrents in a category
qbcl category=movies --hash-check
# Reannounce all active torrents
qbcl state=uploading --reannounce
# Add a tag
qbcl ratio=+3.0 --tag +seeded
# Remove a tag
qbcl tags=old --tag -old
# Set category on matching torrents
qbcl 'name=*4K*' --category hd
Multiple connections
# Use a named connection profile from config
qbcl @seedbox state=downloading
# Pass connection details inline
qbcl --url http://192.168.1.10:8080 --username admin --password secret
Inspecting .torrent files
qbcl lstor file.torrent
qbcl lstor *.torrent
Configuration
Config lives at ~/.config/qbcl/config.lisp:
((:URL . "http://localhost:8080")
(:USERNAME . "admin")
(:PASSWORD . "your-password")
(:OUTPUT-FORMAT . "default")
(:SORT-FIELDS . ("name"))
(:CONNECTIONS
(:DEFAULT . ((:URL . "http://localhost:8080")
(:USERNAME . "admin")
(:PASSWORD . "your-password")))))
Filter Syntax
field=value Exact or glob match (* ? [a-z]) name=*ubuntu*
field=/regex/ Regex match name=/^linux/
field=+N Greater than N ratio=+1.0 size=+1g
field=-N Less than N size=-500m eta=-1h
field=!value Negate state=!error
term1 term2 AND (space-separated) state=downloading dlspeed=+1m
term1 OR term2 OR ratio=+2.0 OR category=archive
- If no field name is given,
name is assumed: qbcl '*ubuntu*' is the same as qbcl name=*ubuntu*
- Size suffixes:
b (bytes), k (KiB), m (MiB), g (GiB), t (TiB)
- Time suffixes:
s (secs), m (mins), h (hours), d (days), w (weeks) — time fields compare against an age offset from now; +7d means "older than 7 days", -7d means "within the last 7 days"
Field Reference
These are all fields you can use in filter expressions and with -o/-s.
Identity
| Field |
Type |
Operators |
Example |
name |
string |
glob, /regex/, ! |
name=*ubuntu* |
hash |
string |
glob, /regex/, ! |
hash=abc123 |
Size & Data
| Field |
Type |
Operators |
Example |
size |
size |
+, -, =, ! |
size=+1g size=-500m |
total-size |
size |
+, -, =, ! |
total-size=+2g |
downloaded |
size |
+, -, =, ! |
downloaded=+100m |
uploaded |
size |
+, -, =, ! |
uploaded=+1g |
amount-left |
size |
+, -, =, ! |
amount-left=+500m |
completed |
size |
+, -, =, ! |
completed=+1g |
Size suffixes: b (bytes), k (KiB), m (MiB), g (GiB), t (TiB).
Progress & Ratio
| Field |
Type |
Operators |
Example |
progress |
numeric |
+, -, =, ! |
progress=+0.9 (>90%) progress=1.0 (complete) |
ratio |
numeric |
+, -, =, ! |
ratio=+1.0 ratio=-0.5 |
Progress is 0.0–1.0. Ratio has no upper bound.
Transfer Speed
| Field |
Type |
Operators |
Example |
dlspeed |
size |
+, -, =, ! |
dlspeed=+1m (>1 MB/s) |
upspeed |
size |
+, -, =, ! |
upspeed=+500k |
dl-limit |
size |
+, -, =, ! |
dl-limit=-1 (unlimited) |
up-limit |
size |
+, -, =, ! |
up-limit=+0 |
Time
| Field |
Type |
Operators |
Example |
added-on |
time |
+, - |
added-on=-7d (added in last 7 days) |
completion-on |
time |
+, - |
completion-on=-30d |
last-activity |
time |
+, - |
last-activity=+1d (no activity >1 day ago) |
seen-complete |
time |
+, - |
seen-complete=+7d |
eta |
numeric |
+, -, =, ! |
eta=+3600 (>1 hour remaining) |
Time fields compare against an age offset in seconds from now, using the same suffixes as duration: s, m, h, d, w. +Nd means "older than N days"; -Nd means "within the last N days".
State & Peers
| Field |
Type |
Operators |
Example |
state |
string |
glob, /regex/, ! |
state=downloading state=!error |
priority |
numeric |
+, -, =, ! |
priority=0 (do not download) |
num-seeds |
numeric |
+, -, =, ! |
num-seeds=+5 |
num-leechs |
numeric |
+, -, =, ! |
num-leechs=+0 |
num-complete |
numeric |
+, -, =, ! |
num-complete=+10 |
num-incomplete |
numeric |
+, -, =, ! |
num-incomplete=+0 |
Known state values (from the qBittorrent WebAPI):
| Value |
Meaning |
downloading |
Actively downloading |
uploading |
Actively seeding |
stalledDL |
Downloading, but no peers |
stalledUP |
Seeding, but no peers |
pausedDL |
Paused before completing |
pausedUP |
Paused after completing |
queuedDL |
Queued for download |
queuedUP |
Queued for seeding |
checkingDL |
Hash-checking during download |
checkingUP |
Hash-checking after download |
checkingResumeData |
Checking resume data on startup |
moving |
Being moved to a new location |
error |
Stopped with an error |
missingFiles |
Data files are missing |
unknown |
Unknown state |
Glob patterns work on state: state=paused* matches both pausedDL and pausedUP.
Classification
| Field |
Type |
Operators |
Example |
category |
tags |
exact, ! |
category=movies category=! (no category) |
tags |
tags |
exact, ! |
tags=keep tags=!seeded |
tracker |
string |
glob, /regex/, ! |
tracker=*example.com* |
Tag matching checks whether the given value is present as a member of the comma-separated tag list. category works the same way (it's treated as a single-value tag field).
Paths
| Field |
Type |
Operators |
Example |
save-path |
string |
glob, /regex/, ! |
save-path=/mnt/media* |
content-path |
string |
glob, /regex/, ! |
content-path=*/downloads/* |
magnet-uri |
string |
glob, /regex/, ! |
magnet-uri=*xt=urn:btih:abc* |
Boolean Flags
| Field |
Type |
Values |
Example |
auto-tmm |
bool |
yes/no, true/false, 1/0 |
auto-tmm=yes |
force-start |
bool |
yes/no, true/false, 1/0 |
force-start=yes |
seq-dl |
bool |
yes/no, true/false, 1/0 |
seq-dl=yes |
f-l-piece-prio |
bool |
yes/no, true/false, 1/0 |
f-l-piece-prio=yes |
super-seeding |
bool |
yes/no, true/false, 1/0 |
super-seeding=no |
Boolean filters do not use +/-; pass a truthy or falsy string instead.
Dependencies
Architecture
qbcl/
├── qbcl.asd # ASDF system definition
└── src/
├── packages.lisp # Package definitions
├── conditions.lisp # Error hierarchy (≈ pyrocore.error)
├── main.lisp # Entry point & command dispatch
├── api/
│ ├── client.lisp # HTTP client (≈ pyrocore.util.xmlrpc)
│ └── endpoints.lisp # WebAPI wrappers
├── config/
│ └── settings.lisp # Configuration (≈ pyrocore.config)
├── torrent/
│ ├── fields.lisp # Field definitions (≈ engine.FieldDefinition)
│ ├── engine.lisp # Engine interface (≈ torrent.rtorrent)
│ └── filter.lisp # Filter bridge (≈ torrent.filter)
├── util/
│ ├── formatting.lisp # Display formatting (≈ util.fmt)
│ └── matching.lisp # Filter parser/engine (≈ util.matching)
└── scripts/
├── base.lisp # CLI framework (≈ scripts.base)
├── qbcontrol.lisp # Main control tool (≈ scripts.rtcontrol)
└── lstor.lisp # Torrent inspector (≈ scripts.lstor)
License
GPL-2.0-or-later (same as pyrocore)