Commit Graph

334 Commits

Author SHA1 Message Date
Jeffrey Sperling ce8e289692 Address review feedback: fix tiebreaker map, error handling, regex patterns
- Add BlueskyItem/TruthSocialItem to _ITEM_SOURCE_MAP (wrong tiebreaker)
- Add bluesky/truthsocial to _DEFAULT_TIEBREAKER
- Log HTTPError in select_openai_model instead of silent fallback
- Remove overly broad 'or.*for' from comparison regex (false positives)
- Remove bare 'will' from prediction regex (misclassifies feature queries)
- Narrow brave_search except clauses to ValueError/TypeError
- Fix stale comments: pricing table, docstrings, penalty descriptions
2026-03-11 18:32:37 -07:00
Jeffrey Sperling 588cff3e00 Optimize model selection for cost-efficiency on structured extraction
The task profile is search tool invocation + JSON extraction — not
reasoning or creative work. Mini models handle this equally well at
3-5x lower cost per call.

OpenAI changes:
- Rename is_mainline_openai_model -> is_search_capable_model
- Include mini variants (gpt-5-mini, gpt-4.1-mini) in candidate pool
- Exclude gpt-4o-mini (no domain filtering) and nano (no web_search)
- select_openai_model() now prefers mini within newest generation
- OPENAI_FALLBACK_MODELS: gpt-5-mini first, mainline as last resort
- MODEL_FALLBACK_ORDER: same mini-first ordering

xAI changes:
- Switch alias from grok-4-1-fast (reasoning) to
  grok-4-1-fast-non-reasoning — same token price, faster response,
  no wasted reasoning tokens for structured extraction

Cost per Reddit search call: ~$0.015 (gpt-5-mini) vs ~$0.044 (gpt-4.1)
2026-03-11 18:05:09 -07:00
Jeffrey Sperling 859f6c5829 Add Brave LLM Context endpoint as opt-in web search mode
Brave's /res/v1/llm/context returns pre-extracted text chunks
optimized for LLM consumption instead of URLs + short snippets.
Enable with BRAVE_LLM_CONTEXT=1 env var; same API key and pricing.

- Add _search_llm_context() and _normalize_llm_context() to brave_search.py
- Wire opt-in flag through _search_web() in last30days.py
- Update module docstring (free tier eliminated Feb 2026)
- Add 23 tests covering normalization, filtering, date parsing
2026-03-11 18:04:43 -07:00
Jeffrey Sperling 4fde52459d Filter Polymarket results to active events only
Add events_status=active and keep_closed_markets=0 to Gamma API
search params, filtering out resolved/closed markets that clutter results.
These params are confirmed in the Polymarket OpenAPI spec.
2026-03-11 18:04:43 -07:00
Jeffrey Sperling ef7c0f05dd Add query-type-aware source tiering and scoring
Detect query type (product/concept/opinion/how_to/comparison/breaking_news/
prediction) via lightweight regex patterns and use it for:

1. Source selection: each query type has tier-1 (always run) and tier-2
   (run if available) sources. Unlisted sources are opt-in only.
   Truth Social is always opt-in regardless of query type.

2. WebSearch penalty: varies by query type instead of flat -15pt.
   Concept queries get 0 penalty (web docs are authoritative),
   how_to gets 5pt, breaking_news gets 10pt, product/opinion get 15pt.

3. Tiebreaker ordering: source priority varies by query type.
   YouTube ranks first for how_to, Polymarket for prediction,
   HN for concept queries, X for breaking news.

All changes are backward-compatible: callers that don't pass query_type
get the original behavior (15pt penalty, Reddit > X > YouTube tiebreaker).
2026-03-11 18:04:43 -07:00
Jeffrey Sperling e568ef8af9 Revert MODEL_FALLBACK_ORDER to upstream values
Model optimization (mini-first fallback, is_search_capable_model) belongs
in PR #67. This PR stays focused on endpoint/API fixes only.

Also fixes pre-existing test bug where test asserted gpt-4o was first in
MODEL_FALLBACK_ORDER when it was actually gpt-4.1.
2026-03-11 18:02:33 -07:00
Jeffrey Sperling 3e9e2f632b Update stale API endpoints and model chains
- Instagram: migrate /v1/ to /v2/ ScrapeCreators endpoint (v1 deprecated Feb 2026)
- OpenAI: switch fallback chain to [gpt-5-mini, gpt-4.1-mini, gpt-4.1] (8x cheaper,
  gpt-5-mini is the first mini model supporting web_search with filters.allowed_domains)
- xAI: use explicit grok-4-1-fast-non-reasoning (bare name aliases to reasoning variant)
- xAI: pass from_date/to_date natively to x_search tool config instead of prompt-only
- Polymarket: correct rate limit comment (15K/10s, not 350/10s)
2026-03-11 16:42:49 -07:00
Jeffrey Sperling 9ca84e495e Fix stale test assertions and truthsocial pytest dependency
- test_models: update xAI model expectations to grok-4-1-fast (matching
  current XAI_POLICY_MAP)
- test_openai_reddit: update fallback order assertion to gpt-4.1 (matching
  current MODEL_FALLBACK_ORDER)
- test_codex_auth: expect 'reddit' not 'web' when no API keys (Reddit
  is available via public JSON fallback)
- test_truthsocial: convert from pytest-style classes to unittest.TestCase,
  fix import path to use sys.path.insert pattern (matching all other tests)
2026-03-11 16:35:55 -07:00
Matt Van Horn b38703e53d feat(truthsocial): Add Truth Social as opt-in source
Mastodon-compatible API at truthsocial.com/api/v2/search.
Opt-in via TRUTHSOCIAL_TOKEN env var (bearer token from browser).
Silent when unconfigured. Full pipeline: search, parse, normalize,
score, dedupe, render across all 10 pipeline files.

27 new tests, 440 total passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 00:14:39 -07:00
Matt Van Horn b6fd5ff406 docs: v2.9.5 README update, fix plugin.json hooks (#62)
- Fix plugin.json hooks field from invalid array to empty object
- Add Claude Code plugin install above ClawHub badge
- Version bump to v2.9.5 with new features block (Bluesky, comparative
  mode, ScrapeCreators X, per-project env, expanded tests)
- Add Bluesky to all source list references
- Document BSKY_HANDLE/BSKY_APP_PASSWORD env vars in install + optional sections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:40:07 -07:00
Matt Van Horn adb5a672d9 fix(bluesky): make Bluesky opt-in with app password auth
searchPosts endpoint now returns 403 for unauthenticated requests.
Add session auth via createSession, gate on BSKY_HANDLE + BSKY_APP_PASSWORD
env vars. When unconfigured, Bluesky is completely invisible (no error).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:27:39 -07:00
Matt Van Horn ecf90c0281 feat(skill): add comparative mode and Bluesky references to SKILL.md
Add COMPARISON query type for "X vs Y" research with 3 parallel passes.
Add Bluesky stats line and update all source list references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:58:21 -07:00
Matt Van Horn 9a1059ee9d feat(bluesky): add Bluesky/AT Protocol as social source
Free, no-auth-required search via public.api.bsky.app.
Always-on like HN and Polymarket (no API key needed).

- New scripts/lib/bluesky.py: search + parse via AT Protocol
- BlueskyItem schema, normalization, scoring, deduplication
- Wired into orchestrator ThreadPoolExecutor with timeout config
- Rendering in compact, full, and JSON output modes
- 14 unit tests covering parsing, dates, relevance, edge cases
- --search=bluesky / --search=bsky for bluesky-only mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:54:20 -07:00
Matt Van Horn 4b7087e136 feat(x): add ScrapeCreators as X/Twitter search backend
One SCRAPECREATORS_API_KEY now covers Reddit, TikTok, Instagram, AND X.
Priority: Bird (free) > xAI API > ScrapeCreators (shared key).

New module scrapecreators_x.py follows the same pattern as tiktok.py.
Updated env.py source routing and last30days.py orchestrator dispatch.
Includes 20 unit tests.

Fixes #55.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:34:39 -07:00
Matt Van Horn 82a006280e Merge pull request #58 from phjlljp/feat/session-start-config-check
feat: add SessionStart hook for config check
2026-03-09 21:55:57 -07:00
Matt Van Horn a7398c50cc Merge pull request #59 from phjlljp/feat/per-project-env-config
feat: add per-project .env config support
2026-03-09 21:55:51 -07:00
Matt Van Horn b1a0e2bcfc Merge pull request #57 from phjlljp/feat/smoke-tests-edge-cases
test: add smoke tests and edge case coverage
2026-03-09 21:55:44 -07:00
Matt Van Horn 75e4b8e2cd Merge pull request #56 from phjlljp/feat/unit-tests-untested-modules
test: add unit tests for untested modules
2026-03-09 21:55:42 -07:00
Matt Van Horn 25e27bdade fix(skill): unquote $ARGUMENTS and add marketplace plugin path
- Remove double quotes around $ARGUMENTS so argparse can parse flags
  like --deep, --store separately instead of as part of the topic string.
  Fixes #61.

- Add ~/.claude/plugins/marketplaces/last30days-skill to the path
  discovery loop so marketplace installs can find scripts/.
  Fixes #54.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:55:24 -07:00
P 3e3615e2c8 feat: add per-project .env config support
Add per-project configuration via .claude/last30days.env, discovered by
walking up from cwd. Uses the same .env format as the existing global
config — no new parsers or formats.

Priority (highest wins):
  1. Environment variables
  2. .claude/last30days.env (per-project)
  3. ~/.config/last30days/.env (global)

Also adds file permission checking — warns to stderr if config files
are readable by other users (should be chmod 600).

Includes tests for discovery, precedence, source tracking, and
permission warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:48:23 -04:00
P 9a58fe6481 feat: add SessionStart hook for config check
Add a lightweight hook that runs on session start to check if any
API keys are configured. Warns users if no config is found and
checks file permissions on existing config files.

Checks (in order): .claude/last30days.env, ~/.config/last30days/.env,
OPENAI_API_KEY env var, SCRAPECREATORS_API_KEY env var.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:47:36 -04:00
P 4756c20ec0 refactor: match upstream unittest convention
Convert all new tests from bare pytest style to unittest.TestCase
with sys.path.insert, matching the convention used by all existing
tests. Remove pyproject.toml and conftest.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:43:04 -04:00
P 8a9f734d14 test: add smoke tests and edge case coverage
Add end-to-end smoke tests and expand coverage for score and render modules:

- test_smoke.py (new): subprocess tests for --diagnose, --help, --mock,
  and missing topic. Validates exit codes, JSON structure, and source
  detection (HN/Polymarket always available).
- test_score.py: add TestCommentQualityWeight (top_comment_score boost),
  TestInstagramEngagement (basic scoring, views vs likes weighting)
- test_render.py: add TestEnsureOutputDir, TestXrefTag, TestRenderEmptyReport

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:41:58 -04:00
P 0979f506db test: add unit tests for untested modules
Add pytest infrastructure (pyproject.toml, conftest.py) and unit tests
for modules that previously had zero test coverage:

- test_schema_roundtrip.py: to_dict() serialization for all data classes
- test_reddit_enrich.py: URL parsing, thread data parsing, comment filtering
- test_reddit_sc.py: ScrapeCreators Reddit search (query expansion, subreddit discovery)
- test_instagram_sc.py: Instagram relevance scoring, tokenization, depth config

Includes fixtures/reddit_thread_sample.json for reddit_enrich tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:36:52 -04:00
Matt Van Horn 627947fc2c feat(plugin): publish as Claude Code marketplace plugin
Update .claude-plugin/marketplace.json and plugin.json to v2.9.5 with
full metadata. Add plugin install instructions to README as the
recommended install method. The repo root serves as both the marketplace
and the plugin - skills/last30days/SKILL.md (symlink) is discovered
automatically.

Users can now install with:
  /plugin marketplace add mvanhorn/last30days-skill
  /plugin install last30days@last30days-skill

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:09:33 -07:00
Matt Van Horn 2f16ff1ee8 feat(gemini): add Gemini CLI extension support
Add gemini-extension.json manifest with correct array-format settings,
symlink skills/last30days/SKILL.md to root SKILL.md for Gemini skill
discovery, add Gemini install paths to bash for-loop in both main and
open variant, and add Gemini CLI install instructions to README.

Incorporates the good parts of PR #53 (manifest, paths, README) while
avoiding duplicate SKILL.md, tool name scattering, and allowed-tools
pollution that would have created maintenance issues.

Closes #45

Co-Authored-By: Alex Ferrari <alex@thealexferrari.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 12:07:55 -07:00
Matt Van Horn 8f7fb5a7fe fix(release): v2.9.5 - remove re-introduced Save Research section
PR merges on March 7 (PR #48 Xiaohongshu, upstream merge) regressed
SKILL.md by re-introducing the "Save Research to Documents" section
that v2.9.4 removed. Those branches were forked before v2.9.4 and
brought the old content back via merge resolution.

Fixes: remove save section, restore --save-dir flag on bash command,
update agent mode line, add tool-call guard to STOP instruction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 11:55:35 -07:00
Matt Van Horn 4503da7920 docs: add perpetual monitoring mode plan (not building yet)
Explored adding scheduled re-runs and cumulative intelligence to
last30days. Concluded that Claude Code's session-scoped scheduling
(CronCreate/loop) can't support true perpetual monitoring since
jobs die when the terminal closes. Plan documents the architecture,
what exists, and why we're waiting for persistent background agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 09:37:18 -07:00
Matt Van Horn 28dff6e7b2 Merge remote-tracking branch 'upstream/main' 2026-03-07 16:36:15 -08:00
Matt Van Horn 32992834ee Merge PR #48: feat: add Xiaohongshu source + Reddit public fallback
- Xiaohongshu search via local MCP service (opt-in, zero impact if service not running)
- Reddit public JSON fallback (works with zero API keys)
- Reddit priority: ScrapeCreators -> OpenAI -> public fallback
- Updated env.py: Reddit always available via public fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 16:11:35 -08:00
Matt Van Horn 7dd8379c61 Merge origin/main into feat/xiaohongshu-reddit-public-fallback
Resolve conflicts between ScrapeCreators Reddit (main) and
public Reddit fallback (PR #48). Priority: ScrapeCreators ->
OpenAI -> public Reddit fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 16:08:20 -08:00
Matt Van Horn 408ac148ec Merge pull request #50 from mark-c4r/add-entity-extract-tests
test: add tests for entity_extract module
2026-03-07 15:59:32 -08:00
Matt Van Horn c9559252cd Merge pull request #52 from 04cb/fix/missing-metadata-files
Fix missing metadata files in skill upload bundle
2026-03-07 15:59:11 -08:00
04cb f70370a6f4 Fix missing metadata files in skill upload bundle 2026-03-07 18:10:44 +08:00
Matt Van Horn fad26d41fd fix: improve ClawHub security scan result
- Remove prompt-injection false positive ("you are now" → "treat yourself as")
- Declare AUTH_TOKEN and CT0 in frontmatter optionalEnv
- Clarify X token access language (no browser session access)
- Add permissions overview block near top of file

Zero functionality changes — metadata and prose only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 18:35:38 -08:00
Matt Van Horn ef1f380cda feat: publish to ClawHub as last30days-official
- Add ClawHub badge and install command to README
- Update SKILL.md: metadata.openclaw canonical key, license/author/repository fields, optionalEnv vars, added instagram/polymarket tags
- Add .clawhubignore to exclude binary assets and dev files from bundle

Published: https://clawhub.ai/skills/last30days-official

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 18:08:14 -08:00
Matt Van Horn e690d61a12 fix: append -raw suffix to saved research filenames
e.g. sam-altman-raw.md instead of sam-altman.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
v2.9.4
2026-03-06 16:56:03 -08:00
Matt Van Horn 6d5acb9121 feat(release): v2.9.4 - move save into Python script, zero post-invitation noise
Add --save-dir flag to last30days.py that saves raw research output
during the existing script run. Remove entire "Save Research to
Documents" section from SKILL.md (~45 lines). No more extra tool
calls, no (No output), no multi-minute cogitation after invitation.

Tested: --mock confirms file creation and duplicate date suffixing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:27:53 -08:00
Matt Van Horn 28d223e43c fix(release): v2.9.3 - foreground save, fix hallucinated user messages
CRITICAL: run_in_background callbacks caused model to re-engage after
save, hallucinate fake "Human:" messages, and generate unsolicited
multi-paragraph responses. Switch to foreground cat > heredoc which
executes sub-second with no callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:50:17 -08:00
Matt Van Horn 471badd329 fix(release): v2.9.2 - silent save, no follow-up text after background save
- Background Bash heredoc instead of Write tool
- Suppress response text on save completion
- 📎 footer line replaces verbose confirmation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:41:55 -08:00
Matt Van Horn 5b1636f94f fix: merge upstream save fix (background Bash instead of Write tool)
Resolves merge conflict, keeping upstream's approach:
- Background heredoc save instead of Write tool
- Adds 📎 footer line
- No more "Wrote N lines..." cluttering output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:59:06 -08:00
Matt Van Horn 18f6273f7c fix: save research silently via background Bash, not Write tool
The Write tool displays "Wrote N lines..." after the invitation,
ruining the end-of-run experience. Now saves via background Bash
with a subtle 📎 footer line in the invitation text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:34:22 -08:00
Matt Van Horn 9950d01ab4 fix: save research silently via background Bash, not Write tool
The Write tool displays "Wrote N lines..." after the invitation,
ruining the end-of-run experience. Now saves via background Bash
with a subtle 📎 footer line in the invitation text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:34:11 -08:00
Matt Van Horn cc774d5e69 feat(release): v2.9.1 - auto-save research to ~/Documents/Last30Days/
Sync from public repo. Every run now saves the complete briefing as a
topic-named .md file to ~/Documents/Last30Days/. Credit @devin_explores.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:50:43 -08:00
Matt Van Horn 8cbbe87c3e docs: add v2.9.1 auto-save note to README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:27:18 -08:00
Matt Van Horn 49d993b162 feat(release): v2.9.1 - auto-save research to ~/Documents/Last30Days/
Bump version to 2.9.1, update changelog and release notes.
Credit @devin_explores for inspiring the feature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
v2.9.1
2026-03-05 19:15:42 -08:00
Matt Van Horn 6afc094bb1 Merge pull request #51 from mvanhorn/feat/auto-save-documents
feat(skill): auto-save research results to ~/Documents/Last30Days/
2026-03-05 19:12:56 -08:00
Matt Van Horn f6a1769e35 feat(skill): auto-save research results to ~/Documents/Last30Days/
Every run now automatically saves the complete briefing (synthesis,
stats, follow-up suggestions) as a topic-named .md file in the user's
Documents folder. Agent mode also saves. No Python script changes -
this is purely a SKILL.md instruction addition.

Inspired by @devin_explores manually saving results to build a
personal research library.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:06:27 -08:00
Matt Van Horn 4d35b53eab docs: v2.9.0 release — ScrapeCreators Reddit default, top comments, smart discovery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
v2.9.0
2026-03-05 18:03:40 -08:00
Matt Van Horn 2247800003 chore: clean up Reddit log prefix, mark plan tasks complete 2026-03-05 18:01:24 -08:00