askaipods 0.2.2 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "askaipods",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
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": {
@@ -98,7 +98,8 @@ Do NOT silently default every query to `--days 90` — omitting `--days` on broa
98
98
  "total_returned": 20,
99
99
  "quota": { "used": 3, "limit": 50, "period": "daily" },
100
100
  "restrictions": null,
101
- "query_hash": "..."
101
+ "query_hash": "...",
102
+ "window": { "requested_days": 7, "served_days": 30, "expanded": true, "reason_code": "expanded_on_empty_window" }
102
103
  }
103
104
  }
104
105
  ```
@@ -113,6 +114,7 @@ Field notes that affect how you render:
113
114
  - **`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.
114
115
  - **`meta.quota`** — passed through from the podlens.net API. Sub-fields like `used`, `limit`, `period` are reliably present; other sub-fields (e.g., a reset timestamp) may or may not appear depending on the server version. Treat all sub-fields as optional and degrade gracefully.
115
116
  - **`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.
117
+ - **`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: `requested_days` (what was asked), `served_days` (what actually returned results), `expanded` (boolean — `true` when the window was widened), `reason_code` (`"expanded_on_empty_window"` when expanded), and optionally `truncated` (`true` when a fallback query errored mid-expansion). **When `expanded` is `true`**, 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 `false` and results are empty, the API tried all available windows and genuinely found nothing.
116
118
  - **No speaker name and no episode URL.** The corpus is indexed at the key-point level without per-speaker attribution (the upstream pipeline intentionally avoids attributing quotes to individuals because automatic speaker diarization is unreliable). Episode URLs are also not exposed by the public API. Render `Podcast — Episode` only; do not fabricate "Dario said" if the text doesn't already attribute itself.
117
119
 
118
120
  ## How to render the response
@@ -207,7 +209,11 @@ The CLI uses stable exit codes so you can branch on the failure mode:
207
209
  | `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. |
208
210
  | `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. |
209
211
 
210
- If the `results` array is empty (zero matches above the similarity threshold), say so explicitly: "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." Do not invent quotes to fill the gap.
212
+ If the `results` array is empty (zero matches above the similarity threshold), check `meta.window` first:
213
+ - If `meta.window.expanded` is `true`: the API already widened the search window (e.g., from 7 to 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."
214
+ - If `meta.window.truncated` is `true`: the expansion was interrupted by a transient error — tell the user to retry in a moment.
215
+ - Otherwise (no expansion, or `meta.window` is `null`): 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."
216
+ Do not invent quotes to fill the gap.
211
217
 
212
218
  Never silently swallow an error. Never fabricate quotes when the API returns nothing.
213
219
 
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.2";
17
+ const VERSION = "0.2.3";
18
18
 
19
19
  const HELP_TEXT = `askaipods ${VERSION} — search AI podcast quotes by topic
20
20
 
package/src/client.js CHANGED
@@ -99,7 +99,8 @@ function isValidPublishedAt(v) {
99
99
  //
100
100
  // Optional (kept loose on purpose):
101
101
  // data.meta.quota.period, data.meta.quota.next_reset,
102
- // data.meta.query_hash, data.meta.restrictions, data.meta.cta
102
+ // data.meta.query_hash, data.meta.restrictions, data.meta.cta,
103
+ // data.meta.window
103
104
  function isValidSuccessEnvelope(data) {
104
105
  if (!isPlainObject(data)) return false;
105
106
  if (typeof data.total !== "number" || !Number.isFinite(data.total)) return false;
@@ -136,7 +137,7 @@ export async function search({ query, days, apiKey, endpoint = PODLENS_ENDPOINT
136
137
 
137
138
  const headers = {
138
139
  "Content-Type": "application/json",
139
- "User-Agent": "askaipods/0.2.2 (+https://github.com/Delibread0601/askaipods)",
140
+ "User-Agent": "askaipods/0.2.3 (+https://github.com/Delibread0601/askaipods)",
140
141
  };
141
142
  if (apiKey) {
142
143
  headers["X-PodLens-API-Key"] = apiKey;
package/src/format.js CHANGED
@@ -91,6 +91,7 @@ export function toStructured(query, response) {
91
91
  quota: response.meta.quota,
92
92
  restrictions: response.meta.restrictions ?? null,
93
93
  query_hash: response.meta.query_hash ?? null,
94
+ window: response.meta.window ?? null,
94
95
  },
95
96
  };
96
97
  }
@@ -115,7 +116,18 @@ export function renderMarkdown(query, response) {
115
116
  lines.push("");
116
117
 
117
118
  if (data.results.length === 0) {
118
- lines.push("No results found. Try a different phrasing or broader topic.");
119
+ const win = data.meta.window;
120
+ if (win && win.expanded) {
121
+ lines.push(
122
+ `No results found. The API expanded the search window from ${win.requested_days} to ${win.served_days} days but still found no matches. Try a different phrasing or broader topic.`,
123
+ );
124
+ } else if (win && win.truncated) {
125
+ lines.push(
126
+ "No results found (search window expansion was interrupted by a transient error). Try again in a moment, or try a different phrasing.",
127
+ );
128
+ } else {
129
+ lines.push("No results found. Try a different phrasing or broader topic.");
130
+ }
119
131
  if (data.tier === "anonymous") {
120
132
  lines.push("");
121
133
  lines.push(`> ${ANONYMOUS_NOTE}`);
@@ -123,6 +135,15 @@ export function renderMarkdown(query, response) {
123
135
  return lines.join("\n");
124
136
  }
125
137
 
138
+ // Surface window expansion so the user knows the actual time range
139
+ const win = data.meta.window;
140
+ if (win && win.expanded) {
141
+ lines.push(
142
+ `*Note: No results in the requested ${win.requested_days}-day window; showing results from the last ${win.served_days} days.*`,
143
+ );
144
+ lines.push("");
145
+ }
146
+
126
147
  lines.push("## Results — newest first");
127
148
  lines.push("");
128
149