@veyralabs/skills 0.4.1 → 0.5.1

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.
@@ -0,0 +1,154 @@
1
+ # Traction Reference
2
+
3
+ Gabriel Weinberg & Justin Mares, "Traction" (2014).
4
+ Framework for finding customer acquisition channels that actually work.
5
+
6
+ ## Core insight
7
+
8
+ Most startups fail not because of product problems but traction problems. Building product and finding traction should happen in parallel, not sequentially.
9
+
10
+ Half your time should be spent on traction, not product — even pre-launch.
11
+
12
+ ## The 19 traction channels
13
+
14
+ 1. **Viral marketing** - product spreads through existing users (Dropbox, Slack)
15
+ 2. **PR** - traditional press coverage
16
+ 3. **Unconventional PR** - stunts, giveaways, memorable campaigns
17
+ 4. **Search engine marketing (SEM)** - paid search (Google Ads)
18
+ 5. **Social and display ads** - Meta, LinkedIn, programmatic
19
+ 6. **Offline ads** - billboards, radio, TV, print
20
+ 7. **Search engine optimization (SEO)** - organic search
21
+ 8. **Content marketing** - blog, YouTube, podcast as acquisition
22
+ 9. **Email marketing** - list building and outbound sequences
23
+ 10. **Engineering as marketing** - free tools, calculators, embeds that drive signups
24
+ 11. **Targeting blogs** - get covered by influential niche blogs
25
+ 12. **Business development** - partnerships with complementary products
26
+ 13. **Sales** - direct outbound sales, inside sales teams
27
+ 14. **Affiliate programs** - revenue share for referrals
28
+ 15. **Existing platforms** - App Store, Shopify App Store, Chrome extensions, marketplace distribution
29
+ 16. **Trade shows** - industry events, conferences
30
+ 17. **Offline events** - meetups, workshops, demos
31
+ 18. **Speaking engagements** - conferences, podcasts, webinars
32
+ 19. **Community building** - Slack groups, forums, ambassador programs
33
+
34
+ ## Bullseye Framework
35
+
36
+ Don't try all 19 channels. Use the Bullseye to find the one that works.
37
+
38
+ ### Step 1 — Brainstorm (outer ring)
39
+ For each of the 19 channels, answer:
40
+ - How could we use this channel?
41
+ - What would success look like?
42
+ - How could we test it cheaply?
43
+
44
+ Produces 19 rough hypotheses.
45
+
46
+ ### Step 2 — Rank (middle ring)
47
+ Score each channel on 3 criteria (1-3):
48
+ - Probability of working
49
+ - Volume potential (enough customers to matter)
50
+ - Cost per acquisition (time + money)
51
+
52
+ Pick the top 6 channels. These are your "promising" channels.
53
+
54
+ ### Step 3 — Test (inner ring — the bullseye)
55
+ Pick the 3 channels with highest combined score. Run cheap, fast tests on each simultaneously.
56
+
57
+ Each test:
58
+ - Fixed budget (~€200-500)
59
+ - Fixed time (2-4 weeks)
60
+ - One metric: cost per lead, or signups, or revenue
61
+
62
+ ### Step 4 — Focus
63
+ One test will outperform the others. Focus 100% on that channel. Optimize it until it no longer scales.
64
+
65
+ When the channel plateaus, go back to Step 2.
66
+
67
+ ## Channel selection by stage
68
+
69
+ Different channels dominate at different stages:
70
+
71
+ | Stage | Best channels |
72
+ |-------|--------------|
73
+ | Pre-PMF | Direct sales, outreach, niche blogs, communities |
74
+ | Early growth | Content, SEO, partnerships, email |
75
+ | Scaling | Paid acquisition (once LTV:CAC proven), viral, affiliates |
76
+ | Dominant | Brand, PR, events |
77
+
78
+ **Pre-PMF rule:** channels that require scale to work (SEO, paid ads, viral) are wrong for early stage. You don't have enough data to optimize them. Use channels where you control the targeting.
79
+
80
+ ## Channel-market fit
81
+
82
+ Some channels only work for specific markets:
83
+
84
+ | Channel | Works for |
85
+ |---------|-----------|
86
+ | Cold outbound | B2B with clear ICP, high LTV |
87
+ | Content/SEO | Markets that Google actively |
88
+ | Product-led viral | Consumer, collaboration tools |
89
+ | Community | Niche markets with strong identity |
90
+ | Paid social | B2C with wide audience |
91
+ | Trade shows | B2B with offline buying decisions |
92
+ | Partnerships | Products with complementary distribution |
93
+
94
+ Picking the wrong channel for your market wastes 6 months.
95
+
96
+ ## Cost benchmarks (rough, 2024)
97
+
98
+ | Channel | Metric | B2C benchmark | B2B benchmark |
99
+ |---------|--------|--------------|--------------|
100
+ | Facebook/Instagram ads | CPL | €3-8 | €15-30 |
101
+ | Google Search | CPL | €5-15 | €20-60 |
102
+ | LinkedIn ads | CPL | - | €40-100 |
103
+ | Cold email | CPL | - | €20-80 (time cost) |
104
+ | Content/SEO | CPL | €5-20 (long term) | €15-50 (long term) |
105
+ | Referral/viral | CPL | €1-5 | €5-20 |
106
+
107
+ LTV must be at least 3x CAC for paid channels to be viable.
108
+
109
+ ## Traction metrics by channel
110
+
111
+ **Viral / product-led:**
112
+ - K-factor = (invites per user) × (invite conversion rate). Target K > 0.5 for meaningful viral effect
113
+ - Time to viral loop: how fast does one user create another?
114
+
115
+ **Content / SEO:**
116
+ - Organic sessions per month
117
+ - Keyword rankings for high-intent terms
118
+ - Time to rank: SEO takes 3-6 months minimum to show results
119
+
120
+ **Email:**
121
+ - List growth rate per week
122
+ - Open rate: >30% is good for cold; >50% for warm
123
+ - Click-through: >5% is strong
124
+
125
+ **Paid acquisition:**
126
+ - CPL and CAC (cost per acquired customer)
127
+ - Payback period: how many months to recoup CAC?
128
+ - ROAS (return on ad spend)
129
+
130
+ **Sales:**
131
+ - Leads per week
132
+ - Conversion rate (leads to paying)
133
+ - Sales cycle length
134
+ - Average contract value
135
+
136
+ ## Applying to venture analysis
137
+
138
+ In Phase 3 (validation), recommend channels based on:
139
+
140
+ 1. Market type (B2B vs B2C)
141
+ 2. LTV estimate (high LTV = more channels viable)
142
+ 3. Available budget
143
+ 4. Target customer location (online? offline? specific communities?)
144
+
145
+ For zero-budget startups, the viable channels are:
146
+ - Direct outreach (cold email/DM) — time only
147
+ - Niche community posting (Reddit, specific Slack/Discord groups) — time only
148
+ - Engineering as marketing — build a free tool that attracts ICPs
149
+ - Content on a specific topic ICP searches for
150
+
151
+ For startups with €100-500/month budget:
152
+ - Fake door test (landing page + small paid traffic)
153
+ - LinkedIn outreach with Sales Navigator trial
154
+ - Sponsored newsletter in a niche (often €100-300 per send)
@@ -0,0 +1,172 @@
1
+ """
2
+ Level 2 auto-detection.
3
+ Silently detects available enhancements. Never asks the user for config.
4
+ """
5
+ import os
6
+ import subprocess
7
+ import time
8
+ import requests
9
+
10
+
11
+ def detect_level() -> dict:
12
+ """
13
+ Detect what's available. Returns a capability map.
14
+ Called once at session start — results cached for the session.
15
+ """
16
+ return {
17
+ "docker": _has_docker(),
18
+ "searxng": _has_searxng(),
19
+ "veyrascrape_mcp": _has_env("VEYRASCRAPE_API_KEY"),
20
+ "github_token": _has_env("GITHUB_TOKEN"),
21
+ "exa_key": _has_env("EXA_API_KEY"),
22
+ "tavily_key": _has_env("TAVILY_API_KEY"),
23
+ "groq_key": _has_env("GROQ_API_KEY"),
24
+ }
25
+
26
+
27
+ def _has_env(key: str) -> bool:
28
+ val = os.environ.get(key, "").strip()
29
+ return bool(val)
30
+
31
+
32
+ def _has_docker() -> bool:
33
+ try:
34
+ result = subprocess.run(
35
+ ["docker", "info"],
36
+ capture_output=True,
37
+ timeout=4,
38
+ )
39
+ return result.returncode == 0
40
+ except Exception:
41
+ return False
42
+
43
+
44
+ def _has_searxng(ports: list[int] = None) -> bool:
45
+ if ports is None:
46
+ ports = [8080, 8888, 4000]
47
+ for port in ports:
48
+ try:
49
+ r = requests.get(
50
+ f"http://localhost:{port}/search",
51
+ params={"q": "test", "format": "json"},
52
+ timeout=2,
53
+ )
54
+ if r.status_code == 200:
55
+ return True
56
+ except Exception:
57
+ pass
58
+ return False
59
+
60
+
61
+ def ensure_searxng() -> dict:
62
+ """
63
+ Launch SearXNG via Docker if available and not already running.
64
+ Returns status dict.
65
+ """
66
+ if _has_searxng():
67
+ return {"available": True, "url": "http://localhost:8080", "launched": False}
68
+
69
+ if not _has_docker():
70
+ return {"available": False, "reason": "docker_not_found"}
71
+
72
+ try:
73
+ subprocess.run(
74
+ [
75
+ "docker", "run", "-d",
76
+ "--name", "venture-searxng",
77
+ "-p", "8080:8080",
78
+ "searxng/searxng",
79
+ ],
80
+ capture_output=True,
81
+ timeout=45,
82
+ )
83
+ # give it time to start
84
+ for _ in range(6):
85
+ time.sleep(2)
86
+ if _has_searxng():
87
+ return {"available": True, "url": "http://localhost:8080", "launched": True}
88
+ return {"available": False, "reason": "searxng_start_timeout"}
89
+ except Exception as e:
90
+ return {"available": False, "reason": str(e)}
91
+
92
+
93
+ def search_searxng(query: str, url: str = "http://localhost:8080", limit: int = 10) -> list[dict]:
94
+ """Search via local SearXNG instance."""
95
+ try:
96
+ r = requests.get(
97
+ f"{url}/search",
98
+ params={"q": query, "format": "json", "pageno": 1},
99
+ timeout=10,
100
+ )
101
+ results = r.json().get("results", [])[:limit]
102
+ return [
103
+ {
104
+ "source": "searxng",
105
+ "title": res.get("title", ""),
106
+ "url": res.get("url", ""),
107
+ "text": res.get("content", ""),
108
+ "engine": res.get("engine", ""),
109
+ }
110
+ for res in results
111
+ ]
112
+ except Exception:
113
+ return []
114
+
115
+
116
+ def search_with_exa(query: str, api_key: str, limit: int = 10) -> list[dict]:
117
+ """Semantic search via Exa API (optional Level 3 enhancement)."""
118
+ try:
119
+ import exa_py
120
+ exa = exa_py.Exa(api_key=api_key)
121
+ results = exa.search(query, num_results=limit, use_autoprompt=True)
122
+ return [
123
+ {
124
+ "source": "exa",
125
+ "title": r.title or "",
126
+ "url": r.url,
127
+ "text": r.text or "",
128
+ }
129
+ for r in results.results
130
+ ]
131
+ except Exception:
132
+ return []
133
+
134
+
135
+ def best_search(query: str, env: dict, limit: int = 10) -> list[dict]:
136
+ """
137
+ Use the best available search method based on detected environment.
138
+ Priority: Exa > SearXNG > Tavily > ddgs
139
+ """
140
+ # Level 3: Exa
141
+ if env.get("exa_key"):
142
+ results = search_with_exa(query, os.environ["EXA_API_KEY"], limit)
143
+ if results:
144
+ return results
145
+
146
+ # Level 2: SearXNG
147
+ if env.get("searxng"):
148
+ results = search_searxng(query, limit=limit)
149
+ if results:
150
+ return results
151
+
152
+ # Level 2: Tavily
153
+ if env.get("tavily_key"):
154
+ try:
155
+ from tavily import TavilyClient
156
+ client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
157
+ response = client.search(query, max_results=limit)
158
+ return [
159
+ {
160
+ "source": "tavily",
161
+ "title": r.get("title", ""),
162
+ "url": r.get("url", ""),
163
+ "text": r.get("content", ""),
164
+ }
165
+ for r in response.get("results", [])
166
+ ]
167
+ except Exception:
168
+ pass
169
+
170
+ # Level 1: ddgs (always available)
171
+ from scripts.sources import search_web
172
+ return search_web(query, limit)
@@ -0,0 +1,228 @@
1
+ """
2
+ Validation experiment generator.
3
+ Based on Lean Startup + Mom Test methodology.
4
+ Outputs concrete, actionable experiments — not analysis.
5
+ """
6
+
7
+ EXPERIMENTS = {
8
+ "cold_outreach": {
9
+ "name": "Cold Outreach Test",
10
+ "type": "discovery",
11
+ "cost": "0€",
12
+ "duration": "5-7 days",
13
+ "effort": "medium",
14
+ "goal": "Validate problem exists and find early adopters",
15
+ "process": [
16
+ "Identify 50 potential customers on LinkedIn, Reddit, or niche communities",
17
+ "Send short message: 'I'm researching [problem area] — would you spare 15 min?'",
18
+ "Do NOT pitch. Ask about their current process and pain.",
19
+ "Apply Mom Test: ask about past behavior, not future intentions",
20
+ ],
21
+ "metric": "Response rate + calls booked",
22
+ "success_criteria": "≥10% response rate, ≥3 discovery calls completed",
23
+ "red_flags": [
24
+ "Everyone says 'great idea' but nobody wants a call",
25
+ "People say they'd pay but won't commit to a free beta",
26
+ "You have to explain the problem before they understand it",
27
+ ],
28
+ "mom_test_questions": [
29
+ "Tell me how you currently handle [problem]. Walk me through your last time.",
30
+ "What's the most frustrating part of that process?",
31
+ "Have you tried other solutions? What happened?",
32
+ "How much time/money does this cost you per month right now?",
33
+ "What would you do if this problem disappeared tomorrow?",
34
+ ],
35
+ "bad_questions_to_avoid": [
36
+ "Would you use a tool that does X?",
37
+ "Do you think this is a good idea?",
38
+ "Would you pay $X for this?",
39
+ "How much would you pay for this?",
40
+ ],
41
+ },
42
+
43
+ "fake_door": {
44
+ "name": "Fake Door Test",
45
+ "type": "demand_signal",
46
+ "cost": "0-30€",
47
+ "duration": "7-14 days",
48
+ "effort": "low",
49
+ "goal": "Measure real demand before building anything",
50
+ "process": [
51
+ "Build a landing page (Carrd.co free, 30 min)",
52
+ "Describe the value proposition clearly — no features, only outcome",
53
+ "Add a CTA button: 'Join Waitlist' or 'Get Early Access'",
54
+ "When clicked: show 'Thanks, we'll notify you' — collect email",
55
+ "Drive traffic: 2-3 Reddit posts in relevant communities + LinkedIn",
56
+ "Track: page visits, CTA clicks, emails collected",
57
+ ],
58
+ "metric": "CTA click-through rate (CTR)",
59
+ "success_criteria": "≥5% CTR from cold traffic, ≥50 emails in 14 days",
60
+ "tools": [
61
+ "Carrd.co — free landing pages",
62
+ "Tally.so — free forms with email collection",
63
+ "Google Analytics (free) — traffic tracking",
64
+ "Plausible.io — privacy-friendly alternative",
65
+ ],
66
+ "headline_formula": "[Outcome they want] without [current pain]",
67
+ "subheadline_formula": "[Product name] helps [target customer] [achieve outcome] by [key mechanism]",
68
+ "red_flags": [
69
+ "CTR below 2% consistently",
70
+ "High visits but nobody clicks CTA",
71
+ "Traffic only from your own network — zero cold traffic",
72
+ ],
73
+ },
74
+
75
+ "waitlist": {
76
+ "name": "Waitlist Campaign",
77
+ "type": "demand_signal",
78
+ "cost": "0€",
79
+ "duration": "14-30 days",
80
+ "effort": "medium",
81
+ "goal": "Build an audience before building the product",
82
+ "process": [
83
+ "Build landing with value proposition + email signup",
84
+ "Write 3-5 posts for relevant communities (Reddit, LinkedIn, Twitter)",
85
+ "Frame as 'I'm building X because Y — who's interested?'",
86
+ "Respond to every comment — this is customer research",
87
+ "Track: emails per day, most common questions, what resonates",
88
+ ],
89
+ "metric": "Email signups per week",
90
+ "success_criteria": "100 organic signups in 30 days without paid ads",
91
+ "tools": [
92
+ "Beehiiv (free for <2500 subs) — email list",
93
+ "Carrd.co — landing",
94
+ "Reddit (organic, zero cost)",
95
+ "LinkedIn (organic, zero cost)",
96
+ ],
97
+ "red_flags": [
98
+ "Only your friends/connections sign up",
99
+ "High unsubscribe rate on first email",
100
+ "Nobody shares it organically",
101
+ ],
102
+ },
103
+
104
+ "concierge_mvp": {
105
+ "name": "Concierge MVP",
106
+ "type": "value_validation",
107
+ "cost": "0€",
108
+ "duration": "2-4 weeks",
109
+ "effort": "high",
110
+ "goal": "Deliver the core value manually to 3-5 real users",
111
+ "process": [
112
+ "Find 3-5 people with the problem (from outreach or waitlist)",
113
+ "Offer to do the service manually for free",
114
+ "Do it by hand: spreadsheets, email, calls, manual research",
115
+ "Observe where they get value, where they get frustrated",
116
+ "At the end: 'Would you pay X/month for this if it was automated?'",
117
+ ],
118
+ "metric": "Willingness to pay (real commitment, not hypothetical)",
119
+ "success_criteria": "≥3/5 users say yes to paying target price, 1+ pre-orders",
120
+ "key_question": "If this service disappeared tomorrow, what would you do?",
121
+ "what_to_track": [
122
+ "Which parts of the manual process they value most",
123
+ "Which parts they don't care about",
124
+ "How often they'd use it",
125
+ "What would make them stop using it",
126
+ "Who else they'd recommend it to",
127
+ ],
128
+ "red_flags": [
129
+ "'I'd use it but can't commit right now'",
130
+ "They use it but never refer anyone",
131
+ "The manual process takes so long it's not viable to automate",
132
+ ],
133
+ },
134
+
135
+ "smoke_test_ads": {
136
+ "name": "Paid Smoke Test",
137
+ "type": "demand_signal",
138
+ "cost": "30-100€",
139
+ "duration": "7 days",
140
+ "effort": "medium",
141
+ "goal": "Measure demand with cold paid traffic — no bias from network",
142
+ "process": [
143
+ "Set up landing page with single CTA",
144
+ "Run €30-50 in Meta or Google Ads to target audience",
145
+ "B2C: Facebook/Instagram interest targeting",
146
+ "B2B: LinkedIn job title targeting (more expensive, ~€5-10/click)",
147
+ "Measure CPL (cost per lead) — this is your true demand signal",
148
+ ],
149
+ "metric": "Cost Per Lead (CPL)",
150
+ "success_criteria": "B2C CPL < 5€, B2B CPL < 40€",
151
+ "tools": [
152
+ "Meta Ads Manager (€30 minimum budget)",
153
+ "Google Ads (€10/day minimum)",
154
+ "LinkedIn Ads (most expensive — B2B only, €15-50/click)",
155
+ ],
156
+ "red_flags": [
157
+ "CPL 5x higher than benchmark — market doesn't respond to messaging",
158
+ "Clicks but no conversions — landing page or offer problem",
159
+ "Can't target audience precisely — unclear ICP",
160
+ ],
161
+ },
162
+ }
163
+
164
+
165
+ def generate_experiments(
166
+ idea: str,
167
+ target_customer: str,
168
+ market_type: str = "b2c",
169
+ competition_level: str = "medium",
170
+ budget: str = "zero",
171
+ ) -> list[dict]:
172
+ """
173
+ Generate 3 prioritized validation experiments.
174
+ Always starts with the cheapest, highest-signal experiments first.
175
+ """
176
+ selected = []
177
+
178
+ # Always: Cold outreach first (zero cost, real signal)
179
+ exp = EXPERIMENTS["cold_outreach"].copy()
180
+ exp["priority"] = 1
181
+ exp["customized_for"] = idea
182
+ exp["target"] = target_customer
183
+ selected.append(exp)
184
+
185
+ # Always: Fake door (low cost, measurable)
186
+ exp = EXPERIMENTS["fake_door"].copy()
187
+ exp["priority"] = 2
188
+ exp["customized_for"] = idea
189
+ selected.append(exp)
190
+
191
+ # B2B with budget: concierge MVP
192
+ # B2C with budget: waitlist
193
+ if market_type.lower() == "b2b" or competition_level == "high":
194
+ exp = EXPERIMENTS["concierge_mvp"].copy()
195
+ exp["priority"] = 3
196
+ exp["customized_for"] = idea
197
+ exp["target"] = target_customer
198
+ selected.append(exp)
199
+ else:
200
+ exp = EXPERIMENTS["waitlist"].copy()
201
+ exp["priority"] = 3
202
+ exp["customized_for"] = idea
203
+ selected.append(exp)
204
+
205
+ return selected
206
+
207
+
208
+ def format_experiment_output(experiments: list[dict], idea: str) -> str:
209
+ """Format experiments as markdown output."""
210
+ lines = [f"# Validation Experiments — {idea}\n"]
211
+ lines.append("Start with Experiment 1. Only move to the next if the previous fails.\n")
212
+
213
+ for exp in experiments:
214
+ lines.append(f"## Experiment {exp['priority']}: {exp['name']}")
215
+ lines.append(f"**Cost:** {exp['cost']} | **Duration:** {exp['duration']} | **Effort:** {exp['effort']}")
216
+ lines.append(f"\n**Goal:** {exp['goal']}\n")
217
+ lines.append("**Process:**")
218
+ for step in exp.get("process", []):
219
+ lines.append(f"- {step}")
220
+ lines.append(f"\n**Metric:** {exp['metric']}")
221
+ lines.append(f"**Success:** {exp['success_criteria']}")
222
+ if exp.get("red_flags"):
223
+ lines.append("\n**Red flags to watch:**")
224
+ for flag in exp["red_flags"]:
225
+ lines.append(f"- {flag}")
226
+ lines.append("")
227
+
228
+ return "\n".join(lines)