--- title: "feat: Merge OpenClaw variant into main repo" type: feat date: 2026-02-14 --- # feat: Merge OpenClaw Variant into Main Repo ## Overview Consolidate the `last30days-openclaw` project into `last30days-skill-private` so there's one unified Python engine powering both the main skill (Claude Code / Codex) and an "open" variant with watchlist, briefing, history, and built-in web search. The open variant also gets YouTube and Bird CLI — features the main project already has but openclaw was built before they existed. ## Problem Statement / Motivation Right now there are two separate repos with diverging codebases: - **`last30days-skill-private`** (main, Feb 14) — YouTube, vendored Bird, better scoring/normalization, Codex compat. But no built-in web search APIs and no persistence layer. - **`last30days-openclaw`** (Feb 10) — SQLite store, watchlist, briefings, 3 web search backends (Parallel AI, Brave, OpenRouter). But frozen without YouTube or latest engine improvements. They share ~80% of the same `scripts/lib/` files but are drifting apart. Maintaining two codebases is unsustainable. **Goal:** One repo, one Python engine, two SKILL.md variants. Install once, works everywhere. ## Proposed Solution Use `last30days-skill-private` as the base (it's 4 days newer with better code) and port the OpenClaw-exclusive features in: ### What gets ported from OpenClaw | File | What it does | Destination | |------|-------------|-------------| | `scripts/store.py` | SQLite research accumulator (WAL, FTS5, dedup) | `scripts/store.py` | | `scripts/watchlist.py` | Topic watchlist CLI (add/remove/list/run) | `scripts/watchlist.py` | | `scripts/briefing.py` | Morning briefing generator (daily/weekly) | `scripts/briefing.py` | | `scripts/lib/brave_search.py` | Brave Search API (free tier, 2K/mo) | `scripts/lib/brave_search.py` | | `scripts/lib/parallel_search.py` | Parallel AI search (LLM-optimized) | `scripts/lib/parallel_search.py` | | `scripts/lib/openrouter_search.py` | OpenRouter/Sonar Pro search | `scripts/lib/openrouter_search.py` | | `references/research.md` | One-shot research instructions | `variants/open/references/research.md` | | `references/watchlist.md` | Watchlist mode instructions | `variants/open/references/watchlist.md` | | `references/briefing.md` | Briefing mode instructions | `variants/open/references/briefing.md` | | `references/history.md` | History query instructions | `variants/open/references/history.md` | ### What gets upgraded in the ported code - **`store.py`**: No changes needed — it's self-contained SQLite, works as-is - **`watchlist.py`**: Remove OpenClaw cron-specific code, make cron setup generic (launchd on macOS, systemd on Linux, or manual cron) - **`briefing.py`**: No changes needed - **`scripts/lib/env.py`**: Merge OpenClaw's web search key support (`PARALLEL_API_KEY`, `BRAVE_API_KEY`, `OPENROUTER_API_KEY`) and `has_web_search_keys()` / `get_web_search_source()` functions into main's env.py. Drop the OpenClaw config loader (`~/.openclaw/openclaw.json`) — just use env vars and `~/.config/last30days/.env` - **`scripts/last30days.py`**: Add OpenClaw's `_search_web()` function so the script can do web search natively when API keys are available (instead of always delegating to the assistant) ### What gets DROPPED from OpenClaw | File | Why | |------|-----| | `scripts/cron_setup.py` | Too OpenClaw-platform-specific. Replace with generic scheduling docs. | | OpenClaw config loader in `env.py` | `~/.openclaw/openclaw.json` path is platform-specific. Use env vars instead. | | `.clawhubignore` | OpenClaw marketplace artifact, not needed in unified repo | ### New file: Open variant SKILL.md Create `variants/open/SKILL.md` — the multi-mode skill with command routing: ``` variants/open/ ├── SKILL.md # Router: watch, briefing, history, or one-shot ├── references/ │ ├── research.md # One-shot research instructions │ ├── watchlist.md # Watchlist management instructions │ ├── briefing.md # Briefing mode instructions │ └── history.md # History query instructions └── context.md # Agent memory (user preferences, source quality) ``` The open variant's SKILL.md points to `{baseDir}/scripts/last30days.py` (same engine) but adds the router and reference file system. It also adds the `--store` flag for persistence. ### How YouTube and Bird CLI get added to the open variant They're already in `scripts/lib/youtube_yt.py` and `scripts/lib/vendor/bird/`. The open variant's SKILL.md just needs to mention YouTube in its description and the research.md reference file gets the YouTube stats line in the output format. No code changes needed — the Python engine already supports all four sources. ## Technical Considerations ### File Structure After Merge ``` last30days-skill-private/ ├── SKILL.md # Main skill (Claude Code / Codex) ├── agents/openai.yaml # Codex discovery (existing) ├── variants/ │ └── open/ │ ├── SKILL.md # Open variant with routing │ ├── references/ │ │ ├── research.md │ │ ├── watchlist.md │ │ ├── briefing.md │ │ └── history.md │ └── context.md ├── scripts/ │ ├── last30days.py # Unified engine (+ native web search) │ ├── store.py # SQLite accumulator (from openclaw) │ ├── watchlist.py # Watchlist CLI (from openclaw, genericized) │ ├── briefing.py # Briefing generator (from openclaw) │ └── lib/ │ ├── ... (existing files) │ ├── brave_search.py # NEW from openclaw │ ├── parallel_search.py # NEW from openclaw │ ├── openrouter_search.py # NEW from openclaw │ ├── youtube_yt.py # Existing │ └── vendor/bird/ # Existing └── README.md # Updated with open variant docs ``` ### Installation for open variant users ```bash # Claude Code (main skill — unchanged) git clone https://github.com/mvanhorn/last30days-skill.git ~/.claude/skills/last30days # Open variant (with watchlist, briefings, history) git clone https://github.com/mvanhorn/last30days-skill.git ~/.claude/skills/last30days # Then in Claude Code settings, point skill to variants/open/SKILL.md # OR symlink: ln -sf ~/.claude/skills/last30days/variants/open/SKILL.md ~/.claude/skills/last30days-open/SKILL.md ``` ### env.py merge strategy Main's `env.py` is the base. Add from OpenClaw: - Three new key names: `PARALLEL_API_KEY`, `BRAVE_API_KEY`, `OPENROUTER_API_KEY` - `has_web_search_keys()` function - `get_web_search_source()` function — returns `'parallel'`, `'brave'`, or `'openrouter'` - `get_available_sources()` update to include web-search-capable modes ### last30days.py merge strategy Main's `last30days.py` is the base. Add from OpenClaw: - `_search_web()` function that calls the appropriate web search backend - `--store` CLI flag to persist findings to SQLite - `--diagnose` CLI flag for source availability diagnostics - Web results integration into the existing report pipeline (normalize → score → dedupe → render) Keep main's: - YouTube integration - Phase 2 supplemental search - 3-tier Reddit fallback - Better error handling - Minimum result guarantee ### Portable path resolution (already done) The main SKILL.md already has portable path resolution (from Codex compat work): ```bash for dir in "." "${CLAUDE_PLUGIN_ROOT:-}" "$HOME/.claude/skills/last30days" ... ``` The open variant's SKILL.md uses `{baseDir}` which resolves to the skill root. Both approaches work — we just need to make sure the open variant's references use `{baseDir}` consistently. ## Acceptance Criteria - [x] `scripts/store.py` ported and working (SQLite creates on first use) - [x] `scripts/watchlist.py` ported with generic scheduling (no OpenClaw cron dependency) - [x] `scripts/briefing.py` ported and generates daily/weekly briefings - [x] `scripts/lib/brave_search.py` ported and functional - [x] `scripts/lib/parallel_search.py` ported and functional - [x] `scripts/lib/openrouter_search.py` ported and functional - [x] `scripts/lib/env.py` updated with web search key support - [x] `scripts/last30days.py` has native `_search_web()` + `--store` + `--diagnose` - [x] `variants/open/SKILL.md` exists with command routing (watch, briefing, history, research) - [x] `variants/open/references/*.md` — all 4 reference files ported - [x] Open variant mentions YouTube in description and research output format - [x] Open variant uses same portable path resolution as main - [x] Main SKILL.md behavior is unchanged (zero regressions) - [x] `python3 scripts/last30days.py "test topic" --mock --emit=compact` still works - [x] `python3 scripts/last30days.py "test topic" --diagnose` shows source availability - [x] README documents open variant installation and usage ## Dependencies & Risks | Risk | Likelihood | Mitigation | |------|-----------|------------| | OpenClaw's store.py has import dependencies we don't have | Low | store.py uses only stdlib (sqlite3, json, datetime). Self-contained. | | Web search backends need API keys to test | Medium | Each has a `--mock` or dry-run path. Test with real keys if available, mock otherwise. | | watchlist.py depends on OpenClaw cron API | High | Known — strip cron_setup.py dependency, replace with generic docs for launchd/systemd/crontab. | | Open variant SKILL.md is too long (>500 lines) | Medium | Use reference file pattern (already planned). Router SKILL.md stays under 100 lines. | | env.py merge introduces regressions | Low | Main's env.py is well-tested. Additive changes only — new keys, new functions. | | Two SKILL.md files = maintenance burden | Low | They serve different purposes. Main is simple one-shot. Open adds routing. Core engine is shared. | ## Files to Create/Modify ### New Files - `variants/open/SKILL.md` — Open variant router (~100 lines) - `variants/open/references/research.md` — One-shot research instructions (from openclaw, updated with YouTube) - `variants/open/references/watchlist.md` — Watchlist management instructions (from openclaw) - `variants/open/references/briefing.md` — Briefing mode instructions (from openclaw) - `variants/open/references/history.md` — History query instructions (from openclaw) - `variants/open/context.md` — Agent memory template - `scripts/store.py` — SQLite accumulator (from openclaw, as-is) - `scripts/watchlist.py` — Watchlist CLI (from openclaw, genericized) - `scripts/briefing.py` — Briefing generator (from openclaw, as-is) - `scripts/lib/brave_search.py` — Brave Search API (from openclaw) - `scripts/lib/parallel_search.py` — Parallel AI search (from openclaw) - `scripts/lib/openrouter_search.py` — OpenRouter/Sonar Pro search (from openclaw) ### Modified Files - `scripts/lib/env.py` — Add web search key support (~30 lines added) - `scripts/last30days.py` — Add `_search_web()`, `--store`, `--diagnose` (~80 lines added) - `README.md` — Add open variant section (~20 lines) ### Total scope: ~12 new files (mostly copied), ~130 lines of new code in existing files. ## References ### Internal - OpenClaw plan: `/Users/mvanhorn/last30days-openclaw/docs/plans/2026-02-10-feat-openclaw-last30days-skill-plan.md` (989 lines, comprehensive spec) - Codex compat plan: `docs/plans/2026-02-14-feat-codex-skill-compatibility-plan.md` (portable paths, platform-neutral text) - OpenClaw source: `/Users/mvanhorn/last30days-openclaw/` ### Key files to port - `store.py`: `/Users/mvanhorn/last30days-openclaw/scripts/store.py` (20KB, SQLite with FTS5) - `watchlist.py`: `/Users/mvanhorn/last30days-openclaw/scripts/watchlist.py` (10KB) - `briefing.py`: `/Users/mvanhorn/last30days-openclaw/scripts/briefing.py` (8KB) - `brave_search.py`: `/Users/mvanhorn/last30days-openclaw/scripts/lib/brave_search.py` (6KB) - `parallel_search.py`: `/Users/mvanhorn/last30days-openclaw/scripts/lib/parallel_search.py` (4KB) - `openrouter_search.py`: `/Users/mvanhorn/last30days-openclaw/scripts/lib/openrouter_search.py` (7KB) - `env.py` (openclaw version): `/Users/mvanhorn/last30days-openclaw/scripts/lib/env.py` (9KB — has web search key functions)