diff --git a/Cargo.lock b/Cargo.lock index 813379e..fe4af6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -647,7 +647,7 @@ dependencies = [ [[package]] name = "keenable-cli" -version = "0.1.22" +version = "0.1.23" dependencies = [ "clap", "colored", diff --git a/Cargo.toml b/Cargo.toml index c32eb39..01b68eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keenable-cli" -version = "0.1.22" +version = "0.1.23" edition = "2024" description = "Keenable CLI — authenticate, manage API keys, configure MCP, and search the web" authors = ["grigorevp "] diff --git a/README.md b/README.md index f51f2aa..db24569 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Works without login (free tier). Log in for higher rate limits. ```bash 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) ``` ### Authentication diff --git a/src/commands/search.rs b/src/commands/search.rs index 4ce68d2..2fbab3f 100644 --- a/src/commands/search.rs +++ b/src/commands/search.rs @@ -139,9 +139,13 @@ async fn execute(req: &DaemonRequest, api_key_override: Option<&str>) -> Result< } "fetch" => { let urls = req.urls.as_ref().ok_or_else(|| missing("urls"))?; + let mut query: Vec<(&str, &str)> = urls.iter().map(|u| ("url", u.as_str())).collect(); + if req.live { + query.push(("live", "true")); + } let resp = client .get(endpoint("/v1/fetch", authenticated)) - .query(&urls.iter().map(|u| ("url", u)).collect::>()) + .query(&query) .send() .await .map_err(send_err)?; @@ -326,6 +330,7 @@ pub async fn search( command: "search".to_string(), urls: None, body: Some(body), + live: false, }; let api_key = key_override(api_key); @@ -380,11 +385,12 @@ pub async fn search( } } -pub async fn fetch(url: &str, human: bool, api_key: Option<&str>) { +pub async fn fetch(url: &str, live: bool, human: bool, api_key: Option<&str>) { let req = DaemonRequest { command: "fetch".to_string(), urls: Some(vec![url.to_string()]), body: None, + live, }; let api_key = key_override(api_key); @@ -460,6 +466,7 @@ pub async fn feedback(query: &str, scores: &[String], human: bool, api_key: Opti command: "feedback".to_string(), urls: None, body: Some(body), + live: false, }; let api_key = key_override(api_key); diff --git a/src/daemon.rs b/src/daemon.rs index d8ac87b..b612c02 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -8,6 +8,9 @@ pub struct DaemonRequest { pub urls: Option>, #[serde(default)] pub body: Option, + /// Fetch only: request live content instead of the cached copy. + #[serde(default)] + pub live: bool, } impl DaemonRequest { @@ -277,10 +280,15 @@ mod platform { Some(u) => u, None => return err_response("Missing urls"), }; + let mut query: Vec<(&str, &str)> = + urls.iter().map(|u| ("url", u.as_str())).collect(); + if req.live { + query.push(("live", "true")); + } send_api( client .get(endpoint("/v1/fetch", authenticated)) - .query(&urls.iter().map(|u| ("url", u)).collect::>()), + .query(&query), ) .await } diff --git a/src/main.rs b/src/main.rs index c2e6a68..3fc9f6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -178,12 +178,16 @@ enum Commands { /// Fetch page content as markdown (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 fetch https://example.com YAML output\n keenable fetch https://example.com -p Pretty output\n keenable fetch https://example.com --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 fetch https://example.com YAML output\n keenable fetch https://example.com -p Pretty output\n keenable fetch https://example.com --live Fetch the live page (skip cache)\n keenable fetch https://example.com --api-key keen_***_***** Use a specific API key" )] Fetch { /// URL to fetch url: String, + /// Fetch the live page instead of the cached copy + #[arg(long)] + live: bool, + /// Pretty-print output for humans instead of YAML #[arg(short = 'p', long = "pretty")] pretty: bool, @@ -319,10 +323,11 @@ async fn main() { } Commands::Fetch { url, + live, pretty, api_key, } => { - commands::search::fetch(&url, pretty, api_key.as_deref()).await; + commands::search::fetch(&url, live, pretty, api_key.as_deref()).await; } Commands::Feedback { query, diff --git a/tests/e2e/test_fetch.py b/tests/e2e/test_fetch.py index 27cb689..e7e5927 100644 --- a/tests/e2e/test_fetch.py +++ b/tests/e2e/test_fetch.py @@ -11,6 +11,14 @@ def test_fetch_single_url(kn): assert "description" not in data +def test_fetch_live(kn): + res = kn("fetch", "https://example.com", "--live") + assert res.code == 0 + data = res.yaml() + assert data["title"] == "Example Domain" + assert "# Example Domain" in data["content"] + + def test_pretty_fetch(kn): res = kn("fetch", "https://example.com", "-p") assert res.code == 0