github.com/1broseidon/ketch
v0.14.0
1broseidon/ketch
What this package is like to depend on
Last release 16 days ago
08 Aug 2026
Ships fairly regularly
a new release about every 1 weeks
Nearly every release is documented
notes for 19 of 21 stable releases
Nothing withdrawn
no release was ever pulled
5 months old
28 releases · first in 2026
28 releases in the last 12 months
see the full history below
Release timeline
28 releases · Mar 2026 to Aug 2026Releases
latest 28-
v0.14.008 Aug 2026Release notes
Open source →Changelog
- f25d7ed Add Parallel as a keyless search backend (#33)
- 00afdcb chore(release): v0.14.0
- 027b4b4 feat: add SerpBase (Google) search engine via REST API (#34)
- 0a6ca1f feat: add Tavily search backend
- eaa2510 feat: add firecrawl_url for self-hosted Firecrawl (#31) (#32)
- fde7912 fix: flatten Parallel result titles and descriptions onto one line (#38)
- 50ed51f fix: normalize a pasted Firecrawl endpoint back to its API base
- dae1a19 fix: stop doctor reporting healthy self-hosted search as unreachable
Release notes
Open source →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
GET https://api.serpbase.dev/google/searchwith query-paramapi_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(401 → misconfigured; 402 → ok with credits detail; 429 → ok rate limited). - Tavily search backend. Agent-oriented web search via
POST https://api.tavily.com/searchwith Bearer auth (tavily_api_key/tavily_api_keys,KETCH_TAVILY_API_KEY). Keyed only — no keyless mode. Defaultsearch_depthisbasic(1 credit). Results fill bothDescriptionandContentfrom Tavily's extracted text. Wired through config set/discovery,NewFromConfig, multi/random (--multi=allincludes it when a key is set), MCP, andketch doctor(401 → misconfigured; 429/432/433 → ok with limit detail). - 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. Wired through config set/discovery,KETCH_FIRECRAWL_URL, searchNewFromConfig, andketch doctor. A pasted full endpoint is normalized back to its base, so.../v2/searchis neither doubled into.../v2/search/v2/searchnor mistaken for a self-hosted instance when it points at the hosted API.
Fixed
- Parallel search results no longer break the one-result-per-line output contract. Parallel returns extracted page text, so titles and excerpts can carry newlines, tabs, and indentation; bounding the description by rune count capped its length but not its shape, and titles were passed through raw. Because results print one per line —
--minimalemitsurl\ttitle\tdescription\n— an embedded newline split a single result across many lines (a 3-result query emitted 30 lines) and corrupted row parsing for scripted callers. Both fields now collapse runs of whitespace to a single space before bounding;Contentstill keeps the complete, unflattened excerpt text. Scoped to the Parallel backend — other providers return short single-line snippets natively. ketch doctorno longer reports healthy self-hosted search instances asunreachable. Both offenders answer more slowly than a hosted API because they run the search themselves: self-hosted Firecrawl spends about eight seconds on a one-result query, and SearXNG about three — landing exactly on doctor's 3s per-probe budget. A self-hosted Firecrawl is now probed for liveness instead of results (a request its validation rejects still proves/v2/searchanswers, distinguishes a base URL pointing elsewhere via 404, and surfaces an instance that demands a key), which also drops that check from a timeout to milliseconds. SearXNG keeps its realformat=jsonsearch — that probe is what detects the blocked-JSON trap — on a 10s budget. Wrong-base 404s now namefirecrawl_urlin the fix hint.
Changed
- Contributor-facing design documentation now lives in
design/:DESIGN.md(the mental model, core abstractions, and the reasoning behind each design principle, including an explicit Non-Goals & Scope section),ROADMAP.md(non-committal, dependency-ordered directions), andadr/(Architecture Decision Records, including new records for the exit-code/error-prefix taxonomy and fast-path-first scraping). Previously the ADR directory sat insidedocs/, which is the Go packagegithub.com/1broseidon/ketch/docs; the prose has moved out so that directory holds only package source.
-
v0.13.1-0.20260802060535-0a6ca1fc1d9c02 Aug 2026 pre-releaseNothing published for this version
-
v0.13.025 Jul 2026Release notes
Open source →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 config showgains 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; seedesign/adr/0001-env-var-config.md. - 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 / 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 via
converter.WithDomain, so recovered tables don't ship bare/wiki/...hrefs. Verified againsthttps://en.wikipedia.org/wiki/List_of_FIFA_World_Cup_finals. 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 (expected only one dir in ...when the leftover confused the single-directory check, thenfile existson the framework symlinks, which Rod reports as the 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/etc.). 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.
- Environment-variable configuration (#26). Nearly every config key now has a
-
v0.12.1-0.20260715041102-2348e01b292f15 Jul 2026 pre-releaseNothing published for this version
-
v0.12.015 Jul 2026Release notes
Open source →Changelog
- a537dd8 chore(release): v0.12.0 changelog
- 7670332 feat: PDF text extraction for scrape, search, and crawl (#19)
- 1948391 feat: PDF text extraction for scrape, search, and crawl (#19)
- e5c1194 feat: authenticated scraping via Netscape cookie file (#25)
- 87d42c9 feat: multiple API keys per provider (#23) + random provider selection (#24)
- a2e0e35 feat: multiple API keys per provider (#23) + random provider selection (#24)
- 9611112 refactor: rename research.md to ketch-research.md (#22)
Release notes
Open source →Added
- BYO-cookie support (#25) for session- and consent-gated pages (e.g. Nvidia NGC docs that render as a bare consent banner to an anonymous headless browser). ketch loads a Netscape
cookies.txtjar — the format exported by browser cookies.txt extensions and consumed bycurl/yt-dlp(no bespoke JSON schema) — via the newcookies/package, honoring the#HttpOnly_line prefix, rejecting malformed scope fields, and rechecking expiry before every request. Two ingestion surfaces feed one jar: a--cookie-file <path>flag onscrape,search(for--scrapefetches), andcrawl, plus a persistentketch config set cookie_file <path>key; the flag overrides config, and an explicit empty flag disables cookies for the run. Matching cookies are injected at both fetch layers — the HTTP request (including/llms.txt, sitemaps, and nested sitemap indexes) and the Rod browser path (cookies set before navigation, which is what unblocks consent-banner walls). HTTP redirects clear inherited cookies and re-match Domain, HostOnly, Path, and Secure scope for every hop, preventing HTTPS-to-HTTP, subdomain, and path-scope leaks. A short fingerprint of every live cookie in the configured jar is folded into the page-cache key, isolating the namespace even before a redirect reaches cookie scope, so authenticated content never collides with anonymous cached copies. Crawl inherits the same jar and cache-key behavior automatically. Cookie values are never printed anywhere (frontmatter,--json, errors,doctor):ketch doctorreportscookies/jar: configured (N cookies, M expired), and a group/world-readable jar triggers a one-time stderrchmod 600warning. The cache directory/database use private0700/0600modes on POSIX and existing database permissions are tightened on open; authenticated responses remain cacheable under cookie-specific keys, so use--no-cachewhen they must not be stored. Non-goals: no password storage, no reading browser profile cookie DBs, no CDP attach to a running browser, no per-call MCP cookie parameter (config-levelcookie_filestill applies to MCP). Respecting site ToS and using only your own session cookies is the operator's responsibility. - Multiple API keys per provider (#23). Plural config fields (
brave_api_keys,exa_api_keys,firecrawl_api_keys,keenable_api_keys) alongside the existing singular fields; a random key is picked per request to spread rate limits. One retry on 401/429 (402 for Firecrawl) with a different key when the pool has more than one. Config set accepts JSON arrays; config show reports counts only; doctor probes effective key pools. Config file mode enforced to 0600; no key values in any output. - Random provider selection (#24).
--random=brave,ddg,exashuffles providers, tries one, and falls back to the rest on failure — stops on the first successful response. Mirrors--multiflag semantics, mutually exclusive with--backend/--multi. MCP parity viarandomparam. Output shows which backend answered and which failed. - PDF text extraction for
scrape,search --scrape, andcrawl(#19). Text-based PDFs are detected by MIME type or%PDF-magic bytes and extracted by the always-available pure-Goledongthuc/pdfimplementation. Operators can instead configureexternal_pdf_to_md_converter_command(validated shlex syntax with exactly one{input}placeholder, Markdown on stdout capped at 10 MiB) andexternal_pdf_to_md_converter_timeout_sec(default 300 seconds); the external converter is authoritative and never silently falls back to the built-in parser. Response bodies retain the existing behavior of truncating at 20 MiB.--rawand--selectreject PDFs as validation errors (exit 2 /[validation]); normal--force-browserPDF scrapes bypass Chromium and extract text directly. PDFs without a text layer are non-retryable precondition errors (exit 5 /[precondition]) with an OCR-converter hint. Crawl link discovery and browser rendering now run only for HTML responses. - Renamed bundled skill verb
research.md→ketch-research.md(#22) to avoid namespace conflicts for users who already have their ownresearchskill installed.
Changed
- The existing
scrape.NewBrowserConn(binPath string)API remains source-compatible;scrape.NewBrowserConnWithCookies(binPath string, jar *cookies.Jar)adds cookie-aware browser construction. TheBrowserConninterface itself is unchanged. Page-cache keys are now cookie-aware: a configured jar with live cookies gets an isolated namespace, including when only a redirect target matches.
-
v0.11.1-0.20260707054810-200c159fe16507 Jul 2026 pre-releaseNothing published for this version
-
v0.11.007 Jul 2026Release notes
Open source →Changelog
- 90eeef5 Merge pull request #20 from keenableai/feat/keenable-search-backend
- 46c6980 Merge pull request #21 from rakshith48/feat/firecrawl-search-backend
- b04a2fa chore(release): v0.11.0
- a69f054 feat: Claude Code plugin + marketplace manifest
- e42d2c3 feat: add Firecrawl web search backend
- 07a3e22 feat: add keenable search backend
- d97935b feat: ketch extract — piped HTML to markdown, no fetch
- c413b73 feat: multi-backend federated search with RRF fusion
- a0e52b7 mcp: list keenable in the search tool schema (post-merge gap from #20)
- 38e8603 refactor: split runMultiSearch and rankFuse below the gocyclo ceiling
Release notes
Open source →Added
- Federated multi-backend search via
ketch search --multi(and the MCPsearchtool'smultiinput): query several backends at once and fuse their rankings with Reciprocal Rank Fusion (RRF, k=60) so a page multiple engines rank highly rises to the top — better results, not just more. Bare--multi(or--multi=all) federates every usable backend using the same key-presence rule as the rest of ketch (ddg/exa/keenablealways;brave/firecrawlwith a key;searxngalways, a dead instance just fails fast and is skipped), so a zero-config install still gets a real federation.--multi=brave,exapicks an explicit ordered set — an unknown name is a validation error (exit 2 /[validation]), a named-but-unconfigured backend a precondition error (exit 5 /[precondition]). Mutually exclusive with--backend(a set contradiction is rejected loudly, not silently resolved). Because--multitakes an optional value, a list needs the=form (--multi=brave,exa);--multi brave,exais caught and rejected as a validation error with a hint to use--multi=brave,exa. Results are deduplicated by a new order-of-operations URL canonicalization (scheme/host lowercasing,http→httpsfolding, default-port andwww./fragment/tracking-param stripping — canonical form is a merge key only, so the emitted URL is always an original backend URL). Each backend is fanned out concurrently with a 10s per-backend timeout; backends that error or time out are dropped and reported (CLI:warn:on stderr plus afailed:frontmatter key; MCP: an additiveerrorsmap), and the search only fails ([upstream]/ exit 4) when every backend fails. Output is additive:search.Resultgains abackendsfield (omitted for single-backend runs, so existing output is byte-identical), the plain-text frontmatter usesbackends:/failed:with a per-resultfound in:line, and--minimalappends a 4th backends column under--multi. No result caching in v1 (a--multiquery costs N live searches, exactly like N single searches today). Also refreshes the MCPsearchtool description, which had drifted to enumerate only four of the six backends. firecrawlweb search backend via the Firecrawl v2 search API (POST /v2/search), configured withketch config set firecrawl_api_key <key>and selected withketch config set backend firecrawlorketch search -b firecrawl. Uses the sharedhttpxclient and slots into the existingsearch.Searcherinterface /NewFromConfigswitch like the other backends.ketch configdiscovery reportsfirecrawl_api_key_set, andketch doctorgains a live search-backend probe (ok/no_key/misconfigured/unreachable). Same provider that powers Firecrawl scrape/crawl workflows, for operators who want one key for both search and page extraction.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. Wired intoketch doctoras a keyless reachability probe.ketch extract— a stdin-only Cobra subcommand that runs ketch's readability + HTML-to-markdown pipeline over piped HTML and emits frontmatter + markdown (or JSON via the root--json). Reads raw HTML from stdin (curl -L https://example.com | ketch extract,cat page.html | ketch extract --select main), rejects positional args and non-piped terminals withExitValidation, and supports--url(metadata + relative-link resolution only; never printed asabout:blank),--select(CSS selector path, exit 3 on no match, exit 2 on a bad selector),--trim, and--max-chars. Deliberately CLI-only and fetch-free: no cache, no/llms.txtprobe, no browser rendering, no concurrency, and none of scrape's--raw/--no-cache/--concurrency/--force-browser/--no-llms-txtflags. The MCP tool surface is unchanged —extractis not exposed as a tool.- Claude Code plugin + marketplace manifest: the repo now doubles as a Claude Code plugin marketplace (
.claude-plugin/marketplace.json) hosting one plugin (plugins/ketch/) —claude plugin marketplace add 1broseidon/ketch, thenclaude plugin install ketch@ketch. The plugin is an optional convenience for Claude Code users, never a prerequisite (the stateless CLI remains the zero-infrastructure path): it wires upketch mcp serveas a stdio MCP server (.mcp.json, expects the ketch binary >= v0.10.0 on PATH — the plugin does not vendor it) and ships the bundled agent skill via a symlink to the canonical copy atskills/ketch/, which stays where it is for non-Claude-Code agents.
-
v0.10.1-0.20260701230240-a69f0543c94d01 Jul 2026 pre-releaseNothing published for this version
-
v0.10.001 Jul 2026Release notes
Open source →Changelog
- 94a95b9 chore(release): v0.10.0
- f9cf321 feat(mcp): add MCP server exposing search/code/docs/scrape as tools
- aa72ed8 feat(mcp): rebuild MCP server — shared lifecycle, error taxonomy, full tool parity, crawl
- 9278c86 feat(mcp): return server instructions in the initialize result
- 7781975 feat: bundle the ketch agent skill (research + setup verbs, CLI-first)
- 265386e feat: ketch doctor — live health checks for every surface; key-presence booleans in config discovery
- f2fcdee fix(cache): honor --json for stats and clear
- 328ba4a fix(lint): migrate issues.exclude-dirs to v2 linters.exclusions.paths
- a55624e fix: align error classes, backend advertising, and resolve limits across CLI and MCP
- 7e1c8a0 refactor: move backend constructors and scrape pipeline into owning packages
- b5ae378 test(mcp): extend smoke test to the full tool surface
- dfab158 test(mcp): retry transient upstream 504s in the code smoke round-trip
Release notes
Open source →Added
- Bundled agent skill at
skills/ketch/— aSKILL.mdplaybook (plus verb references) any skill-loading agent can install: surface routing (search vs code vs docs vs scrape vs crawl), token budgets with measured costs, error-prefix/exit-code control flow, aketch researchdeep-research recipe (bounded fan-out, cited synthesis), and aketch setupguided backend-configuration flow that prefersketch doctor --jsonand includes the SearXNGformat: jsonsettings fix. CLI-first by design: the stateless CLI is the default transport; the MCP server is honored when the operator wired it up, never required. ketch mcp serve— runs ketch as an MCP (Model Context Protocol) server over stdio viagithub.com/modelcontextprotocol/go-sdk, exposing five tools:search,code,docs,scrape, andcrawl. Tool handlers call the same underlying packages through the same config-driven constructors as the CLI and resolve backends/API keys from the same~/.config/ketch/config, so an agent talking MCP sees the same configured backends as a human usingketchdirectly.- Tool surface:
search(backend/limit/searxng_url, plusscrape+trim+max_charsto inline extracted content per result);code(backend/lang/limit/regexp);docs(backend/library/tokens/limit/resolve);scrape(singleurlor batchurlswith a bounded worker pool and per-URL error entries, selector/raw/force_browser/trim/max_chars/no_cache/no_llms_txt/concurrency, and the CLI's automatic/llms.txtprobe for bare domains);crawl(synchronous bounded BFS: depth/sitemap/allow/deny/max_chars/no_cache,max_pagesdefault 30 hard-capped at 100, 3-minute wall-clock budget, partial results returned withstopped: "max_pages"|"timeout"). Detached background crawls, cache admin, and config stay CLI-only. - Lifecycle: the SDK runs tool calls concurrently, so the headless-browser scraper, the bbolt page-cache handle, and the compiled URL rewriter are constructed once at server start, shared across calls, and closed on shutdown (no per-call Chrome launches or cache-lock contention).
- Agent contract: every tool error carries a stable machine-readable kind prefix mirroring the CLI exit codes —
[validation]/[not_found]/[upstream]/[precondition]/[cancelled]— and all tools declarereadOnlyHint/openWorldHintannotations. Scrape/crawl descriptions note that the server fetches whatever URL it is given (no SSRF filtering).
- Tool surface:
- Shared config-driven constructors
search.NewFromConfig,code.NewFromConfig,docs.NewFromConfig,scrape.NewFromConfig, andcache.NewFromConfig, plus the cache-aware scrape pipeline asscrape.Scrapermethods (CachedScrape,ScrapeRaw,ScrapeSelector,FetchLLMSTxt, ...) andextract.PostProcess/extract.Truncate.cmd/andmcp/both call these; the duplicated backend switches and scrape-pipeline copies (which had already drifted) are gone. - MIT
LICENSEfile. Resolves pkg.go.dev's "License: None detected" (which had hidden the package docs) and satisfies the awesome-go licensing requirement. - The MCP server now returns concise
instructionsin theinitializeresult: tool routing (which of the five tools to use when), that backend defaults come from the operator's config, the error-prefix taxonomy with retry semantics, and advice to bound scrapes of unknown pages withmax_chars/trim. ketch doctor— a deterministic live health check of every surface (likebrew doctor). Concurrent read-only probes with a 3-second per-probe timeout cover the search backends (brave/ddg/searxng/exa), code backends (grepapp/sourcegraph/github, with the full token-resolution chain and a quota-free authed/rate_limitcall when a token resolves), docs (context7), the configured browser binary (on disk/PATH), and the page cache (writable, entry count, size, lock state — via the existing read-only stats path; probes never write cache entries). Each check reportsok/no_key/unreachable/misconfigured(with a fix hint) /skipped; the classic SearXNG trap — stock instances return 403 forformat=jsonuntil settings.yml enables it — is detected as its ownmisconfiguredstatus with the settings.yml hint. Output is an aligned human report or a stable--jsonarray of{surface, backend, status, detail, latency_ms}. Exit0when every applicable check is ok or cleanly skipped; exit5(precondition) when a configured surface is broken — the default backend of a surface, a backend with an API key explicitly set, the configured browser, or the cache. Optional backends merely lacking a key stay informational. Doctor is CLI-only by design (an operator action, likeconfigandcache) and is not exposed over MCP.- Key-presence booleans in the
ketch configdiscovery payload:brave_api_key_set,exa_api_key_set,context7_api_key_set, andgithub_token_setreport whether each credential is configured without ever printing the value.github_token_setfollows the same resolution chain as the existinggithub_token_sourcefield (config →$GITHUB_TOKEN/$GH_TOKEN→ gh CLI): it is true iff the source is notnone. Lets an agent distinguish "backend unconfigured" from "backend ready" in one call instead of firing a request and parsing the[precondition]error.
Changed
ketch docs --librarywith a non-context7 backend now fails with a clear validation error (exit 2) instead of silently ignoring--libraryand re-routing the query to the selected backend. Same rule on the MCPdocstool ([validation] library requires the context7 backend).ketch docs -b local(the unimplemented FTS5 stub) is now rejected up front with "not yet implemented" (exit 5, precondition) instead of failing at search time with exit 4; the MCPdocstool no longer advertiseslocalin its schema.localis no longer advertised as a usable docs backend anywhere:config.AvailableDocBackends(and thusketch configdiscovery output, the root summary, andketch docs --help) now lists onlycontext7. The name is still recognized and rejected with the precondition error above.ketch code --regexon a backend without regex support (github) is now a validation failure (exit 2, was 5): the request is wrong for that backend and no retry or operator action can make it succeed. Aligns the CLI with the MCPcodetool's[validation]classification.
Fixed
- A Context7 404 for a nonexistent library ID is now classified as not-found (exit 3 /
[not_found]) instead of retryable-upstream (exit 4 /[upstream]) on both the CLI and MCP surfaces. The 404 is detected once in the docs package (docs.ErrNotFoundsentinel), not by string matching at the edges. ketch docs --resolvenow respects--limit(and the MCPdocstool'slimit): resolve results are bounded in the sharedResolveLibrarylayer, which previously returned every match regardless of the requested limit.ketch cache --jsonnow emits a stable JSON object (path,entries,size_bytes,size,ttl,locked) instead of ignoring--json;ketch cache clear --jsonemits{"cleared":true}.cache clearon a locked cache now exits 5 (precondition) instead of 1.- Unknown-backend errors now list the valid options, sourced from the config package's
Available*Backendslists so they cannot drift, e.g.unknown search backend "bogus" (available: brave, ddg, searxng, exa). - Documentation drift: AGENTS.md claimed the
ketch codedefault backend issourcegraph(it isgrepapp), andsearch/codehelp text claimed hardcoded defaults ("Brave (default)") when the effective default is config-driven — both now say so. - Data race in
scrape.Scraper:HasBrowser(and the browser-warning path) readbrowserBinunsynchronized whilegetBrowsercan clear it under the mutex on failed resolution. All reads now take the mutex — relevant for concurrent multi-URL scrapes and concurrent MCP tool calls sharing one scraper. - README badges: pkg.go.dev (Go Reference) and Go Report Card, plus GitHub stars and latest release.
- CI workflow (
.github/workflows/ci.yml) running build, lint (golangci-lint), and test on pushes tomainand pull requests, with amake build-checktarget (go build ./...) for the build job.
-
v0.9.529 Jun 2026Release notes
Open source →Fixed
- Tables now render as GFM pipe tables across readability, raw, and selector extraction paths via a shared
base+commonmark+tableMarkdown converter. The table converter promotes header rows, preserves cell newlines, emits empty cells for spans, and skips empty rows; readability extraction now falls back to coarse raw-page conversion when readability drops a table that the raw conversion can render (#14). - Brave searches now cap the API
countparameter at Brave's current per-request maximum of 20, preventing HTTP 422 responses whenketch search --limitis set higher than Brave accepts (#17). Brave non-200 errors now include the response body so upstream validation failures identify the rejected parameter. - Client-rendered SPA pages (e.g. Next.js App Router) are no longer misdetected as static. The JS-shell detector now recognizes modern framework and hydration markers (
__next_f,id="_r_",data-v-app,__sveltekit,data-svelte,q:container,astro-island) and escalates a content-bearing page to a browser render when a strong client-render marker is present and the inline script payload dwarfs the visible text (>8x), bounding needless browser renders. Adds aspa_markersconfig key to extend detection for additional frameworks without code changes (#15).
- Tables now render as GFM pipe tables across readability, raw, and selector extraction paths via a shared
-
v0.9.422 Jun 2026Release notes
Open source →Changelog
- e93dbb9 chore(release): v0.9.4
- bed6374 docs(site): sync changelog with canonical CHANGELOG.md
- c83613e feat(scrape): add --force-browser escape hatch; finalize --raw (#11, #12) (#13)
- d2b29c9 feat: add exa search backend
- 331ad8f feat: add exa search backend (#10)
- 1f9ab77 fix: improve exa backend SSE parsing and content heuristic
Release notes
Open source →Added
exaweb search backend via Exa's hosted MCP endpoint, with optionalexa_api_keyconfig for authenticated usage.ketch scrape --force-browser— a deterministic escape hatch for JS-rendered pages whose meaningful content (e.g. pricing tables) is injected at runtime but whose static HTML still trips the "static" auto-detection (#12). Always renders via the configured browser, skipping JS-shell detection entirely; errors withExitPreconditionwhen no browser is configured rather than silently falling back to HTTP. Composes with--raw(dump the rendered HTML) and--select(run the CSS selector against the rendered DOM), and skips the/llms.txtprobe. A cache hit is honored only for a prior browser render (source == browser); HTTP/shell/markdown-only entries never satisfy a forced request. JS-shell auto-detection tuning is tracked separately. Documents the previously-undocumented--rawflag alongside it (#11).
-
v0.9.4-0.20260529113544-bed6374b52dc29 May 2026 pre-releaseNothing published for this version
-
v0.9.329 May 2026Release notes
Open source →Added
grepappcode search backend (Grep MCP,mcp.grep.app) — keyless, JSON-RPC over SSE, literal/regex search across 1M+ public GitHub repos. It is now the default forketch code(wassourcegraph).ketch code --regexinterprets the query as a regular expression. Supported ongrepapp(setsuseRegexpon the MCPsearchGitHubtool) andsourcegraph(appendspatterntype:regexp);githubrejects it with a cleanExitPreconditionerror 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 (grepapp, notsourcegraph), scoped-b/--backendtosearch/code/docs(it is not a global flag), documented the previously-missing flags (--minimal,--trim,--max-chars,--select,--no-llms-txt, scrape--concurrency,--regex,--searxng-url) and theversioncommand, added code/docs backend sections to the site reference, synced theketch configdiscovery JSON example with real output, and dropped completed "What's Next" items (--rawis implemented; unit tests exist).
-
v0.9.224 May 2026Release notes
Open source →Added
- Differentiated exit codes. Scripts and agents can now distinguish failure classes instead of treating every non-zero return as the same:
2validation/bad input (missing arg, unknown backend, unknown config key, unparseable value),3not found (crawl status <missing-id>,crawl stop <missing-id>,--selectwith no matches),4upstream/network (scrape/search/code/docs/crawl fetch failures),5precondition (brave/context7 API key missing, github token missing,config initwhen file exists,crawl stopon a non-running crawl),6cancelled (SIGINT/SIGTERM during any operation, including crawls that previously swallowed cancellation as exit 0). Unwrapped errors continue to exit1. Implementation: smallcmd.ExitErrortype wrapped viacmd/exit.gohelpers (exitErrf,exitArgs);main.gomaps it toos.Exit.
Changed
ketch crawlno longer swallows Ctrl+C as exit 0. SIGINT during a foreground crawl now exits6while still printing the summary of what was collected before shutdown. Background crawls (crawl --background) are unaffected — they continue to record "stopped" status.-b/--backendis no longer a persistent root flag. It now lives onsearchonly (matching the existingcodeanddocslocal flags). User impact is negligible because cobra still resolves-bagainst the matching subcommand:ketch -b ddg search "q"andketch search -b ddg "q"both continue to work viasearch's local flag. The pre-cleanup behavior — where-bappeared (inert) in the--helpofscrape,crawl,cache,browser, andconfigand rendered a per-machine default reflecting the user's config rather than the source default — is gone. Now:ketch --helplists only--json; each search-style command (search,code,docs) advertises its own-b/--backendwith its own backend enum.
- Differentiated exit codes. Scripts and agents can now distinguish failure classes instead of treating every non-zero return as the same:
-
v0.9.123 May 2026Release notes
Open source →Changelog
- 82af3c9 Fix broken example URLs in README.md
- 0cd7da1 Merge pull request #8 from abhmul/main
- 93fa9a3 chore(release): v0.9.1
- b002496 docs(crawl): document idempotency requirement for URL rewrite rules
- 395224f feat(cmd/scrape): construct scraper with rewriter; key cache by rewritten URL
- 112ea63 feat(cmd/search): apply URL rewrites in search --scrape via shared Scraper
- 439eb74 feat(config): add URLRewrites field
- 67af60a feat(config): support url_rewrites in config set and discovery
- ac1e645 feat(crawl): receive scraper from caller; rewrite URLs in enqueue and cache key
- f922254 feat(output): emit fetched_url in scrape/search frontmatter when set
- c71a61b feat(scrape): add optional URL rewriter to Scraper
- 790e94c feat(scrape): apply URL rewrites in Scrape/ScrapeConditional/BrowserScrape
- 0b34aa3 feat(search): expose FetchedURL on Result and populate from scraped Page
- 3dc5396 feat(urlrewrite): add regex-based URL rewriter package
- 2361cfc refactor(cmd): extract newScraper helper for rewriter+browser construction
- 376093c refactor(crawl): drop redundant Rewrite in processItem cache key
Release notes
Open source →Added
url_rewritesconfig: an ordered list of{match, replace}regex rules applied transparently before any fetch inscrape,search --scrape, andcrawl. Lets users redirect URLs without touching the agent surface — e.g.www.reddit.com→old.reddit.com(the verification-wall workaround) ortheguardian.com/uk→/uk/rss(RSS-over-rendered-page). Original URL is preserved in output frontmatter asurl:; the actually-fetched URL appears asfetched_url:when different. JSON output exposes both viaurlandfetched_url. The page cache is keyed by the rewritten URL so original/rewritten aliases share one entry. Rules validated atketch config set url_rewrites '<json>'time (JSON parse + regex compile); first-match-wins; capture groups ($1,$2) supported inreplace. Closes #9.
Changed
crawl.Crawl()signature now takes*scrape.Scraperfrom the caller (was constructed internally fromOptions.BrowserBin);Options.BrowserBinremoved. Only affects direct importers of thecrawlpackage — theketch crawlCLI is unchanged. Lets the cmd-layernewScraper()helper own scraper construction uniformly acrossscrape,search, andcrawl.
Fixed
- Broken example URLs in README (#8, thanks @abhmul).
-
v0.9.012 May 2026Release notes
Open source →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>forcache,code,config,crawl,docs,extract,httpx,scrape,search, andupdatecheck. Thepkg/prefix is a community convention (golang-standards/project-layout) that the Go team has explicitly not endorsed; stdlib and most idiomatic libraries expose packages at the module root. - VitePress documentation site moved from
docs/tosite/to free thedocs/path for the docs-search Go package (context7 / FTS5 backends). The Deploy Docs workflow and.gitignoreare updated. Site URL is unaffected (gh-pages serves from a separate branch).
- BREAKING. Reusable packages moved from
-
v0.8.112 May 2026Release notes
Open source →Fixed
- Page cache no longer returns unrendered JS-shell garbage after the user configures a browser. Entries now record the fetch source (
http/http_shell/browser); a cache hit is bypassed when the entry was an unrendered JS-shell extraction and a browser is now available. Plain HTTP entries are not churned by browser config changes. Pre-existing entries (no source recorded) are invalidated once when a browser is configured, migrating them in place. Fixes #7.
- Page cache no longer returns unrendered JS-shell garbage after the user configures a browser. Entries now record the fetch source (
-
v0.8.002 May 2026Release notes
Open source →Changed
- Reusable packages moved from
internal/topkg/. Affected:cache,code,config,crawl,docs,extract,httpx,scrape,search,updatecheck. Pure rename, no behavior changes — exposes these packages for import by external Go programs. Theinternal/directory is removed; any out-of-tree code that importedgithub.com/1broseidon/ketch/internal/<pkg>(Go's visibility rules already prohibited this from outside the module) must switch togithub.com/1broseidon/ketch/pkg/<pkg>.
- Reusable packages moved from
-
v0.7.121 Apr 2026Release notes
Open source →Fixed
ketch docs --resolve <name>was returning HTTP 400 "Query is required" after an upstream context7 API change. The query parameter was renamed (?q=→?query=), results moved into a{"results": [...]}envelope, and field names changed (name→title,codeSnippets→totalSnippets,truststring →trustScorefloat).LibraryMatchand the CLI print now track the current schema.ketch docs <query>and--librarywere unaffected.
-
v0.7.021 Apr 2026Release notes
Open source →Added
ketch versioncommand and--versionflag. Reports build version, commit, and date injected by goreleaser; falls back todebug.ReadBuildInfo()forgo installbuilds.- Passive update reminder: when a newer release exists, a two-line hint is printed to stderr after command output. Cached for 24h; throttled so the same version is only announced once per 24h. Honors
KETCH_NO_UPDATE_NOTIFIER=1,CI,--json, and non-TTY stderr. Install-type detection selects the right upgrade command (homebrew /go install/ release URL). - Ctrl+C (SIGINT) and SIGTERM now cancel the root context, so foreground
ketch crawldrains gracefully: workers stop, in-flight HTTP aborts, summary prints, exit 0. Previously the default signal handler hard-killed the process.
Changed
- HTTP stack tuned for crawling: shared
*http.Transportwith a 30s request Timeout,MaxIdleConnsPerHost=16, HTTP/2, and a keep-alive dialer (newinternal/httpx). Every backend (brave, ddg, searxng, context7, sourcegraph, github, scraper) reuses it. Measured: 50 requests to one host in ~385ms; 20 mixed-host URLs at c=10 in ~300ms (down from ~9.7s at c=1). context.Contextis now plumbed throughScraper.Scrape/Fetch/ScrapeConditional/BrowserScrape/MaybeBrowserFetch,BrowserConn.Fetch(via rodPage.Context(ctx)),crawl.Crawl,fetchSitemap, andfetchLLMSTxt. Cancellation reaches all the way into rod andhttp.Client.Do.crawl.Options.StopChremoved — cancellation is via the ctx passed tocrawl.Crawl.ketch crawl stop <id>sends SIGTERM, which cancels the worker ctx and aborts in-flight requests mid-fetch.DetectJSShellrewritten: single DOM traversal for the static-page fast path, lazy corroborator phase.DetectJSShellFromDocaccepts a pre-parsed document so callers don't pay twice.ScrapeConditionalparses HTML once and exposes the*goquery.DocumentviaFetchResult.Doc; the crawler reuses it for link extraction instead of re-parsing.- Crawl scheduler replaced:
sync.Cond+ growing slice →chan queueItem+sync.WaitGroupwith goroutine-per-enqueue. The old pop pattern (queue = queue[1:]) never reclaimed the backing array. - All HTTP response bodies are capped at 20 MiB via
io.LimitReaderso a misbehaving server cannot OOM the process.
-
v0.6.011 Apr 2026Release notes
Open source →Added
ketch scrapesmart input detection: multiple positional args, JSON array ('["url1","url2"]'), file (one URL per line), or stdin pipe — input mode is auto-detected, no extra flags needed.--concurrency Nflag onketch scrape(default 5) — replaces unbounded goroutine-per-URL with a semaphore-based worker pool.--selectand--no-llms-txtflags now propagate to multi-URL scraping (previously only worked for single URL).- Pipe chain support:
ketch search "query" --json | jq -r '.[].url' | ketch scrape --trim --max-chars 2000.
Fixed
resolveURLsnow checks explicit args before stdin —ketch scrape url < fileuses the URL, not the pipe.scrapeWithSelectordeduped: delegates toscrapeURLWithSelectorinstead of duplicating the fetch/browser-fallback/selector logic.search_feature_test.goupdated forsearch.Searcher.Search(ctx, ...)interface change.
Changed
search.Searcher.Searchanddocs.Searcher.Searchnow takecontext.Contextas first param, consistent withcode.Searcher. All HTTP backends usehttp.NewRequestWithContextfor proper cancellation propagation.
-
v0.5.1-0.20260411181520-cb0dfbe122f311 Apr 2026 pre-releaseNothing published for this version
-
v0.5.011 Apr 2026Release notes
Open source →Added
ketch scrape --select <css>— CSS selector extraction, bypasses readability and runs directly against fetched HTML (with browser fallback for JS-rendered pages).ketch scrape --max-chars N— truncate markdown output to N Unicode code points, appends[truncated]marker.ketch scrape --trim— strip markdown formatting syntax (bold, italic, links, headings, inline code) while preserving content text. Fenced code blocks are preserved. Typically 30-40% token reduction.ketch search/code/docs --minimal— one result per line, tab-separated (url\ttitle\tsnippet), no frontmatter. Pipe-friendly.- llms.txt auto-detection: bare domain URLs (e.g.
ketch scrape https://example.com) automatically check/llms.txtand return it directly if found (Content-Type: text/plain). Disable with--no-llms-txt. internal/extract.Title(html)exported for use across packages.- Running
ketchwith no args now shows a compact, generated summary derived from the live command tree andconfig.Available*Backends()— always current, never drifts.
Fixed
StripMarkdown: fenced code blocks (``` ```) now protected via sentinel tokens so inline backtick stripping can't corrupt their content.StripMarkdown: italic regex tightened to require non-space after opening*, preventing unordered-list markers (* item) from being misread as italic delimiters.truncateContent: slices by Unicode rune instead of byte, preventing split of multibyte UTF-8 characters at the truncation point.scrapeWithSelectornow callsMaybeBrowserFetchafter raw fetch so CSS selectors run against rendered content, not JS shell HTML.- Duplicate
extractTitleFromHTMLincmd/scraperemoved; both callers now useextract.Title.
Changed
Scraper.maybeBrowserFetchexported asMaybeBrowserFetchfor use by the command layer.
-
v0.4.011 Apr 2026Release notes
Open source →Added
ketch code -b github— GitHub Code Search backend. Token resolution chain: explicit config (ketch config set github_token) →$GITHUB_TOKEN→$GH_TOKEN→gh auth token(piggybacks on existing gh CLI login). Usestext-matchmedia type for accurate line-level snippets via match indices.- GitHub backend populates
stargazer_countvia a single batched GraphQLnodes(ids:)call (REST/search/codedoes not return stars). Non-fatal on failure. - Rate-limit-aware error messages using
X-RateLimit-Reset. github_token_sourcefield inketch configdiscovery payload (shows which resolution source is active; token itself is never printed).
Changed
code.Searcher.Searchnow takescontext.Contextas its first arg; both Sourcegraph and GitHub backends usehttp.NewRequestWithContextso cobra command cancellation propagates to in-flight requests.config.ResolveGithubTokenwraps thegh auth tokensubprocess inexec.CommandContextwith a 2s deadline so a hungghcan't block ketch startup.Searcher.Searchinterface now owns its own query dialect (per-backendbuildQuery); callers pass plain user input and language separately. Sourcegraph appliesarchived:no/fork:nodefaults; GitHub applieslanguage:(archived/fork qualifiers are not valid on the code search endpoint).Resultstruct gainsStarsfield, populated by both backends.- README documents both code backends, the GitHub auth chain, and dedicated sections for
ketch codeandketch docs. AGENTS.md listsinternal/code/github.go.
-
v0.3.011 Apr 2026Release notes
Open source →Added
ketch codecommand — code search via Sourcegraph streaming SSE API with--lang,--limit,--backend,--jsonflags. Zero config.ketch docscommand — library documentation search via Context7 with--library,--resolve,--tokens,--limit,--backend,--jsonflags. Requires API key.- Config keys:
code_backend,docs_backend,context7_api_key,sourcegraph_url.
Changed
- Documentation updates (README, AGENTS.md, CLAUDE.md) for browser rendering and the new code/docs backends.
-
v0.2.1-0.20260327013756-7f58809c5fcb27 Mar 2026 pre-releaseNothing published for this version
-
v0.2.026 Mar 2026Nothing published for this version
-
v0.1.025 Mar 2026Nothing published for this version