terch
A modular, lightning-fast CLI meta-search tool — query Google, DuckDuckGo, Bing, Brave, or SearXNG from one full-screen, keyboard-driven TUI.
Features
- Modular Architecture: Easily plug in custom search engines — ships with Google Custom Search, DuckDuckGo, Bing, Brave, and SearXNG.
- Full-Screen TUI: Built with Textual — a compact, scrollable list of results next to a live detail preview, so you never have to scroll up and down to read a snippet.
- Stays Open: Pressing
Enteropens a link in your default browser without closing the app, so you can open several results from one search. - Multi-Select: Mark multiple results and open them all at once.
- Fuzzy Filter: Narrow down the currently loaded results locally (no extra network round-trip) as you type.
- History-Aware Search: The
/search box suggests matching past queries as you type — arrow through them,Tabto accept. - Pagination:
n/Nfetch the next/previous page of results for the same query. - In-App Search & Provider Switching: Run a new query or switch search engines without leaving the app.
- Result Caching: Repeat queries (including pages you've already visited) are served from a local cache instead of re-hitting the API.
- Search History: Every search (and every link you open) is logged; browse and re-run past searches from inside the app.
- Export: Dump the current (or marked) results to Markdown or JSON.
- Config File: Set defaults (provider, result count, cache behavior) once in
~/.config/terch/config.toml. - Automatic Fallbacks: Defaults to DuckDuckGo (zero setup required) if no other provider is configured.
- Built with Modern Python Tooling: Managed via
uvand packaged usinghatchling.
Keybindings
| Key(s) | Action |
|---|---|
↑/↓, k/j |
Move selection |
g / G |
Jump to first / last result |
Enter / o |
Open selected link in browser (stays open) |
Space |
Mark / unmark the selected result |
O |
Open all marked links (or selection) |
y |
Copy the selected link to the clipboard |
/ |
Run a new search (↑/↓ to browse history suggestions, Tab to accept, Enter to run) |
f |
Fuzzy-filter the loaded results locally |
n / N |
Next / previous page of results |
p |
Cycle to the next configured search engine |
r |
Re-run the current search |
e |
Export results (marked, or all) to a file |
H |
Browse & re-run recent searches |
Esc |
Cancel the current input box / filter |
q / Ctrl+C |
Quit |
DuckDuckGo's pagination goes through its unofficial HTML endpoint (there's no public API), so page 2+ can overlap with earlier pages. Google, Bing, Brave, and SearXNG paginate cleanly through their real APIs.
Quickstart
terch is published on PyPI: pypi.org/project/terch
Global Installation via uv (Recommended)
uv tool install terch
Global Installation via pipx
pipx install terch
Usage
Run terch (or search) directly from your command line:
# Quick search (uses DuckDuckGo by default if no keys configured)
terch "IIIT Hyderabad"
# Specify provider explicitly
terch "python rich cli" -p duckduckgo
# Limit result count
terch "latest machine learning papers" -n 3
# Bypass the result cache for this run
terch "breaking news" --no-cache
# Search and export straight to a file, skipping the TUI entirely
terch "python rich cli" --export results.md
This drops you into a full-screen TUI — see Keybindings above for how to navigate it.
Configuration & API Keys
terch works out-of-the-box using DuckDuckGo without requiring any API keys. Other providers need credentials, set via a .env file, .env.local (local overrides, gitignored), or exported to your shell:
# Google Custom Search: https://developers.google.com/custom-search/v1/introduction
export GOOGLE_API_KEY="your_google_api_key"
export GOOGLE_CX_ID="your_custom_search_engine_id"
# Bing Web Search (Azure Cognitive Services)
export BING_API_KEY="your_bing_api_key"
# Brave Search API: https://brave.com/search/api/
export BRAVE_API_KEY="your_brave_api_key"
# A SearXNG instance with JSON output enabled
export SEARXNG_URL="https://your-searxng-instance.example.com"
Copy .env.example as a starting point. .env and .env.local are both gitignored, so your keys never get committed.
Config File
Set defaults once in ~/.config/terch/config.toml instead of passing flags every time:
default_provider = "duckduckgo"
max_results = 15
cache_enabled = true
cache_ttl = 900 # seconds
history_enabled = true
CLI flags (-p, -n, --no-cache) always override the config file.
You don't need to open the file by hand — terch config manages it from the terminal:
terch config show # print the full current config
terch config get max_results # print one value
terch config set max_results 15 # set a value (creates the file if needed)
terch config set default_provider none # clear an optional value
terch config unset max_results # reset one key back to its default
terch config path # print the config file's location
Caching & History
Searches are cached on disk (~/.cache/terch/cache.json) for cache_ttl seconds (default 15 minutes), so re-running the same query against the same provider doesn't hit the network again. Disable it per-run with --no-cache, or permanently via cache_enabled = false in the config file.
Every search and every link you open is logged to ~/.local/share/terch/history.jsonl. Press H in the TUI to browse recent searches and re-run one with Enter.
Exporting Results
Press e in the TUI and type a path — marked results are exported if any are marked, otherwise all currently loaded results are. The format is chosen by extension: .json for structured data, anything else (e.g. .md) for a Markdown list of links. --export PATH does the same thing from the command line for one-shot scripting, skipping the TUI entirely.
Adding Custom Search Providers
terch uses an extensible plugin architecture. To add a new search provider:
- Create a new class subclassing
BaseSearchProviderinsearch_cli/providers/:
from search_cli.providers.base import BaseSearchProvider
from search_cli.models import SearchResult
from typing import List
class MyProvider(BaseSearchProvider):
@property
def name(self) -> str:
return "my_provider"
@property
def display_name(self) -> str:
return "My Search Engine"
def is_configured(self) -> bool:
return True
def search(self, query: str, max_results: int = 5) -> List[SearchResult]:
# Implement API or search fetch here
return []
- Add your provider to the
REGISTRYinsearch_cli/providers/__init__.py.
Local Development
Clone & Install Dependencies
git clone [https://github.com/shrey715/search_cli.git](https://github.com/shrey715/search_cli.git)
cd search_cli
# Install dependencies and setup virtual environment
uv sync
Run Locally
uv run terch "python terminal tools"
License
Distributed under the MIT License
Metadata
Release files for terch 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| terch-0.5.0.tar.gz | 34.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| terch-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.7 kB
Release files / terch-0.5.0.tar.gz
| Download URL | terch-0.5.0.tar.gz |
|---|---|
| Size | 34.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e18ccbca947e101f92327d2872e224859c028e51bd63df8ab8ad5a6a575f0ece
|
|
BLAKE2b-256 checksum How to use checksums |
f0b1659bc56494c6cae98c210b87cf8230bbb808f1322b4321e376a890ab252f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / terch-0.5.0-py3-none-any.whl
| Download URL | terch-0.5.0-py3-none-any.whl |
|---|---|
| Size | 24.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
70d9f0e90a6cd49e9085c4de7c6d3474d1dd9b7148d686675699bfcc10f4b1f1
|
|
BLAKE2b-256 checksum How to use checksums |
432c5199d406de162b805dfdc4576f852f8e4c1897ccdf149d40f5cc1054b7a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for terch 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.