last30days: require Python 3.12 in setup flows

This commit is contained in:
Pejman Pour-Moezzi
2026-04-08 14:40:19 -07:00
parent 9c203d3595
commit 57ec92c299
4 changed files with 76 additions and 13 deletions
+24 -6
View File
@@ -65,6 +65,24 @@ metadata:
Research ANY topic across Reddit, X, YouTube, and other sources. Surface what people are actually discussing, recommending, betting on, and debating right now. Research ANY topic across Reddit, X, YouTube, and other sources. Surface what people are actually discussing, recommending, betting on, and debating right now.
## Runtime Preflight
Before running any `last30days.py` command in this skill, resolve a Python 3.12+ interpreter once and keep it in `LAST30DAYS_PYTHON`:
```bash
for py in python3.14 python3.13 python3.12 python3; do
command -v "$py" >/dev/null 2>&1 || continue
"$py" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 12) else 1)' || continue
LAST30DAYS_PYTHON="$py"
break
done
if [ -z "${LAST30DAYS_PYTHON:-}" ]; then
echo "ERROR: last30days v3 requires Python 3.12+. Install python3.12 or python3.13 and rerun." >&2
exit 1
fi
```
## Step 0: First-Run Setup Wizard ## Step 0: First-Run Setup Wizard
**CRITICAL: ALWAYS execute Step 0 BEFORE Step 1, even if the user provided a topic.** If the user typed `/last30days Mercer Island`, you MUST check for FIRST_RUN and present the wizard BEFORE running research. The topic "Mercer Island" is preserved — research runs immediately after the wizard completes. Do NOT skip the wizard because a topic was provided. The wizard takes 10 seconds and only runs once ever. **CRITICAL: ALWAYS execute Step 0 BEFORE Step 1, even if the user provided a topic.** If the user typed `/last30days Mercer Island`, you MUST check for FIRST_RUN and present the wizard BEFORE running research. The topic "Mercer Island" is preserved — research runs immediately after the wizard completes. Do NOT skip the wizard because a topic was provided. The wizard takes 10 seconds and only runs once ever.
@@ -82,7 +100,7 @@ To detect first run: check if `~/.config/last30days/.env` exists. If it does NOT
Run environment detection first: Run environment detection first:
```bash ```bash
python3 "${SKILL_ROOT}/scripts/last30days.py" setup --openclaw "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" setup --openclaw
``` ```
Read the JSON output. It tells you what's already configured. Display a status summary: Read the JSON output. It tells you what's already configured. Display a status summary:
@@ -100,7 +118,7 @@ Detected:
Then for each missing item, offer setup in priority order: Then for each missing item, offer setup in priority order:
1. **ScrapeCreators** (if not configured): "ScrapeCreators adds TikTok and Instagram search (plus a Reddit backup if public Reddit gets rate-limited). 10,000 free calls, no credit card. (No referrals, no kickbacks - we don't get a cut.)" 1. **ScrapeCreators** (if not configured): "ScrapeCreators adds TikTok and Instagram search (plus a Reddit backup if public Reddit gets rate-limited). 10,000 free calls, no credit card. (No referrals, no kickbacks - we don't get a cut.)"
- Option A: "ScrapeCreators via GitHub (recommended)" -- Check if `gh` CLI was detected in the environment detection output above. If gh IS detected: description should say "Registers directly via GitHub CLI in ~2 seconds - no browser needed". Before running the command, display: "Registering via GitHub CLI..." If gh is NOT detected: description should say "Copies a one-time code to your clipboard and opens GitHub to authorize". Before running the command, display: "I'll copy a one-time code to your clipboard and open GitHub. When GitHub asks for a device code, just paste (Cmd+V / Ctrl+V)." Then run `python3 "${SKILL_ROOT}/scripts/last30days.py" setup --github`, parse JSON output. Tries PAT first (if `gh` is installed), falls back to device flow which copies a one-time code to your clipboard and opens your browser. If `status` is `success`, write `SCRAPECREATORS_API_KEY={api_key}` to .env. - Option A: "ScrapeCreators via GitHub (recommended)" -- Check if `gh` CLI was detected in the environment detection output above. If gh IS detected: description should say "Registers directly via GitHub CLI in ~2 seconds - no browser needed". Before running the command, display: "Registering via GitHub CLI..." If gh is NOT detected: description should say "Copies a one-time code to your clipboard and opens GitHub to authorize". Before running the command, display: "I'll copy a one-time code to your clipboard and open GitHub. When GitHub asks for a device code, just paste (Cmd+V / Ctrl+V)." Then run `"${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" setup --github`, parse JSON output. Tries PAT first (if `gh` is installed), falls back to device flow which copies a one-time code to your clipboard and opens your browser. If `status` is `success`, write `SCRAPECREATORS_API_KEY={api_key}` to .env.
- Option B: "I have a key" -- accept paste, write to .env - Option B: "I have a key" -- accept paste, write to .env
- Option C: "Skip for now" - Option C: "Skip for now"
@@ -160,7 +178,7 @@ Options:
Run the setup subcommand: Run the setup subcommand:
```bash ```bash
cd {SKILL_DIR} && python3 scripts/last30days.py setup cd {SKILL_DIR} && "${LAST30DAYS_PYTHON}" scripts/last30days.py setup
``` ```
Show the user the results (what cookies were found, whether yt-dlp was installed). Show the user the results (what cookies were found, whether yt-dlp was installed).
@@ -173,7 +191,7 @@ Want TikTok and Instagram too? ScrapeCreators adds those platforms - 10,000 free
**Call AskUserQuestion:** **Call AskUserQuestion:**
Question: "Want to add TikTok, Instagram, and Reddit backup via ScrapeCreators? (We don't get a cut.)" Question: "Want to add TikTok, Instagram, and Reddit backup via ScrapeCreators? (We don't get a cut.)"
Options: Options:
- "ScrapeCreators via GitHub (fastest, recommended)" - If gh_available: description should say "Registers directly via GitHub CLI in ~2 seconds - no browser needed". If NOT gh_available: description should say "Copies a one-time code to your clipboard and opens GitHub to authorize". After the user selects this option: If gh_available, display "Registering via GitHub CLI..." before running the command. If NOT gh_available, display "I'll copy a one-time code to your clipboard and open GitHub. When GitHub asks for a device code, just paste (Cmd+V on Mac, Ctrl+V on Windows/Linux)." Then run `cd {SKILL_DIR} && python3 scripts/last30days.py setup --github` via Bash with a 5-minute timeout. This tries PAT auth first (if `gh` CLI is installed, zero browser needed), then falls back to GitHub device flow which copies a one-time code to your clipboard and opens GitHub in your browser. Parse the JSON stdout. If `status` is `success`, write `SCRAPECREATORS_API_KEY={api_key}` to `~/.config/last30days/.env`. If `method` is `pat`, show: "You're in! Registered via GitHub CLI - zero browser needed. 10,000 free calls. TikTok, Instagram, and Reddit backup are now active." If `method` is `device` and `clipboard_ok` is true, show: "You're in! (The authorization code was copied to your clipboard automatically.) 10,000 free calls. TikTok, Instagram, and Reddit backup are now active." If `method` is `device` and `clipboard_ok` is false, show: "You're in! 10,000 free calls. TikTok, Instagram, and Reddit backup are now active." If `status` is `timeout` or `error`, show: "GitHub auth didn't complete. No worries - you can sign up at scrapecreators.com instead or try again later." Then offer the web signup option. - "ScrapeCreators via GitHub (fastest, recommended)" - If gh_available: description should say "Registers directly via GitHub CLI in ~2 seconds - no browser needed". If NOT gh_available: description should say "Copies a one-time code to your clipboard and opens GitHub to authorize". After the user selects this option: If gh_available, display "Registering via GitHub CLI..." before running the command. If NOT gh_available, display "I'll copy a one-time code to your clipboard and open GitHub. When GitHub asks for a device code, just paste (Cmd+V on Mac, Ctrl+V on Windows/Linux)." Then run `cd {SKILL_DIR} && "${LAST30DAYS_PYTHON}" scripts/last30days.py setup --github` via Bash with a 5-minute timeout. This tries PAT auth first (if `gh` CLI is installed, zero browser needed), then falls back to GitHub device flow which copies a one-time code to your clipboard and opens GitHub in your browser. Parse the JSON stdout. If `status` is `success`, write `SCRAPECREATORS_API_KEY={api_key}` to `~/.config/last30days/.env`. If `method` is `pat`, show: "You're in! Registered via GitHub CLI - zero browser needed. 10,000 free calls. TikTok, Instagram, and Reddit backup are now active." If `method` is `device` and `clipboard_ok` is true, show: "You're in! (The authorization code was copied to your clipboard automatically.) 10,000 free calls. TikTok, Instagram, and Reddit backup are now active." If `method` is `device` and `clipboard_ok` is false, show: "You're in! 10,000 free calls. TikTok, Instagram, and Reddit backup are now active." If `status` is `timeout` or `error`, show: "GitHub auth didn't complete. No worries - you can sign up at scrapecreators.com instead or try again later." Then offer the web signup option.
- "Open scrapecreators.com (Google sign-in)" - run `open https://scrapecreators.com` via Bash to open in the user's browser. Then ask them to paste the API key they get. When they paste it, write SCRAPECREATORS_API_KEY={key} to ~/.config/last30days/.env - "Open scrapecreators.com (Google sign-in)" - run `open https://scrapecreators.com` via Bash to open in the user's browser. Then ask them to paste the API key they get. When they paste it, write SCRAPECREATORS_API_KEY={key} to ~/.config/last30days/.env
- "I have a key" - accept the key, write to .env - "I have a key" - accept the key, write to .env
- "Skip for now" - proceed without ScrapeCreators - "Skip for now" - proceed without ScrapeCreators
@@ -541,7 +559,7 @@ When the user asks "X vs Y", run ONE research pass with a comparison-optimized p
**Single pass with entity-aware subqueries:** **Single pass with entity-aware subqueries:**
```bash ```bash
python3 "${SKILL_ROOT}/scripts/last30days.py" "{TOPIC_A} vs {TOPIC_B}" --emit=compact --save-dir=~/Documents/Last30Days --save-suffix=v3 --plan 'COMPARISON_PLAN_JSON' --x-handle={TOPIC_A_HANDLE} --x-related={TOPIC_B_HANDLE},{COMPANY_A_HANDLE},{COMPANY_B_HANDLE},{COMMENTATOR_HANDLES} --subreddits={RESOLVED_SUBREDDITS} --tiktok-hashtags={RESOLVED_HASHTAGS} --tiktok-creators={RESOLVED_TIKTOK_CREATORS} --ig-creators={RESOLVED_IG_CREATORS} "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" "{TOPIC_A} vs {TOPIC_B}" --emit=compact --save-dir=~/Documents/Last30Days --save-suffix=v3 --plan 'COMPARISON_PLAN_JSON' --x-handle={TOPIC_A_HANDLE} --x-related={TOPIC_B_HANDLE},{COMPANY_A_HANDLE},{COMPANY_B_HANDLE},{COMMENTATOR_HANDLES} --subreddits={RESOLVED_SUBREDDITS} --tiktok-hashtags={RESOLVED_HASHTAGS} --tiktok-creators={RESOLVED_TIKTOK_CREATORS} --ig-creators={RESOLVED_IG_CREATORS}
``` ```
**The `--plan` JSON for comparisons should include 3-4 subqueries:** **The `--plan` JSON for comparisons should include 3-4 subqueries:**
@@ -730,7 +748,7 @@ if [ -z "${SKILL_ROOT:-}" ]; then
exit 1 exit 1
fi fi
python3 "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --emit=compact --save-dir=~/Documents/Last30Days --save-suffix=v3 "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --emit=compact --save-dir=~/Documents/Last30Days --save-suffix=v3
``` ```
**If you ran Steps 0.55 and 0.75 (agent planning), add these flags:** **If you ran Steps 0.55 and 0.75 (agent planning), add these flags:**
+19
View File
@@ -14,6 +14,25 @@ import sys
import threading import threading
from pathlib import Path from pathlib import Path
MIN_PYTHON = (3, 12)
def ensure_supported_python(version_info: tuple[int, int, int] | object | None = None) -> None:
if version_info is None:
version_info = sys.version_info
major, minor, micro = tuple(version_info[:3])
if (major, minor) >= MIN_PYTHON:
return
sys.stderr.write(
"last30days v3 requires Python 3.12+.\n"
f"Detected Python {major}.{minor}.{micro}.\n"
"Install and use python3.12 or python3.13, then rerun this command.\n"
)
raise SystemExit(1)
ensure_supported_python()
SCRIPT_DIR = Path(__file__).parent.resolve() SCRIPT_DIR = Path(__file__).parent.resolve()
sys.path.insert(0, str(SCRIPT_DIR)) sys.path.insert(0, str(SCRIPT_DIR))
+19 -7
View File
@@ -45,23 +45,35 @@ if [ -z "${SKILL_ROOT:-}" ]; then
echo "ERROR: Could not find scripts/last30days.py" >&2 echo "ERROR: Could not find scripts/last30days.py" >&2
exit 1 exit 1
fi fi
for py in python3.14 python3.13 python3.12 python3; do
command -v "$py" >/dev/null 2>&1 || continue
"$py" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 12) else 1)' || continue
LAST30DAYS_PYTHON="$py"
break
done
if [ -z "${LAST30DAYS_PYTHON:-}" ]; then
echo "ERROR: last30days v3 requires Python 3.12+. Install python3.12 or python3.13 and rerun." >&2
exit 1
fi
``` ```
## Default command ## Default command
```bash ```bash
python3 "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --emit=compact "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --emit=compact
``` ```
## Useful commands ## Useful commands
```bash ```bash
python3 "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --emit=json "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --emit=json
python3 "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --quick "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --quick
python3 "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --deep "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --deep
python3 "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --search=reddit,x,grounding "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --search=reddit,x,grounding
python3 "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --store "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" $ARGUMENTS --store
python3 "${SKILL_ROOT}/scripts/last30days.py" --diagnose "${LAST30DAYS_PYTHON}" "${SKILL_ROOT}/scripts/last30days.py" --diagnose
``` ```
## Runtime expectations ## Runtime expectations
+14
View File
@@ -77,6 +77,20 @@ class CliV3Tests(unittest.TestCase):
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
cli.parse_search_flag(" , ") cli.parse_search_flag(" , ")
def test_ensure_supported_python_rejects_old_interpreter_with_actionable_error(self):
stderr = io.StringIO()
with redirect_stderr(stderr):
with self.assertRaises(SystemExit) as exc:
cli.ensure_supported_python((3, 9, 6))
self.assertEqual(1, exc.exception.code)
message = stderr.getvalue()
self.assertIn("last30days v3 requires Python 3.12+", message)
self.assertIn("Detected Python 3.9.6", message)
self.assertIn("python3.12", message)
def test_ensure_supported_python_allows_supported_interpreter(self):
cli.ensure_supported_python((3, 12, 0))
def test_missing_sources_for_promo_prefers_reddit_x_then_web(self): def test_missing_sources_for_promo_prefers_reddit_x_then_web(self):
self.assertEqual( self.assertEqual(
"both", "both",