From 6ae497b98a6c76e2d2b84401b27d9145481717a4 Mon Sep 17 00:00:00 2001 From: Ilya Gusev Date: Wed, 19 Aug 2026 19:18:25 +0000 Subject: [PATCH] feat(search): add --max-results flag Forward max_results (1-50, server default 10) in the search body, matching the backend parameter added in keenable-backend-ts#370. Bump version to 0.2.2. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/commands/search.rs | 4 ++++ src/main.rs | 8 +++++++- tests/e2e/test_search.py | 16 ++++++++++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eabff83..c3ddf2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -647,7 +647,7 @@ dependencies = [ [[package]] name = "keenable-cli" -version = "0.2.1" +version = "0.2.2" dependencies = [ "clap", "colored", diff --git a/Cargo.toml b/Cargo.toml index e5f692c..6c66f93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keenable-cli" -version = "0.2.1" +version = "0.2.2" edition = "2024" description = "Keenable CLI — authenticate, manage API keys, configure MCP, and search the web" authors = ["grigorevp "] diff --git a/src/commands/search.rs b/src/commands/search.rs index 2f1710d..34a0d66 100644 --- a/src/commands/search.rs +++ b/src/commands/search.rs @@ -278,6 +278,7 @@ pub async fn search( mode: Option<&str>, filters: SearchFilters, snippet_max_length: Option, + max_results: Option, human: bool, api_key: Option<&str>, ) { @@ -311,6 +312,9 @@ pub async fn search( if let Some(n) = snippet_max_length { body["snippet_max_length"] = json!(n); } + if let Some(n) = max_results { + body["max_results"] = json!(n); + } // Merge filter fields into body if let Value::Object(filter_map) = filters.to_json() && let Value::Object(ref mut body_map) = body diff --git a/src/main.rs b/src/main.rs index 98543a0..c04d600 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,7 +137,7 @@ enum Commands { /// Search the web (outputs YAML by default, use -p for pretty output) #[command( - after_help = "Works without login (free tier). Log in for higher rate limits.\n\nExamples:\n keenable search \"rust async\" YAML output (for agents)\n keenable search \"rust async\" -p Pretty output (for humans)\n keenable search \"AI news\" --site techcrunch.com Restrict to site\n keenable search \"dodgers braves\" --published-after 2026-01-01 Date filter (YYYY-MM-DD)\n keenable search \"AI news\" --acquired-after 7d Relative date (min, h, d, mo, y)\n keenable search \"AI news\" --acquired-after 2026-01-15T10:30:00Z ISO 8601 datetime\n keenable search \"rust async\" --snippet-max-length 2000 Longer snippets (180-10000)\n keenable search \"AI news\" --query-time 2026-01-01T00:00:00Z Point-in-time search\n keenable search \"rust async\" --api-key keen_***_***** Use a specific API key" + after_help = "Works without login (free tier). Log in for higher rate limits.\n\nExamples:\n keenable search \"rust async\" YAML output (for agents)\n keenable search \"rust async\" -p Pretty output (for humans)\n keenable search \"AI news\" --site techcrunch.com Restrict to site\n keenable search \"dodgers braves\" --published-after 2026-01-01 Date filter (YYYY-MM-DD)\n keenable search \"AI news\" --acquired-after 7d Relative date (min, h, d, mo, y)\n keenable search \"AI news\" --acquired-after 2026-01-15T10:30:00Z ISO 8601 datetime\n keenable search \"rust async\" --snippet-max-length 2000 Longer snippets (180-10000)\n keenable search \"rust async\" --max-results 25 More results (1-50, default 10)\n keenable search \"AI news\" --query-time 2026-01-01T00:00:00Z Point-in-time search\n keenable search \"rust async\" --api-key keen_***_***** Use a specific API key" )] Search { /// Search query @@ -171,6 +171,10 @@ enum Commands { #[arg(long = "snippet-max-length")] snippet_max_length: Option, + /// Maximum number of results (API accepts 1-50, default 10) + #[arg(long = "max-results")] + max_results: Option, + /// Point-in-time search: only pages available on or before this timestamp (YYYY-MM-DD, ISO 8601, or relative e.g. 7d, 3mo, 1y) #[arg(long)] query_time: Option, @@ -304,6 +308,7 @@ async fn main() { published_after, published_before, snippet_max_length, + max_results, query_time, pretty, api_key, @@ -321,6 +326,7 @@ async fn main() { mode.as_deref(), filters, snippet_max_length, + max_results, pretty, api_key.as_deref(), ) diff --git a/tests/e2e/test_search.py b/tests/e2e/test_search.py index bc2303e..f8cfa82 100644 --- a/tests/e2e/test_search.py +++ b/tests/e2e/test_search.py @@ -69,6 +69,22 @@ def test_snippet_max_length_out_of_range(kn): assert "snippet_max_length" in data["message"] +def test_max_results(kn): + few = kn("search", SEARCH_QUERY, "--max-results", "3").yaml() + many = kn("search", SEARCH_QUERY, "--max-results", "30").yaml() + assert len(results_of(few)) <= 3 + # A dropped param would give both runs the default count (10). + assert len(results_of(many)) > 10 + + +def test_max_results_out_of_range(kn): + res = kn("search", SEARCH_QUERY, "--max-results", "51") + assert res.code == 1 + data = res.yaml() + assert data["error"] == "Invalid parameter" + assert "max_results" in data["message"] + + # --- 2.2 modes --- def test_mode_realtime(kn):