askaipods 0.2.2 → 0.2.4
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 +4 -4
- package/examples/claude-code-install.md +1 -1
- package/package.json +1 -1
- package/skill/askaipods/SKILL.md +11 -5
- package/src/cli.js +3 -3
- package/src/client.js +7 -6
- package/src/format.js +23 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ $ askaipods "what are people saying about test-time compute"
|
|
|
7
7
|
|
|
8
8
|
# askaipods · "what are people saying about test-time compute"
|
|
9
9
|
|
|
10
|
-
*Tier: anonymous · Results: 20 · Quota: 1/
|
|
10
|
+
*Tier: anonymous · Results: 20 · Quota: 1/20 daily*
|
|
11
11
|
|
|
12
12
|
## Results — newest first
|
|
13
13
|
|
|
@@ -83,7 +83,7 @@ askaipods "Anthropic safety research" --format json
|
|
|
83
83
|
# Restrict to recent episodes only (anonymous tier caps --days at 90; member tier accepts any value)
|
|
84
84
|
askaipods "GPU shortage" --days 90
|
|
85
85
|
|
|
86
|
-
# Use a member-tier API key for
|
|
86
|
+
# Use a member-tier API key for 100/day instead of 20/day
|
|
87
87
|
ASKAIPODS_API_KEY=pk_xxx askaipods "your query"
|
|
88
88
|
askaipods "your query" --api-key pk_xxx
|
|
89
89
|
```
|
|
@@ -100,7 +100,7 @@ Your agent will recognize the trigger phrase, invoke `askaipods`, and present th
|
|
|
100
100
|
|
|
101
101
|
| | Anonymous (default) | Member |
|
|
102
102
|
|---|---|---|
|
|
103
|
-
| **Daily quota** |
|
|
103
|
+
| **Daily quota** | 20 searches per IP | 100 searches per user |
|
|
104
104
|
| **Results returned** | 20 (deterministic top 20, sorted newest-first) | 20 (deterministic top 20, sorted by relevance) |
|
|
105
105
|
| **Text length** | Full text | Full text |
|
|
106
106
|
| **Date precision** | Month only (`2025-10`) | Full date (`2025-10-15`) |
|
|
@@ -108,7 +108,7 @@ Your agent will recognize the trigger phrase, invoke `askaipods`, and present th
|
|
|
108
108
|
| **Setup** | Nothing | `ASKAIPODS_API_KEY` env var |
|
|
109
109
|
| **Sign up** | n/a | https://podlens.net |
|
|
110
110
|
|
|
111
|
-
The anonymous tier exists so you can try the skill end-to-end with zero setup. Sign up for member access only when you outgrow the
|
|
111
|
+
The anonymous tier exists so you can try the skill end-to-end with zero setup. Sign up for member access only when you outgrow the 20/day quota or need full dates and unlimited lookback.
|
|
112
112
|
|
|
113
113
|
## Honest limitations
|
|
114
114
|
|
|
@@ -45,7 +45,7 @@ Claude Code should recognize the trigger phrase, run `npx askaipods search "..."
|
|
|
45
45
|
|
|
46
46
|
- **Skill not appearing**: Make sure the parent directory name matches the `name` field in `SKILL.md` (both must be `askaipods`).
|
|
47
47
|
- **`npx askaipods` fails**: Check that Node.js 18.3.0+ is installed: `node --version`. The CLI uses zero dependencies so there are no other prereqs.
|
|
48
|
-
- **Anonymous quota exhausted**: Sign up at https://podlens.net for
|
|
48
|
+
- **Anonymous quota exhausted**: Sign up at https://podlens.net for 100/day, then `export ASKAIPODS_API_KEY=pk_xxx`.
|
|
49
49
|
- **Skill triggers too rarely**: Front-load your prompt with the trigger phrases in `SKILL.md` description, or invoke directly with `/askaipods <query>`.
|
|
50
50
|
|
|
51
51
|
## Reference
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "askaipods",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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": {
|
package/skill/askaipods/SKILL.md
CHANGED
|
@@ -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 real episodes of Lex Fridman, Dwarkesh Patel, No Priors, Latent Space, and dozens more, sorted newest-first via the podlens.net semantic search API. Trigger eagerly on AI-research, ML-engineering, AI-investing, or AI-policy questions where real-human commentary beats a web search summary. Do not use for general web search, full transcript reading, or non-AI topics.
|
|
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
|
|
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; without it 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
|
|
@@ -96,9 +96,10 @@ Do NOT silently default every query to `--days 90` — omitting `--days` on broa
|
|
|
96
96
|
],
|
|
97
97
|
"meta": {
|
|
98
98
|
"total_returned": 20,
|
|
99
|
-
"quota": { "used": 3, "limit":
|
|
99
|
+
"quota": { "used": 3, "limit": 100, "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
|
|
@@ -172,7 +174,7 @@ If the same result appears in both Latest and Top Relevant sections, that's fine
|
|
|
172
174
|
|
|
173
175
|
---
|
|
174
176
|
|
|
175
|
-
*Anonymous tier: 20 results sorted newest-first, dates fuzzed to month, `--days` capped at 90 when specified. Set `ASKAIPODS_API_KEY` for
|
|
177
|
+
*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 — sign up at https://podlens.net.*
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
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.
|
|
@@ -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),
|
|
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.
|
|
17
|
+
const VERSION = "0.2.4";
|
|
18
18
|
|
|
19
19
|
const HELP_TEXT = `askaipods ${VERSION} — search AI podcast quotes by topic
|
|
20
20
|
|
|
@@ -30,8 +30,8 @@ OPTIONS:
|
|
|
30
30
|
-v, --version Show version
|
|
31
31
|
|
|
32
32
|
ENVIRONMENT:
|
|
33
|
-
ASKAIPODS_API_KEY PodLens API key. Without it:
|
|
34
|
-
With it:
|
|
33
|
+
ASKAIPODS_API_KEY PodLens API key. Without it: 20 searches/day per IP (anonymous).
|
|
34
|
+
With it: 100 searches/day per user (member).
|
|
35
35
|
Sign up at https://podlens.net to get one.
|
|
36
36
|
|
|
37
37
|
EXIT CODES:
|
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.
|
|
140
|
+
"User-Agent": "askaipods/0.2.4 (+https://github.com/Delibread0601/askaipods)",
|
|
140
141
|
};
|
|
141
142
|
if (apiKey) {
|
|
142
143
|
headers["X-PodLens-API-Key"] = apiKey;
|
|
@@ -189,15 +190,15 @@ export async function search({ query, days, apiKey, endpoint = PODLENS_ENDPOINT
|
|
|
189
190
|
// Distinguish 429 cases by inspecting the message: the server uses
|
|
190
191
|
// distinct strings for "burst limit hit" vs "daily quota exhausted",
|
|
191
192
|
// and only the latter warrants the "daily quota" exit code. The
|
|
192
|
-
// quota message is tier-aware: a member hitting the
|
|
193
|
+
// quota message is tier-aware: a member hitting the 100/day cap must
|
|
193
194
|
// not be told to "set ASKAIPODS_API_KEY" — they already have one.
|
|
194
195
|
if (response.status === 429) {
|
|
195
196
|
const msg = String(data?.error ?? "").toLowerCase();
|
|
196
197
|
if (msg.includes("quota")) {
|
|
197
198
|
const quotaMsg = apiKey
|
|
198
|
-
? "daily search quota exhausted (member tier:
|
|
199
|
-
: "daily search quota exhausted (anonymous tier:
|
|
200
|
-
"For
|
|
199
|
+
? "daily search quota exhausted (member tier: 100/day). Quota resets at 00:00 UTC."
|
|
200
|
+
: "daily search quota exhausted (anonymous tier: 20/day). Quota resets at 00:00 UTC. " +
|
|
201
|
+
"For 100 searches/day, set ASKAIPODS_API_KEY (sign up at https://podlens.net).";
|
|
201
202
|
throw exitErr(2, quotaMsg);
|
|
202
203
|
}
|
|
203
204
|
throw exitErr(3, "rate limited by podlens.net (too many requests in a short window). Retry in a minute.");
|
package/src/format.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const ANONYMOUS_NOTE =
|
|
15
15
|
"Anonymous tier: 20 results sorted newest-first, dates fuzzed to month, " +
|
|
16
|
-
"--days capped at 90 when specified. Set ASKAIPODS_API_KEY for
|
|
16
|
+
"--days capped at 90 when specified. Set ASKAIPODS_API_KEY for 100 searches/day with full dates and unlimited lookback.";
|
|
17
17
|
|
|
18
18
|
// Sort results newest-first by parsing each `published_at` to a UTC
|
|
19
19
|
// millisecond timestamp and comparing numerically. Pure lexical compare
|
|
@@ -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
|
-
|
|
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
|
|