Changelog
This page mirrors the canonical CHANGELOG.md in the repo root. Versions follow Semantic Versioning and match the published git tags.
v0.14.0 — 2026-08-07
Added
- Parallel search backend: keyless current-web search through Parallel's hosted Search MCP endpoint. Wired through
NewFromConfig, config discovery, CLI/MCP selection, multi/random search, andketch doctorwithout changing the Brave default or adding authentication configuration. - SerpBase search backend: Google search results via the SerpBase REST API with query-param
api_keyauth (serpbase_api_key/serpbase_api_keys,KETCH_SERPBASE_API_KEY). Keyed only — no keyless mode. Wired through config set/discovery,NewFromConfig, multi/random (--multi=allincludes it when a key is set), MCP, andketch doctor. - Tavily search backend: agent-oriented web search with Bearer auth (
tavily_api_key/tavily_api_keys,KETCH_TAVILY_API_KEY). Keyed only. Defaultsearch_depthisbasic(1 credit); results fill bothDescriptionandContentfrom Tavily's extracted text. Wired through config set/discovery,NewFromConfig, multi/random, MCP, andketch doctor. - Self-hosted Firecrawl (#31):
firecrawl_url(defaulthttps://api.firecrawl.dev) overrides the Firecrawl API base; ketch appends/v2/search. Hosted cloud still requiresfirecrawl_api_key; a non-default base allows keyless self-hosted instances. A pasted full endpoint is normalized back to its base.
Changed
- Contributor-facing design documentation now lives in
design/:DESIGN.md(mental model, core abstractions, and an explicit Non-Goals & Scope section),ROADMAP.md(non-committal directions), andadr/(Architecture Decision Records).
Fixed
- Parallel search results no longer break the one-result-per-line output contract. Titles and excerpts from extracted page text could carry newlines, so a 3-result
--minimalquery emitted 30 lines and corrupted row parsing. Both fields now collapse whitespace to a single space before bounding;Contentkeeps the complete text. ketch doctorno longer reports healthy self-hosted search instances asunreachable. Self-hosted Firecrawl is now probed for liveness instead of results, and SearXNG keeps its realformat=jsonsearch on a 10s budget.
v0.13.0 — 2026-07-25
Added
- Environment-variable configuration (#26): nearly every config key now has a
KETCH_*env override (mechanicalKETCH_+ upper-snake naming, e.g.KETCH_BRAVE_API_KEY,KETCH_LIMIT), with precedence CLI flag > env > config file > default. Singular*_API_KEYvars accept comma-separated lists that replace the provider's key pool.KETCH_CONFIG=<path>selects an alternate config file (read and write);KETCH_GITHUB_TOKENslots above the config file in the token chain. Invalid env values fail loud — listing every bad variable — but only on commands that consume config;version,help,completion, andconfig init/set/pathkeep working under a broken environment.ketch configgains anenv_overridesprovenance section (previous secret values redacted),config setnever persists env-derived values, andKETCH_*secrets are scrubbed from browser and PDF-converter subprocess environments.url_rewrites,spa_markers, and the plural*_api_keysfields remain file-only. - Configurable HTTP User-Agent for scrape fetches: override via
ketch config set user_agent <ua>,KETCH_USER_AGENT, or--user-agentonscrape/search/crawl(flag > env > config > default). Empty clears back to the built-in default.ketch configalways reports the effective UA so agents can diagnose bot-filter 403s without guessing.
Changed
- Default scrape
User-Agentis now an honestketch/<version> (+https://github.com/1broseidon/ketch)instead ofMozilla/5.0 (compatible; ketch/1.0). The old string matched Shield Security and similar fake-crawler rules (e.g.https://jenson.org/ma/returned HTTP 403 while barecurlsucceeded). No browser impersonation — operators who need a custom UA set one explicitly. Release builds embed the semver; local/dirty builds collapse toketch/dev. - Headless browser fetches no longer inherit Rod's default
LaptopWithMDPIScreendevice emulation (a hardcoded macOS Chrome 114 UA that bot filters also blocklist). Cleared viadevices.Clear, so the browser presents as the real installed Chrome, includingsec-ch-uaclient hints.
Fixed
- Readability no longer silently drops data tables when a smaller table (e.g. a Wikipedia infobox) survives extraction (#28). The raw-table fallback now compares DOM data-table counts between the raw HTML and readability's output — ignoring layout/nav/footer/presentation/hidden tables — and only swaps to the noisier full-page conversion when readability actually lost tables. Relative links on the raw path are absolutized, so recovered tables don't ship bare
/wiki/...hrefs. ketch browser installno longer wedges after an interrupted download (#27). Extraction is not atomic, so a partial tree left by a cancelled or failed download broke every subsequent attempt — and broke it differently each time, surfacing as Rod's misleading "can't find a browser binary for your OS". The revision directory is now cleared before download, so each install starts from a known state, and a failed download reports the cache path plus theketch config set browser <path>escape hatch instead of a bare Rod error. Affected macOS most visibly, whereChromium.appships symlink-heavy frameworks.--force-browserno longer aborts when the preliminary HTTP classification probe is blocked (HTTP 403 and similar). The probe exists only to detect PDFs before a forced render; a failed probe now falls through to the browser instead of failing the scrape — which is the point of the flag against bot walls. Without a configured browser the probe error is still returned.
v0.12.0 — 2026-07-15
Added
- BYO-cookie support (#25): ketch loads a Netscape
cookies.txtjar — the format exported by browser cookies.txt extensions and consumed bycurl/yt-dlp— and injects matching cookies on both the HTTP and headless-browser fetch paths, unblocking session- and consent-gated pages.--cookie-file <path>onscrape,search(for--scrapefetches), andcrawl, plus a persistentcookie_fileconfig key (the flag overrides config; an explicit empty flag disables cookies for the run). Scope (Domain, HostOnly, Path, Secure) is re-matched on every request and redirect; a configured jar gets an isolated page-cache namespace; cookie values are never printed anywhere, and a group/world-readable jar triggers achmod 600warning. - Multiple API keys per provider (#23): plural config fields (
brave_api_keys,exa_api_keys,firecrawl_api_keys,keenable_api_keys) alongside the singular keys. A random key is picked per request to spread rate limits, with one retry on 401/429 (402 for Firecrawl) when the pool holds more than one.config setaccepts JSON arrays,ketch configreports*_api_keys_countonly, andketch doctorprobes the effective pools. - Random provider selection (#24):
ketch search --random(or--random=brave,exa) shuffles the candidate backends, tries one, and falls back to the rest on failure — stopping at the first successful response. Mirrors--multiflag semantics, mutually exclusive with--backend/--multi, with MCP parity via thesearchtool'srandominput. - PDF text extraction for
scrape,search --scrape, andcrawl(#19): text-based PDFs are detected by MIME type or%PDF-magic bytes and extracted with a built-in pure-Go parser. An optional external converter (external_pdf_to_md_converter_commandwith exactly one{input}placeholder, plusexternal_pdf_to_md_converter_timeout_sec, default 300) is authoritative when configured.--rawand--selectreject PDFs (exit 2); PDFs without a text layer are precondition errors (exit 5) with an OCR hint. - Bundled skill verb renamed
research.md→ketch-research.md(#22) to avoid namespace conflicts.
v0.11.0 — 2026-07-07
Added
- Federated multi-backend search:
ketch search --multi(and the MCPsearchtool'smultiinput) queries several backends at once and fuses their rankings with Reciprocal Rank Fusion, so a page multiple engines rank highly rises to the top. Bare--multifederates every usable backend (zero-config installs still get ddg + exa + keenable);--multi=brave,exapicks an explicit set (the=is required for a list). Results are deduplicated by URL canonicalization, each backend gets a 10s timeout with graceful degradation (failed:frontmatter / additive MCPerrorsmap), and every fused result lists the engines that found it. firecrawlweb search backend via the Firecrawl v2 search API, configured withketch config set firecrawl_api_key <key>and selected with-b firecrawl. Reportsfirecrawl_api_key_setinketch configdiscovery and is covered by a liveketch doctorprobe.keenableweb search backend over the Keenable index, built for AI agents. Keyless by default (public endpoint, rate-limited); an optionalkeenable_api_keylifts the rate limit.ketch extract— pipe HTML through ketch's readability + markdown pipeline with no fetch:curl -L <url> | ketch extract. Supports--url(metadata + relative-link resolution),--select,--trim,--max-chars, and the global--json; deliberately no cache, browser, or MCP surface.- Claude Code plugin + marketplace manifest:
claude plugin marketplace add 1broseidon/ketch, thenclaude plugin install ketch@ketchwires upketch mcp serveand the bundled agent skill. Optional convenience — the stateless CLI remains the zero-infrastructure path.
v0.10.0 — 2026-07-01
Added
ketch mcp serve— ketch as an MCP server over stdio, exposingsearch,code,docs,scrape, andcrawlwith the same config-driven backends as the CLI, stable[kind]error prefixes mirroring the CLI exit codes, and concise server instructions in the initialize result.- Bundled agent skill at
skills/ketch/— a SKILL.md playbook any skill-loading agent can install: surface routing, token budgets, error-prefix control flow, a deep-research recipe, and a guided backend-setup flow. ketch doctor— deterministic live health checks for every surface (search/code/docs backends, browser, cache) withok/no_key/unreachable/misconfiguredstatuses, fix hints (including the SearXNGformat: jsontrap), aligned human output or stable--json.- Key-presence booleans in
ketch configdiscovery (brave_api_key_set,exa_api_key_set,context7_api_key_set,github_token_set) so agents can tell "unconfigured" from "ready" in one call. - Shared config-driven constructors (
search/code/docs/scrape/cache.NewFromConfig) used by both the CLI and MCP server, ending backend-switch drift. MIT LICENSE file.
Changed
ketch docs --libraryon a non-context7 backend andketch code --regexon GitHub are clear validation errors instead of silent re-routes; the unimplementedlocaldocs backend is rejected up front and no longer advertised.
Fixed
- Context7 404s classify as not-found (exit 3) instead of retryable-upstream;
docs --resolverespects--limit;ketch cache --jsonemits stable JSON; unknown-backend errors list the valid options; a data race in the scraper's browser-binary resolution.
v0.9.5 — 2026-06-29
Fixed
- Tables render as GFM pipe tables across readability, raw, and selector extraction paths (#14).
- Brave searches cap the API
countat Brave's per-request maximum of 20, preventing HTTP 422 when--limitis higher (#17). - Client-rendered SPA pages (e.g. Next.js App Router) are no longer misdetected as static; adds a
spa_markersconfig key to extend detection (#15).
v0.9.4 — 2026-06-22
Added
exaweb search backend via Exa's hosted MCP endpoint, with optionalexa_api_keyconfig for authenticated usage.ketch scrape --force-browser— always render via the configured browser, skipping JS-shell auto-detection (#12); composes with--rawand--select. Documents the previously-undocumented--rawflag (#11).
v0.9.3 — 2026-05-29
Added
grepappcode search backend (Grep MCP,mcp.grep.app) — keyless, literal/regex search across 1M+ public GitHub repos. Now the default forketch code(wassourcegraph).ketch code --regexinterprets the query as a regular expression. Supported ongrepappandsourcegraph;githubrejects it because REST code search is literal-only.
Changed
code.Searcherinterface refactored from positional params to aQuerystruct so backend options can grow without signature churn.
Fixed
- Documentation drift across README, CLAUDE.md, and the site reference: corrected the
ketch codedefault backend, scoped-b/--backendtosearch/code/docs, documented previously-missing flags and theversioncommand, and synced theketch configdiscovery JSON example with real output.
v0.9.2 — 2026-05-24
Added
- Differentiated exit codes so scripts and agents can distinguish failure classes:
2validation/bad input,3not found,4upstream/network,5precondition (missing API key/token),6interrupted.
Changed
ketch crawlno longer swallows Ctrl+C as exit 0. SIGINT during a foreground crawl exits6while still printing the summary collected before shutdown.-b/--backendis no longer a persistent root flag — it lives onsearch(matching the existingcodeanddocslocal flags).ketch -b ddg search "q"andketch search -b ddg "q"both still work.
v0.9.1 — 2026-05-22
Added
url_rewritesconfig: an ordered list of{match, replace}regex rules applied transparently before any fetch inscrape,search --scrape, andcrawl. Redirect URLs without touching the agent surface (e.g.www.reddit.com→old.reddit.com). The original URL is preserved in output asurl:; the fetched URL appears asfetched_url:when different.
Changed
crawl.Crawl()now takes a*scrape.Scraperfrom the caller (Options.BrowserBinremoved). Affects only direct importers of thecrawlpackage — the CLI is unchanged.
Fixed
- Broken example URLs in the README (#8, thanks @abhmul).
v0.9.0 — 2026-05-12
Changed
- Breaking. Reusable packages moved from
pkg/<pkg>to the module root. Import paths change fromgithub.com/1broseidon/ketch/pkg/<pkg>togithub.com/1broseidon/ketch/<pkg>. - VitePress documentation site moved from
docs/tosite/, freeingdocs/for the docs-search Go package. Site URL is unaffected.
v0.8.1 — 2026-05-12
Fixed
- Page cache no longer returns unrendered JS-shell content after a browser is configured. Entries record their fetch source (
http/http_shell/browser); JS-shell hits are bypassed once a browser is available, and pre-existing entries migrate in place. Fixes #7.
v0.8.0 — 2026-05-02
Changed
- Reusable packages moved from
internal/topkg/(cache,code,config,crawl,docs,extract,httpx,scrape,search,updatecheck). Pure rename — exposes them for import by external Go programs.
v0.7.1 — 2026-04-21
Fixed
ketch docs --resolve <name>returned HTTP 400 after an upstream Context7 API change. Query param renamed (?q=→?query=), results moved into a{"results": [...]}envelope, and field names updated.ketch docs <query>and--librarywere unaffected.
v0.7.0 — 2026-04-21
Added
ketch versioncommand and--versionflag — reports build version, commit, and date.- Passive update reminder when a newer release exists (cached 24h, throttled). Honors
KETCH_NO_UPDATE_NOTIFIER=1,CI,--json, and non-TTY stderr. - Ctrl+C (SIGINT) and SIGTERM cancel the root context, so foreground
ketch crawldrains gracefully.
Changed
- HTTP stack tuned for crawling: a shared
*http.Transportwith a 30s timeout,MaxIdleConnsPerHost=16, HTTP/2, and a keep-alive dialer, reused by every backend. context.Contextplumbed through the scraper, browser, crawler, and sitemap/llms.txt fetches — cancellation reaches into Rod andhttp.Client.Do.- All HTTP response bodies capped at 20 MiB via
io.LimitReader.
v0.6.0 — 2026-04-11
Added
ketch scrapesmart input detection: multiple args, JSON array, file (one URL per line), or stdin pipe — auto-detected, no extra flags.--concurrency Nonketch scrape(default 5) — semaphore-based worker pool.--selectand--no-llms-txtnow propagate to multi-URL scraping.
Changed
search.Searcher.Searchanddocs.Searcher.Searchnow takecontext.Contextas the first param, consistent withcode.Searcher.
v0.5.0 — 2026-04-11
Added
ketch scrape --select <css>— CSS selector extraction, bypasses readability (with browser fallback for JS-rendered pages).ketch scrape --max-chars N— truncate markdown output to N Unicode code points.ketch scrape --trim— strip markdown formatting while preserving content text (typically 30–40% token reduction).ketch search/code/docs --minimal— one result per line, tab-separated, pipe-friendly.- llms.txt auto-detection: bare domain URLs check
/llms.txtand return it directly when found. Disable with--no-llms-txt.
v0.4.0 — 2026-04-11
Added
ketch code -b github— GitHub Code Search backend. Token resolution: explicit config →$GITHUB_TOKEN→$GH_TOKEN→gh auth token.- GitHub backend populates star counts via a single batched GraphQL call.
github_token_sourcefield in theketch configdiscovery payload (the token itself is never printed).
Changed
code.Searcher.Searchtakescontext.Contextas its first arg; backends usehttp.NewRequestWithContextfor cancellation.
v0.3.0 — 2026-04-10
Added
ketch codecommand — code search via the Sourcegraph streaming SSE API. Zero config.ketch docscommand — library documentation search via Context7. Requires an API key.- Config keys:
code_backend,docs_backend,context7_api_key,sourcegraph_url.
v0.2.0
Browser rendering, crawl, and cache overhaul.
- Browser rendering: JS-rendered pages (React, Angular, Salesforce Lightning) automatically detected and re-fetched via headless Chrome using Rod.
ketch config set browser chrome— configure browserketch browser install— download Chromiumketch browser status— check browser availability- Transparent fallback — agents see the same output format
- Crawl command: BFS and sitemap-based site crawling.
ketch crawl <url>— BFS crawl with configurable depth and concurrencyketch crawl <url> --sitemap— sitemap-based crawlketch crawl <url> --background— detached process with status trackingketch crawl status/ketch crawl stop— monitor and control background crawls
- Cache backend: migrated from filesystem to an embedded bbolt database.
- Single
cache.dbfile;Storeinterface for future backends - Default TTL changed to 72h; shared cache between scrape and crawl
- Single
v0.1.0
Initial release.
- Search via Brave, DuckDuckGo, or SearXNG
- Scrape URLs to clean markdown (readability + html-to-markdown)
- Concurrent batch scraping
- YAML frontmatter + markdown output format
- JSON config at
~/.config/ketch/config.json - TTL-based page cache with platform-correct paths
ketch configdiscovery payload for agent introspection--jsonflag on all commands- GoReleaser + Homebrew tap publishing