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,794 @@
1
+ """Polymarket prediction market search via Gamma API (free, no auth required).
2
+
3
+ Uses gamma-api.polymarket.com for event/market discovery.
4
+ No API key needed - public read-only API with generous rate limits (15K req/10s).
5
+ """
6
+
7
+ import json
8
+ import math
9
+ import re
10
+ import sys
11
+ from concurrent.futures import ThreadPoolExecutor, as_completed
12
+ from typing import Any, Dict, List, Optional
13
+ from urllib.parse import quote_plus, urlencode
14
+
15
+ from . import http, log
16
+ from .relevance import LOW_SIGNAL_QUERY_TOKENS, token_overlap_relevance
17
+
18
+ GAMMA_SEARCH_URL = "https://gamma-api.polymarket.com/public-search"
19
+
20
+ # Pages to fetch per query (API returns 5 events per page, limit param is a no-op)
21
+ DEPTH_CONFIG = {
22
+ "quick": 1,
23
+ "default": 3,
24
+ "deep": 4,
25
+ }
26
+
27
+ # Max events to return after merge + dedup + re-ranking
28
+ RESULT_CAP = {
29
+ "quick": 5,
30
+ "default": 15,
31
+ "deep": 25,
32
+ }
33
+
34
+
35
+ def _log(msg: str):
36
+ log.source_log("PM", msg, tty_only=False)
37
+
38
+
39
+ def _extract_core_subject(topic: str) -> str:
40
+ """Extract core subject from topic string.
41
+
42
+ Strips common prefixes like 'last 7 days', 'what are people saying about', etc.
43
+ """
44
+ topic = topic.strip()
45
+ # Remove common leading phrases
46
+ prefixes = [
47
+ r"^last \d+ days?\s+",
48
+ r"^what(?:'s| is| are) (?:people saying about|happening with|going on with)\s+",
49
+ r"^how (?:is|are)\s+",
50
+ r"^tell me about\s+",
51
+ r"^research\s+",
52
+ ]
53
+ for pattern in prefixes:
54
+ topic = re.sub(pattern, "", topic, flags=re.IGNORECASE)
55
+ return topic.strip()
56
+
57
+
58
+ def _expand_queries(topic: str) -> List[str]:
59
+ """Generate search queries to cast a wider net.
60
+
61
+ Strategy:
62
+ - Always include the core subject
63
+ - Add ALL individual words as standalone searches (not just first)
64
+ - Include the full topic if different from core
65
+ - Cap at 6 queries, dedupe
66
+ """
67
+ core = _extract_core_subject(topic)
68
+ queries = [core]
69
+
70
+ # Add ALL individual words as separate queries
71
+ words = core.split()
72
+ if len(words) >= 2:
73
+ for word in words:
74
+ if len(word) > 1 and word.lower() not in LOW_SIGNAL_QUERY_TOKENS and word.lower() not in _NOISE_WORDS:
75
+ queries.append(word)
76
+
77
+ # Add the full topic if different from core
78
+ if topic.lower().strip() != core.lower():
79
+ queries.append(topic.strip())
80
+
81
+ # Dedupe while preserving order, cap at 6
82
+ seen = set()
83
+ unique = []
84
+ for q in queries:
85
+ q_lower = q.lower().strip()
86
+ if q_lower and q_lower not in seen:
87
+ seen.add(q_lower)
88
+ unique.append(q.strip())
89
+ return unique[:6]
90
+
91
+
92
+ _GENERIC_TAGS = frozenset({"sports", "politics", "crypto", "science", "culture", "pop culture"})
93
+
94
+ # Words that are too generic to serve as the sole topic-match signal.
95
+ # If ALL core words from the topic are in this set, we skip filtering (can't meaningfully filter).
96
+ # But if some words are informative and some are generic, we require at least one informative word.
97
+ _NOISE_WORDS = frozenset({
98
+ # Articles, prepositions, conjunctions
99
+ "the", "a", "an", "in", "on", "at", "of", "for", "and", "or", "to", "is", "are",
100
+ "was", "were", "will", "be", "by", "with", "from", "as", "it", "its", "not", "no",
101
+ "but", "if", "so", "do", "has", "had", "have", "this", "that", "what", "who",
102
+ # Directional / geographic terms that cause false matches
103
+ "west", "east", "north", "south", "central", "southern", "northern", "eastern", "western",
104
+ # Common sports / category terms
105
+ "champion", "championship", "league", "division", "conference", "cup", "series",
106
+ "team", "game", "match", "season", "win", "winner", "finals",
107
+ # Common geographic / place nouns that cause false matches
108
+ # "club" -> Athletic Club, Racing Club; "island" -> Epstein's Island, Rhode Island
109
+ "club", "island", "city", "park", "hill", "lake", "bay", "beach", "valley",
110
+ "river", "mountain", "county", "state", "village", "town", "point", "creek",
111
+ "springs", "heights", "ridge", "bridge", "harbor", "port", "station", "center",
112
+ "square", "field", "forest", "garden", "tower", "school", "church", "camp",
113
+ "ranch", "crossing", "shore", "rock", "summit", "falls", "grove", "haven",
114
+ # Generic tech terms that match too broadly on Polymarket
115
+ # "cli" -> any CLI tool market; "mcp" -> protocol markets; "ai" -> every AI market
116
+ "cli", "mcp", "protocol", "tool", "app", "code", "model", "ai", "api",
117
+ "software", "plugin", "skill", "agent", "bot", "search", "research",
118
+ # Generic prediction market terms
119
+ "market", "odds", "prediction", "forecast", "chance", "probability",
120
+ # Comparison-query conjunctions — should not count as informative filter tokens
121
+ # when the topic is "X vs Y vs Z"
122
+ "vs", "versus",
123
+ })
124
+
125
+
126
+ def _passes_topic_filter(topic: str, event_title: str) -> bool:
127
+ """Check if event title contains enough informative words from the topic.
128
+
129
+ Prevents noise like "Meek Mill" matching "Mill.com food recycler" by requiring
130
+ proportional word overlap. For topics with 3+ informative words, at least 2 must
131
+ match. For shorter topics, 1 match suffices (existing behavior).
132
+
133
+ Returns True if the event should be kept, False if it should be filtered out.
134
+ """
135
+ core = _extract_core_subject(topic).lower()
136
+ core_words = [w for w in re.sub(r"[^\w\s]", " ", core).split() if len(w) > 1]
137
+
138
+ if not core_words:
139
+ return True # No words to check against
140
+
141
+ # Split into informative vs generic
142
+ informative = [w for w in core_words if w not in _NOISE_WORDS]
143
+
144
+ # If ALL words are generic, we can't meaningfully filter — keep everything
145
+ if not informative:
146
+ return True
147
+
148
+ # Normalize the title for matching
149
+ title_lower = " ".join(re.sub(r"[^\w\s]", " ", event_title.lower()).split())
150
+ title_words = set(title_lower.split())
151
+
152
+ # Count how many informative words appear in the title
153
+ match_count = 0
154
+ for word in informative:
155
+ # Check as whole word in the title word set
156
+ if word in title_words:
157
+ match_count += 1
158
+ continue
159
+ # Also check as substring for compound words (e.g., "kanye" in "kanyewest")
160
+ if len(word) >= 4 and word in title_lower:
161
+ match_count += 1
162
+
163
+ # For topics with 3+ informative words, require at least 2 matches.
164
+ # This prevents single-word false positives like "mill" in "Meek Mill"
165
+ # when the topic is "Mill.com food recycler" (3 informative words).
166
+ min_matches = 2 if len(informative) >= 3 else 1
167
+
168
+ return match_count >= min_matches
169
+
170
+
171
+ def _passes_any_informative_word(topic: str, event_title: str) -> bool:
172
+ """Looser variant of _passes_topic_filter that keeps an item if ANY
173
+ informative word from the topic appears in the title.
174
+
175
+ Designed for post-merge validation of comparison topics (e.g., "OpenClaw vs
176
+ Hermes vs Paperclip"), where a market mentioning just one of the entities
177
+ is still on-topic. The stricter _passes_topic_filter (min_matches=2 for
178
+ 3+ informative words) is correct for single-entity topics like "Mill.com
179
+ food recycler" but drops legitimate single-entity comparison results.
180
+ """
181
+ core = _extract_core_subject(topic).lower()
182
+ core_words = [w for w in re.sub(r"[^\w\s]", " ", core).split() if len(w) > 1]
183
+ if not core_words:
184
+ return True
185
+ informative = [w for w in core_words if w not in _NOISE_WORDS]
186
+ if not informative:
187
+ return True
188
+
189
+ title_lower = " ".join(re.sub(r"[^\w\s]", " ", event_title.lower()).split())
190
+ title_words = set(title_lower.split())
191
+
192
+ for word in informative:
193
+ if word in title_words:
194
+ return True
195
+ if len(word) >= 4 and word in title_lower:
196
+ return True
197
+ return False
198
+
199
+
200
+ def filter_items_against_topic(topic: str, items: List[Any]) -> List[Any]:
201
+ """Drop items whose title shares no informative word with the original topic.
202
+
203
+ Called post-merge from pipeline.py so per-entity subquery results for
204
+ comparison topics get re-validated against the ORIGINAL full topic before
205
+ landing in the footer. Prevents noise like WTI crude oil or Elon tweet
206
+ markets from surviving a loose "Hermes" single-entity subquery match.
207
+
208
+ Uses the looser _passes_any_informative_word rule (ANY entity name match
209
+ is sufficient) so a market mentioning just one of several compared entities
210
+ still counts as on-topic.
211
+
212
+ Accepts a list of either raw dicts (with 'title') or SourceItem-like objects
213
+ (with .title attribute). Returns the filtered list in the same order.
214
+ """
215
+ if not topic:
216
+ return items
217
+
218
+ filtered = []
219
+ for item in items:
220
+ title = getattr(item, "title", None)
221
+ if title is None and isinstance(item, dict):
222
+ title = item.get("title", "")
223
+ title = title or ""
224
+
225
+ if _passes_any_informative_word(topic, title):
226
+ filtered.append(item)
227
+
228
+ dropped = len(items) - len(filtered)
229
+ if dropped:
230
+ _log(f"Post-merge topic filter dropped {dropped} Polymarket items against full topic '{topic}'")
231
+
232
+ return filtered
233
+
234
+
235
+ def filter_items_against_keywords(items: List[Any], keywords: List[str]) -> List[Any]:
236
+ """Keep only items whose title contains at least one keyword (case-insensitive).
237
+
238
+ Intended for disambiguating ambiguous single-token topics like 'Warriors'
239
+ via --polymarket-keywords (e.g., 'nba,gsw,golden-state') to filter out
240
+ Glasgow Warriors rugby, Honor of Kings Rogue Warriors markets that share
241
+ the 'Warriors' token but are not the target entity.
242
+ """
243
+ if not keywords:
244
+ return items
245
+ normalized_keywords = [kw.strip().lower() for kw in keywords if kw and kw.strip()]
246
+ if not normalized_keywords:
247
+ return items
248
+
249
+ filtered = []
250
+ for item in items:
251
+ title = getattr(item, "title", None)
252
+ if title is None and isinstance(item, dict):
253
+ title = item.get("title", "")
254
+ title = (title or "").lower()
255
+ if any(kw in title for kw in normalized_keywords):
256
+ filtered.append(item)
257
+
258
+ dropped = len(items) - len(filtered)
259
+ if dropped:
260
+ _log(
261
+ f"Keyword filter dropped {dropped} Polymarket items; "
262
+ f"kept {len(filtered)} matching {normalized_keywords}"
263
+ )
264
+
265
+ return filtered
266
+
267
+
268
+ def _extract_domain_queries(topic: str, events: List[Dict]) -> List[str]:
269
+ """Extract domain-indicator search terms from first-pass event tags.
270
+
271
+ Uses structured tag metadata from Gamma API events to discover broader
272
+ domain categories (e.g., 'NCAA CBB' from a Big 12 basketball event).
273
+ Falls back to frequent title bigrams if no useful tags exist.
274
+ """
275
+ query_words = set(_extract_core_subject(topic).lower().split())
276
+
277
+ # Collect tag labels from all first-pass events, count occurrences
278
+ tag_counts: Dict[str, int] = {}
279
+ for event in events:
280
+ tags = event.get("tags") or []
281
+ for tag in tags:
282
+ label = tag.get("label", "") if isinstance(tag, dict) else str(tag)
283
+ if not label:
284
+ continue
285
+ label_lower = label.lower()
286
+ # Skip generic category tags and tags matching existing queries
287
+ if label_lower in _GENERIC_TAGS:
288
+ continue
289
+ if label_lower in query_words:
290
+ continue
291
+ tag_counts[label] = tag_counts.get(label, 0) + 1
292
+
293
+ # Sort by frequency, take top 2 that appear in 2+ events
294
+ domain_queries = [
295
+ label for label, count in sorted(tag_counts.items(), key=lambda x: -x[1])
296
+ if count >= 2
297
+ ][:2]
298
+
299
+ return domain_queries
300
+
301
+
302
+ def _infer_query_intent(topic: str) -> str:
303
+ """Narrower local classifier for Polymarket search tuning only.
304
+
305
+ Deliberately does NOT delegate to ``query.infer_query_intent``:
306
+ Polymarket only needs the prediction/non-prediction split, and the
307
+ broader classifier would route queries to ``how_to``, ``opinion``,
308
+ ``product``, etc. without any matching expansion branch downstream.
309
+ Keep this narrow until polymarket grows additional intents.
310
+ """
311
+ text = topic.lower().strip()
312
+ if re.search(r"\b(predict|prediction|odds|forecast|chance|probability|will .* win)\b", text):
313
+ return "prediction"
314
+ return "breaking_news"
315
+
316
+
317
+ def _search_single_query(query: str, page: int = 1) -> Dict[str, Any]:
318
+ """Run a single search query against Gamma API."""
319
+ params = {
320
+ "q": query,
321
+ "page": str(page),
322
+ "events_status": "active",
323
+ "keep_closed_markets": "0",
324
+ }
325
+ url = f"{GAMMA_SEARCH_URL}?{urlencode(params)}"
326
+
327
+ try:
328
+ response = http.request("GET", url, timeout=15, retries=2)
329
+ return response
330
+ except http.HTTPError as e:
331
+ _log(f"Search failed for '{query}' page {page}: {e}")
332
+ return {"events": [], "error": str(e)}
333
+ except Exception as e:
334
+ _log(f"Search failed for '{query}' page {page}: {e}")
335
+ return {"events": [], "error": str(e)}
336
+
337
+
338
+ def _run_queries_parallel(
339
+ queries: List[str], pages: int, all_events: Dict, errors: List, start_idx: int = 0,
340
+ ) -> None:
341
+ """Run (query, page) combinations in parallel, merging into all_events."""
342
+ with ThreadPoolExecutor(max_workers=min(8, len(queries) * pages)) as executor:
343
+ futures = {}
344
+ for i, q in enumerate(queries, start=start_idx):
345
+ for p in range(1, pages + 1):
346
+ future = executor.submit(_search_single_query, q, p)
347
+ futures[future] = i
348
+
349
+ for future in as_completed(futures):
350
+ query_idx = futures[future]
351
+ try:
352
+ response = future.result(timeout=15)
353
+ if response.get("error"):
354
+ errors.append(response["error"])
355
+
356
+ events = response.get("events", [])
357
+ for event in events:
358
+ event_id = event.get("id", "")
359
+ if not event_id:
360
+ continue
361
+ if event_id not in all_events:
362
+ all_events[event_id] = (event, query_idx)
363
+ elif query_idx < all_events[event_id][1]:
364
+ all_events[event_id] = (event, query_idx)
365
+ except Exception as e:
366
+ errors.append(str(e))
367
+
368
+
369
+ def search_polymarket(
370
+ topic: str,
371
+ from_date: str,
372
+ to_date: str,
373
+ depth: str = "default",
374
+ ) -> Dict[str, Any]:
375
+ """Search Polymarket via Gamma API with two-pass query expansion.
376
+
377
+ Pass 1: Run expanded queries in parallel, merge and dedupe by event ID.
378
+ Pass 2: Extract domain-indicator terms from first-pass titles, search those.
379
+
380
+ Args:
381
+ topic: Search topic
382
+ from_date: Start date (YYYY-MM-DD) - used for activity filtering
383
+ to_date: End date (YYYY-MM-DD)
384
+ depth: 'quick', 'default', or 'deep'
385
+
386
+ Returns:
387
+ Dict with 'events' list and optional 'error'.
388
+ """
389
+ pages = DEPTH_CONFIG.get(depth, DEPTH_CONFIG["default"])
390
+ cap = RESULT_CAP.get(depth, RESULT_CAP["default"])
391
+ queries = _expand_queries(topic)
392
+
393
+ _log(f"Searching for '{topic}' with queries: {queries} (pages={pages})")
394
+
395
+ # Pass 1: run expanded queries in parallel
396
+ all_events: Dict[str, tuple] = {}
397
+ errors: List[str] = []
398
+ _run_queries_parallel(queries, pages, all_events, errors)
399
+
400
+ # Pass 2: extract domain-indicator terms from first-pass titles and search
401
+ first_pass_events = [ev for ev, _ in all_events.values()]
402
+ domain_queries = _extract_domain_queries(topic, first_pass_events)
403
+ # Filter out queries we already ran
404
+ seen_queries = {q.lower() for q in queries}
405
+ domain_queries = [dq for dq in domain_queries if dq.lower() not in seen_queries]
406
+
407
+ if domain_queries:
408
+ _log(f"Domain expansion queries: {domain_queries}")
409
+ _run_queries_parallel(domain_queries, 1, all_events, errors, start_idx=len(queries))
410
+
411
+ merged_events = [ev for ev, _ in sorted(all_events.values(), key=lambda x: x[1])]
412
+ total_queries = len(queries) + len(domain_queries)
413
+ _log(f"Found {len(merged_events)} unique events across {total_queries} queries")
414
+
415
+ result = {"events": merged_events, "_cap": cap}
416
+ if errors and not merged_events:
417
+ result["error"] = "; ".join(errors[:2])
418
+ return result
419
+
420
+
421
+ def _format_price_movement(market: Dict[str, Any]) -> Optional[str]:
422
+ """Pick the most significant price change and format it.
423
+
424
+ Returns string like 'down 11.7% this month' or None if no significant change.
425
+ """
426
+ changes = [
427
+ (abs(market.get("oneDayPriceChange") or 0), market.get("oneDayPriceChange"), "today"),
428
+ (abs(market.get("oneWeekPriceChange") or 0), market.get("oneWeekPriceChange"), "this week"),
429
+ (abs(market.get("oneMonthPriceChange") or 0), market.get("oneMonthPriceChange"), "this month"),
430
+ ]
431
+
432
+ # Pick the largest absolute change
433
+ changes.sort(key=lambda x: x[0], reverse=True)
434
+ abs_change, raw_change, period = changes[0]
435
+
436
+ # Skip if change is less than 1% (noise)
437
+ if abs_change < 0.01:
438
+ return None
439
+
440
+ direction = "up" if raw_change > 0 else "down"
441
+ pct = abs_change * 100
442
+ return f"{direction} {pct:.1f}% {period}"
443
+
444
+
445
+ def _parse_outcome_prices(market: Dict[str, Any]) -> List[tuple]:
446
+ """Parse outcomePrices JSON string into list of (outcome_name, price) tuples."""
447
+ outcomes_raw = market.get("outcomes") or []
448
+ prices_raw = market.get("outcomePrices")
449
+
450
+ if not prices_raw:
451
+ return []
452
+
453
+ # Both outcomes and outcomePrices can be JSON-encoded strings
454
+ try:
455
+ if isinstance(outcomes_raw, str):
456
+ outcomes = json.loads(outcomes_raw)
457
+ else:
458
+ outcomes = outcomes_raw
459
+ except (json.JSONDecodeError, TypeError):
460
+ outcomes = []
461
+
462
+ try:
463
+ if isinstance(prices_raw, str):
464
+ prices = json.loads(prices_raw)
465
+ else:
466
+ prices = prices_raw
467
+ except (json.JSONDecodeError, TypeError):
468
+ return []
469
+
470
+ result = []
471
+ for i, price in enumerate(prices):
472
+ try:
473
+ p = float(price)
474
+ except (ValueError, TypeError):
475
+ continue
476
+ name = outcomes[i] if i < len(outcomes) else f"Outcome {i+1}"
477
+ result.append((name, p))
478
+
479
+ return result
480
+
481
+
482
+ def _shorten_question(question: str) -> str:
483
+ """Extract a short display name from a market question.
484
+
485
+ 'Will Arizona win the 2026 NCAA Tournament?' -> 'Arizona'
486
+ 'Will Duke be a number 1 seed in the 2026 NCAA...' -> 'Duke'
487
+ """
488
+ q = question.strip().rstrip("?")
489
+ # Common patterns: "Will X win/be/...", "X wins/loses..."
490
+ m = re.match(r"^Will\s+(.+?)\s+(?:win|be|make|reach|have|lose|qualify|advance|strike|agree|pass|sign|get|become|remain|stay|leave|survive|next)\b", q, re.IGNORECASE)
491
+ if m:
492
+ return m.group(1).strip()
493
+ m = re.match(r"^Will\s+(.+?)\s+", q, re.IGNORECASE)
494
+ if m and len(m.group(1).split()) <= 4:
495
+ return m.group(1).strip()
496
+ # Fallback: truncate, dropping a leading article so the name doesn't read "an"/"the"
497
+ text = q[:40] if len(q) > 40 else q
498
+ return re.sub(r"^(?:a|an|the)\s+", "", text, flags=re.I)
499
+
500
+
501
+ def _compute_text_similarity(topic: str, title: str, outcomes: List[str] = None) -> float:
502
+ """Score how well the event title (or outcome names) match the search topic.
503
+
504
+ Returns 0.0-1.0. Exact title phrase match gets 1.0. Otherwise we reuse the
505
+ shared query-centric relevance scorer and take the best title/outcome match.
506
+ """
507
+ core = _extract_core_subject(topic).lower()
508
+ title_lower = title.lower()
509
+ if not core:
510
+ return 0.5
511
+
512
+ # Full substring match in title
513
+ if core in title_lower:
514
+ return 1.0
515
+
516
+ query_type = _infer_query_intent(topic)
517
+ title_score = token_overlap_relevance(core, title)
518
+ best_score = title_score
519
+
520
+ if outcomes:
521
+ for outcome_name in outcomes:
522
+ outcome_lower = outcome_name.lower()
523
+ outcome_score = token_overlap_relevance(core, outcome_name)
524
+ if _strong_phrase_match(core, outcome_lower):
525
+ outcome_score = max(outcome_score, 0.92 if len(outcome_lower.split()) >= 2 else 0.88)
526
+ if title_score < 0.3:
527
+ outcome_cap = 0.55 if query_type == "prediction" else 0.24
528
+ outcome_score = min(outcome_cap, outcome_score)
529
+ else:
530
+ outcome_score = max(title_score, 0.75 * title_score + 0.25 * outcome_score)
531
+ best_score = max(best_score, outcome_score)
532
+
533
+ return round(best_score, 2)
534
+
535
+
536
+ def _strong_phrase_match(core: str, candidate: str) -> bool:
537
+ """Require real token matches, not accidental short substrings.
538
+
539
+ This prevents binary outcomes like "No" from matching "nano" or similar
540
+ short-string accidents.
541
+ """
542
+ candidate = " ".join(re.sub(r"[^\w\s]", " ", candidate.lower()).split())
543
+ core = " ".join(re.sub(r"[^\w\s]", " ", core.lower()).split())
544
+ if not candidate or not core:
545
+ return False
546
+
547
+ candidate_tokens = candidate.split()
548
+ core_tokens = set(core.split())
549
+
550
+ if len(candidate_tokens) >= 2:
551
+ return candidate in core or core in candidate
552
+
553
+ token = candidate_tokens[0]
554
+ return len(token) > 2 and token in core_tokens
555
+
556
+
557
+ def _safe_float(val, default=0.0) -> float:
558
+ """Safely convert a value to float."""
559
+ try:
560
+ return float(val or default)
561
+ except (ValueError, TypeError):
562
+ return default
563
+
564
+
565
+ def parse_polymarket_response(response: Dict[str, Any], topic: str = "") -> List[Dict[str, Any]]:
566
+ """Parse Gamma API response into normalized item dicts.
567
+
568
+ Each event becomes one item showing its title and top markets.
569
+
570
+ Args:
571
+ response: Raw Gamma API response
572
+ topic: Original search topic (for relevance scoring)
573
+
574
+ Returns:
575
+ List of item dicts ready for normalization.
576
+ """
577
+ events = response.get("events", [])
578
+ items = []
579
+
580
+ filtered_count = 0
581
+ for i, event in enumerate(events):
582
+ event_id = event.get("id", "")
583
+ title = event.get("title", "")
584
+ slug = event.get("slug", "")
585
+
586
+ # Filter: skip closed/resolved events
587
+ if event.get("closed", False):
588
+ continue
589
+ if not event.get("active", True):
590
+ continue
591
+
592
+ # Filter: skip events that don't match the topic's core subject
593
+ # This prevents "NFC West" from matching a "Kanye West" search
594
+ if topic and not _passes_topic_filter(topic, title):
595
+ filtered_count += 1
596
+ continue
597
+
598
+ # Get markets for this event
599
+ markets = event.get("markets", [])
600
+ if not markets:
601
+ continue
602
+
603
+ # Filter to active, open markets with liquidity (excludes resolved markets)
604
+ active_markets = []
605
+ for m in markets:
606
+ if m.get("closed", False):
607
+ continue
608
+ if not m.get("active", True):
609
+ continue
610
+ # Must have liquidity (resolved markets have 0 or None)
611
+ try:
612
+ liq = float(m.get("liquidity", 0) or 0)
613
+ except (ValueError, TypeError):
614
+ liq = 0
615
+ if liq > 0:
616
+ active_markets.append(m)
617
+
618
+ if not active_markets:
619
+ continue
620
+
621
+ # Sort markets by volume (most liquid first)
622
+ def market_volume(m):
623
+ try:
624
+ return float(m.get("volume", 0) or 0)
625
+ except (ValueError, TypeError):
626
+ return 0
627
+ active_markets.sort(key=market_volume, reverse=True)
628
+
629
+ # Take top market for the event
630
+ top_market = active_markets[0]
631
+
632
+ # Collect outcome names from ALL active markets (not just top) for similarity scoring
633
+ # Filter to outcomes with price > 1% to avoid noise
634
+ # Also extract subjects from market questions for neg-risk events (outcomes are Yes/No)
635
+ all_outcome_names = []
636
+ for m in active_markets:
637
+ for name, price in _parse_outcome_prices(m):
638
+ if price > 0.01 and name not in all_outcome_names:
639
+ all_outcome_names.append(name)
640
+ # For neg-risk binary markets (Yes/No outcomes), the team/entity name
641
+ # lives in the question, e.g., "Will Arizona win the NCAA Tournament?"
642
+ question = m.get("question", "")
643
+ if question and question != title:
644
+ all_outcome_names.append(question)
645
+
646
+ # Parse outcome prices - for multi-market events with Yes/No binary
647
+ # sub-markets, synthesize from market questions to show actual
648
+ # team/entity probabilities instead of a single market's Yes/No
649
+ outcome_prices = _parse_outcome_prices(top_market)
650
+ top_outcomes_are_binary = (
651
+ len(outcome_prices) == 2
652
+ and {n.lower() for n, _ in outcome_prices} == {"yes", "no"}
653
+ )
654
+ if top_outcomes_are_binary and len(active_markets) > 1:
655
+ synth_outcomes = []
656
+ for m in active_markets:
657
+ q = m.get("question", "")
658
+ if not q:
659
+ continue
660
+ pairs = _parse_outcome_prices(m)
661
+ yes_price = next((p for name, p in pairs if name.lower() == "yes"), None)
662
+ if yes_price is not None and yes_price > 0.005:
663
+ synth_outcomes.append((q, yes_price))
664
+ if synth_outcomes:
665
+ synth_outcomes.sort(key=lambda x: x[1], reverse=True)
666
+ outcome_prices = [(_shorten_question(q), p) for q, p in synth_outcomes]
667
+
668
+ # Format price movement
669
+ price_movement = _format_price_movement(top_market)
670
+
671
+ # Volume and liquidity - prefer event-level (more stable), fall back to market-level
672
+ event_volume1mo = _safe_float(event.get("volume1mo"))
673
+ event_volume1wk = _safe_float(event.get("volume1wk"))
674
+ event_liquidity = _safe_float(event.get("liquidity"))
675
+ event_competitive = _safe_float(event.get("competitive"))
676
+ volume24hr = _safe_float(event.get("volume24hr")) or _safe_float(top_market.get("volume24hr"))
677
+ liquidity = event_liquidity or _safe_float(top_market.get("liquidity"))
678
+
679
+ # Event URL
680
+ url = f"https://polymarket.com/event/{slug}" if slug else f"https://polymarket.com/event/{event_id}"
681
+
682
+ # Date: use updatedAt from event
683
+ updated_at = event.get("updatedAt", "")
684
+ date_str = None
685
+ if updated_at:
686
+ try:
687
+ date_str = updated_at[:10] # YYYY-MM-DD
688
+ except (IndexError, TypeError):
689
+ pass
690
+
691
+ # End date for the market
692
+ end_date = top_market.get("endDate")
693
+ if end_date:
694
+ try:
695
+ end_date = end_date[:10]
696
+ except (IndexError, TypeError):
697
+ end_date = None
698
+
699
+ # Semantic relevance should dominate. Market quality should refine
700
+ # relevant matches, not rescue unrelated high-liquidity events.
701
+ text_score = _compute_text_similarity(topic, title, all_outcome_names) if topic else 0.5
702
+
703
+ # Volume signal: log-scaled monthly volume (most stable signal)
704
+ vol_raw = event_volume1mo or event_volume1wk or volume24hr
705
+ vol_score = min(1.0, math.log1p(vol_raw) / 16) # ~$9M = 1.0
706
+
707
+ # Liquidity signal
708
+ liq_score = min(1.0, math.log1p(liquidity) / 14) # ~$1.2M = 1.0
709
+
710
+ # Price movement: daily weighted more than monthly
711
+ day_change = abs(top_market.get("oneDayPriceChange") or 0) * 3
712
+ week_change = abs(top_market.get("oneWeekPriceChange") or 0) * 2
713
+ month_change = abs(top_market.get("oneMonthPriceChange") or 0)
714
+ max_change = max(day_change, week_change, month_change)
715
+ movement_score = min(1.0, max_change * 5) # 20% change = 1.0
716
+
717
+ # Competitive bonus: markets near 50/50 are more interesting
718
+ competitive_score = event_competitive
719
+
720
+ market_quality = (
721
+ 0.50 * vol_score +
722
+ 0.25 * liq_score +
723
+ 0.15 * movement_score +
724
+ 0.10 * competitive_score
725
+ )
726
+ relevance = min(1.0, text_score * (0.75 + 0.25 * market_quality))
727
+
728
+ # Surface the topic-matching outcome to the front before truncating
729
+ if topic and outcome_prices:
730
+ core = _extract_core_subject(topic).lower()
731
+ core_tokens = set(core.split())
732
+ reordered = []
733
+ rest = []
734
+ for pair in outcome_prices:
735
+ name_lower = pair[0].lower()
736
+ # Match if full core is substring, or name is substring of core,
737
+ # or any core token appears in the name (handles long question strings)
738
+ if (core in name_lower or name_lower in core
739
+ or any(tok in name_lower for tok in core_tokens if len(tok) > 2)):
740
+ reordered.append(pair)
741
+ else:
742
+ rest.append(pair)
743
+ if reordered:
744
+ outcome_prices = reordered + rest
745
+
746
+ # Top 3 outcomes for multi-outcome markets
747
+ top_outcomes = outcome_prices[:3]
748
+ remaining = len(outcome_prices) - 3
749
+ if remaining < 0:
750
+ remaining = 0
751
+
752
+ items.append({
753
+ "event_id": event_id,
754
+ "title": title,
755
+ "question": top_market.get("question", title),
756
+ "url": url,
757
+ "outcome_prices": top_outcomes,
758
+ "outcomes_remaining": remaining,
759
+ "price_movement": price_movement,
760
+ "volume24hr": volume24hr,
761
+ "volume1mo": event_volume1mo,
762
+ "liquidity": liquidity,
763
+ "date": date_str,
764
+ "end_date": end_date,
765
+ "relevance": round(relevance, 2),
766
+ "why_relevant": f"Prediction market: {title[:60]}",
767
+ })
768
+
769
+ if filtered_count:
770
+ _log(f"Filtered {filtered_count} noise events (topic: '{topic}')")
771
+
772
+ # Sort by relevance (quality-signal ranked) and apply cap
773
+ items.sort(key=lambda x: x["relevance"], reverse=True)
774
+
775
+ # Drop ALL results if nothing is genuinely on-topic.
776
+ # If the best item's relevance is below the threshold, the Gamma API
777
+ # returned only tangential matches (e.g., "Anthropic best AI model"
778
+ # for a "CLI vs MCP" query). Better to show 0 than noise.
779
+ _MIN_RELEVANCE = 0.15
780
+ if items and items[0]["relevance"] < _MIN_RELEVANCE:
781
+ _log(f"All {len(items)} Polymarket results below relevance threshold "
782
+ f"({items[0]['relevance']:.2f} < {_MIN_RELEVANCE}), dropping all")
783
+ return []
784
+
785
+ # Per-item floor: drop individual noise items even if the best item passed
786
+ _ITEM_MIN_RELEVANCE = 0.10
787
+ before_count = len(items)
788
+ items = [i for i in items if i["relevance"] >= _ITEM_MIN_RELEVANCE]
789
+ dropped = before_count - len(items)
790
+ if dropped:
791
+ _log(f"Dropped {dropped} Polymarket items below per-item relevance floor ({_ITEM_MIN_RELEVANCE})")
792
+
793
+ cap = response.get("_cap", len(items))
794
+ return items[:cap]