luckiest-co 1.0.1 → 1.0.3

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 (618) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/LICENSE +24 -0
  4. package/bin/install.js +6 -4
  5. package/commands/{luckiest/finish.md → finish.md} +2 -0
  6. package/commands/go.md +53 -0
  7. package/commands/plan.md +66 -0
  8. package/package.json +3 -2
  9. package/skills/brainstorm/ATTRIBUTION.md +27 -0
  10. package/skills/brainstorm/SKILL.md +139 -0
  11. package/skills/luckiest-ab-testing/ATTRIBUTION.md +35 -0
  12. package/skills/luckiest-ab-testing/CHANGELOG.md +27 -0
  13. package/skills/luckiest-ab-testing/SKILL.md +379 -0
  14. package/skills/luckiest-ab-testing/evals/evals.json +118 -0
  15. package/skills/luckiest-ab-testing/references/sample-size-guide.md +290 -0
  16. package/skills/luckiest-ab-testing/references/test-templates.md +284 -0
  17. package/skills/luckiest-ad-creative/ATTRIBUTION.md +35 -0
  18. package/skills/luckiest-ad-creative/CHANGELOG.md +23 -0
  19. package/skills/luckiest-ad-creative/SKILL.md +393 -0
  20. package/skills/luckiest-ad-creative/evals/evals.json +90 -0
  21. package/skills/luckiest-ad-creative/references/generative-tools.md +637 -0
  22. package/skills/luckiest-ad-creative/references/platform-specs.md +213 -0
  23. package/skills/luckiest-ads/ATTRIBUTION.md +36 -0
  24. package/skills/luckiest-ads/CHANGELOG.md +24 -0
  25. package/skills/luckiest-ads/LICENSE +21 -0
  26. package/skills/luckiest-ads/SKILL.md +434 -0
  27. package/skills/luckiest-ads/evals/evals.json +90 -0
  28. package/skills/luckiest-ads/references/ad-copy-templates.md +207 -0
  29. package/skills/luckiest-ads/references/audience-targeting.md +243 -0
  30. package/skills/luckiest-ads/references/conversion-tracking.md +361 -0
  31. package/skills/luckiest-ads/references/platform-setup-checklists.md +277 -0
  32. package/skills/luckiest-advisors/ATTRIBUTION.md +16 -0
  33. package/skills/luckiest-advisors/CHANGELOG.md +99 -0
  34. package/skills/luckiest-advisors/SKILL.md +151 -0
  35. package/skills/luckiest-aeo-grader/ATTRIBUTION.md +19 -0
  36. package/skills/luckiest-aeo-grader/CHANGELOG.md +52 -0
  37. package/skills/luckiest-aeo-grader/SKILL.md +200 -0
  38. package/skills/luckiest-aeo-grader/evals/evals.json +19 -0
  39. package/skills/luckiest-aeo-grader/references/rubric.md +72 -0
  40. package/skills/luckiest-aeo-grader/scripts/scan.py +265 -0
  41. package/skills/luckiest-ai-seo/ATTRIBUTION.md +38 -0
  42. package/skills/luckiest-ai-seo/CHANGELOG.md +23 -0
  43. package/skills/luckiest-ai-seo/SKILL.md +510 -0
  44. package/skills/luckiest-ai-seo/evals/evals.json +90 -0
  45. package/skills/luckiest-ai-seo/references/content-patterns.md +285 -0
  46. package/skills/luckiest-ai-seo/references/content-types.md +71 -0
  47. package/skills/luckiest-ai-seo/references/okf.md +104 -0
  48. package/skills/luckiest-ai-seo/references/platform-ranking-factors.md +154 -0
  49. package/skills/luckiest-analytics/ATTRIBUTION.md +42 -0
  50. package/skills/luckiest-analytics/CHANGELOG.md +36 -0
  51. package/skills/luckiest-analytics/SKILL.md +380 -0
  52. package/skills/luckiest-analytics/evals/evals.json +90 -0
  53. package/skills/luckiest-analytics/references/event-library.md +260 -0
  54. package/skills/luckiest-analytics/references/ga4-implementation.md +300 -0
  55. package/skills/luckiest-analytics/references/gtm-implementation.md +390 -0
  56. package/skills/luckiest-aso/ATTRIBUTION.md +40 -0
  57. package/skills/luckiest-aso/CHANGELOG.md +33 -0
  58. package/skills/luckiest-aso/SKILL.md +327 -0
  59. package/skills/luckiest-aso/evals/evals.json +91 -0
  60. package/skills/luckiest-aso/references/apple-specs.md +107 -0
  61. package/skills/luckiest-aso/references/benchmarks.md +129 -0
  62. package/skills/luckiest-aso/references/google-play-specs.md +131 -0
  63. package/skills/luckiest-aso/references/report-template.md +213 -0
  64. package/skills/luckiest-aso/references/scoring-criteria.md +213 -0
  65. package/skills/luckiest-churn-prevention/ATTRIBUTION.md +41 -0
  66. package/skills/luckiest-churn-prevention/CHANGELOG.md +24 -0
  67. package/skills/luckiest-churn-prevention/SKILL.md +445 -0
  68. package/skills/luckiest-churn-prevention/evals/evals.json +93 -0
  69. package/skills/luckiest-churn-prevention/references/cancel-flow-patterns.md +316 -0
  70. package/skills/luckiest-churn-prevention/references/dunning-playbook.md +408 -0
  71. package/skills/luckiest-co-marketing/ATTRIBUTION.md +38 -0
  72. package/skills/luckiest-co-marketing/CHANGELOG.md +25 -0
  73. package/skills/luckiest-co-marketing/SKILL.md +331 -0
  74. package/skills/luckiest-co-marketing/evals/evals.json +84 -0
  75. package/skills/luckiest-coder/ATTRIBUTION.md +26 -0
  76. package/skills/luckiest-coder/CHANGELOG.md +30 -0
  77. package/skills/luckiest-coder/SKILL.md +77 -0
  78. package/skills/luckiest-coder/references/antigravity-tools.md +23 -0
  79. package/skills/luckiest-coder/references/codex-tools.md +39 -0
  80. package/skills/luckiest-coder/references/pi-tools.md +16 -0
  81. package/skills/luckiest-coder-brainstorming/ATTRIBUTION.md +26 -0
  82. package/skills/luckiest-coder-brainstorming/CHANGELOG.md +31 -0
  83. package/skills/luckiest-coder-brainstorming/SKILL.md +173 -0
  84. package/skills/luckiest-coder-brainstorming/scripts/frame-template.html +213 -0
  85. package/skills/luckiest-coder-brainstorming/scripts/helper.js +168 -0
  86. package/skills/luckiest-coder-brainstorming/scripts/server.cjs +721 -0
  87. package/skills/luckiest-coder-brainstorming/scripts/start-server.sh +209 -0
  88. package/skills/luckiest-coder-brainstorming/scripts/stop-server.sh +120 -0
  89. package/skills/luckiest-coder-brainstorming/spec-document-reviewer-prompt.md +49 -0
  90. package/skills/luckiest-coder-brainstorming/visual-companion.md +291 -0
  91. package/skills/luckiest-coder-consistency/ATTRIBUTION.md +30 -0
  92. package/skills/luckiest-coder-consistency/CHANGELOG.md +26 -0
  93. package/skills/luckiest-coder-consistency/SKILL.md +98 -0
  94. package/skills/luckiest-coder-constitution/ATTRIBUTION.md +28 -0
  95. package/skills/luckiest-coder-constitution/CHANGELOG.md +25 -0
  96. package/skills/luckiest-coder-constitution/SKILL.md +115 -0
  97. package/skills/luckiest-coder-debugging/ATTRIBUTION.md +26 -0
  98. package/skills/luckiest-coder-debugging/CHANGELOG.md +22 -0
  99. package/skills/luckiest-coder-debugging/CREATION-LOG.md +119 -0
  100. package/skills/luckiest-coder-debugging/SKILL.md +309 -0
  101. package/skills/luckiest-coder-debugging/condition-based-waiting-example.ts +158 -0
  102. package/skills/luckiest-coder-debugging/condition-based-waiting.md +115 -0
  103. package/skills/luckiest-coder-debugging/defense-in-depth.md +122 -0
  104. package/skills/luckiest-coder-debugging/find-polluter.sh +63 -0
  105. package/skills/luckiest-coder-debugging/root-cause-tracing.md +169 -0
  106. package/skills/luckiest-coder-debugging/test-academic.md +14 -0
  107. package/skills/luckiest-coder-debugging/test-pressure-1.md +58 -0
  108. package/skills/luckiest-coder-debugging/test-pressure-2.md +68 -0
  109. package/skills/luckiest-coder-debugging/test-pressure-3.md +69 -0
  110. package/skills/luckiest-coder-dispatching-parallel-agents/ATTRIBUTION.md +26 -0
  111. package/skills/luckiest-coder-dispatching-parallel-agents/CHANGELOG.md +23 -0
  112. package/skills/luckiest-coder-dispatching-parallel-agents/SKILL.md +198 -0
  113. package/skills/luckiest-coder-executing-plans/ATTRIBUTION.md +26 -0
  114. package/skills/luckiest-coder-executing-plans/CHANGELOG.md +22 -0
  115. package/skills/luckiest-coder-executing-plans/SKILL.md +83 -0
  116. package/skills/luckiest-coder-finishing-a-branch/ATTRIBUTION.md +26 -0
  117. package/skills/luckiest-coder-finishing-a-branch/CHANGELOG.md +22 -0
  118. package/skills/luckiest-coder-finishing-a-branch/SKILL.md +258 -0
  119. package/skills/luckiest-coder-git-worktrees/ATTRIBUTION.md +26 -0
  120. package/skills/luckiest-coder-git-worktrees/CHANGELOG.md +22 -0
  121. package/skills/luckiest-coder-git-worktrees/SKILL.md +215 -0
  122. package/skills/luckiest-coder-guard/ATTRIBUTION.md +29 -0
  123. package/skills/luckiest-coder-guard/CHANGELOG.md +48 -0
  124. package/skills/luckiest-coder-guard/LICENSE +21 -0
  125. package/skills/luckiest-coder-guard/SKILL.md +126 -0
  126. package/skills/luckiest-coder-guard/bin/check-careful.sh +65 -0
  127. package/skills/luckiest-coder-guard/bin/check-freeze.sh +48 -0
  128. package/skills/luckiest-coder-qa/ATTRIBUTION.md +28 -0
  129. package/skills/luckiest-coder-qa/CHANGELOG.md +49 -0
  130. package/skills/luckiest-coder-qa/LICENSE +21 -0
  131. package/skills/luckiest-coder-qa/SKILL.md +160 -0
  132. package/skills/luckiest-coder-qa/references/issue-taxonomy.md +85 -0
  133. package/skills/luckiest-coder-qa/references/qa-report-template.md +126 -0
  134. package/skills/luckiest-coder-receiving-code-review/ATTRIBUTION.md +26 -0
  135. package/skills/luckiest-coder-receiving-code-review/CHANGELOG.md +28 -0
  136. package/skills/luckiest-coder-receiving-code-review/SKILL.md +226 -0
  137. package/skills/luckiest-coder-requesting-code-review/ATTRIBUTION.md +26 -0
  138. package/skills/luckiest-coder-requesting-code-review/CHANGELOG.md +31 -0
  139. package/skills/luckiest-coder-requesting-code-review/SKILL.md +118 -0
  140. package/skills/luckiest-coder-requesting-code-review/code-reviewer.md +172 -0
  141. package/skills/luckiest-coder-shipping/ATTRIBUTION.md +26 -0
  142. package/skills/luckiest-coder-shipping/CHANGELOG.md +48 -0
  143. package/skills/luckiest-coder-shipping/LICENSE +21 -0
  144. package/skills/luckiest-coder-shipping/SKILL.md +148 -0
  145. package/skills/luckiest-coder-shipping/references/canary.md +44 -0
  146. package/skills/luckiest-coder-shipping/references/test-and-coverage.md +91 -0
  147. package/skills/luckiest-coder-subagent-driven-development/ATTRIBUTION.md +26 -0
  148. package/skills/luckiest-coder-subagent-driven-development/CHANGELOG.md +28 -0
  149. package/skills/luckiest-coder-subagent-driven-development/SKILL.md +439 -0
  150. package/skills/luckiest-coder-subagent-driven-development/implementer-prompt.md +139 -0
  151. package/skills/luckiest-coder-subagent-driven-development/scripts/review-package +44 -0
  152. package/skills/luckiest-coder-subagent-driven-development/scripts/sdd-workspace +22 -0
  153. package/skills/luckiest-coder-subagent-driven-development/scripts/task-brief +40 -0
  154. package/skills/luckiest-coder-subagent-driven-development/task-reviewer-prompt.md +188 -0
  155. package/skills/luckiest-coder-tdd/ATTRIBUTION.md +26 -0
  156. package/skills/luckiest-coder-tdd/CHANGELOG.md +26 -0
  157. package/skills/luckiest-coder-tdd/SKILL.md +384 -0
  158. package/skills/luckiest-coder-tdd/testing-anti-patterns.md +299 -0
  159. package/skills/luckiest-coder-verification/ATTRIBUTION.md +26 -0
  160. package/skills/luckiest-coder-verification/CHANGELOG.md +22 -0
  161. package/skills/luckiest-coder-verification/SKILL.md +152 -0
  162. package/skills/luckiest-coder-writing-plans/ATTRIBUTION.md +26 -0
  163. package/skills/luckiest-coder-writing-plans/CHANGELOG.md +22 -0
  164. package/skills/luckiest-coder-writing-plans/SKILL.md +191 -0
  165. package/skills/luckiest-coder-writing-plans/plan-document-reviewer-prompt.md +49 -0
  166. package/skills/luckiest-coder-writing-skills/ATTRIBUTION.md +26 -0
  167. package/skills/luckiest-coder-writing-skills/CHANGELOG.md +31 -0
  168. package/skills/luckiest-coder-writing-skills/SKILL.md +702 -0
  169. package/skills/luckiest-coder-writing-skills/anthropic-best-practices.md +1150 -0
  170. package/skills/luckiest-coder-writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  171. package/skills/luckiest-coder-writing-skills/graphviz-conventions.dot +172 -0
  172. package/skills/luckiest-coder-writing-skills/persuasion-principles.md +187 -0
  173. package/skills/luckiest-coder-writing-skills/render-graphs.js +168 -0
  174. package/skills/luckiest-coder-writing-skills/testing-skills-with-subagents.md +384 -0
  175. package/skills/luckiest-cold-email/ATTRIBUTION.md +36 -0
  176. package/skills/luckiest-cold-email/CHANGELOG.md +24 -0
  177. package/skills/luckiest-cold-email/SKILL.md +213 -0
  178. package/skills/luckiest-cold-email/evals/evals.json +94 -0
  179. package/skills/luckiest-cold-email/references/benchmarks.md +83 -0
  180. package/skills/luckiest-cold-email/references/follow-up-sequences.md +81 -0
  181. package/skills/luckiest-cold-email/references/frameworks.md +90 -0
  182. package/skills/luckiest-cold-email/references/personalization.md +79 -0
  183. package/skills/luckiest-cold-email/references/subject-lines.md +53 -0
  184. package/skills/luckiest-community-marketing/ATTRIBUTION.md +41 -0
  185. package/skills/luckiest-community-marketing/CHANGELOG.md +35 -0
  186. package/skills/luckiest-community-marketing/SKILL.md +200 -0
  187. package/skills/luckiest-community-marketing/evals/evals.json +89 -0
  188. package/skills/luckiest-competitor-profiling/ATTRIBUTION.md +36 -0
  189. package/skills/luckiest-competitor-profiling/CHANGELOG.md +24 -0
  190. package/skills/luckiest-competitor-profiling/SKILL.md +443 -0
  191. package/skills/luckiest-competitor-profiling/evals/evals.json +85 -0
  192. package/skills/luckiest-competitor-profiling/references/templates.md +167 -0
  193. package/skills/luckiest-competitor-profiling/references/tool-reference.md +179 -0
  194. package/skills/luckiest-competitors/ATTRIBUTION.md +36 -0
  195. package/skills/luckiest-competitors/CHANGELOG.md +25 -0
  196. package/skills/luckiest-competitors/SKILL.md +171 -0
  197. package/skills/luckiest-competitors/evals/evals.json +93 -0
  198. package/skills/luckiest-competitors/references/content-architecture.md +271 -0
  199. package/skills/luckiest-competitors/references/templates.md +223 -0
  200. package/skills/luckiest-content-strategy/ATTRIBUTION.md +39 -0
  201. package/skills/luckiest-content-strategy/CHANGELOG.md +23 -0
  202. package/skills/luckiest-content-strategy/SKILL.md +395 -0
  203. package/skills/luckiest-content-strategy/evals/evals.json +90 -0
  204. package/skills/luckiest-content-strategy/references/headless-cms.md +194 -0
  205. package/skills/luckiest-copy-editing/ATTRIBUTION.md +41 -0
  206. package/skills/luckiest-copy-editing/CHANGELOG.md +31 -0
  207. package/skills/luckiest-copy-editing/SKILL.md +491 -0
  208. package/skills/luckiest-copy-editing/evals/evals.json +89 -0
  209. package/skills/luckiest-copy-editing/references/checklist.md +66 -0
  210. package/skills/luckiest-copy-editing/references/content-refresh.md +38 -0
  211. package/skills/luckiest-copy-editing/references/plain-english-alternatives.md +394 -0
  212. package/skills/luckiest-copywriting/ATTRIBUTION.md +35 -0
  213. package/skills/luckiest-copywriting/CHANGELOG.md +23 -0
  214. package/skills/luckiest-copywriting/SKILL.md +296 -0
  215. package/skills/luckiest-copywriting/evals/evals.json +111 -0
  216. package/skills/luckiest-copywriting/references/copy-frameworks.md +344 -0
  217. package/skills/luckiest-copywriting/references/natural-transitions.md +272 -0
  218. package/skills/luckiest-cro/ATTRIBUTION.md +34 -0
  219. package/skills/luckiest-cro/CHANGELOG.md +23 -0
  220. package/skills/luckiest-cro/SKILL.md +215 -0
  221. package/skills/luckiest-cro/evals/evals.json +111 -0
  222. package/skills/luckiest-cro/references/experiments.md +248 -0
  223. package/skills/luckiest-cro/references/form.md +422 -0
  224. package/skills/luckiest-customer-research/ATTRIBUTION.md +40 -0
  225. package/skills/luckiest-customer-research/CHANGELOG.md +26 -0
  226. package/skills/luckiest-customer-research/SKILL.md +293 -0
  227. package/skills/luckiest-customer-research/evals/evals.json +162 -0
  228. package/skills/luckiest-customer-research/references/source-guides.md +401 -0
  229. package/skills/luckiest-directory-submissions/ATTRIBUTION.md +40 -0
  230. package/skills/luckiest-directory-submissions/CHANGELOG.md +33 -0
  231. package/skills/luckiest-directory-submissions/SKILL.md +419 -0
  232. package/skills/luckiest-directory-submissions/evals/evals.json +94 -0
  233. package/skills/luckiest-directory-submissions/references/directory-list.md +477 -0
  234. package/skills/luckiest-directory-submissions/references/positioning-variations.md +232 -0
  235. package/skills/luckiest-directory-submissions/references/submission-tracker-template.csv +266 -0
  236. package/skills/luckiest-emails/ATTRIBUTION.md +42 -0
  237. package/skills/luckiest-emails/CHANGELOG.md +33 -0
  238. package/skills/luckiest-emails/SKILL.md +220 -0
  239. package/skills/luckiest-emails/evals/evals.json +93 -0
  240. package/skills/luckiest-emails/references/copy-guidelines.md +120 -0
  241. package/skills/luckiest-emails/references/email-types.md +515 -0
  242. package/skills/luckiest-emails/references/sequence-templates.md +168 -0
  243. package/skills/luckiest-extract-design-system/ATTRIBUTION.md +8 -0
  244. package/skills/luckiest-extract-design-system/CHANGELOG.md +37 -0
  245. package/skills/luckiest-extract-design-system/SKILL.md +83 -0
  246. package/skills/luckiest-free-tools/ATTRIBUTION.md +43 -0
  247. package/skills/luckiest-free-tools/CHANGELOG.md +25 -0
  248. package/skills/luckiest-free-tools/SKILL.md +220 -0
  249. package/skills/luckiest-free-tools/evals/evals.json +90 -0
  250. package/skills/luckiest-free-tools/references/tool-types.md +217 -0
  251. package/skills/luckiest-image/ATTRIBUTION.md +43 -0
  252. package/skills/luckiest-image/CHANGELOG.md +23 -0
  253. package/skills/luckiest-image/SKILL.md +376 -0
  254. package/skills/luckiest-image/evals/evals.json +89 -0
  255. package/skills/luckiest-image/references/ai-image-prompting.md +229 -0
  256. package/skills/luckiest-launch/ATTRIBUTION.md +40 -0
  257. package/skills/luckiest-launch/CHANGELOG.md +43 -0
  258. package/skills/luckiest-launch/SKILL.md +385 -0
  259. package/skills/luckiest-launch/evals/evals.json +91 -0
  260. package/skills/luckiest-lead-magnets/ATTRIBUTION.md +35 -0
  261. package/skills/luckiest-lead-magnets/CHANGELOG.md +23 -0
  262. package/skills/luckiest-lead-magnets/SKILL.md +331 -0
  263. package/skills/luckiest-lead-magnets/evals/evals.json +87 -0
  264. package/skills/luckiest-lead-magnets/references/benchmarks.md +129 -0
  265. package/skills/luckiest-lead-magnets/references/format-guide.md +196 -0
  266. package/skills/luckiest-marketing-ideas/ATTRIBUTION.md +39 -0
  267. package/skills/luckiest-marketing-ideas/CHANGELOG.md +33 -0
  268. package/skills/luckiest-marketing-ideas/SKILL.md +191 -0
  269. package/skills/luckiest-marketing-ideas/evals/evals.json +90 -0
  270. package/skills/luckiest-marketing-ideas/references/ideas-by-category.md +366 -0
  271. package/skills/luckiest-marketing-plan/ATTRIBUTION.md +40 -0
  272. package/skills/luckiest-marketing-plan/CHANGELOG.md +23 -0
  273. package/skills/luckiest-marketing-plan/SKILL.md +285 -0
  274. package/skills/luckiest-marketing-plan/evals/evals.json +96 -0
  275. package/skills/luckiest-marketing-plan/references/aarrr-framework.md +180 -0
  276. package/skills/luckiest-marketing-plan/references/budget-planning.md +168 -0
  277. package/skills/luckiest-marketing-plan/references/client-types.md +373 -0
  278. package/skills/luckiest-marketing-plan/references/current-state-rubric.md +255 -0
  279. package/skills/luckiest-marketing-plan/references/example-quietude.md +972 -0
  280. package/skills/luckiest-marketing-plan/references/funding-stage-unlocks.md +230 -0
  281. package/skills/luckiest-marketing-plan/references/growth-patterns.md +148 -0
  282. package/skills/luckiest-marketing-plan/references/idea-cross-reference.md +265 -0
  283. package/skills/luckiest-marketing-plan/references/measurement-framework.md +213 -0
  284. package/skills/luckiest-marketing-plan/references/methodology.md +363 -0
  285. package/skills/luckiest-marketing-plan/references/ops-stack-mapping.md +197 -0
  286. package/skills/luckiest-marketing-plan/references/plan-template.md +494 -0
  287. package/skills/luckiest-marketing-plan/references/team-and-agency-model.md +278 -0
  288. package/skills/luckiest-marketing-psychology/ATTRIBUTION.md +31 -0
  289. package/skills/luckiest-marketing-psychology/CHANGELOG.md +32 -0
  290. package/skills/luckiest-marketing-psychology/SKILL.md +202 -0
  291. package/skills/luckiest-marketing-psychology/evals.json +88 -0
  292. package/skills/luckiest-model-router/ATTRIBUTION.md +11 -0
  293. package/skills/luckiest-model-router/CHANGELOG.md +45 -0
  294. package/skills/luckiest-model-router/SKILL.md +74 -0
  295. package/skills/luckiest-model-router/scripts/route.py +145 -0
  296. package/skills/luckiest-offers/ATTRIBUTION.md +39 -0
  297. package/skills/luckiest-offers/CHANGELOG.md +23 -0
  298. package/skills/luckiest-offers/SKILL.md +167 -0
  299. package/skills/luckiest-offers/references/bonus-stacking.md +150 -0
  300. package/skills/luckiest-offers/references/examples.md +215 -0
  301. package/skills/luckiest-offers/references/guarantee-design.md +172 -0
  302. package/skills/luckiest-offers/references/offer-anatomy.md +203 -0
  303. package/skills/luckiest-offers/references/offer-formats.md +240 -0
  304. package/skills/luckiest-offers/references/scarcity-urgency.md +175 -0
  305. package/skills/luckiest-offers/references/value-equation.md +134 -0
  306. package/skills/luckiest-onboarding/ATTRIBUTION.md +43 -0
  307. package/skills/luckiest-onboarding/CHANGELOG.md +31 -0
  308. package/skills/luckiest-onboarding/SKILL.md +243 -0
  309. package/skills/luckiest-onboarding/references/experiments.md +258 -0
  310. package/skills/luckiest-paywalls/ATTRIBUTION.md +39 -0
  311. package/skills/luckiest-paywalls/CHANGELOG.md +25 -0
  312. package/skills/luckiest-paywalls/SKILL.md +256 -0
  313. package/skills/luckiest-paywalls/evals/evals.json +93 -0
  314. package/skills/luckiest-paywalls/references/experiments.md +164 -0
  315. package/skills/luckiest-popups/ATTRIBUTION.md +35 -0
  316. package/skills/luckiest-popups/CHANGELOG.md +27 -0
  317. package/skills/luckiest-popups/SKILL.md +477 -0
  318. package/skills/luckiest-popups/evals/evals.json +107 -0
  319. package/skills/luckiest-pricing/ATTRIBUTION.md +35 -0
  320. package/skills/luckiest-pricing/CHANGELOG.md +31 -0
  321. package/skills/luckiest-pricing/SKILL.md +283 -0
  322. package/skills/luckiest-pricing/evals/evals.json +90 -0
  323. package/skills/luckiest-pricing/references/research-methods.md +152 -0
  324. package/skills/luckiest-pricing/references/tier-structure.md +232 -0
  325. package/skills/luckiest-product-marketing/ATTRIBUTION.md +38 -0
  326. package/skills/luckiest-product-marketing/CHANGELOG.md +23 -0
  327. package/skills/luckiest-product-marketing/SKILL.md +256 -0
  328. package/skills/luckiest-product-marketing/evals/evals.json +85 -0
  329. package/skills/luckiest-programmatic-seo/ATTRIBUTION.md +35 -0
  330. package/skills/luckiest-programmatic-seo/CHANGELOG.md +24 -0
  331. package/skills/luckiest-programmatic-seo/SKILL.md +286 -0
  332. package/skills/luckiest-programmatic-seo/evals/evals.json +95 -0
  333. package/skills/luckiest-programmatic-seo/references/playbooks.md +308 -0
  334. package/skills/luckiest-prompt-rewrite/ATTRIBUTION.md +39 -0
  335. package/skills/luckiest-prompt-rewrite/CHANGELOG.md +70 -0
  336. package/skills/luckiest-prompt-rewrite/LICENSE +21 -0
  337. package/skills/luckiest-prompt-rewrite/SKILL.md +494 -0
  338. package/skills/luckiest-prompt-rewrite/references/patterns.md +82 -0
  339. package/skills/luckiest-prompt-rewrite/references/templates.md +454 -0
  340. package/skills/luckiest-prospecting/ATTRIBUTION.md +29 -0
  341. package/skills/luckiest-prospecting/CHANGELOG.md +24 -0
  342. package/skills/luckiest-prospecting/SKILL.md +275 -0
  343. package/skills/luckiest-prospecting/evals/evals.json +107 -0
  344. package/skills/luckiest-prospecting/references/b2b-prospecting.md +106 -0
  345. package/skills/luckiest-prospecting/references/compliance.md +123 -0
  346. package/skills/luckiest-prospecting/references/data-sources.md +287 -0
  347. package/skills/luckiest-prospecting/references/local-prospecting.md +165 -0
  348. package/skills/luckiest-prospecting/references/saas-prospecting.md +123 -0
  349. package/skills/luckiest-public-relations/ATTRIBUTION.md +34 -0
  350. package/skills/luckiest-public-relations/CHANGELOG.md +23 -0
  351. package/skills/luckiest-public-relations/SKILL.md +154 -0
  352. package/skills/luckiest-public-relations/references/journalist-pitching.md +368 -0
  353. package/skills/luckiest-public-relations/references/media-outlets.md +207 -0
  354. package/skills/luckiest-public-relations/references/newsjacking.md +218 -0
  355. package/skills/luckiest-public-relations/references/press-platforms.md +188 -0
  356. package/skills/luckiest-referrals/ATTRIBUTION.md +41 -0
  357. package/skills/luckiest-referrals/CHANGELOG.md +23 -0
  358. package/skills/luckiest-referrals/SKILL.md +292 -0
  359. package/skills/luckiest-referrals/evals/evals.json +90 -0
  360. package/skills/luckiest-referrals/references/affiliate-programs.md +167 -0
  361. package/skills/luckiest-referrals/references/program-examples.md +143 -0
  362. package/skills/luckiest-revops/ATTRIBUTION.md +40 -0
  363. package/skills/luckiest-revops/CHANGELOG.md +24 -0
  364. package/skills/luckiest-revops/SKILL.md +362 -0
  365. package/skills/luckiest-revops/evals/evals.json +91 -0
  366. package/skills/luckiest-revops/references/automation-playbooks.md +290 -0
  367. package/skills/luckiest-revops/references/lifecycle-definitions.md +278 -0
  368. package/skills/luckiest-revops/references/routing-rules.md +203 -0
  369. package/skills/luckiest-revops/references/scoring-models.md +247 -0
  370. package/skills/luckiest-sales-enablement/ATTRIBUTION.md +39 -0
  371. package/skills/luckiest-sales-enablement/CHANGELOG.md +23 -0
  372. package/skills/luckiest-sales-enablement/SKILL.md +379 -0
  373. package/skills/luckiest-sales-enablement/evals/evals.json +91 -0
  374. package/skills/luckiest-sales-enablement/references/deck-frameworks.md +263 -0
  375. package/skills/luckiest-sales-enablement/references/demo-scripts.md +355 -0
  376. package/skills/luckiest-sales-enablement/references/objection-library.md +270 -0
  377. package/skills/luckiest-sales-enablement/references/one-pager-templates.md +208 -0
  378. package/skills/luckiest-schema/ATTRIBUTION.md +39 -0
  379. package/skills/luckiest-schema/CHANGELOG.md +25 -0
  380. package/skills/luckiest-schema/SKILL.md +209 -0
  381. package/skills/luckiest-schema/evals/evals.json +88 -0
  382. package/skills/luckiest-schema/references/schema-examples.md +398 -0
  383. package/skills/luckiest-seo-audit/ATTRIBUTION.md +39 -0
  384. package/skills/luckiest-seo-audit/CHANGELOG.md +23 -0
  385. package/skills/luckiest-seo-audit/SKILL.md +529 -0
  386. package/skills/luckiest-seo-audit/evals/evals.json +136 -0
  387. package/skills/luckiest-seo-audit/references/ai-writing-detection.md +200 -0
  388. package/skills/luckiest-seo-audit/references/international-seo.md +230 -0
  389. package/skills/luckiest-session-handoff/ATTRIBUTION.md +14 -0
  390. package/skills/luckiest-session-handoff/CHANGELOG.md +30 -0
  391. package/skills/luckiest-session-handoff/SKILL.md +96 -0
  392. package/skills/luckiest-signup/ATTRIBUTION.md +48 -0
  393. package/skills/luckiest-signup/CHANGELOG.md +24 -0
  394. package/skills/luckiest-signup/SKILL.md +382 -0
  395. package/skills/luckiest-signup/evals/evals.json +88 -0
  396. package/skills/luckiest-site-architecture/ATTRIBUTION.md +38 -0
  397. package/skills/luckiest-site-architecture/CHANGELOG.md +23 -0
  398. package/skills/luckiest-site-architecture/SKILL.md +383 -0
  399. package/skills/luckiest-site-architecture/evals/evals.json +88 -0
  400. package/skills/luckiest-site-architecture/references/mermaid-templates.md +216 -0
  401. package/skills/luckiest-site-architecture/references/navigation-patterns.md +305 -0
  402. package/skills/luckiest-site-architecture/references/site-type-templates.md +293 -0
  403. package/skills/luckiest-sms/ATTRIBUTION.md +27 -0
  404. package/skills/luckiest-sms/CHANGELOG.md +24 -0
  405. package/skills/luckiest-sms/SKILL.md +400 -0
  406. package/skills/luckiest-sms/evals/evals.json +100 -0
  407. package/skills/luckiest-sms/references/compliance.md +202 -0
  408. package/skills/luckiest-sms/references/platforms.md +318 -0
  409. package/skills/luckiest-sms/references/sequence-templates.md +282 -0
  410. package/skills/luckiest-social/ATTRIBUTION.md +27 -0
  411. package/skills/luckiest-social/CHANGELOG.md +24 -0
  412. package/skills/luckiest-social/SKILL.md +430 -0
  413. package/skills/luckiest-social/evals/evals.json +92 -0
  414. package/skills/luckiest-social/references/listening-sources-template.md +123 -0
  415. package/skills/luckiest-social/references/listening.md +283 -0
  416. package/skills/luckiest-social/references/platform-limits.md +110 -0
  417. package/skills/luckiest-social/references/platforms.md +170 -0
  418. package/skills/luckiest-social/references/post-templates.md +177 -0
  419. package/skills/luckiest-social/references/reverse-engineering.md +195 -0
  420. package/skills/luckiest-social/references/short-form-video.md +237 -0
  421. package/skills/luckiest-thinker/ATTRIBUTION.md +16 -0
  422. package/skills/luckiest-thinker/CHANGELOG.md +20 -0
  423. package/skills/luckiest-thinker/LICENSE +21 -0
  424. package/skills/luckiest-thinker/SKILL.md +92 -0
  425. package/skills/luckiest-thinker/references/distribution-calibration.md +106 -0
  426. package/skills/luckiest-thinker/references/research-foundations.md +214 -0
  427. package/skills/luckiest-thinker/references/thinking-patterns.md +138 -0
  428. package/skills/luckiest-trends/.skillignore +10 -0
  429. package/skills/luckiest-trends/ATTRIBUTION.md +43 -0
  430. package/skills/luckiest-trends/CHANGELOG.md +86 -0
  431. package/skills/luckiest-trends/LICENSE +21 -0
  432. package/skills/luckiest-trends/SKILL.md +194 -0
  433. package/skills/luckiest-trends/agents/openai.yaml +8 -0
  434. package/skills/luckiest-trends/references/engine-contract.md +2041 -0
  435. package/skills/luckiest-trends/references/save-html-brief.md +206 -0
  436. package/skills/luckiest-trends/scripts/briefing.py +272 -0
  437. package/skills/luckiest-trends/scripts/build-skill.sh +39 -0
  438. package/skills/luckiest-trends/scripts/compare.sh +61 -0
  439. package/skills/luckiest-trends/scripts/evaluate_search_quality.py +573 -0
  440. package/skills/luckiest-trends/scripts/last30days.py +1467 -0
  441. package/skills/luckiest-trends/scripts/lib/__init__.py +1 -0
  442. package/skills/luckiest-trends/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  443. package/skills/luckiest-trends/scripts/lib/__pycache__/arxiv.cpython-313.pyc +0 -0
  444. package/skills/luckiest-trends/scripts/lib/__pycache__/bird_x.cpython-313.pyc +0 -0
  445. package/skills/luckiest-trends/scripts/lib/__pycache__/bluesky.cpython-313.pyc +0 -0
  446. package/skills/luckiest-trends/scripts/lib/__pycache__/categories.cpython-313.pyc +0 -0
  447. package/skills/luckiest-trends/scripts/lib/__pycache__/cjk.cpython-313.pyc +0 -0
  448. package/skills/luckiest-trends/scripts/lib/__pycache__/cluster.cpython-313.pyc +0 -0
  449. package/skills/luckiest-trends/scripts/lib/__pycache__/dates.cpython-313.pyc +0 -0
  450. package/skills/luckiest-trends/scripts/lib/__pycache__/dedupe.cpython-313.pyc +0 -0
  451. package/skills/luckiest-trends/scripts/lib/__pycache__/digg.cpython-313.pyc +0 -0
  452. package/skills/luckiest-trends/scripts/lib/__pycache__/entity_extract.cpython-313.pyc +0 -0
  453. package/skills/luckiest-trends/scripts/lib/__pycache__/env.cpython-313.pyc +0 -0
  454. package/skills/luckiest-trends/scripts/lib/__pycache__/fusion.cpython-313.pyc +0 -0
  455. package/skills/luckiest-trends/scripts/lib/__pycache__/github.cpython-313.pyc +0 -0
  456. package/skills/luckiest-trends/scripts/lib/__pycache__/grounding.cpython-313.pyc +0 -0
  457. package/skills/luckiest-trends/scripts/lib/__pycache__/hackernews.cpython-313.pyc +0 -0
  458. package/skills/luckiest-trends/scripts/lib/__pycache__/hiring_signals.cpython-313.pyc +0 -0
  459. package/skills/luckiest-trends/scripts/lib/__pycache__/html_render.cpython-313.pyc +0 -0
  460. package/skills/luckiest-trends/scripts/lib/__pycache__/http.cpython-313.pyc +0 -0
  461. package/skills/luckiest-trends/scripts/lib/__pycache__/instagram.cpython-313.pyc +0 -0
  462. package/skills/luckiest-trends/scripts/lib/__pycache__/jobs.cpython-313.pyc +0 -0
  463. package/skills/luckiest-trends/scripts/lib/__pycache__/linkedin.cpython-313.pyc +0 -0
  464. package/skills/luckiest-trends/scripts/lib/__pycache__/log.cpython-313.pyc +0 -0
  465. package/skills/luckiest-trends/scripts/lib/__pycache__/normalize.cpython-313.pyc +0 -0
  466. package/skills/luckiest-trends/scripts/lib/__pycache__/permission_preflight.cpython-313.pyc +0 -0
  467. package/skills/luckiest-trends/scripts/lib/__pycache__/perplexity.cpython-313.pyc +0 -0
  468. package/skills/luckiest-trends/scripts/lib/__pycache__/pinterest.cpython-313.pyc +0 -0
  469. package/skills/luckiest-trends/scripts/lib/__pycache__/pipeline.cpython-313.pyc +0 -0
  470. package/skills/luckiest-trends/scripts/lib/__pycache__/planner.cpython-313.pyc +0 -0
  471. package/skills/luckiest-trends/scripts/lib/__pycache__/polymarket.cpython-313.pyc +0 -0
  472. package/skills/luckiest-trends/scripts/lib/__pycache__/preflight.cpython-313.pyc +0 -0
  473. package/skills/luckiest-trends/scripts/lib/__pycache__/providers.cpython-313.pyc +0 -0
  474. package/skills/luckiest-trends/scripts/lib/__pycache__/quality_nudge.cpython-313.pyc +0 -0
  475. package/skills/luckiest-trends/scripts/lib/__pycache__/query.cpython-313.pyc +0 -0
  476. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit.cpython-313.pyc +0 -0
  477. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_arctic.cpython-313.pyc +0 -0
  478. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_enrich.cpython-313.pyc +0 -0
  479. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_keyless.cpython-313.pyc +0 -0
  480. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_listing.cpython-313.pyc +0 -0
  481. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_public.cpython-313.pyc +0 -0
  482. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_rss.cpython-313.pyc +0 -0
  483. package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_shreddit.cpython-313.pyc +0 -0
  484. package/skills/luckiest-trends/scripts/lib/__pycache__/relevance.cpython-313.pyc +0 -0
  485. package/skills/luckiest-trends/scripts/lib/__pycache__/render.cpython-313.pyc +0 -0
  486. package/skills/luckiest-trends/scripts/lib/__pycache__/rerank.cpython-313.pyc +0 -0
  487. package/skills/luckiest-trends/scripts/lib/__pycache__/resolve.cpython-313.pyc +0 -0
  488. package/skills/luckiest-trends/scripts/lib/__pycache__/schema.cpython-313.pyc +0 -0
  489. package/skills/luckiest-trends/scripts/lib/__pycache__/signals.cpython-313.pyc +0 -0
  490. package/skills/luckiest-trends/scripts/lib/__pycache__/skill_meta.cpython-313.pyc +0 -0
  491. package/skills/luckiest-trends/scripts/lib/__pycache__/snippet.cpython-313.pyc +0 -0
  492. package/skills/luckiest-trends/scripts/lib/__pycache__/subproc.cpython-313.pyc +0 -0
  493. package/skills/luckiest-trends/scripts/lib/__pycache__/techmeme.cpython-313.pyc +0 -0
  494. package/skills/luckiest-trends/scripts/lib/__pycache__/threads.cpython-313.pyc +0 -0
  495. package/skills/luckiest-trends/scripts/lib/__pycache__/tiktok.cpython-313.pyc +0 -0
  496. package/skills/luckiest-trends/scripts/lib/__pycache__/trustpilot.cpython-313.pyc +0 -0
  497. package/skills/luckiest-trends/scripts/lib/__pycache__/truthsocial.cpython-313.pyc +0 -0
  498. package/skills/luckiest-trends/scripts/lib/__pycache__/ui.cpython-313.pyc +0 -0
  499. package/skills/luckiest-trends/scripts/lib/__pycache__/web_search_keyless.cpython-313.pyc +0 -0
  500. package/skills/luckiest-trends/scripts/lib/__pycache__/xai_x.cpython-313.pyc +0 -0
  501. package/skills/luckiest-trends/scripts/lib/__pycache__/xiaohongshu_api.cpython-313.pyc +0 -0
  502. package/skills/luckiest-trends/scripts/lib/__pycache__/xquik.cpython-313.pyc +0 -0
  503. package/skills/luckiest-trends/scripts/lib/__pycache__/xurl_x.cpython-313.pyc +0 -0
  504. package/skills/luckiest-trends/scripts/lib/__pycache__/youtube_yt.cpython-313.pyc +0 -0
  505. package/skills/luckiest-trends/scripts/lib/arxiv.py +290 -0
  506. package/skills/luckiest-trends/scripts/lib/bird_x.py +639 -0
  507. package/skills/luckiest-trends/scripts/lib/bluesky.py +323 -0
  508. package/skills/luckiest-trends/scripts/lib/categories.py +283 -0
  509. package/skills/luckiest-trends/scripts/lib/chrome_cookies.py +414 -0
  510. package/skills/luckiest-trends/scripts/lib/cjk.py +107 -0
  511. package/skills/luckiest-trends/scripts/lib/cluster.py +271 -0
  512. package/skills/luckiest-trends/scripts/lib/competitors.py +198 -0
  513. package/skills/luckiest-trends/scripts/lib/cookie_extract.py +508 -0
  514. package/skills/luckiest-trends/scripts/lib/dates.py +169 -0
  515. package/skills/luckiest-trends/scripts/lib/dedupe.py +145 -0
  516. package/skills/luckiest-trends/scripts/lib/digg.py +440 -0
  517. package/skills/luckiest-trends/scripts/lib/entity_extract.py +127 -0
  518. package/skills/luckiest-trends/scripts/lib/env.py +1073 -0
  519. package/skills/luckiest-trends/scripts/lib/fanout.py +84 -0
  520. package/skills/luckiest-trends/scripts/lib/fusion.py +207 -0
  521. package/skills/luckiest-trends/scripts/lib/github.py +996 -0
  522. package/skills/luckiest-trends/scripts/lib/grounding.py +331 -0
  523. package/skills/luckiest-trends/scripts/lib/hackernews.py +357 -0
  524. package/skills/luckiest-trends/scripts/lib/health.py +92 -0
  525. package/skills/luckiest-trends/scripts/lib/hiring_signals.py +326 -0
  526. package/skills/luckiest-trends/scripts/lib/html_publish.py +73 -0
  527. package/skills/luckiest-trends/scripts/lib/html_render.py +738 -0
  528. package/skills/luckiest-trends/scripts/lib/http.py +372 -0
  529. package/skills/luckiest-trends/scripts/lib/instagram.py +521 -0
  530. package/skills/luckiest-trends/scripts/lib/jobs.py +708 -0
  531. package/skills/luckiest-trends/scripts/lib/linkedin.py +363 -0
  532. package/skills/luckiest-trends/scripts/lib/log.py +35 -0
  533. package/skills/luckiest-trends/scripts/lib/normalize.py +759 -0
  534. package/skills/luckiest-trends/scripts/lib/permission_preflight.py +242 -0
  535. package/skills/luckiest-trends/scripts/lib/perplexity.py +745 -0
  536. package/skills/luckiest-trends/scripts/lib/pinterest.py +154 -0
  537. package/skills/luckiest-trends/scripts/lib/pipeline.py +1720 -0
  538. package/skills/luckiest-trends/scripts/lib/planner.py +763 -0
  539. package/skills/luckiest-trends/scripts/lib/polymarket.py +794 -0
  540. package/skills/luckiest-trends/scripts/lib/preflight.py +119 -0
  541. package/skills/luckiest-trends/scripts/lib/providers.py +380 -0
  542. package/skills/luckiest-trends/scripts/lib/quality_nudge.py +383 -0
  543. package/skills/luckiest-trends/scripts/lib/query.py +202 -0
  544. package/skills/luckiest-trends/scripts/lib/reddit.py +731 -0
  545. package/skills/luckiest-trends/scripts/lib/reddit_arctic.py +92 -0
  546. package/skills/luckiest-trends/scripts/lib/reddit_enrich.py +322 -0
  547. package/skills/luckiest-trends/scripts/lib/reddit_keyless.py +329 -0
  548. package/skills/luckiest-trends/scripts/lib/reddit_listing.py +188 -0
  549. package/skills/luckiest-trends/scripts/lib/reddit_public.py +275 -0
  550. package/skills/luckiest-trends/scripts/lib/reddit_rss.py +224 -0
  551. package/skills/luckiest-trends/scripts/lib/reddit_shreddit.py +184 -0
  552. package/skills/luckiest-trends/scripts/lib/relevance.py +200 -0
  553. package/skills/luckiest-trends/scripts/lib/render.py +2130 -0
  554. package/skills/luckiest-trends/scripts/lib/rerank.py +708 -0
  555. package/skills/luckiest-trends/scripts/lib/resolve.py +345 -0
  556. package/skills/luckiest-trends/scripts/lib/safari_cookies.py +193 -0
  557. package/skills/luckiest-trends/scripts/lib/schema.py +319 -0
  558. package/skills/luckiest-trends/scripts/lib/setup_wizard.py +843 -0
  559. package/skills/luckiest-trends/scripts/lib/signals.py +334 -0
  560. package/skills/luckiest-trends/scripts/lib/skill_meta.py +33 -0
  561. package/skills/luckiest-trends/scripts/lib/snippet.py +51 -0
  562. package/skills/luckiest-trends/scripts/lib/subproc.py +116 -0
  563. package/skills/luckiest-trends/scripts/lib/techmeme.py +239 -0
  564. package/skills/luckiest-trends/scripts/lib/threads.py +207 -0
  565. package/skills/luckiest-trends/scripts/lib/tiktok.py +596 -0
  566. package/skills/luckiest-trends/scripts/lib/transcribe.py +235 -0
  567. package/skills/luckiest-trends/scripts/lib/trustpilot.py +277 -0
  568. package/skills/luckiest-trends/scripts/lib/truthsocial.py +162 -0
  569. package/skills/luckiest-trends/scripts/lib/ui.py +626 -0
  570. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/LICENSE +21 -0
  571. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/bird-search.mjs +147 -0
  572. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/cookies.js +220 -0
  573. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/features.json +17 -0
  574. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/paginate-cursor.js +37 -0
  575. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/query-ids.json +20 -0
  576. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/runtime-features.js +151 -0
  577. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/runtime-query-ids.js +264 -0
  578. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-base.js +129 -0
  579. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-constants.js +50 -0
  580. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-features.js +347 -0
  581. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-search.js +157 -0
  582. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-types.js +2 -0
  583. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-utils.js +511 -0
  584. package/skills/luckiest-trends/scripts/lib/vendor/bird-search/package.json +13 -0
  585. package/skills/luckiest-trends/scripts/lib/web_fetch_keyless.py +105 -0
  586. package/skills/luckiest-trends/scripts/lib/web_search_keyless.py +158 -0
  587. package/skills/luckiest-trends/scripts/lib/xai_x.py +236 -0
  588. package/skills/luckiest-trends/scripts/lib/xiaohongshu_api.py +162 -0
  589. package/skills/luckiest-trends/scripts/lib/xquik.py +404 -0
  590. package/skills/luckiest-trends/scripts/lib/xurl_x.py +171 -0
  591. package/skills/luckiest-trends/scripts/lib/youtube_yt.py +1365 -0
  592. package/skills/luckiest-trends/scripts/setup-keychain.sh +123 -0
  593. package/skills/luckiest-trends/scripts/setup-pass.sh +132 -0
  594. package/skills/luckiest-trends/scripts/store.py +1020 -0
  595. package/skills/luckiest-trends/scripts/test-v1-vs-v2.sh +220 -0
  596. package/skills/luckiest-trends/scripts/test_device_auth.py +128 -0
  597. package/skills/luckiest-trends/scripts/verify_v3.py +195 -0
  598. package/skills/luckiest-trends/scripts/watchlist.py +318 -0
  599. package/skills/luckiest-video/ATTRIBUTION.md +27 -0
  600. package/skills/luckiest-video/CHANGELOG.md +23 -0
  601. package/skills/luckiest-video/SKILL.md +371 -0
  602. package/skills/luckiest-video/evals/evals.json +88 -0
  603. package/skills/luckiest-video/references/ai-video-prompting.md +175 -0
  604. package/skills/remix/SKILL.md +32 -0
  605. package/commands/luckiest/go.md +0 -45
  606. package/commands/luckiest/plan.md +0 -55
  607. /package/commands/{luckiest/charms.md → charms.md} +0 -0
  608. /package/commands/{luckiest/helpers.md → helpers.md} +0 -0
  609. /package/commands/{luckiest/home.md → home.md} +0 -0
  610. /package/commands/{luckiest/leaderboard.md → leaderboard.md} +0 -0
  611. /package/commands/{luckiest/merge.md → merge.md} +0 -0
  612. /package/commands/{luckiest/overlaps.md → overlaps.md} +0 -0
  613. /package/commands/{luckiest/skills.md → skills.md} +0 -0
  614. /package/commands/{luckiest/start.md → start.md} +0 -0
  615. /package/commands/{luckiest/status.md → status.md} +0 -0
  616. /package/commands/{luckiest/updates.md → updates.md} +0 -0
  617. /package/commands/{luckiest/vouch.md → vouch.md} +0 -0
  618. /package/commands/{luckiest/wishes.md → wishes.md} +0 -0
@@ -0,0 +1,843 @@
1
+ """First-run setup wizard for last30days.
2
+
3
+ Detects first run, performs auto-setup (cookie extraction + yt-dlp check),
4
+ and writes configuration. The actual wizard UI is SKILL.md-driven (the LLM
5
+ presents it), but this module provides the detection and setup actions.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import os
11
+ import shutil
12
+ import subprocess
13
+ import time
14
+ from pathlib import Path
15
+ from typing import Any, Dict, Optional, Tuple
16
+ from urllib.error import HTTPError, URLError
17
+ from urllib.request import Request, urlopen
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def is_first_run(config: Dict[str, Any]) -> bool:
23
+ """Return True if the setup wizard has not been completed.
24
+
25
+ Checks for SETUP_COMPLETE in the config dict. If it's not set
26
+ (None or empty string), the user hasn't gone through setup yet.
27
+ """
28
+ return not config.get("SETUP_COMPLETE")
29
+
30
+
31
+ def run_auto_setup(config: Dict[str, Any], *, allow_browser_cookies: bool = False) -> Dict[str, Any]:
32
+ """Perform the auto-setup actions.
33
+
34
+ - Optionally runs cookie extraction for all registered domains, trying the
35
+ browsers from ``env.cookie_extraction_browsers()``. Browser reads are off
36
+ unless ``allow_browser_cookies`` is true.
37
+ - Checks if yt-dlp is installed
38
+ - Best-effort install of digg-pp-cli (Printing Press library)
39
+
40
+ Returns:
41
+ Dict with keys:
42
+ cookies_found: {source_name: browser_name} for each source where cookies were found
43
+ ytdlp_installed: bool
44
+ ytdlp_action: already_installed | installed | install_failed | no_homebrew
45
+ digg_installed: bool (True when the engine can resolve digg-pp-cli on PATH)
46
+ digg_action: already_installed | installed | installed_off_path | install_failed | no_npx
47
+ env_written: bool (always False here — caller writes config separately)
48
+ ytdlp_stderr: present when ytdlp_action is install_failed
49
+ digg_stderr: present when digg_action is install_failed
50
+ digg_path: present when digg_action is installed_off_path (binary on disk, not on PATH)
51
+ """
52
+ from .env import COOKIE_DOMAINS, cookie_extraction_browsers
53
+
54
+ cookies_found: Dict[str, str] = {}
55
+
56
+ if allow_browser_cookies:
57
+ from . import cookie_extract
58
+
59
+ cookie_config = dict(config)
60
+ if not (cookie_config.get("FROM_BROWSER") or "").strip():
61
+ cookie_config["FROM_BROWSER"] = "firefox,safari"
62
+ browsers = cookie_extraction_browsers(cookie_config)
63
+
64
+ for source_name, spec in COOKIE_DOMAINS.items():
65
+ domain = spec["domain"]
66
+ cookie_names = spec["cookies"]
67
+
68
+ for browser in browsers:
69
+ try:
70
+ result = cookie_extract.extract_cookies_with_source(browser, domain, cookie_names)
71
+ except Exception as exc:
72
+ logger.debug("Cookie extraction failed for %s via %s: %s", source_name, browser, exc)
73
+ continue
74
+ if result is not None and result[0]:
75
+ cookies_found[source_name] = result[1]
76
+ break # Found cookies for this service, stop trying browsers
77
+
78
+ # Check yt-dlp availability and install via Homebrew if missing
79
+ ytdlp_action: str
80
+ if shutil.which("yt-dlp") is not None:
81
+ ytdlp_installed = True
82
+ ytdlp_action = "already_installed"
83
+ elif shutil.which("brew") is not None:
84
+ brew_stderr = ""
85
+ try:
86
+ proc = subprocess.run(
87
+ ["brew", "install", "yt-dlp"],
88
+ capture_output=True, text=True, timeout=120,
89
+ )
90
+ if proc.returncode == 0:
91
+ ytdlp_installed = True
92
+ ytdlp_action = "installed"
93
+ else:
94
+ ytdlp_installed = False
95
+ ytdlp_action = "install_failed"
96
+ brew_stderr = proc.stderr
97
+ logger.warning("brew install yt-dlp failed: %s", proc.stderr)
98
+ except Exception as exc:
99
+ ytdlp_installed = False
100
+ ytdlp_action = "install_failed"
101
+ brew_stderr = str(exc)
102
+ logger.warning("brew install yt-dlp exception: %s", exc)
103
+ else:
104
+ ytdlp_installed = False
105
+ ytdlp_action = "no_homebrew"
106
+
107
+ digg_installed, digg_action, digg_stderr, digg_path = _install_digg_cli()
108
+ pp_sources = install_default_pp_sources()
109
+
110
+ results: Dict[str, Any] = {
111
+ "cookies_found": cookies_found,
112
+ "ytdlp_installed": ytdlp_installed,
113
+ "ytdlp_action": ytdlp_action,
114
+ "digg_installed": digg_installed,
115
+ "digg_action": digg_action,
116
+ # Per-CLI status for the additional default-on Printing Press sources
117
+ # (arxiv, techmeme, trustpilot): {source: {installed, action, ...}}.
118
+ "pp_sources": pp_sources,
119
+ "env_written": False,
120
+ }
121
+ if ytdlp_action == "install_failed":
122
+ results["ytdlp_stderr"] = brew_stderr
123
+ if digg_action == "install_failed":
124
+ results["digg_stderr"] = digg_stderr
125
+ if digg_path:
126
+ results["digg_path"] = digg_path
127
+ return results
128
+
129
+
130
+ # Generous timeout: the install shells out to `npx`, which may download the
131
+ # Printing Press package and build the Go binary over the network.
132
+ DIGG_INSTALL_TIMEOUT = 300
133
+ DIGG_CLI_BIN = "digg-pp-cli"
134
+ # Pin the catalog installer; matches printing-press-library npm 0.1.16 default
135
+ # ($HOME/.local/bin on macOS/Linux).
136
+ PRINTING_PRESS_NPM = "@mvanhorn/printing-press-library@0.1.16"
137
+ DIGG_INSTALL_CMD = f"npx -y {PRINTING_PRESS_NPM} install digg --cli-only"
138
+
139
+
140
+ def _digg_bin_candidate_paths() -> list[Path]:
141
+ """Known install locations for digg-pp-cli (Printing Press library defaults).
142
+
143
+ Order: current installer default (~/.local/bin), legacy Go bins, Windows
144
+ managed dir. ``pipeline.available_sources()`` only activates Digg when
145
+ ``shutil.which`` resolves on PATH — probing these dirs is for setup
146
+ verification and honest off-PATH messaging, not engine activation.
147
+ """
148
+ home = Path.home()
149
+ candidates: list[Path] = [home / ".local" / "bin" / DIGG_CLI_BIN]
150
+ gopath = os.environ.get("GOPATH")
151
+ if gopath:
152
+ candidates.append(Path(gopath) / "bin" / DIGG_CLI_BIN)
153
+ candidates.append(home / "go" / "bin" / DIGG_CLI_BIN)
154
+ if os.name == "nt":
155
+ local_app = os.environ.get("LOCALAPPDATA") or os.environ.get("LocalAppData")
156
+ if local_app:
157
+ candidates.append(
158
+ Path(local_app) / "Programs" / "PrintingPress" / "bin" / f"{DIGG_CLI_BIN}.exe"
159
+ )
160
+ return candidates
161
+
162
+
163
+ def _digg_on_path() -> Optional[str]:
164
+ """Return digg-pp-cli when the engine would activate Digg (PATH-resolvable)."""
165
+ return shutil.which(DIGG_CLI_BIN)
166
+
167
+
168
+ def _digg_off_path_binary() -> Optional[str]:
169
+ """Return digg-pp-cli path from known install dirs when not on PATH."""
170
+ for candidate in _digg_bin_candidate_paths():
171
+ if candidate.is_file() and os.access(candidate, os.X_OK):
172
+ return str(candidate)
173
+ return None
174
+
175
+
176
+ def _digg_bin_dir_hint(digg_path: str) -> str:
177
+ """Return a copy-pasteable PATH directory for the given binary path."""
178
+ parent = os.path.dirname(os.path.expanduser(digg_path))
179
+ if os.name == "nt":
180
+ # Windows PATH edits use absolute dirs; $HOME is a Unix shell convention.
181
+ return parent
182
+ home = str(Path.home())
183
+ if parent == home:
184
+ return "$HOME"
185
+ prefix = home + os.sep
186
+ if parent.startswith(prefix):
187
+ rel = parent[len(prefix):].replace(os.sep, "/")
188
+ return f"$HOME/{rel}" if rel else "$HOME"
189
+ return parent
190
+
191
+
192
+ def _install_digg_cli() -> Tuple[bool, str, str, str]:
193
+ """Best-effort install of the digg-pp-cli binary.
194
+
195
+ Mirrors the yt-dlp/brew auto-install: it never raises, and degrades to a
196
+ recommend-only outcome when the installer is unavailable. Uses
197
+ ``@mvanhorn/printing-press-library`` (``--cli-only``) — the same catalog
198
+ installer as pp-digg; Hermes/OpenClaw skill wiring is irrelevant here.
199
+
200
+ Returns ``(engine_active, action, stderr, off_path_binary)`` where
201
+ ``engine_active`` is True only when ``shutil.which`` resolves the binary
202
+ (matching ``pipeline.available_sources()``). ``action`` is one of:
203
+ already_installed | installed | installed_off_path | install_failed | no_npx
204
+ ``stderr`` is populated on ``install_failed``. ``off_path_binary`` is set
205
+ when the binary exists on disk but is not PATH-visible to this process.
206
+ """
207
+ on_path = _digg_on_path()
208
+ if on_path:
209
+ return True, "already_installed", "", ""
210
+ off_path = _digg_off_path_binary()
211
+ if off_path:
212
+ return False, "installed_off_path", "", off_path
213
+ if shutil.which("npx") is None:
214
+ return False, "no_npx", "", ""
215
+ try:
216
+ proc = subprocess.run(
217
+ ["npx", "-y", PRINTING_PRESS_NPM, "install", "digg", "--cli-only"],
218
+ capture_output=True, text=True, timeout=DIGG_INSTALL_TIMEOUT,
219
+ )
220
+ except Exception as exc:
221
+ logger.warning("npx install digg exception: %s", exc)
222
+ return False, "install_failed", str(exc), ""
223
+ if proc.returncode != 0:
224
+ stderr = proc.stderr or f"npx install digg exited {proc.returncode}"
225
+ logger.warning("npx install digg failed (rc=%s): %s", proc.returncode, stderr)
226
+ return False, "install_failed", stderr, ""
227
+ on_path = _digg_on_path()
228
+ if on_path:
229
+ return True, "installed", "", ""
230
+ off_path = _digg_off_path_binary()
231
+ if off_path:
232
+ combined = (proc.stderr or "").strip()
233
+ if combined:
234
+ logger.warning("digg-pp-cli installed off PATH: %s", combined)
235
+ return False, "installed_off_path", combined, off_path
236
+ stderr = proc.stderr or "install completed but digg-pp-cli was not found"
237
+ logger.warning("npx install digg failed verification: %s", stderr)
238
+ return False, "install_failed", stderr, ""
239
+
240
+
241
+ # Additional default-on Printing Press sources installed the same way as Digg:
242
+ # (engine source key, slug for `install <slug>`, binary name). These activate in
243
+ # ``pipeline.available_sources()`` when ``shutil.which`` resolves the binary.
244
+ # Trustpilot is intentionally NOT here: it is opt-in (INCLUDE_SOURCES=trustpilot)
245
+ # because of its headless-Chrome cookie harvest, so auto-installing its binary
246
+ # for a source that stays off by default would be wasted work. Opting in installs
247
+ # it on demand via `npx ... install trustpilot --cli-only` (see CONFIGURATION.md).
248
+ PP_DEFAULT_SOURCES: list[tuple[str, str, str]] = [
249
+ ("arxiv", "arxiv", "arxiv-pp-cli"),
250
+ ("techmeme", "techmeme", "techmeme-pp-cli"),
251
+ ]
252
+
253
+
254
+ def _pp_bin_candidate_paths(bin_name: str) -> list[Path]:
255
+ """Known install locations for a Printing Press CLI binary (slug-parameterized
256
+ mirror of ``_digg_bin_candidate_paths``)."""
257
+ home = Path.home()
258
+ candidates: list[Path] = [home / ".local" / "bin" / bin_name]
259
+ gopath = os.environ.get("GOPATH")
260
+ if gopath:
261
+ candidates.append(Path(gopath) / "bin" / bin_name)
262
+ candidates.append(home / "go" / "bin" / bin_name)
263
+ if os.name == "nt":
264
+ local_app = os.environ.get("LOCALAPPDATA") or os.environ.get("LocalAppData")
265
+ if local_app:
266
+ candidates.append(
267
+ Path(local_app) / "Programs" / "PrintingPress" / "bin" / f"{bin_name}.exe"
268
+ )
269
+ return candidates
270
+
271
+
272
+ def _pp_off_path_binary(bin_name: str) -> Optional[str]:
273
+ for candidate in _pp_bin_candidate_paths(bin_name):
274
+ if candidate.is_file() and os.access(candidate, os.X_OK):
275
+ return str(candidate)
276
+ return None
277
+
278
+
279
+ def _install_pp_cli(slug: str, bin_name: str) -> Tuple[bool, str, str, str]:
280
+ """Best-effort install of a Printing Press CLI binary.
281
+
282
+ Slug-parameterized mirror of ``_install_digg_cli``: never raises, degrades
283
+ to recommend-only when the installer is unavailable. Returns
284
+ ``(engine_active, action, stderr, off_path_binary)`` with the same action
285
+ taxonomy: already_installed | installed | installed_off_path |
286
+ install_failed | no_npx.
287
+ """
288
+ on_path = shutil.which(bin_name)
289
+ if on_path:
290
+ return True, "already_installed", "", ""
291
+ off_path = _pp_off_path_binary(bin_name)
292
+ if off_path:
293
+ return False, "installed_off_path", "", off_path
294
+ if shutil.which("npx") is None:
295
+ return False, "no_npx", "", ""
296
+ try:
297
+ proc = subprocess.run(
298
+ ["npx", "-y", PRINTING_PRESS_NPM, "install", slug, "--cli-only"],
299
+ capture_output=True, text=True, timeout=DIGG_INSTALL_TIMEOUT,
300
+ )
301
+ except Exception as exc:
302
+ logger.warning("npx install %s exception: %s", slug, exc)
303
+ return False, "install_failed", str(exc), ""
304
+ if proc.returncode != 0:
305
+ stderr = proc.stderr or f"npx install {slug} exited {proc.returncode}"
306
+ logger.warning("npx install %s failed (rc=%s): %s", slug, proc.returncode, stderr)
307
+ return False, "install_failed", stderr, ""
308
+ on_path = shutil.which(bin_name)
309
+ if on_path:
310
+ return True, "installed", "", ""
311
+ off_path = _pp_off_path_binary(bin_name)
312
+ if off_path:
313
+ combined = (proc.stderr or "").strip()
314
+ if combined:
315
+ logger.warning("%s installed off PATH: %s", bin_name, combined)
316
+ return False, "installed_off_path", combined, off_path
317
+ stderr = proc.stderr or f"install completed but {bin_name} was not found"
318
+ logger.warning("npx install %s failed verification: %s", slug, stderr)
319
+ return False, "install_failed", stderr, ""
320
+
321
+
322
+ def install_default_pp_sources() -> Dict[str, Dict[str, Any]]:
323
+ """Best-effort install of every additional default-on Printing Press source.
324
+
325
+ Returns ``{source_key: {installed, action, stderr?, path?}}`` so the wizard
326
+ can report per-CLI status alongside Digg without raising on any single
327
+ failure.
328
+ """
329
+ out: Dict[str, Dict[str, Any]] = {}
330
+ for source_key, slug, bin_name in PP_DEFAULT_SOURCES:
331
+ installed, action, stderr, off_path = _install_pp_cli(slug, bin_name)
332
+ entry: Dict[str, Any] = {"installed": installed, "action": action}
333
+ if action == "install_failed" and stderr:
334
+ entry["stderr"] = stderr
335
+ if off_path:
336
+ entry["path"] = off_path
337
+ out[source_key] = entry
338
+ return out
339
+
340
+
341
+ def _open_secret_append(path: Path):
342
+ """Open ``path`` for appending as a 0o600 secret file with no readable window.
343
+
344
+ ``os.open`` with ``O_CREAT|O_WRONLY|O_APPEND`` and mode ``0o600`` sets
345
+ restrictive permissions at creation (umask can only further restrict, never
346
+ widen, so the file is never world-readable even transiently). An explicit
347
+ ``chmod`` afterwards also tightens a pre-existing loose file. This matters
348
+ because the .env stores API keys, cookies, and tokens.
349
+ """
350
+ fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_APPEND, 0o600)
351
+ try:
352
+ os.chmod(path, 0o600)
353
+ except OSError:
354
+ pass
355
+ return os.fdopen(fd, "a", encoding="utf-8")
356
+
357
+
358
+ def _format_env_value(value: str) -> str:
359
+ """Quote a value so it round-trips through env.load_env_file.
360
+
361
+ env.load_env_file strips a single layer of matching surrounding quotes but
362
+ does NOT process backslash escapes, so we wrap (never escape):
363
+ - plain tokens (no whitespace, no leading quote): returned unchanged;
364
+ - values with whitespace/leading quote and no double-quote: double-quoted;
365
+ - values containing a double-quote but no single-quote: single-quoted;
366
+ - values containing both quote types: returned as-is (best effort; no
367
+ wrapper round-trips through the loader, and tokens never hit this).
368
+ Newlines are not valid in a single-line env value and are stripped.
369
+ """
370
+ value = value.replace("\r", "").replace("\n", " ")
371
+ needs_quoting = (not value) or value[0] in ("'", '"') or any(c.isspace() for c in value)
372
+ if not needs_quoting:
373
+ return value
374
+ if '"' not in value:
375
+ return f'"{value}"'
376
+ if "'" not in value:
377
+ return f"'{value}'"
378
+ return value
379
+
380
+
381
+ def write_setup_config(env_path: Path, from_browser: str | None = None) -> bool:
382
+ """Write SETUP_COMPLETE and FROM_BROWSER to the .env file.
383
+
384
+ Creates the file and parent directories if needed.
385
+ Appends to existing file without overwriting existing keys.
386
+
387
+ Args:
388
+ env_path: Path to the .env file (e.g. ~/.config/last30days/.env)
389
+ from_browser: Browser extraction mode to persist. Pass the browser that
390
+ actually yielded cookies (e.g. "firefox") to fast-path future runs.
391
+ Pass None (default) to NOT pin FROM_BROWSER — the steady-state
392
+ default (Firefox/Safari, no Keychain prompt) then applies. We avoid
393
+ persisting "auto" because it makes every later run probe Chrome and
394
+ re-trigger the Keychain prompt.
395
+
396
+ Returns:
397
+ True if config was written successfully, False on error.
398
+ """
399
+ try:
400
+ env_path = Path(env_path)
401
+ env_path.parent.mkdir(parents=True, exist_ok=True)
402
+
403
+ # Read existing content to avoid overwriting keys
404
+ existing_keys: set = set()
405
+ existing_content = ""
406
+ if env_path.exists():
407
+ existing_content = env_path.read_text(encoding="utf-8")
408
+ for line in existing_content.splitlines():
409
+ stripped = line.strip()
410
+ if stripped and not stripped.startswith("#") and "=" in stripped:
411
+ key = stripped.split("=", 1)[0].strip()
412
+ existing_keys.add(key)
413
+
414
+ lines_to_add = []
415
+ if "SETUP_COMPLETE" not in existing_keys:
416
+ lines_to_add.append("SETUP_COMPLETE=true")
417
+ if from_browser and "FROM_BROWSER" not in existing_keys:
418
+ lines_to_add.append(f"FROM_BROWSER={_format_env_value(from_browser)}")
419
+
420
+ if not lines_to_add:
421
+ return True # Nothing to write, already configured
422
+
423
+ # Create/append as a 0o600 secret file: the .env holds tokens and keys,
424
+ # so it must never be created world-readable.
425
+ with _open_secret_append(env_path) as f:
426
+ if existing_content and not existing_content.endswith("\n"):
427
+ f.write("\n")
428
+ f.write("\n".join(lines_to_add) + "\n")
429
+
430
+ return True
431
+
432
+ except OSError as exc:
433
+ logger.error("Failed to write setup config to %s: %s", env_path, exc)
434
+ return False
435
+
436
+
437
+ def write_api_key(env_path: Path, api_key: str, key_name: str = "SCRAPECREATORS_API_KEY") -> bool:
438
+ """Append an API key to the .env file as a 0o600 secret.
439
+
440
+ Reuses the same secret-safe write path as ``write_setup_config`` so the
441
+ value lands with restrictive permissions and round-trips through
442
+ ``env.load_env_file``. Idempotent: if ``key_name`` is already present in
443
+ the file, nothing is written and the existing value is preserved (we never
444
+ clobber a key the user may have set by hand).
445
+
446
+ Args:
447
+ env_path: Path to the .env file (e.g. ~/.config/last30days/.env).
448
+ api_key: The raw key value to persist.
449
+ key_name: The env var name to write (default SCRAPECREATORS_API_KEY).
450
+
451
+ Returns:
452
+ True if the key was written or already present, False on error or when
453
+ ``api_key`` is empty.
454
+ """
455
+ if not api_key:
456
+ return False
457
+ try:
458
+ env_path = Path(env_path)
459
+ env_path.parent.mkdir(parents=True, exist_ok=True)
460
+
461
+ existing_content = ""
462
+ if env_path.exists():
463
+ existing_content = env_path.read_text(encoding="utf-8")
464
+ for line in existing_content.splitlines():
465
+ stripped = line.strip()
466
+ if stripped and not stripped.startswith("#") and "=" in stripped:
467
+ if stripped.split("=", 1)[0].strip() == key_name:
468
+ return True # Already configured; do not duplicate
469
+
470
+ line = f"{key_name}={_format_env_value(api_key)}\n"
471
+ with _open_secret_append(env_path) as f:
472
+ if existing_content and not existing_content.endswith("\n"):
473
+ f.write("\n")
474
+ f.write(line)
475
+
476
+ return True
477
+
478
+ except OSError as exc:
479
+ logger.error("Failed to write API key to %s: %s", env_path, exc)
480
+ return False
481
+
482
+
483
+ def mask_api_key(api_key: str) -> str:
484
+ """Return a non-secret display form of an API key (prefix + last 4).
485
+
486
+ Used so the key never appears verbatim in stdout the host model captures.
487
+ Short or empty keys collapse to a fixed placeholder.
488
+ """
489
+ if not api_key or len(api_key) <= 8:
490
+ return "sc_…"
491
+ return f"{api_key[:3]}…{api_key[-4:]}"
492
+
493
+
494
+ def get_setup_status_text(results: Dict[str, Any]) -> str:
495
+ """Return a human-readable summary of auto-setup results.
496
+
497
+ Args:
498
+ results: Dict from run_auto_setup()
499
+
500
+ Returns:
501
+ Multi-line status text.
502
+ """
503
+ lines = []
504
+ lines.append("Setup complete! Here's what I found:")
505
+ lines.append("")
506
+
507
+ cookies_found = results.get("cookies_found", {})
508
+ if cookies_found:
509
+ for source, browser in cookies_found.items():
510
+ lines.append(f" - {source.upper()} cookies found in {browser}")
511
+ else:
512
+ lines.append(" - No browser cookies found for X/Twitter")
513
+
514
+ ytdlp_action = results.get("ytdlp_action", "")
515
+ if ytdlp_action == "installed":
516
+ lines.append(" - Installed yt-dlp via Homebrew")
517
+ elif ytdlp_action == "install_failed":
518
+ lines.append(" - yt-dlp install failed \u2014 run `brew install yt-dlp` manually")
519
+ elif ytdlp_action == "no_homebrew":
520
+ lines.append(" - yt-dlp not found. Install Homebrew first, then: brew install yt-dlp")
521
+ elif ytdlp_action == "already_installed":
522
+ lines.append(" - yt-dlp already installed")
523
+ elif results.get("ytdlp_installed", False):
524
+ lines.append(" - yt-dlp is installed (YouTube search ready)")
525
+ else:
526
+ lines.append(" - yt-dlp not found (install with: brew install yt-dlp)")
527
+
528
+ digg_action = results.get("digg_action", "")
529
+ if digg_action == "installed":
530
+ lines.append(" - Installed Digg CLI (free AI-news clusters source now active)")
531
+ elif digg_action == "already_installed":
532
+ lines.append(" - Digg CLI already installed (AI-news clusters active)")
533
+ elif digg_action == "installed_off_path":
534
+ digg_path = results.get("digg_path", "")
535
+ if digg_path:
536
+ bin_dir = _digg_bin_dir_hint(digg_path)
537
+ lines.append(
538
+ f" - Digg CLI found at {digg_path} but not on PATH — add "
539
+ f"{bin_dir} to PATH and restart your agent session/gateway "
540
+ "for Digg to activate"
541
+ )
542
+ else:
543
+ lines.append(
544
+ " - Digg CLI is installed but not on PATH — add its install "
545
+ "directory to PATH and restart your agent session/gateway for "
546
+ "Digg to activate"
547
+ )
548
+ elif digg_action == "install_failed":
549
+ lines.append(f" - Digg CLI install failed — run `{DIGG_INSTALL_CMD}` manually")
550
+ elif digg_action == "no_npx":
551
+ lines.append(
552
+ " - Digg CLI not installed (free, optional). Install Node/npx, then: "
553
+ f"{DIGG_INSTALL_CMD}"
554
+ )
555
+
556
+ env_written = results.get("env_written", False)
557
+ if env_written:
558
+ lines.append("")
559
+ lines.append("Configuration saved. Future runs will auto-detect your browsers.")
560
+
561
+ return "\n".join(lines)
562
+
563
+
564
+ # ---------------------------------------------------------------------------
565
+ # OpenClaw server-side setup (no browser, JSON output)
566
+ # ---------------------------------------------------------------------------
567
+
568
+ _OPENCLAW_KEY_NAMES = [
569
+ "SCRAPECREATORS_API_KEY",
570
+ "XAI_API_KEY",
571
+ "BRAVE_API_KEY",
572
+ "EXA_API_KEY",
573
+ "SERPER_API_KEY",
574
+ "OPENAI_API_KEY",
575
+ "AUTH_TOKEN",
576
+ ]
577
+
578
+
579
+ def run_openclaw_setup(config: Dict[str, Any]) -> Dict[str, Any]:
580
+ """Server-side setup probe: no cookies, tool + key availability, Digg CLI.
581
+
582
+ Best-effort installs digg-pp-cli when npx is available (same as desktop
583
+ ``run_auto_setup``). Returns a dict suitable for JSON output to stdout so
584
+ that SKILL.md can present appropriate options to the user.
585
+ """
586
+ yt_dlp = shutil.which("yt-dlp") is not None
587
+ node = shutil.which("node") is not None
588
+ python3 = shutil.which("python3") is not None
589
+
590
+ digg_installed, digg_action, digg_stderr, digg_path = _install_digg_cli()
591
+
592
+ keys: Dict[str, bool] = {}
593
+ for key_name in _OPENCLAW_KEY_NAMES:
594
+ short = key_name.lower().replace("_api_key", "").replace("_key", "").replace("_token", "")
595
+ # Normalize: AUTH_TOKEN -> auth, SCRAPECREATORS_API_KEY -> scrapecreators
596
+ keys[short] = bool(config.get(key_name))
597
+
598
+ # Determine x_method
599
+ if config.get("XAI_API_KEY"):
600
+ x_method: Optional[str] = "xai"
601
+ elif config.get("AUTH_TOKEN") and config.get("CT0"):
602
+ x_method = "cookies"
603
+ else:
604
+ x_method = None
605
+
606
+ payload: Dict[str, Any] = {
607
+ "yt_dlp": yt_dlp,
608
+ "node": node,
609
+ "python3": python3,
610
+ "digg_cli": digg_installed,
611
+ "digg_action": digg_action,
612
+ "keys": keys,
613
+ "x_method": x_method,
614
+ }
615
+ if digg_path:
616
+ payload["digg_path"] = digg_path
617
+ if digg_action == "install_failed" and digg_stderr:
618
+ payload["digg_stderr"] = digg_stderr
619
+ return payload
620
+
621
+
622
+ # ---------------------------------------------------------------------------
623
+ # Device auth flow (GitHub OAuth via ScrapeCreators)
624
+ # ---------------------------------------------------------------------------
625
+
626
+ _DEVICE_BASE = "https://api.scrapecreators.com/v1/github/device"
627
+
628
+
629
+ def run_device_auth() -> Optional[Tuple[str, str, str, int]]:
630
+ """Start the device authorization flow.
631
+
632
+ POSTs to the ScrapeCreators device/code endpoint.
633
+
634
+ Returns:
635
+ (device_code, user_code, verification_uri, interval) on success,
636
+ None on failure.
637
+ """
638
+ try:
639
+ body = json.dumps({}).encode()
640
+ req = Request(f"{_DEVICE_BASE}/code", data=body, method="POST")
641
+ req.add_header("Content-Type", "application/json")
642
+ with urlopen(req, timeout=15) as resp:
643
+ data = json.loads(resp.read())
644
+ except (HTTPError, URLError, OSError) as exc:
645
+ logger.warning("Device auth code request failed: %s", exc)
646
+ return None
647
+
648
+ device_code = data.get("device_code")
649
+ user_code = data.get("user_code")
650
+ verification_uri = data.get("verification_uri")
651
+ interval = data.get("interval", 5)
652
+
653
+ if not device_code or not user_code:
654
+ logger.warning("Device auth returned incomplete response: %s", data)
655
+ return None
656
+
657
+ return (device_code, user_code, verification_uri or "", interval)
658
+
659
+
660
+ def poll_device_auth(
661
+ device_code: str,
662
+ interval: int,
663
+ timeout: int = 300,
664
+ user_code: str = "",
665
+ clipboard_ok: bool = False,
666
+ ) -> Optional[str]:
667
+ """Poll for an access token after the user authorizes the device.
668
+
669
+ Args:
670
+ device_code: The device_code from run_device_auth().
671
+ interval: Polling interval in seconds.
672
+ timeout: Maximum time to poll in seconds.
673
+ user_code: The user code to remind about during polling.
674
+ clipboard_ok: Whether the code was copied to clipboard.
675
+
676
+ Returns:
677
+ access_token on success, None on timeout or failure.
678
+ """
679
+ import sys
680
+
681
+ started_at = time.time()
682
+ deadline = started_at + timeout
683
+ last_reminder = started_at
684
+ reminder_count = 0
685
+ max_reminders = 4
686
+ reminder_interval = 30 # seconds between reminders
687
+
688
+ while time.time() < deadline:
689
+ time.sleep(interval)
690
+
691
+ # Periodic reminder of the code while waiting
692
+ if (
693
+ user_code
694
+ and reminder_count < max_reminders
695
+ and time.time() - last_reminder >= reminder_interval
696
+ ):
697
+ clipboard_hint = " (on your clipboard)" if clipboard_ok else ""
698
+ print(
699
+ f" Still waiting... Your code: {user_code}{clipboard_hint}",
700
+ file=sys.stderr,
701
+ flush=True,
702
+ )
703
+ last_reminder = time.time()
704
+ reminder_count += 1
705
+
706
+ try:
707
+ body = json.dumps({"device_code": device_code}).encode()
708
+ req = Request(f"{_DEVICE_BASE}/token", data=body, method="POST")
709
+ req.add_header("Content-Type", "application/json")
710
+ with urlopen(req, timeout=15) as resp:
711
+ data = json.loads(resp.read())
712
+ except HTTPError as exc:
713
+ if exc.code in (400, 403, 428):
714
+ continue
715
+ logger.warning("Device auth poll error: %s", exc)
716
+ return None
717
+ except (URLError, OSError):
718
+ continue
719
+
720
+ if data.get("access_token"):
721
+ return data["access_token"]
722
+
723
+ error = data.get("error")
724
+ if error == "slow_down":
725
+ interval = min(interval + 2, 30)
726
+ continue
727
+ if error == "authorization_pending":
728
+ continue
729
+ if error in ("expired_token", "access_denied"):
730
+ logger.warning("Device auth failed: %s", error)
731
+ return None
732
+
733
+ return None
734
+
735
+
736
+ def fetch_api_key(access_token: str) -> Optional[str]:
737
+ """Fetch the ScrapeCreators API key using the GitHub access token.
738
+
739
+ GETs the device/profile endpoint with Bearer auth.
740
+
741
+ Returns:
742
+ api_key string on success, None on failure.
743
+ """
744
+ try:
745
+ req = Request(f"{_DEVICE_BASE}/profile")
746
+ req.add_header("Authorization", f"Bearer {access_token}")
747
+ with urlopen(req, timeout=15) as resp:
748
+ data = json.loads(resp.read())
749
+ except (HTTPError, URLError, OSError) as exc:
750
+ logger.warning("Failed to fetch API key: %s", exc)
751
+ return None
752
+
753
+ return data.get("api_key")
754
+
755
+
756
+ def run_full_device_auth(timeout: int = 300) -> Dict[str, Any]:
757
+ """Run the complete GitHub device auth flow and return JSON-serializable result.
758
+
759
+ Chains: start device flow -> open browser -> poll -> fetch API key.
760
+ Designed to be called from the CLI and have its stdout parsed by the LLM.
761
+
762
+ Returns:
763
+ Dict with status and relevant fields:
764
+ - {"status": "success", "api_key": "sc_...", "user_code": "ABCD-1234"}
765
+ - {"status": "error", "message": "..."}
766
+ - {"status": "timeout", "user_code": "ABCD-1234"}
767
+ - {"status": "denied"}
768
+ """
769
+ import webbrowser
770
+
771
+ # Step 1: Start device flow
772
+ result = run_device_auth()
773
+ if result is None:
774
+ return {"status": "error", "message": "Failed to start device auth flow"}
775
+
776
+ device_code, user_code, verification_uri, interval = result
777
+
778
+ import sys
779
+
780
+ # Step 2: Copy code to clipboard BEFORE opening browser
781
+ clipboard_ok = False
782
+ if sys.platform == "darwin":
783
+ try:
784
+ subprocess.run(
785
+ ["pbcopy"], input=user_code.encode(), check=True, timeout=5,
786
+ )
787
+ clipboard_ok = True
788
+ except Exception:
789
+ pass # pbcopy unavailable or failed, fall through
790
+
791
+ # Step 3: Show code prominently, then open browser
792
+ clipboard_hint = " (copied to clipboard)" if clipboard_ok else ""
793
+ code_line = f" Your code: {user_code}{clipboard_hint}"
794
+ action_line = " Paste it on the GitHub page that just opened"
795
+ width = max(len(code_line), len(action_line)) + 2
796
+ border = "-" * width
797
+ print(f"\n+{border}+", file=sys.stderr)
798
+ print(f"|{code_line.ljust(width)}|", file=sys.stderr)
799
+ print(f"|{action_line.ljust(width)}|", file=sys.stderr)
800
+ print(f"+{border}+", file=sys.stderr)
801
+
802
+ if verification_uri:
803
+ try:
804
+ webbrowser.open(verification_uri)
805
+ except Exception:
806
+ print(f"Open: {verification_uri}", file=sys.stderr)
807
+
808
+ print("Waiting for authorization...", file=sys.stderr, flush=True)
809
+
810
+ # Step 4: Poll for token (with periodic code reminders)
811
+ access_token = poll_device_auth(
812
+ device_code, interval, timeout=timeout,
813
+ user_code=user_code, clipboard_ok=clipboard_ok,
814
+ )
815
+ if access_token is None:
816
+ return {"status": "timeout", "user_code": user_code, "clipboard_ok": clipboard_ok}
817
+
818
+ # Step 4: Fetch API key
819
+ api_key = fetch_api_key(access_token)
820
+ if api_key is None:
821
+ return {
822
+ "status": "error",
823
+ "message": "Authorized but failed to fetch API key",
824
+ "clipboard_ok": clipboard_ok,
825
+ }
826
+
827
+ return {"status": "success", "method": "device", "api_key": api_key, "user_code": user_code, "clipboard_ok": clipboard_ok}
828
+
829
+
830
+ # ---------------------------------------------------------------------------
831
+ # Unified GitHub auth
832
+ # ---------------------------------------------------------------------------
833
+
834
+
835
+ def run_github_auth(timeout: int = 300) -> Dict[str, Any]:
836
+ """Run the --github setup path via device auth only.
837
+
838
+ Kept as the semantic entry point for GitHub-backed setup; this path must
839
+ not read or forward local GitHub CLI tokens.
840
+
841
+ Returns JSON-serializable dict with status, method, and api_key.
842
+ """
843
+ return run_full_device_auth(timeout=timeout)