keinsaasforever/gtm-pipeline-skills/gtm-demo

Claude Code and agent skill by keinsaasforever/gtm-pipeline-skills

Trust tier 2
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Generate a demo lead list of ~10 enriched contacts with personalized message examples. Use when a demo is requested, a webhook prompt describes a target audience, or someone asks to "create a demo for [client]". Enforces demo mode restrictions (email only, no phone, ~10 contacts). Chains people-search → contact-filter → people-enrichment → (optional signal-search) → message generation. Pass `--with-signals` (or ask the user) to enable a buying-intent scoring pass before message generation — pricier but produces sharper, signal-anchored messages.

Install

npx vskill@latest install keinsaasforever/gtm-pipeline-skills/gtm-demo

SKILL.md

Demo

Generate a demo lead list of ~10 enriched contacts with personalized message examples, triggered by a webhook prompt.

Read `~/.claude/skills/gtm-pipeline/_shared/conventions.md` before executing.


When to Use

  • Webhook trigger: user submits a free demo form describing their target audience
  • Goal: prove the AI agent writes authentic, non-generic outreach using real leads
  • Scope: ~10 contacts, enriched with LinkedIn + email, 2–4 message examples

Demo Restrictions

  • No phone enrichment — email only
  • ~10 contacts (request 10–15, expect enrichment drop-off)
  • Message generation is optional but recommended
  • Signal search is opt-in — off by default. Enable via --with-signals flag or explicit user request. See Step 5.5.
  • Cost gate: before the first paid provider call, state the expected spend (finder searches per People-Source Cadence are mostly free; ~10-15 email enrichment credits; signals <$0.50 if enabled). Interactive: confirm with the user first. Headless: log the estimate in run_log.md and proceed, never block.

Step 1 — Parse the Prompt & Establish the ICP

The webhook prompt describes the user's target audience. Extract:

Must have:

  • What do you sell / offer?
  • Who is your ideal customer? (industry, role, company size, location)
  • What's your value proposition?
  • What tone? (formal vs. casual, examples if possible)
  • Is this for recruiting OR selling to customers?

If not in the prompt, infer (do not stall):

  • Target job titles, location, company size/type

Auto-resolve the requester before interpreting the audience (fixes the recurring "what do they even sell / why do they want this audience" gap):

  1. Resolve the requester's own domain from their email (e.g. name@acme.de → acme.de),

scrape/enrich it, and establish what they sell. Multi-offering companies: confirm which product line the demo is for — the prompt's stated product can mismatch the real one.

  1. Determine the relationship to the target audience — a target term (e.g. "call centers")

is usually a segment they sell to, not what they are. Classify: sell-to / buy-from / acquire / partner / recruit. Persona keywords derive from this, not from a guess.

Interactive vs deployed (headless):

  • Interactive: if a must-have is genuinely ambiguous after auto-resolution, ask one concise

clarifying question. Otherwise proceed.

  • Deployed / headless (invoked via claude -p from the webhook — see Deployment): **never

block on questions.** Infer every field from the prompt + requester-domain research, record assumptions in context/icp.md under an "Assumptions" heading, and proceed end-to-end.

Do NOT proceed to search until the ICP is clear enough to build a meaningful filter.

Save ICP to: {client-slug}-gtm/context/icp.md (include the offering, the relationship classification, and any headless assumptions).


Step 2 — Create Working Directory

Create the {client-slug}-gtm/ directory structure as defined in conventions.md. Write the ICP definition to context/icp.md.


Step 3 — People Search (10 contacts)

Use the people-search skill to find ~10–15 contacts.

Provider selection for demo: follow the finder cadence in conventions.md → People-Source Cadence — FullEnrich Finder → BetterContact → Pipe0 → Amplemarket/Crustdata (last resort), max 2 attempts/source, FE-first for SME/owner-led/non-English segments. Both FE and BC return LinkedIn URLs directly (needed for email enrichment). If no company list (persona-based prompt), use Parallel FindAll or BC Search. For directory/scrape-sourced company lists, search by company name + location, never by exact domain (conventions #11).

Key fields to collect:

full_name, first_name, last_name,
job_title, company_name, company_domain,
linkedin_profile_url, location

Follow the people-search execution protocol: sandbox → test → review → run.


Step 4 — Contact Filter (ICP Ranking)

Run contact-filter on the 10–15 contacts found. Even small batches benefit from ICP ranking — it ensures the enrichment step focuses on the best-fit contacts.

  • Applies job tier, industry tier, location tier, and company size classification
  • Rejects hard non-ICP contacts
  • Ranks passed contacts by priority
  • Output: csv/intermediate/contacts_filtered.csv

For demos: use a relaxed hard-reject threshold (allow tiers 1–5 to pass), prioritize ranking over filtering.


Step 5 — People Enrichment (Email Only)

Run people-enrichment on the filtered contacts. Demo mode: email only, no phone.

Recommended flow:

  1. FullEnrich v2 (email) — all contacts
  2. Pipe0 waterfall — for FE misses only

One address, one person. A provider can return the same address for two people at one company (one mailbox for two "Scott"s), so check addresses across contacts before Step 7: a shared address stays only with the person a source ties it to (their name in the local part, scott.clay@ for Scott Clay, or a page that pairs the name and the address, such as the company's team page); otherwise drop it from both. A contact that loses its address keeps its card, LinkedIn draft only, with the est-warn badge.

An address the organisation publishes itself. Public-sector mail servers often don't answer a verifier's check, so the address comes back unknown. When the organisation's own website or own document (staff page, org chart, imprint, its own PDF) prints that exact address next to that person's name, keep it with email_status published and write that page's URL into email_source. Example: a city's IT head whose address is printed on the city's staff page. Only for an unknown verdict: never for an invalid one, never for a pattern or format guess, never from a third-party source (a directory, a data provider, an article on another site). The deck shows it with its own badge and never as verified (Step 7b).

Additional enrichment for message personalization (if available):

  • LinkedIn headline and summary (from LinkedIn scrape via PhantomBuster)
  • Recent LinkedIn posts (2–3 per contact) — significantly improves message quality

Minimum viable fields for message generation:

name, job_title, company_name, linkedin_profile_url,
headline (optional), summary (optional), recent_posts (optional)

Low yield: if fewer than ~8 contacts survive filtering + enrichment, run one additional finder pass (next source in the People-Source Cadence) before proceeding to messages.


Step 5.5 — Signal Search (Optional)

Default: OFF. Enable when:

  • The user explicitly asks for signal-anchored messages
  • The webhook prompt mentions buying triggers (funding, hiring, transformation, recent news)
  • The client's offering depends on timing signals to make sense (e.g. "we help post-Series-A companies scale ops")
  • A --with-signals flag is passed to the demo invocation

Cost note: adds ~$0.01–0.05 per unique company (web search + scoring). On a 10-contact demo, that's typically 5–10 unique companies, so <$0.50.

Required inputs for signal-search

Signal-search needs three context files:

  • context/icp.md — already collected in Step 1
  • context/offering.md — the value-prop block from Step 1 (write it now if not already saved)
  • context/signal_criteria.md — bulleted list of signal types relevant to the offering

If signal_criteria.md doesn't exist, ask the user: "What would a company be doing right now that suggests they need what you offer?" — capture 5–10 bullets and save.

Run
bash
# Build company list from unique companies in the enriched contacts
python3 -c "
import csv, sys
seen = set()
with open('csv/output/contacts_enriched.csv') as f, open('csv/input/companies_raw.csv', 'w', newline='') as out:
reader = csv.DictReader(f)
writer = csv.DictWriter(out, fieldnames=['company_name', 'company_domain', 'company_website'])
writer.writeheader()
for row in reader:
c = row.get('company_name') or row.get('company') or ''
if c and c not in seen:
seen.add(c)
writer.writerow({
'company_name': c,
'company_domain': row.get('company_domain') or '',
'company_website': row.get('company_website') or row.get('website') or '',
})
"
# Run signal-search on the unique companies
source "$HOME/.claude/skills/gtm-pipeline/_shared/resolve_env.sh" && \
while IFS= read -r line; do export "$line"; done < <(grep -E '^(PARALLEL_API_KEY|OPENROUTER_API_KEY|FIRECRAWL_API_KEY|GEMINI_API_KEY)=' "$GTM_ENV_PATH") && \
python3 ~/.claude/skills/gtm-signal-search/signal_search.py \
--client-dir {client-slug}-gtm

The resolve_env.sh source line ensures $GTM_ENV_PATH is set even in a fresh shell (see signal-search SKILL.md / conventions). For the demo, leave Firecrawl and Parallel enrichment OFF — web search + scoring is enough for a ~10-contact lead list. Enable Firecrawl only if on-site content (careers, blog) is the primary signal source; if this machine has Firecrawl only via MCP (no FIRECRAWL_API_KEY), use the --firecrawl-pages-dir route documented in the signal-search skill. When the signal criteria include hiring, signal-search also reads the companies' job feeds on its own (their ATS board, plus the Arbeitsagentur for German companies; free, no flag; signal-search "Hiring signals").

Merge signals back into contacts
python
import csv, json
signals = {}
with open('csv/intermediate/signals.csv') as f:
for row in csv.DictReader(f):
signals[row['company_name']] = {
'overall_score': row.get('overallScore', ''),
'scored_signals': row.get('scoredSignals', ''),
'overall_summary': row.get('overallSummary', ''),
}
rows_out = []
with open('csv/output/contacts_enriched.csv') as f:
for row in csv.DictReader(f):
s = signals.get(row.get('company_name') or row.get('company') or '', {})
row['company_overall_score'] = s.get('overall_score', '')
row['company_scored_signals'] = s.get('scored_signals', '')
row['company_overall_summary'] = s.get('overall_summary', '')
rows_out.append(row)
with open('csv/output/contacts_enriched.csv', 'w', newline='') as f:
w = csv.DictWriter(f, fieldnames=list(rows_out[0].keys()))
w.writeheader()
w.writerows(rows_out)

The message-generation step in Step 6 will then have company_overall_summary and company_scored_signals per contact — use the highest-scored signal as the message hook.


Step 6 — Generate Message Examples

Generate 2–4 sample messages before committing to the full batch.

Message Structure

Every message must follow: Hook → Bridge → Offer → Soft CTA

PartPurposeLength
HookReference something specific to this person (post, career move, company signal)1 sentence
BridgeConnect their situation to your offer1 sentence
OfferWhat you provide, clearly stated1 sentence
CTASoft ask — not "let's schedule a call"1 sentence

Total: 320–450 characters. No blank line after greeting. Paragraphs separated by single line break.

Quality Rules

Must have:

  • Specific hook (post reference OR career insight OR scored buying signal — not generic)
  • Clear value proposition
  • Natural, conversational tone
  • Soft CTA

Must avoid:

  • Repeating profile info they already know ("You work as X at Y")
  • Generic observations ("impressive background", "I noticed you're in [industry]")
  • Corporate jargon or buzzwords
  • Pushy CTAs ("Let's schedule a call this week")

If Step 5.5 ran: for each contact, prefer the highest-scored signal from company_scored_signals as the hook over generic LinkedIn-post references. A score >= 70 signal anchored in real recent news is the strongest hook the demo can produce.

Generation Process

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
61
Repository forks
6
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/keinsaasforever/gtm-pipeline-skills/gtm-demo/badge)](https://verified-skill.com/skills/keinsaasforever/gtm-pipeline-skills/gtm-demo)

Something wrong with this skill or its page? Tell us.