luckiest-co 1.0.0 → 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 +7 -5
  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 +16 -5
  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,235 @@
1
+ """Caption-free transcription: compress -> chunk -> provider-fallback.
2
+
3
+ When a video/audio item has no captions, this turns the media into text via a
4
+ Whisper API. Source-agnostic: the input is a media URL or local path, the output
5
+ is transcript text. The pipeline:
6
+
7
+ 1. Acquire audio (yt-dlp for a URL, or use a local file as-is).
8
+ 2. Re-encode to a low-bitrate mono stream so most clips fit under the provider
9
+ upload limit.
10
+ 3. If still over the limit, split into bounded-duration chunks.
11
+ 4. Transcribe each chunk through an ordered provider list (Groq free tier
12
+ first, OpenAI paid backstop), with per-chunk fallback, then join.
13
+
14
+ Never raises. Returns a typed :class:`TranscriptResult`; missing prerequisites
15
+ (no ffmpeg, no provider key) yield a degraded result with a reason, consumed by
16
+ the source-health layer, rather than a crash.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import os
22
+ import shutil
23
+ import tempfile
24
+ from dataclasses import dataclass, field
25
+ from typing import Optional
26
+
27
+ from . import env, health, subproc
28
+
29
+ # Whisper's documented upload ceiling. We compress to stay under it and chunk
30
+ # when a single clip still exceeds it.
31
+ MAX_UPLOAD_BYTES = 25 * 1024 * 1024
32
+ CHUNK_SECONDS = 600 # 10-minute chunks when splitting is required
33
+
34
+ _PROVIDER_ENDPOINTS = {
35
+ "groq": "https://api.groq.com/openai/v1/audio/transcriptions",
36
+ "openai": "https://api.openai.com/v1/audio/transcriptions",
37
+ }
38
+ _PROVIDER_MODELS = {
39
+ "groq": "whisper-large-v3",
40
+ "openai": "whisper-1",
41
+ }
42
+
43
+
44
+ @dataclass
45
+ class TranscriptResult:
46
+ text: str = ""
47
+ ok: bool = False
48
+ reason: str = ""
49
+ provider: str = ""
50
+ chunks: int = 0
51
+ health: Optional[health.SourceHealth] = field(default=None)
52
+
53
+
54
+ def is_available(config: dict) -> bool:
55
+ """True when ffmpeg is present AND at least one Whisper provider key is set."""
56
+ return bool(shutil.which("ffmpeg")) and bool(env.transcription_providers(config))
57
+
58
+
59
+ def transcribe_media(
60
+ source: str,
61
+ config: dict,
62
+ timeout: float = 120.0,
63
+ ) -> TranscriptResult:
64
+ """Transcribe a media URL or local path. Never raises.
65
+
66
+ Returns a degraded result (ok=False, reason set) when prerequisites are
67
+ missing or every provider fails, so callers can report the gap honestly.
68
+ """
69
+ if not shutil.which("ffmpeg"):
70
+ return _degraded("ffmpeg not installed", health.MISSING)
71
+ providers = env.transcription_providers(config)
72
+ if not providers:
73
+ return _degraded(
74
+ "no transcription provider key (set GROQ_API_KEY or OPENAI_API_KEY)",
75
+ health.MISSING,
76
+ )
77
+
78
+ workdir = tempfile.mkdtemp(prefix="l30d-transcribe-")
79
+ try:
80
+ audio_path = _acquire_audio(source, workdir, timeout=timeout)
81
+ if not audio_path:
82
+ return _degraded("could not acquire/compress audio", health.ERROR)
83
+
84
+ chunk_paths = _chunk_audio(audio_path, workdir)
85
+ if not chunk_paths:
86
+ return _degraded("audio chunking produced no segments", health.ERROR)
87
+
88
+ texts: list[str] = []
89
+ used_provider = ""
90
+ for chunk in chunk_paths:
91
+ chunk_text, provider = _transcribe_chunk(chunk, providers, timeout=timeout)
92
+ if chunk_text is None:
93
+ return _degraded(
94
+ f"all providers failed on a chunk ({len(texts)}/{len(chunk_paths)} done)",
95
+ health.ERROR,
96
+ )
97
+ texts.append(chunk_text)
98
+ used_provider = provider or used_provider
99
+
100
+ joined = "\n".join(t.strip() for t in texts if t.strip())
101
+ if not joined:
102
+ return _degraded("transcription produced empty text", health.DEGRADED)
103
+ return TranscriptResult(
104
+ text=joined,
105
+ ok=True,
106
+ provider=used_provider,
107
+ chunks=len(chunk_paths),
108
+ health=health.SourceHealth(name="transcribe", state=health.OK),
109
+ )
110
+ finally:
111
+ shutil.rmtree(workdir, ignore_errors=True)
112
+
113
+
114
+ def _degraded(reason: str, state: str) -> TranscriptResult:
115
+ return TranscriptResult(
116
+ ok=False,
117
+ reason=reason,
118
+ health=health.SourceHealth(name="transcribe", state=state, reason=reason),
119
+ )
120
+
121
+
122
+ def _acquire_audio(source: str, workdir: str, timeout: float) -> Optional[str]:
123
+ """Produce a compressed mono/16kHz/low-bitrate audio file, or None.
124
+
125
+ For a URL, extract audio with yt-dlp; for a local path, transcode it. The
126
+ re-encode keeps most clips under the upload ceiling.
127
+ """
128
+ raw = source
129
+ if source.startswith("http"):
130
+ raw = os.path.join(workdir, "raw.m4a")
131
+ if not _run([
132
+ "yt-dlp", "-f", "bestaudio", "-o", raw, "--no-playlist", source
133
+ ], timeout=timeout):
134
+ return None
135
+ if not os.path.exists(raw):
136
+ return None
137
+ elif not os.path.exists(source):
138
+ return None
139
+
140
+ out = os.path.join(workdir, "audio.mp3")
141
+ # Mono, 16kHz, 32kbps keeps speech intelligible while shrinking the file.
142
+ if not _run([
143
+ "ffmpeg", "-y", "-i", raw, "-ac", "1", "-ar", "16000", "-b:a", "32k", out
144
+ ], timeout=timeout):
145
+ return None
146
+ return out if os.path.exists(out) else None
147
+
148
+
149
+ def _chunk_audio(audio_path: str, workdir: str) -> list[str]:
150
+ """Return [audio_path] when small enough, else ffmpeg-segmented chunk paths."""
151
+ try:
152
+ size = os.path.getsize(audio_path)
153
+ except OSError:
154
+ return []
155
+ if size <= MAX_UPLOAD_BYTES:
156
+ return [audio_path]
157
+
158
+ pattern = os.path.join(workdir, "chunk_%03d.mp3")
159
+ if not _run([
160
+ "ffmpeg", "-y", "-i", audio_path, "-f", "segment",
161
+ "-segment_time", str(CHUNK_SECONDS), "-c", "copy", pattern
162
+ ]):
163
+ # Fall back to the single (oversized) file; the provider may still accept it.
164
+ return [audio_path]
165
+ chunks = sorted(
166
+ os.path.join(workdir, f) for f in os.listdir(workdir) if f.startswith("chunk_")
167
+ )
168
+ return chunks or [audio_path]
169
+
170
+
171
+ def _transcribe_chunk(
172
+ path: str,
173
+ providers: list[tuple[str, str]],
174
+ timeout: float,
175
+ ) -> tuple[Optional[str], str]:
176
+ """Try each provider in order; return (text, provider) or (None, '')."""
177
+ for name, key in providers:
178
+ try:
179
+ text = _post_audio(name, path, key, timeout=timeout)
180
+ except Exception: # noqa: BLE001 - any provider failure -> try the next
181
+ text = None
182
+ if text is not None:
183
+ return text, name
184
+ return None, ""
185
+
186
+
187
+ def _post_audio(provider: str, path: str, api_key: str, timeout: float) -> Optional[str]:
188
+ """POST one audio file to a Whisper-compatible endpoint; return text or None."""
189
+ import json
190
+ import urllib.request
191
+
192
+ endpoint = _PROVIDER_ENDPOINTS[provider]
193
+ model = _PROVIDER_MODELS[provider]
194
+ boundary = "----l30dTranscribeBoundary"
195
+ with open(path, "rb") as fh:
196
+ audio = fh.read()
197
+
198
+ parts: list[bytes] = []
199
+ parts.append(f"--{boundary}\r\n".encode())
200
+ parts.append(b'Content-Disposition: form-data; name="model"\r\n\r\n')
201
+ parts.append(f"{model}\r\n".encode())
202
+ parts.append(f"--{boundary}\r\n".encode())
203
+ parts.append(
204
+ b'Content-Disposition: form-data; name="file"; filename="audio.mp3"\r\n'
205
+ b"Content-Type: audio/mpeg\r\n\r\n"
206
+ )
207
+ parts.append(audio)
208
+ parts.append(f"\r\n--{boundary}--\r\n".encode())
209
+ body = b"".join(parts)
210
+
211
+ req = urllib.request.Request(
212
+ endpoint,
213
+ data=body,
214
+ headers={
215
+ "Authorization": f"Bearer {api_key}",
216
+ "Content-Type": f"multipart/form-data; boundary={boundary}",
217
+ },
218
+ method="POST",
219
+ )
220
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
221
+ data = json.loads(resp.read().decode("utf-8"))
222
+ return data.get("text")
223
+
224
+
225
+ def _run(command: list[str], timeout: float = 120.0) -> bool:
226
+ """Run a subprocess; return True on exit 0, False on any failure. No raise.
227
+
228
+ Uses subproc.run_with_timeout so a timed-out yt-dlp/ffmpeg is killed at the
229
+ process-group level (os.setsid/killpg) instead of orphaning child trees.
230
+ """
231
+ try:
232
+ result = subproc.run_with_timeout(command, timeout=int(timeout))
233
+ except (subproc.SubprocTimeout, FileNotFoundError, OSError):
234
+ return False
235
+ return result.returncode == 0
@@ -0,0 +1,277 @@
1
+ """Trustpilot brand-sentiment source for last30days.
2
+
3
+ Shells out to ``trustpilot-pp-cli`` to surface a company's TrustScore and
4
+ Trustpilot's own AI review summary for brand/company topics. Trustpilot has no
5
+ API key, but it sits behind AWS WAF: the CLI harvests an ``aws-waf-token`` via
6
+ a one-time headless Chrome launch (~10s), then replays it over plain HTTP until
7
+ it expires.
8
+
9
+ Activation gate: only available when ``trustpilot-pp-cli`` is on PATH.
10
+ ``pipeline.available_sources`` checks ``shutil.which`` before including
11
+ ``trustpilot``.
12
+
13
+ Default-on safety (three gates):
14
+ 1. Brand-shape gate. The CLI is invoked only when the topic resolves to a
15
+ company/brand -- a domain-like token, or a short (<=2-word) capitalized
16
+ proper noun. Generic phrases ("AI coding agents", "agent memory") and
17
+ longer multi-word phrases never call the CLI, so Trustpilot stays quiet --
18
+ and never harvests Chrome -- on non-company topics.
19
+ 2. Browser opt-out. Automated contexts (cron, CI, the eval harness) can set
20
+ ``LAST30DAYS_TRUSTPILOT_NO_BROWSER`` to disable the source entirely, so a
21
+ headless run never spawns the cookie harvest.
22
+ 3. Graceful degradation. Any CLI failure (no Chrome, expired cookie that
23
+ cannot re-harvest, timeout) degrades to empty results, never an error.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ import os
30
+ import re
31
+ import shutil
32
+ from typing import Any, Dict, List, Optional
33
+
34
+ from . import dates, log, subproc
35
+ from .relevance import token_overlap_relevance
36
+
37
+
38
+ CLI_BIN = "trustpilot-pp-cli"
39
+
40
+ SEARCH_TIMEOUT = 75 # generous: a cold run may harvest a WAF cookie (~10s).
41
+
42
+ NO_BROWSER_ENV = "LAST30DAYS_TRUSTPILOT_NO_BROWSER"
43
+
44
+ # Domain-like token, e.g. "chownow.com", "nothing.tech".
45
+ _DOMAIN_RE = re.compile(r"\b[a-z0-9][a-z0-9-]*\.(com|io|co|net|org|app|ai|dev|gg|tech|shop|store)\b")
46
+
47
+ # Generic tokens that disqualify a short capitalized phrase from being a brand.
48
+ _GENERIC_TOKENS = {
49
+ "ai", "best", "top", "vs", "review", "reviews", "guide", "tutorial",
50
+ "how", "what", "why", "agents", "agent", "memory", "tips", "news",
51
+ }
52
+
53
+ # Single-word programming languages, frameworks, runtimes, OSes, and dev tools.
54
+ # A bare capitalized "Python"/"React"/"Docker" query is overwhelmingly about the
55
+ # technology, not a company's customer reviews -- letting it through would both
56
+ # trigger the Chrome harvest and risk surfacing an unrelated company that shares
57
+ # the name. A user who genuinely wants the company can use its domain
58
+ # (e.g. "docker.com"), which still passes via the domain branch.
59
+ _TECH_TOKENS = {
60
+ "python", "javascript", "typescript", "java", "rust", "ruby", "php",
61
+ "kotlin", "scala", "golang", "swift", "elixir", "erlang", "haskell",
62
+ "react", "vue", "angular", "svelte", "django", "flask", "rails", "spring",
63
+ "node", "nodejs", "deno", "bun", "express", "nextjs", "nuxt",
64
+ "linux", "ubuntu", "debian", "fedora", "windows", "macos", "android",
65
+ "docker", "kubernetes", "k8s", "terraform", "ansible", "nginx",
66
+ "redis", "postgres", "postgresql", "mysql", "sqlite", "mongodb", "kafka",
67
+ "graphql", "webpack", "vite", "rust", "wasm",
68
+ }
69
+
70
+
71
+ def _log(msg: str) -> None:
72
+ log.source_log("Trustpilot", msg, tty_only=False)
73
+
74
+
75
+ def _is_available() -> bool:
76
+ """True when the trustpilot-pp-cli binary is on PATH."""
77
+ return shutil.which(CLI_BIN) is not None
78
+
79
+
80
+ def _truthy(value: Any) -> bool:
81
+ return str(value or "").strip().lower() in ("1", "true", "yes", "on")
82
+
83
+
84
+ def _harvest_allowed(config: Optional[Dict[str, Any]]) -> bool:
85
+ """False when the browser opt-out is set (automated/headless contexts).
86
+
87
+ Reads the opt-out from the merged config AND directly from the process
88
+ environment. The env fallback is load-bearing: ``config`` is assembled from
89
+ an allowlist in ``env.get_config``, so a fallback here guarantees the
90
+ documented kill-switch works even when the key is not propagated into
91
+ config (e.g. a bare ``LAST30DAYS_TRUSTPILOT_NO_BROWSER=1`` in cron/CI).
92
+ """
93
+ if config and _truthy(config.get(NO_BROWSER_ENV)):
94
+ return False
95
+ if _truthy(os.environ.get(NO_BROWSER_ENV)):
96
+ return False
97
+ return True
98
+
99
+
100
+ def is_brand_shaped(topic: str) -> bool:
101
+ """True when the topic looks like a company/brand Trustpilot can resolve.
102
+
103
+ A domain-like token always qualifies. Otherwise the topic must be a short
104
+ (<=2-word) capitalized proper noun with no generic tokens -- this lets
105
+ "ChowNow", "Nothing Phone", and "OpenAI" through while keeping "AI coding
106
+ agents", "agent memory", and "Golden State Warriors" out.
107
+ """
108
+ if not topic or not topic.strip():
109
+ return False
110
+ text = topic.strip()
111
+ if _DOMAIN_RE.search(text.lower()):
112
+ return True
113
+ words = text.split()
114
+ if len(words) > 2:
115
+ return False
116
+ if any(w.lower() in _GENERIC_TOKENS or w.lower() in _TECH_TOKENS for w in words):
117
+ return False
118
+ # At least one token must look like a proper noun (leading capital).
119
+ return any(w[:1].isupper() for w in words)
120
+
121
+
122
+ def _company_identifier(topic: str) -> str:
123
+ """Pick the identifier to hand the CLI: a domain token if present, else the
124
+ cleaned topic string."""
125
+ m = _DOMAIN_RE.search(topic.lower())
126
+ if m:
127
+ return m.group(0)
128
+ return topic.strip()
129
+
130
+
131
+ def _build_info_args(identifier: str) -> List[str]:
132
+ return [CLI_BIN, "info", identifier, "--agent"]
133
+
134
+
135
+ def _run_cli(cmd: List[str], timeout: int) -> Dict[str, Any]:
136
+ """Invoke trustpilot-pp-cli and parse the JSON object. Never raises."""
137
+ if not _is_available():
138
+ return {"error": f"{CLI_BIN} not on PATH"}
139
+ try:
140
+ result = subproc.run_with_timeout(cmd, timeout=timeout)
141
+ except subproc.SubprocTimeout as exc:
142
+ _log(f"Timeout: {exc}")
143
+ return {"error": str(exc)}
144
+ except FileNotFoundError as exc:
145
+ _log(f"Binary missing: {exc}")
146
+ return {"error": str(exc)}
147
+ except OSError as exc:
148
+ _log(f"Spawn failed: {exc}")
149
+ return {"error": str(exc)}
150
+
151
+ if result.returncode != 0:
152
+ snippet = (result.stderr or "").strip().splitlines()[:1]
153
+ first = snippet[0] if snippet else f"exit {result.returncode}"
154
+ _log(f"CLI exit {result.returncode}: {first}")
155
+ return {"error": first}
156
+
157
+ stdout = result.stdout or ""
158
+ if not stdout.strip():
159
+ return {}
160
+ try:
161
+ data = json.loads(stdout)
162
+ except json.JSONDecodeError as exc:
163
+ _log(f"JSON decode failed: {exc}")
164
+ return {"error": f"json decode: {exc}"}
165
+ return data if isinstance(data, dict) else {}
166
+
167
+
168
+ def search_trustpilot(
169
+ topic: str,
170
+ from_date: str,
171
+ to_date: str,
172
+ depth: str = "default",
173
+ config: Optional[Dict[str, Any]] = None,
174
+ ) -> Dict[str, Any]:
175
+ """Look up a company's Trustpilot sentiment, gated on a brand-shaped topic.
176
+
177
+ Returns ``{"results": [info_dict]}`` for a resolved company, or
178
+ ``{"results": []}`` when the topic is not brand-shaped, the browser opt-out
179
+ is set, or the CLI fails.
180
+ """
181
+ if not is_brand_shaped(topic):
182
+ return {"results": []}
183
+ if not _is_available():
184
+ return {"results": [], "error": f"{CLI_BIN} not on PATH"}
185
+ if not _harvest_allowed(config):
186
+ _log("skipped: browser opt-out set")
187
+ return {"results": []}
188
+ identifier = _company_identifier(topic)
189
+ _log(f"info '{identifier}'")
190
+ data = _run_cli(_build_info_args(identifier), timeout=SEARCH_TIMEOUT)
191
+ if "error" in data or not data:
192
+ return {"results": []}
193
+ return {"results": [data]}
194
+
195
+
196
+ def _coerce_float(value: Any) -> Optional[float]:
197
+ try:
198
+ return float(value)
199
+ except (TypeError, ValueError):
200
+ return None
201
+
202
+
203
+ def _coerce_int(value: Any) -> Optional[int]:
204
+ try:
205
+ return int(value)
206
+ except (TypeError, ValueError):
207
+ return None
208
+
209
+
210
+ def parse_trustpilot_response(
211
+ response: Dict[str, Any],
212
+ query: str = "",
213
+ ) -> List[Dict[str, Any]]:
214
+ """Parse a Trustpilot ``info`` envelope into a single normalized item.
215
+
216
+ The AI summary is the body (it already balances positive and negative
217
+ sentiment). TrustScore and review count feed engagement and metadata.
218
+ Returns dicts ready for ``normalize._normalize_trustpilot``.
219
+ """
220
+ raw = response.get("results") if isinstance(response, dict) else None
221
+ if not isinstance(raw, list) or not raw:
222
+ return []
223
+ info = raw[0]
224
+ if not isinstance(info, dict):
225
+ return []
226
+
227
+ resolved_name = str(info.get("name") or info.get("displayName") or "").strip()
228
+ ai_summary = str(info.get("aiSummary") or info.get("summary") or "").strip()
229
+ trust_score = _coerce_float(info.get("trustScore") or info.get("score"))
230
+ review_count = _coerce_int(
231
+ info.get("reviewCount") or info.get("numberOfReviews") or info.get("total")
232
+ )
233
+ url = str(info.get("url") or "").strip()
234
+ domain = str(info.get("domain") or info.get("identifyingName") or "").strip()
235
+ if not url and domain:
236
+ url = f"https://www.trustpilot.com/review/{domain}"
237
+
238
+ # Require substantive content from the company record itself; do not
239
+ # fabricate an item from the query alone when the CLI returned nothing.
240
+ if not resolved_name and not ai_summary and trust_score is None and review_count is None:
241
+ return []
242
+
243
+ name = resolved_name or query.strip()
244
+
245
+ title = f"{name} on Trustpilot" if name else "Trustpilot reviews"
246
+ if trust_score is not None:
247
+ title = f"{name}: TrustScore {trust_score}" if name else title
248
+
249
+ engagement: Dict[str, float | int] = {}
250
+ if review_count is not None:
251
+ engagement["reviews"] = review_count
252
+ if trust_score is not None:
253
+ engagement["trustScore"] = trust_score
254
+
255
+ relevance = token_overlap_relevance(query, name) if (query and name) else 0.7
256
+
257
+ why = "Trustpilot brand sentiment"
258
+ if trust_score is not None and review_count is not None:
259
+ why = f"Trustpilot: TrustScore {trust_score} across {review_count} reviews"
260
+ elif trust_score is not None:
261
+ why = f"Trustpilot: TrustScore {trust_score}"
262
+
263
+ return [
264
+ {
265
+ "id": domain or name or "trustpilot",
266
+ "title": title,
267
+ "url": url,
268
+ "summary": ai_summary,
269
+ "name": name,
270
+ "trustScore": trust_score,
271
+ "reviewCount": review_count,
272
+ "date": dates.get_date_range(1)[0],
273
+ "engagement": engagement,
274
+ "relevance": round(min(1.0, max(0.4, relevance)), 2),
275
+ "why_relevant": why,
276
+ }
277
+ ]
@@ -0,0 +1,162 @@
1
+ """Truth Social search via Mastodon-compatible API (requires bearer token).
2
+
3
+ Uses truthsocial.com/api/v2/search endpoint.
4
+ Requires TRUTHSOCIAL_TOKEN env var (bearer token from browser dev tools).
5
+ """
6
+
7
+ import math
8
+ import re
9
+ import sys
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from . import http, log
13
+
14
+ TRUTHSOCIAL_SEARCH_URL = "https://truthsocial.com/api/v2/search"
15
+
16
+ DEPTH_CONFIG = {
17
+ "quick": 15,
18
+ "default": 30,
19
+ "deep": 60,
20
+ }
21
+
22
+
23
+ def _log(msg: str):
24
+ log.source_log("TruthSocial", msg, tty_only=False)
25
+
26
+
27
+ def _strip_html(html: str) -> str:
28
+ """Strip HTML tags from Truth Social post content."""
29
+ text = re.sub(r'<br\s*/?>', '\n', html)
30
+ text = re.sub(r'<[^>]+>', '', text)
31
+ return text.strip()
32
+
33
+
34
+ def _extract_core_subject(topic: str) -> str:
35
+ """Extract core subject from verbose query for Truth Social search."""
36
+ from .query import SOCIAL_NOISE, extract_core_subject
37
+ return extract_core_subject(topic, noise=SOCIAL_NOISE)
38
+
39
+
40
+ def _parse_date(status: Dict[str, Any]) -> Optional[str]:
41
+ """Parse date from Mastodon status to YYYY-MM-DD.
42
+
43
+ Mastodon uses ISO 8601 format in created_at field.
44
+ """
45
+ val = status.get("created_at")
46
+ if val and isinstance(val, str) and len(val) >= 10:
47
+ return val[:10]
48
+ return None
49
+
50
+
51
+ def search_truthsocial(
52
+ topic: str,
53
+ from_date: str,
54
+ to_date: str,
55
+ depth: str = "default",
56
+ config: Optional[Dict[str, Any]] = None,
57
+ ) -> Dict[str, Any]:
58
+ """Search Truth Social via Mastodon-compatible API.
59
+
60
+ Args:
61
+ topic: Search topic
62
+ from_date: Start date (YYYY-MM-DD)
63
+ to_date: End date (YYYY-MM-DD)
64
+ depth: 'quick', 'default', or 'deep'
65
+ config: Config dict with TRUTHSOCIAL_TOKEN
66
+
67
+ Returns:
68
+ Dict with 'statuses' list from Mastodon API response.
69
+ """
70
+ config = config or {}
71
+ token = config.get("TRUTHSOCIAL_TOKEN", "")
72
+
73
+ if not token:
74
+ return {"statuses": [], "error": "Truth Social token not configured"}
75
+
76
+ count = DEPTH_CONFIG.get(depth, DEPTH_CONFIG["default"])
77
+ core_topic = _extract_core_subject(topic)
78
+
79
+ _log(f"Searching for '{core_topic}' (depth={depth}, limit={count})")
80
+
81
+ from urllib.parse import urlencode
82
+ params = {
83
+ "q": core_topic,
84
+ "type": "statuses",
85
+ "limit": str(min(count, 40)),
86
+ }
87
+ url = f"{TRUTHSOCIAL_SEARCH_URL}?{urlencode(params)}"
88
+
89
+ try:
90
+ response = http.request(
91
+ "GET", url,
92
+ headers={"Authorization": f"Bearer {token}"},
93
+ timeout=30,
94
+ )
95
+ except http.HTTPError as e:
96
+ if e.status_code == 401:
97
+ _log("Token expired")
98
+ return {"statuses": [], "error": "Truth Social token expired"}
99
+ elif e.status_code == 403:
100
+ _log("Access denied (Cloudflare)")
101
+ return {"statuses": [], "error": "Truth Social access denied (Cloudflare)"}
102
+ elif e.status_code == 429:
103
+ _log("Rate limited")
104
+ return {"statuses": [], "error": "Truth Social rate limited"}
105
+ else:
106
+ _log(f"Search failed: {e}")
107
+ return {"statuses": [], "error": f"Truth Social search failed: {e.status_code}"}
108
+ except Exception as e:
109
+ _log(f"Search failed: {e}")
110
+ return {"statuses": [], "error": str(e)}
111
+
112
+ statuses = response.get("statuses", [])
113
+ _log(f"Found {len(statuses)} posts")
114
+ return response
115
+
116
+
117
+ def parse_truthsocial_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
118
+ """Parse Mastodon API response into normalized item dicts.
119
+
120
+ Returns:
121
+ List of item dicts ready for normalization.
122
+ """
123
+ statuses = response.get("statuses", [])
124
+ items = []
125
+
126
+ for i, status in enumerate(statuses):
127
+ content_html = status.get("content") or ""
128
+ text = _strip_html(content_html)
129
+
130
+ account = status.get("account") or {}
131
+ handle = account.get("acct") or account.get("username") or ""
132
+ display_name = account.get("display_name") or handle
133
+
134
+ url = status.get("url") or ""
135
+
136
+ likes = status.get("favourites_count") or 0
137
+ reposts = status.get("reblogs_count") or 0
138
+ replies = status.get("replies_count") or 0
139
+
140
+ date_str = _parse_date(status)
141
+
142
+ # Relevance: position-based (search results are ranked by relevance)
143
+ rank_score = max(0.3, 1.0 - (i * 0.02))
144
+ engagement_boost = min(0.2, math.log1p(likes + reposts) / 40)
145
+ relevance = min(1.0, rank_score * 0.7 + engagement_boost + 0.1)
146
+
147
+ items.append({
148
+ "handle": handle,
149
+ "display_name": display_name,
150
+ "text": text,
151
+ "url": url,
152
+ "date": date_str,
153
+ "engagement": {
154
+ "likes": likes,
155
+ "reposts": reposts,
156
+ "replies": replies,
157
+ },
158
+ "relevance": round(relevance, 2),
159
+ "why_relevant": f"Truth Social: @{handle}: {text[:60]}" if text else f"Truth Social: {handle}",
160
+ })
161
+
162
+ return items