Rust CLI for scraping public X surfaces with session cookies.
- Overview
- Architecture
- Request strategy
- Cache and TTL overview
- Pagination strategy
- Runtime policy config
- Docs
- Install
- Configure cookies
- Runtime policy tuning
- Verify
- Alternative install
- Stack
- Features
- Examples
finch is a Rust CLI that turns noisy X web/GraphQL payloads into stable command outputs for:
- search
- profile surfaces
- tweet reads
- list reads
- article reads
- exact batch hydration
At a high level, it works like this:
flowchart LR
A["CLI Command"] --> B["Planner"]
B --> C{"Cache Fresh?"}
C -->|Yes| D["Normalized Output"]
C -->|No| E["Cookie / Session Router"]
E --> F["Registry-Driven Request"]
F --> G["Normalize Payload"]
G --> H{"Needs Hydration?"}
H -->|Yes| I["Batch Hydrate Entities"]
H -->|No| J["Pagination / Stop Rules"]
I --> J
J --> K["Persist Cache / Cursor / Health"]
K --> D
flowchart TB
subgraph CLI["CLI Layer"]
MAIN["main.rs"]
CMD["commands.rs"]
OUT["output.rs"]
end
subgraph Runtime["Runtime Layer"]
PLAN["planner.rs"]
CACHE["cache.rs"]
STATE["state.rs"]
COOKIES["cookies.rs"]
TRANSPORT["transport.rs"]
end
subgraph Data["Data / Parsing Layer"]
REG["registry.rs"]
NORM["normalize.rs"]
CFG["config.rs"]
end
MAIN --> CMD
CMD --> PLAN
CMD --> CACHE
CMD --> STATE
CMD --> COOKIES
CMD --> TRANSPORT
CMD --> REG
CMD --> NORM
CMD --> OUT
CMD --> CFG
The CLI is built around four runtime ideas:
| Area | Strategy |
|---|---|
| Session routing | Pick the healthiest cookie per endpoint family instead of random rotation |
| Cache | Cache-first unless --live, with dynamic TTL by tier, pressure, confidence, and depth |
| Hydration | Discover on timeline/search surfaces, then batch-hydrate entities |
| Pagination | Adaptive continuation based on target, yield, duplication, cursor presence, and pressure |
Finch does not use one flat cache timer. It uses cache tiers plus dynamic TTL adjustment.
| Tier | Typical use | Default base TTL |
|---|---|---|
frontier |
hot first-page collection reads | 45s |
surface_page |
deeper paginated reads | 300s |
entity |
single stable entities | 3600s |
derived |
derived views such as people-search shaped results | 300s |
failure |
thin, empty, or suspicious payloads | 20s |
The final TTL is adjusted using these default rules:
| Signal | Rule | Default effect |
|---|---|---|
| thin / empty result | result is thin or returned count is 0 |
multiply TTL by 0.45 |
| low fill ratio | returned/requested ratio is < 0.25 |
multiply TTL by 1.6 |
| overshoot / full page | returned/requested ratio is > 1.0 |
multiply TTL by 0.85 |
page depth >= 2 |
deeper pagination page | multiply TTL by 1.8 |
page depth >= 4 |
very deep pagination page | multiply TTL by 3.0 |
| medium pressure | family pressure >= 0.25 |
multiply TTL by 1.3 |
| high pressure | family pressure >= 0.5 |
multiply TTL by 1.8 |
| severe pressure | family pressure >= 0.75 |
multiply TTL by 2.5 |
| low confidence | confidence < 0.4 |
multiply TTL by 0.4 |
| medium confidence | confidence < 0.7 |
multiply TTL by 0.75 |
| search family | search result family | multiply TTL by 0.8 |
| article entity | article entity cache | multiply TTL by 2.0 |
| list entity | list entity cache | multiply TTL by 3.0 |
| deep family | thread/edit-history family | multiply TTL by 1.4 |
Then the final TTL is clamped between:
- minimum:
10s - maximum:
86400s| family type | family-specific multipliers |
flowchart LR
A["Base TTL by Cache Tier"] --> B["Apply Result Quality Multipliers"]
B --> C["Apply Page Depth Multiplier"]
C --> D["Apply Family Pressure Multiplier"]
D --> E["Apply Confidence Multiplier"]
E --> F["Apply Family-Specific Multiplier"]
F --> G["Clamp Between Min/Max TTL"]
Finch keeps fetching collection pages while they are still useful.
| Rule | Meaning |
|---|---|
| target reached | requested limit has been met |
| source exhausted | no cursor / no more content |
| duplicate frontier | repeated frontier or too many duplicates |
| low-yield stop | consecutive pages add too little new value |
| pressure stop | runtime pressure says stop live pagination |
Collection commands are target-based by default.
If the final productive page takes:
48 -> 63
then Finch keeps the full 63.
Use:
--strict-limitif you want exact trimming.
The main tuning file is:
~/.finch/config.jsonKey sections:
| Section | What it controls |
|---|---|
transport |
timeout and user-agent |
cache.tier_ttl_s |
base TTL per cache tier |
hydration |
default hydrate batch size |
pagination.search |
search hard caps |
pagination.collection |
collection hard caps |
pagination.yield_rules |
low-yield detection |
pagination.thresholds |
duplicate and pressure stop thresholds |
routing.base_concurrency |
concurrency defaults by family |
These are good user-tunable knobs:
| Area | Safe examples |
|---|---|
| hydrate batch size | 200, 400, 600 |
| frontier TTL | 30-120 seconds |
| entity TTL | 1800-21600 seconds |
| search hard caps | 20-60 |
| duplicate ratio stop | 0.7-0.9 |
| pressure stop | 0.8-0.95 |
| low-yield streak limit | 2-4 |
These are intentionally not treated as user policy knobs:
| Area | Reason |
|---|---|
| response parsing | correctness logic |
| normalization rules | output contract stability |
| endpoint wiring | protocol correctness |
| cache key structure | cache integrity |
| author-filter enforcement | correctness for search semantics |
{
"hydration": {
"default_batch_size": 200
},
"pagination": {
"search": {
"broad_hard_cap": 25,
"filtered_hard_cap": 25,
"author_hard_cap": 20
},
"thresholds": {
"duplicate_ratio_stop": 0.75,
"pressure_stop": 0.85
}
}
}{
"hydration": {
"default_batch_size": 600
},
"pagination": {
"search": {
"broad_hard_cap": 50,
"filtered_hard_cap": 40,
"author_hard_cap": 35
},
"yield_rules": {
"minimum_new_items_floor": 2,
"minimum_new_items_ratio": 0.005,
"low_yield_streak_limit": 3
}
}
}git clone https://github.com/KEYURBODAR/finch-cli.git
cd finch-cli
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo 'source "$HOME/.cargo/env"' >> ~/.zshrc
source "$HOME/.cargo/env"
source ~/.zshrc
cargo install --path .
mkdir -p ~/.finch
cp config.example.json ~/.finch/config.json
cp registry.phase1.json ~/.finch/registry.json
cp cookies.example.json ~/.finch/cookies.json
finch statusgit clone https://github.com/KEYURBODAR/finch-cli.git
cd finch-cli
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo 'source "$HOME/.cargo/env"' >> ~/.bashrc
source "$HOME/.cargo/env"
source ~/.bashrc
cargo install --path .
mkdir -p ~/.finch
cp config.example.json ~/.finch/config.json
cp registry.phase1.json ~/.finch/registry.json
cp cookies.example.json ~/.finch/cookies.json
finch statusEdit:
nano ~/.finch/cookies.jsonPut real values in this format:
[
{
"id": "ck_01",
"auth_token": "YOUR_AUTH_TOKEN",
"ct0": "YOUR_CT0"
}
]~/.finch/config.json now controls the main runtime policy knobs.
You can change things like:
- cache TTLs
- default hydrate batch size
- pagination hard caps
- low-yield stop thresholds
- duplicate frontier stop threshold
- pressure stop threshold
Start from:
cp config.example.json ~/.finch/config.jsonThen edit:
nano ~/.finch/config.jsonfinch status
finch search posts --query 'from:naval ai' --dry-run --output jsonInstall directly from GitHub:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo 'source "$HOME/.cargo/env"' >> ~/.zshrc
source "$HOME/.cargo/env"
source ~/.zshrc
cargo install --git https://github.com/KEYURBODAR/finch-cli.git
mkdir -p ~/.finch
curl -L https://raw.githubusercontent.com/KEYURBODAR/finch-cli/main/config.example.json -o ~/.finch/config.json
curl -L https://raw.githubusercontent.com/KEYURBODAR/finch-cli/main/registry.phase1.json -o ~/.finch/registry.json
curl -L https://raw.githubusercontent.com/KEYURBODAR/finch-cli/main/cookies.example.json -o ~/.finch/cookies.json
finch status- Rust
- clap
- tokio
- reqwest
- serde
- rusqlite
- sha2
- regex
- time
- csv
- tracing
search postssearch usersuser resolve/user getuser posts/user media/user articles/user highlightstweet get/tweet thread/tweet editsarticle getlist resolve/list get/list posts/list membersbatch tweets/batch users/batch handlescache stats/cache clearcookies healthhealth/health --watch- output formats:
table,json,csv,md
finch statusfinch search posts --query 'from:naval ai' --limit 5 --live --output jsonfinch search users --query 'openai' --limit 5 --strict-limit --live --output jsonfinch user resolve @naval --live --output jsonfinch user posts @injective --limit 20 --live --output jsonfinch user media @injective --limit 20 --live --output jsonfinch user articles @injective --limit 20 --live --output jsonfinch user highlights @injective --limit 20 --live --output jsonfinch article get 2032323059714306048 --live --output jsonfinch list resolve @pmarca/ai-founders --live --output jsonfinch tweet get 2031702075307176248 --output jsonfinch tweet thread 2031702075307176248 --output jsonfinch tweet edits 2031702075307176248 --output jsonfinch batch users --ids users.txt --output jsonfinch batch tweets --ids tweets.txt --output json