Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ keenable search "AI news" --site techcrunch.com # Restrict to site
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" --api-key KEY # Use a specific API key
```

Expand Down
7 changes: 7 additions & 0 deletions src/commands/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct SearchFilters {
pub acquired_before: Option<String>,
pub published_after: Option<String>,
pub published_before: Option<String>,
pub query_time: Option<String>,
}

impl SearchFilters {
Expand All @@ -33,6 +34,9 @@ impl SearchFilters {
if let Some(v) = &self.published_before {
map.insert("published_before".into(), json!(v));
}
if let Some(v) = &self.query_time {
map.insert("query_time".into(), json!(v));
}
Value::Object(map)
}
}
Expand Down Expand Up @@ -503,6 +507,7 @@ mod tests {
acquired_before: None,
published_after: None,
published_before: None,
query_time: None,
}
}

Expand All @@ -516,11 +521,13 @@ mod tests {
let f = SearchFilters {
site: Some("docs.rs".into()),
published_after: Some("2024-01-01".into()),
query_time: Some("2024-06-01T00:00:00Z".into()),
..filters()
};
let v = f.to_json();
assert_eq!(v["site"], json!("docs.rs"));
assert_eq!(v["published_after"], json!("2024-01-01"));
assert_eq!(v["query_time"], json!("2024-06-01T00:00:00Z"));
assert!(v.get("acquired_after").is_none());
assert!(v.get("published_before").is_none());
}
Expand Down
8 changes: 7 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 \"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 \"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
Expand Down Expand Up @@ -171,6 +171,10 @@ enum Commands {
#[arg(long = "snippet-max-length")]
snippet_max_length: Option<u64>,

/// 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<String>,

/// Pretty-print output for humans instead of YAML
#[arg(short = 'p', long = "pretty")]
pretty: bool,
Expand Down Expand Up @@ -322,6 +326,7 @@ async fn main() {
published_after,
published_before,
snippet_max_length,
query_time,
pretty,
api_key,
} => {
Expand All @@ -331,6 +336,7 @@ async fn main() {
acquired_before,
published_after,
published_before,
query_time,
};
commands::search::search(
&query,
Expand Down
5 changes: 5 additions & 0 deletions tests/e2e/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ def test_iso8601_datetime(kn):
assert parse_ts(r["acquired_at"]) >= instant, r["url"]


def test_query_time(kn):
for r in _non_empty_results(kn, "AI news", "--query-time", "2026-05-01T00:00:00Z"):
assert parse_ts(r["acquired_at"]) <= utc(2026, 5, 1), r["url"]


def test_combined_filters(kn):
for r in _non_empty_results(kn, "async", "--site", "docs.rs", "--acquired-after", "2025-01-01"):
assert host_under(host_of(r["url"]), "docs.rs"), r["url"]
Expand Down
Loading