Commit Graph

170 Commits

Author SHA1 Message Date
Matt Van Horn 309c8e37c6 fix(sync): deploy to last30daysCROSS with patched frontmatter
- Add CROSS to sync targets with sed-patched name/version/description
- Switch cp to rsync to handle identical file edge case on APFS
- CROSS SKILL.md gets last30daysCROSS skill root injected into search path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 08:27:30 -08:00
Matt Van Horn 2ff9b6f6c1 feat(polymarket): outcome-aware scoring and synthesis instructions
- _compute_text_similarity() now checks outcome names with bidirectional
  substring matching (0.85) and token overlap (0.7), not just event titles
- Collect outcomes from ALL active markets per event, filter to >1% price
- Reorder outcome_prices to surface topic-matching outcome before top-3 truncation
- Add SKILL.md "Prediction Markets" synthesis section with structural/long-term
  market preference, domain examples, citation format, and narrative weaving
- Add Polymarket to citation priority list between HN and Web
- Update stats box template to show up to 5 market odds
- Fix render.py "vol24h" label to "volume"
- Add NCAA seed fixture event for outcome-only matching tests
- 82 polymarket tests pass (14 new)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 08:12:45 -08:00
Matt Van Horn 9d9e7e89d9 feat(polymarket): replace position-based ranking with quality-signal relevance
Polymarket results now rank by text similarity, volume, liquidity, price
movement, and competitive score instead of API return position. Also fixes
pagination (DEPTH_CONFIG now controls page count, not a no-op limit param)
and caps results after re-ranking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:07:43 -08:00
Matt Van Horn 994a4ab2ca feat(polymarket): add Polymarket prediction markets as 6th research source
Search Polymarket's free Gamma API for relevant prediction markets on any
topic. Uses smart multi-query expansion to cast a wider net (e.g., "Arizona
Basketball" also searches "Arizona"), merges and dedupes by event ID, and
shows price movement context ("up 22.5% this week"). No API key required.

Also hides sources with zero results from the stats output (all sources).

54 new tests, all passing. Full pipeline integration with scoring, dedupe,
cross-source linking, and rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:27:19 -08:00
Matt Van Horn bbaaf28d2e fix(x): don't skip unfiltered resolved handle search when entity-extracted
The resolved handle dedup was wrong: if entity_extract found @thedorbrothers
(from @mentions in Phase 1 results), the resolved handle search was skipped
entirely. But entity-extracted handles are searched WITH topic keywords
(from:handle topic), while resolved handles need UNFILTERED search
(from:handle) to find posts that don't mention the topic string.

Example: Dor Brothers' viral tweet (5.5K likes) says "We made a $300M movie
starring @LoganPaul" - no mention of "dor brothers" anywhere. The topic-
filtered entity search missed it. The unfiltered resolved search finds it.

Before: 30 X posts, 161+ likes (entity search only)
After: 40 X posts, 5549+ likes (resolved handle adds viral tweet)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:35:08 -08:00
Matt Van Horn 4f584a4e96 feat(x): resolve X handles for person/brand topics via agent WebSearch
When a topic is a person/brand (e.g. "Dor Brothers", "Jason Calacanis"),
the agent now resolves their X handle via WebSearch before running the
script, then passes --x-handle to search their posts unfiltered (no
topic keywords required). This finds posts the entity made without
mentioning their own name.

- SKILL.md + OpenClaw variant: Step 0.5 handle resolution instructions
- last30days.py: --x-handle CLI arg, passed through to _run_supplemental()
- bird_x.search_handles(): topic is now Optional[str] for unfiltered mode
- schema.py: resolved_x_handle field on Report
- render.py: show resolved handle in stats output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:00:49 -08:00
Matt Van Horn bed0557b65 feat(quality): GOAT synthesis improvements - hybrid cross-source linking, YouTube synonyms, human-readable xref tags
Ran 15-way blinded comparison (5 topics x 3 versions). CROSS won all 5 topics
(4.74/5.0 avg vs HN 4.10, Base 3.73). Then improved CROSS further:

- dedupe.py: hybrid similarity (token+trigram Jaccard) at 0.40 threshold,
  cross-source links went from 3 to 13 items across 5 topics
- render.py: [xref: HN5, HN4] -> [also on: HN, Reddit] for human-readable tags
- youtube_yt.py: SYNONYMS dict so "hip hop" matches "rap" (0.33 -> 0.71 score)
- SKILL.md: instruction #7 tells Claude to lead with cross-platform signals

Validation: improved CROSS scores 4.38/5.0 vs original 3.98 (+0.40), wins 4/5
topics. Biggest gains in specificity (+0.8) and format compliance (+1.0).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:06:53 -08:00
Matt Van Horn 0591f55f0e feat(quality): YouTube relevance scoring and cross-source linking
YouTube videos now get real relevance scores based on token overlap
between the search query and video title (was hardcoded at 0.7).
Uses ratio overlap with stopword removal, floored at 0.1.

Cross-source linking annotates items that discuss the same story
across different platforms (e.g., Reddit + HN + X). Items get
bidirectional cross_refs displayed as [xref: R3, HN5] in compact
output so Claude can triangulate multi-platform coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 10:46:58 -08:00
Matt Van Horn f60a4359a0 fix(ui): quiet HN and YouTube spinners in non-TTY mode
Reddit and X are the star of the show. In Claude Code (non-TTY),
suppress  start messages for HN and YouTube so Reddit/X are the
first visible lines. HN/YouTube still show ✓ completion messages.
Also suppress [HN] debug logs in non-TTY to reduce output clutter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:58:01 -08:00
Matt Van Horn 7a9f447231 fix(ordering): move HN after YouTube in stats, sort priority, and SKILL.md
HN was appearing before YouTube in the stats block, sort tiebreaker,
and source status. Now consistently: Reddit > X > YouTube > HN > Web.
Also restored emoji + box-drawing chars in test skill SKILL.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:41:14 -08:00
Matt Van Horn 38a7ea253e feat(hackernews): add Hacker News as 5th research source
Add HN search via free Algolia API (no key needed). Two-phase approach:
search for stories, then enrich top ones with comments. Integrated into
the full pipeline (normalize, score, dedupe, render) running in parallel
with Reddit/X/YouTube. Source priority: Reddit > X > HN > YouTube > Web.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 18:33:31 -08:00
Tjarko Leifer 451ebb3e22 Fix OpenAI 429 rate limiting with exponential backoff
The Reddit search uses OpenAI's Responses API with web_search, which
frequently returns 429 rate limit errors. The previous retry logic used
linear backoff (1s, 2s, 3s) which is too aggressive for OpenAI's rate
limiter (often needs 10-60s waits).

Changes:
- Increase max retries from 3 to 5
- Switch from linear to exponential backoff (2s, 5s, 9s, 17s, 33s)
- Parse and respect Retry-After header from OpenAI 429 responses
- Fall back to cheaper models (gpt-4.1 → gpt-4o) on 429s, not just
  on 400/403 access errors
- Remove gpt-4o-mini from fallback chain — it doesn't support
  web_search with the filters parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:40:36 +01:00
Matt Van Horn a4d09e470e docs: add CHANGELOG.md, release notes, and v2.1 polish
Add CHANGELOG.md (Keep a Changelog format) and release-notes.md
for the v2.1.0 GitHub Release. Also includes queued copy updates:
plugin.json version bump to 2.1.0, YouTube added to descriptions,
SKILL.md security/permissions section and ClawdBot metadata,
updated watchlist examples, and v2.1 launch post refinements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:16:01 -08:00
Matt Van Horn 7d6bac7b0e fix: handle engagement: null in watchlist and YouTube normalizer
When X posts return engagement: null, dict.get("engagement", {})
returns None (key exists with null value), causing AttributeError.
Use `or {}` idiom to coalesce None to empty dict.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 07:29:27 -08:00
Matt Van Horn 2d6855483d fix: Reddit future timeout — same bug as YouTube
OpenAI Responses API web_search takes 60-90s but the generic
future timeout was killing it at 30s (quick) / 60s (default).
Added reddit_future key to TIMEOUT_PROFILES (60/90/120s).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:57:32 -08:00
Matt Van Horn 20a859ecec fix: YouTube timeout bump to 90s + Reddit 429 fail-fast
YouTube: Add youtube_future timeout key (60/90/120s for quick/default/deep)
separate from the shared future timeout. YouTube needs more time because
it does search + parallel transcript fetching. Previously, 20 videos +
5 transcripts exceeded the 60s budget and all results were discarded.

Reddit 429: Propagate rate-limit errors instead of swallowing them.
Enrichment now uses 10s timeout / 1 retry (was 30s / 3 retries).
On first 429, cancel remaining enrichment and skip Phase 2 Reddit.
Total time wasted on 429 drops from ~75s to ~12s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:48:26 -08:00
Matt Van Horn c3640931ed fix: skip hard date filter for YouTube — keep evergreen content
The hard date filter in the main script was double-filtering YouTube
results. youtube_yt.py already applies a soft date filter that prefers
recent videos but keeps older ones when < 3 are within range (for
evergreen topics like "youtube thumbnails"). The hard filter then
removed all of them, resulting in 0 YouTube items.

YouTube content has a longer shelf life than tweets/posts, so the
soft filter's fallback behavior is correct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:32:36 -08:00
Matt Van Horn 1acb5c6fff fix: remove eager import from __init__.py and add sync.sh
The `from . import bird_x` in __init__.py caused ImportError for all
other module imports (youtube_yt, render, etc.) when bird_x had issues,
crashing the entire script in Codex. Making it a bare package marker
lets Python resolve `from lib import X` normally.

Added scripts/sync.sh to deploy to all three skill locations
(~/.claude, ~/.agents, ~/.codex) with import verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:31:50 -08:00
Matt Van Horn 14b6ac06e1 feat(nux): conversational NUX with "last30" trigger and dynamic source status
Replace CLI-style promo banners with casual, conversational NUX message
that teaches users to say "last30" + natural language. Remove stale Bird
CLI npm install references and dead prompt_bird_install methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:19:38 -08:00
Matt Van Horn 06f74a4d0c fix: add timeouts, process cleanup, and source diagnostics
Script was hanging indefinitely when API sources were slow or
unresponsive. Now enforces bounded execution:

- Global timeout watchdog (180s default, 90s --quick, 300s --deep)
- Per-source future.result() timeouts (60s/30s/90s by depth)
- Parallel Reddit enrichment capped at 15 items / 45s total
- Subprocess process-group isolation (os.setsid + killpg)
- atexit cleanup kills all tracked child processes
- --timeout=N flag for user override

Also fixes the UX gap where missing sources were silently skipped:

- Pre-flight diagnostic banner shows source status before research
- Source status footer in compact output shows used/skipped/why
- Actionable fix commands for each missing source

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:05:36 -08:00
Matt Van Horn 7162eb6b36 fix: YouTube display and search quality
- Remove 2>&1 from SKILL.md so stderr doesn't pollute model input
- Run script in foreground (not background) with 5min timeout
- Add explicit YouTube synthesis instruction for Claude
- Remove --flat-playlist which broke date filtering (all dates were None)
- Move date filtering to Python with soft fallback for evergreen topics
- Keep 'tips', 'tutorial', 'review', 'guide' in YouTube search queries
- Increase yt-dlp timeout from 60s to 120s for full metadata fetch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:02:16 -08:00
Matt Van Horn 08e2010554 feat(engine): add native web search, --store, and --diagnose to main engine
- _search_web() dispatches to Parallel AI / Brave / OpenRouter based on config
- Web results flow through full pipeline: normalize → score → dedupe
- --diagnose shows all source availability (API keys, Bird, YouTube, web backends)
- --store persists findings to SQLite via store.py for watchlist/briefing system
- run_research() now returns web_items alongside reddit/x/youtube
- web_needed flag only set when no native web backend is available

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:43:34 -08:00
Matt Van Horn ba330e9a0c feat(open): Port web search backends, persistence layer, and env merge from openclaw 2026-02-14 23:35:44 -08:00
Matt Van Horn a09413608d feat: Add Codex CLI compatibility
- Add agents/openai.yaml for Codex skill discovery
- Make SKILL.md script path portable (repo, Claude, Codex, agents dirs)
- Platform-neutral output text ("assistant" instead of "Claude")
- Sandbox-friendly cache/output dirs with env var overrides and tempdir fallback
- Add Codex installation docs to README

Inspired by PR #24 (el-analista) and PR #5 (jblwilliams).
Zero impact on existing Claude Code behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:18:53 -08:00
Matt Van Horn c66ca7f43d feat: Add YouTube as 4th research source via yt-dlp
YouTube search and transcript extraction runs automatically when yt-dlp
is installed. Searches for topic videos from the last N days, fetches
auto-generated transcripts for top results, and feeds them through the
same scoring pipeline (relevance + recency + engagement) as Reddit/X.

New files:
- youtube_yt.py: search, transcript extraction, VTT cleanup

Modified files:
- schema.py: YouTubeItem dataclass, updated Report
- normalize.py: normalize_youtube_items()
- score.py: YouTube engagement scoring (views-dominated)
- dedupe.py: YouTube deduplication
- render.py: YouTube section in compact output
- env.py: is_ytdlp_available() check
- ui.py: YouTube progress messages
- last30days.py: _search_youtube(), parallel execution with Reddit/X
- SKILL.md: YouTube in stats box, citation priority
- README.md: YouTube docs, yt-dlp requirement, Peter shoutout

Inspired by Peter Steinberger's yt-dlp + summarize toolchain approach.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:38:04 -08:00
Matt Van Horn 31313c69ac v2.1: Bundle Bird X search - no external CLI needed
Vendor Bird's Twitter GraphQL search client directly into /last30days,
eliminating the dependency on `npm install -g @steipete/bird`. X search
now works out of the box with just Node.js 22+ and browser cookies.

- Add vendored bird-search.mjs wrapper (search-only subset of Bird v0.8.0)
- Vendor @steipete/sweet-cookie for browser cookie extraction
- Update bird_x.py to call vendored Node.js module instead of `bird` binary
- Update README.md and SKILL.md for v2.1 (remove Bird CLI install steps)
- Include Bird's MIT LICENSE in vendor directory

The fallback chain is: vendored search -> xAI API key -> web-only mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:16:04 -08:00
Matt Van Horn 41779b81c0 Fix X search returning 0 results on popular topics
Three bugs in _extract_core_subject():

1. Multi-word noise phrases ("what are", "how to") never matched
   because code compared individual words against multi-word strings.
   "what are people saying about DeepSeek R1" became "what are people
   saying" — losing the entire topic.

2. Missing meta words — "prompt", "techniques", "tips" weren't
   filtered (only "prompting" was). "vibe motion best prompt
   techniques" kept 4 keywords instead of 2.

3. No retry on 0 results — Reddit retries with simplified queries
   but X accepted 0 and moved on.

Fix: Two-phase extraction (strip multi-word prefixes/suffixes first,
then individual noise words), expanded noise set, max 3 words (was 4),
and automatic retry with first 2 words when Bird returns 0 results.

Before → After:
- "vibe motion best prompt techniques" → "vibe motion" (was 4 words, 0 results)
- "what are people saying about DeepSeek R1" → "deepseek r1" (was "what are people saying")
- "nano banana pro prompts for gemini" → "nano banana pro" (was 4 words)

Tested: vibe motion (12 X posts, was 0), DeepSeek R1 (12 posts),
kanye west (12 posts, no regression).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 10:43:45 -08:00
Matt Van Horn 52456e5820 Update README for V2 and bump USER_AGENT to 2.0
- Add V2 to title
- Document Bird CLI installation (free X search)
- Add --days=N to options table
- Update Requirements to show Bird CLI as recommended free option
- Replace How It Works with two-phase search architecture
- Add What's New in V2 section with all new features
- Credit community contributors (@JosephOIbrahim, @levineam, @jonthebeef)
- Bump http.py USER_AGENT from 1.0 to 2.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 10:26:53 -08:00
Matt Van Horn 1ae7a16c75 feat: Smart supplemental search — Phase 2 entity-aware drill-down
After the initial broad search (Phase 1), extract key entities from results
and run targeted secondary searches to surface content the broad pass missed:

- New entity_extract.py: parses @handles, #hashtags, subreddits from results
- bird_x.py: search_handles() does targeted from:handle searches via Bird CLI
- openai_reddit.py: search_subreddits() uses Reddit's free .json search endpoint
- last30days.py: Phase 2 orchestration runs after enrichment, merges + dedupes

Tested with "kanye west" (+9 Reddit, +1 X) and "claude code skills" (+6 Reddit, +1 X).
Phase 2 is skipped on --quick mode. Default caps at 3 handles/subs, deep at 5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 10:00:28 -08:00
Matt Van Horn 60f06ea86e Merge community PRs: Windows Unicode fix, 403 fallback, --days flag
- PR #17 (JosephOIbrahim): Fix UnicodeEncodeError on Windows cp1252
- PR #16 (levineam): Handle HTTP 403 model access errors, add gpt-4.1 fallback
- PR #18 (jonthebeef): Add --days=N flag for configurable lookback (1-30)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 09:05:46 -08:00
Matt Van Horn 7c36866524 Fix v2 output quality: stats format, Reddit results, citations, summary structure
- Stats: replace BAD/GOOD examples with strict fill-in-the-blank template
- Reddit: add subreddit-targeted fallback search, soften scoring penalties
  (engagement -10→-3, date confidence -10→-5), add minimum result guarantee
- Citations: limit to 1 per insight, short format, no engagement metrics
- Summary: add bold topic headers template for structured paragraphs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 10:04:46 -08:00
Matt Van Horn e7610ddcac Fix Bird search: strip noise words + fix date parsing
1. Add _extract_core_subject() to strip noise words before X search
   - X search is literal keyword matching, not semantic
   - "best open claw usecases" → "open claw" (27 results vs 0)

2. Fix ISO date detection bug
   - Was: if "T" in created_at (matched "Tue" in "Tue Feb 03...")
   - Now: if created_at[10] == "T" (proper ISO separator check)
   - Dates now parse correctly: (2026-02-03) instead of (date unknown)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:38:38 -08:00
Matt Van Horn f800b5eb1b Fix Bird CLI response parsing
1. last30days.py: Check isinstance(dict) before .get() - Bird returns
   list on success, dict on error

2. bird_x.py: Update field mappings for Bird's actual response format:
   - author.username not user.screen_name
   - createdAt not created_at (camelCase)
   - likeCount, retweetCount, etc. (camelCase)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:00:40 -08:00
Matt Van Horn bcf5c2cc3f Fix: embed date filter in query instead of --since flag
Bird CLI doesn't support --since as a CLI flag.
Use X's search syntax: 'topic since:YYYY-MM-DD'

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 13:26:51 -08:00
Matt Van Horn 976a6e2867 Match original NUX: non-blocking Bird promotion
- Remove interactive "Install Bird CLI?" prompt
- Add Bird as FREE option in promo messages
- Auto-detect Bird silently (use if authenticated)
- Keep original flow: show promo → continue with available sources

Bird now appears in the promo alongside API keys:
  🔵 X (Twitter)
     └─ FREE: npm install -g @steipete/bird (uses browser session)
     └─ Or: Add XAI_API_KEY (paid API)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 12:38:54 -08:00
Matt Van Horn 73b42c5c89 fix: show Bird install prompt before topic validation
Move Bird CLI check to run before topic validation so users see
the install prompt even when running without a topic argument.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:53:04 -08:00
Matt Van Horn fe9ebb9944 feat: add clean mode for testing Bird CLI flow
- Add LAST30DAYS_CONFIG_DIR env var to override config location
- Set LAST30DAYS_CONFIG_DIR="" in test skill for clean mode
- Test skill now ignores existing API keys to show Bird install prompt

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:48:40 -08:00
Matt Van Horn ecdee52d04 feat(main): integrate Bird setup into main flow
- Move progress display initialization early to support Bird prompts
- Check Bird availability and offer install if no X source available
- Override available sources when Bird is ready
- Pass x_source parameter to run_research function

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:04:56 -08:00
Matt Van Horn e57b82f1ad feat(main): dispatch X search to Bird or xAI 2026-02-03 11:03:29 -08:00
Matt Van Horn 7a7e35bcf5 feat(main): add Bird setup function
Add bird_x import and setup_bird_if_needed function to main script.
The function checks Bird status and offers installation if needed,
returning 'bird' if ready, 'declined' if user declined, or None if
unavailable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:02:28 -08:00
Matt Van Horn 25d58a4494 feat(lib): export bird_x module
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:01:46 -08:00
Matt Van Horn df849e7278 feat(env): add X source detection with Bird priority 2026-02-03 11:01:23 -08:00
Matt Van Horn 57e238cb9c feat(ui): add Bird CLI install prompts and auth help 2026-02-03 11:00:42 -08:00
Matt Van Horn 34723c70ac feat(bird): add response parser matching xai_x format 2026-02-03 10:59:39 -08:00
Matt Van Horn 2910e83552 feat(bird): add search_x function 2026-02-03 10:58:44 -08:00
Matt Van Horn 444bd6e185 feat(bird): add installation and status functions 2026-02-03 10:57:57 -08:00
Matt Van Horn a7fb951257 feat(bird): add detection functions for Bird CLI
- is_bird_installed() checks if bird command is in PATH
- is_bird_authenticated() runs bird whoami to verify auth
- check_npm_available() checks if npm can install bird

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 10:57:26 -08:00
Matt Van Horn 6cc7dad86e fix: YAML frontmatter and GPT-5 model fallback
- Fix invalid YAML in SKILL.md argument-hint (closes #8)
  Wrapped value in single quotes to properly escape double quotes

- Add automatic model fallback for GPT-5 access errors (closes #9)
  When OpenAI returns 400 for unverified orgs, retry with gpt-4o

- Add tests for model fallback logic

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 11:07:33 -08:00
Matt Van Horn 3a4a727f4b Run Reddit and X searches in parallel, fix timeout handling
- Refactor run_research() to use ThreadPoolExecutor for parallel execution
- Reddit timeout/crash no longer blocks X search from running
- Add catch for ConnectionResetError/OSError in http.py
- Per-item error handling in Reddit enrichment (one failure doesn't crash all)
- Increase API timeouts from 60/90/120 to 90/120/180 seconds
- Add ClawdBot setup example to README

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:33:41 -08:00
Matt Van Horn 86422a74af feat: Add honesty warning when results aren't from last 30 days
Adds ⚠️ LIMITED RECENT DATA warning when:
- Fewer than 5 items are confirmed from the date range
- Tells Claude to be transparent with user about data freshness

Example output for obscure topic (June Oven):
"Only 4 item(s) confirmed from 2025-12-26 to 2026-01-25.
Results below may include older/evergreen content."

Popular topics (clawdbot, nano banana) don't show the warning.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:00:53 -08:00