askaipods 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,7 +80,7 @@ askaipods "what are VCs saying about reasoning models"
80
80
  # JSON output (for scripts and agents)
81
81
  askaipods "Anthropic safety research" --format json
82
82
 
83
- # Restrict to recent episodes only (anonymous tier caps --days at 90; member tier accepts any value)
83
+ # Restrict to recent episodes only (anonymous tier caps --days at 90; member tier caps at 365)
84
84
  askaipods "GPU shortage" --days 90
85
85
 
86
86
  # Use a member-tier API key for 100/day instead of 20/day
@@ -104,11 +104,11 @@ Your agent will recognize the trigger phrase, invoke `askaipods`, and present th
104
104
  | **Results returned** | Top 20 newest (API returns newest-first; `api_rank` = temporal order) | Top 20 by semantic relevance (structured output is emitted newest-first; semantic rank preserved in `api_rank`) |
105
105
  | **Text length** | Full text | Full text |
106
106
  | **Date precision** | Month only (`2025-10`) | Full date (`2025-10-15`) |
107
- | **`--days` cap (when specified)** | 90 days | Unlimited |
107
+ | **`--days` cap (when specified)** | 90 days | 365 days |
108
108
  | **Setup** | Nothing | `ASKAIPODS_API_KEY` env var |
109
109
  | **Access** | n/a | invite-only · request at https://podlens.net |
110
110
 
111
- The anonymous tier exists so you can try the skill end-to-end with zero setup. Member access is currently invite-only — request access at https://podlens.net (you'll be added to the waitlist for review) only if you outgrow the 20/day quota or need full dates and unlimited lookback.
111
+ The anonymous tier exists so you can try the skill end-to-end with zero setup. Member access is currently invite-only — request access at https://podlens.net (you'll be added to the waitlist for review) only if you outgrow the 20/day quota or need full dates and the longer 365-day lookback window.
112
112
 
113
113
  ## Honest limitations
114
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "askaipods",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Search AI podcast quotes by topic — recent episode excerpts from Lex Fridman, Dwarkesh Patel, No Priors, Latent Space, and dozens more. Universal agentskills.io skill compatible with Claude Code, OpenAI Codex, Hermes Agent, OpenClaw, and any other agent that supports the open skill standard. Powered by podlens.net.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
  name: askaipods
3
3
  description: Search AI podcast quotes about a topic. Use whenever the user asks "what are people saying about X", "latest takes on Y", "find AI podcast quotes about Z", "who is discussing <model/concept>", or wants to know how AI researchers, founders, or VCs are publicly discussing any AI topic — even when they don't say "podcast". Returns recent excerpts from Lex Fridman, Dwarkesh Patel, No Priors, Latent Space, and dozens more via podlens.net. Optional ASKAIPODS_API_KEY unlocks invite-only member tier; anonymous works out of the box.
4
4
  license: MIT
5
- requirements: Node.js 18.3.0+ on PATH (the CLI uses `node:util.parseArgs`, which was added in 18.3.0), internet access to podlens.net. Optional ASKAIPODS_API_KEY env var unlocks the 100/day member tier with full dates and unlimited lookback (member tier is invite-only — request access at https://podlens.net); without the key the skill works on the 20/day anonymous tier (per-IP, month-precision dates, `--days` capped at 90 when specified).
5
+ requirements: Node.js 18.3.0+ on PATH (the CLI uses `node:util.parseArgs`, which was added in 18.3.0), internet access to podlens.net. Optional ASKAIPODS_API_KEY env var unlocks the 100/day member tier with full dates and `--days` lookback up to 365 days (omit `--days` for all-time results); member tier is invite-only — request access at https://podlens.net. Without the key the skill works on the 20/day anonymous tier (per-IP, month-precision dates, `--days` capped at 90 when specified).
6
6
  ---
7
7
 
8
8
  # askaipods — AI podcast quote search
@@ -45,26 +45,29 @@ Argv-array form (preferred for agent use):
45
45
  ["npx", "-y", "askaipods", "search", "--format", "json", "--", "<USER QUERY>"]
46
46
  ```
47
47
 
48
- Shell-string form (only when argv is unavailable AND the query has been properly shell-quoteddo NOT use raw string interpolation):
48
+ Shell-string form (only when argv is unavailable; the host runtime must shell-quote the query first via `printf %q`, Node's `shell-quote`, or equivalent wrapping the raw query in double quotes is **not** sufficient, since `$VAR` and backticks still expand inside double quotes):
49
49
 
50
50
  ```bash
51
- npx -y askaipods search --format json -- "<USER QUERY>"
51
+ # <SHELL_QUOTED_QUERY> represents the user's query AFTER passing it through
52
+ # `printf %q` (or equivalent). For example, the query what's new? becomes
53
+ # the literal token what\'s\ new\? before reaching this command line.
54
+ npx -y askaipods search --format json -- <SHELL_QUOTED_QUERY>
52
55
  ```
53
56
 
54
57
  The `-y` flag bypasses npm's first-run confirmation prompt; without it, a non-interactive runtime may hang on first use waiting for a TTY response that never comes (audit R7-03). The package is published on npm as `askaipods`, so `npx -y` resolves it regardless of whether the user has it installed globally. If `npx` is unavailable in the host environment, the user can install once with `npm install -g askaipods` and run `askaipods` directly.
55
58
 
56
- The `search` subcommand is optional — `npx -y askaipods --format json -- "<USER QUERY>"` works identically. Both forms are supported; use whichever reads better in context.
59
+ The `search` subcommand is optional — `npx -y askaipods --format json -- <SHELL_QUOTED_QUERY>` works identically. Both subcommand spellings are supported; this does NOT relax the argv-array requirement above when argv execution is available — the choice is just which form reads better in context.
57
60
 
58
- To restrict to recent episodes only, add `--days N`. When `--days` is passed, the server caps the value at 90 for anonymous tier (member tier accepts any value). When `--days` is omitted entirely, there is no time filter — the server returns all-time results.
61
+ To restrict to recent episodes only, add `--days N`. When `--days` is passed, the server caps the value at 90 for anonymous tier and at 365 for member tier (larger values are silently clamped — do NOT pass `--days 730` expecting a two-year window). When `--days` is omitted entirely, there is no time filter — the server returns all-time results.
59
62
 
60
63
  ```bash
61
- npx -y askaipods search --days 90 --format json -- "<USER QUERY>"
64
+ npx -y askaipods search --days 90 --format json -- <SHELL_QUOTED_QUERY>
62
65
  ```
63
66
 
64
67
  To authenticate with a PodLens API key (member tier), pass `--api-key <key>` or set the `ASKAIPODS_API_KEY` environment variable. The flag takes priority over the env var when both are present.
65
68
 
66
69
  ```bash
67
- npx -y askaipods search --api-key pk_abc123... --format json -- "<USER QUERY>"
70
+ npx -y askaipods search --api-key pk_abc123... --format json -- <SHELL_QUOTED_QUERY>
68
71
  ```
69
72
 
70
73
  The query must be 1–300 characters after trimming. Longer queries are rejected locally (exit code 1) before reaching the API.
@@ -87,14 +90,18 @@ When the user's query implies a time window, you MUST pass the appropriate `--da
87
90
 
88
91
  Do NOT silently default every query to `--days 90` — omitting `--days` on broad research queries preserves valuable historical context that the user did not ask to exclude.
89
92
 
93
+ When the converted day count exceeds the tier cap (90 anonymous / 365 member), pass the cap value rather than the larger number — the server silently clamps anything above. Disclose the clamp in your response (e.g., "searched within member's 365-day cap; the user's 'last 2 years' would have meant ~730 days").
94
+
90
95
  ## JSON shape returned by the CLI
91
96
 
97
+ The example below is a **member-tier** response. Anonymous-tier responses differ in: top-level `tier: "anonymous"`, `render_hint: "single_view"`, `results[].date` fuzzed to `"YYYY-MM"` precision, lower `meta.quota.limit` (20), and a fuller `meta.restrictions` object describing anonymous caps (see field notes below).
98
+
92
99
  ```json
93
100
  {
94
- "tier": "anonymous" | "member",
101
+ "tier": "member",
95
102
  "query": "the user's query string",
96
103
  "fetched_at": "<ISO-8601 timestamp set by the CLI at request time>",
97
- "render_hint": "single_view" | "dual_view",
104
+ "render_hint": "dual_view",
98
105
  "results": [
99
106
  {
100
107
  "podcast": "Dwarkesh Patel",
@@ -107,7 +114,7 @@ Do NOT silently default every query to `--days 90` — omitting `--days` on broa
107
114
  "meta": {
108
115
  "total_returned": 20,
109
116
  "quota": { "used": 3, "limit": 100, "period": "daily", "next_reset": "2026-04-21T00:00:00Z" },
110
- "restrictions": null,
117
+ "restrictions": { "max_days": 365 },
111
118
  "query_hash": "...",
112
119
  "window": {
113
120
  "requested_days": 7,
@@ -128,11 +135,11 @@ Field notes that affect how you render:
128
135
  - **`tier`** — `member` if the user has a valid API key, `anonymous` otherwise. Drives the rendering branch below. On exit `0`, `tier` is always one of these two values — there is no third "unknown" path to handle (the CLI validates the upstream response and exits `3` if the value is missing or unexpected).
129
136
  - **`fetched_at`** — ISO-8601 timestamp set by the CLI at request time (not by the server). Use it for staleness: if the user asks about the same topic again later in the session, compare `fetched_at` against the current time to decide whether to re-query or reuse the cached output. A reasonable freshness threshold is ~30 minutes for time-sensitive queries and ~2 hours for broad research.
130
137
  - **`render_hint`** — `dual_view` for member, `single_view` for anonymous. Honor this. The reason: anonymous results are sorted by `published_at` desc (newest-first) by the API, so `api_rank` reflects temporal order, not semantic relevance. Showing a "Top Most Relevant" section for anonymous tier would mislead the user. Member results arrive in similarity order, so `api_rank` is meaningful for relevance-based views.
131
- - **`results[]`** — already sorted **newest first** by the CLI. Each result carries `api_rank` (1 = most semantically relevant in API order) so you can derive a "Top Relevant" sub-view without re-querying.
138
+ - **`results[]`** — already sorted **newest first** by the CLI. Each result carries `api_rank`, its position in the API's original ordering. **For member tier**, `api_rank` 1 = most semantically relevant you can derive a "Top Relevant" sub-view without re-querying. **For anonymous tier**, the API sorts by date, so `api_rank` reflects temporal order (1 = newest), not relevance — see the `render_hint` note above for why a "Top Relevant" view should not be rendered for anonymous responses.
132
139
  - **`results[].podcast` / `episode` / `date`** — any of these may be `null` if the upstream record is incomplete. Render `Unknown podcast` / `Untitled episode` / `date unknown` rather than dropping the result. The CLI's own markdown renderer falls back the same way.
133
140
  - **`results[].date` format** — `YYYY-MM-DD` (or full ISO timestamp) for member tier; `YYYY-MM` only for anonymous tier (deliberately fuzzed by the API). Display whatever you got — don't guess a day.
134
141
  - **`meta.quota`** — passed through from the podlens.net API. `used` and `limit` are guaranteed present (the CLI validates them as part of the success envelope); `period` is typically `"daily"`, `next_reset` is an ISO-8601 timestamp, and **`refunded`** (optional boolean) — when present and `true`, the server refunded this request's quota slot under its P1-b narrow-refund rule (triggered when the corpus is stale for the requested window AND zero results were delivered). The field is often absent; check with `quota.refunded === true` rather than `typeof quota.refunded === "boolean"`. When set, mention in the response that the search didn't count against the user's quota — it's a transparency signal worth surfacing.
135
- - **`meta.restrictions`** — `null` for member tier; for anonymous tier, an object describing the cap (e.g., `{ max_results: 20, text_truncated: false, results_randomized: false, date_precision: "month", max_days: 90, order: "published_at_desc" }`). If non-null, the closing anonymous-tier note (templated below) is the right way to surface it; do not parse the object field-by-field.
142
+ - **`meta.restrictions`** — for member tier, an object describing the member cap (currently `{ max_days: 365 }`); for anonymous tier, a fuller object describing the anonymous restrictions (e.g., `{ max_results: 20, text_truncated: false, results_randomized: false, date_precision: "month", max_days: 90, order: "published_at_desc" }`). For anonymous tier, the closing anonymous-tier note (templated below) is the right way to surface the cap; for member tier, the field is informational only. Do not parse field-by-field, and do not branch tier on this field — use the top-level `tier` field.
136
143
  - **`meta.window`** — present when the API includes window expansion metadata (may be `null` for older server versions). When the user passes `--days` and the requested window has no results, the API automatically retries with wider windows (`[30, 60, 90]` days). The `window` object contains:
137
144
  - `requested_days` — what the client asked for.
138
145
  - `served_days` — the last window actually attempted. When `expanded` is `true`, results came from that wider window.
@@ -144,10 +151,10 @@ Field notes that affect how you render:
144
151
  - `"exhausted_windows_empty"` — wider windows tried AND delivered nothing.
145
152
  - `truncated` — `true` when a fallback query errored mid-expansion, aborting further windows. When present, `reason_code` is suppressed.
146
153
 
147
- **When `expanded` is `true` AND results were delivered**, tell the user: "No results in the requested N-day window; showing results from the last M days" (using `requested_days` and `served_days`). When `expanded` is `true` AND results are empty, the API tried all available windows and genuinely found nothing — prefer checking `meta.warning` first for a freshness-aware message before falling back to generic "no results" copy.
154
+ **When `expanded` is `true` AND results were delivered**, tell the user: "No results in the requested N-day window; showing results from the last M days" (using `requested_days` and `served_days`). When `expanded` is `true` AND results are empty AND `window.truncated` is not `true`, the API tried all available windows and genuinely found nothing — prefer checking `meta.warning` first for a freshness-aware message before falling back to generic "no results" copy. When `window.truncated === true`, expansion was aborted mid-way by a transient Vectorize error rather than running the full window plan; tell the user to retry rather than rephrase (see the §Error handling priority ladder below for the canonical ordering).
148
155
  - **`meta.corpus_freshness`** — `{ "newest_date": "YYYY-MM-DD" | null }`. The latest `published_at` indexed in the corpus. Use it as an honest "data as of X" signal, especially when results are empty and `meta.warning` indicates a stale corpus. `null` when the corpus-freshness probe failed server-side — render "date unknown" rather than omitting.
149
156
  - **`meta.warning`** — `null` in the common case. When present, an object `{ "code": "..." }` signalling an honest server-side explanation for an empty or partial response:
150
- - `"corpus_stale_for_requested_window"` — the user bounded the search with `--days` but the newest indexed episode predates that window's cutoff. Tell the user: "No episodes indexed in the requested window (newest indexed episode: `<corpus_freshness.newest_date>`). Try a longer `--days` value or omit it." **Do NOT** suggest rephrasing the query — the cause is freshness, not semantics.
157
+ - `"corpus_stale_for_requested_window"` — the user bounded the search with `--days` but the newest indexed episode predates that window's cutoff. Tell the user: "No episodes indexed in the requested window (newest indexed episode: `<corpus_freshness.newest_date>`). Try a longer `--days` value (up to your tier cap of 90 anonymous / 365 member) or omit `--days` for all-time results." **Do NOT** suggest rephrasing the query — the cause is freshness, not semantics.
151
158
  - `"index_metadata_stale"` — fresh episodes exist in the corpus but haven't propagated to the Vectorize index yet. Tell the user: "Recently indexed episodes are still propagating — retry in a few minutes."
152
159
 
153
160
  These warnings mean an empty or near-empty result is an infrastructure signal, not a semantic-relevance signal — they take precedence over `window.expanded`/`truncated` messaging when both apply.
@@ -158,13 +165,15 @@ Field notes that affect how you render:
158
165
 
159
166
  Output exactly this structure. It is required for consistency across runtimes — users of this skill across Claude Code, OpenAI Codex, Hermes Agent, OpenClaw, and any other agentskills.io-compatible agent should see the same shape regardless of which agent ran it.
160
167
 
168
+ Note: parenthetical notes and `<placeholder>` tokens inside the fenced template blocks below are author guidance for the agent. Agents MUST replace placeholders with actual values from the JSON response, and MUST NOT include the parentheticals in the final user-facing output.
169
+
161
170
  (Note: the CLI's own `--format markdown` output uses a different layout — `### N. Podcast — Episode` headings — because that mode targets humans running `askaipods` directly in a terminal. As an agent you should always pass `--format json` and reformat the parsed payload yourself per the templates below; do not copy the CLI's markdown.)
162
171
 
163
172
  **Freshness banner rule (applies to every render path below, regardless of result count)**: before the first section heading, if `meta.warning` is non-null, emit a one-line italicized note:
164
173
 
165
- - `meta.warning.code === "corpus_stale_for_requested_window"` AND results are present → `*Note: The indexed corpus has no episodes in the requested window (newest indexed episode: <meta.corpus_freshness.newest_date>) — results below may come from an expanded window. Consider omitting --days for broader coverage.*`
166
- - `meta.warning.code === "index_metadata_stale"` AND results are present → `*Note: Recently indexed episodes are still propagating to the search index — some relevant matches may be missing. Retry in a few minutes for complete coverage.*`
167
- - `meta.warning.code` is set to any other value AND results are present (forward-compat for future server codes) → `*Note: Server flagged a freshness concern with this search (code: <meta.warning.code>) — results may be incomplete.*`
174
+ - `meta.warning?.code === "corpus_stale_for_requested_window"` AND results are present → `*Note: The indexed corpus has no episodes in the requested window (newest indexed episode: <meta.corpus_freshness.newest_date>) — results below may come from an expanded window. Consider omitting --days for broader coverage.*`
175
+ - `meta.warning?.code === "index_metadata_stale"` AND results are present → `*Note: Recently indexed episodes are still propagating to the search index — some relevant matches may be missing. Retry in a few minutes for complete coverage.*`
176
+ - `meta.warning?.code` is set to any other value AND results are present (forward-compat for future server codes) → `*Note: Server flagged a freshness concern with this search (code: <meta.warning.code>) — results may be incomplete.*`
168
177
 
169
178
  The banner is mandatory whenever `meta.warning` is non-null and the response is rendered — an unannotated partial result misrepresents the corpus state as authoritative. For empty-result renders, the equivalent freshness copy replaces the "no results" message per the §Error handling priority ladder (do not stack both).
170
179
 
@@ -178,7 +187,7 @@ The banner is mandatory whenever `meta.warning` is non-null and the response is
178
187
 
179
188
  2. ...
180
189
 
181
- (continue through the 5 most recent of the returned results, which are the first 5 in the `results` array since the CLI already sorted by date desc)
190
+ (continue through up to 5 of the most recent returned results — when the response carries fewer than 5 results (partial fills are possible when `window.reason_code` is `"expanded_partial_fill"`, when a freshness warning fires, or simply when the corpus is sparse for the query), render whichever exist and drop the "5" from the heading; do NOT pad or fabricate. The first 5 in the `results` array are the right slice since the CLI already sorted by date desc.)
182
191
 
183
192
  ## 🎯 Top 5 Most Relevant
184
193
 
@@ -187,7 +196,7 @@ The banner is mandatory whenever `meta.warning` is non-null and the response is
187
196
 
188
197
  2. ...
189
198
 
190
- (these 5 are the results with `api_rank` 1 through 5, regardless of date. Pull them from the `results` array by filtering on `api_rank`.
199
+ (these are the results with `api_rank` 1 through 5, regardless of date — render whichever exist (when the server returns fewer than 5 results overall, this section will have fewer too; drop the "5" from the heading in that case). Pull them from the `results` array by filtering on `api_rank`.
191
200
 
192
201
  **Then sort ascending by `api_rank`** so rank 1 appears first — the `results` array is sorted newest-first, so a naive filter without re-sorting would leave these in date order instead of rank order.)
193
202
 
@@ -221,7 +230,7 @@ If the same result appears in both Latest and Top Relevant sections, that's fine
221
230
 
222
231
  ---
223
232
 
224
- *Anonymous tier: 20 results sorted newest-first, dates fuzzed to month, `--days` capped at 90 when specified. Set `ASKAIPODS_API_KEY` for 100 searches/day with full dates and unlimited lookback — member tier is invite-only, request access at https://podlens.net.*
233
+ *Anonymous tier: up to 20 results sorted newest-first, dates fuzzed to month, `--days` capped at 90 when specified. Set `ASKAIPODS_API_KEY` for 100 searches/day, full dates, and `--days` up to 365 (or omit for all-time) — member tier is invite-only, request access at https://podlens.net.*
225
234
  ```
226
235
 
227
236
  The closing note about the anonymous tier matters because it tells the user (a) why the dates are coarse, (b) what the lookback cap is, and (c) what the upgrade path is. Skipping it leaves the user wondering why dates lack day precision.
@@ -256,13 +265,13 @@ The CLI uses stable exit codes so you can branch on the failure mode:
256
265
  | `2` | Daily quota exhausted | Surface the CLI's stderr message verbatim — it is already tier-aware (distinct copy for member vs anonymous) and includes the correct reset time and upgrade path. |
257
266
  | `3` | Transient or unexpected failure (network error, rate-limit burst, service 503, protocol/shape error, or internal exception) | Retry once after a brief pause. If it fails again, surface the CLI's stderr message verbatim — it distinguishes "rate limited, retry in a minute" from "podlens.net temporarily unavailable" from "unexpected response shape" from internal exceptions, so the user sees the actionable detail. |
258
267
 
259
- If the `results` array is empty (zero matches above the similarity threshold), check the honesty signals in this priority order — freshness warnings dominate because they tell the user something stronger than "rephrase your query":
268
+ If the `results` array is empty (zero matches above the similarity threshold), check the honesty signals in this priority order — freshness warnings dominate because they tell the user something stronger than "rephrase your query". `meta.warning` and `meta.window` are both nullable (the server omits either when not applicable), so use optional chaining (`?.`) when implementing these checks — absent fields must fall through to step 6 rather than throwing:
260
269
 
261
- 1. **`meta.warning.code === "corpus_stale_for_requested_window"`** — corpus has no indexed episodes in the requested window. Tell the user: "No episodes indexed in the requested window (newest indexed episode: `<meta.corpus_freshness.newest_date>`). Try a longer `--days` value or omit it." Do NOT suggest rephrasing the query.
262
- 2. **`meta.warning.code === "index_metadata_stale"`** — fresh episodes exist but haven't propagated to the vector index. Tell the user: "Recently indexed episodes are still propagating to the search index — retry in a few minutes."
263
- 3. **`meta.warning.code` is set to any other value** (forward-compat for future server codes) — preserve the signal rather than falling through. Tell the user: "No results. The server flagged a freshness issue with this search (code: `<meta.warning.code>`) — results may be incomplete or the requested window may be stale. Try omitting `--days` or retry in a few minutes."
264
- 4. **`meta.window.truncated === true`** — the expansion was interrupted by a transient error. Tell the user to retry in a moment.
265
- 5. **`meta.window.expanded === true`** — the API widened the window (e.g., 7→30 days) and still found nothing. Tell the user: "No quotes found. The API expanded the search from N to M days but found no matches. Try rephrasing or broadening the query." If `meta.corpus_freshness.newest_date` is present, append "(corpus indexed through `<newest_date>`)" as an honest data-freshness signal.
270
+ 1. **`meta.warning?.code === "corpus_stale_for_requested_window"`** — corpus has no indexed episodes in the requested window. Tell the user: "No episodes indexed in the requested window (newest indexed episode: `<meta.corpus_freshness.newest_date>`). Try a longer `--days` value (up to your tier cap of 90 anonymous / 365 member) or omit `--days` for all-time results." Do NOT suggest rephrasing the query.
271
+ 2. **`meta.warning?.code === "index_metadata_stale"`** — fresh episodes exist but haven't propagated to the vector index. Tell the user: "Recently indexed episodes are still propagating to the search index — retry in a few minutes."
272
+ 3. **`meta.warning?.code` is set to any other value** (forward-compat for future server codes) — preserve the signal rather than falling through. Tell the user: "No results. The server flagged a freshness issue with this search (code: `<meta.warning.code>`) — results may be incomplete or the requested window may be stale. Try omitting `--days` or retry in a few minutes."
273
+ 4. **`meta.window?.truncated === true`** — the expansion was interrupted by a transient error. Tell the user to retry in a moment.
274
+ 5. **`meta.window?.expanded === true`** — the API widened the window (e.g., 7→30 days) and still found nothing. Tell the user: "No quotes found. The API expanded the search from N to M days but found no matches. Try rephrasing or broadening the query." If `meta.corpus_freshness?.newest_date` is present, append "(corpus indexed through `<newest_date>`)" as an honest data-freshness signal.
266
275
  6. **Otherwise** (no warning, no expansion): say "No quotes found for that topic. The corpus is AI-focused — for non-AI topics, try a web search instead. For AI topics, try rephrasing or broadening the query."
267
276
 
268
277
  If `meta.quota.refunded === true`, add a one-line note at the end: "_This search was refunded — it did not count against your daily quota._" (The server's P1-b narrow-refund rule fires when the corpus is stale for the requested window AND zero results are delivered.)
package/src/cli.js CHANGED
@@ -14,7 +14,7 @@ import { parseArgs } from "node:util";
14
14
  import { search, AskaipodsError } from "./client.js";
15
15
  import { renderJson, renderMarkdown } from "./format.js";
16
16
 
17
- const VERSION = "0.2.6";
17
+ const VERSION = "0.2.7";
18
18
 
19
19
  const HELP_TEXT = `askaipods ${VERSION} — search AI podcast quotes by topic
20
20
 
@@ -24,7 +24,7 @@ USAGE:
24
24
 
25
25
  OPTIONS:
26
26
  --format <json|markdown> Output format (default: markdown if TTY, json if piped)
27
- --days <N> Only return results from the last N days (anonymous tier caps at 90; member tier accepts any value)
27
+ --days <N> Only return results from the last N days (anonymous tier caps at 90; member tier caps at 365)
28
28
  --api-key <key> PodLens API key (overrides ASKAIPODS_API_KEY env var)
29
29
  -h, --help Show this message
30
30
  -v, --version Show version
package/src/client.js CHANGED
@@ -243,7 +243,7 @@ export async function search({ query, days, apiKey, endpoint = PODLENS_ENDPOINT
243
243
 
244
244
  const headers = {
245
245
  "Content-Type": "application/json",
246
- "User-Agent": "askaipods/0.2.6 (+https://github.com/Delibread0601/askaipods)",
246
+ "User-Agent": "askaipods/0.2.7 (+https://github.com/Delibread0601/askaipods)",
247
247
  };
248
248
  if (apiKey) {
249
249
  headers["X-PodLens-API-Key"] = apiKey;
package/src/format.js CHANGED
@@ -12,9 +12,9 @@
12
12
  // Relevant" sub-views — see SKILL.md.
13
13
 
14
14
  const ANONYMOUS_NOTE =
15
- "Anonymous tier: 20 results sorted newest-first, dates fuzzed to month, " +
16
- "--days capped at 90 when specified. Set ASKAIPODS_API_KEY for 100 searches/day with full dates and unlimited lookback " +
17
- "(member tier is invite-only — request access at https://podlens.net).";
15
+ "Anonymous tier: up to 20 results sorted newest-first, dates fuzzed to month, " +
16
+ "--days capped at 90 when specified. Set ASKAIPODS_API_KEY for 100 searches/day, full dates, " +
17
+ "and --days up to 365 (or omit for all-time). Member tier is invite-only — request access at https://podlens.net.";
18
18
 
19
19
  // Sort results newest-first by parsing each `published_at` to a UTC
20
20
  // millisecond timestamp and comparing numerically. Pure lexical compare