luckiest-co 1.0.47 → 1.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/ab-testing/CHANGELOG.md +5 -0
- package/skills/ab-testing/SKILL.md +12 -9
- package/skills/ab-testing/evals/evals.json +18 -7
- package/skills/ab-testing/references/sample-size-guide.md +2 -2
- package/skills/ads/CHANGELOG.md +5 -0
- package/skills/ads/SKILL.md +4 -4
- package/skills/ai-seo/CHANGELOG.md +7 -0
- package/skills/ai-seo/SKILL.md +27 -20
- package/skills/analytics/CHANGELOG.md +6 -0
- package/skills/analytics/SKILL.md +4 -3
- package/skills/analytics/evals/evals.json +27 -0
- package/skills/analytics/references/ga4-implementation.md +7 -3
- package/skills/analytics/references/gtm-implementation.md +8 -2
- package/skills/aso/CHANGELOG.md +5 -0
- package/skills/aso/SKILL.md +4 -4
- package/skills/aso/evals/evals.json +15 -2
- package/skills/aso/references/benchmarks.md +4 -2
- package/skills/aso/references/google-play-specs.md +5 -1
- package/skills/brainstorm/SKILL.md +59 -16
- package/skills/brainstorm/builder-check-prompt.md +23 -0
- package/skills/churn-prevention/CHANGELOG.md +5 -0
- package/skills/churn-prevention/SKILL.md +3 -3
- package/skills/churn-prevention/evals/evals.json +14 -0
- package/skills/churn-prevention/references/dunning-playbook.md +6 -1
- package/skills/coder-brainstorming/CHANGELOG.md +10 -0
- package/skills/coder-brainstorming/SKILL.md +62 -20
- package/skills/coder-brainstorming/builder-check-prompt.md +23 -0
- package/skills/coder-brainstorming/scripts/server.cjs +1 -1
- package/skills/coder-debugging/CHANGELOG.md +4 -0
- package/skills/coder-debugging/SKILL.md +5 -5
- package/skills/coder-requesting-code-review/CHANGELOG.md +4 -0
- package/skills/coder-requesting-code-review/SKILL.md +4 -4
- package/skills/coder-subagent-driven-development/CHANGELOG.md +4 -0
- package/skills/coder-subagent-driven-development/SKILL.md +3 -3
- package/skills/coder-subagent-driven-development/scripts/sdd-workspace +3 -1
- package/skills/cold-email/CHANGELOG.md +7 -0
- package/skills/cold-email/SKILL.md +27 -7
- package/skills/cold-email/evals/evals.json +30 -0
- package/skills/cold-email/references/benchmarks.md +15 -11
- package/skills/cold-email/references/deliverability.md +110 -0
- package/skills/cold-email/references/linkedin-outreach.md +91 -0
- package/skills/cold-email/references/multichannel-cadence.md +79 -0
- package/skills/cold-email/references/outbound-plays.md +88 -0
- package/skills/cold-email/references/personalization.md +11 -0
- package/skills/cold-email/references/reply-handling.md +65 -0
- package/skills/competitor-profiling/CHANGELOG.md +5 -0
- package/skills/competitor-profiling/SKILL.md +4 -4
- package/skills/content-strategy/CHANGELOG.md +5 -0
- package/skills/content-strategy/SKILL.md +5 -3
- package/skills/copy-editing/CHANGELOG.md +5 -0
- package/skills/copy-editing/SKILL.md +5 -5
- package/skills/copy-editing/references/content-refresh.md +33 -0
- package/skills/cro/CHANGELOG.md +5 -0
- package/skills/cro/SKILL.md +14 -3
- package/skills/cro/references/form.md +1 -1
- package/skills/directory-submissions/CHANGELOG.md +5 -0
- package/skills/directory-submissions/SKILL.md +13 -4
- package/skills/directory-submissions/evals/evals.json +15 -1
- package/skills/directory-submissions/references/directory-list.md +2 -4
- package/skills/emails/CHANGELOG.md +5 -0
- package/skills/emails/SKILL.md +14 -4
- package/skills/launch/CHANGELOG.md +7 -0
- package/skills/launch/SKILL.md +8 -4
- package/skills/launch/evals/evals.json +29 -0
- package/skills/launch/references/shipped-changes.md +84 -0
- package/skills/launch/references/site-launch-qa.md +55 -0
- package/skills/lead-magnets/CHANGELOG.md +5 -0
- package/skills/lead-magnets/SKILL.md +6 -4
- package/skills/lead-magnets/evals/evals.json +18 -0
- package/skills/lead-magnets/references/content-to-skill-corpus-design.md +89 -0
- package/skills/lead-magnets/references/content-to-skill-release.md +71 -0
- package/skills/lead-magnets/references/content-to-skill.md +108 -0
- package/skills/marketing-plan/CHANGELOG.md +5 -0
- package/skills/marketing-plan/SKILL.md +3 -3
- package/skills/marketing-plan/evals/evals.json +13 -0
- package/skills/marketing-plan/references/budget-planning.md +26 -17
- package/skills/model-router/CHANGELOG.md +6 -0
- package/skills/model-router/SKILL.md +3 -3
- package/skills/model-router/scripts/route.py +1 -1
- package/skills/model-router/scripts/test_route.py +5 -0
- package/skills/pricing/CHANGELOG.md +5 -0
- package/skills/pricing/SKILL.md +4 -4
- package/skills/pricing/evals/evals.json +12 -0
- package/skills/pricing/references/research-methods.md +8 -4
- package/skills/programmatic-seo/CHANGELOG.md +5 -0
- package/skills/programmatic-seo/SKILL.md +8 -4
- package/skills/programmatic-seo/evals/evals.json +13 -0
- package/skills/programmatic-seo/references/implementation-platforms.md +37 -0
- package/skills/prospecting/CHANGELOG.md +7 -0
- package/skills/prospecting/SKILL.md +27 -13
- package/skills/prospecting/evals/evals.json +41 -0
- package/skills/prospecting/references/account-research.md +59 -0
- package/skills/prospecting/references/b2b-prospecting.md +1 -1
- package/skills/prospecting/references/compliance.md +44 -6
- package/skills/prospecting/references/data-sources.md +13 -15
- package/skills/prospecting/references/enrichment-playbook.md +61 -0
- package/skills/prospecting/references/saas-prospecting.md +1 -1
- package/skills/prospecting/references/signal-plays.md +99 -0
- package/skills/prospecting/references/sourcing-and-enrichment.md +89 -0
- package/skills/referrals/CHANGELOG.md +5 -0
- package/skills/referrals/SKILL.md +10 -4
- package/skills/referrals/evals/evals.json +99 -0
- package/skills/referrals/references/affiliate-operations.md +64 -0
- package/skills/referrals/references/affiliate-programs.md +2 -2
- package/skills/referrals/references/program-examples.md +14 -6
- package/skills/referrals/references/review-platform-rules.md +36 -0
- package/skills/referrals/references/review-program-operations.md +62 -0
- package/skills/referrals/references/review-programs.md +96 -0
- package/skills/revops/CHANGELOG.md +5 -0
- package/skills/revops/SKILL.md +10 -6
- package/skills/revops/evals/evals.json +13 -0
- package/skills/revops/references/automation-playbooks.md +1 -1
- package/skills/revops/references/inbound-routing.md +43 -0
- package/skills/revops/references/lifecycle-definitions.md +31 -0
- package/skills/sales-enablement/CHANGELOG.md +5 -0
- package/skills/sales-enablement/SKILL.md +6 -4
- package/skills/sales-enablement/evals/evals.json +14 -0
- package/skills/sales-enablement/references/cold-call-scripts.md +68 -0
- package/skills/schema/CHANGELOG.md +5 -0
- package/skills/schema/SKILL.md +20 -8
- package/skills/schema/evals/evals.json +40 -0
- package/skills/schema/references/schema-examples.md +9 -15
- package/skills/seo-audit/CHANGELOG.md +5 -0
- package/skills/seo-audit/SKILL.md +10 -16
- package/skills/seo-audit/evals/evals.json +68 -0
- package/skills/seo-audit/references/international-seo.md +9 -11
- package/skills/seo-audit/references/local-seo-pages-and-measurement.md +39 -0
- package/skills/seo-audit/references/local-seo-profile-and-citations.md +41 -0
- package/skills/seo-audit/references/local-seo.md +86 -0
- package/skills/seo-audit/references/rankings-push.md +62 -0
- package/skills/seo-audit/references/title-tags.md +89 -0
- package/skills/signup/CHANGELOG.md +5 -0
- package/skills/signup/SKILL.md +7 -3
- package/skills/site-architecture/CHANGELOG.md +5 -0
- package/skills/site-architecture/SKILL.md +5 -4
- package/skills/site-architecture/evals/evals.json +25 -0
- package/skills/site-architecture/references/platforms-and-migration.md +84 -0
- package/skills/social/CHANGELOG.md +5 -0
- package/skills/social/SKILL.md +4 -4
- package/skills/trends/.skillignore +3 -0
- package/skills/trends/CHANGELOG.md +20 -0
- package/skills/trends/SKILL.md +12 -8
- package/skills/trends/references/agent-mode.md +36 -0
- package/skills/trends/references/category-peers.md +38 -0
- package/skills/trends/references/comparison.md +227 -0
- package/skills/trends/references/competitors.md +29 -0
- package/skills/trends/references/discovery.md +90 -0
- package/skills/trends/references/engine-contract.md +111 -1889
- package/skills/trends/references/followups.md +112 -0
- package/skills/trends/references/grok-bot-x.md +55 -0
- package/skills/trends/references/hiring-signals.md +19 -0
- package/skills/trends/references/library-queue.md +33 -0
- package/skills/trends/references/recommendations.md +86 -0
- package/skills/trends/references/research-runbook.md +781 -0
- package/skills/trends/references/runtime.md +165 -0
- package/skills/trends/references/save-html-brief.md +39 -20
- package/skills/trends/references/setup-wizard.md +253 -0
- package/skills/trends/references/synthesis.md +289 -0
- package/skills/trends/scripts/box_chrome_login.py +209 -0
- package/skills/trends/scripts/briefing.py +9 -17
- package/skills/trends/scripts/last30days.py +3247 -280
- package/skills/trends/scripts/lib/agentcookie.py +196 -0
- package/skills/trends/scripts/lib/amazon.py +877 -0
- package/skills/trends/scripts/lib/arxiv.py +42 -8
- package/skills/trends/scripts/lib/backends.py +769 -0
- package/skills/trends/scripts/lib/bird_x.py +340 -57
- package/skills/trends/scripts/lib/bluesky.py +84 -10
- package/skills/trends/scripts/lib/bounded_get.py +156 -0
- package/skills/trends/scripts/lib/brightdata.py +291 -0
- package/skills/trends/scripts/lib/categories.py +7 -1
- package/skills/trends/scripts/lib/chrome_cdp.py +455 -0
- package/skills/trends/scripts/lib/chrome_cookies.py +132 -39
- package/skills/trends/scripts/lib/cluster.py +31 -54
- package/skills/trends/scripts/lib/competitors.py +10 -1
- package/skills/trends/scripts/lib/cookie_extract.py +221 -44
- package/skills/trends/scripts/lib/cookie_paths.py +27 -0
- package/skills/trends/scripts/lib/corpus.py +439 -0
- package/skills/trends/scripts/lib/devto.py +313 -0
- package/skills/trends/scripts/lib/discovery_handoff.py +988 -0
- package/skills/trends/scripts/lib/doctor.py +2166 -0
- package/skills/trends/scripts/lib/dripstack.py +203 -0
- package/skills/trends/scripts/lib/entity_extract.py +37 -0
- package/skills/trends/scripts/lib/env.py +1092 -138
- package/skills/trends/scripts/lib/fanout.py +11 -1
- package/skills/trends/scripts/lib/feed.py +61 -0
- package/skills/trends/scripts/lib/fetch_memo.py +135 -0
- package/skills/trends/scripts/lib/freshness.py +566 -0
- package/skills/trends/scripts/lib/fusion.py +314 -28
- package/skills/trends/scripts/lib/github.py +438 -24
- package/skills/trends/scripts/lib/grok_x.py +1237 -0
- package/skills/trends/scripts/lib/grounding.py +129 -11
- package/skills/trends/scripts/lib/hackernews.py +61 -4
- package/skills/trends/scripts/lib/health.py +373 -0
- package/skills/trends/scripts/lib/hosted.py +333 -0
- package/skills/trends/scripts/lib/html_publish.py +34 -0
- package/skills/trends/scripts/lib/html_render.py +169 -3
- package/skills/trends/scripts/lib/http.py +1215 -70
- package/skills/trends/scripts/lib/instagram.py +132 -9
- package/skills/trends/scripts/lib/jobs.py +4 -2
- package/skills/trends/scripts/lib/library.py +308 -0
- package/skills/trends/scripts/lib/library_index.py +477 -0
- package/skills/trends/scripts/lib/linkedin.py +271 -24
- package/skills/trends/scripts/lib/log.py +5 -2
- package/skills/trends/scripts/lib/meta_ads.py +982 -0
- package/skills/trends/scripts/lib/normalize.py +382 -28
- package/skills/trends/scripts/lib/parallel_mcp.py +234 -0
- package/skills/trends/scripts/lib/permission_preflight.py +42 -3
- package/skills/trends/scripts/lib/perplexity.py +1080 -468
- package/skills/trends/scripts/lib/pipeline.py +5079 -211
- package/skills/trends/scripts/lib/planner.py +396 -53
- package/skills/trends/scripts/lib/polymarket.py +285 -23
- package/skills/trends/scripts/lib/prescriptions.py +432 -0
- package/skills/trends/scripts/lib/private_files.py +21 -0
- package/skills/trends/scripts/lib/providers.py +231 -10
- package/skills/trends/scripts/lib/quality_nudge.py +122 -37
- package/skills/trends/scripts/lib/reddit.py +177 -12
- package/skills/trends/scripts/lib/reddit_arctic.py +146 -5
- package/skills/trends/scripts/lib/reddit_keyless.py +121 -60
- package/skills/trends/scripts/lib/reddit_listing.py +215 -14
- package/skills/trends/scripts/lib/reddit_public.py +12 -13
- package/skills/trends/scripts/lib/reddit_search.py +304 -0
- package/skills/trends/scripts/lib/reddit_shreddit.py +51 -7
- package/skills/trends/scripts/lib/registers.py +134 -0
- package/skills/trends/scripts/lib/relevance.py +17 -0
- package/skills/trends/scripts/lib/render.py +2161 -271
- package/skills/trends/scripts/lib/rerank.py +623 -55
- package/skills/trends/scripts/lib/resolve.py +50 -1
- package/skills/trends/scripts/lib/safari_cookies.py +42 -15
- package/skills/trends/scripts/lib/schema.py +782 -1
- package/skills/trends/scripts/lib/setup_wizard.py +779 -120
- package/skills/trends/scripts/lib/signals.py +206 -7
- package/skills/trends/scripts/lib/stocktwits.py +374 -0
- package/skills/trends/scripts/lib/subproc.py +307 -53
- package/skills/trends/scripts/lib/techmeme.py +89 -64
- package/skills/trends/scripts/lib/telegram.py +317 -0
- package/skills/trends/scripts/lib/tiktok.py +13 -9
- package/skills/trends/scripts/lib/topic_shape.py +340 -0
- package/skills/trends/scripts/lib/transcribe.py +12 -3
- package/skills/trends/scripts/lib/trustpilot.py +256 -5
- package/skills/trends/scripts/lib/truthsocial.py +9 -1
- package/skills/trends/scripts/lib/ui.py +6 -0
- package/skills/trends/scripts/lib/usage.py +132 -0
- package/skills/trends/scripts/lib/web_search_keyless.py +63 -4
- package/skills/trends/scripts/lib/windows_job.py +138 -0
- package/skills/trends/scripts/lib/x_api.py +824 -0
- package/skills/trends/scripts/lib/x_envelope.py +733 -0
- package/skills/trends/scripts/lib/x_judge.py +292 -0
- package/skills/trends/scripts/lib/xai_x.py +26 -3
- package/skills/trends/scripts/lib/xquik.py +89 -15
- package/skills/trends/scripts/lib/xurl_x.py +284 -91
- package/skills/trends/scripts/lib/youtube_yt.py +570 -119
- package/skills/trends/scripts/setup-keychain.sh +12 -4
- package/skills/trends/scripts/setup-pass.sh +3 -0
- package/skills/trends/scripts/store.py +444 -50
- package/skills/trends/scripts/watchlist.py +38 -11
- package/skills/trends/scripts/x_lane_parity.py +107 -0
- package/skills/video-studio/CHANGELOG.md +4 -0
- package/skills/video-studio/SKILL.md +4 -4
- package/skills/video-studio/references/styles/product-flight.md +45 -0
- package/skills/video-studio/references/styles.md +3 -1
- package/skills/video-studio-motion/ATTRIBUTION.md +1 -1
- package/skills/video-studio-motion/CHANGELOG.md +4 -0
- package/skills/video-studio-motion/SKILL.md +8 -5
- package/skills/youtube-planner/ATTRIBUTION.md +25 -0
- package/skills/youtube-planner/CHANGELOG.md +12 -0
- package/skills/youtube-planner/SKILL.md +191 -0
- package/skills/youtube-planner/references/plan-template.md +57 -0
- package/skills/youtube-planner/references/stages.md +119 -0
- package/skills/youtube-planner/scripts/floor_ceiling.py +132 -0
- package/skills/coder-brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/skills/trends/scripts/lib/reddit_rss.py +0 -224
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Runtime and configuration
|
|
2
|
+
|
|
3
|
+
Read before any engine command. Bind `SKILL_DIR` to the loaded root SKILL.md before running these snippets. A reference directory is never an install root.
|
|
4
|
+
|
|
5
|
+
# STEP 0: STALE-CLONE SELF-CHECK — RUN BEFORE READING BELOW
|
|
6
|
+
|
|
7
|
+
Before reading anything else in this file, check whether you loaded SKILL.md from the one known stale-clone location: Claude Code's marketplaces directory.
|
|
8
|
+
|
|
9
|
+
**Run this check:**
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
CLAUDE_CACHE_LATEST=$(find "$HOME/.claude/plugins/cache/last30days-skill/last30days" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)
|
|
13
|
+
# Two cache layouts ship in the wild — nested ({version}/skills/last30days/SKILL.md)
|
|
14
|
+
# and flat ({version}/SKILL.md). Resolve to whichever shape actually exists.
|
|
15
|
+
CLAUDE_CACHE_SKILL_MD=""
|
|
16
|
+
if [ -n "$CLAUDE_CACHE_LATEST" ]; then
|
|
17
|
+
if [ -f "$CLAUDE_CACHE_LATEST/skills/last30days/SKILL.md" ]; then
|
|
18
|
+
CLAUDE_CACHE_SKILL_MD="$CLAUDE_CACHE_LATEST/skills/last30days/SKILL.md"
|
|
19
|
+
elif [ -f "$CLAUDE_CACHE_LATEST/SKILL.md" ]; then
|
|
20
|
+
CLAUDE_CACHE_SKILL_MD="$CLAUDE_CACHE_LATEST/SKILL.md"
|
|
21
|
+
fi
|
|
22
|
+
fi
|
|
23
|
+
echo "CLAUDE_CACHE_SKILL_MD=$CLAUDE_CACHE_SKILL_MD"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If the loaded root SKILL.md path contains `/.claude/plugins/marketplaces/` AND `$CLAUDE_CACHE_SKILL_MD` is non-empty, STOP and re-read `$CLAUDE_CACHE_SKILL_MD` before proceeding. Otherwise the SKILL.md you have is fine — continue.
|
|
27
|
+
|
|
28
|
+
**Other install paths are fine:** `~/.codex/skills/`, `~/.agents/skills/`, an `npx skills add` install dir, or a repo checkout are all valid load points - the root `SKILL_DIR` binding picks them up. Do NOT abort or hop on those paths.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Runtime Preflight
|
|
34
|
+
|
|
35
|
+
Before running any `last30days.py` command in this skill, resolve a Python 3.12+ interpreter once and keep it in `LAST30DAYS_PYTHON`:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
try_last30days_python() {
|
|
39
|
+
candidate="${1}"
|
|
40
|
+
[ -n "$candidate" ] || return 1
|
|
41
|
+
if [ -x "$candidate" ]; then
|
|
42
|
+
:
|
|
43
|
+
elif command -v "$candidate" >/dev/null 2>&1; then
|
|
44
|
+
:
|
|
45
|
+
else
|
|
46
|
+
return 1
|
|
47
|
+
fi
|
|
48
|
+
"$candidate" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 12) else 1)' || return 1
|
|
49
|
+
LAST30DAYS_PYTHON="$candidate"
|
|
50
|
+
return 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
windows_path_to_unix() {
|
|
54
|
+
path="${1}"
|
|
55
|
+
[ -n "$path" ] || return 1
|
|
56
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
57
|
+
cygpath -u "$path"
|
|
58
|
+
else
|
|
59
|
+
printf '%s\n' "$path"
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if [ -z "${LAST30DAYS_PYTHON:-}" ]; then
|
|
64
|
+
while IFS= read -r windows_python_root; do
|
|
65
|
+
[ -n "$windows_python_root" ] && [ -d "$windows_python_root" ] || continue
|
|
66
|
+
while IFS= read -r py; do
|
|
67
|
+
try_last30days_python "$py" && break 2
|
|
68
|
+
done <<EOF_PYTHON_CANDIDATES
|
|
69
|
+
$(find "$windows_python_root" -maxdepth 2 -type f -iname python.exe 2>/dev/null | sort -r)
|
|
70
|
+
EOF_PYTHON_CANDIDATES
|
|
71
|
+
done <<EOF_WINDOWS_PYTHON_ROOTS
|
|
72
|
+
$([ -n "${LOCALAPPDATA:-}" ] && printf '%s\n' "$(windows_path_to_unix "$LOCALAPPDATA")/Programs/Python")
|
|
73
|
+
$([ -n "${ProgramFiles:-}" ] && windows_path_to_unix "$ProgramFiles")
|
|
74
|
+
$([ -n "${PROGRAMFILES:-}" ] && windows_path_to_unix "$PROGRAMFILES")
|
|
75
|
+
$(program_files_x86="$(printenv 'ProgramFiles(x86)' 2>/dev/null || true)"; [ -n "$program_files_x86" ] && windows_path_to_unix "$program_files_x86")
|
|
76
|
+
EOF_WINDOWS_PYTHON_ROOTS
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [ -z "${LAST30DAYS_PYTHON:-}" ]; then
|
|
80
|
+
for py in python3.14 python3.13 python3.12 python3 python; do
|
|
81
|
+
try_last30days_python "$py" && break
|
|
82
|
+
done
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# uv fallback: on hosts without a system 3.12 but with `uv` on PATH (most agent
|
|
86
|
+
# sandboxes: Cowork, Codex, etc.), provision a managed 3.12 automatically instead
|
|
87
|
+
# of hard-failing. No-op when uv is absent — those hosts still hit the error below.
|
|
88
|
+
if [ -z "${LAST30DAYS_PYTHON:-}" ] && command -v uv >/dev/null 2>&1; then
|
|
89
|
+
uv_py="$(uv python find '>=3.12' 2>/dev/null)"
|
|
90
|
+
if [ -z "$uv_py" ] || [ ! -x "$uv_py" ]; then
|
|
91
|
+
echo "NOTE: no Python 3.12+ found; installing a managed CPython 3.12 via uv (~28MB, one-time)." >&2
|
|
92
|
+
if UV_HTTP_TIMEOUT=30 uv python install 3.12 >/dev/null 2>&1; then
|
|
93
|
+
uv_py="$(uv python find '>=3.12' 2>/dev/null)"
|
|
94
|
+
else
|
|
95
|
+
echo "WARN: 'uv python install 3.12' failed (network, disk space, or proxy?); falling through to the version-gate error below." >&2
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
try_last30days_python "$uv_py"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [ -z "${LAST30DAYS_PYTHON:-}" ]; then
|
|
102
|
+
echo "ERROR: last30days v3 requires Python 3.12+. Install Python 3.12+ or set LAST30DAYS_PYTHON to a supported interpreter." >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
"${LAST30DAYS_PYTHON}" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 12) else 1)' || {
|
|
107
|
+
echo "ERROR: LAST30DAYS_PYTHON must point to Python 3.12+." >&2
|
|
108
|
+
exit 1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
last30days_python_path="$(command -v "$LAST30DAYS_PYTHON")" || exit 1
|
|
112
|
+
case "$last30days_python_path" in
|
|
113
|
+
/*) LAST30DAYS_PYTHON="$last30days_python_path" ;;
|
|
114
|
+
*/*) last30days_python_dir="$(cd -P -- "${last30days_python_path%/*}" && pwd -P)" || exit 1
|
|
115
|
+
LAST30DAYS_PYTHON="$last30days_python_dir/${last30days_python_path##*/}" ;;
|
|
116
|
+
*) echo "ERROR: selected Python must resolve to an executable path." >&2; exit 1 ;;
|
|
117
|
+
esac
|
|
118
|
+
export LAST30DAYS_PYTHON
|
|
119
|
+
"${LAST30DAYS_PYTHON}" -c 'import os, shlex; print("LAST30DAYS_PYTHON=" + shlex.quote(os.environ["LAST30DAYS_PYTHON"]))'
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**PYTHON VERSION GATE — when the Runtime Preflight Bash block above exits with a Python version error:**
|
|
124
|
+
|
|
125
|
+
If the preflight script (including the uv fallback above) emits `ERROR: last30days v3 requires Python 3.12+` (or `LAST30DAYS_PYTHON must point to Python 3.12+`) and exits, you MUST:
|
|
126
|
+
|
|
127
|
+
1. Display this message to the user:
|
|
128
|
+
> "The last30days engine needs Python 3.12+. Your system has an older version. Install it with one command:
|
|
129
|
+
> - **Mac:** `brew install python@3.12`
|
|
130
|
+
> - **Windows:** `winget install Python.Python.3.12`
|
|
131
|
+
> - **Linux:** `sudo apt install python3.12` (or `pyenv install 3.12`)
|
|
132
|
+
>
|
|
133
|
+
> Then re-run `/last30days <your topic>` and the setup wizard will configure everything automatically."
|
|
134
|
+
2. **Stop.** Do not attempt research. Do not fall back to WebSearch-only synthesis.
|
|
135
|
+
|
|
136
|
+
WebSearch-only synthesis is not equivalent to running the engine — it misses Reddit community data, X/Twitter timelines, YouTube transcripts, TikTok, and Polymarket. Presenting it without disclosure misleads the user about what was actually searched. This is the same category of failure as a WebSearch-only run with no engine footer.
|
|
137
|
+
|
|
138
|
+
**Native-search signal (web coverage).** If you (the hosting model) have your own web-search tool available, export `LAST30DAYS_NATIVE_SEARCH=1` in the same shell before invoking the engine:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
export LAST30DAYS_NATIVE_SEARCH=1 # ONLY when you have a native web-search tool
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Your host search is better than the engine's keyless web fallback, so this tells the engine to skip that fallback and leave general web to you (you already run web-search supplements in Step 2). If you have NO web-search tool in the agent session, do **not** set this: the engine's keyless web floor supplies general-web coverage automatically. The rule is capability-based, not host-name-based — set it only when you genuinely have a better search, never to suppress the floor on a host that has nothing else.
|
|
145
|
+
|
|
146
|
+
## Save-directory resolution
|
|
147
|
+
|
|
148
|
+
After interpreter bootstrap, resolve the save directory once before the first engine operation that needs it. If the user supplied a one-off directory, add `--save-dir="<requested directory>"` to the resolver command below before running it. Remove any duplicate save-directory flag from the later research arguments. Never source a `.env` file.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
LAST30DAYS_MEMORY_DIR="$("${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" --resolve-save-dir)" || exit
|
|
152
|
+
printf 'Resolved save directory: <%s>\n' "$LAST30DAYS_MEMORY_DIR" >&2
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Capture that exact value, including an empty string. Later library, discovery, and research recipes use this captured value and do not resolve it again. When shell calls do not share variables, restore the captured value explicitly. All discovery legs use the same value even if configuration or working directory changes.
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
Set `LAST30DAYS_MEMORY_DIR` in the process environment, trusted project config, or global `~/.config/last30days/.env` to choose where raw research files are saved. Resolution follows that order after an explicit `--save-dir`. Only an absent setting defaults the skill to `~/Documents/Last30Days`; an explicitly empty value disables research file saves. The engine creates a nonempty directory on first save.
|
|
160
|
+
|
|
161
|
+
The engine reads `LAST30DAYS_MEMORY_DIR` from either the process env or `~/.config/last30days/.env`, so direct CLI invocations (`python3 scripts/last30days.py ...`) without `--save-dir` will still save when the env var is set. Mirrors the `LAST30DAYS_STORE` env-or-flag convention. Explicit `--save-dir` always wins.
|
|
162
|
+
|
|
163
|
+
When both `LAST30DAYS_API_KEY` and `LAST30DAYS_API_BASE` are set, the engine runs the research through that configured remote API instead of local sources (unless `--mock` is passed); `LAST30DAYS_API_BASE` is the endpoint and has no built-in default, so leaving either variable unset runs local sources normally. A configured `--corpus` / `LAST30DAYS_CORPUS_DIRS` is the privacy exception: the engine bypasses the hosted backend and runs locally so no file-derived input is forwarded. An explicit `--perplexity-search-type` flag also runs locally when a direct `PERPLEXITY_API_KEY` exists, because the hosted backend cannot apply it; the engine prints one stderr notice. Without a direct key the run stays hosted and prints a note that the flag was not applied. A `LAST30DAYS_PERPLEXITY_SEARCH_TYPE` value set only in the environment keeps hosted routing and prints a note that it was not applied. The invocation is otherwise unchanged: same flags, `--quick`/`--deep` map to search depth, a non-default `--register` is forwarded for server-side synthesis, progress lines still stream on stderr (`[narrate] step=...` plus a compact elapsed/eta line), and the report prints on stdout and saves to the memory dir as usual, so Steps 1-4 proceed normally on the output. The exception is research JSON: the remote endpoint does not return the local `Report` needed for the versioned agent profile, so use `--emit=json --json-profile=raw` for its existing server-response JSON contract. No per-source keys or setup-wizard credentials are needed for the search itself in this mode. Two engine exits need specific handling: exit code 3 means the API asked a clarifying question first - the engine prints the question and options on stderr; present them to the user and re-run with the chosen angle folded into the topic. An insufficient-credits failure (HTTP 402) prints the account's balance, the amount needed, and a billing link - relay those lines to the user verbatim; do not fall back to WebSearch-only synthesis.
|
|
164
|
+
|
|
165
|
+
**Developer-only eval capture:** `--record-fixtures <dir>` is a hidden direct-engine flag for maintaining the deterministic research-quality suite. It records scrubbed HTTP and CLI-adapter responses to `<dir>/http.json`; it is never part of the user-facing slash-command invocation. Follow `docs/reference/eval.md` for fixture review, replay, and baseline rules.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Save shareable HTML brief
|
|
2
2
|
|
|
3
|
-
> **luckiest-trends note.** This file preserves the original HTML-brief flow from the upstream `last30days` skill. Where it says `last30days` in *user-facing* output (the badge, the `/
|
|
3
|
+
> **luckiest-trends note.** This file preserves the original HTML-brief flow from the upstream `last30days` skill. Where it says `last30days` in *user-facing* output (the badge, the `/luckiest-trends` command), read it as `luckiest-trends`. Where it names *technical identifiers* (`scripts/last30days.py`, `LAST30DAYS_*` env vars, `~/.config/last30days/`, temp file names, stderr log prefixes), those are unchanged and correct.
|
|
4
4
|
|
|
5
5
|
This reference file is loaded by the main `SKILL.md` when the user asked for an HTML brief (either through an HTML-looking prompt argument like `--emit=html` / `--emit:html` / `--html`, or in natural language - "give me a shareable HTML brief", "give it to me in HTML", "for Slack", "for Notion", "export as HTML", etc.). The detection happens in `SKILL.md` so that the common no-HTML path stays short; the implementation lives here. Those prompt arguments are user intent signals for the skill; they are not the full Python CLI contract.
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ The contract has two modes:
|
|
|
13
13
|
|
|
14
14
|
- For normal-report-plus-HTML mode: after you have already emitted the full chat response: badge, "What I learned:" (or comparison title), bold-lead-in paragraphs with citations, KEY PATTERNS list, engine footer pass-through, invitation block.
|
|
15
15
|
- For HTML-as-deliverable mode: after you have drafted the synthesis that will go into the HTML, before emitting the final chat response.
|
|
16
|
-
- BEFORE the
|
|
16
|
+
- BEFORE the closing invitation and wait for the user's response.
|
|
17
17
|
- ONLY if the user asked. Do NOT save HTML when the user didn't ask for it.
|
|
18
18
|
|
|
19
19
|
## How to fire it
|
|
@@ -33,7 +33,7 @@ The contract has two modes:
|
|
|
33
33
|
SYNTHESIS_FILE="/tmp/last30days-synthesis-${CLAUDE_SESSION_ID}.md"
|
|
34
34
|
# >| not >: fixed path may already exist on a same-session re-run; a plain >
|
|
35
35
|
# is refused under `set -o noclobber`.
|
|
36
|
-
cat >| "$SYNTHESIS_FILE" <<'SYNTHESIS_EOF'
|
|
36
|
+
cat >| "${SYNTHESIS_FILE}" << 'SYNTHESIS_EOF'
|
|
37
37
|
What I learned:
|
|
38
38
|
|
|
39
39
|
**{First headline}** - {body with [name](url) inline citations}
|
|
@@ -59,20 +59,39 @@ SYNTHESIS_EOF
|
|
|
59
59
|
# different topic, stderr says "No matching cached report data" and the
|
|
60
60
|
# engine falls back to a fresh run; the same scope flags keep that fallback
|
|
61
61
|
# aligned with the synthesis body.
|
|
62
|
-
SLUG=$(
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"${
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
SLUG=$(printf '%s\n' "${TOPIC}" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-//;s/-$//')
|
|
63
|
+
# Render beside the destination, then create a new name atomically. The hard
|
|
64
|
+
# link refuses existing paths even when another exporter wins the same name.
|
|
65
|
+
HTML_PATH=$(
|
|
66
|
+
HTML_TMP=$(mktemp "${LAST30DAYS_MEMORY_DIR}/.last30days-html.XXXXXXXX") || exit "$?"
|
|
67
|
+
trap 'rm -f -- "${HTML_TMP}"' EXIT
|
|
68
|
+
"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" "${TOPIC}" \
|
|
69
|
+
--emit=html \
|
|
70
|
+
--synthesis-file "${SYNTHESIS_FILE}" \
|
|
71
|
+
"${SCOPE_FLAGS[@]}" \
|
|
72
|
+
>| "${HTML_TMP}" || exit "$?"
|
|
73
|
+
"${LAST30DAYS_PYTHON}" - "${HTML_TMP}" "${LAST30DAYS_MEMORY_DIR}" "${SLUG}" << 'HTML_PATH_PY'
|
|
74
|
+
from datetime import date
|
|
75
|
+
import os
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
import sys
|
|
78
|
+
|
|
79
|
+
temporary, directory, slug = sys.argv[1:]
|
|
80
|
+
base = Path(directory) / f"{slug}-brief"
|
|
81
|
+
dated = f"{base}-{date.today().isoformat()}"
|
|
82
|
+
candidate = Path(f"{base}.html")
|
|
83
|
+
suffix = 1
|
|
84
|
+
while True:
|
|
85
|
+
try:
|
|
86
|
+
os.link(temporary, candidate)
|
|
87
|
+
except FileExistsError:
|
|
88
|
+
candidate = Path(f"{dated}.html" if suffix == 1 else f"{dated}-{suffix}.html")
|
|
89
|
+
suffix += 1
|
|
90
|
+
else:
|
|
91
|
+
print(candidate)
|
|
92
|
+
break
|
|
93
|
+
HTML_PATH_PY
|
|
94
|
+
) || exit "$?"
|
|
76
95
|
# where SCOPE_FLAGS is the same array you passed the first time, e.g.
|
|
77
96
|
# SCOPE_FLAGS=(--hiring-signals --plan "$QUERY_PLAN_FILE" --x-handle=acme).
|
|
78
97
|
# For a scoped --hiring-signals brief, --hiring-signals MUST be here too so
|
|
@@ -112,7 +131,7 @@ When the user chooses the built-in `ht-ml.app` path, add `--publish-html` to the
|
|
|
112
131
|
|
|
113
132
|
```bash
|
|
114
133
|
LAST30DAYS_PUBLISH_PASSWORD="${PUBLISH_PASSWORD:-}" \
|
|
115
|
-
"${LAST30DAYS_PYTHON}" "${
|
|
134
|
+
"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" "${TOPIC}" \
|
|
116
135
|
--emit=html \
|
|
117
136
|
--synthesis-file "$SYNTHESIS_FILE" \
|
|
118
137
|
--output "$HTML_PATH" \
|
|
@@ -191,7 +210,7 @@ The engine will try to reuse `~/.config/last30days/last-report.json` for that se
|
|
|
191
210
|
- Do NOT save HTML if the user didn't ask. The sparse mode (no synthesis) produces a thin file; not useful as a shareable.
|
|
192
211
|
- Do NOT add content to the temp file beyond your synthesis prose. The badge / footer / colophon come from the engine.
|
|
193
212
|
- Do NOT change the file path convention. `${LAST30DAYS_MEMORY_DIR}/${SLUG}-brief.html` is the canonical location.
|
|
194
|
-
- Do NOT
|
|
213
|
+
- Do NOT overwrite an existing file. Step 2 renders to a private temporary file and links the completed output to a new name. If `{slug}-brief.html` exists, try `{slug}-brief-YYYY-MM-DD.html`, then `{slug}-brief-YYYY-MM-DD-2.html`, incrementing until a name is available. This also handles concurrent exporters without replacing files, directories, or symlinks. The temporary file is removed on success or failure. If rendering or linking fails, report the error instead of a successful save. Always report the path stored in `HTML_PATH` in the chat handoff.
|
|
195
214
|
- Do NOT include the data quality warning text in the temp file or in your final chat line. Warnings are an engine-stderr concern, not an artifact concern.
|
|
196
215
|
- Do NOT publish, upload, or send the HTML to a third-party service as part of the local save flow.
|
|
197
216
|
- Do NOT publish to any service merely because HTML was requested. Show the saved path and next-step choices first; publishing requires the user to choose a publish option.
|
|
@@ -203,4 +222,4 @@ The engine will try to reuse `~/.config/last30days/last-report.json` for that se
|
|
|
203
222
|
- **Topic with shell-special characters** (quotes, ampersands): the temp filename uses a slugified version, but the engine receives the raw topic. The `cat <<'SYNTHESIS_EOF'` quoted heredoc form handles arbitrary content without expansion. Your synthesis text can include any character.
|
|
204
223
|
- **Very long synthesis**: no upper bound. The engine handles long markdown bodies. Just paste verbatim.
|
|
205
224
|
- **Synthesis with images or non-ASCII**: emoji and Unicode pass through. Image tags pass through as raw HTML; the renderer doesn't transform them. If you didn't include images in chat, don't add them here.
|
|
206
|
-
- **No `${LAST30DAYS_MEMORY_DIR}` set**: defaults to `~/Documents/Last30Days/` per the
|
|
225
|
+
- **No `${LAST30DAYS_MEMORY_DIR}` set**: defaults to `~/Documents/Last30Days/` per the `Configuration` section of `references/runtime.md`.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# First-run setup and source repair
|
|
2
|
+
|
|
3
|
+
Read when either root setup or repair gate applies. For first-run setup, follow Step 0 below. After onboarding, resume the original requested route, including discovery; do not replace it with ordinary topic research.
|
|
4
|
+
|
|
5
|
+
**Repair entry (including when `SETUP_COMPLETE=true`):** For a requested repair or a doctor's host-specific prescription, go directly to the applicable **Manual Setup Guide** subsection below. Skip Step 0, its welcome, offers, source-tier prompts, and topic picker. The Manual Setup Guide's completion tail applies only to onboarding: during repair, do not write `SETUP_COMPLETE=true`, and do not start research unless it was requested. Return to the original request after the authorized repair or repair guidance.
|
|
6
|
+
|
|
7
|
+
Carry out only the authorized repair. Reading this guide or a doctor prescription does not authorize installs, browser launches, cookie reads, or configuration writes. Browser access requires explicit browser-cookie consent. Existing authorization still applies; a refusal, skip, or no answer is not consent. On a `LAST30DAYS_HOST=grok-bot` host, use only **X on a Grok Bot (repair)** for X; never read a browser session or use the Linux / Mac mini repair. Preserve the MacBook exception, human login handoff, and block/challenge/rate-limit stop rules in the applicable subsection.
|
|
8
|
+
|
|
9
|
+
## Step 0: First-Run Setup Wizard
|
|
10
|
+
|
|
11
|
+
**CRITICAL: ALWAYS execute Step 0 BEFORE Step 1 (topic research in the research runbook), even when the user provided a topic.** If the user typed `/last30days Mercer Island`, preserve that topic while handling the first-run choice. The wizard may ask for browser-cookie consent, but declining or skipping X must never stop the requested research.
|
|
12
|
+
|
|
13
|
+
**RESEARCH CONTINUATION OVERRIDE (dominates every optional onboarding step below):** When the invocation already includes a topic and the user declines X/browser-cookie access inside Auto setup, run the cookie-free setup path, then immediately research that topic with the sources that are available. When the user chooses Skip for now, mark setup complete and immediately research without running setup. In either case, skip the ScrapeCreators offer, source-tier prompt, retry prompt, and first-topic picker until after the useful research response. Do not ask another X question in the same run. After the findings, report X once as an optional omitted source without an unlock pitch, **then RESUME the deferred onboarding in the SAME run: present the Step 4 ScrapeCreators offer, and Step 5 source opt-in if a key gets saved.** Deferred is not dropped — `SETUP_COMPLETE=true` is already written, so a later invocation skips Step 0 entirely and this run is the only chance to make the offer. The first-topic picker stays skipped (a topic was already supplied), and the resume never re-asks X/browser-cookie consent. Browser-cookie reads still require explicit consent; a skip or no answer is never consent.
|
|
14
|
+
|
|
15
|
+
**You are the conversational driver.** The Python setup script does only mechanical work (cookie reads, tool installs, the GitHub device-auth flow) - it CANNOT prompt the user, because it runs as a non-interactive subprocess. So consent happens HERE, in chat: you ask, the user answers, and you gate each subprocess call on the answer. Do NOT just run `setup` and report the result - that is the silent-onboarding regression this section exists to prevent.
|
|
16
|
+
|
|
17
|
+
**First-run detection (silent, no commands, no output to user):**
|
|
18
|
+
- If `SETUP_COMPLETE=true` is available from process env, project config (`.claude/last30days.env`), global config (`~/.config/last30days/.env`), or the setup check reports configured credentials, skip Step 0 entirely and return to the root dispatch for the original request (for topic research, the research runbook's Parse User Intent). Do NOT announce that setup is complete. The user does not need a status message on every run.
|
|
19
|
+
- Do NOT treat the absence of `~/.config/last30days/.env` alone as a first run. Credentials may live in process env, project config, macOS Keychain (`last30days-<KEY>`), pass(1), or host-provided auth.
|
|
20
|
+
- Detect first-run from key presence and setup status, not by dumping files. Do not print `.env` contents or credential values.
|
|
21
|
+
- `--preflight` is an opt-in inspector (what this run would read or write, ignored project config). Do not run it as a required first-run step.
|
|
22
|
+
- If no setup marker or credential source is present, this is a first run.
|
|
23
|
+
|
|
24
|
+
**Modal requirement:** preserve the guided welcome → Auto/Manual/Skip → cookie consent → ScrapeCreators offer → source opt-in → first-topic picker flow. Do not replace the modal flow with silent setup or prose-only onboarding.
|
|
25
|
+
|
|
26
|
+
**Platform split - run exactly ONE branch:**
|
|
27
|
+
- **If you HAVE WebSearch and AskUserQuestion (Claude Code):** run the **Claude Code Modal Flow** immediately below.
|
|
28
|
+
- **If you do NOT (OpenClaw, Codex, Cursor, Gemini CLI, raw CLI):** run the **Non-Modal Prose Flow** further down. It does the same work conversationally, without modals.
|
|
29
|
+
- **If you are running as a Grok Bot** (the GROK BOT HOST RULE in the root SKILL.md): run the **Grok Bot Prose Flow**, the third branch, below the Non-Modal flow. The bot's own X tools come first, any backup key is written only through the engine, and no browser session is read. Cursor stays in the Non-Modal Prose Flow.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### Claude Code Modal Flow
|
|
34
|
+
|
|
35
|
+
**Follow these steps IN ORDER unless the Research Continuation Override routes a waiting topic directly to research.** The normal sequence is: (1) welcome (built into the setup modal) → (2) setup modal → (3) run setup if chosen → (4) ScrapeCreators offer modal → (5) source opt-in modal → (6) first-topic picker. Start at step 1.
|
|
36
|
+
|
|
37
|
+
**Step 1 - Welcome.** The welcome pitch is delivered INSIDE the Step 2 setup modal, NOT as a separate message. Claude Code folds Bash/tool output behind "ctrl+o to expand", so a separate welcome message - or a `--welcome` command run - gets buried and the user never sees it. The AskUserQuestion modal is the only always-fully-visible surface, so the pitch lives in its question text. Do NOT run a separate `--welcome` command in this modal flow, and do NOT try to print the welcome as a chat message before the modal; go straight to Step 2. (The `--welcome` command still exists for the Non-Modal Prose Flow below, where there is no modal.)
|
|
38
|
+
|
|
39
|
+
**Step 2 - Welcome + setup choice (one modal).** Call AskUserQuestion with EXACTLY this question and these options. Reproduce the question verbatim, including the welcome pitch on the first lines:
|
|
40
|
+
|
|
41
|
+
Question:
|
|
42
|
+
"Welcome to /last30days! I research any topic across Reddit, X, YouTube, TikTok, Digg, arXiv, Techmeme, HN, Polymarket & more - pulling what people actually said in the last 30 days.
|
|
43
|
+
|
|
44
|
+
How would you like to set up?"
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
- "Auto setup (~30s)" - description: "Scan browser cookies for X + install yt-dlp (YouTube), Digg, arXiv, Techmeme. Reddit/HN/Polymarket/GitHub/Web work out of the box. Add TikTok + Instagram after via ScrapeCreators (10k free calls)."
|
|
48
|
+
- "Manual setup" - description: "Show me each source and credential to configure by hand."
|
|
49
|
+
- "Skip for now" - description: "Just the free no-setup sources: Reddit (with comments), HN, Polymarket, GitHub, Web."
|
|
50
|
+
|
|
51
|
+
**Step 3 - Run setup based on the choice.**
|
|
52
|
+
|
|
53
|
+
**If the user picks Skip for now:** write `SETUP_COMPLETE=true` and `BROWSER_CONSENT=false` to `~/.config/last30days/.env` (preserve other keys, replace an existing consent marker, and create the file with mode `0600` if absent) so the wizard does NOT re-fire on every subsequent run and later runs keep browser reads disabled. Do not run any `setup` command - the always-on sources (Reddit, HN, Polymarket, GitHub, Web) need no setup. If the invocation already includes a topic, research it immediately, then resume Step 4 (and Step 5 if a key is saved) in the same run after the findings; Step 6 stays skipped because the topic was supplied. Otherwise continue to Step 6.
|
|
54
|
+
|
|
55
|
+
**If the user picks Auto setup:**
|
|
56
|
+
|
|
57
|
+
Get cookie consent first. Check if `BROWSER_CONSENT=true` already exists in `~/.config/last30days/.env`; if so, skip the consent prompt and run `setup --allow-browser-cookies` directly. Otherwise **call AskUserQuestion:**
|
|
58
|
+
Question: "Auto setup installs the free CLIs either way - yt-dlp (YouTube), Digg, arXiv, and Techmeme. May I read your browser's x.com cookies for X/Twitter and other logged-in sources now and on later research runs? On macOS I check Chrome and other Chromium browsers first, then Firefox and Safari. On Windows and Linux I use Firefox, with Windows Firefox as a fallback under WSL. Your existing browser selection takes precedence. Setup remembers successful browser names, never cookie values. macOS Keychain may prompt again on later runs; choose Always Allow if you want continuing OS access. You can stop native browser and CDP reads by setting BROWSER_CONSENT=false or FROM_BROWSER=off in ~/.config/last30days/.env. Include X?"
|
|
59
|
+
Options (give each option the description shown):
|
|
60
|
+
- "Yes - X cookies + all CLIs" - description: "Read x.com cookies for X/Twitter search AND install yt-dlp (YouTube), Digg, arXiv, and Techmeme." Run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup --allow-browser-cookies`. The engine saves `BROWSER_CONSENT=true`; do not append a duplicate marker.
|
|
61
|
+
- "Skip X - just the CLIs" - description: "No cookie reads. Still installs yt-dlp (YouTube), Digg, arXiv, and Techmeme." Run `FROM_BROWSER=off "${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup`. The engine saves `BROWSER_CONSENT=false`, so the refusal also applies on future runs. If the invocation already includes a topic, immediately research it with `--no-browser-cookies`, then resume Step 4 (and Step 5 if a key is saved) in the same run after the findings; Step 6 stays skipped because the topic was supplied.
|
|
62
|
+
- "xAI API key for X instead" - description: "Use an api.x.ai key for X search (no cookie read), plus install yt-dlp (YouTube), Digg, arXiv, and Techmeme." Ask them to paste it, write `XAI_API_KEY` to `.env`, then run `FROM_BROWSER=off "${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup`.
|
|
63
|
+
|
|
64
|
+
**Grok CLI is an opt-in backup, not a setup-time recommendation.** Do NOT check for grok first or offer it as a primary option during setup. A leftover `~/.grok/auth.json` must never steal the X lane. If the user mentions having a Grok account, tell them: "You can use the Grok CLI by pinning `LAST30DAYS_X_BACKEND=grok` in your `.env` after running `grok login`. This is opt-in because a leftover grok login should not take over X automatically." Do not call it free — it needs a Grok plan.
|
|
65
|
+
|
|
66
|
+
The consented `setup --allow-browser-cookies` run extracts cookies using the platform order explained in the prompt. Unless `FROM_BROWSER` is already configured, setup saves the browsers that supplied complete cookies as a comma-separated `FROM_BROWSER` list, including Chromium browsers. It normalizes Windows Firefox's `firefox-wsl` source label to `firefox`. It never saves cookie values. Later research reads those browser sessions again; macOS Keychain may prompt again. `BROWSER_CONSENT=false` or `FROM_BROWSER=off` disables native browser and CDP reads. A later consented setup replaces a saved refusal, but an explicit `FROM_BROWSER=off` still blocks reads until changed. The `agentcookie` sidecar is controlled separately by `AGENTCOOKIE=off`.
|
|
67
|
+
|
|
68
|
+
Setup also best-effort installs yt-dlp (YouTube), the free keyless Digg CLI (`digg-pp-cli` via `@mvanhorn/printing-press-library install digg --cli-only`; Digg activates only when the binary is on the **agent subprocess PATH**, typically `$HOME/.local/bin`; setup reports honestly if installed off-PATH; recommend-only if `npx` is unavailable), plus the free keyless arXiv and Techmeme CLIs. Show the user what was found and installed - including whether Digg landed on PATH (active) or off-PATH (installed but not yet active).
|
|
69
|
+
|
|
70
|
+
**Extras-host X login (Linux / Mac mini / Darwin agentcookie sink — a MacBook SKIPS this, and so does a `LAST30DAYS_HOST=grok-bot` host, which never runs it).** On a MacBook the Keychain/Firefox/Safari extract above serves X when the OS permits browser-data reads. On an extras host the local Chrome store can't be decrypted, so that extract finds nothing and X stays empty unless you also capture a LIVE login over CDP. After the cookie-consent setup run: run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/box_chrome_login.py"` — it prints the exact host-correct command (or launches it with `--exec`), and on a MacBook it prints "no launch needed" and spawns nothing. When `box-chrome` is on PATH it launches a throwaway profile on the last30days extras port **18800** (the last30days convention `SAND_CHROME_REMOTE_DEBUG_PORT=18800`, not box-chrome's default): `CHROME_USER_DATA_DIR=/tmp/last30days-x-chrome SAND_CHROME_REMOTE_DEBUG_PORT=18800 box-chrome --new-window https://x.com/login`. Wait for the x.com login page, then HAND THE DESKTOP to the human to type — do NOT fill or drive the form. After they sign in, append `BROWSER_CDP_URL=http://127.0.0.1:18800` to `.env` (never `AUTH_TOKEN`/`CT0`; keep `AGENTCOOKIE=off` during the harvest), then re-run `setup --allow-browser-cookies` so extras CDP reads the live pair. Full steps and the block/rate-limit stop rule are in **X on Linux / Mac mini** below. This extras-host login only runs on the consented "Yes - X cookies" path; the Research Continuation Override never skips it when the user said yes to X.
|
|
71
|
+
|
|
72
|
+
**macOS browser-data permission remediation.** After consented `setup`, if stderr says `X cookie access was permission denied`, name the browser; if it says `Permission denied reading Cookies.binarycookies`, name Safari. Advise: `Check System Settings > Privacy & Security permissions for your terminal or agent app. Full Disk Access may help, but may not unlock another app's data. You can supply AUTH_TOKEN and CT0 manually or use XAI_API_KEY.` Offer ONE retry only when no research topic is waiting; otherwise continue with available sources.
|
|
73
|
+
|
|
74
|
+
**Step 4: ScrapeCreators offer (every first run unless the Research Continuation Override already started a waiting topic).** Show this as plain text, then a modal:
|
|
75
|
+
|
|
76
|
+
ScrapeCreators adds TikTok/Instagram posts and top comments, plus YouTube comments. The key grants 10,000 free calls (then PAYG, no card or cut) and spends credits on Reddit search when free results are fewer than 5 items, YouTube search below 3 yt-dlp videos, and YouTube transcript rescue. Set `LAST30DAYS_REDDIT_SC_MIN_ITEMS=0` or `LAST30DAYS_YT_SC_MIN_ITEMS=0` for empty-only search fallback. Each YouTube backfill can fetch transcripts; a retry can spend again. Reddit comments remain free via shreddit.
|
|
77
|
+
|
|
78
|
+
Before the modal, run `which gh` via Bash silently; store as gh_available.
|
|
79
|
+
|
|
80
|
+
**Call AskUserQuestion:**
|
|
81
|
+
Question: "Want to add TikTok and Instagram? The key also spends credits on Reddit search below 5, YouTube search below 3, and transcript rescue. Set LAST30DAYS_YT_SC_MIN_ITEMS=0 for empty-only YouTube search."
|
|
82
|
+
Options:
|
|
83
|
+
- "ScrapeCreators via GitHub (recommended - most free calls)" - description: "Opens GitHub - we copy your code to your clipboard automatically, so you just paste it (Cmd+V), ~20-30s. Grants the full 10,000 free calls - more than the web signup." (Recommend this over the web option because the GitHub path grants more free calls.) This is a **two-command flow** - `--github-start` returns the code fast (foreground), then `--github-poll` waits for you to authorize. The code comes back in the command output, so it can't be missed:
|
|
84
|
+
1. **Run `--github-start` in the FOREGROUND** (it returns in ~1-2s, it does NOT block-poll): `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup --github-start`. It submits the device flow, copies the code to the clipboard, opens the browser, and returns a JSON blob plus a plain `Your GitHub code: XXXX-XXXX` line on stdout.
|
|
85
|
+
- If the returned `status == "already_registered"` (a key was already saved): tell the user "You're already set up - your existing ScrapeCreators key is active" and STOP (do not run poll).
|
|
86
|
+
- If `status == "error"`: show the message and offer the web option below.
|
|
87
|
+
2. **SHOW THE CODE.** Read the `user_code` from the output and output ONE chat message: "Enter this code on the GitHub page: **XXXX-XXXX** - it's already on your clipboard, so just paste (Cmd+V) and click Continue." (If the output said the clipboard copy failed, tell them to type it instead.) The code is right there in step 1's output - surfacing it is the whole point.
|
|
88
|
+
3. **Run `--github-poll`** (background with a 5-minute timeout, or foreground): `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup --github-poll`. Parse the **LAST** JSON line of its stdout for the final status:
|
|
89
|
+
- `status == "success"`: the engine persisted the key (`"persisted": true`, MASKED `api_key` - never echo it); confirm "You're in! 10,000 free calls. TikTok/Instagram, Reddit search below 5, YouTube search below 3, and transcript rescue are active. LAST30DAYS_YT_SC_MIN_ITEMS=0 makes YouTube search empty-only."
|
|
90
|
+
- `status == "success"` but `"persisted": false` (key write failed): do NOT claim sources are active - tell the user signup worked but saving the key failed, and have them add `SCRAPECREATORS_API_KEY=<key>` to `~/.config/last30days/.env` manually.
|
|
91
|
+
- `status == "error"` **with `message == "Authorized but failed to fetch API key"`** (exact match; often also `"reason": "no_api_key"`): GitHub authorized fine - do NOT say auth failed. This usually means your GitHub is **already linked** to a ScrapeCreators account. Tell the user: "GitHub authorized, but I couldn't auto-grab your ScrapeCreators key - your GitHub is probably already linked to an account. Get your key at scrapecreators.com and paste it here, or Skip." Then accept a pasted key (write `SCRAPECREATORS_API_KEY` to `.env`) or offer the web/skip options.
|
|
92
|
+
- `status == "error"` **with `message` containing `ScrapeCreators profile failed`** (or `"reason": "upstream_error"`): GitHub authorized fine - do NOT say auth failed, and do **NOT** say the GitHub is already linked. ScrapeCreators returned a server error after auth. Tell the user: "GitHub authorized, but ScrapeCreators had a server error minting your key - this is on their side, not yours. Sign up or log in at scrapecreators.com, grab a key there, and paste it, or Skip / retry later." Accept a pasted key or offer web/skip. Do not imply they already have a working linked account.
|
|
93
|
+
- `status == "timeout"`, or any other `status == "error"` message: show "GitHub auth didn't complete - no worries, sign up at scrapecreators.com or try again later," then offer the web option below.
|
|
94
|
+
- **One-shot fallback:** hosts that prefer a single call can still run `setup --github` (foreground), which chains start+poll; tell the user first that a code will appear on their clipboard to paste.
|
|
95
|
+
- "Open scrapecreators.com (Google sign-in)" - run `open https://scrapecreators.com` via Bash, then ask them to paste the API key. Write `SCRAPECREATORS_API_KEY={key}` to `~/.config/last30days/.env`.
|
|
96
|
+
- "I have a key" - accept the key, write to `.env`.
|
|
97
|
+
- "Skip for now" - proceed without ScrapeCreators. TikTok/Instagram, paid Reddit/YouTube backfill, and transcript rescue stay off; free sources and Reddit comments still work.
|
|
98
|
+
|
|
99
|
+
**Step 5: Source opt-in (only if a ScrapeCreators key was saved, not if skipped).** Comments are the DEFAULT, never an opt-in - there is no posts-only tier. Plain text then modal:
|
|
100
|
+
|
|
101
|
+
Your key is set. On by default: TikTok + Instagram (posts AND top comments), and YouTube comments. Reddit search stays on the free keyless path (ScrapeCreators backfills search when it returns fewer than 5 items); Reddit comments stay free via shreddit. Want the widest net?
|
|
102
|
+
|
|
103
|
+
**Call AskUserQuestion:**
|
|
104
|
+
Question: "Which ScrapeCreators sources?"
|
|
105
|
+
Options:
|
|
106
|
+
- "TikTok + Instagram + all comments (recommended)" - the default: posts AND top comments (ranked by votes) for TikTok + Instagram, plus YouTube comments. Append `INCLUDE_SOURCES=tiktok,instagram,youtube_comments,tiktok_comments,instagram_comments` to `~/.config/last30days/.env` (the list must include `tiktok,instagram` so they are not treated as excluded). Confirm: "TikTok, Instagram, and top YouTube/TikTok/Instagram comments are on."
|
|
107
|
+
- "Everything (also Threads + Pinterest)" - everything above plus Threads and Pinterest searches. Most coverage, most credits. Append `INCLUDE_SOURCES=tiktok,instagram,youtube_comments,tiktok_comments,instagram_comments,threads,pinterest`. Confirm: "Everything's on: posts + comments for TikTok/Instagram/YouTube, plus Threads and Pinterest."
|
|
108
|
+
|
|
109
|
+
**Step 6: First-topic picker.** Once `SETUP_COMPLETE=true` is written, **call AskUserQuestion:**
|
|
110
|
+
Question: "What do you want to research first?"
|
|
111
|
+
Options:
|
|
112
|
+
- "Claude Code vs Codex" - tech comparison
|
|
113
|
+
- "Sam Altman" - person in the news
|
|
114
|
+
- "Warriors Basketball" - sports
|
|
115
|
+
- "AI Legal Prompting Techniques" - niche/professional
|
|
116
|
+
- "Type my own topic"
|
|
117
|
+
|
|
118
|
+
If the user picks an example, run research with it. If "Type my own", ask what they want. **If the user already supplied a topic with the command (e.g. `/last30days Mercer Island`), SKIP this picker and use their topic directly.**
|
|
119
|
+
|
|
120
|
+
**END OF FIRST-RUN WIZARD.** Everything in the Modal Flow ONLY runs on first run. If `SETUP_COMPLETE=true` exists, skip ALL of it - no welcome, no modals, no topic picker - and go straight to research (Parse User Intent).
|
|
121
|
+
|
|
122
|
+
**If the user picked Manual setup** at Step 2, follow the **Manual Setup Guide** below instead of the Auto branch (the guide writes `SETUP_COMPLETE=true` itself), then continue to Step 6.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### Non-Modal Prose Flow
|
|
127
|
+
|
|
128
|
+
For hosts without interactive modal prompts (OpenClaw, Codex, Cursor, Gemini CLI, raw CLI). Same work, done conversationally. Run in order; wait where it says to wait.
|
|
129
|
+
|
|
130
|
+
**1. Welcome.** Run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" --welcome` and show its stdout to the user VERBATIM (do not summarize or reformat). The welcome is engine-owned so it renders the same everywhere.
|
|
131
|
+
|
|
132
|
+
**2. Cookie consent (ask BEFORE reading anything).** First check if `BROWSER_CONSENT=true` already exists in `~/.config/last30days/.env` (e.g. granted in a prior Claude Code session); if so, skip this prompt and run `setup --allow-browser-cookies` directly. Otherwise ask. Example: `May I read your browser cookies for X/Twitter and other logged-in sources now and on later research runs? On macOS I check Chrome and other Chromium browsers first, then Firefox and Safari. On Windows and Linux I use Firefox, with Windows Firefox as a fallback under WSL. Your existing browser selection takes precedence. Setup remembers successful browser names, never cookie values. macOS Keychain may prompt again on later runs; choose Always Allow if you want continuing OS access. You can stop native browser and CDP reads by setting BROWSER_CONSENT=false or FROM_BROWSER=off in ~/.config/last30days/.env. (yes / no)` **Wait for the answer.**
|
|
133
|
+
- On **yes** → run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup --allow-browser-cookies` (the engine saves `BROWSER_CONSENT=true`; do not append a duplicate marker). Unless `FROM_BROWSER` is already configured, setup saves the browsers that supplied complete cookies as a comma-separated `FROM_BROWSER` list, including Chromium browsers. It normalizes Windows Firefox's `firefox-wsl` source label to `firefox`. It never saves cookie values. Later research reads those browser sessions again; macOS Keychain may prompt again. `BROWSER_CONSENT=false` or `FROM_BROWSER=off` disables native browser and CDP reads. A later consented setup replaces a saved refusal, but an explicit `FROM_BROWSER=off` still blocks reads until changed. The `agentcookie` sidecar is controlled separately by `AGENTCOOKIE=off`. Setup also best-effort installs yt-dlp (YouTube), the free keyless Digg CLI (`digg-pp-cli` via `@mvanhorn/printing-press-library install digg --cli-only`; activates only when on the agent subprocess PATH, typically `$HOME/.local/bin`; reports honestly if off-PATH; recommend-only if `npx` is unavailable), plus the free keyless arXiv and Techmeme CLIs.
|
|
134
|
+
- **Extras hosts (Linux / Mac mini / Darwin agentcookie sink) — a MacBook SKIPS this, and so does a `LAST30DAYS_HOST=grok-bot` host.** On these hosts the Chrome cookie store can't be decrypted, so the extract above finds nothing and X stays empty unless you capture a LIVE login over CDP. Run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/box_chrome_login.py"` (prints the host-correct command; `--exec` launches it; a MacBook prints "no launch needed" and spawns nothing). When `box-chrome` is on PATH it launches a throwaway profile on the last30days extras port **18800** (`SAND_CHROME_REMOTE_DEBUG_PORT=18800`, not box-chrome's default): `CHROME_USER_DATA_DIR=/tmp/last30days-x-chrome SAND_CHROME_REMOTE_DEBUG_PORT=18800 box-chrome --new-window https://x.com/login`. Wait for the x.com login page, then HAND THE DESKTOP to the human to type — do NOT drive the form. After they sign in, append `BROWSER_CDP_URL=http://127.0.0.1:18800` to `.env` (never `AUTH_TOKEN`/`CT0`; keep `AGENTCOOKIE=off` during the harvest) and re-run `setup --allow-browser-cookies`. Full steps and the block/rate-limit stop rule: **X on Linux / Mac mini** below. This extras-host login only runs on the consented **yes** path; a waiting topic never skips it when the user said yes to X.
|
|
135
|
+
- On **no** → run `FROM_BROWSER=off "${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup`. Skips all cookie reads; still installs yt-dlp (YouTube), Digg, arXiv, and Techmeme, still writes `SETUP_COMPLETE`. The engine saves `BROWSER_CONSENT=false`, so later runs also skip native browser and CDP reads. If the invocation already includes a topic, immediately research it with `--no-browser-cookies`, then resume the deferred onboarding in the same run after the findings: the ScrapeCreators offer (step 4) and source tier (step 4b) if a key is saved. Do not re-ask cookie consent as part of the resume.
|
|
136
|
+
|
|
137
|
+
**3. Browser-data permission remediation (macOS only).** After consented `setup`, if stderr says `X cookie access was permission denied`, name the browser; if it says `Permission denied reading Cookies.binarycookies`, name Safari. Advise: `Check System Settings > Privacy & Security permissions for your terminal or agent app. Full Disk Access may help, but may not unlock another app's data. You can supply AUTH_TOKEN and CT0 manually or use XAI_API_KEY.` Offer ONE retry only when no research topic is waiting; otherwise continue with available sources.
|
|
138
|
+
|
|
139
|
+
**4. ScrapeCreators offer (every first run; consent before browser launch).** Explain: the key grants 10,000 free calls for TikTok/Instagram and spends credits on Reddit search with fewer than 5 free items, YouTube search with fewer than 3 yt-dlp videos, and transcript rescue. `LAST30DAYS_REDDIT_SC_MIN_ITEMS=0` / `LAST30DAYS_YT_SC_MIN_ITEMS=0` make backfills empty-only; `LAST30DAYS_REDDIT_BACKEND=scrapecreators` pins Reddit to SC. A YouTube backfill may fetch transcripts and retry. GitHub signup grants more free calls than web signup and opens a page for a short code. Ask, e.g.: `Want to add TikTok and Instagram? Your key also spends credits on Reddit search below 5, YouTube search below 3, and transcripts. LAST30DAYS_YT_SC_MIN_ITEMS=0 makes YouTube search empty-only. GitHub signup grants 10,000 free calls and opens a browser for a code. (yes / no)` **Wait for the answer.**
|
|
140
|
+
- On **yes** → two commands. FIRST run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup --github-start` in the FOREGROUND - it returns in ~1-2s with a `Your GitHub code: XXXX-XXXX` line plus a JSON blob, copies the code to the clipboard, and opens the browser. Read the `user_code` from that output and immediately tell the user: the code, that it's on their clipboard so they can just paste it (Cmd+V) on the GitHub page - do not make them hunt for it. (If `status == "already_registered"`, stop here - their existing key is active. If the output said the clipboard copy failed, tell them to type the code.) THEN run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/last30days.py" setup --github-poll` (background with a 5-min timeout, or foreground) and parse the **LAST** JSON line of its stdout for the final status. On success the engine persists the key automatically and returns `"persisted": true` with a MASKED `api_key` (never ask for or echo the raw key). Confirm the paid sources are active.
|
|
141
|
+
- On **success but `"persisted": false`** (auth completed yet the key write failed) → do NOT claim sources are active. Tell the user signup worked but saving failed, and have them add `SCRAPECREATORS_API_KEY=<key>` to `~/.config/last30days/.env` manually (the raw key is masked in output, so re-run `setup --github` or retrieve it from scrapecreators.com to get the value).
|
|
142
|
+
- On **`status == "error"` with `message == "Authorized but failed to fetch API key"`** (exact match; often `"reason": "no_api_key"`) → GitHub authorized fine, so do NOT say auth failed. This usually means the GitHub account is already linked to a ScrapeCreators account. Tell the user: "GitHub authorized, but I couldn't auto-grab your ScrapeCreators key - your GitHub is probably already linked to an account. Get your key at scrapecreators.com and paste it, or Skip." Accept a pasted key or offer web/skip.
|
|
143
|
+
- On **`status == "error"` with `message` containing `ScrapeCreators profile failed`** (or `"reason": "upstream_error"`) → GitHub authorized fine; do NOT say auth failed, and do **NOT** say the account is already linked. ScrapeCreators returned a server error after auth. Tell the user: "GitHub authorized, but ScrapeCreators had a server error minting your key - this is on their side, not yours. Sign up or log in at scrapecreators.com, grab a key there, and paste it, or Skip / retry later." Accept a pasted key or offer web/skip.
|
|
144
|
+
- On **timeout, or any other error** → tell the user it didn't complete and offer to retry or the web signup at scrapecreators.com.
|
|
145
|
+
- On **no** → note they can run it later by asking to set up ScrapeCreators, then continue.
|
|
146
|
+
|
|
147
|
+
**4b. Source tier (after saving a key).** Comments default on: TikTok/Instagram posts and top comments, plus YouTube comments. Backfills run regardless of tier: Reddit with fewer than 5 free items, YouTube below 3 yt-dlp videos. Ask: `Recommended adds TikTok + Instagram posts and top comments, plus YouTube comments. Everything also adds Threads + Pinterest (more credits). (recommended / everything)` **Wait for the answer.**
|
|
148
|
+
- On **recommended** → append `INCLUDE_SOURCES=tiktok,instagram,youtube_comments,tiktok_comments,instagram_comments` to `~/.config/last30days/.env` (include `tiktok,instagram` so they are not treated as excluded). Confirm posts + top comments for TikTok/Instagram/YouTube are on.
|
|
149
|
+
- On **everything** → append `INCLUDE_SOURCES=tiktok,instagram,youtube_comments,tiktok_comments,instagram_comments,threads,pinterest`. Confirm Threads and Pinterest are on too.
|
|
150
|
+
|
|
151
|
+
**5. Complete.** Once `SETUP_COMPLETE=true` is written, proceed to research. Setup stdout is what this run installed, not the runtime source list; the engine diagnostic at research confirmation is authoritative. For Codex desktop, Cursor, Gemini CLI, and raw folder-mode hosts, hidden `.claude/last30days.env` project config is ignored unless `LAST30DAYS_TRUST_PROJECT_CONFIG=1` is set from the process environment or global config; only report a project file as active when the engine reports it as the config source.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### Grok Bot Prose Flow
|
|
156
|
+
|
|
157
|
+
For a Grok Bot host. The GROK BOT HOST RULE in the root SKILL.md applies throughout: `LAST30DAYS_HOST=grok-bot` is exported in every shell that runs the engine, including every command below. Same conversational shape as the Non-Modal Prose Flow, no modals, and the bot's own X tools come first: on this host the engine never reads a browser session and X runs only through official access. Run in order; wait where it says to wait.
|
|
158
|
+
|
|
159
|
+
**1. Host key + welcome.** Persist the host signal so later runs and `doctor` see it: if `~/.config/last30days/.env` is missing, `mkdir -p ~/.config/last30days && touch ~/.config/last30days/.env`; then append one line `LAST30DAYS_HOST=grok-bot` (append-only with `>>`; never `>`). Then run `"${LAST30DAYS_PYTHON:-python3}" "${SKILL_DIR}/scripts/last30days.py" --welcome` and show its stdout VERBATIM.
|
|
160
|
+
|
|
161
|
+
**2. X tools (primary - check this BEFORE offering any key).** Look for an X post-search tool in this session, for example `search_posts_all`: first Grok Bot's built-in X tools (namespace `x`), then the "X for Grok Bot" plugin's tools. Any tool that searches posts by query counts; tell the two apart by where the tool comes from, never by its name.
|
|
162
|
+
- **Present** → tell the user: `X is ready - it runs through Grok Bot's own X access with full 30-day coverage, nothing to configure.` On every research run follow the Grok Bot X recipe in Research Execution, and export `LAST30DAYS_X_HOST_LANE=1` next to `LAST30DAYS_HOST=grok-bot` only in an engine shell whose fetch returned posts: one `topic` call sized 10 / 30 / 60 by depth (`--quick` / default / `--deep`) with `-is:retweet` and the window; per `--x-handle`, a `from` call of 8 and a `mention` call of 5; per `--x-related` handle, a `related` call of 3; written to a `last30days-x-posts/1` envelope (`generated_at`, `topic`, `window`, `status`, `calls` tagged `topic` / `from` / `mention` / `related`, each post exactly `id`, `author_handle`, `created_at`, `text`, `likes`, `reposts`, `replies`, `quotes` and nothing else); if the tool rejects the window or count parameters, omit them and write `status: partial` with `error: window-unsupported`; pass the file as `--x-posts <file>`. Skip step 3.
|
|
163
|
+
- **Absent** → continue to step 3.
|
|
164
|
+
|
|
165
|
+
**3. Backup key (only when neither is present).** Say, then WAIT: `This session has no X search tools, so X needs a key. Best fix: add the "X for Grok Bot" plugin and connect X inside Grok Bot (it provisions an X developer account for you, with credits included). Otherwise paste an X_BEARER_TOKEN from the X developer console - recent posts, about the last week, unless your X developer project has full-archive access - or an XAI_API_KEY from console.x.ai. Or skip X for now. (bearer / xai / skip)`
|
|
166
|
+
- On a pasted key → persist it ONLY through the engine's key-write path, feeding stdin from a single-quoted heredoc. Never an ad-hoc shell write of the value, never interpolate it into a command line:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
"${LAST30DAYS_PYTHON:-python3}" "${SKILL_DIR}/scripts/last30days.py" setup --store-key X_BEARER_TOKEN <<'KEY_EOF'
|
|
170
|
+
{PASTED_VALUE}
|
|
171
|
+
KEY_EOF
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Use `setup --store-key XAI_API_KEY` for an xAI key. The engine prints `X_BEARER_TOKEN=****` (or `XAI_API_KEY=****`) plus a JSON `persisted` line; never echo the value back, and confirm only in the masked `NAME=****` form. Running it again with a new value replaces the stored one (that is how a rejected key is rotated). A `"persisted": false` means the write failed: say so and do not claim X is active.
|
|
175
|
+
- On **skip** → append `X_DECLINED=grok-bot` to `~/.config/last30days/.env` (append-only) so later runs stay quiet about X: no unlock pitch, no second key question. If the invocation already includes a topic, research it right after step 4 and resume steps 5-6 after the findings.
|
|
176
|
+
|
|
177
|
+
**4. Setup (free CLIs, no browser reads).** Run `"${LAST30DAYS_PYTHON:-python3}" "${SKILL_DIR}/scripts/last30days.py" setup` (the plain form; on this host it reads nothing from a browser). It best-effort installs yt-dlp (YouTube), the Digg CLI, arXiv, and Techmeme and writes `SETUP_COMPLETE=true`. Show what was installed, including whether Digg landed on PATH.
|
|
178
|
+
|
|
179
|
+
**5. ScrapeCreators offer and source tier.** Run steps 4 and 4b of the Non-Modal Prose Flow exactly as written there (GitHub device-code signup with `setup --github-start` then `setup --github-poll`; the engine persists the key and masks it).
|
|
180
|
+
|
|
181
|
+
**6. Complete.** Confirm `SETUP_COMPLETE=true` is in `~/.config/last30days/.env` (append it if `setup` did not run) and proceed to research (with the host signal, and `LAST30DAYS_X_HOST_LANE=1` exported when the X fetch returned posts). Setup stdout is what this run installed, not the runtime source list; the engine diagnostic at research confirmation is authoritative.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### Manual Setup Guide
|
|
186
|
+
|
|
187
|
+
Shown when a Claude Code user picks "Manual setup", or for anyone who wants to configure by hand. Present as plain text (not blockquoted).
|
|
188
|
+
|
|
189
|
+
The magic of /last30days is Reddit comments + X posts together - and both are free. Add these to `~/.config/last30days/.env`:
|
|
190
|
+
|
|
191
|
+
**X/Twitter (pick one - the most important source):**
|
|
192
|
+
- `X_BEARER_TOKEN=xxx` - the official X API v2 (`api.x.com`) with an app-only bearer from the X developer console. Recent posts, about a week back on the Basic tier, unless your X developer project has full-archive access. Persist it with `setup --store-key X_BEARER_TOKEN` (value on stdin, masked in output). Outside a Grok Bot host also add `LAST30DAYS_X_BACKEND=xapi` so the engine selects it (`doctor` says so when the bearer is set without it).
|
|
193
|
+
- **Grok CLI (no X credential):** install with `curl -fsSL https://x.ai/cli/install.sh | bash`, then `grok login`. No X account, no cookies, no API key. Needs a Grok plan; calls draw on it.
|
|
194
|
+
- `FROM_BROWSER=auto` - free. Reads your x.com login cookies live at search time: Firefox and Safari first, then Chromium browsers on macOS; Firefox only on Windows and Linux, with a Windows Firefox fallback under WSL. Cookie values are never saved to disk. Chromium reads may prompt for macOS Keychain access again; choose Always Allow for continuing OS access. A saved `BROWSER_CONSENT=false` blocks these reads until you consent again. Set `BROWSER_CONSENT=false` or `FROM_BROWSER=off` to disable native browser and CDP reads.
|
|
195
|
+
- `XAI_API_KEY=xxx` - no browser access needed. Get a key at api.x.ai. Best for servers.
|
|
196
|
+
- `XQUIK_API_KEY=xxx` - keyless-style X via Xquik.
|
|
197
|
+
- `AUTH_TOKEN=xxx` + `CT0=xxx` - paste your X cookies manually (x.com → F12 → Application → Cookies).
|
|
198
|
+
|
|
199
|
+
**X on a Grok Bot (repair).** On a `LAST30DAYS_HOST=grok-bot` host X runs only through official access, so if X returns nothing there: confirm Grok Bot's built-in X tools (namespace `x`) are in the session and the fetch ran before the engine command; otherwise add the "X for Grok Bot" plugin and connect X inside Grok Bot (the X connector lane), or add `X_BEARER_TOKEN` (recent posts, about the last week, unless your X developer project has full-archive access) or `XAI_API_KEY` from console.x.ai through `setup --store-key <NAME>`, or, when the footer reports X API credits exhausted (`payment-required`), top up credits in the X developer console. Nothing in the Linux / Mac mini repair section below applies on a Grok Bot.
|
|
200
|
+
|
|
201
|
+
**X on Linux / Mac mini (repair).** These hosts (never a `LAST30DAYS_HOST=grok-bot` host) can't decrypt a local Chrome cookie store, so if X returns nothing there, feed bird a cookie pair one of these ways (a MacBook does NOT do any of this — it uses its Keychain / Firefox / Safari extract; do not launch box-chrome on a MacBook):
|
|
202
|
+
- **agentcookie sidecar:** install the `agentcookie` CLI so the engine can read your `auth_token`/`ct0` from it automatically. Nothing to configure; `AGENTCOOKIE=off` disables it.
|
|
203
|
+
- **Live Chrome login over CDP (the proven extras-host path).** The engine reads a live signed-in Chrome over the DevTools Protocol, but you must LAUNCH that Chrome on the extras port and log in first — `setup --allow-browser-cookies` alone opens no window. Steps (a MacBook skips all of this):
|
|
204
|
+
1. Set `AGENTCOOKIE=off` for this harvest so a sidecar can't mix in a different pair (leave it unset again after success).
|
|
205
|
+
2. Launch a throwaway login Chrome on the last30days extras port **18800** — this port is the last30days convention (`SAND_CHROME_REMOTE_DEBUG_PORT=18800`), NOT box-chrome's built-in default (`9222` + the display number). **Launch via the host `box-chrome` wrapper** (which sets `--class=box-chrome`); do NOT launch raw `google-chrome-stable`, and in particular do NOT launch raw Chrome with a custom `--class` — a raw Chrome with `--class=l30d-…` failed where `box-chrome` (class `box-chrome`) succeeded. Do NOT rely on any `GrokAgent` user-agent token: it is not required and may be disabled on the host (`/tmp/sand-ua-token-disabled`), so never tell users it must be present. Run `"${LAST30DAYS_PYTHON}" "${SKILL_DIR}/scripts/box_chrome_login.py"` to print the exact host-correct command (add `--exec` to launch it); on a MacBook it prints "no launch needed" and spawns nothing. When `box-chrome` is on PATH the command is:
|
|
206
|
+
```
|
|
207
|
+
mkdir -p /tmp/last30days-x-chrome
|
|
208
|
+
CHROME_USER_DATA_DIR=/tmp/last30days-x-chrome SAND_CHROME_REMOTE_DEBUG_PORT=18800 box-chrome --new-window https://x.com/login
|
|
209
|
+
```
|
|
210
|
+
If `box-chrome` is missing, do NOT invent a google-chrome flag soup (a raw Chrome with a custom `--class` is what failed) — sign into x.com in a Chrome that already exposes a remote-debugging port and pin `BROWSER_CDP_URL` to that endpoint instead.
|
|
211
|
+
3. Do NOT fill the login form and do NOT drive the page (no Playwright/Puppeteer/computerUse/xdotool, no typing credentials). Wait until the x.com login page is actually visible, THEN hand the desktop / computer-preview to the human: "Log into X in this Chrome window." (A HUD over the login is OK only as a handoff, never to click the page.)
|
|
212
|
+
4. After they hand back, confirm the window is signed in (x.com/home). Append `BROWSER_CDP_URL=http://127.0.0.1:<port>` to `~/.config/last30days/.env` (append-only), using the debug port the Chrome actually listens on — `18800` if you launched with the command above, or the real port otherwise (a live harvest pinned `http://127.0.0.1:9334`). Do NOT write `AUTH_TOKEN` or `CT0` — the pair is read live each run. Then run `setup --allow-browser-cookies`; extras CDP reads the live pair. Pinning `BROWSER_CDP_URL` after login is also the guard if a stale/logged-out Chrome happens to answer on `18800`.
|
|
213
|
+
5. If X shows a block / challenge / rate-limit, STOP — tell them to wait and retry later. Do not keep launching Chromes.
|
|
214
|
+
- **`XAI_API_KEY=xxx`** - key-based X, no browser at all.
|
|
215
|
+
- **Grok CLI** - run `grok login`, then pin `LAST30DAYS_X_BACKEND=grok` (pin-only; a leftover grok login never auto-steals the X lane). Needs a Grok plan.
|
|
216
|
+
|
|
217
|
+
**Reddit (free, works out of the box):**
|
|
218
|
+
- Free keyless discovery (Reddit search + shreddit) gives threads + top comments with upvote counts. No setup required.
|
|
219
|
+
- `SCRAPECREATORS_API_KEY=xxx` - optional Reddit search backfill when the free path returns **fewer than 5 items** (default floor). A run makes at most one ScrapeCreators Reddit backfill per distinct query, date window, and subreddit set, and a backfill is several API calls (searches plus comment fetches, roughly 10 at default depth). Set `LAST30DAYS_REDDIT_SC_MIN_ITEMS=0` to backfill only when the free path returns nothing, or `LAST30DAYS_REDDIT_BACKEND=scrapecreators` to make ScrapeCreators primary.
|
|
220
|
+
|
|
221
|
+
**YouTube (free, open source):**
|
|
222
|
+
- Run `brew install yt-dlp` (or `pip install yt-dlp`) - enables YouTube search + transcripts.
|
|
223
|
+
- `SCRAPECREATORS_API_KEY=xxx` - paid search backfill below 3 yt-dlp videos and transcript rescue. `LAST30DAYS_YT_SC_MIN_ITEMS=0` makes search empty-only; a retry may spend again.
|
|
224
|
+
|
|
225
|
+
**Digg (free, keyless):**
|
|
226
|
+
- Run `npx @mvanhorn/printing-press-library install digg --cli-only` - installs the Digg CLI for trending news, GitHub stars, and pipeline feeds. Activates when `digg-pp-cli` is on your PATH (typically `$HOME/.local/bin`).
|
|
227
|
+
|
|
228
|
+
**GitHub Issues/PRs (free, no key needed):**
|
|
229
|
+
- If the `gh` CLI is installed and authed (`brew install gh && gh auth login`), GitHub search is automatic. No API key required.
|
|
230
|
+
|
|
231
|
+
**Bonus: TikTok, Instagram, YouTube comments (ScrapeCreators):**
|
|
232
|
+
- `SCRAPECREATORS_API_KEY=xxx` - 10,000 free calls at scrapecreators.com.
|
|
233
|
+
- After adding your key, set `INCLUDE_SOURCES=tiktok,instagram` to turn on the popular ones. (Threads, Pinterest, LinkedIn, and Meta Ads are also available via `INCLUDE_SOURCES=threads,pinterest,linkedin,meta_ads` for power users.)
|
|
234
|
+
|
|
235
|
+
**Other optional sources (add anytime):**
|
|
236
|
+
- `PERPLEXITY_API_KEY=xxx` - preferred Agent/Search API path with citations; set `INCLUDE_SOURCES=perplexity`. Existing `OPENROUTER_API_KEY` installs keep the synchronous Sonar fallback.
|
|
237
|
+
- `XIAOHONGSHU_API_BASE=http://localhost:18060` - Xiaohongshu/RED via a logged-in x-mcp browser plugin or `xiaohongshu-mcp` service; optional unless the local service runs on a custom URL. Opt in per run with `--search xhs`, or persistently via `INCLUDE_SOURCES=xiaohongshu`.
|
|
238
|
+
- DripStack (premium financial newsletter search) is opt-in only: per run with `--search dripstack`, or persistently via `INCLUDE_SOURCES=dripstack`. Free public search API, no key; never active without the opt-in.
|
|
239
|
+
- Telegram (public channels) is opt-in via `--telegram-sources=handle1,handle2` (auto-activates for that run) or persistently via `TELEGRAM_SOURCES=handles` + `INCLUDE_SOURCES=telegram`. Requires `SCRAPECREATORS_API_KEY`. Named public channels only; no keyword discovery.
|
|
240
|
+
- `BSKY_HANDLE=you.bsky.social` + `BSKY_APP_PASSWORD=xxx` - Bluesky (free app password).
|
|
241
|
+
- `BRAVE_API_KEY=xxx` or `EXA_API_KEY=xxx` - web search backends.
|
|
242
|
+
|
|
243
|
+
**CRITICAL: NEVER overwrite an existing `.env`.** Before writing ANY key:
|
|
244
|
+
1. Check if the file exists: `test -f ~/.config/last30days/.env`
|
|
245
|
+
2. If it exists, READ it, then APPEND only missing keys with `>>` (double redirect).
|
|
246
|
+
3. NEVER use `>` (single redirect) - it destroys existing content.
|
|
247
|
+
4. If it doesn't exist: `mkdir -p ~/.config/last30days && touch ~/.config/last30days/.env`
|
|
248
|
+
|
|
249
|
+
Always add this last line: `SETUP_COMPLETE=true`. Then proceed to research.
|
|
250
|
+
|
|
251
|
+
The setup wizard's mechanical work lives in a Python module so it runs across all hosts (Claude Code, Codex, Cursor, etc.) while you drive the consent conversation above. The common-case (already set up) path through this file stays short.
|
|
252
|
+
|
|
253
|
+
---
|