技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
last30days-skill【数据与研究】Installs, configures and runs last30days, an open-source research engine that searches Reddit, X, YouTube, Hacker News, Polymarket, GitHub and the web inside a recent time window and ranks what it finds by audience reaction. Use when someone asks "what are people saying about X", "research the last 30 days", "what does the community think of A vs B",…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
last30days is an open-source research engine packaged as an agent skill (plugin name last30days, release 3.26.0 when this was written). A single Python program, scripts/last30days.py, sends one topic to a dozen or more community platforms at once, keeps what was published inside the chosen window, scores every hit on relevance, recency and audience reaction (upvotes, likes, views, money staked), folds repeated coverage of one story into a cluster, and prints an evidence pack. The agent that launched it writes the brief. It runs on Python 3.12 or newer and installs no pip packages.
Reach for it when the question concerns current opinion, not settled fact: how a release landed, which of two tools practitioners prefer this month, what a company or person did lately, what a community keeps complaining about. Training data is too old for these, and an ordinary web search returns articles instead of the conversation.
What a run can see depends on the machine:
| Tier | Sources | What it takes |
|---|---|---|
| Always on | Reddit threads plus their top comments, Hacker News, Polymarket, GitHub; StockTwits if the subject is a stock or coin symbol | nothing (gh on PATH or GITHUB_TOKEN lifts GitHub rate limits) |
| Free tools | YouTube transcripts and comments; arXiv, Techmeme, Digg | yt-dlp; arxiv-pp-cli, techmeme-pp-cli, digg-pp-cli on PATH |
| Free account | Bluesky | BSKY_HANDLE and BSKY_APP_PASSWORD (an app password) |
| X | posts, replies, a named account's timeline | XAI_API_KEY, XQUIK_API_KEY, or X_BEARER_TOKEN with LAST30DAYS_X_BACKEND=xapi; alternatively a logged-in browser session the user opts into |
| Metered | TikTok, Instagram, Threads, Pinterest, LinkedIn | SCRAPECREATORS_API_KEY and the source named in INCLUDE_SOURCES |
| Web pages | articles and blogs | the host agent's own web search, or BRAVE_API_KEY, EXA_API_KEY, SERPER_API_KEY, PARALLEL_API_KEY; failing those, a keyless fallback |
Settle four things before touching the tool, asking only for what the user's message leaves open.
--days 7 for a launch week, --as-of 2026-08-31 to end the window on a past date.default, exec, dev, creator, eli5) and how much detail survives.--discover "AI agents", with no topic).In Claude Code, as a plugin that updates itself:
/plugin marketplace add mvanhorn/last30days-skill
/plugin install last30days
On Codex, Cursor, Gemini CLI, Copilot and other Agent Skills hosts:
npx skills add mvanhorn/last30days-skill -g # every detected host, user-wide
npx skills add mvanhorn/last30days-skill -g -a codex # one named host
npx skills update last30days -g # later
Pick one method per machine: a marketplace plugin beside an npx skills copy registers /last30days twice. Then find the engine and confirm the interpreter:
ENGINE="$(find -L ~/.claude ~/.codex ~/.agents ~/.gemini -type f \
-path '*/last30days/scripts/last30days.py' 2>/dev/null | sort -V | tail -n 1)"
python3 -c 'import sys; sys.exit(sys.version_info < (3, 12))' || echo "Python 3.12+ required"
python3 "$ENGINE" --preflight
--preflight prints where configuration comes from, which sources are reachable, which optional commands are absent and which files a run would write. It opens no browser data and starts no research.
Settings are KEY=value lines in ~/.config/last30days/.env. Precedence, strongest first: process environment; a project file .claude/last30days.env, honoured only when LAST30DAYS_TRUST_PROJECT_CONFIG=1 is set outside that file; the global file; the macOS Keychain. Values are not shell-expanded, so write ~/... or an absolute path and never $HOME.
# ~/.config/last30days/.env (non-secret settings)
LAST30DAYS_MEMORY_DIR=~/Documents/Last30Days # where raw research is saved
INCLUDE_SOURCES=tiktok,instagram # switch on opt-in sources
EXCLUDE_SOURCES=polymarket # drop sources you never want
LAST30DAYS_DEFAULT_SEARCH=reddit,x,youtube,hn # fixed source set when --search is absent
LAST30DAYS_REGISTER=dev # default audience preset
LAST30DAYS_STORE=1 # also record findings in SQLite
Pass each secret from an environment variable through stdin, so it never sits in a command line or in the agent's transcript. The engine writes it to the global file with mode 600 and echoes only a mask:
printf '%s\n' "$SCRAPECREATORS_API_KEY" | python3 "$ENGINE" setup --store-key SCRAPECREATORS_API_KEY
python3 "$ENGINE" doctor
doctor sorts every source into WORKING, TURNED ON - UNVERIFIED, NOT WORKING or COULD BE ON, prints the fix beside each problem, and always exits 0. Variants: doctor --json, doctor --cached, doctor --probe (a bounded live test of the free sources only) and doctor --postmortem (what failed on the previous run, offline).
Using the person's browser session for X costs nothing, but it is their decision alone: describe the option and leave FROM_BROWSER unset unless they request it. On scheduled or shared machines add --no-browser-cookies.
Given a bare topic and no reasoning-provider key, the engine falls back to one keyword query and flags the run as degraded. Supply two things instead.
Targets, for named entities. Use web search to confirm the official X handle, the GitHub owner or owner/repo, the subject's own subreddit and two or three broader ones. Pass only what you confirmed; a guessed handle pulls in a stranger's posts.
A query plan, saved as a JSON file so the same research can be repeated:
{
"intent": "product",
"freshness_mode": "balanced_recent",
"cluster_mode": "debate",
"subqueries": [
{"label": "reception", "search_query": "Kestrelbase 2.0", "ranking_query": "How did users react to the Kestrelbase 2.0 release?", "sources": ["reddit", "hackernews", "x", "youtube"], "weight": 1.0},
{"label": "upgrade pain", "search_query": "Kestrelbase migrate OR upgrade OR broke", "ranking_query": "Which problems did teams hit while upgrading Kestrelbase?", "sources": ["reddit", "github", "hackernews"], "weight": 0.8},
{"label": "alternatives", "search_query": "Kestrelbase alternative OR replaced OR switched", "ranking_query": "What are people choosing in place of Kestrelbase, and why?", "sources": ["reddit", "hackernews", "grounding"], "weight": 0.6}
]
}
intent: factual, product, concept, opinion, how_to, comparison, breaking_news or prediction.freshness_mode: strict_recent, balanced_recent or evergreen_ok. cluster_mode: none, story, workflow, market or debate.search_query holds short keywords phrased the way posts are titled, without dates or words like "recent"; ranking_query is the full question used for re-ranking. sources are engine names (grounding is the web). Names this machine lacks are dropped, and a plan left with none stops with an error before any retrieval.export LAST30DAYS_NATIVE_SEARCH=1 # only when the host agent has web search of its own
python3 "$ENGINE" "Kestrelbase" --plan ~/Documents/Last30Days/plans/kestrelbase.json \
--x-handle=kestrelbase --github-repo=kestrelbase/kestrelbase \
--dedicated-subreddits=kestrelbase --subreddits=selfhosted,Database \
--emit=compact --save-dir ~/Documents/Last30Days
Run it in the foreground and allow five minutes; a default pass usually finishes in one to three.
| Flag | Effect |
|---|---|
--quick / --deep | smaller or larger ranked pool (15 or 60 items; 40 by default) |
--days N, --as-of YYYY-MM-DD | window length and its end date |
--search reddit,hn,youtube | only the named sources (hn, bsky, web, xhs are aliases) |
--competitors [N], --competitors-list "A,B" | add discovered or named rivals and compare them |
--github-user NAME | a person's pull requests and repositories |
--hiring-signals | treat a company's open roles as a signal of where it is investing |
--emit compact, md, json, html, brief, context | output format; add --json-profile raw for the internal dump |
--register exec, dev, creator, eli5 | audience preset for the standard brief |
--save-suffix NAME, --output FILE | keep variants apart, or write to one exact path |
--store | also write findings to ~/.local/share/last30days/research.db |
--auto-resolve | the engine looks up handles itself when the host cannot search the web |
--verify-freshness | re-check odds, star counts and status claims after research |
When the user typed the slash command (/last30days Kestrelbase), the installed skill drives this same engine; the plan, the flags and the checks below still apply.
kestrelbase-raw.md (-raw-NAME.md with a suffix, .json for JSON, -raw-html.html for HTML). With neither --save-dir nor LAST30DAYS_MEMORY_DIR, a direct engine call writes no research file.--emit=json returns schema_version, query, generated_at, window_days, source_status (source name mapped to an outcome), clusters (title, summary, sources, engagement_total) and results (title, url, source, published_at, relevance_score, engagement, summary, cluster).source=external. source=deterministic means your plan was ignored or missing.Date range line equals the window that was requested.python3 "$ENGINE" doctor --postmortem. Outcomes other than ok, no-results and skipped-unconfigured (partial, rate-limited, auth-failed, payment-required, timeout, unreachable, schema-drift, error) mean coverage has a hole, and the brief has to say where.--dedicated-subreddits, --polymarket-keywords or narrower subqueries, then repeat the run.Lead with the answer, then show the support. A layout that works for most readers:
# SUBJECT: what people said, START to END
**Bottom line.** Two or three sentences the reader can act on.
| Finding | Evidence | Reach |
|---|---|---|
| one claim per row | where it was said (subreddit, handle, channel, site) | the engagement numbers from the evidence |
**Where opinion splits.** The strongest disagreement and who is on each side.
**Thin or missing.** Sources that failed or were not configured; claims backed by a single post.
**Coverage.** Items per source.
**Evidence file.** Path of the saved raw file.
Name sources the way readers recognise them (r/selfhosted, @handle, a channel or publication) and keep links for the handful of items the reader should open.
--drill "cluster 3" (or a cluster title), with no topic, digs into one cluster from the cached report; the cache lives one hour (LAST30DAYS_REPORT_CACHE_TTL_SECONDS).python3 "$ENGINE" library search "replication lag" searches saved briefs offline.--emit=html --synthesis-file brief.md renders a self-contained page from that cache.--store, then python3 "$(dirname "$ENGINE")/watchlist.py" add "Kestrelbase" --weekly, have a scheduler call watchlist.py run-all, and use briefing.py generate --weekly (same directory as the engine) for the digest.Priya Raman, staff engineer at a 14-person logistics startup, asks: "We run Kestrelbase 1.8. What are users saying about 2.0 before we upgrade?" The agent confirms the repository kestrelbase/kestrelbase, the subreddit r/kestrelbase and the handle @kestrelbase, saves the plan from step 4 and runs the command from step 5. stderr reports Plan: intent=product, freshness=balanced_recent, cluster_mode=debate, subqueries=3, source=external; the tally lists Reddit, Hacker News, GitHub, X and YouTube. The brief:
# Kestrelbase 2.0: what people said, 1 Sep to 1 Oct 2026
**Bottom line.** Users like the replication rewrite and resent a storage-format change that needs a manual migration. Installs under 50 GB report a clean upgrade; larger ones should wait for 2.0.3.
| Finding | Evidence | Reach |
|---|---|---|
| Replication lag dropped after upgrading | r/kestrelbase benchmark thread; Hacker News launch discussion | 412 upvotes, 96 comments; 287 points |
| Format change broke unattended upgrades | GitHub issue 1841; two r/selfhosted threads | 73 reactions; 158 and 64 upvotes |
| Maintainers promised a fix in 2.0.3 | @kestrelbase on X | 1,204 likes |
**Where opinion splits.** Hosting providers praise the smaller memory footprint; hobbyists on single-board computers report slower cold starts.
**Thin or missing.** One video covered the release (18,300 views). TikTok and Instagram are not configured. "30% faster writes" comes from a single Reddit post.
**Coverage.** Reddit 14 threads, Hacker News 3 stories, GitHub 9 issues, X 22 posts, YouTube 1 video.
**Evidence file.** ~/Documents/Last30Days/kestrelbase-raw.md
Dana Whitfield runs growth at Tallybird, a nine-person bookkeeping startup, and wants a Monday digest of complaints about late client payments. No agent is present, so the plan file is reused, cookies are off, and degraded coverage must not pass as success. With LAST30DAYS_STRICT_EXIT=1, a run in which some source failed returns exit code 3.
#!/usr/bin/env bash
set -u
OUT="$HOME/research/late-payments-$(date +%F).json"
LAST30DAYS_STRICT_EXIT=1 python3 "$ENGINE" "freelancer late payment complaints" \
--plan "$HOME/research/plans/late-payments.json" --days 7 --search reddit,hn,youtube \
--no-browser-cookies --emit=json --output "$OUT" > /dev/null # stdout repeats the JSON
status=$?
[ "$status" -eq 0 ] || [ "$status" -eq 3 ] || { echo "run failed ($status)"; exit "$status"; }
python3 - "$OUT" <<'PY'
import json, sys
report = json.load(open(sys.argv[1]))
print(f'{report["query"]}: {len(report["results"])} items over {report["window_days"]} days')
for c in sorted(report["clusters"], key=lambda c: -c["engagement_total"])[:5]:
print(f'- {c["title"]} ({c["engagement_total"]} interactions; {", ".join(c["sources"])})')
fine = ("ok", "no-results", "skipped-unconfigured")
bad = {name: state for name, state in report["source_status"].items() if state not in fine}
if bad:
print("incomplete coverage:", bad)
PY
exit "$status"
freelancer late payment complaints: 31 items over 7 days
- Net-60 terms pushed onto solo designers (1940 interactions; reddit, hackernews)
- Late-fee clauses that clients actually honour (655 interactions; reddit)
- Chasing invoices without losing the account (212 interactions; youtube)
incomplete coverage: {'youtube': 'rate-limited'}
The script exits 3, so the scheduler marks the digest as partial instead of shipping it as complete.
The user expected X posts and the tally shows none. python3 "$ENGINE" doctor --postmortem lists x under Failed: with its state and a fix: line, and reddit (14) under Succeeded:. The agent relays the fix, the user rotates the key with setup --store-key XAI_API_KEY, and the agent repeats the run. The first brief is not presented as full coverage in the meantime.
--agent and --trending belong to the slash command. The engine rejects unknown arguments with a usage error (exit 2), as it does malformed plans and inline JSON given to --x-posts.Evidence is thin for this topic. in the warnings is a result to report.--deep-research. Mention them before enabling a paid source, and check doctor before blaming the topic for a quiet run.X_BEARER_TOKEN reaches roughly the past week unless the X developer account was granted full-archive search, so a 30-day X claim may rest on seven days.--publish-html and library feed --publish upload to a public host. Use them only on an explicit request.--corpus DIR are read locally and kept out of published output.--deep-research cannot be combined with them.