luckiest-co 1.0.46 → 1.0.48

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.
Files changed (266) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/skills/ab-testing/CHANGELOG.md +5 -0
  5. package/skills/ab-testing/SKILL.md +12 -9
  6. package/skills/ab-testing/evals/evals.json +18 -7
  7. package/skills/ab-testing/references/sample-size-guide.md +2 -2
  8. package/skills/ads/CHANGELOG.md +5 -0
  9. package/skills/ads/SKILL.md +4 -4
  10. package/skills/ai-seo/CHANGELOG.md +7 -0
  11. package/skills/ai-seo/SKILL.md +27 -20
  12. package/skills/analytics/CHANGELOG.md +6 -0
  13. package/skills/analytics/SKILL.md +4 -3
  14. package/skills/analytics/evals/evals.json +27 -0
  15. package/skills/analytics/references/ga4-implementation.md +7 -3
  16. package/skills/analytics/references/gtm-implementation.md +8 -2
  17. package/skills/aso/CHANGELOG.md +5 -0
  18. package/skills/aso/SKILL.md +4 -4
  19. package/skills/aso/evals/evals.json +15 -2
  20. package/skills/aso/references/benchmarks.md +4 -2
  21. package/skills/aso/references/google-play-specs.md +5 -1
  22. package/skills/brainstorm/SKILL.md +59 -16
  23. package/skills/brainstorm/builder-check-prompt.md +23 -0
  24. package/skills/churn-prevention/CHANGELOG.md +5 -0
  25. package/skills/churn-prevention/SKILL.md +3 -3
  26. package/skills/churn-prevention/evals/evals.json +14 -0
  27. package/skills/churn-prevention/references/dunning-playbook.md +6 -1
  28. package/skills/coder-brainstorming/CHANGELOG.md +10 -0
  29. package/skills/coder-brainstorming/SKILL.md +62 -20
  30. package/skills/coder-brainstorming/builder-check-prompt.md +23 -0
  31. package/skills/coder-brainstorming/scripts/server.cjs +1 -1
  32. package/skills/coder-debugging/CHANGELOG.md +4 -0
  33. package/skills/coder-debugging/SKILL.md +5 -5
  34. package/skills/coder-requesting-code-review/CHANGELOG.md +4 -0
  35. package/skills/coder-requesting-code-review/SKILL.md +4 -4
  36. package/skills/coder-subagent-driven-development/CHANGELOG.md +4 -0
  37. package/skills/coder-subagent-driven-development/SKILL.md +3 -3
  38. package/skills/coder-subagent-driven-development/scripts/sdd-workspace +3 -1
  39. package/skills/cold-email/CHANGELOG.md +7 -0
  40. package/skills/cold-email/SKILL.md +27 -7
  41. package/skills/cold-email/evals/evals.json +30 -0
  42. package/skills/cold-email/references/benchmarks.md +15 -11
  43. package/skills/cold-email/references/deliverability.md +110 -0
  44. package/skills/cold-email/references/linkedin-outreach.md +91 -0
  45. package/skills/cold-email/references/multichannel-cadence.md +79 -0
  46. package/skills/cold-email/references/outbound-plays.md +88 -0
  47. package/skills/cold-email/references/personalization.md +11 -0
  48. package/skills/cold-email/references/reply-handling.md +65 -0
  49. package/skills/competitor-profiling/CHANGELOG.md +5 -0
  50. package/skills/competitor-profiling/SKILL.md +4 -4
  51. package/skills/content-strategy/CHANGELOG.md +5 -0
  52. package/skills/content-strategy/SKILL.md +5 -3
  53. package/skills/copy-editing/CHANGELOG.md +5 -0
  54. package/skills/copy-editing/SKILL.md +5 -5
  55. package/skills/copy-editing/references/content-refresh.md +33 -0
  56. package/skills/cro/CHANGELOG.md +5 -0
  57. package/skills/cro/SKILL.md +14 -3
  58. package/skills/cro/references/form.md +1 -1
  59. package/skills/directory-submissions/CHANGELOG.md +5 -0
  60. package/skills/directory-submissions/SKILL.md +13 -4
  61. package/skills/directory-submissions/evals/evals.json +15 -1
  62. package/skills/directory-submissions/references/directory-list.md +2 -4
  63. package/skills/emails/CHANGELOG.md +5 -0
  64. package/skills/emails/SKILL.md +14 -4
  65. package/skills/launch/CHANGELOG.md +7 -0
  66. package/skills/launch/SKILL.md +8 -4
  67. package/skills/launch/evals/evals.json +29 -0
  68. package/skills/launch/references/shipped-changes.md +84 -0
  69. package/skills/launch/references/site-launch-qa.md +55 -0
  70. package/skills/lead-magnets/CHANGELOG.md +5 -0
  71. package/skills/lead-magnets/SKILL.md +6 -4
  72. package/skills/lead-magnets/evals/evals.json +18 -0
  73. package/skills/lead-magnets/references/content-to-skill-corpus-design.md +89 -0
  74. package/skills/lead-magnets/references/content-to-skill-release.md +71 -0
  75. package/skills/lead-magnets/references/content-to-skill.md +108 -0
  76. package/skills/marketing-plan/CHANGELOG.md +5 -0
  77. package/skills/marketing-plan/SKILL.md +3 -3
  78. package/skills/marketing-plan/evals/evals.json +13 -0
  79. package/skills/marketing-plan/references/budget-planning.md +26 -17
  80. package/skills/pricing/CHANGELOG.md +5 -0
  81. package/skills/pricing/SKILL.md +4 -4
  82. package/skills/pricing/evals/evals.json +12 -0
  83. package/skills/pricing/references/research-methods.md +8 -4
  84. package/skills/programmatic-seo/CHANGELOG.md +5 -0
  85. package/skills/programmatic-seo/SKILL.md +8 -4
  86. package/skills/programmatic-seo/evals/evals.json +13 -0
  87. package/skills/programmatic-seo/references/implementation-platforms.md +37 -0
  88. package/skills/prospecting/CHANGELOG.md +7 -0
  89. package/skills/prospecting/SKILL.md +27 -13
  90. package/skills/prospecting/evals/evals.json +41 -0
  91. package/skills/prospecting/references/account-research.md +59 -0
  92. package/skills/prospecting/references/b2b-prospecting.md +1 -1
  93. package/skills/prospecting/references/compliance.md +44 -6
  94. package/skills/prospecting/references/data-sources.md +13 -15
  95. package/skills/prospecting/references/enrichment-playbook.md +61 -0
  96. package/skills/prospecting/references/saas-prospecting.md +1 -1
  97. package/skills/prospecting/references/signal-plays.md +99 -0
  98. package/skills/prospecting/references/sourcing-and-enrichment.md +89 -0
  99. package/skills/referrals/CHANGELOG.md +5 -0
  100. package/skills/referrals/SKILL.md +10 -4
  101. package/skills/referrals/evals/evals.json +99 -0
  102. package/skills/referrals/references/affiliate-operations.md +64 -0
  103. package/skills/referrals/references/affiliate-programs.md +2 -2
  104. package/skills/referrals/references/program-examples.md +14 -6
  105. package/skills/referrals/references/review-platform-rules.md +36 -0
  106. package/skills/referrals/references/review-program-operations.md +62 -0
  107. package/skills/referrals/references/review-programs.md +96 -0
  108. package/skills/revops/CHANGELOG.md +5 -0
  109. package/skills/revops/SKILL.md +10 -6
  110. package/skills/revops/evals/evals.json +13 -0
  111. package/skills/revops/references/automation-playbooks.md +1 -1
  112. package/skills/revops/references/inbound-routing.md +43 -0
  113. package/skills/revops/references/lifecycle-definitions.md +31 -0
  114. package/skills/sales-enablement/CHANGELOG.md +5 -0
  115. package/skills/sales-enablement/SKILL.md +6 -4
  116. package/skills/sales-enablement/evals/evals.json +14 -0
  117. package/skills/sales-enablement/references/cold-call-scripts.md +68 -0
  118. package/skills/schema/CHANGELOG.md +5 -0
  119. package/skills/schema/SKILL.md +20 -8
  120. package/skills/schema/evals/evals.json +40 -0
  121. package/skills/schema/references/schema-examples.md +9 -15
  122. package/skills/seo-audit/CHANGELOG.md +5 -0
  123. package/skills/seo-audit/SKILL.md +10 -16
  124. package/skills/seo-audit/evals/evals.json +68 -0
  125. package/skills/seo-audit/references/international-seo.md +9 -11
  126. package/skills/seo-audit/references/local-seo-pages-and-measurement.md +39 -0
  127. package/skills/seo-audit/references/local-seo-profile-and-citations.md +41 -0
  128. package/skills/seo-audit/references/local-seo.md +86 -0
  129. package/skills/seo-audit/references/rankings-push.md +62 -0
  130. package/skills/seo-audit/references/title-tags.md +89 -0
  131. package/skills/signup/CHANGELOG.md +5 -0
  132. package/skills/signup/SKILL.md +7 -3
  133. package/skills/site-architecture/CHANGELOG.md +5 -0
  134. package/skills/site-architecture/SKILL.md +5 -4
  135. package/skills/site-architecture/evals/evals.json +25 -0
  136. package/skills/site-architecture/references/platforms-and-migration.md +84 -0
  137. package/skills/social/CHANGELOG.md +5 -0
  138. package/skills/social/SKILL.md +4 -4
  139. package/skills/trends/.skillignore +3 -0
  140. package/skills/trends/CHANGELOG.md +20 -0
  141. package/skills/trends/SKILL.md +12 -8
  142. package/skills/trends/references/agent-mode.md +36 -0
  143. package/skills/trends/references/category-peers.md +38 -0
  144. package/skills/trends/references/comparison.md +227 -0
  145. package/skills/trends/references/competitors.md +29 -0
  146. package/skills/trends/references/discovery.md +90 -0
  147. package/skills/trends/references/engine-contract.md +111 -1889
  148. package/skills/trends/references/followups.md +112 -0
  149. package/skills/trends/references/grok-bot-x.md +55 -0
  150. package/skills/trends/references/hiring-signals.md +19 -0
  151. package/skills/trends/references/library-queue.md +33 -0
  152. package/skills/trends/references/recommendations.md +86 -0
  153. package/skills/trends/references/research-runbook.md +781 -0
  154. package/skills/trends/references/runtime.md +165 -0
  155. package/skills/trends/references/save-html-brief.md +39 -20
  156. package/skills/trends/references/setup-wizard.md +253 -0
  157. package/skills/trends/references/synthesis.md +289 -0
  158. package/skills/trends/scripts/box_chrome_login.py +209 -0
  159. package/skills/trends/scripts/briefing.py +9 -17
  160. package/skills/trends/scripts/last30days.py +3247 -280
  161. package/skills/trends/scripts/lib/agentcookie.py +196 -0
  162. package/skills/trends/scripts/lib/amazon.py +877 -0
  163. package/skills/trends/scripts/lib/arxiv.py +42 -8
  164. package/skills/trends/scripts/lib/backends.py +769 -0
  165. package/skills/trends/scripts/lib/bird_x.py +340 -57
  166. package/skills/trends/scripts/lib/bluesky.py +84 -10
  167. package/skills/trends/scripts/lib/bounded_get.py +156 -0
  168. package/skills/trends/scripts/lib/brightdata.py +291 -0
  169. package/skills/trends/scripts/lib/categories.py +7 -1
  170. package/skills/trends/scripts/lib/chrome_cdp.py +455 -0
  171. package/skills/trends/scripts/lib/chrome_cookies.py +132 -39
  172. package/skills/trends/scripts/lib/cluster.py +31 -54
  173. package/skills/trends/scripts/lib/competitors.py +10 -1
  174. package/skills/trends/scripts/lib/cookie_extract.py +221 -44
  175. package/skills/trends/scripts/lib/cookie_paths.py +27 -0
  176. package/skills/trends/scripts/lib/corpus.py +439 -0
  177. package/skills/trends/scripts/lib/devto.py +313 -0
  178. package/skills/trends/scripts/lib/discovery_handoff.py +988 -0
  179. package/skills/trends/scripts/lib/doctor.py +2166 -0
  180. package/skills/trends/scripts/lib/dripstack.py +203 -0
  181. package/skills/trends/scripts/lib/entity_extract.py +37 -0
  182. package/skills/trends/scripts/lib/env.py +1092 -138
  183. package/skills/trends/scripts/lib/fanout.py +11 -1
  184. package/skills/trends/scripts/lib/feed.py +61 -0
  185. package/skills/trends/scripts/lib/fetch_memo.py +135 -0
  186. package/skills/trends/scripts/lib/freshness.py +566 -0
  187. package/skills/trends/scripts/lib/fusion.py +314 -28
  188. package/skills/trends/scripts/lib/github.py +438 -24
  189. package/skills/trends/scripts/lib/grok_x.py +1237 -0
  190. package/skills/trends/scripts/lib/grounding.py +129 -11
  191. package/skills/trends/scripts/lib/hackernews.py +61 -4
  192. package/skills/trends/scripts/lib/health.py +373 -0
  193. package/skills/trends/scripts/lib/hosted.py +333 -0
  194. package/skills/trends/scripts/lib/html_publish.py +34 -0
  195. package/skills/trends/scripts/lib/html_render.py +169 -3
  196. package/skills/trends/scripts/lib/http.py +1215 -70
  197. package/skills/trends/scripts/lib/instagram.py +132 -9
  198. package/skills/trends/scripts/lib/jobs.py +4 -2
  199. package/skills/trends/scripts/lib/library.py +308 -0
  200. package/skills/trends/scripts/lib/library_index.py +477 -0
  201. package/skills/trends/scripts/lib/linkedin.py +271 -24
  202. package/skills/trends/scripts/lib/log.py +5 -2
  203. package/skills/trends/scripts/lib/meta_ads.py +982 -0
  204. package/skills/trends/scripts/lib/normalize.py +382 -28
  205. package/skills/trends/scripts/lib/parallel_mcp.py +234 -0
  206. package/skills/trends/scripts/lib/permission_preflight.py +42 -3
  207. package/skills/trends/scripts/lib/perplexity.py +1080 -468
  208. package/skills/trends/scripts/lib/pipeline.py +5079 -211
  209. package/skills/trends/scripts/lib/planner.py +396 -53
  210. package/skills/trends/scripts/lib/polymarket.py +285 -23
  211. package/skills/trends/scripts/lib/prescriptions.py +432 -0
  212. package/skills/trends/scripts/lib/private_files.py +21 -0
  213. package/skills/trends/scripts/lib/providers.py +231 -10
  214. package/skills/trends/scripts/lib/quality_nudge.py +122 -37
  215. package/skills/trends/scripts/lib/reddit.py +177 -12
  216. package/skills/trends/scripts/lib/reddit_arctic.py +146 -5
  217. package/skills/trends/scripts/lib/reddit_keyless.py +121 -60
  218. package/skills/trends/scripts/lib/reddit_listing.py +215 -14
  219. package/skills/trends/scripts/lib/reddit_public.py +12 -13
  220. package/skills/trends/scripts/lib/reddit_search.py +304 -0
  221. package/skills/trends/scripts/lib/reddit_shreddit.py +51 -7
  222. package/skills/trends/scripts/lib/registers.py +134 -0
  223. package/skills/trends/scripts/lib/relevance.py +17 -0
  224. package/skills/trends/scripts/lib/render.py +2161 -271
  225. package/skills/trends/scripts/lib/rerank.py +623 -55
  226. package/skills/trends/scripts/lib/resolve.py +50 -1
  227. package/skills/trends/scripts/lib/safari_cookies.py +42 -15
  228. package/skills/trends/scripts/lib/schema.py +782 -1
  229. package/skills/trends/scripts/lib/setup_wizard.py +779 -120
  230. package/skills/trends/scripts/lib/signals.py +206 -7
  231. package/skills/trends/scripts/lib/stocktwits.py +374 -0
  232. package/skills/trends/scripts/lib/subproc.py +307 -53
  233. package/skills/trends/scripts/lib/techmeme.py +89 -64
  234. package/skills/trends/scripts/lib/telegram.py +317 -0
  235. package/skills/trends/scripts/lib/tiktok.py +13 -9
  236. package/skills/trends/scripts/lib/topic_shape.py +340 -0
  237. package/skills/trends/scripts/lib/transcribe.py +12 -3
  238. package/skills/trends/scripts/lib/trustpilot.py +256 -5
  239. package/skills/trends/scripts/lib/truthsocial.py +9 -1
  240. package/skills/trends/scripts/lib/ui.py +6 -0
  241. package/skills/trends/scripts/lib/usage.py +132 -0
  242. package/skills/trends/scripts/lib/web_search_keyless.py +63 -4
  243. package/skills/trends/scripts/lib/windows_job.py +138 -0
  244. package/skills/trends/scripts/lib/x_api.py +824 -0
  245. package/skills/trends/scripts/lib/x_envelope.py +733 -0
  246. package/skills/trends/scripts/lib/x_judge.py +292 -0
  247. package/skills/trends/scripts/lib/xai_x.py +26 -3
  248. package/skills/trends/scripts/lib/xquik.py +89 -15
  249. package/skills/trends/scripts/lib/xurl_x.py +284 -91
  250. package/skills/trends/scripts/lib/youtube_yt.py +570 -119
  251. package/skills/trends/scripts/setup-keychain.sh +12 -4
  252. package/skills/trends/scripts/setup-pass.sh +3 -0
  253. package/skills/trends/scripts/store.py +444 -50
  254. package/skills/trends/scripts/watchlist.py +38 -11
  255. package/skills/trends/scripts/x_lane_parity.py +107 -0
  256. package/skills/video-studio/CHANGELOG.md +8 -0
  257. package/skills/video-studio/SKILL.md +6 -5
  258. package/skills/video-studio/references/styles/product-flight.md +45 -0
  259. package/skills/video-studio/references/styles.md +3 -1
  260. package/skills/video-studio/scripts/qa.mjs +64 -2
  261. package/skills/video-studio/scripts/qa.test.mjs +29 -1
  262. package/skills/video-studio-motion/ATTRIBUTION.md +1 -1
  263. package/skills/video-studio-motion/CHANGELOG.md +4 -0
  264. package/skills/video-studio-motion/SKILL.md +8 -5
  265. package/skills/coder-brainstorming/spec-document-reviewer-prompt.md +0 -49
  266. package/skills/trends/scripts/lib/reddit_rss.py +0 -224
@@ -0,0 +1,432 @@
1
+ """Fix-prescription registry: the single remediation vocabulary (KTD 7).
2
+
3
+ Each (source, failure mode) entry carries a cause line, a natural-language
4
+ fix, an exact CLI fix, and an optional CONFIGURATION.md anchor. Two real
5
+ consumers keep the vocabulary honest from day one:
6
+
7
+ - ``lib/quality_nudge.py`` builds its post-research fix text from these
8
+ entries (only the fix strings migrated here; trigger logic is untouched).
9
+ - The doctor aggregator (U4) looks entries up per failed source/backend.
10
+
11
+ Because both surfaces read the same entry, the nudge a user sees after a
12
+ degraded run and the prescription doctor prints for the same failure can
13
+ never drift apart.
14
+
15
+ Composition with the other health layers (reference, don't restate):
16
+
17
+ - U1 (``lib/health.py``) owns the machine-aware package-manager strings
18
+ (brew/pipx/apt/npx install-vs-reinstall, off-PATH PATH edits). Binary-class
19
+ entries here pull their static defaults from U1's tables, and
20
+ ``for_dependency_probe`` lets a live probe's machine-specific prescription
21
+ win the CLI form while the registry supplies cause/NL/anchor vocabulary.
22
+ - U2 (``lib/backends.py``) embeds this registry's CLI forms inside its
23
+ chain-failure prescriptions, so a backend finding and a registry lookup
24
+ agree on the command to run.
25
+
26
+ No secrets: CLI forms use obvious ``<placeholder>`` values only.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from dataclasses import dataclass, replace
32
+ from typing import Dict, Optional, Tuple
33
+
34
+ from . import env, health
35
+ from .x_api import BEARER_COVERAGE_NOTE
36
+
37
+ # Direct engine invocation prefix (scripting fallback; the slash-command UX
38
+ # is "ask the agent to run setup ...", which is the natural-language form).
39
+ ENGINE_CLI = "python3 skills/last30days/scripts/last30days.py"
40
+ SETUP_BROWSER_COOKIES_CLI = f"{ENGINE_CLI} setup --allow-browser-cookies"
41
+ SETUP_GITHUB_CLI = f"{ENGINE_CLI} setup --github"
42
+
43
+ # U1 owns these remediation strings; reference them instead of restating.
44
+ _YTDLP_BREW_INSTALL, _YTDLP_BREW_REINSTALL = health.static_prescription("yt-dlp", "brew")
45
+ _YTDLP_PIPX_REINSTALL = health.static_prescription("yt-dlp", "pipx")[1]
46
+ _DIGG_PP_INSTALL_CLI = health.pp_install_cmd("digg")
47
+
48
+ GENERIC_FIX_NL = "see CONFIGURATION.md for setup options for this source"
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class Prescription:
53
+ """Remediation for one (source, failure mode).
54
+
55
+ ``fix_nl`` is the natural-language form ("ask the agent to run setup
56
+ with browser-cookie consent"); ``fix_cli`` is the exact command.
57
+ ``alt_cli`` carries per-platform alternates (Windows/pip) when the
58
+ primary CLI form is macOS/brew. ``anchor`` is a CONFIGURATION.md
59
+ heading anchor ("" when the doc has no dedicated section).
60
+ """
61
+
62
+ source: str
63
+ failure: str
64
+ cause: str
65
+ fix_nl: str
66
+ fix_cli: str
67
+ alt_cli: Tuple[str, ...] = ()
68
+ anchor: str = ""
69
+
70
+
71
+ def _entry(source: str, failure: str, **kwargs) -> Tuple[Tuple[str, str], Prescription]:
72
+ return (source, failure), Prescription(source=source, failure=failure, **kwargs)
73
+
74
+
75
+ REGISTRY: Dict[Tuple[str, str], Prescription] = dict((
76
+ _entry(
77
+ "x", "cookies_missing",
78
+ cause="X browser cookies (AUTH_TOKEN/CT0) are not configured",
79
+ fix_nl=(
80
+ "log into x.com in your browser and re-run (cookies detected "
81
+ "automatically), or add XAI_API_KEY to your .env (get key at "
82
+ "api.x.ai), or add XQUIK_API_KEY to your .env (get key at xquik.com)"
83
+ ),
84
+ fix_cli=SETUP_BROWSER_COOKIES_CLI,
85
+ anchor="api-keys-env",
86
+ ),
87
+ _entry(
88
+ "x", "cookies_expired",
89
+ cause="X errored this run: cookies are configured but likely expired or revoked",
90
+ fix_nl="log into x.com in your browser, then re-run",
91
+ fix_cli=SETUP_BROWSER_COOKIES_CLI,
92
+ anchor="api-keys-env",
93
+ ),
94
+ _entry(
95
+ "x", "xai_error",
96
+ cause="the xAI X search backend failed this run",
97
+ fix_nl=(
98
+ "check XAI_API_KEY and its chat/model permissions in console.x.ai, "
99
+ "confirm LAST30DAYS_X_MODEL names an available model, then re-run"
100
+ ),
101
+ fix_cli="XAI_API_KEY=<your-xai-key> LAST30DAYS_X_MODEL=<available-xai-model>",
102
+ anchor="api-keys-env",
103
+ ),
104
+ _entry(
105
+ "x", "xai_payment_required",
106
+ cause="the xAI X search backend has no available credits",
107
+ fix_nl="check xAI billing and credits in console.x.ai, then re-run",
108
+ fix_cli=f'{ENGINE_CLI} "<topic>" --search x',
109
+ anchor="api-keys-env",
110
+ ),
111
+ _entry(
112
+ "x", "xai_rate_limited",
113
+ cause="the xAI X search backend hit its rate limit",
114
+ fix_nl="wait for the xAI rate limit to reset, then re-run",
115
+ fix_cli=f'{ENGINE_CLI} "<topic>" --search x',
116
+ anchor="api-keys-env",
117
+ ),
118
+ _entry(
119
+ "x", "xai_timeout",
120
+ cause="the xAI X search backend timed out",
121
+ fix_nl="xAI timed out; retry the run or select another configured X backend",
122
+ fix_cli=f'{ENGINE_CLI} "<topic>" --search x',
123
+ anchor="api-keys-env",
124
+ ),
125
+ _entry(
126
+ "x", "xai_unavailable",
127
+ cause="the xAI X search backend failed this run",
128
+ fix_nl="retry xAI later or select another configured X backend",
129
+ fix_cli=f'{ENGINE_CLI} "<topic>" --search x',
130
+ anchor="api-keys-env",
131
+ ),
132
+ _entry(
133
+ "x", "grok_cli_missing",
134
+ cause="the Grok CLI is not installed, so the keyless X path is unavailable",
135
+ fix_nl=(
136
+ "install the Grok CLI (curl -fsSL https://x.ai/cli/install.sh | bash) "
137
+ "and sign in with `grok login` to search X without any X credential"
138
+ ),
139
+ fix_cli="npm install -g @xai-official/grok",
140
+ anchor="api-keys-env",
141
+ ),
142
+ _entry(
143
+ "x", "grok_not_authenticated",
144
+ cause="the Grok CLI is installed but not signed in",
145
+ fix_nl="sign in to Grok once; no X account or API key is needed after that",
146
+ fix_cli="grok login",
147
+ anchor="api-keys-env",
148
+ ),
149
+ # Official X path (an official-only host per env.x_policy, or an explicit
150
+ # xapi pin). Copy is limited to the host X lanes (Grok Bot's built-in X
151
+ # tools, then the X for Grok Bot connector), X_BEARER_TOKEN, XAI_API_KEY,
152
+ # and X API credits; the bearer path is described as about a week, never
153
+ # as parity with the host lanes. Anchors point at
154
+ # the CONFIGURATION.md Grok Bot subsection (slug grok-bot).
155
+ _entry(
156
+ "x", "bearer_missing",
157
+ cause=(
158
+ "no official X path is configured (Grok Bot X tools, X connector, "
159
+ "X_BEARER_TOKEN, or XAI_API_KEY)"
160
+ ),
161
+ fix_nl=(
162
+ "fetch X with Grok Bot's built-in X tools and pass the posts with --x-posts, or add the X for Grok Bot "
163
+ "plugin and connect X in Grok Bot settings (full 30-day coverage), or set "
164
+ f"X_BEARER_TOKEN from the X developer console ({BEARER_COVERAGE_NOTE}), "
165
+ "or set XAI_API_KEY from console.x.ai"
166
+ ),
167
+ fix_cli="X_BEARER_TOKEN=<your-x-api-bearer-token>",
168
+ anchor="grok-bot",
169
+ ),
170
+ _entry(
171
+ "x", "bearer_invalid",
172
+ cause="the X API rejected X_BEARER_TOKEN (401/403)",
173
+ fix_nl=(
174
+ "set a valid X_BEARER_TOKEN from the X developer console "
175
+ f"({BEARER_COVERAGE_NOTE}), or add the X for Grok Bot plugin and connect X in Grok Bot settings "
176
+ "(full 30-day coverage)"
177
+ ),
178
+ fix_cli="X_BEARER_TOKEN=<your-x-api-bearer-token>",
179
+ anchor="grok-bot",
180
+ ),
181
+ _entry(
182
+ "x", "payment_required",
183
+ cause="X API credits are exhausted (HTTP 402)",
184
+ fix_nl=(
185
+ "top up X API credits in the X developer console, or connect X in "
186
+ "Grok Bot settings (full 30-day coverage)"
187
+ ),
188
+ fix_cli="X_BEARER_TOKEN=<bearer-from-a-project-with-credits>",
189
+ anchor="grok-bot",
190
+ ),
191
+ _entry(
192
+ "x", "connector_missing",
193
+ cause=(
194
+ "the host X lane was declared but no host-fetched result was "
195
+ "passed to the engine"
196
+ ),
197
+ fix_nl=(
198
+ "fetch X with Grok Bot's built-in X tools (or the X for Grok Bot connector) and pass the "
199
+ "posts with --x-posts, or unset LAST30DAYS_X_HOST_LANE and set X_BEARER_TOKEN from the X "
200
+ f"developer console ({BEARER_COVERAGE_NOTE})"
201
+ ),
202
+ fix_cli=f'{ENGINE_CLI} "<topic>" --x-posts <path-to-x-posts.json>',
203
+ anchor="grok-bot",
204
+ ),
205
+ _entry(
206
+ "scrapecreators", "key_missing",
207
+ cause="SCRAPECREATORS_API_KEY is not set",
208
+ fix_nl=(
209
+ "ask the agent to run setup with the GitHub device flow "
210
+ "(free 10,000-call signup; the key is persisted automatically)"
211
+ ),
212
+ fix_cli=SETUP_GITHUB_CLI,
213
+ anchor="api-keys-env",
214
+ ),
215
+ _entry(
216
+ "bluesky", "app_password_missing",
217
+ cause="BSKY_HANDLE and/or BSKY_APP_PASSWORD are not set",
218
+ fix_nl=(
219
+ "generate an app password at bsky.app/settings/app-passwords and "
220
+ "add BSKY_HANDLE plus BSKY_APP_PASSWORD to ~/.config/last30days/.env"
221
+ ),
222
+ fix_cli="BSKY_HANDLE=<your-handle> BSKY_APP_PASSWORD=<xxxx-xxxx-xxxx-xxxx>",
223
+ anchor="bluesky-app-password-format-and-search-host",
224
+ ),
225
+ _entry(
226
+ "youtube", "transcription_key_missing",
227
+ cause=(
228
+ "no transcription provider key for the caption-free transcript "
229
+ "backstop (GROQ_API_KEY or OPENAI_API_KEY)"
230
+ ),
231
+ fix_nl=(
232
+ "add a free Groq key from console.groq.com to "
233
+ "~/.config/last30days/.env so caption-free videos still get "
234
+ "transcripts (OPENAI_API_KEY also works as the paid backstop)"
235
+ ),
236
+ fix_cli="GROQ_API_KEY=<your-groq-key>",
237
+ anchor="api-keys-env",
238
+ ),
239
+ _entry(
240
+ "digg", "pp_cli_missing",
241
+ cause="digg-pp-cli is not installed",
242
+ fix_nl=(
243
+ "install the Digg CLI through the Printing Press library, then "
244
+ "re-run setup so the source activates"
245
+ ),
246
+ fix_cli=_DIGG_PP_INSTALL_CLI,
247
+ anchor="first-run-onboarding",
248
+ ),
249
+ _entry(
250
+ "digg", "pp_cli_broken",
251
+ cause=(
252
+ "digg-pp-cli resolves on PATH but won't execute (broken or "
253
+ "hanging binary left behind by a bad install)"
254
+ ),
255
+ fix_nl=(
256
+ "reinstall the Digg CLI (re-run the Printing Press install) so "
257
+ "the binary actually executes; it is installed but not serving"
258
+ ),
259
+ fix_cli=_DIGG_PP_INSTALL_CLI,
260
+ anchor="first-run-onboarding",
261
+ ),
262
+ _entry(
263
+ "digg", "pp_cli_off_path",
264
+ cause=(
265
+ "digg-pp-cli is installed but its directory is not on the "
266
+ "agent-subprocess PATH"
267
+ ),
268
+ fix_nl=(
269
+ "add the install directory (default ~/.local/bin) to the PATH the "
270
+ "agent subprocess uses; the engine gate only activates the source "
271
+ "when the binary resolves on PATH"
272
+ ),
273
+ fix_cli='export PATH="$HOME/.local/bin:$PATH"',
274
+ anchor="first-run-onboarding",
275
+ ),
276
+ _entry(
277
+ "youtube", "ytdlp_missing",
278
+ cause="yt-dlp is not installed on the agent-subprocess PATH",
279
+ fix_nl="install yt-dlp to enable the free local YouTube lane",
280
+ fix_cli=_YTDLP_BREW_INSTALL,
281
+ alt_cli=("scoop install yt-dlp", "pip install -U yt-dlp"),
282
+ ),
283
+ _entry(
284
+ "youtube", "ytdlp_stale",
285
+ cause=(
286
+ "yt-dlp is installed but stale: YouTube's caption format changes "
287
+ "frequently and old binaries silently fail every transcript"
288
+ ),
289
+ fix_nl="update yt-dlp via your package manager",
290
+ fix_cli="brew upgrade yt-dlp",
291
+ alt_cli=("scoop update yt-dlp", "pip install -U yt-dlp"),
292
+ ),
293
+ _entry(
294
+ "youtube", "ytdlp_broken",
295
+ cause=(
296
+ "yt-dlp resolves on PATH but won't execute (the stale-shim class: "
297
+ "a wrapper left behind by an interpreter upgrade)"
298
+ ),
299
+ fix_nl=(
300
+ "reinstall yt-dlp so the binary actually executes; a plain "
301
+ "install reads as a no-op because the broken shim is still present"
302
+ ),
303
+ fix_cli=_YTDLP_BREW_REINSTALL,
304
+ alt_cli=(_YTDLP_PIPX_REINSTALL,),
305
+ ),
306
+ _entry(
307
+ "truthsocial", "token_missing",
308
+ cause="TRUTHSOCIAL_TOKEN is not set",
309
+ fix_nl=(
310
+ "log into truthsocial.com in your browser and let setup read the "
311
+ "session cookie, or copy the bearer token from your browser's dev "
312
+ "tools into ~/.config/last30days/.env"
313
+ ),
314
+ fix_cli=SETUP_BROWSER_COOKIES_CLI,
315
+ anchor="api-keys-env",
316
+ ),
317
+ _entry(
318
+ "xiaohongshu", "service_unreachable",
319
+ cause=(
320
+ "Xiaohongshu browser-session service is unreachable or not logged "
321
+ "in; last30days auto-probes http://localhost:18060 and "
322
+ "http://host.docker.internal:18060 unless XIAOHONGSHU_API_BASE is set"
323
+ ),
324
+ fix_nl=(
325
+ "start a local x-mcp browser plugin or xpzouying/xiaohongshu-mcp "
326
+ "service that can see your logged-in Xiaohongshu browser session; "
327
+ "set XIAOHONGSHU_API_BASE only when it runs on a custom host/port"
328
+ ),
329
+ fix_cli="XIAOHONGSHU_API_BASE=http://your-host:18060 # only for a custom host; leave unset to auto-probe localhost and host.docker.internal",
330
+ anchor="api-keys-env",
331
+ ),
332
+ ))
333
+
334
+
335
+ def lookup(source: str, failure: str) -> Optional[Prescription]:
336
+ """Return the registered entry for (source, failure), or None."""
337
+ return REGISTRY.get((source, failure))
338
+
339
+
340
+ # Failure names remapped on an official-only host (env.x_policy): every
341
+ # cookie or Grok CLI failure has an official-path counterpart, so no fix
342
+ # line there names cookies, the scraper, or the CLI. Official names
343
+ # pass through unchanged on every host.
344
+ _OFFICIAL_X_FAILURES: Dict[str, str] = {
345
+ "cookies_missing": "bearer_missing",
346
+ "cookies_expired": "bearer_invalid",
347
+ "grok_cli_missing": "bearer_missing",
348
+ "grok_not_authenticated": "bearer_missing",
349
+ }
350
+
351
+
352
+ def for_x(config: dict, failure: str) -> Prescription:
353
+ """Policy-aware X prescription.
354
+
355
+ On an official-only host (``env.x_policy(config).hint_namespace ==
356
+ "official"``) a default-namespace failure resolves to its official
357
+ counterpart; elsewhere today's entry is returned unchanged. Never
358
+ raises (falls through to ``get``'s generic fallback).
359
+ """
360
+ if env.x_policy(config or {}).hint_namespace == "official":
361
+ failure = _OFFICIAL_X_FAILURES.get(failure, failure)
362
+ return get("x", failure)
363
+
364
+
365
+ def get(source: str, failure: str) -> Prescription:
366
+ """Return the registered entry, or the generic CONFIGURATION.md fallback.
367
+
368
+ Never raises: an unregistered failure mode still yields an actionable
369
+ (if generic) prescription, so a report renderer cannot crash on a
370
+ failure class the registry has not learned yet.
371
+ """
372
+ entry = lookup(source, failure)
373
+ if entry is not None:
374
+ return entry
375
+ return Prescription(
376
+ source=source,
377
+ failure=failure,
378
+ cause=f"{source}: {failure.replace('_', ' ')}",
379
+ fix_nl=GENERIC_FIX_NL,
380
+ fix_cli=f"{ENGINE_CLI} setup",
381
+ )
382
+
383
+
384
+ # ---------------------------------------------------------------------------
385
+ # Composition with U1 dependency probes
386
+ # ---------------------------------------------------------------------------
387
+
388
+ def _dependency_failure(probe: health.DependencyProbe) -> Optional[Tuple[str, str]]:
389
+ """Map a failed dependency probe onto a registered (source, failure)."""
390
+ if probe.name == "yt-dlp":
391
+ if probe.status == health.MISSING:
392
+ return ("youtube", "ytdlp_missing")
393
+ return ("youtube", "ytdlp_broken") # BROKEN and TIMEOUT: reinstall class
394
+ if probe.name == "digg-pp-cli":
395
+ # health reports off-PATH binaries as MISSING with ``off_path=True``;
396
+ # the distinction only picks cause/NL wording — the probe's own
397
+ # prescription wins the CLI form either way.
398
+ if probe.status == health.MISSING:
399
+ if probe.off_path:
400
+ return ("digg", "pp_cli_off_path")
401
+ return ("digg", "pp_cli_missing")
402
+ return ("digg", "pp_cli_broken") # BROKEN and TIMEOUT: reinstall class
403
+ return None
404
+
405
+
406
+ def for_dependency_probe(probe: health.DependencyProbe) -> Optional[Prescription]:
407
+ """Prescription for a failed U1 dependency probe (None when OK).
408
+
409
+ U1's machine-aware prescription (the manager that owns the binary on
410
+ THIS machine, or a PATH edit for off-PATH installs) wins the CLI form;
411
+ the registry entry supplies the shared cause/NL/anchor vocabulary.
412
+ Unregistered dependencies wrap the probe so callers still get both
413
+ fix forms without this module restating U1's strings.
414
+ """
415
+ if probe.ok:
416
+ return None
417
+ key = _dependency_failure(probe)
418
+ entry = REGISTRY.get(key) if key else None
419
+ if entry is None:
420
+ return Prescription(
421
+ source=probe.name,
422
+ failure=probe.status,
423
+ cause=probe.detail or f"{probe.name}: {probe.status}",
424
+ fix_nl=f"repair the {probe.name} install; {GENERIC_FIX_NL}",
425
+ fix_cli=probe.prescription or f"{ENGINE_CLI} setup",
426
+ )
427
+ updates = {}
428
+ if probe.detail:
429
+ updates["cause"] = probe.detail
430
+ if probe.prescription and probe.prescription != entry.fix_cli:
431
+ updates["fix_cli"] = probe.prescription
432
+ return replace(entry, **updates) if updates else entry
@@ -0,0 +1,21 @@
1
+ """Private local text files."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+
7
+ def write_private_text(path: Path, content: str, *, encoding: str | None = None) -> int:
8
+ """Write text with owner-only permissions before any bytes on POSIX."""
9
+ if os.name != "posix":
10
+ return path.write_text(content, encoding=encoding)
11
+
12
+ fd = os.open(path, os.O_CREAT | os.O_WRONLY, 0o600)
13
+ try:
14
+ os.fchmod(fd, 0o600)
15
+ stream = os.fdopen(fd, "w", encoding=encoding)
16
+ except Exception:
17
+ os.close(fd)
18
+ raise
19
+ with stream:
20
+ stream.truncate(0)
21
+ return stream.write(content)