CLI for Keenable — authenticate, manage API keys, configure MCP, and search the web.
Homebrew (macOS + Linux):
brew install keenableai/tap/keenable-cliShell (macOS + Linux):
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/keenableai/keenable-cli/releases/latest/download/keenable-cli-installer.sh | shPowerShell (Windows):
irm https://github.com/keenableai/keenable-cli/releases/latest/download/keenable-cli-installer.ps1 | iexFrom source:
cargo install --git https://github.com/keenableai/keenable-cli# Search the web (works without login)
keenable search "rust async patterns" -p
# Login for higher rate limits
keenable login
# Configure MCP for your AI clients (Claude Code, Cursor, etc.)
keenable configure-mcp --allkeenable search "query" # YAML output (for agents)
keenable search "query" -p # Pretty output (for humans)
keenable search "AI news" --site techcrunch.com # Restrict to site
keenable search "query" --exclude-domain spam.com # Never these domains
keenable search "query" --published-after 2026-01-01 # Date filter
keenable search "query" --acquired-before 2026-05-01 # Date filter
keenable search "query" --snippet-max-length 2000 # Longer snippets (180-10000)
keenable search "query" --query-time 2026-01-01T00:00:00Z # Point-in-time search
keenable search "query" --session-id agent-run-42 # Group related calls in a session
keenable search "query" --api-key KEY # Use a specific API keyWorks without login (free tier). Log in for higher rate limits.
--exclude-domain drops results from a block-list of hosts. It is repeatable
and also takes a comma list, so these are equivalent:
keenable search "security council vote" --exclude-domain blogs.un.org,reddit.com
keenable search "security council vote" --exclude-domain blogs.un.org --exclude-domain reddit.com- Each entry is a host and covers that host and every subdomain
(
yahoo.comalso excludesfinance.yahoo.com). At most 1,000 distinct hosts per request, 2,000 entries as sent, and 2,048 bytes per entry. - The server cleans up harmless noise: scheme, a leading
www., case, port, a trailing/or dot, a query string or#fragmentand surrounding whitespace (https://WWW.UN.org/becomesun.org); international names become punycode. - Anything else is rejected with an
invalid_filtererror naming the field, the entry and the reason, and the command exits 8: paths (un.org/press), suffixes (.com), wildcards (*.gov), IP addresses,user@hostand invalid hosts. Private-suffix hosts such asgithub.ioorblogspot.comare accepted. - Spaces around comma-separated entries are trimmed. A blank entry, such as
--exclude-domain ""or the trailing piece ofa.com,, is sent as""and the server rejects it withinvalid_filter. When a script builds the flag from a variable that may be empty, omit the flag instead. - If every result is excluded, the response can be empty. Response warnings
are printed in YAML and shown above the results with
-p.
keenable fetch https://example.com # Fetch page content
keenable fetch https://example.com -p # Pretty output
keenable fetch https://example.com --live # Fetch the live page (skip cache)
keenable fetch https://example.com --prompt "List all pricing tiers" # LLM extraction instead of the full page
keenable fetch https://example.com --max-chars 200000 # Raise the 50000-char content cap
keenable fetch https://example.com --session-id agent-run-42 # Group with related calls in a sessionWhen content is cut at the character cap, it ends with the API's truncation note and the YAML output carries truncated: true.
On a failed search or fetch, YAML mode prints the structured error on
stdout (error, type, code, message, fix, retry_after, hint, ...)
and a one-line summary on stderr. -p prints only to stderr. The exit code
says what failed, so a script does not need to parse the output:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Any other error: a local setup problem such as a bad SSL_CERT_FILE, an invalid KEENABLE_API_URL or an invalid --mode |
| 2 | Invalid arguments (the CLI's own usage error) |
| 3 | Authentication or permission: the key is invalid, revoked or not allowed this |
| 4 | Rate limited: wait retry_after seconds (or until rate_limit_reset), then retry |
| 5 | Out of credits: add credits at https://keenable.ai/console |
| 6 | The page or site failed (fetch): not found, blocked, timed out, unreachable |
| 7 | Network: no response from the API (proxy, DNS, TLS) |
| 8 | Invalid request: fix the arguments; retrying unchanged fails the same way |
| 9 | Server error on Keenable's side: retry later |
keenable search "query" > out.yaml
case $? in
0) ;; # results in out.yaml
4) sleep 2 ;; # rate limited: retry later
5) echo "out of credits" ;; # stop the run
9) sleep 30 ;; # Keenable server error: retry later
esacWith an API key, each response's usage.credits_remaining is the
organization's credits left after the request (an estimate that can lag a few
seconds); -p shows it as Credits left: N.
Set KEENABLE_API_URL to send API calls somewhere other than https://api.keenable.ai, e.g. a local gateway that adds credentials. It must be https://, except http:// to localhost, 127.0.0.1 or ::1, and must not contain a user or password. configure-mcp still writes the production MCP URL.
KEENABLE_API_URL=http://127.0.0.1:8080 keenable search "query"A stored API key is only sent to the API it was saved for. keenable login saves the key for the current KEENABLE_API_URL (production when unset), next to any key you saved for another API, so logging in under an override keeps your production key. A command run against an API with no key of its own stops with exit code 1 instead of sending the production key there. Under an override, pass a key with KEENABLE_API_KEY or --api-key, or run keenable login with the override set. This keeps a KEENABLE_API_URL set by a project's environment from redirecting your key to another host.
Redirects are followed only within the same scheme, host and port, so the API key never follows a redirect to another host or to plain http.
keenable login # Device-code login (opens browser)
printenv KEENABLE_API_KEY | keenable login --api-key -
# Save a key from stdin (CI, servers)
keenable login --api-key <KEY> # Save API key directly
keenable logout # Remove the key for the current API addressPrefer --api-key -: a key passed as an argument is visible to other processes on the machine and lands in shell history. The key is stored in ~/.keenable/config.json (mode 0600, in a 0700 directory). configure-mcp also writes it into each configured client's MCP config; logout lists those clients, and keenable reset --all removes it from them.
keenable configure-mcp # Show client status
keenable configure-mcp --all # Configure all detected clients
keenable configure-mcp --cursor # Configure a specific client
keenable reset --all # Remove Keenable from all clientsSupported clients: Claude Code, Cursor, Devin Desktop (--devin, writes ~/.config/devin/mcp_config.json), Windsurf (--windsurf, writes the legacy ~/.codeium/windsurf/mcp_config.json), Codex, OpenCode.
The CLI honors HTTPS_PROXY / HTTP_PROXY / NO_PROXY. It trusts its bundled Mozilla roots plus the OS certificate store; when SSL_CERT_FILE or SSL_CERT_DIR is set, those replace the OS store. A TLS-intercepting proxy works once its CA is in that bundle:
export HTTPS_PROXY=http://proxy.internal:3128
export SSL_CERT_FILE=/etc/ssl/certs/ca-bundle-with-proxy-ca.pem
keenable search "query"The background daemon (macOS/Linux) exits after 5 minutes idle. It restarts on its own when the proxy or certificate variables change, so there is nothing to do after changing them. Commands run with --api-key, KEENABLE_API_KEY or -v skip the daemon.
The CLI checks for updates automatically (once per hour). To update manually:
keenable update # Installer-script installs
brew update && brew upgrade keenable-cli # Homebrewcargo build --releaseEnd-to-end suite (real binary, live API — see tests/e2e/):
export KEENABLE_API_KEY=keen_...
uv run --project tests/e2e pytest tests/e2e -vSee CLAUDE.md for project conventions and architecture.