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.
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +1 -1
- package/LICENSE +24 -0
- package/bin/install.js +7 -5
- package/commands/{luckiest/finish.md → finish.md} +2 -0
- package/commands/go.md +53 -0
- package/commands/plan.md +66 -0
- package/package.json +16 -5
- package/skills/brainstorm/ATTRIBUTION.md +27 -0
- package/skills/brainstorm/SKILL.md +139 -0
- package/skills/luckiest-ab-testing/ATTRIBUTION.md +35 -0
- package/skills/luckiest-ab-testing/CHANGELOG.md +27 -0
- package/skills/luckiest-ab-testing/SKILL.md +379 -0
- package/skills/luckiest-ab-testing/evals/evals.json +118 -0
- package/skills/luckiest-ab-testing/references/sample-size-guide.md +290 -0
- package/skills/luckiest-ab-testing/references/test-templates.md +284 -0
- package/skills/luckiest-ad-creative/ATTRIBUTION.md +35 -0
- package/skills/luckiest-ad-creative/CHANGELOG.md +23 -0
- package/skills/luckiest-ad-creative/SKILL.md +393 -0
- package/skills/luckiest-ad-creative/evals/evals.json +90 -0
- package/skills/luckiest-ad-creative/references/generative-tools.md +637 -0
- package/skills/luckiest-ad-creative/references/platform-specs.md +213 -0
- package/skills/luckiest-ads/ATTRIBUTION.md +36 -0
- package/skills/luckiest-ads/CHANGELOG.md +24 -0
- package/skills/luckiest-ads/LICENSE +21 -0
- package/skills/luckiest-ads/SKILL.md +434 -0
- package/skills/luckiest-ads/evals/evals.json +90 -0
- package/skills/luckiest-ads/references/ad-copy-templates.md +207 -0
- package/skills/luckiest-ads/references/audience-targeting.md +243 -0
- package/skills/luckiest-ads/references/conversion-tracking.md +361 -0
- package/skills/luckiest-ads/references/platform-setup-checklists.md +277 -0
- package/skills/luckiest-advisors/ATTRIBUTION.md +16 -0
- package/skills/luckiest-advisors/CHANGELOG.md +99 -0
- package/skills/luckiest-advisors/SKILL.md +151 -0
- package/skills/luckiest-aeo-grader/ATTRIBUTION.md +19 -0
- package/skills/luckiest-aeo-grader/CHANGELOG.md +52 -0
- package/skills/luckiest-aeo-grader/SKILL.md +200 -0
- package/skills/luckiest-aeo-grader/evals/evals.json +19 -0
- package/skills/luckiest-aeo-grader/references/rubric.md +72 -0
- package/skills/luckiest-aeo-grader/scripts/scan.py +265 -0
- package/skills/luckiest-ai-seo/ATTRIBUTION.md +38 -0
- package/skills/luckiest-ai-seo/CHANGELOG.md +23 -0
- package/skills/luckiest-ai-seo/SKILL.md +510 -0
- package/skills/luckiest-ai-seo/evals/evals.json +90 -0
- package/skills/luckiest-ai-seo/references/content-patterns.md +285 -0
- package/skills/luckiest-ai-seo/references/content-types.md +71 -0
- package/skills/luckiest-ai-seo/references/okf.md +104 -0
- package/skills/luckiest-ai-seo/references/platform-ranking-factors.md +154 -0
- package/skills/luckiest-analytics/ATTRIBUTION.md +42 -0
- package/skills/luckiest-analytics/CHANGELOG.md +36 -0
- package/skills/luckiest-analytics/SKILL.md +380 -0
- package/skills/luckiest-analytics/evals/evals.json +90 -0
- package/skills/luckiest-analytics/references/event-library.md +260 -0
- package/skills/luckiest-analytics/references/ga4-implementation.md +300 -0
- package/skills/luckiest-analytics/references/gtm-implementation.md +390 -0
- package/skills/luckiest-aso/ATTRIBUTION.md +40 -0
- package/skills/luckiest-aso/CHANGELOG.md +33 -0
- package/skills/luckiest-aso/SKILL.md +327 -0
- package/skills/luckiest-aso/evals/evals.json +91 -0
- package/skills/luckiest-aso/references/apple-specs.md +107 -0
- package/skills/luckiest-aso/references/benchmarks.md +129 -0
- package/skills/luckiest-aso/references/google-play-specs.md +131 -0
- package/skills/luckiest-aso/references/report-template.md +213 -0
- package/skills/luckiest-aso/references/scoring-criteria.md +213 -0
- package/skills/luckiest-churn-prevention/ATTRIBUTION.md +41 -0
- package/skills/luckiest-churn-prevention/CHANGELOG.md +24 -0
- package/skills/luckiest-churn-prevention/SKILL.md +445 -0
- package/skills/luckiest-churn-prevention/evals/evals.json +93 -0
- package/skills/luckiest-churn-prevention/references/cancel-flow-patterns.md +316 -0
- package/skills/luckiest-churn-prevention/references/dunning-playbook.md +408 -0
- package/skills/luckiest-co-marketing/ATTRIBUTION.md +38 -0
- package/skills/luckiest-co-marketing/CHANGELOG.md +25 -0
- package/skills/luckiest-co-marketing/SKILL.md +331 -0
- package/skills/luckiest-co-marketing/evals/evals.json +84 -0
- package/skills/luckiest-coder/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder/CHANGELOG.md +30 -0
- package/skills/luckiest-coder/SKILL.md +77 -0
- package/skills/luckiest-coder/references/antigravity-tools.md +23 -0
- package/skills/luckiest-coder/references/codex-tools.md +39 -0
- package/skills/luckiest-coder/references/pi-tools.md +16 -0
- package/skills/luckiest-coder-brainstorming/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-brainstorming/CHANGELOG.md +31 -0
- package/skills/luckiest-coder-brainstorming/SKILL.md +173 -0
- package/skills/luckiest-coder-brainstorming/scripts/frame-template.html +213 -0
- package/skills/luckiest-coder-brainstorming/scripts/helper.js +168 -0
- package/skills/luckiest-coder-brainstorming/scripts/server.cjs +721 -0
- package/skills/luckiest-coder-brainstorming/scripts/start-server.sh +209 -0
- package/skills/luckiest-coder-brainstorming/scripts/stop-server.sh +120 -0
- package/skills/luckiest-coder-brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/skills/luckiest-coder-brainstorming/visual-companion.md +291 -0
- package/skills/luckiest-coder-consistency/ATTRIBUTION.md +30 -0
- package/skills/luckiest-coder-consistency/CHANGELOG.md +26 -0
- package/skills/luckiest-coder-consistency/SKILL.md +98 -0
- package/skills/luckiest-coder-constitution/ATTRIBUTION.md +28 -0
- package/skills/luckiest-coder-constitution/CHANGELOG.md +25 -0
- package/skills/luckiest-coder-constitution/SKILL.md +115 -0
- package/skills/luckiest-coder-debugging/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-debugging/CHANGELOG.md +22 -0
- package/skills/luckiest-coder-debugging/CREATION-LOG.md +119 -0
- package/skills/luckiest-coder-debugging/SKILL.md +309 -0
- package/skills/luckiest-coder-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/luckiest-coder-debugging/condition-based-waiting.md +115 -0
- package/skills/luckiest-coder-debugging/defense-in-depth.md +122 -0
- package/skills/luckiest-coder-debugging/find-polluter.sh +63 -0
- package/skills/luckiest-coder-debugging/root-cause-tracing.md +169 -0
- package/skills/luckiest-coder-debugging/test-academic.md +14 -0
- package/skills/luckiest-coder-debugging/test-pressure-1.md +58 -0
- package/skills/luckiest-coder-debugging/test-pressure-2.md +68 -0
- package/skills/luckiest-coder-debugging/test-pressure-3.md +69 -0
- package/skills/luckiest-coder-dispatching-parallel-agents/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-dispatching-parallel-agents/CHANGELOG.md +23 -0
- package/skills/luckiest-coder-dispatching-parallel-agents/SKILL.md +198 -0
- package/skills/luckiest-coder-executing-plans/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-executing-plans/CHANGELOG.md +22 -0
- package/skills/luckiest-coder-executing-plans/SKILL.md +83 -0
- package/skills/luckiest-coder-finishing-a-branch/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-finishing-a-branch/CHANGELOG.md +22 -0
- package/skills/luckiest-coder-finishing-a-branch/SKILL.md +258 -0
- package/skills/luckiest-coder-git-worktrees/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-git-worktrees/CHANGELOG.md +22 -0
- package/skills/luckiest-coder-git-worktrees/SKILL.md +215 -0
- package/skills/luckiest-coder-guard/ATTRIBUTION.md +29 -0
- package/skills/luckiest-coder-guard/CHANGELOG.md +48 -0
- package/skills/luckiest-coder-guard/LICENSE +21 -0
- package/skills/luckiest-coder-guard/SKILL.md +126 -0
- package/skills/luckiest-coder-guard/bin/check-careful.sh +65 -0
- package/skills/luckiest-coder-guard/bin/check-freeze.sh +48 -0
- package/skills/luckiest-coder-qa/ATTRIBUTION.md +28 -0
- package/skills/luckiest-coder-qa/CHANGELOG.md +49 -0
- package/skills/luckiest-coder-qa/LICENSE +21 -0
- package/skills/luckiest-coder-qa/SKILL.md +160 -0
- package/skills/luckiest-coder-qa/references/issue-taxonomy.md +85 -0
- package/skills/luckiest-coder-qa/references/qa-report-template.md +126 -0
- package/skills/luckiest-coder-receiving-code-review/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-receiving-code-review/CHANGELOG.md +28 -0
- package/skills/luckiest-coder-receiving-code-review/SKILL.md +226 -0
- package/skills/luckiest-coder-requesting-code-review/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-requesting-code-review/CHANGELOG.md +31 -0
- package/skills/luckiest-coder-requesting-code-review/SKILL.md +118 -0
- package/skills/luckiest-coder-requesting-code-review/code-reviewer.md +172 -0
- package/skills/luckiest-coder-shipping/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-shipping/CHANGELOG.md +48 -0
- package/skills/luckiest-coder-shipping/LICENSE +21 -0
- package/skills/luckiest-coder-shipping/SKILL.md +148 -0
- package/skills/luckiest-coder-shipping/references/canary.md +44 -0
- package/skills/luckiest-coder-shipping/references/test-and-coverage.md +91 -0
- package/skills/luckiest-coder-subagent-driven-development/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-subagent-driven-development/CHANGELOG.md +28 -0
- package/skills/luckiest-coder-subagent-driven-development/SKILL.md +439 -0
- package/skills/luckiest-coder-subagent-driven-development/implementer-prompt.md +139 -0
- package/skills/luckiest-coder-subagent-driven-development/scripts/review-package +44 -0
- package/skills/luckiest-coder-subagent-driven-development/scripts/sdd-workspace +22 -0
- package/skills/luckiest-coder-subagent-driven-development/scripts/task-brief +40 -0
- package/skills/luckiest-coder-subagent-driven-development/task-reviewer-prompt.md +188 -0
- package/skills/luckiest-coder-tdd/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-tdd/CHANGELOG.md +26 -0
- package/skills/luckiest-coder-tdd/SKILL.md +384 -0
- package/skills/luckiest-coder-tdd/testing-anti-patterns.md +299 -0
- package/skills/luckiest-coder-verification/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-verification/CHANGELOG.md +22 -0
- package/skills/luckiest-coder-verification/SKILL.md +152 -0
- package/skills/luckiest-coder-writing-plans/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-writing-plans/CHANGELOG.md +22 -0
- package/skills/luckiest-coder-writing-plans/SKILL.md +191 -0
- package/skills/luckiest-coder-writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/luckiest-coder-writing-skills/ATTRIBUTION.md +26 -0
- package/skills/luckiest-coder-writing-skills/CHANGELOG.md +31 -0
- package/skills/luckiest-coder-writing-skills/SKILL.md +702 -0
- package/skills/luckiest-coder-writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/luckiest-coder-writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/luckiest-coder-writing-skills/graphviz-conventions.dot +172 -0
- package/skills/luckiest-coder-writing-skills/persuasion-principles.md +187 -0
- package/skills/luckiest-coder-writing-skills/render-graphs.js +168 -0
- package/skills/luckiest-coder-writing-skills/testing-skills-with-subagents.md +384 -0
- package/skills/luckiest-cold-email/ATTRIBUTION.md +36 -0
- package/skills/luckiest-cold-email/CHANGELOG.md +24 -0
- package/skills/luckiest-cold-email/SKILL.md +213 -0
- package/skills/luckiest-cold-email/evals/evals.json +94 -0
- package/skills/luckiest-cold-email/references/benchmarks.md +83 -0
- package/skills/luckiest-cold-email/references/follow-up-sequences.md +81 -0
- package/skills/luckiest-cold-email/references/frameworks.md +90 -0
- package/skills/luckiest-cold-email/references/personalization.md +79 -0
- package/skills/luckiest-cold-email/references/subject-lines.md +53 -0
- package/skills/luckiest-community-marketing/ATTRIBUTION.md +41 -0
- package/skills/luckiest-community-marketing/CHANGELOG.md +35 -0
- package/skills/luckiest-community-marketing/SKILL.md +200 -0
- package/skills/luckiest-community-marketing/evals/evals.json +89 -0
- package/skills/luckiest-competitor-profiling/ATTRIBUTION.md +36 -0
- package/skills/luckiest-competitor-profiling/CHANGELOG.md +24 -0
- package/skills/luckiest-competitor-profiling/SKILL.md +443 -0
- package/skills/luckiest-competitor-profiling/evals/evals.json +85 -0
- package/skills/luckiest-competitor-profiling/references/templates.md +167 -0
- package/skills/luckiest-competitor-profiling/references/tool-reference.md +179 -0
- package/skills/luckiest-competitors/ATTRIBUTION.md +36 -0
- package/skills/luckiest-competitors/CHANGELOG.md +25 -0
- package/skills/luckiest-competitors/SKILL.md +171 -0
- package/skills/luckiest-competitors/evals/evals.json +93 -0
- package/skills/luckiest-competitors/references/content-architecture.md +271 -0
- package/skills/luckiest-competitors/references/templates.md +223 -0
- package/skills/luckiest-content-strategy/ATTRIBUTION.md +39 -0
- package/skills/luckiest-content-strategy/CHANGELOG.md +23 -0
- package/skills/luckiest-content-strategy/SKILL.md +395 -0
- package/skills/luckiest-content-strategy/evals/evals.json +90 -0
- package/skills/luckiest-content-strategy/references/headless-cms.md +194 -0
- package/skills/luckiest-copy-editing/ATTRIBUTION.md +41 -0
- package/skills/luckiest-copy-editing/CHANGELOG.md +31 -0
- package/skills/luckiest-copy-editing/SKILL.md +491 -0
- package/skills/luckiest-copy-editing/evals/evals.json +89 -0
- package/skills/luckiest-copy-editing/references/checklist.md +66 -0
- package/skills/luckiest-copy-editing/references/content-refresh.md +38 -0
- package/skills/luckiest-copy-editing/references/plain-english-alternatives.md +394 -0
- package/skills/luckiest-copywriting/ATTRIBUTION.md +35 -0
- package/skills/luckiest-copywriting/CHANGELOG.md +23 -0
- package/skills/luckiest-copywriting/SKILL.md +296 -0
- package/skills/luckiest-copywriting/evals/evals.json +111 -0
- package/skills/luckiest-copywriting/references/copy-frameworks.md +344 -0
- package/skills/luckiest-copywriting/references/natural-transitions.md +272 -0
- package/skills/luckiest-cro/ATTRIBUTION.md +34 -0
- package/skills/luckiest-cro/CHANGELOG.md +23 -0
- package/skills/luckiest-cro/SKILL.md +215 -0
- package/skills/luckiest-cro/evals/evals.json +111 -0
- package/skills/luckiest-cro/references/experiments.md +248 -0
- package/skills/luckiest-cro/references/form.md +422 -0
- package/skills/luckiest-customer-research/ATTRIBUTION.md +40 -0
- package/skills/luckiest-customer-research/CHANGELOG.md +26 -0
- package/skills/luckiest-customer-research/SKILL.md +293 -0
- package/skills/luckiest-customer-research/evals/evals.json +162 -0
- package/skills/luckiest-customer-research/references/source-guides.md +401 -0
- package/skills/luckiest-directory-submissions/ATTRIBUTION.md +40 -0
- package/skills/luckiest-directory-submissions/CHANGELOG.md +33 -0
- package/skills/luckiest-directory-submissions/SKILL.md +419 -0
- package/skills/luckiest-directory-submissions/evals/evals.json +94 -0
- package/skills/luckiest-directory-submissions/references/directory-list.md +477 -0
- package/skills/luckiest-directory-submissions/references/positioning-variations.md +232 -0
- package/skills/luckiest-directory-submissions/references/submission-tracker-template.csv +266 -0
- package/skills/luckiest-emails/ATTRIBUTION.md +42 -0
- package/skills/luckiest-emails/CHANGELOG.md +33 -0
- package/skills/luckiest-emails/SKILL.md +220 -0
- package/skills/luckiest-emails/evals/evals.json +93 -0
- package/skills/luckiest-emails/references/copy-guidelines.md +120 -0
- package/skills/luckiest-emails/references/email-types.md +515 -0
- package/skills/luckiest-emails/references/sequence-templates.md +168 -0
- package/skills/luckiest-extract-design-system/ATTRIBUTION.md +8 -0
- package/skills/luckiest-extract-design-system/CHANGELOG.md +37 -0
- package/skills/luckiest-extract-design-system/SKILL.md +83 -0
- package/skills/luckiest-free-tools/ATTRIBUTION.md +43 -0
- package/skills/luckiest-free-tools/CHANGELOG.md +25 -0
- package/skills/luckiest-free-tools/SKILL.md +220 -0
- package/skills/luckiest-free-tools/evals/evals.json +90 -0
- package/skills/luckiest-free-tools/references/tool-types.md +217 -0
- package/skills/luckiest-image/ATTRIBUTION.md +43 -0
- package/skills/luckiest-image/CHANGELOG.md +23 -0
- package/skills/luckiest-image/SKILL.md +376 -0
- package/skills/luckiest-image/evals/evals.json +89 -0
- package/skills/luckiest-image/references/ai-image-prompting.md +229 -0
- package/skills/luckiest-launch/ATTRIBUTION.md +40 -0
- package/skills/luckiest-launch/CHANGELOG.md +43 -0
- package/skills/luckiest-launch/SKILL.md +385 -0
- package/skills/luckiest-launch/evals/evals.json +91 -0
- package/skills/luckiest-lead-magnets/ATTRIBUTION.md +35 -0
- package/skills/luckiest-lead-magnets/CHANGELOG.md +23 -0
- package/skills/luckiest-lead-magnets/SKILL.md +331 -0
- package/skills/luckiest-lead-magnets/evals/evals.json +87 -0
- package/skills/luckiest-lead-magnets/references/benchmarks.md +129 -0
- package/skills/luckiest-lead-magnets/references/format-guide.md +196 -0
- package/skills/luckiest-marketing-ideas/ATTRIBUTION.md +39 -0
- package/skills/luckiest-marketing-ideas/CHANGELOG.md +33 -0
- package/skills/luckiest-marketing-ideas/SKILL.md +191 -0
- package/skills/luckiest-marketing-ideas/evals/evals.json +90 -0
- package/skills/luckiest-marketing-ideas/references/ideas-by-category.md +366 -0
- package/skills/luckiest-marketing-plan/ATTRIBUTION.md +40 -0
- package/skills/luckiest-marketing-plan/CHANGELOG.md +23 -0
- package/skills/luckiest-marketing-plan/SKILL.md +285 -0
- package/skills/luckiest-marketing-plan/evals/evals.json +96 -0
- package/skills/luckiest-marketing-plan/references/aarrr-framework.md +180 -0
- package/skills/luckiest-marketing-plan/references/budget-planning.md +168 -0
- package/skills/luckiest-marketing-plan/references/client-types.md +373 -0
- package/skills/luckiest-marketing-plan/references/current-state-rubric.md +255 -0
- package/skills/luckiest-marketing-plan/references/example-quietude.md +972 -0
- package/skills/luckiest-marketing-plan/references/funding-stage-unlocks.md +230 -0
- package/skills/luckiest-marketing-plan/references/growth-patterns.md +148 -0
- package/skills/luckiest-marketing-plan/references/idea-cross-reference.md +265 -0
- package/skills/luckiest-marketing-plan/references/measurement-framework.md +213 -0
- package/skills/luckiest-marketing-plan/references/methodology.md +363 -0
- package/skills/luckiest-marketing-plan/references/ops-stack-mapping.md +197 -0
- package/skills/luckiest-marketing-plan/references/plan-template.md +494 -0
- package/skills/luckiest-marketing-plan/references/team-and-agency-model.md +278 -0
- package/skills/luckiest-marketing-psychology/ATTRIBUTION.md +31 -0
- package/skills/luckiest-marketing-psychology/CHANGELOG.md +32 -0
- package/skills/luckiest-marketing-psychology/SKILL.md +202 -0
- package/skills/luckiest-marketing-psychology/evals.json +88 -0
- package/skills/luckiest-model-router/ATTRIBUTION.md +11 -0
- package/skills/luckiest-model-router/CHANGELOG.md +45 -0
- package/skills/luckiest-model-router/SKILL.md +74 -0
- package/skills/luckiest-model-router/scripts/route.py +145 -0
- package/skills/luckiest-offers/ATTRIBUTION.md +39 -0
- package/skills/luckiest-offers/CHANGELOG.md +23 -0
- package/skills/luckiest-offers/SKILL.md +167 -0
- package/skills/luckiest-offers/references/bonus-stacking.md +150 -0
- package/skills/luckiest-offers/references/examples.md +215 -0
- package/skills/luckiest-offers/references/guarantee-design.md +172 -0
- package/skills/luckiest-offers/references/offer-anatomy.md +203 -0
- package/skills/luckiest-offers/references/offer-formats.md +240 -0
- package/skills/luckiest-offers/references/scarcity-urgency.md +175 -0
- package/skills/luckiest-offers/references/value-equation.md +134 -0
- package/skills/luckiest-onboarding/ATTRIBUTION.md +43 -0
- package/skills/luckiest-onboarding/CHANGELOG.md +31 -0
- package/skills/luckiest-onboarding/SKILL.md +243 -0
- package/skills/luckiest-onboarding/references/experiments.md +258 -0
- package/skills/luckiest-paywalls/ATTRIBUTION.md +39 -0
- package/skills/luckiest-paywalls/CHANGELOG.md +25 -0
- package/skills/luckiest-paywalls/SKILL.md +256 -0
- package/skills/luckiest-paywalls/evals/evals.json +93 -0
- package/skills/luckiest-paywalls/references/experiments.md +164 -0
- package/skills/luckiest-popups/ATTRIBUTION.md +35 -0
- package/skills/luckiest-popups/CHANGELOG.md +27 -0
- package/skills/luckiest-popups/SKILL.md +477 -0
- package/skills/luckiest-popups/evals/evals.json +107 -0
- package/skills/luckiest-pricing/ATTRIBUTION.md +35 -0
- package/skills/luckiest-pricing/CHANGELOG.md +31 -0
- package/skills/luckiest-pricing/SKILL.md +283 -0
- package/skills/luckiest-pricing/evals/evals.json +90 -0
- package/skills/luckiest-pricing/references/research-methods.md +152 -0
- package/skills/luckiest-pricing/references/tier-structure.md +232 -0
- package/skills/luckiest-product-marketing/ATTRIBUTION.md +38 -0
- package/skills/luckiest-product-marketing/CHANGELOG.md +23 -0
- package/skills/luckiest-product-marketing/SKILL.md +256 -0
- package/skills/luckiest-product-marketing/evals/evals.json +85 -0
- package/skills/luckiest-programmatic-seo/ATTRIBUTION.md +35 -0
- package/skills/luckiest-programmatic-seo/CHANGELOG.md +24 -0
- package/skills/luckiest-programmatic-seo/SKILL.md +286 -0
- package/skills/luckiest-programmatic-seo/evals/evals.json +95 -0
- package/skills/luckiest-programmatic-seo/references/playbooks.md +308 -0
- package/skills/luckiest-prompt-rewrite/ATTRIBUTION.md +39 -0
- package/skills/luckiest-prompt-rewrite/CHANGELOG.md +70 -0
- package/skills/luckiest-prompt-rewrite/LICENSE +21 -0
- package/skills/luckiest-prompt-rewrite/SKILL.md +494 -0
- package/skills/luckiest-prompt-rewrite/references/patterns.md +82 -0
- package/skills/luckiest-prompt-rewrite/references/templates.md +454 -0
- package/skills/luckiest-prospecting/ATTRIBUTION.md +29 -0
- package/skills/luckiest-prospecting/CHANGELOG.md +24 -0
- package/skills/luckiest-prospecting/SKILL.md +275 -0
- package/skills/luckiest-prospecting/evals/evals.json +107 -0
- package/skills/luckiest-prospecting/references/b2b-prospecting.md +106 -0
- package/skills/luckiest-prospecting/references/compliance.md +123 -0
- package/skills/luckiest-prospecting/references/data-sources.md +287 -0
- package/skills/luckiest-prospecting/references/local-prospecting.md +165 -0
- package/skills/luckiest-prospecting/references/saas-prospecting.md +123 -0
- package/skills/luckiest-public-relations/ATTRIBUTION.md +34 -0
- package/skills/luckiest-public-relations/CHANGELOG.md +23 -0
- package/skills/luckiest-public-relations/SKILL.md +154 -0
- package/skills/luckiest-public-relations/references/journalist-pitching.md +368 -0
- package/skills/luckiest-public-relations/references/media-outlets.md +207 -0
- package/skills/luckiest-public-relations/references/newsjacking.md +218 -0
- package/skills/luckiest-public-relations/references/press-platforms.md +188 -0
- package/skills/luckiest-referrals/ATTRIBUTION.md +41 -0
- package/skills/luckiest-referrals/CHANGELOG.md +23 -0
- package/skills/luckiest-referrals/SKILL.md +292 -0
- package/skills/luckiest-referrals/evals/evals.json +90 -0
- package/skills/luckiest-referrals/references/affiliate-programs.md +167 -0
- package/skills/luckiest-referrals/references/program-examples.md +143 -0
- package/skills/luckiest-revops/ATTRIBUTION.md +40 -0
- package/skills/luckiest-revops/CHANGELOG.md +24 -0
- package/skills/luckiest-revops/SKILL.md +362 -0
- package/skills/luckiest-revops/evals/evals.json +91 -0
- package/skills/luckiest-revops/references/automation-playbooks.md +290 -0
- package/skills/luckiest-revops/references/lifecycle-definitions.md +278 -0
- package/skills/luckiest-revops/references/routing-rules.md +203 -0
- package/skills/luckiest-revops/references/scoring-models.md +247 -0
- package/skills/luckiest-sales-enablement/ATTRIBUTION.md +39 -0
- package/skills/luckiest-sales-enablement/CHANGELOG.md +23 -0
- package/skills/luckiest-sales-enablement/SKILL.md +379 -0
- package/skills/luckiest-sales-enablement/evals/evals.json +91 -0
- package/skills/luckiest-sales-enablement/references/deck-frameworks.md +263 -0
- package/skills/luckiest-sales-enablement/references/demo-scripts.md +355 -0
- package/skills/luckiest-sales-enablement/references/objection-library.md +270 -0
- package/skills/luckiest-sales-enablement/references/one-pager-templates.md +208 -0
- package/skills/luckiest-schema/ATTRIBUTION.md +39 -0
- package/skills/luckiest-schema/CHANGELOG.md +25 -0
- package/skills/luckiest-schema/SKILL.md +209 -0
- package/skills/luckiest-schema/evals/evals.json +88 -0
- package/skills/luckiest-schema/references/schema-examples.md +398 -0
- package/skills/luckiest-seo-audit/ATTRIBUTION.md +39 -0
- package/skills/luckiest-seo-audit/CHANGELOG.md +23 -0
- package/skills/luckiest-seo-audit/SKILL.md +529 -0
- package/skills/luckiest-seo-audit/evals/evals.json +136 -0
- package/skills/luckiest-seo-audit/references/ai-writing-detection.md +200 -0
- package/skills/luckiest-seo-audit/references/international-seo.md +230 -0
- package/skills/luckiest-session-handoff/ATTRIBUTION.md +14 -0
- package/skills/luckiest-session-handoff/CHANGELOG.md +30 -0
- package/skills/luckiest-session-handoff/SKILL.md +96 -0
- package/skills/luckiest-signup/ATTRIBUTION.md +48 -0
- package/skills/luckiest-signup/CHANGELOG.md +24 -0
- package/skills/luckiest-signup/SKILL.md +382 -0
- package/skills/luckiest-signup/evals/evals.json +88 -0
- package/skills/luckiest-site-architecture/ATTRIBUTION.md +38 -0
- package/skills/luckiest-site-architecture/CHANGELOG.md +23 -0
- package/skills/luckiest-site-architecture/SKILL.md +383 -0
- package/skills/luckiest-site-architecture/evals/evals.json +88 -0
- package/skills/luckiest-site-architecture/references/mermaid-templates.md +216 -0
- package/skills/luckiest-site-architecture/references/navigation-patterns.md +305 -0
- package/skills/luckiest-site-architecture/references/site-type-templates.md +293 -0
- package/skills/luckiest-sms/ATTRIBUTION.md +27 -0
- package/skills/luckiest-sms/CHANGELOG.md +24 -0
- package/skills/luckiest-sms/SKILL.md +400 -0
- package/skills/luckiest-sms/evals/evals.json +100 -0
- package/skills/luckiest-sms/references/compliance.md +202 -0
- package/skills/luckiest-sms/references/platforms.md +318 -0
- package/skills/luckiest-sms/references/sequence-templates.md +282 -0
- package/skills/luckiest-social/ATTRIBUTION.md +27 -0
- package/skills/luckiest-social/CHANGELOG.md +24 -0
- package/skills/luckiest-social/SKILL.md +430 -0
- package/skills/luckiest-social/evals/evals.json +92 -0
- package/skills/luckiest-social/references/listening-sources-template.md +123 -0
- package/skills/luckiest-social/references/listening.md +283 -0
- package/skills/luckiest-social/references/platform-limits.md +110 -0
- package/skills/luckiest-social/references/platforms.md +170 -0
- package/skills/luckiest-social/references/post-templates.md +177 -0
- package/skills/luckiest-social/references/reverse-engineering.md +195 -0
- package/skills/luckiest-social/references/short-form-video.md +237 -0
- package/skills/luckiest-thinker/ATTRIBUTION.md +16 -0
- package/skills/luckiest-thinker/CHANGELOG.md +20 -0
- package/skills/luckiest-thinker/LICENSE +21 -0
- package/skills/luckiest-thinker/SKILL.md +92 -0
- package/skills/luckiest-thinker/references/distribution-calibration.md +106 -0
- package/skills/luckiest-thinker/references/research-foundations.md +214 -0
- package/skills/luckiest-thinker/references/thinking-patterns.md +138 -0
- package/skills/luckiest-trends/.skillignore +10 -0
- package/skills/luckiest-trends/ATTRIBUTION.md +43 -0
- package/skills/luckiest-trends/CHANGELOG.md +86 -0
- package/skills/luckiest-trends/LICENSE +21 -0
- package/skills/luckiest-trends/SKILL.md +194 -0
- package/skills/luckiest-trends/agents/openai.yaml +8 -0
- package/skills/luckiest-trends/references/engine-contract.md +2041 -0
- package/skills/luckiest-trends/references/save-html-brief.md +206 -0
- package/skills/luckiest-trends/scripts/briefing.py +272 -0
- package/skills/luckiest-trends/scripts/build-skill.sh +39 -0
- package/skills/luckiest-trends/scripts/compare.sh +61 -0
- package/skills/luckiest-trends/scripts/evaluate_search_quality.py +573 -0
- package/skills/luckiest-trends/scripts/last30days.py +1467 -0
- package/skills/luckiest-trends/scripts/lib/__init__.py +1 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/arxiv.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/bird_x.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/bluesky.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/categories.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/cjk.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/cluster.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/dates.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/dedupe.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/digg.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/entity_extract.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/env.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/fusion.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/github.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/grounding.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/hackernews.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/hiring_signals.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/html_render.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/http.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/instagram.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/jobs.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/linkedin.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/log.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/normalize.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/permission_preflight.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/perplexity.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/pinterest.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/pipeline.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/planner.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/polymarket.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/preflight.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/providers.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/quality_nudge.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/query.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_arctic.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_enrich.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_keyless.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_listing.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_public.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_rss.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/reddit_shreddit.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/relevance.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/render.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/rerank.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/resolve.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/schema.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/signals.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/skill_meta.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/snippet.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/subproc.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/techmeme.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/threads.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/tiktok.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/trustpilot.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/truthsocial.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/ui.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/web_search_keyless.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/xai_x.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/xiaohongshu_api.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/xquik.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/xurl_x.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/__pycache__/youtube_yt.cpython-313.pyc +0 -0
- package/skills/luckiest-trends/scripts/lib/arxiv.py +290 -0
- package/skills/luckiest-trends/scripts/lib/bird_x.py +639 -0
- package/skills/luckiest-trends/scripts/lib/bluesky.py +323 -0
- package/skills/luckiest-trends/scripts/lib/categories.py +283 -0
- package/skills/luckiest-trends/scripts/lib/chrome_cookies.py +414 -0
- package/skills/luckiest-trends/scripts/lib/cjk.py +107 -0
- package/skills/luckiest-trends/scripts/lib/cluster.py +271 -0
- package/skills/luckiest-trends/scripts/lib/competitors.py +198 -0
- package/skills/luckiest-trends/scripts/lib/cookie_extract.py +508 -0
- package/skills/luckiest-trends/scripts/lib/dates.py +169 -0
- package/skills/luckiest-trends/scripts/lib/dedupe.py +145 -0
- package/skills/luckiest-trends/scripts/lib/digg.py +440 -0
- package/skills/luckiest-trends/scripts/lib/entity_extract.py +127 -0
- package/skills/luckiest-trends/scripts/lib/env.py +1073 -0
- package/skills/luckiest-trends/scripts/lib/fanout.py +84 -0
- package/skills/luckiest-trends/scripts/lib/fusion.py +207 -0
- package/skills/luckiest-trends/scripts/lib/github.py +996 -0
- package/skills/luckiest-trends/scripts/lib/grounding.py +331 -0
- package/skills/luckiest-trends/scripts/lib/hackernews.py +357 -0
- package/skills/luckiest-trends/scripts/lib/health.py +92 -0
- package/skills/luckiest-trends/scripts/lib/hiring_signals.py +326 -0
- package/skills/luckiest-trends/scripts/lib/html_publish.py +73 -0
- package/skills/luckiest-trends/scripts/lib/html_render.py +738 -0
- package/skills/luckiest-trends/scripts/lib/http.py +372 -0
- package/skills/luckiest-trends/scripts/lib/instagram.py +521 -0
- package/skills/luckiest-trends/scripts/lib/jobs.py +708 -0
- package/skills/luckiest-trends/scripts/lib/linkedin.py +363 -0
- package/skills/luckiest-trends/scripts/lib/log.py +35 -0
- package/skills/luckiest-trends/scripts/lib/normalize.py +759 -0
- package/skills/luckiest-trends/scripts/lib/permission_preflight.py +242 -0
- package/skills/luckiest-trends/scripts/lib/perplexity.py +745 -0
- package/skills/luckiest-trends/scripts/lib/pinterest.py +154 -0
- package/skills/luckiest-trends/scripts/lib/pipeline.py +1720 -0
- package/skills/luckiest-trends/scripts/lib/planner.py +763 -0
- package/skills/luckiest-trends/scripts/lib/polymarket.py +794 -0
- package/skills/luckiest-trends/scripts/lib/preflight.py +119 -0
- package/skills/luckiest-trends/scripts/lib/providers.py +380 -0
- package/skills/luckiest-trends/scripts/lib/quality_nudge.py +383 -0
- package/skills/luckiest-trends/scripts/lib/query.py +202 -0
- package/skills/luckiest-trends/scripts/lib/reddit.py +731 -0
- package/skills/luckiest-trends/scripts/lib/reddit_arctic.py +92 -0
- package/skills/luckiest-trends/scripts/lib/reddit_enrich.py +322 -0
- package/skills/luckiest-trends/scripts/lib/reddit_keyless.py +329 -0
- package/skills/luckiest-trends/scripts/lib/reddit_listing.py +188 -0
- package/skills/luckiest-trends/scripts/lib/reddit_public.py +275 -0
- package/skills/luckiest-trends/scripts/lib/reddit_rss.py +224 -0
- package/skills/luckiest-trends/scripts/lib/reddit_shreddit.py +184 -0
- package/skills/luckiest-trends/scripts/lib/relevance.py +200 -0
- package/skills/luckiest-trends/scripts/lib/render.py +2130 -0
- package/skills/luckiest-trends/scripts/lib/rerank.py +708 -0
- package/skills/luckiest-trends/scripts/lib/resolve.py +345 -0
- package/skills/luckiest-trends/scripts/lib/safari_cookies.py +193 -0
- package/skills/luckiest-trends/scripts/lib/schema.py +319 -0
- package/skills/luckiest-trends/scripts/lib/setup_wizard.py +843 -0
- package/skills/luckiest-trends/scripts/lib/signals.py +334 -0
- package/skills/luckiest-trends/scripts/lib/skill_meta.py +33 -0
- package/skills/luckiest-trends/scripts/lib/snippet.py +51 -0
- package/skills/luckiest-trends/scripts/lib/subproc.py +116 -0
- package/skills/luckiest-trends/scripts/lib/techmeme.py +239 -0
- package/skills/luckiest-trends/scripts/lib/threads.py +207 -0
- package/skills/luckiest-trends/scripts/lib/tiktok.py +596 -0
- package/skills/luckiest-trends/scripts/lib/transcribe.py +235 -0
- package/skills/luckiest-trends/scripts/lib/trustpilot.py +277 -0
- package/skills/luckiest-trends/scripts/lib/truthsocial.py +162 -0
- package/skills/luckiest-trends/scripts/lib/ui.py +626 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/LICENSE +21 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/bird-search.mjs +147 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/cookies.js +220 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/features.json +17 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/paginate-cursor.js +37 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/query-ids.json +20 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/runtime-features.js +151 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/runtime-query-ids.js +264 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-base.js +129 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-constants.js +50 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-features.js +347 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-search.js +157 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-types.js +2 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/lib/twitter-client-utils.js +511 -0
- package/skills/luckiest-trends/scripts/lib/vendor/bird-search/package.json +13 -0
- package/skills/luckiest-trends/scripts/lib/web_fetch_keyless.py +105 -0
- package/skills/luckiest-trends/scripts/lib/web_search_keyless.py +158 -0
- package/skills/luckiest-trends/scripts/lib/xai_x.py +236 -0
- package/skills/luckiest-trends/scripts/lib/xiaohongshu_api.py +162 -0
- package/skills/luckiest-trends/scripts/lib/xquik.py +404 -0
- package/skills/luckiest-trends/scripts/lib/xurl_x.py +171 -0
- package/skills/luckiest-trends/scripts/lib/youtube_yt.py +1365 -0
- package/skills/luckiest-trends/scripts/setup-keychain.sh +123 -0
- package/skills/luckiest-trends/scripts/setup-pass.sh +132 -0
- package/skills/luckiest-trends/scripts/store.py +1020 -0
- package/skills/luckiest-trends/scripts/test-v1-vs-v2.sh +220 -0
- package/skills/luckiest-trends/scripts/test_device_auth.py +128 -0
- package/skills/luckiest-trends/scripts/verify_v3.py +195 -0
- package/skills/luckiest-trends/scripts/watchlist.py +318 -0
- package/skills/luckiest-video/ATTRIBUTION.md +27 -0
- package/skills/luckiest-video/CHANGELOG.md +23 -0
- package/skills/luckiest-video/SKILL.md +371 -0
- package/skills/luckiest-video/evals/evals.json +88 -0
- package/skills/luckiest-video/references/ai-video-prompting.md +175 -0
- package/skills/remix/SKILL.md +32 -0
- package/commands/luckiest/go.md +0 -45
- package/commands/luckiest/plan.md +0 -55
- /package/commands/{luckiest/charms.md → charms.md} +0 -0
- /package/commands/{luckiest/helpers.md → helpers.md} +0 -0
- /package/commands/{luckiest/home.md → home.md} +0 -0
- /package/commands/{luckiest/leaderboard.md → leaderboard.md} +0 -0
- /package/commands/{luckiest/merge.md → merge.md} +0 -0
- /package/commands/{luckiest/overlaps.md → overlaps.md} +0 -0
- /package/commands/{luckiest/skills.md → skills.md} +0 -0
- /package/commands/{luckiest/start.md → start.md} +0 -0
- /package/commands/{luckiest/status.md → status.md} +0 -0
- /package/commands/{luckiest/updates.md → updates.md} +0 -0
- /package/commands/{luckiest/vouch.md → vouch.md} +0 -0
- /package/commands/{luckiest/wishes.md → wishes.md} +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Auto-resolve subreddits, X handles, and current events context for a topic.
|
|
2
|
+
|
|
3
|
+
Uses web search (Brave/Exa/Serper) to discover relevant communities and context
|
|
4
|
+
before the planner runs. This is the engine-side equivalent of SKILL.md Steps
|
|
5
|
+
0.55/0.75 which use Claude Code's WebSearch tool.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from . import categories, dates, grounding, log
|
|
16
|
+
|
|
17
|
+
MAX_SUBS = 10
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _log(msg: str) -> None:
|
|
21
|
+
log.source_log("Resolve", msg, tty_only=False)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _merge_category_peers(topic: str, subreddits: list[str]) -> tuple[list[str], Optional[str]]:
|
|
25
|
+
"""Extend the WebSearch-extracted subreddit list with category peers.
|
|
26
|
+
|
|
27
|
+
Classifies the topic, fetches the category's peer subs, dedupes
|
|
28
|
+
case-insensitively against the existing list, and appends missing
|
|
29
|
+
peers in priority order. Caps the final list at MAX_SUBS, preserving
|
|
30
|
+
every WebSearch-returned sub (they are the freshest signal) and
|
|
31
|
+
trimming from the peer-additions end.
|
|
32
|
+
|
|
33
|
+
Returns a tuple of (merged_subs, matched_category_id_or_None).
|
|
34
|
+
Emits a [Resolve] Matched category log line only when peers were
|
|
35
|
+
actually added (not when every peer was already in the WebSearch set).
|
|
36
|
+
|
|
37
|
+
Classification failures degrade to "no match" — the unwidened list
|
|
38
|
+
is returned and a warning is logged.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
category = categories.detect_category(topic)
|
|
42
|
+
except Exception as exc:
|
|
43
|
+
_log(f"Category classification failed: {exc}")
|
|
44
|
+
return list(subreddits)[:MAX_SUBS], None
|
|
45
|
+
|
|
46
|
+
if category is None:
|
|
47
|
+
return list(subreddits)[:MAX_SUBS], None
|
|
48
|
+
|
|
49
|
+
peers = categories.peer_subs_for(category)
|
|
50
|
+
if not peers:
|
|
51
|
+
return list(subreddits)[:MAX_SUBS], category
|
|
52
|
+
|
|
53
|
+
existing_lower = {s.lower() for s in subreddits}
|
|
54
|
+
merged = list(subreddits)
|
|
55
|
+
added: list[str] = []
|
|
56
|
+
for peer in peers:
|
|
57
|
+
if len(merged) >= MAX_SUBS:
|
|
58
|
+
break
|
|
59
|
+
if peer.lower() in existing_lower:
|
|
60
|
+
continue
|
|
61
|
+
merged.append(peer)
|
|
62
|
+
existing_lower.add(peer.lower())
|
|
63
|
+
added.append(peer)
|
|
64
|
+
|
|
65
|
+
if added:
|
|
66
|
+
_log(f"Matched category={category}, adding peers: {', '.join(added)}")
|
|
67
|
+
|
|
68
|
+
return merged, category
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _has_backend(config: dict) -> bool:
|
|
72
|
+
"""Check if any web search backend is available."""
|
|
73
|
+
return bool(
|
|
74
|
+
config.get("BRAVE_API_KEY")
|
|
75
|
+
or config.get("EXA_API_KEY")
|
|
76
|
+
or config.get("SERPER_API_KEY")
|
|
77
|
+
or config.get("PARALLEL_API_KEY")
|
|
78
|
+
or config.get("OPENROUTER_API_KEY")
|
|
79
|
+
or config.get("PERPLEXITY_API_KEY")
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _extract_subreddits(items: list[dict]) -> list[str]:
|
|
84
|
+
"""Parse subreddit names from search result titles and snippets."""
|
|
85
|
+
pattern = re.compile(r"r/([A-Za-z0-9_]{2,21})")
|
|
86
|
+
seen: set[str] = set()
|
|
87
|
+
results: list[str] = []
|
|
88
|
+
for item in items:
|
|
89
|
+
text = f"{item.get('title', '')} {item.get('snippet', '')} {item.get('url', '')}"
|
|
90
|
+
for match in pattern.findall(text):
|
|
91
|
+
lower = match.lower()
|
|
92
|
+
if lower not in seen:
|
|
93
|
+
seen.add(lower)
|
|
94
|
+
results.append(match)
|
|
95
|
+
return results
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _extract_x_handle(items: list[dict]) -> str:
|
|
99
|
+
"""Extract the most likely X/Twitter handle from search results."""
|
|
100
|
+
pattern = re.compile(r"@([A-Za-z0-9_]{1,15})")
|
|
101
|
+
url_pattern = re.compile(r"(?:twitter\.com|x\.com)/([A-Za-z0-9_]{1,15})(?:/|$|\?)")
|
|
102
|
+
counts: dict[str, int] = {}
|
|
103
|
+
for item in items:
|
|
104
|
+
text = f"{item.get('title', '')} {item.get('snippet', '')}"
|
|
105
|
+
url = item.get("url", "")
|
|
106
|
+
for match in pattern.findall(text):
|
|
107
|
+
lower = match.lower()
|
|
108
|
+
counts[lower] = counts.get(lower, 0) + 1
|
|
109
|
+
for match in url_pattern.findall(url):
|
|
110
|
+
lower = match.lower()
|
|
111
|
+
# URL matches are stronger signals
|
|
112
|
+
counts[lower] = counts.get(lower, 0) + 3
|
|
113
|
+
# Filter out generic handles
|
|
114
|
+
skip = {"twitter", "x", "search", "hashtag", "intent", "share", "i", "home", "explore", "settings"}
|
|
115
|
+
counts = {k: v for k, v in counts.items() if k not in skip}
|
|
116
|
+
if not counts:
|
|
117
|
+
return ""
|
|
118
|
+
return max(counts, key=counts.get)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _extract_github_user(items: list[dict]) -> str:
|
|
122
|
+
"""Extract GitHub username from search results."""
|
|
123
|
+
url_pattern = re.compile(r"github\.com/([A-Za-z0-9_-]{1,39})(?:/|$|\?)")
|
|
124
|
+
counts: dict[str, int] = {}
|
|
125
|
+
for item in items:
|
|
126
|
+
url = item.get("url", "")
|
|
127
|
+
text = f"{item.get('title', '')} {item.get('snippet', '')}"
|
|
128
|
+
for match in url_pattern.findall(url):
|
|
129
|
+
lower = match.lower()
|
|
130
|
+
counts[lower] = counts.get(lower, 0) + 3
|
|
131
|
+
for match in url_pattern.findall(text):
|
|
132
|
+
lower = match.lower()
|
|
133
|
+
counts[lower] = counts.get(lower, 0) + 1
|
|
134
|
+
# Filter out org/repo-like names and generic pages
|
|
135
|
+
skip = {"topics", "explore", "settings", "orgs", "search", "features", "about", "pricing", "enterprise"}
|
|
136
|
+
counts = {k: v for k, v in counts.items() if k not in skip}
|
|
137
|
+
if not counts:
|
|
138
|
+
return ""
|
|
139
|
+
return max(counts, key=counts.get)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _extract_github_repos(items: list[dict]) -> list[str]:
|
|
143
|
+
"""Extract owner/repo strings from search results."""
|
|
144
|
+
repo_pattern = re.compile(r"github\.com/([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)")
|
|
145
|
+
skip_owners = {"topics", "explore", "settings", "orgs", "search", "features", "about", "pricing", "enterprise"}
|
|
146
|
+
seen: set[str] = set()
|
|
147
|
+
repos: list[str] = []
|
|
148
|
+
for item in items:
|
|
149
|
+
url = item.get("url", "")
|
|
150
|
+
text = f"{item.get('title', '')} {item.get('snippet', '')}"
|
|
151
|
+
for source in [url, text]:
|
|
152
|
+
for match in repo_pattern.findall(source):
|
|
153
|
+
owner = match.split("/")[0].lower()
|
|
154
|
+
if owner in skip_owners:
|
|
155
|
+
continue
|
|
156
|
+
lower = match.lower()
|
|
157
|
+
if lower not in seen:
|
|
158
|
+
seen.add(lower)
|
|
159
|
+
repos.append(match)
|
|
160
|
+
return repos[:5] # cap at 5 repos
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_INTEGRATION_SUFFIX_KEYWORDS: dict[str, set[str]] = {
|
|
164
|
+
"-action": {"action", "actions", "workflow", "workflows"},
|
|
165
|
+
"-sdk": {"sdk", "client", "library"},
|
|
166
|
+
"-plugin": {"plugin", "plugins", "extension", "extensions"},
|
|
167
|
+
"-plugins": {"plugin", "plugins", "extension", "extensions"},
|
|
168
|
+
"-docs": {"docs", "documentation"},
|
|
169
|
+
"-examples": {"example", "examples", "sample", "samples"},
|
|
170
|
+
"-template": {"template", "templates", "starter", "boilerplate"},
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _topic_tokens(topic: str) -> set[str]:
|
|
175
|
+
return set(re.findall(r"[a-z0-9]+", (topic or "").lower()))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _topic_entity_slugs(topic: str) -> list[str]:
|
|
179
|
+
entities = re.split(r"\b(?:vs|versus)\b", (topic or "").lower())
|
|
180
|
+
slugs: list[str] = []
|
|
181
|
+
for entity in entities:
|
|
182
|
+
tokens = re.findall(r"[a-z0-9]+", entity)
|
|
183
|
+
if tokens:
|
|
184
|
+
slugs.append("-".join(tokens))
|
|
185
|
+
return slugs
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _repo_slug(repo: str) -> str:
|
|
189
|
+
parts = repo.split("/", 1)
|
|
190
|
+
if len(parts) != 2:
|
|
191
|
+
return ""
|
|
192
|
+
return parts[1].lower()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _canonicalize_integration_repo(topic: str, repo: str) -> str:
|
|
196
|
+
"""Map integration repos back to canonical product repos when intent allows.
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
anthropics/claude-code-action -> anthropics/claude-code
|
|
200
|
+
unless topic explicitly asks for "action"/"workflow".
|
|
201
|
+
"""
|
|
202
|
+
parts = repo.split("/", 1)
|
|
203
|
+
if len(parts) != 2:
|
|
204
|
+
return repo
|
|
205
|
+
owner, name = parts[0], parts[1]
|
|
206
|
+
lower_name = name.lower()
|
|
207
|
+
topic_words = _topic_tokens(topic)
|
|
208
|
+
for suffix, intent_words in _INTEGRATION_SUFFIX_KEYWORDS.items():
|
|
209
|
+
if not lower_name.endswith(suffix):
|
|
210
|
+
continue
|
|
211
|
+
if topic_words.intersection(intent_words):
|
|
212
|
+
return repo
|
|
213
|
+
base = name[: -len(suffix)]
|
|
214
|
+
if base:
|
|
215
|
+
return f"{owner}/{base}"
|
|
216
|
+
return repo
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def canonicalize_github_repos(topic: str, repos: list[str], *, cap: int | None = 5) -> list[str]:
|
|
220
|
+
"""Normalize/priority-sort GitHub repos for the current topic.
|
|
221
|
+
|
|
222
|
+
- Rewrites common integration suffixes to canonical product repos when
|
|
223
|
+
topic intent does not mention those integrations.
|
|
224
|
+
- Promotes exact topic slug matches (e.g., `claude-code`) over partials.
|
|
225
|
+
"""
|
|
226
|
+
canonicalized: list[str] = []
|
|
227
|
+
seen: set[str] = set()
|
|
228
|
+
for repo in repos:
|
|
229
|
+
candidate = _canonicalize_integration_repo(topic, repo.strip())
|
|
230
|
+
if "/" not in candidate:
|
|
231
|
+
continue
|
|
232
|
+
key = candidate.lower()
|
|
233
|
+
if key in seen:
|
|
234
|
+
continue
|
|
235
|
+
seen.add(key)
|
|
236
|
+
canonicalized.append(candidate)
|
|
237
|
+
|
|
238
|
+
topic_slugs = set(_topic_entity_slugs(topic))
|
|
239
|
+
if topic_slugs:
|
|
240
|
+
exact = [r for r in canonicalized if _repo_slug(r) in topic_slugs]
|
|
241
|
+
prefixed = [r for r in canonicalized if any(_repo_slug(r).startswith(f"{slug}-") for slug in topic_slugs) and r not in exact]
|
|
242
|
+
rest = [r for r in canonicalized if r not in exact and r not in prefixed]
|
|
243
|
+
canonicalized = exact + prefixed + rest
|
|
244
|
+
|
|
245
|
+
if cap is not None:
|
|
246
|
+
return canonicalized[:cap]
|
|
247
|
+
return canonicalized
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _build_context_summary(items: list[dict]) -> str:
|
|
251
|
+
"""Build a 1-2 sentence current events summary from news search results."""
|
|
252
|
+
snippets: list[str] = []
|
|
253
|
+
for item in items[:3]:
|
|
254
|
+
snippet = item.get("snippet", "").strip()
|
|
255
|
+
if snippet:
|
|
256
|
+
snippets.append(snippet)
|
|
257
|
+
if not snippets:
|
|
258
|
+
return ""
|
|
259
|
+
# Take the first two meaningful snippets and truncate to keep it concise
|
|
260
|
+
combined = " ".join(snippets[:2])
|
|
261
|
+
if len(combined) > 300:
|
|
262
|
+
combined = combined[:297] + "..."
|
|
263
|
+
return combined
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def auto_resolve(topic: str, config: dict) -> dict:
|
|
267
|
+
"""Discover subreddits, X handles, and current events context for a topic.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
topic: The research topic.
|
|
271
|
+
config: Dict with API keys (BRAVE_API_KEY, EXA_API_KEY, SERPER_API_KEY).
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Dict with keys: subreddits, x_handle, github_user, github_repos,
|
|
275
|
+
context, category, searches_run. Returns empty result if no web
|
|
276
|
+
search backend is available.
|
|
277
|
+
"""
|
|
278
|
+
empty = {
|
|
279
|
+
"subreddits": [],
|
|
280
|
+
"x_handle": "",
|
|
281
|
+
"github_user": "",
|
|
282
|
+
"github_repos": [],
|
|
283
|
+
"context": "",
|
|
284
|
+
"category": None,
|
|
285
|
+
"searches_run": 0,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if not _has_backend(config):
|
|
289
|
+
_log("No web search backend available, skipping resolve")
|
|
290
|
+
return empty
|
|
291
|
+
|
|
292
|
+
from_date, to_date = dates.get_date_range(30)
|
|
293
|
+
date_range = (from_date, to_date)
|
|
294
|
+
now = datetime.now(timezone.utc)
|
|
295
|
+
current_month = now.strftime("%B")
|
|
296
|
+
current_year = now.strftime("%Y")
|
|
297
|
+
|
|
298
|
+
queries = {
|
|
299
|
+
"subreddit": f"{topic} subreddit reddit",
|
|
300
|
+
"news": f"{topic} news {current_month} {current_year}",
|
|
301
|
+
"x_handle": f"{topic} X twitter handle",
|
|
302
|
+
"github": f"{topic} github profile site:github.com",
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
results: dict[str, list[dict]] = {}
|
|
306
|
+
searches_run = 0
|
|
307
|
+
|
|
308
|
+
def _search(label: str, query: str) -> tuple[str, list[dict]]:
|
|
309
|
+
items, _artifact = grounding.web_search(query, date_range, config)
|
|
310
|
+
return label, items
|
|
311
|
+
|
|
312
|
+
with ThreadPoolExecutor(max_workers=3) as executor:
|
|
313
|
+
futures = {
|
|
314
|
+
executor.submit(_search, label, q): label
|
|
315
|
+
for label, q in queries.items()
|
|
316
|
+
}
|
|
317
|
+
for future in as_completed(futures):
|
|
318
|
+
label = futures[future]
|
|
319
|
+
try:
|
|
320
|
+
_label, items = future.result()
|
|
321
|
+
results[label] = items
|
|
322
|
+
searches_run += 1
|
|
323
|
+
except Exception as exc:
|
|
324
|
+
_log(f"Search failed for {label}: {exc}")
|
|
325
|
+
results[label] = []
|
|
326
|
+
|
|
327
|
+
subreddits = _extract_subreddits(results.get("subreddit", []))
|
|
328
|
+
x_handle = _extract_x_handle(results.get("x_handle", []))
|
|
329
|
+
github_user = _extract_github_user(results.get("github", []))
|
|
330
|
+
github_repos = canonicalize_github_repos(topic, _extract_github_repos(results.get("github", [])))
|
|
331
|
+
context = _build_context_summary(results.get("news", []))
|
|
332
|
+
|
|
333
|
+
subreddits, category = _merge_category_peers(topic, subreddits)
|
|
334
|
+
|
|
335
|
+
_log(f"Resolved {len(subreddits)} subreddits, x_handle={x_handle!r}, github_user={github_user!r}, github_repos={github_repos!r}, context_len={len(context)}, category={category!r}")
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"subreddits": subreddits,
|
|
339
|
+
"x_handle": x_handle,
|
|
340
|
+
"github_user": github_user,
|
|
341
|
+
"github_repos": github_repos,
|
|
342
|
+
"context": context,
|
|
343
|
+
"category": category,
|
|
344
|
+
"searches_run": searches_run,
|
|
345
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Safari binary cookie extractor for macOS.
|
|
3
|
+
|
|
4
|
+
Parses ~/Library/Cookies/Cookies.binarycookies (unencrypted binary format)
|
|
5
|
+
using only stdlib. Zero pip dependencies.
|
|
6
|
+
|
|
7
|
+
Reference: github.com/mdegrazia/Safari-Binary-Cookie-Parser
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import io
|
|
13
|
+
import struct
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Mac epoch: 2001-01-01 00:00:00 UTC (not used for filtering, but documented)
|
|
18
|
+
_MAC_EPOCH_OFFSET = 978307200 # seconds between Unix epoch and Mac epoch
|
|
19
|
+
|
|
20
|
+
_MAGIC = b"cook"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _read_null_terminated(data: bytes, offset: int) -> str:
|
|
24
|
+
"""Read a null-terminated string from data starting at offset."""
|
|
25
|
+
end = data.find(b"\x00", offset)
|
|
26
|
+
if end == -1:
|
|
27
|
+
end = len(data)
|
|
28
|
+
return data[offset:end].decode("utf-8", errors="replace")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _parse_cookie_record(data: bytes) -> dict | None:
|
|
32
|
+
"""Parse a single cookie record. Returns dict with url, name, value, path or None."""
|
|
33
|
+
if len(data) < 44:
|
|
34
|
+
return None
|
|
35
|
+
try:
|
|
36
|
+
(size,) = struct.unpack("<I", data[0:4])
|
|
37
|
+
# flags at offset 4 (4 bytes, little-endian) — not needed for extraction
|
|
38
|
+
(url_offset,) = struct.unpack("<I", data[16:20])
|
|
39
|
+
(name_offset,) = struct.unpack("<I", data[20:24])
|
|
40
|
+
(path_offset,) = struct.unpack("<I", data[24:28])
|
|
41
|
+
(value_offset,) = struct.unpack("<I", data[28:32])
|
|
42
|
+
# expiry at offset 40 (8-byte double, little-endian) — not needed for filtering
|
|
43
|
+
# creation at offset 48 (8-byte double, little-endian) — not needed
|
|
44
|
+
|
|
45
|
+
url = _read_null_terminated(data, url_offset)
|
|
46
|
+
name = _read_null_terminated(data, name_offset)
|
|
47
|
+
path = _read_null_terminated(data, path_offset)
|
|
48
|
+
value = _read_null_terminated(data, value_offset)
|
|
49
|
+
|
|
50
|
+
return {"url": url, "name": name, "value": value, "path": path}
|
|
51
|
+
except (struct.error, IndexError, UnicodeDecodeError):
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _parse_page(page_data: bytes) -> list[dict]:
|
|
56
|
+
"""Parse a single page of cookies. Returns list of cookie dicts."""
|
|
57
|
+
cookies = []
|
|
58
|
+
if len(page_data) < 8:
|
|
59
|
+
return cookies
|
|
60
|
+
|
|
61
|
+
# Page header: 4 bytes (always 00 00 01 00), then 4-byte LE cookie count
|
|
62
|
+
try:
|
|
63
|
+
(num_cookies,) = struct.unpack("<I", page_data[4:8])
|
|
64
|
+
except struct.error:
|
|
65
|
+
return cookies
|
|
66
|
+
|
|
67
|
+
# Sanity check
|
|
68
|
+
if num_cookies > 10000:
|
|
69
|
+
return cookies
|
|
70
|
+
|
|
71
|
+
# Cookie offsets: array of 4-byte LE uint32 starting at offset 8
|
|
72
|
+
offsets_end = 8 + num_cookies * 4
|
|
73
|
+
if offsets_end > len(page_data):
|
|
74
|
+
return cookies
|
|
75
|
+
|
|
76
|
+
for i in range(num_cookies):
|
|
77
|
+
off_start = 8 + i * 4
|
|
78
|
+
try:
|
|
79
|
+
(cookie_offset,) = struct.unpack("<I", page_data[off_start : off_start + 4])
|
|
80
|
+
except struct.error:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
if cookie_offset >= len(page_data):
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
cookie_data = page_data[cookie_offset:]
|
|
87
|
+
record = _parse_cookie_record(cookie_data)
|
|
88
|
+
if record:
|
|
89
|
+
cookies.append(record)
|
|
90
|
+
|
|
91
|
+
return cookies
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def extract_safari_cookies_macos(
|
|
95
|
+
domain: str, cookie_names: list[str]
|
|
96
|
+
) -> dict[str, str] | None:
|
|
97
|
+
"""
|
|
98
|
+
Extract cookies from Safari on macOS.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
domain: Domain to match (substring match, e.g. "x.com")
|
|
102
|
+
cookie_names: List of cookie names to extract (e.g. ["auth_token", "ct0"])
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Dict mapping cookie name to value for found cookies, or None on failure.
|
|
106
|
+
"""
|
|
107
|
+
if sys.platform != "darwin":
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
cookie_paths = [
|
|
111
|
+
Path.home()
|
|
112
|
+
/ "Library"
|
|
113
|
+
/ "Containers"
|
|
114
|
+
/ "com.apple.Safari"
|
|
115
|
+
/ "Data"
|
|
116
|
+
/ "Library"
|
|
117
|
+
/ "Cookies"
|
|
118
|
+
/ "Cookies.binarycookies",
|
|
119
|
+
Path.home() / "Library" / "Cookies" / "Cookies.binarycookies",
|
|
120
|
+
]
|
|
121
|
+
cookie_path = next((path for path in cookie_paths if path.exists()), cookie_paths[0])
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
raw = cookie_path.read_bytes()
|
|
125
|
+
except FileNotFoundError:
|
|
126
|
+
return None
|
|
127
|
+
except PermissionError:
|
|
128
|
+
print(
|
|
129
|
+
"[safari] Permission denied reading Cookies.binarycookies. "
|
|
130
|
+
"Enable Full Disk Access for Terminal in System Settings > "
|
|
131
|
+
"Privacy & Security > Full Disk Access.",
|
|
132
|
+
file=sys.stderr,
|
|
133
|
+
)
|
|
134
|
+
return None
|
|
135
|
+
except OSError:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
return _parse_binary_cookies(raw, domain, cookie_names)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _parse_binary_cookies(
|
|
142
|
+
raw: bytes, domain: str, cookie_names: list[str]
|
|
143
|
+
) -> dict[str, str] | None:
|
|
144
|
+
"""Parse raw binary cookie data. Separated for testability."""
|
|
145
|
+
if len(raw) < 8:
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
# Validate magic
|
|
149
|
+
if raw[:4] != _MAGIC:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
(num_pages,) = struct.unpack(">I", raw[4:8])
|
|
154
|
+
except struct.error:
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
if num_pages > 100000:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
# Read page sizes (big-endian uint32 array)
|
|
161
|
+
page_sizes_end = 8 + num_pages * 4
|
|
162
|
+
if page_sizes_end > len(raw):
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
page_sizes = []
|
|
166
|
+
for i in range(num_pages):
|
|
167
|
+
off = 8 + i * 4
|
|
168
|
+
try:
|
|
169
|
+
(ps,) = struct.unpack(">I", raw[off : off + 4])
|
|
170
|
+
page_sizes.append(ps)
|
|
171
|
+
except struct.error:
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
# Parse each page
|
|
175
|
+
names_set = set(cookie_names)
|
|
176
|
+
result: dict[str, str] = {}
|
|
177
|
+
offset = page_sizes_end
|
|
178
|
+
|
|
179
|
+
for ps in page_sizes:
|
|
180
|
+
if offset + ps > len(raw):
|
|
181
|
+
break
|
|
182
|
+
page_data = raw[offset : offset + ps]
|
|
183
|
+
cookies = _parse_page(page_data)
|
|
184
|
+
for c in cookies:
|
|
185
|
+
# Substring match on domain (handles leading dots like ".x.com")
|
|
186
|
+
if domain in c["url"] and c["name"] in names_set:
|
|
187
|
+
result[c["name"]] = c["value"]
|
|
188
|
+
offset += ps
|
|
189
|
+
|
|
190
|
+
if not result:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
return result
|