ketch: a stateless Go CLI that puts search, code grep, docs and scraping behind one binary
Fast, stateless CLI for web search and scrape. Built for AI agents.
At a glance
- What is it?
- ketch collapses several provider SDKs into a single Go binary with JSON output and documented exit codes. It is aimed at terminal users and at agents that need one predictable command surface, but the default search backend still needs a key and the PDF path has a hard boundary at scanned documents.
- Who is it for?
- Adopt ketch if you are running agents or shell scripts that need search, code grep, docs and scraping behind one command with stable JSON and exit codes, and if you are willing to configure a backend key once. Do not adopt it if your corpus is scanned PDFs, since the built-in parser needs OCR and returns a precondition error, or if you need a hosted service with per-tenant quotas.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem ketch targets: four research surfaces, four auth models, one agent loop
An agent that needs to answer a question usually needs more than one kind of lookup. It wants a web search, sometimes source code from a real repository, sometimes a version-specific library snippet, and almost always the full text of a page it just found. Wiring that up by hand means pulling in a search provider SDK, a code search client, a documentation API and an HTML-to-markdown library, then normalising four response shapes and four auth schemes into something a model can read. The README describes this directly: "Most research tooling for agents means wiring up several provider SDKs, each with its own auth and response shape." ketch's answer is to ship one binary that exposes the surfaces as subcommands (search, code, docs, scrape, extract, crawl) and to make the output shape identical across them.
The audience is stated explicitly and it is two groups. Humans who would otherwise reach for curl piped into pandoc, or open a browser tab. And AI agents, which the README says want "structured, predictable output (--json everywhere), documented exit codes for control flow, and a single ketch config call to discover what backends are active." That second framing is the more specific claim, and it is the one worth judging the project against. The design bet is that an operator configures a backend once and every later invocation is backend-agnostic, so the agent never probes the environment or branches on which provider is present.
How the backend abstraction actually works, and where Reciprocal Rank Fusion fits
The mechanism is a config layer sitting between the command and the provider. The README gives the operator path as "ketch config set backend searxng", after which, in its words, "every agent invocation afterward just calls ketch search or ketch scrape without knowing or caring which provider is behind it." Three surfaces are backed by named providers. search lists Brave, DuckDuckGo, SearXNG, Exa, Firecrawl, Keenable, Tavily, Parallel and SerpBase, with degoog also appearing in the backends table. code runs against Grep, Sourcegraph or GitHub Code Search, with Grep as the default. docs is backed by Context7, described as curated, version-aware snippets.
The more interesting piece is what happens when you do not want to pick one engine. ketch search --multi federates across every usable backend and fuses the rankings with Reciprocal Rank Fusion, so a page several engines rank highly floats up, duplicates are collapsed by URL, and each result carries a tag naming the engines that returned it. ketch search --random shuffles the backend list, tries one, and falls back to the rest, which the README frames as avoiding wasted rate limits across all providers. Both accept a bare form meaning all usable backends, or an explicit list such as =brave,exa. Both are mutually exclusive with --backend and with each other. That exclusivity is a real constraint rather than a detail: you cannot ask for a specific backend and multi-federation in the same call.
Scraping runs a readability plus markdown pipeline. The extract subcommand exposes that pipeline on its own, so curl -L https://chain.sh/ketch | ketch extract converts piped HTML without a fetch, a cache or a browser. The README also shows cat page.html | ketch extract --select article --max-chars 4000, which is the shape you would use to bound context length before handing text to a model.
Getting it running: install paths, the key-gated default, and the config keys that matter
Installation is offered three ways. brew install 1broseidon/tap/ketch for Homebrew, go install github.com/1broseidon/ketch@latest for a Go toolchain, or a prebuilt binary from the releases page covering linux, darwin and windows on amd64 and arm64.
The first friction point is the default search backend. The README states plainly that web search needs a backend configured first and that the default, brave, requires a free API key: ketch config set brave_api_key <key>. The backends table adds that Tavily and SerpBase are also key-gated (tavily_api_key, serpbase_api_key), while ddg, searxng, exa, firecrawl and keenable are listed with their own setup notes in the material I have. code search is the exception that works with no configuration at all: ketch code "http.NewRequestWithContext" --lang go --limit 2 returns matches from public repositories with file paths, line numbers and a github.com link per hit.
PDF handling has its own config surface. ketch scrape detects a PDF from the response MIME type or a %PDF- signature and extracts text with a built-in pure-Go parser. If you want better output you configure an external converter that writes Markdown to stdout, capped at 10 MiB, with a shlex-parsed command containing exactly one {input} placeholder. The README's example is ketch config set external_pdf_to_md_converter_command 'pdftotext "{input}" -' alongside ketch config set external_pdf_to_md_converter_timeout_sec 300. Note the semantics: once configured, the external converter is authoritative and its failures are returned rather than silently falling back to the built-in parser. That is a defensible choice, but it means a broken pdftotext on the host turns into scrape failures rather than degraded results.
Two more keys are worth knowing before you script anything. ketch config with no arguments prints the effective configuration as JSON, which is the discovery call the README points agents at. ketch doctor runs a live health check of every backend, the browser and the cache, exiting 0 when healthy and 5 when a configured surface is broken.
The PDF boundary and other places ketch is the wrong tool
The clearest limitation is stated in the README itself: scanned or image-only PDFs need OCR and return a precondition error with an OCR-converter hint in the built-in path. If your document corpus is scans, ketch's built-in parser will not help, and the external converter route only helps if you supply a command that does the OCR work itself. There is no OCR engine inside the binary as far as the material shows.
PDFs also behave differently from HTML in ways that can surprise a caller. The README says PDF binary output is never emitted, and that --raw and --select reject PDFs as validation errors. So a script that passes --select article to every URL will fail on any URL that turns out to be a PDF. With --force-browser, PDF markdown still uses text extraction and never opens Chromium's PDF viewer, which is a deliberate divergence from what the flag name might lead you to expect.
The stateless design has a cost too. There is no daemon and no API server, which is the selling point, but it also means no shared in-process state between invocations. The cache subcommand exists (ketch cache shows page-cache stats or clears them), so some state is persisted on disk, though the material does not describe the eviction policy or the cache location. If you need per-tenant rate limiting, audit logging or a central quota, a stateless binary gives you nowhere obvious to put it. The crawl subcommand does offer optional background execution and status tracking, which is the one place the project steps toward long-running work, but the README does not describe how that background state is stored or cleaned up.
How ketch differs from calling provider APIs or MCP servers directly
The obvious alternative is to call the providers yourself. If you only ever need Brave search, a direct HTTP call to the Brave API is a few lines and no extra binary. The difference in approach is what ketch adds on top: a uniform response envelope, one --json shape across search, code, docs and scrape, exit codes designed to be branched on, and the ability to switch engines by changing a config key rather than rewriting a client. The --multi and --random modes have no direct equivalent in a single-provider client, because they only make sense once several backends sit behind one interface. Reciprocal Rank Fusion across engines is a concrete thing you would otherwise have to implement and tune yourself.
The second alternative is an MCP server, and ketch ships one: ketch mcp serve runs ketch as an MCP server over stdio, exposing the five research surfaces as tools. So the choice is not ketch versus MCP, it is whether an agent host speaks MCP or shells out. A host that already speaks MCP can use ketch mcp serve and get the same backends without parsing CLI output. A host that does not, or a shell script, is better served by the plain subcommands. The README lists five research surfaces for the MCP mode, which lines up with search, code, docs, scrape and crawl, though extract is not named in that count.
A third comparison point is the curl | pandoc pipeline the README mentions. ketch extract is positioned as the replacement, and the difference is that it applies a readability pass before markdown conversion rather than converting the whole document, plus it supports --select and --max-chars to bound what comes out.
Maintenance surface: release cadence, Go toolchain and the MIT licence
The release history in the material shows v0.16.2 on 2026-09-10, v0.16.1 two days earlier, and v0.15.0 on 2026-09-07. Three releases in four days, with a minor version bump in the middle, is a fast cadence for a pre-1.0 project. That is worth weighing if you pin ketch in a production agent: the surface you script against is still moving. The repository is not archived, and the last push timestamp matches the v0.16.2 release.
For upgrade cost, the go install path ties you to a Go toolchain and to whatever the module requires; the Homebrew and prebuilt binary paths avoid that. Because the CLI is stateless and configured through ketch config set, upgrading is mostly a matter of replacing the binary and re-checking that your configured backends still exist under the same names. That is exactly what ketch doctor is for. The exit code 5 on a broken configured surface is the signal to watch after an upgrade, and the README points at site/reference/commands.md for the full command reference rather than inlining it.
On licensing, the repository is MIT. I am not a lawyer and this is not legal advice, but the practical implication of MIT for an agent tool is that embedding the binary or vendoring the source into a commercial product does not by itself impose a copyleft obligation. What MIT does not settle is the terms of the backends you point it at: Brave, Tavily, SerpBase, Exa, Firecrawl, Sourcegraph, Context7 and the rest each have their own terms, and ketch's licence says nothing about them. If you are shipping a product that queries those providers through ketch, the provider agreements are the ones to read.
Who should pick it up, and the one command to run first
ketch fits an operator who wants a single binary on a build agent or a developer laptop, who is willing to run ketch config set brave_api_key <key> or point backend at searxng or ddg once, and who then wants agents to call ketch search and ketch scrape without per-provider glue. The --json flag on every command and the documented exit codes are the parts that make it usable from a script rather than only from a terminal, and the --multi federation is the feature that is hard to replicate cheaply by hand.
It fits less well if your documents are scans, if you need centralised quotas and audit trails, or if you cannot accept that --select and --raw reject PDFs. It also fits less well if you need a stable pre-1.0 API contract, given three releases in four days at v0.16.x.
Before committing, run ketch doctor against your intended configuration. Exit 0 means every backend, the browser and the cache are healthy; exit 5 means a configured surface is broken, and that is the state you want to discover before an agent does. Then read site/reference/commands.md for the flag reference, because the README defers to it and the flag list per command is not reproduced in the repository front page.
Editorial conclusion
Adopt ketch if you are running agents or shell scripts that need search, code grep, docs and scraping behind one command with stable JSON and exit codes, and if you are willing to configure a backend key once. Do not adopt it if your corpus is scanned PDFs, since the built-in parser needs OCR and returns a precondition error, or if you need a hosted service with per-tenant quotas. Verify first with ketch doctor, which exits 5 when a configured surface is broken, and read the exit-code table in site/reference/commands.md before you build control flow on it.
Community notes