Compare commits

...

6 Commits

Author SHA1 Message Date
Corey Haines b325e41c91 fix: address review — two more rigid imperatives caught
- sales-enablement: "Never demo without discovery" → "Demo after discovery, not before"
- site-architecture: "No exceptions" → explains why (backlink equity, broken pages)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:28:30 -08:00
Corey Haines b0a3a23699 refactor: replace rigid imperatives with reasoning-based guidance
Audit all 32 SKILL.md files against Anthropic's skill-creator writing
guidance ("why > MUST" pattern). Replaces ALWAYS/NEVER/MUST/IMPORTANT
imperatives with explanations of WHY the guidance matters.

17 edits across 14 skills:
- ad-creative: character limits reasoning, CTA headline reasoning
- seo-audit: schema detection warning softened, reasoning added
- programmatic-seo: subfolder vs subdomain reasoning
- paid-ads: exclusion list reasoning
- copywriting: honesty principle reasoning
- cold-email: follow-up value reasoning
- ai-seo: freshness signal reasoning
- churn-prevention: post-cancel path reasoning
- product-marketing-context: verbatim language reasoning
- popup-cro: close button visibility reasoning
- signup-flow-cro: label visibility reasoning
- form-cro: label visibility reasoning
- revops: fallback owner reasoning
- ab-test-setup: DON'T → Avoid

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:19:09 -08:00
Corey Haines 89fd57a8b0 Merge pull request #70 from coreyhaines31/feature/new-connector-tools
Add 10 new CLI tools and integration guides
2026-03-04 12:51:35 -08:00
Corey Haines a2e646c2e6 fix: update MCP tools list in CLAUDE.md, fix REGISTRY.md inconsistencies
- Add 6 new MCP-enabled tools to CLAUDE.md (zoominfo, clay, supermetrics,
  coupler, outreach, crossbeam)
- Fix broken Payments table in REGISTRY.md (blank row between stripe/paddle)
- Fix category name mismatch: "Competitive Intel" → "Competitive Intelligence"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:42:15 -08:00
Corey Haines 9a2bb97784 fix: address codex review findings across new CLI tools
- supermetrics: fix query endpoint /query → /query/data/json
- airops: fix base URL /v1 → /public_api/v1
- zoominfo: fix auth --dry-run leaking real JWT, add response validation
- outreach: remove parseInt() on JSON:API string IDs (caused NaN)
- similarweb: add encodeURIComponent on domain in all URL paths
- coupler: fix dry-run auth mask from '***' to 'Bearer ***'
- clay: allow name-based enrich (--first-name + --last-name + --domain)
- pendo.md: fix guide state from 'published' to 'public'
- close.md: fix rate limit header names to ratelimit-*

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:08:36 -08:00
Corey Haines 4ff486a702 feat: add 10 new CLI tools and integration guides from Claude connectors
New tools: Clay, Outreach, ZoomInfo, Close, Pendo, Similarweb,
Supermetrics, AirOps, Crossbeam, and Coupler.io. Each includes a
zero-dependency Node.js CLI and an integration guide.

Updates REGISTRY.md with new tool index entries, categories, and
MCP-enabled tools list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:59:51 -08:00
38 changed files with 3667 additions and 23 deletions
+1 -1
View File
@@ -167,7 +167,7 @@ This repository includes a tools registry for agent-compatible marketing tools.
- **Tool discovery**: Read `tools/REGISTRY.md` to see available tools and their capabilities - **Tool discovery**: Read `tools/REGISTRY.md` to see available tools and their capabilities
- **Integration details**: See `tools/integrations/{tool}.md` for API endpoints, auth, and common operations - **Integration details**: See `tools/integrations/{tool}.md` for API endpoints, auth, and common operations
- **MCP-enabled tools**: ga4, stripe, mailchimp, google-ads, resend, zapier - **MCP-enabled tools**: ga4, stripe, mailchimp, google-ads, resend, zapier, zoominfo, clay, supermetrics, coupler, outreach, crossbeam
### Registry Structure ### Registry Structure
+1 -1
View File
@@ -180,7 +180,7 @@ We'll know this is true when [metrics].
- Check segment quality - Check segment quality
- Document external factors - Document external factors
**DON'T:** **Avoid:**
- Peek at results and stop early - Peek at results and stop early
- Make changes to variants - Make changes to variants
- Add traffic from new sources - Add traffic from new sources
+2 -2
View File
@@ -64,7 +64,7 @@ Pull performance data → Identify winning patterns → Generate new variations
## Platform Specs ## Platform Specs
**Always enforce these limits.** Never deliver creative that exceeds platform character limits. Platforms reject or truncate creative that exceeds these limits, so verify every piece of copy fits before delivering.
### Google Ads (Responsive Search Ads) ### Google Ads (Responsive Search Ads)
@@ -320,7 +320,7 @@ For large-scale creative production (Anthropic's growth team generates 100+ vari
- **Writing headlines that only work together** — RSA headlines get combined randomly - **Writing headlines that only work together** — RSA headlines get combined randomly
- **Ignoring character limits** — Platforms truncate without warning - **Ignoring character limits** — Platforms truncate without warning
- **All variations sound the same** — Vary angles, not just word choice - **All variations sound the same** — Vary angles, not just word choice
- **No CTA headlines** — Always include action-oriented headlines - **No CTA headlines** — RSAs need action-oriented headlines to drive clicks; include at least 2-3
- **Generic descriptions** — "Learn more about our solution" wastes the slot - **Generic descriptions** — "Learn more about our solution" wastes the slot
- **Iterating without data** — Gut feelings are less reliable than metrics - **Iterating without data** — Gut feelings are less reliable than metrics
- **Testing too many things at once** — Change one variable per test cycle - **Testing too many things at once** — Change one variable per test cycle
+1 -1
View File
@@ -353,7 +353,7 @@ Monthly manual check:
- **Ignoring AI search entirely** — ~45% of Google searches now show AI Overviews, and ChatGPT/Perplexity are growing fast - **Ignoring AI search entirely** — ~45% of Google searches now show AI Overviews, and ChatGPT/Perplexity are growing fast
- **Treating AI SEO as separate from SEO** — Good traditional SEO is the foundation; AI SEO adds structure and authority on top - **Treating AI SEO as separate from SEO** — Good traditional SEO is the foundation; AI SEO adds structure and authority on top
- **Writing for AI, not humans** — If content reads like it was written to game an algorithm, it won't get cited or convert - **Writing for AI, not humans** — If content reads like it was written to game an algorithm, it won't get cited or convert
- **No freshness signals** — Undated content loses to dated content. Always show when content was last updated - **No freshness signals** — Undated content loses to dated content because AI systems weight recency heavily. Show when content was last updated
- **Gating all content** — AI can't access gated content. Keep your most authoritative content open - **Gating all content** — AI can't access gated content. Keep your most authoritative content open
- **Ignoring third-party presence** — You may get more AI citations from a Wikipedia mention than from your own blog - **Ignoring third-party presence** — You may get more AI citations from a Wikipedia mention than from your own blog
- **No structured data** — Schema markup gives AI systems structured context about your content - **No structured data** — Schema markup gives AI systems structured context about your content
+1 -1
View File
@@ -375,7 +375,7 @@ Test one variable at a time:
- **Guilt-trip copy** — "Are you sure you want to abandon us?" damages brand trust - **Guilt-trip copy** — "Are you sure you want to abandon us?" damages brand trust
- **Not tracking save offer LTV** — A "saved" customer who churns 30 days later wasn't really saved - **Not tracking save offer LTV** — A "saved" customer who churns 30 days later wasn't really saved
- **Pausing too long** — Pauses beyond 3 months rarely reactivate. Set limits. - **Pausing too long** — Pauses beyond 3 months rarely reactivate. Set limits.
- **No post-cancel path** — Always make reactivation easy and trigger win-back emails - **No post-cancel path** — Make reactivation easy and trigger win-back emails, because some churned users will want to come back
--- ---
+1 -1
View File
@@ -100,7 +100,7 @@ See [subject-lines.md](references/subject-lines.md) for the full data.
## Follow-Up Sequences ## Follow-Up Sequences
Each follow-up must add something new — a different angle, fresh proof, a useful resource. Never "just checking in." Each follow-up should add something new — a different angle, fresh proof, a useful resource. "Just checking in" gives the reader no reason to respond.
- 3-5 total emails, increasing gaps between them - 3-5 total emails, increasing gaps between them
- Each email should stand alone (they may not have read the previous ones) - Each email should stand alone (they may not have read the previous ones)
+1 -1
View File
@@ -67,7 +67,7 @@ Each section should advance one argument. Build a logical flow down the page.
3. **Active over passive** — "We generate reports" not "Reports are generated" 3. **Active over passive** — "We generate reports" not "Reports are generated"
4. **Confident over qualified** — Remove "almost," "very," "really" 4. **Confident over qualified** — Remove "almost," "very," "really"
5. **Show over tell** — Describe the outcome instead of using adverbs 5. **Show over tell** — Describe the outcome instead of using adverbs
6. **Honest over sensational**Never fabricate statistics or testimonials 6. **Honest over sensational**Fabricated statistics or testimonials erode trust and create legal liability
### Quick Quality Check ### Quick Quality Check
+1 -1
View File
@@ -120,7 +120,7 @@ For each field, ask:
4. Logical grouping if many fields 4. Logical grouping if many fields
### Labels and Placeholders ### Labels and Placeholders
- Labels: Always visible (not just placeholder) - Labels: Keep visible (not just placeholder) — placeholders disappear when typing, leaving users unsure what they're filling in
- Placeholders: Examples, not labels - Placeholders: Examples, not labels
- Help text: Only when genuinely helpful - Help text: Only when genuinely helpful
+1 -1
View File
@@ -122,7 +122,7 @@ LI_LeadGen_CMOs-SaaS_Whitepaper_Mar24
- **Lookalikes**: Base on best customers (by LTV), not all customers - **Lookalikes**: Base on best customers (by LTV), not all customers
- **Retargeting**: Segment by funnel stage (visitors vs. cart abandoners) - **Retargeting**: Segment by funnel stage (visitors vs. cart abandoners)
- **Exclusions**: Always exclude existing customers and recent converters - **Exclusions**: Exclude existing customers and recent converters — showing ads to people who already bought wastes spend
**For detailed targeting strategies by platform**: See [references/audience-targeting.md](references/audience-targeting.md) **For detailed targeting strategies by platform**: See [references/audience-targeting.md](references/audience-targeting.md)
+1 -1
View File
@@ -182,7 +182,7 @@ Before providing recommendations, understand:
- Leave space to close (visible X, click outside) - Leave space to close (visible X, click outside)
### Close Button ### Close Button
- Always visible (top right is convention) - Keep visible (top right is convention) — users who can't find the close button will bounce entirely
- Large enough to tap on mobile - Large enough to tap on mobile
- "No thanks" text link as alternative - "No thanks" text link as alternative
- Click outside to close - Click outside to close
+1 -1
View File
@@ -47,7 +47,7 @@ For each section:
3. Confirm accuracy 3. Confirm accuracy
4. Move to the next 4. Move to the next
**Important:** Push for verbatim customer language. Exact phrases are more valuable than polished descriptions. Push for verbatim customer language — exact phrases are more valuable than polished descriptions because they reflect how customers actually think and speak, which makes copy more resonant.
--- ---
+1 -1
View File
@@ -49,7 +49,7 @@ Hierarchy of data defensibility:
5. Public (anyone can use—weakest) 5. Public (anyone can use—weakest)
### 3. Clean URL Structure ### 3. Clean URL Structure
**Always use subfolders, not subdomains**: **Use subfolders, not subdomains** — subfolders consolidate domain authority while subdomains split it:
- Good: `yoursite.com/templates/resume/` - Good: `yoursite.com/templates/resume/`
- Bad: `templates.yoursite.com/resume/` - Bad: `templates.yoursite.com/resume/`
+1 -1
View File
@@ -133,7 +133,7 @@ Define response times and document them:
### Routing Rules Essentials ### Routing Rules Essentials
- Route to the **most specific match** first, then fall back to general - Route to the **most specific match** first, then fall back to general
- Always include a **fallback owner**no lead should go unassigned - Include a **fallback owner**unassigned leads go cold fast and waste pipeline
- Round-robin should account for **rep capacity and availability** (PTO, quota attainment) - Round-robin should account for **rep capacity and availability** (PTO, quota attainment)
- Log every routing decision for audit and optimization - Log every routing decision for audit and optimization
+1 -1
View File
@@ -205,7 +205,7 @@ For each objection, document:
### Key Principles ### Key Principles
- **Never demo without discovery.** If you don't know their pain, you're guessing which features matter. - **Demo after discovery, not before.** If you don't know their pain, you're guessing which features matter.
- **Customize to their use case.** Use their terminology, their data (if possible), their workflow. - **Customize to their use case.** Use their terminology, their data (if possible), their workflow.
- **Leave time for questions.** A demo where the prospect doesn't talk is a demo that doesn't close. - **Leave time for questions.** A demo where the prospect doesn't talk is a demo that doesn't close.
+3 -3
View File
@@ -35,7 +35,7 @@ Before auditing, understand:
## Audit Framework ## Audit Framework
### ⚠️ Important: Schema Markup Detection Limitation ### Schema Markup Detection Limitation
**`web_fetch` and `curl` cannot reliably detect structured data / schema markup.** **`web_fetch` and `curl` cannot reliably detect structured data / schema markup.**
@@ -46,7 +46,7 @@ Many CMS plugins (AIOSEO, Yoast, RankMath) inject JSON-LD via client-side JavaSc
2. **Google Rich Results Test** — https://search.google.com/test/rich-results 2. **Google Rich Results Test** — https://search.google.com/test/rich-results
3. **Screaming Frog export** — if the client provides one, use it (SF renders JavaScript) 3. **Screaming Frog export** — if the client provides one, use it (SF renders JavaScript)
**Never report "no schema found" based solely on `web_fetch` or `curl`.** This has led to false audit findings in production. Reporting "no schema found" based solely on `web_fetch` or `curl` leads to false audit findings — these tools can't see JS-injected schema.
### Priority Order ### Priority Order
1. **Crawlability & Indexation** (can Google find and index it?) 1. **Crawlability & Indexation** (can Google find and index it?)
@@ -382,7 +382,7 @@ Same format as above
- Mobile-Friendly Test - Mobile-Friendly Test
- Schema Validator - Schema Validator
> **Note on schema detection:** `web_fetch` strips `<script>` tags (including JSON-LD) and cannot detect JS-injected schema. Always use the browser tool, Rich Results Test, or Screaming Frog for schema checks. See the warning at the top of the Audit Framework section. > **Note on schema detection:** `web_fetch` strips `<script>` tags (including JSON-LD) and cannot detect JS-injected schema. Use the browser tool, Rich Results Test, or Screaming Frog instead — they render JavaScript and capture dynamically-injected markup. See the Schema Markup Detection Limitation section above.
**Paid Tools** (if available) **Paid Tools** (if available)
- Screaming Frog - Screaming Frog
+1 -1
View File
@@ -161,7 +161,7 @@ Every field reduces conversion. For each field, ask:
### Microcopy ### Microcopy
- Placeholder text: Use for examples, not labels - Placeholder text: Use for examples, not labels
- Labels: Always visible (not just placeholders) - Labels: Keep visible (not just placeholders) — placeholders disappear when typing, leaving users unsure what they're filling in
- Help text: Only when needed, placed close to field - Help text: Only when needed, placed close to field
--- ---
+1 -1
View File
@@ -186,7 +186,7 @@ Breadcrumbs should mirror the URL hierarchy. Every breadcrumb segment should be
- **Dates in blog URLs** — `/blog/2024/01/15/post-title` adds no value and makes URLs long. Use `/blog/post-title`. - **Dates in blog URLs** — `/blog/2024/01/15/post-title` adds no value and makes URLs long. Use `/blog/post-title`.
- **Over-nesting** — `/products/category/subcategory/item/detail` is too deep. Flatten where possible. - **Over-nesting** — `/products/category/subcategory/item/detail` is too deep. Flatten where possible.
- **Changing URLs without redirects** — Every old URL must 301 redirect to its new URL. No exceptions. - **Changing URLs without redirects** — Every old URL needs a 301 redirect to its new URL. Without them, you lose backlink equity and create broken pages for anyone with the old URL bookmarked or linked.
- **IDs in URLs** — `/product/12345` is not human-readable. Use slugs. - **IDs in URLs** — `/product/12345` is not human-readable. Use slugs.
- **Query parameters for content** — `/blog?id=123` should be `/blog/post-title`. - **Query parameters for content** — `/blog?id=123` should be `/blog/post-title`.
- **Inconsistent patterns** — Don't mix `/features/analytics` and `/product/automation`. Pick one parent. - **Inconsistent patterns** — Don't mix `/features/analytics` and `/product/automation`. Pick one parent.
+82 -3
View File
@@ -28,8 +28,13 @@ Quick reference for AI agents to discover tool capabilities and integration meth
| keywords-everywhere | SEO | ✓ | - | [](clis/keywords-everywhere.js) | - | [keywords-everywhere.md](integrations/keywords-everywhere.md) | | keywords-everywhere | SEO | ✓ | - | [](clis/keywords-everywhere.js) | - | [keywords-everywhere.md](integrations/keywords-everywhere.md) |
| clearbit | Data Enrichment | ✓ | - | [](clis/clearbit.js) | ✓ | [clearbit.md](integrations/clearbit.md) | | clearbit | Data Enrichment | ✓ | - | [](clis/clearbit.js) | ✓ | [clearbit.md](integrations/clearbit.md) |
| apollo | Data Enrichment | ✓ | - | [](clis/apollo.js) | - | [apollo.md](integrations/apollo.md) | | apollo | Data Enrichment | ✓ | - | [](clis/apollo.js) | - | [apollo.md](integrations/apollo.md) |
| zoominfo | Data Enrichment | ✓ | ✓ | [](clis/zoominfo.js) | - | [zoominfo.md](integrations/zoominfo.md) |
| clay | Data Enrichment | ✓ | ✓ | [](clis/clay.js) | - | [clay.md](integrations/clay.md) |
| supermetrics | Data Aggregation | ✓ | ✓ | [](clis/supermetrics.js) | - | [supermetrics.md](integrations/supermetrics.md) |
| coupler | Data Aggregation | ✓ | ✓ | [](clis/coupler.js) | - | [coupler.md](integrations/coupler.md) |
| hubspot | CRM | ✓ | - | ✓ | ✓ | [hubspot.md](integrations/hubspot.md) | | hubspot | CRM | ✓ | - | ✓ | ✓ | [hubspot.md](integrations/hubspot.md) |
| salesforce | CRM | ✓ | - | ✓ | ✓ | [salesforce.md](integrations/salesforce.md) | | salesforce | CRM | ✓ | - | ✓ | ✓ | [salesforce.md](integrations/salesforce.md) |
| close | CRM | ✓ | - | [](clis/close.js) | - | [close.md](integrations/close.md) |
| stripe | Payments | ✓ | ✓ | ✓ | ✓ | [stripe.md](integrations/stripe.md) | | stripe | Payments | ✓ | ✓ | ✓ | ✓ | [stripe.md](integrations/stripe.md) |
| paddle | Payments | ✓ | - | [](clis/paddle.js) | ✓ | [paddle.md](integrations/paddle.md) | | paddle | Payments | ✓ | - | [](clis/paddle.js) | ✓ | [paddle.md](integrations/paddle.md) |
| rewardful | Referral | ✓ | - | [](clis/rewardful.js) | - | [rewardful.md](integrations/rewardful.md) | | rewardful | Referral | ✓ | - | [](clis/rewardful.js) | - | [rewardful.md](integrations/rewardful.md) |
@@ -62,6 +67,11 @@ Quick reference for AI agents to discover tool capabilities and integration meth
| savvycal | Scheduling | ✓ | - | [](clis/savvycal.js) | - | [savvycal.md](integrations/savvycal.md) | | savvycal | Scheduling | ✓ | - | [](clis/savvycal.js) | - | [savvycal.md](integrations/savvycal.md) |
| typeform | Forms | ✓ | - | [](clis/typeform.js) | ✓ | [typeform.md](integrations/typeform.md) | | typeform | Forms | ✓ | - | [](clis/typeform.js) | ✓ | [typeform.md](integrations/typeform.md) |
| intercom | Messaging | ✓ | - | [](clis/intercom.js) | ✓ | [intercom.md](integrations/intercom.md) | | intercom | Messaging | ✓ | - | [](clis/intercom.js) | ✓ | [intercom.md](integrations/intercom.md) |
| outreach | Sales Engagement | ✓ | ✓ | [](clis/outreach.js) | - | [outreach.md](integrations/outreach.md) |
| crossbeam | Partner Ecosystem | ✓ | ✓ | [](clis/crossbeam.js) | - | [crossbeam.md](integrations/crossbeam.md) |
| pendo | Product Analytics | ✓ | - | [](clis/pendo.js) | - | [pendo.md](integrations/pendo.md) |
| similarweb | Competitive Intelligence | ✓ | - | [](clis/similarweb.js) | - | [similarweb.md](integrations/similarweb.md) |
| airops | AI Content | ✓ | - | [](clis/airops.js) | - | [airops.md](integrations/airops.md) |
| buffer | Social | ✓ | - | [](clis/buffer.js) | - | [buffer.md](integrations/buffer.md) | | buffer | Social | ✓ | - | [](clis/buffer.js) | - | [buffer.md](integrations/buffer.md) |
| wistia | Video | ✓ | - | [](clis/wistia.js) | - | [wistia.md](integrations/wistia.md) | | wistia | Video | ✓ | - | [](clis/wistia.js) | - | [wistia.md](integrations/wistia.md) |
| trustpilot | Reviews | ✓ | - | [](clis/trustpilot.js) | - | [trustpilot.md](integrations/trustpilot.md) | | trustpilot | Reviews | ✓ | - | [](clis/trustpilot.js) | - | [trustpilot.md](integrations/trustpilot.md) |
@@ -115,8 +125,9 @@ Customer relationship management and sales tools.
|------|----------|:-------------:| |------|----------|:-------------:|
| **hubspot** | SMB, marketing + sales alignment | ✓ | | **hubspot** | SMB, marketing + sales alignment | ✓ |
| **salesforce** | Enterprise, complex sales processes | ✓ | | **salesforce** | Enterprise, complex sales processes | ✓ |
| **close** | SMB, high-velocity sales | [](clis/close.js) |
**Agent recommendation**: HubSpot for startups/SMBs, Salesforce for enterprise. **Agent recommendation**: HubSpot for startups/SMBs. Close for high-velocity inside sales. Salesforce for enterprise.
### Payments ### Payments
@@ -125,7 +136,6 @@ Payment processing and subscription management.
| Tool | Best For | MCP Available | | Tool | Best For | MCP Available |
|------|----------|:-------------:| |------|----------|:-------------:|
| **stripe** | SaaS subscriptions, developer-friendly | ✓ | | **stripe** | SaaS subscriptions, developer-friendly | ✓ |
| **paddle** | SaaS billing with tax handling | - | | **paddle** | SaaS billing with tax handling | - |
**Agent recommendation**: Stripe is the default for SaaS. Paddle for built-in tax compliance. **Agent recommendation**: Stripe is the default for SaaS. Paddle for built-in tax compliance.
@@ -256,8 +266,10 @@ Company and person data enrichment for sales and marketing.
|------|----------|-------| |------|----------|-------|
| **clearbit** | Company/person enrichment | Now HubSpot Breeze | | **clearbit** | Company/person enrichment | Now HubSpot Breeze |
| **apollo** | B2B prospecting, email finding | Large database | | **apollo** | B2B prospecting, email finding | Large database |
| **zoominfo** | B2B contacts, intent data | Enterprise-grade |
| **clay** | Waterfall enrichment, outbound | 75+ data providers |
**Agent recommendation**: Clearbit for enrichment. Apollo for prospecting and outbound. **Agent recommendation**: Clearbit for enrichment. Apollo for prospecting and outbound. ZoomInfo for enterprise B2B data with intent signals. Clay for waterfall enrichment across multiple providers.
### Reviews ### Reviews
@@ -291,6 +303,56 @@ Webinar and virtual event platforms.
**Agent recommendation**: Demio for marketing-focused webinars. Livestorm for full event engagement. **Agent recommendation**: Demio for marketing-focused webinars. Livestorm for full event engagement.
### Sales Engagement
Sales engagement and outreach automation platforms.
| Tool | Best For | Notes |
|------|----------|-------|
| **outreach** | Enterprise sales engagement | Sequences, tasks, analytics |
**Agent recommendation**: Outreach for enterprise sales teams managing multi-touch sequences at scale.
### Product Analytics
Product analytics, feature adoption tracking, and in-app guidance.
| Tool | Best For | Notes |
|------|----------|-------|
| **pendo** | Feature adoption, in-app guides | Product-led growth |
**Agent recommendation**: Pendo for tracking feature adoption and delivering targeted in-app guidance.
### Competitive Intelligence
Traffic analytics, competitor benchmarking, and market research.
| Tool | Best For | Notes |
|------|----------|-------|
| **similarweb** | Website traffic, competitor analysis | Traffic sources, keywords |
**Agent recommendation**: Similarweb for competitor traffic analysis and market benchmarking.
### AI Content
AI-powered content generation and optimization platforms.
| Tool | Best For | Notes |
|------|----------|-------|
| **airops** | AI content workflows, SEO content | Flow-based automation |
**Agent recommendation**: AirOps for building AI content workflows that generate SEO-optimized content at scale.
### Partner Ecosystem
Partner data sharing, co-sell, and ecosystem management.
| Tool | Best For | Notes |
|------|----------|-------|
| **crossbeam** | Account overlaps, co-sell | Now part of Reveal |
**Agent recommendation**: Crossbeam for identifying partner account overlaps and co-sell opportunities.
### Email Outreach ### Email Outreach
Cold email outreach and email finding tools for link building and sales prospecting. Cold email outreach and email finding tools for link building and sales prospecting.
@@ -304,6 +366,17 @@ Cold email outreach and email finding tools for link building and sales prospect
**Agent recommendation**: Hunter for finding emails. Lemlist or Instantly for sending cold email campaigns. Snov for combined finding + outreach. **Agent recommendation**: Hunter for finding emails. Lemlist or Instantly for sending cold email campaigns. Snov for combined finding + outreach.
### Data Aggregation
Marketing data pipeline tools that connect multiple platforms for unified reporting.
| Tool | Best For | Notes |
|------|----------|-------|
| **supermetrics** | Cross-platform data pulling | 200+ connectors |
| **coupler** | Automated data flows to sheets/BI | Scheduled pipelines |
**Agent recommendation**: Supermetrics for pulling data from multiple marketing platforms into unified reports. Coupler.io for automated data flows to spreadsheets and BI tools.
### Commerce & CMS ### Commerce & CMS
E-commerce platforms and content management systems. E-commerce platforms and content management systems.
@@ -340,6 +413,12 @@ These tools have Model Context Protocol servers available, enabling direct agent
- **google-ads** - Ad campaign management - **google-ads** - Ad campaign management
- **resend** - Transactional email sending - **resend** - Transactional email sending
- **zapier** - Workflow automation - **zapier** - Workflow automation
- **zoominfo** - B2B contacts and intent data
- **clay** - Data enrichment and outbound automation
- **supermetrics** - Cross-platform marketing data
- **coupler** - Marketing data pipelines
- **outreach** - Sales engagement sequences
- **crossbeam** - Partner ecosystem data
To use MCP tools, ensure the appropriate MCP server is configured in your environment. To use MCP tools, ensure the appropriate MCP server is configured in your environment.
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env node
const API_KEY = process.env.AIROPS_API_KEY
const WORKSPACE_ID = process.env.AIROPS_WORKSPACE_ID
const BASE_URL = 'https://api.airops.com/public_api/v1'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'AIROPS_API_KEY environment variable required' }))
process.exit(1)
}
if (!WORKSPACE_ID) {
console.error(JSON.stringify({ error: 'AIROPS_WORKSPACE_ID environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
const url = `${BASE_URL}${path}`
if (args['dry-run']) {
return { _dry_run: true, method, url, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined }
}
const res = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'flows':
switch (sub) {
case 'list': {
result = await api('GET', `/workspaces/${WORKSPACE_ID}/flows`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/workspaces/${WORKSPACE_ID}/flows/${id}`)
break
}
case 'execute': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
const inputs = args.inputs
let parsedInputs = {}
if (inputs) {
try {
parsedInputs = JSON.parse(inputs)
} catch {
result = { error: '--inputs must be valid JSON' }
break
}
}
result = await api('POST', `/workspaces/${WORKSPACE_ID}/flows/${id}/execute`, { inputs: parsedInputs })
break
}
case 'runs': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/workspaces/${WORKSPACE_ID}/flows/${id}/runs`)
break
}
case 'run-status': {
const runId = args['run-id']
if (!runId) { result = { error: '--run-id required' }; break }
result = await api('GET', `/workspaces/${WORKSPACE_ID}/runs/${runId}`)
break
}
default:
result = { error: 'Unknown flows subcommand. Use: list, get, execute, runs, run-status' }
}
break
case 'workflows':
switch (sub) {
case 'list': {
result = await api('GET', `/workspaces/${WORKSPACE_ID}/workflows`)
break
}
case 'execute': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
const inputs = args.inputs
let parsedInputs = {}
if (inputs) {
try {
parsedInputs = JSON.parse(inputs)
} catch {
result = { error: '--inputs must be valid JSON' }
break
}
}
result = await api('POST', `/workspaces/${WORKSPACE_ID}/workflows/${id}/execute`, { inputs: parsedInputs })
break
}
default:
result = { error: 'Unknown workflows subcommand. Use: list, execute' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
flows: {
list: 'flows list',
get: 'flows get --id <id>',
execute: 'flows execute --id <id> --inputs <json>',
runs: 'flows runs --id <id>',
'run-status': 'flows run-status --run-id <id>',
},
workflows: {
list: 'workflows list',
execute: 'workflows execute --id <id> --inputs <json>',
},
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env node
const API_KEY = process.env.CLAY_API_KEY
const BASE_URL = 'https://api.clay.com/v3'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'CLAY_API_KEY environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
if (args['dry-run']) {
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined }
}
const options = {
method,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
}
if (body) options.body = JSON.stringify(body)
const res = await fetch(`${BASE_URL}${path}`, options)
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
const page = args.page ? Number(args.page) : 1
const perPage = args['per-page'] ? Number(args['per-page']) : 25
switch (cmd) {
case 'tables':
switch (sub) {
case 'list': {
result = await api('GET', `/tables?page=${page}&per_page=${perPage}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/tables/${id}`)
break
}
case 'rows': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/tables/${id}/rows?page=${page}&per_page=${perPage}`)
break
}
case 'add-row': {
const id = args.id
const data = args.data
if (!id) { result = { error: '--id required' }; break }
if (!data) { result = { error: '--data required (JSON string)' }; break }
let parsed
try {
parsed = JSON.parse(data)
} catch {
result = { error: 'Invalid JSON in --data' }
break
}
result = await api('POST', `/tables/${id}/rows`, parsed)
break
}
default:
result = { error: 'Unknown tables subcommand. Use: list, get, rows, add-row' }
}
break
case 'people':
switch (sub) {
case 'enrich': {
const body = {}
if (args.email) body.email = args.email
if (args.linkedin) body.linkedin_url = args.linkedin
if (args['first-name']) body.first_name = args['first-name']
if (args['last-name']) body.last_name = args['last-name']
if (args['first-name'] && args['last-name'] && args.domain) body.domain = args.domain
if (!args.email && !args.linkedin && !(args['first-name'] && args['last-name'] && args.domain)) {
result = { error: '--email or --linkedin required (or --first-name + --last-name + --domain)' }
break
}
result = await api('POST', '/people/enrich', body)
break
}
default:
result = { error: 'Unknown people subcommand. Use: enrich' }
}
break
case 'companies':
switch (sub) {
case 'enrich': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
result = await api('POST', '/companies/enrich', { domain })
break
}
default:
result = { error: 'Unknown companies subcommand. Use: enrich' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
tables: {
list: 'tables list [--page <n>] [--per-page <n>]',
get: 'tables get --id <table_id>',
rows: 'tables rows --id <table_id> [--page <n>] [--per-page <n>]',
'add-row': 'tables add-row --id <table_id> --data <json>',
},
people: {
enrich: 'people enrich --email <email> | --linkedin <url> | --first-name <n> --last-name <n> --domain <d>',
},
companies: {
enrich: 'companies enrich --domain <domain>',
},
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env node
const API_KEY = process.env.CLOSE_API_KEY
const BASE_URL = 'https://api.close.com/api/v1'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'CLOSE_API_KEY environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
const url = `${BASE_URL}${path}`
if (args['dry-run']) {
return { _dry_run: true, method, url, headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ***' }, body: body || undefined }
}
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Basic ${btoa(API_KEY + ':')}`,
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'leads':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args.query) params.set('query', args.query)
if (args.page) params.set('_skip', (parseInt(args.page) - 1) * 100)
const qs = params.toString()
result = await api('GET', `/lead/${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/lead/${id}/`)
break
}
case 'create': {
const name = args.name
if (!name) { result = { error: '--name required' }; break }
const body = { name }
if (args.url) body.url = args.url
if (args.description) body.description = args.description
result = await api('POST', '/lead/', body)
break
}
default:
result = { error: 'Unknown leads subcommand. Use: list, get, create' }
}
break
case 'contacts':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args['lead-id']) params.set('lead_id', args['lead-id'])
const qs = params.toString()
result = await api('GET', `/contact/${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/contact/${id}/`)
break
}
case 'create': {
const leadId = args['lead-id']
const name = args.name
if (!leadId) { result = { error: '--lead-id required' }; break }
if (!name) { result = { error: '--name required' }; break }
const body = { lead_id: leadId, name }
if (args.email) {
body.emails = [{ email: args.email, type: 'office' }]
}
if (args.phone) {
body.phones = [{ phone: args.phone, type: 'office' }]
}
result = await api('POST', '/contact/', body)
break
}
default:
result = { error: 'Unknown contacts subcommand. Use: list, get, create' }
}
break
case 'opportunities':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args.status) params.set('status', args.status)
const qs = params.toString()
result = await api('GET', `/opportunity/${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/opportunity/${id}/`)
break
}
case 'create': {
const leadId = args['lead-id']
const value = args.value
if (!leadId) { result = { error: '--lead-id required' }; break }
if (!value) { result = { error: '--value required (in cents)' }; break }
const body = { lead_id: leadId, value: parseInt(value) }
if (args.status) body.status_type = args.status
result = await api('POST', '/opportunity/', body)
break
}
default:
result = { error: 'Unknown opportunities subcommand. Use: list, get, create' }
}
break
case 'activities':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args['lead-id']) params.set('lead_id', args['lead-id'])
if (args.type) params.set('_type__type', args.type)
const qs = params.toString()
result = await api('GET', `/activity/${qs ? '?' + qs : ''}`)
break
}
default:
result = { error: 'Unknown activities subcommand. Use: list' }
}
break
case 'tasks':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args['assigned-to']) params.set('assigned_to', args['assigned-to'])
if (args['is-complete']) params.set('is_complete', args['is-complete'])
const qs = params.toString()
result = await api('GET', `/task/${qs ? '?' + qs : ''}`)
break
}
case 'create': {
const leadId = args['lead-id']
const text = args.text
if (!leadId) { result = { error: '--lead-id required' }; break }
if (!text) { result = { error: '--text required' }; break }
const body = { lead_id: leadId, text, _type: 'lead' }
if (args['assigned-to']) body.assigned_to = args['assigned-to']
if (args.date) body.date = args.date
result = await api('POST', '/task/', body)
break
}
default:
result = { error: 'Unknown tasks subcommand. Use: list, create' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
leads: {
list: 'leads list [--query <q>] [--page <n>]',
get: 'leads get --id <id>',
create: 'leads create --name <name> [--url <url>] [--description <desc>]',
},
contacts: {
list: 'contacts list [--lead-id <id>]',
get: 'contacts get --id <id>',
create: 'contacts create --lead-id <id> --name <name> [--email <email>] [--phone <phone>]',
},
opportunities: {
list: 'opportunities list [--status <status>]',
get: 'opportunities get --id <id>',
create: 'opportunities create --lead-id <id> --value <cents> [--status <status>]',
},
activities: {
list: 'activities list [--lead-id <id>] [--type <type>]',
},
tasks: {
list: 'tasks list [--assigned-to <user-id>] [--is-complete <bool>]',
create: 'tasks create --lead-id <id> --text <text> [--assigned-to <user-id>] [--date <date>]',
},
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env node
const API_KEY = process.env.COUPLER_API_KEY
const BASE_URL = 'https://api.coupler.io/v1'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'COUPLER_API_KEY environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
if (args['dry-run']) {
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined }
}
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'importers':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args.limit) params.set('limit', args.limit)
if (args.offset) params.set('offset', args.offset)
const qs = params.toString()
result = await api('GET', `/importers${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/importers/${id}`)
break
}
case 'run': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('POST', `/importers/${id}/run`)
break
}
case 'create': {
const source = args.source
const destination = args.destination
const name = args.name
if (!source) { result = { error: '--source required' }; break }
if (!destination) { result = { error: '--destination required' }; break }
if (!name) { result = { error: '--name required' }; break }
const body = { source_type: source, destination_type: destination, name }
if (args.schedule) body.schedule = args.schedule
result = await api('POST', '/importers', body)
break
}
case 'delete': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('DELETE', `/importers/${id}`)
break
}
default:
result = { error: 'Unknown importers subcommand. Use: list, get, run, create, delete' }
}
break
case 'runs':
switch (sub) {
case 'list': {
const importerId = args['importer-id']
if (!importerId) { result = { error: '--importer-id required' }; break }
const params = new URLSearchParams()
if (args.limit) params.set('limit', args.limit)
if (args.offset) params.set('offset', args.offset)
const qs = params.toString()
result = await api('GET', `/importers/${importerId}/runs${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/runs/${id}`)
break
}
default:
result = { error: 'Unknown runs subcommand. Use: list, get' }
}
break
case 'sources':
switch (sub) {
case 'list':
result = await api('GET', '/sources')
break
default:
result = { error: 'Unknown sources subcommand. Use: list' }
}
break
case 'destinations':
switch (sub) {
case 'list':
result = await api('GET', '/destinations')
break
default:
result = { error: 'Unknown destinations subcommand. Use: list' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
importers: {
list: 'importers list [--limit <n>] [--offset <n>]',
get: 'importers get --id <id>',
run: 'importers run --id <id>',
create: 'importers create --source <source-type> --destination <dest-type> --name <name> [--schedule <schedule>]',
delete: 'importers delete --id <id>',
},
runs: {
list: 'runs list --importer-id <id> [--limit <n>] [--offset <n>]',
get: 'runs get --id <id>',
},
sources: 'sources list',
destinations: 'destinations list',
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env node
const API_KEY = process.env.CROSSBEAM_API_KEY
const BASE_URL = 'https://api.crossbeam.com/v1'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'CROSSBEAM_API_KEY environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
const url = `${BASE_URL}${path}`
if (args['dry-run']) {
return { _dry_run: true, method, url, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined }
}
const res = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'partners':
switch (sub) {
case 'list': {
result = await api('GET', '/partners')
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/partners/${id}`)
break
}
default:
result = { error: 'Unknown partners subcommand. Use: list, get' }
}
break
case 'populations':
switch (sub) {
case 'list': {
result = await api('GET', '/populations')
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/populations/${id}`)
break
}
default:
result = { error: 'Unknown populations subcommand. Use: list, get' }
}
break
case 'overlaps':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args['partner-id']) params.set('partner_id', args['partner-id'])
if (args['population-id']) params.set('population_id', args['population-id'])
const qs = params.toString()
result = await api('GET', `/overlaps${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/overlaps/${id}`)
break
}
default:
result = { error: 'Unknown overlaps subcommand. Use: list, get' }
}
break
case 'reports':
switch (sub) {
case 'list': {
result = await api('GET', '/reports')
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/reports/${id}`)
break
}
default:
result = { error: 'Unknown reports subcommand. Use: list, get' }
}
break
case 'threads':
switch (sub) {
case 'list': {
result = await api('GET', '/threads')
break
}
default:
result = { error: 'Unknown threads subcommand. Use: list' }
}
break
case 'accounts':
switch (sub) {
case 'search': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
const params = new URLSearchParams({ domain })
result = await api('GET', `/accounts/search?${params.toString()}`)
break
}
default:
result = { error: 'Unknown accounts subcommand. Use: search' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
partners: {
list: 'partners list',
get: 'partners get --id <id>',
},
populations: {
list: 'populations list',
get: 'populations get --id <id>',
},
overlaps: {
list: 'overlaps list [--partner-id <id>] [--population-id <id>]',
get: 'overlaps get --id <id>',
},
reports: {
list: 'reports list',
get: 'reports get --id <id>',
},
threads: {
list: 'threads list',
},
accounts: {
search: 'accounts search --domain <domain>',
},
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+213
View File
@@ -0,0 +1,213 @@
#!/usr/bin/env node
const ACCESS_TOKEN = process.env.OUTREACH_ACCESS_TOKEN
const BASE_URL = 'https://api.outreach.io/api/v2'
if (!ACCESS_TOKEN) {
console.error(JSON.stringify({ error: 'OUTREACH_ACCESS_TOKEN environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
const url = `${BASE_URL}${path}`
if (args['dry-run']) {
return { _dry_run: true, method, url, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json' }, body: body || undefined }
}
const res = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${ACCESS_TOKEN}`,
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json',
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'prospects':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args.page) params.set('page[number]', args.page)
if (args['per-page']) params.set('page[size]', args['per-page'])
const qs = params.toString()
result = await api('GET', `/prospects${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/prospects/${id}`)
break
}
case 'create': {
const email = args.email
if (!email) { result = { error: '--email required' }; break }
const attributes = { emails: [email] }
if (args['first-name']) attributes.firstName = args['first-name']
if (args['last-name']) attributes.lastName = args['last-name']
const body = { data: { type: 'prospect', attributes } }
result = await api('POST', '/prospects', body)
break
}
default:
result = { error: 'Unknown prospects subcommand. Use: list, get, create' }
}
break
case 'sequences':
switch (sub) {
case 'list': {
result = await api('GET', '/sequences')
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/sequences/${id}`)
break
}
default:
result = { error: 'Unknown sequences subcommand. Use: list, get' }
}
break
case 'sequence-states':
switch (sub) {
case 'create': {
const sequenceId = args['sequence-id']
const prospectId = args['prospect-id']
if (!sequenceId) { result = { error: '--sequence-id required' }; break }
if (!prospectId) { result = { error: '--prospect-id required' }; break }
const body = {
data: {
type: 'sequenceState',
relationships: {
prospect: { data: { type: 'prospect', id: prospectId } },
sequence: { data: { type: 'sequence', id: sequenceId } },
},
},
}
result = await api('POST', '/sequenceStates', body)
break
}
default:
result = { error: 'Unknown sequence-states subcommand. Use: create' }
}
break
case 'mailings':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args['sequence-id']) params.set('filter[sequence][id]', args['sequence-id'])
const qs = params.toString()
result = await api('GET', `/mailings${qs ? '?' + qs : ''}`)
break
}
default:
result = { error: 'Unknown mailings subcommand. Use: list' }
}
break
case 'accounts':
switch (sub) {
case 'list': {
result = await api('GET', '/accounts')
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/accounts/${id}`)
break
}
default:
result = { error: 'Unknown accounts subcommand. Use: list, get' }
}
break
case 'tasks':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args.status) params.set('filter[status]', args.status)
const qs = params.toString()
result = await api('GET', `/tasks${qs ? '?' + qs : ''}`)
break
}
default:
result = { error: 'Unknown tasks subcommand. Use: list' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
prospects: {
list: 'prospects list [--page <n>] [--per-page <n>]',
get: 'prospects get --id <id>',
create: 'prospects create --email <email> [--first-name <name>] [--last-name <name>]',
},
sequences: {
list: 'sequences list',
get: 'sequences get --id <id>',
},
'sequence-states': {
create: 'sequence-states create --sequence-id <id> --prospect-id <id>',
},
mailings: {
list: 'mailings list [--sequence-id <id>]',
},
accounts: {
list: 'accounts list',
get: 'accounts get --id <id>',
},
tasks: {
list: 'tasks list [--status <status>]',
},
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+221
View File
@@ -0,0 +1,221 @@
#!/usr/bin/env node
const API_KEY = process.env.PENDO_INTEGRATION_KEY
const BASE_URL = 'https://app.pendo.io/api/v1'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'PENDO_INTEGRATION_KEY environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
const url = `${BASE_URL}${path}`
if (args['dry-run']) {
return { _dry_run: true, method, url, headers: { 'Content-Type': 'application/json', 'x-pendo-integration-key': '***' }, body: body || undefined }
}
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'x-pendo-integration-key': API_KEY,
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'features':
switch (sub) {
case 'list':
result = await api('GET', '/feature')
break
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/feature/${id}`)
break
}
default:
result = { error: 'Unknown features subcommand. Use: list, get' }
}
break
case 'pages':
switch (sub) {
case 'list':
result = await api('GET', '/page')
break
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/page/${id}`)
break
}
default:
result = { error: 'Unknown pages subcommand. Use: list, get' }
}
break
case 'guides':
switch (sub) {
case 'list': {
const params = new URLSearchParams()
if (args.state) params.set('state', args.state)
const qs = params.toString()
result = await api('GET', `/guide${qs ? '?' + qs : ''}`)
break
}
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/guide/${id}`)
break
}
default:
result = { error: 'Unknown guides subcommand. Use: list, get' }
}
break
case 'visitors':
switch (sub) {
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/visitor/${id}`)
break
}
case 'search': {
const query = args.query
if (!query) { result = { error: '--query <json> required' }; break }
let body
try { body = JSON.parse(query) } catch { result = { error: 'Invalid JSON in --query' }; break }
result = await api('POST', '/aggregation', body)
break
}
default:
result = { error: 'Unknown visitors subcommand. Use: get, search' }
}
break
case 'accounts':
switch (sub) {
case 'get': {
const id = args.id
if (!id) { result = { error: '--id required' }; break }
result = await api('GET', `/account/${id}`)
break
}
case 'search': {
const query = args.query
if (!query) { result = { error: '--query <json> required' }; break }
let body
try { body = JSON.parse(query) } catch { result = { error: 'Invalid JSON in --query' }; break }
result = await api('POST', '/aggregation', body)
break
}
default:
result = { error: 'Unknown accounts subcommand. Use: get, search' }
}
break
case 'reports':
switch (sub) {
case 'funnel': {
const pipeline = args.pipeline
if (!pipeline) { result = { error: '--pipeline <json> required' }; break }
let body
try { body = JSON.parse(pipeline) } catch { result = { error: 'Invalid JSON in --pipeline' }; break }
result = await api('POST', '/aggregation', body)
break
}
default:
result = { error: 'Unknown reports subcommand. Use: funnel' }
}
break
case 'metadata':
switch (sub) {
case 'list': {
const kind = args.kind
if (!kind) { result = { error: '--kind <visitor|account|parentAccount> required' }; break }
result = await api('GET', `/metadata/schema/${kind}`)
break
}
default:
result = { error: 'Unknown metadata subcommand. Use: list' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
features: {
list: 'features list',
get: 'features get --id <id>',
},
pages: {
list: 'pages list',
get: 'pages get --id <id>',
},
guides: {
list: 'guides list [--state <state>]',
get: 'guides get --id <id>',
},
visitors: {
get: 'visitors get --id <id>',
search: 'visitors search --query <json>',
},
accounts: {
get: 'accounts get --id <id>',
search: 'accounts search --query <json>',
},
reports: {
funnel: 'reports funnel --pipeline <json>',
},
metadata: {
list: 'metadata list --kind <visitor|account|parentAccount>',
},
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+214
View File
@@ -0,0 +1,214 @@
#!/usr/bin/env node
const API_KEY = process.env.SIMILARWEB_API_KEY
const BASE_URL = 'https://api.similarweb.com/v1'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'SIMILARWEB_API_KEY environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
const separator = path.includes('?') ? '&' : '?'
const url = `${BASE_URL}${path}${separator}api_key=${API_KEY}`
if (args['dry-run']) {
return { _dry_run: true, method, url: url.replace(API_KEY, '***'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined }
}
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'traffic':
switch (sub) {
case 'visits': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
if (args.granularity) params.set('granularity', args.granularity)
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/visits?${params.toString()}`)
break
}
case 'pages-per-visit': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
if (args.granularity) params.set('granularity', args.granularity)
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/pages-per-visit?${params.toString()}`)
break
}
case 'avg-duration': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
if (args.granularity) params.set('granularity', args.granularity)
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/average-visit-duration?${params.toString()}`)
break
}
case 'bounce-rate': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
if (args.granularity) params.set('granularity', args.granularity)
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/bounce-rate?${params.toString()}`)
break
}
case 'sources': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
result = await api('GET', `/website/${encodeURIComponent(domain)}/traffic-sources/overview?${params.toString()}`)
break
}
default:
result = { error: 'Unknown traffic subcommand. Use: visits, pages-per-visit, avg-duration, bounce-rate, sources' }
}
break
case 'referrals': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
result = await api('GET', `/website/${encodeURIComponent(domain)}/traffic-sources/referrals?${params.toString()}`)
break
}
case 'search':
switch (sub) {
case 'keywords-organic': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
if (args.limit) params.set('limit', args.limit)
result = await api('GET', `/website/${encodeURIComponent(domain)}/search/organic-search-keywords?${params.toString()}`)
break
}
case 'keywords-paid': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
if (args.country) params.set('country', args.country)
if (args.limit) params.set('limit', args.limit)
result = await api('GET', `/website/${encodeURIComponent(domain)}/search/paid-search-keywords?${params.toString()}`)
break
}
default:
result = { error: 'Unknown search subcommand. Use: keywords-organic, keywords-paid' }
}
break
case 'competitors': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
result = await api('GET', `/website/${encodeURIComponent(domain)}/similar-sites/similarsites`)
break
}
case 'category-rank': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
result = await api('GET', `/website/${encodeURIComponent(domain)}/category-rank/category-rank`)
break
}
case 'geography': {
const domain = args.domain
if (!domain) { result = { error: '--domain required' }; break }
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
result = await api('GET', `/website/${encodeURIComponent(domain)}/geo/traffic-by-country?${params.toString()}`)
break
}
default:
result = {
error: 'Unknown command',
usage: {
traffic: {
visits: 'traffic visits --domain <domain> --start <YYYY-MM> --end <YYYY-MM> [--country <cc>] [--granularity <monthly|weekly|daily>]',
'pages-per-visit': 'traffic pages-per-visit --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
'avg-duration': 'traffic avg-duration --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
'bounce-rate': 'traffic bounce-rate --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
sources: 'traffic sources --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
},
referrals: 'referrals --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
search: {
'keywords-organic': 'search keywords-organic --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
'keywords-paid': 'search keywords-paid --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
},
competitors: 'competitors --domain <domain>',
'category-rank': 'category-rank --domain <domain>',
geography: 'geography --domain <domain> --start <YYYY-MM> --end <YYYY-MM>',
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env node
const API_KEY = process.env.SUPERMETRICS_API_KEY
const BASE_URL = 'https://api.supermetrics.com/enterprise/v2'
if (!API_KEY) {
console.error(JSON.stringify({ error: 'SUPERMETRICS_API_KEY environment variable required' }))
process.exit(1)
}
async function api(method, path, body) {
const separator = path.includes('?') ? '&' : '?'
const url = `${BASE_URL}${path}${separator}api_key=${API_KEY}`
if (args['dry-run']) {
return { _dry_run: true, method, url: url.replace(API_KEY, '***'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined }
}
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
switch (cmd) {
case 'query': {
const dsId = args['ds-id']
const dsAccounts = args['ds-accounts']
const dateRange = args['date-range']
const fields = args.fields
if (!dsId) { result = { error: '--ds-id required (e.g., GA4, AW, FB, LI, GSC)' }; break }
if (!dsAccounts) { result = { error: '--ds-accounts required' }; break }
if (!dateRange) { result = { error: '--date-range required (e.g., last_28_days, last_month, this_month, custom)' }; break }
if (!fields) { result = { error: '--fields required (comma-separated field names)' }; break }
const body = {
ds_id: dsId,
ds_accounts: dsAccounts,
date_range_type: dateRange,
fields: fields.split(',').map(f => ({ name: f.trim() })),
}
if (args.filter) body.filter = args.filter
if (args['max-rows']) body.max_rows = parseInt(args['max-rows'], 10)
if (args['start-date']) body.start_date = args['start-date']
if (args['end-date']) body.end_date = args['end-date']
result = await api('POST', '/query/data/json', body)
break
}
case 'sources':
switch (sub) {
case 'list':
result = await api('GET', '/datasources')
break
default:
result = { error: 'Unknown sources subcommand. Use: list' }
}
break
case 'accounts':
switch (sub) {
case 'list': {
const dsId = args['ds-id']
if (!dsId) { result = { error: '--ds-id required (e.g., GA4, AW, FB)' }; break }
const params = new URLSearchParams({ ds_id: dsId })
result = await api('GET', `/datasources/accounts?${params.toString()}`)
break
}
default:
result = { error: 'Unknown accounts subcommand. Use: list' }
}
break
case 'teams':
switch (sub) {
case 'list':
result = await api('GET', '/teams')
break
default:
result = { error: 'Unknown teams subcommand. Use: list' }
}
break
case 'users':
switch (sub) {
case 'list':
result = await api('GET', '/users')
break
default:
result = { error: 'Unknown users subcommand. Use: list' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
query: 'query --ds-id <data-source> --ds-accounts <account-id> --date-range <range> --fields <f1,f2> [--filter <filter>] [--max-rows <n>] [--start-date <date>] [--end-date <date>]',
sources: {
list: 'sources list',
},
accounts: {
list: 'accounts list --ds-id <data-source>',
},
teams: {
list: 'teams list',
},
users: {
list: 'users list',
},
'data-source-ids': {
'GA4': 'Google Analytics 4',
'GA4_PAID': 'Google Analytics (paid)',
'AW': 'Google Ads',
'FB': 'Facebook Ads',
'LI': 'LinkedIn Ads',
'TW_ADS': 'Twitter Ads',
'IG_IA': 'Instagram',
'FB_IA': 'Facebook Pages',
'GSC': 'Google Search Console',
'SE': 'Semrush',
'MC': 'Mailchimp',
},
'date-ranges': ['last_28_days', 'last_month', 'this_month', 'custom'],
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+216
View File
@@ -0,0 +1,216 @@
#!/usr/bin/env node
const BASE_URL = 'https://api.zoominfo.com'
let ACCESS_TOKEN = process.env.ZOOMINFO_ACCESS_TOKEN
if (!ACCESS_TOKEN && !process.env.ZOOMINFO_USERNAME) {
console.error(JSON.stringify({ error: 'ZOOMINFO_ACCESS_TOKEN or ZOOMINFO_USERNAME + ZOOMINFO_PRIVATE_KEY environment variables required' }))
process.exit(1)
}
async function authenticate() {
if (ACCESS_TOKEN) return ACCESS_TOKEN
const username = process.env.ZOOMINFO_USERNAME
const password = process.env.ZOOMINFO_PRIVATE_KEY
if (!username || !password) {
throw new Error('ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for authentication')
}
const res = await fetch(`${BASE_URL}/authenticate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
})
const text = await res.text()
if (!res.ok) {
throw new Error(`Authentication failed (${res.status}): ${text}`)
}
try {
const data = JSON.parse(text)
if (!data.jwt) throw new Error('No JWT in response')
ACCESS_TOKEN = data.jwt
return ACCESS_TOKEN
} catch (e) {
if (e.message === 'No JWT in response') throw e
throw new Error(`Authentication failed: ${text}`)
}
}
async function api(method, path, body) {
if (args['dry-run']) {
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ***' }, body }
}
const token = await authenticate()
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: body ? JSON.stringify(body) : undefined,
})
const text = await res.text()
try {
return JSON.parse(text)
} catch {
return { status: res.status, body: text }
}
}
function parseArgs(args) {
const result = { _: [] }
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--')) {
const key = arg.slice(2)
const next = args[i + 1]
if (next && !next.startsWith('--')) {
result[key] = next
i++
} else {
result[key] = true
}
} else {
result._.push(arg)
}
}
return result
}
const args = parseArgs(process.argv.slice(2))
const [cmd, sub, ...rest] = args._
async function main() {
let result
const page = args.page ? Number(args.page) : 1
const perPage = args['per-page'] ? Number(args['per-page']) : 25
switch (cmd) {
case 'auth': {
if (args['dry-run']) {
result = { _dry_run: true, action: 'authenticate', url: `${BASE_URL}/authenticate`, jwt: '***' }
break
}
const token = await authenticate()
result = { jwt: token }
break
}
case 'contacts':
switch (sub) {
case 'search': {
const body = { rpp: perPage, page }
if (args['job-title']) body.jobTitle = [args['job-title']]
if (args.company) body.companyName = [args.company]
if (args.location) body.locationSearchType = 'PersonLocation', body.personLocationCity = [args.location]
if (args.seniority) body.managementLevel = [args.seniority]
if (args.department) body.department = [args.department]
result = await api('POST', '/search/contact', body)
break
}
case 'enrich': {
const body = {}
if (args.email) body.matchEmail = [args.email]
if (args['person-id']) body.personId = [args['person-id']]
if (!args.email && !args['person-id']) {
result = { error: '--email or --person-id required' }
break
}
result = await api('POST', '/enrich/contact', body)
break
}
default:
result = { error: 'Unknown contacts subcommand. Use: search, enrich' }
}
break
case 'companies':
switch (sub) {
case 'search': {
const body = { rpp: perPage, page }
if (args.name) body.companyName = [args.name]
if (args.industry) body.industry = [args.industry]
if (args['revenue-min']) body.revenueMin = Number(args['revenue-min'])
if (args['revenue-max']) body.revenueMax = Number(args['revenue-max'])
if (args['employees-min']) body.employeeCountMin = Number(args['employees-min'])
if (args['employees-max']) body.employeeCountMax = Number(args['employees-max'])
result = await api('POST', '/search/company', body)
break
}
case 'enrich': {
const body = {}
if (args.domain) body.matchCompanyWebsite = [args.domain]
if (args['company-id']) body.companyId = [args['company-id']]
if (!args.domain && !args['company-id']) {
result = { error: '--domain or --company-id required' }
break
}
result = await api('POST', '/enrich/company', body)
break
}
default:
result = { error: 'Unknown companies subcommand. Use: search, enrich' }
}
break
case 'intent':
switch (sub) {
case 'search': {
if (!args.topic) {
result = { error: '--topic required' }
break
}
const body = { topicId: [args.topic] }
if (args['company-id']) body.companyId = [args['company-id']]
result = await api('POST', '/lookup/intent', body)
break
}
default:
result = { error: 'Unknown intent subcommand. Use: search' }
}
break
case 'scoops':
switch (sub) {
case 'search': {
const body = { rpp: perPage, page }
if (args['company-id']) body.companyId = [args['company-id']]
if (args.topic) body.topicId = [args.topic]
result = await api('POST', '/lookup/scoops', body)
break
}
default:
result = { error: 'Unknown scoops subcommand. Use: search' }
}
break
default:
result = {
error: 'Unknown command',
usage: {
auth: 'auth — authenticate and get JWT token',
contacts: {
search: 'contacts search [--job-title <t>] [--company <c>] [--location <l>] [--seniority <s>] [--department <d>] [--page <n>]',
enrich: 'contacts enrich --email <email> | --person-id <id>',
},
companies: {
search: 'companies search [--name <n>] [--industry <i>] [--revenue-min <n>] [--employees-min <n>] [--page <n>]',
enrich: 'companies enrich --domain <domain> | --company-id <id>',
},
intent: {
search: 'intent search --topic <topic> [--company-id <id>]',
},
scoops: {
search: 'scoops search [--company-id <id>] [--topic <topic>] [--page <n>]',
},
}
}
}
console.log(JSON.stringify(result, null, 2))
}
main().catch(err => {
console.error(JSON.stringify({ error: err.message }))
process.exit(1)
})
+128
View File
@@ -0,0 +1,128 @@
# AirOps
AI content platform for crafting content that wins AI search. Build and execute AI workflows (flows) for SEO content generation, data enrichment, and automation.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Flows, Workflows, Runs |
| MCP | - | Not available |
| CLI | ✓ | [airops.js](../clis/airops.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: API Key + Workspace ID
- **Header**: `Authorization: Bearer {api_key}`
- **Env vars**: `AIROPS_API_KEY`, `AIROPS_WORKSPACE_ID`
- **Get key**: Settings > API Keys at https://app.airops.com
## Common Agent Operations
### List Flows
```bash
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows
```
### Get Flow Details
```bash
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}
```
### Execute a Flow
```bash
POST https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}/execute
{
"inputs": {
"keyword": "best project management tools",
"target_audience": "startup founders"
}
}
```
### List Runs for a Flow
```bash
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}/runs
```
### Get Run Status
```bash
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/runs/{run_id}
```
### List Workflows
```bash
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/workflows
```
### Execute a Workflow
```bash
POST https://api.airops.com/public_api/v1/workspaces/{workspace_id}/workflows/{workflow_id}/execute
{
"inputs": {
"topic": "email marketing best practices",
"content_type": "blog_post"
}
}
```
## Key Metrics
### Flow Data
- `id` - Flow identifier
- `name` - Flow name
- `description` - Flow description
- `status` - Active/inactive status
- `created_at` - Creation timestamp
- `updated_at` - Last modified timestamp
### Run Data
- `id` - Run identifier
- `flow_id` - Parent flow ID
- `status` - pending, running, completed, failed
- `inputs` - Input parameters used
- `outputs` - Generated results
- `started_at` - Run start time
- `completed_at` - Run completion time
## Parameters
### Flow Execution
- `inputs` - JSON object of key-value pairs matching the flow's expected inputs
- Input keys vary per flow (e.g., `keyword`, `topic`, `url`, `target_audience`)
### Workflow Execution
- `inputs` - JSON object of key-value pairs matching the workflow's expected inputs
## When to Use
- Bulk content generation for SEO at scale
- SEO-optimized article creation with AI workflows
- Data enrichment pipelines for marketing lists
- Keyword research automation
- Content optimization and rewriting
- Programmatic SEO page generation
- AI-powered content briefs and outlines
## Rate Limits
- Rate limits vary by plan
- Concurrent execution limits depend on workspace tier
- Check AirOps dashboard for current usage and limits
## Relevant Skills
- ai-seo
- content-strategy
- programmatic-seo
- copywriting
+149
View File
@@ -0,0 +1,149 @@
# Clay
Data enrichment and outbound automation platform for building lead lists with waterfall enrichment across 75+ data providers.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Tables, People Enrichment, Company Enrichment |
| MCP | ✓ | [Claude connector](https://claude.com/connectors/clay) |
| CLI | ✓ | [clay.js](../clis/clay.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: API Key (Bearer token)
- **Header**: `Authorization: Bearer {api_key}`
- **Get key**: Settings > API at https://app.clay.com
## Common Agent Operations
### List Tables
```bash
GET https://api.clay.com/v3/tables
Authorization: Bearer {api_key}
```
### Get Table Details
```bash
GET https://api.clay.com/v3/tables/{table_id}
Authorization: Bearer {api_key}
```
### Get Table Rows
```bash
GET https://api.clay.com/v3/tables/{table_id}/rows?page=1&per_page=25
Authorization: Bearer {api_key}
```
### Add Row to Table
```bash
POST https://api.clay.com/v3/tables/{table_id}/rows
{
"first_name": "Jane",
"last_name": "Doe",
"company": "Acme Inc",
"email": "jane@acme.com"
}
```
### People Enrichment
```bash
POST https://api.clay.com/v3/people/enrich
{
"email": "jane@acme.com"
}
```
### Company Enrichment
```bash
POST https://api.clay.com/v3/companies/enrich
{
"domain": "acme.com"
}
```
## Key Metrics
### Person Data
- `first_name`, `last_name` - Name
- `email` - Email address
- `title` - Job title
- `linkedin_url` - LinkedIn profile
- `company` - Company name
- `location` - Location
- `seniority` - Seniority level
### Company Data
- `name` - Company name
- `domain` - Website domain
- `industry` - Industry
- `employee_count` - Number of employees
- `revenue` - Estimated revenue
- `location` - Headquarters location
- `technologies` - Tech stack
- `description` - Company description
### Table Data
- `id` - Table ID
- `name` - Table name
- `row_count` - Number of rows
- `columns` - Column definitions
- `created_at` - Creation timestamp
- `updated_at` - Last update timestamp
## Parameters
### Tables
- `page` - Page number (default: 1)
- `per_page` - Results per page (default: 25)
### People Enrichment
- `email` - Email address
- `linkedin_url` - LinkedIn profile URL
- `first_name` + `last_name` - Name-based lookup
### Company Enrichment
- `domain` - Company domain (e.g., "acme.com")
### Add Row
- Fields are dynamic and match the table's column definitions
- Pass data as key-value pairs matching column names
## When to Use
- Building enriched prospect lists with waterfall enrichment across multiple providers
- Enriching leads with person and company data from 75+ sources
- Automating outbound workflows with enriched data
- Finding verified contact info (emails, phone numbers, social profiles)
- Company research and firmographic analysis
- Triggering enrichment workflows via webhooks
- Syncing enriched data back to CRM or outbound tools
## Rate Limits
- Rate limits vary by plan
- Standard: 100 requests/minute
- Enterprise plans have higher limits
- Enrichment credits consumed per lookup vary by data provider
- Webhook endpoints accept data continuously
## Relevant Skills
- cold-email
- revops
- sales-enablement
- competitor-alternatives
+191
View File
@@ -0,0 +1,191 @@
# Close
Sales CRM for SMBs with built-in calling, email, and pipeline management designed for high-velocity sales teams.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Leads, Contacts, Opportunities, Activities, Tasks |
| MCP | - | Not available |
| CLI | ✓ | [close.js](../clis/close.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: Basic Auth
- **Header**: `Authorization: Basic {base64(api_key + ':')}`
- **Get key**: Settings > API Keys at https://app.close.com
## Common Agent Operations
### List Leads
```bash
GET https://api.close.com/api/v1/lead/
Authorization: Basic {base64(api_key + ':')}
```
### Search Leads
```bash
GET https://api.close.com/api/v1/lead/?query=company_name
Authorization: Basic {base64(api_key + ':')}
```
### Create Lead
```bash
POST https://api.close.com/api/v1/lead/
{
"name": "Acme Corp",
"url": "https://acme.com",
"description": "Enterprise prospect"
}
```
### Get Contact
```bash
GET https://api.close.com/api/v1/contact/{contact_id}/
Authorization: Basic {base64(api_key + ':')}
```
### Create Contact
```bash
POST https://api.close.com/api/v1/contact/
{
"lead_id": "lead_xxx",
"name": "Jane Smith",
"emails": [{ "email": "jane@acme.com", "type": "office" }],
"phones": [{ "phone": "+15551234567", "type": "office" }]
}
```
### Create Opportunity
```bash
POST https://api.close.com/api/v1/opportunity/
{
"lead_id": "lead_xxx",
"value": 50000,
"status_type": "active"
}
```
### List Activities
```bash
GET https://api.close.com/api/v1/activity/?lead_id=lead_xxx
Authorization: Basic {base64(api_key + ':')}
```
### Create Task
```bash
POST https://api.close.com/api/v1/task/
{
"lead_id": "lead_xxx",
"text": "Follow up on demo request",
"_type": "lead",
"date": "2026-03-10"
}
```
## Key Metrics
### Lead Data
- `id` - Lead ID
- `display_name` - Lead name
- `url` - Website URL
- `description` - Lead description
- `status_id` - Pipeline status
- `contacts` - Associated contacts
- `opportunities` - Associated opportunities
- `tasks` - Associated tasks
### Contact Data
- `id` - Contact ID
- `lead_id` - Parent lead
- `name` - Full name
- `title` - Job title
- `emails` - Email addresses array
- `phones` - Phone numbers array
### Opportunity Data
- `id` - Opportunity ID
- `lead_id` - Parent lead
- `value` - Value in cents
- `status_type` - active, won, or lost
- `confidence` - Win probability (0-100)
- `date_won` - Close date
### Task Data
- `id` - Task ID
- `lead_id` - Parent lead
- `text` - Task description
- `assigned_to` - Assigned user ID
- `date` - Due date
- `is_complete` - Completion status
## Parameters
### Leads
- `query` - Search query string
- `_skip` - Number of results to skip (pagination)
- `_limit` - Max results to return (default: 100)
- `_fields` - Comma-separated fields to return
### Contacts
- `lead_id` - Filter by parent lead
- `_skip` - Pagination offset
- `_limit` - Max results
### Opportunities
- `lead_id` - Filter by parent lead
- `status` - Filter by status type (active, won, lost)
- `_skip` - Pagination offset
- `_limit` - Max results
### Activities
- `lead_id` - Filter by lead
- `_type__type` - Filter by type (Email, Call, Note, SMS, Meeting)
- `date_created__gt` - After date
- `date_created__lt` - Before date
### Tasks
- `assigned_to` - Filter by user ID
- `is_complete` - Filter by completion (true/false)
- `lead_id` - Filter by lead
- `_type` - Task type (lead)
## When to Use
- Managing SMB sales pipelines with high-touch outreach
- Tracking sales activities (calls, emails, meetings) per lead
- Creating and managing tasks for sales follow-ups
- Opportunity tracking and revenue forecasting
- Building automated outreach workflows
- Sales team performance reporting
## Rate Limits
- Rate limits based on organization plan
- Standard: ~100 requests/minute
- Responses include `ratelimit-limit` and `ratelimit-remaining` headers
- 429 responses include `Retry-After` header
## Relevant Skills
- revops
- sales-enablement
- cold-email
+142
View File
@@ -0,0 +1,142 @@
# Coupler.io
Data integration platform that connects marketing, sales, analytics, and e-commerce data sources to destinations like spreadsheets, BI tools, and data warehouses with automated scheduling.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Importers, Runs, Sources, Destinations |
| MCP | ✓ | [Claude connector](https://claude.com/connectors/coupler-io) |
| CLI | ✓ | [coupler.js](../clis/coupler.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: API Key
- **Header**: `Authorization: Bearer {api_key}`
- **Get key**: Settings > API at https://app.coupler.io
## Common Agent Operations
### List Importers
```bash
GET https://api.coupler.io/v1/importers
```
### Get Importer Details
```bash
GET https://api.coupler.io/v1/importers/{id}
```
### Trigger an Importer Run
```bash
POST https://api.coupler.io/v1/importers/{id}/run
```
### Create an Importer
```bash
POST https://api.coupler.io/v1/importers
{
"source_type": "google_analytics",
"destination_type": "google_sheets",
"name": "GA4 to Sheets Daily"
}
```
### Delete an Importer
```bash
DELETE https://api.coupler.io/v1/importers/{id}
```
### List Runs for an Importer
```bash
GET https://api.coupler.io/v1/importers/{id}/runs
```
### Get Run Details
```bash
GET https://api.coupler.io/v1/runs/{id}
```
### List Available Sources
```bash
GET https://api.coupler.io/v1/sources
```
### List Available Destinations
```bash
GET https://api.coupler.io/v1/destinations
```
## Key Metrics
### Importer Data
- `id` - Importer ID
- `name` - Importer name
- `source_type` - Source connector type
- `destination_type` - Destination connector type
- `schedule` - Automation schedule
- `status` - Current status
- `last_run_at` - Last run timestamp
### Run Data
- `id` - Run ID
- `importer_id` - Parent importer
- `status` - Run status (pending, running, completed, failed)
- `started_at` - Start timestamp
- `finished_at` - Finish timestamp
- `rows_imported` - Number of rows processed
- `error` - Error message if failed
## Parameters
### Importer Creation
- `source_type` - Source connector (e.g., google_analytics, google_ads, facebook_ads, hubspot, shopify, stripe, airtable)
- `destination_type` - Destination connector (e.g., google_sheets, bigquery, snowflake, postgresql)
- `name` - Importer name
- `schedule` - Automation schedule (e.g., hourly, daily, weekly)
### Supported Sources
- **Analytics**: Google Analytics, Adobe Analytics
- **Ads**: Google Ads, Facebook Ads, LinkedIn Ads, TikTok Ads
- **CRM**: HubSpot, Salesforce, Pipedrive
- **E-commerce**: Shopify, Stripe, WooCommerce
- **Other**: Airtable, Google Sheets, BigQuery, MySQL, PostgreSQL
### Supported Destinations
- **Spreadsheets**: Google Sheets, Excel Online
- **BI Tools**: Looker Studio, Power BI, Tableau
- **Data Warehouses**: BigQuery, Snowflake, Redshift
- **Databases**: PostgreSQL, MySQL
## When to Use
- Automating marketing data pipelines from ads and analytics platforms
- Consolidating multi-channel campaign data into a single destination
- Scheduling recurring data syncs from CRM to spreadsheets or BI tools
- Building marketing dashboards with fresh data from multiple sources
- Exporting e-commerce data for reporting and analysis
- Connecting data sources without writing custom ETL code
## Rate Limits
- Rate limits vary by plan
- Standard: API access available on Professional and higher plans
- Importer run frequency depends on plan tier
## Relevant Skills
- analytics-tracking
- paid-ads
- revops
+137
View File
@@ -0,0 +1,137 @@
# Crossbeam
Partner ecosystem platform (now part of Reveal) for sharing account data with partners to identify co-sell opportunities, overlapping customers, and partner-sourced pipeline.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Partners, Populations, Overlaps, Reports, Threads |
| MCP | ✓ | [Claude connector](https://claude.com/connectors/crossbeam) |
| CLI | ✓ | [crossbeam.js](../clis/crossbeam.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: API Key
- **Header**: `Authorization: Bearer {api_key}`
- **Get key**: Settings > API at https://app.crossbeam.com
## Common Agent Operations
### List Partners
```bash
GET https://api.crossbeam.com/v1/partners
Authorization: Bearer {api_key}
```
### Get Partner Details
```bash
GET https://api.crossbeam.com/v1/partners/{id}
Authorization: Bearer {api_key}
```
### List Populations
```bash
GET https://api.crossbeam.com/v1/populations
Authorization: Bearer {api_key}
```
### List Overlaps
```bash
GET https://api.crossbeam.com/v1/overlaps?partner_id={partner_id}&population_id={population_id}
Authorization: Bearer {api_key}
```
### Get Overlap Details
```bash
GET https://api.crossbeam.com/v1/overlaps/{id}
Authorization: Bearer {api_key}
```
### Search Accounts
```bash
GET https://api.crossbeam.com/v1/accounts/search?domain={domain}
Authorization: Bearer {api_key}
```
### List Reports
```bash
GET https://api.crossbeam.com/v1/reports
Authorization: Bearer {api_key}
```
### List Collaboration Threads
```bash
GET https://api.crossbeam.com/v1/threads
Authorization: Bearer {api_key}
```
## Key Metrics
### Partner Data
- `id` - Partner ID
- `name` - Partner company name
- `status` - Partnership status (active, pending, etc.)
- `created_at` - When the partnership was established
- `populations_shared` - Number of shared populations
### Population Data
- `id` - Population ID
- `name` - Population name (e.g., "Customers", "Open Opportunities")
- `record_count` - Number of records in population
- `partner_visibility` - What partners can see
### Overlap Data
- `id` - Overlap ID
- `partner_id` - Partner involved
- `population_id` - Population matched
- `account_name` - Overlapping account name
- `overlap_type` - Type of overlap (customer, prospect, etc.)
- `match_confidence` - Match confidence score
### Report Data
- `id` - Report ID
- `name` - Report name
- `type` - Report type
- `created_at` - Creation date
- `results` - Report results data
## Parameters
### Overlaps List
- `partner_id` - Filter by specific partner
- `population_id` - Filter by specific population
### Accounts Search
- `domain` - Company domain to search for
## When to Use
- Identifying co-sell opportunities with channel partners
- Finding overlapping customers and prospects across partner ecosystems
- Building partner-sourced pipeline by matching accounts
- Tracking partner influence on deals
- Creating account mapping reports for partner meetings
- Prioritizing which partners to engage based on overlap data
## Rate Limits
- Rate limits vary by plan
- Standard: 100 requests/minute
- Pagination supported on list endpoints
## Relevant Skills
- revops
- sales-enablement
- referral-program
- competitor-alternatives
+172
View File
@@ -0,0 +1,172 @@
# Outreach
Sales engagement platform for managing prospects, sequences, and outbound campaigns at scale.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Prospects, Sequences, Mailings, Accounts, Tasks |
| MCP | ✓ | [Claude connector](https://claude.com/connectors/outreach) |
| CLI | ✓ | [outreach.js](../clis/outreach.js) |
| SDK | - | REST API only (JSON:API format) |
## Authentication
- **Type**: OAuth2 Bearer Token
- **Header**: `Authorization: Bearer {access_token}`
- **Content-Type**: `application/vnd.api+json`
- **Get token**: Settings > API at https://app.outreach.io or via OAuth2 flow
## Common Agent Operations
### List Prospects
```bash
curl -s https://api.outreach.io/api/v2/prospects \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json"
```
### Get a Prospect
```bash
curl -s https://api.outreach.io/api/v2/prospects/42 \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json"
```
### Create a Prospect
```bash
curl -s -X POST https://api.outreach.io/api/v2/prospects \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{
"data": {
"type": "prospect",
"attributes": {
"emails": ["jane@example.com"],
"firstName": "Jane",
"lastName": "Doe"
}
}
}'
```
### List Sequences
```bash
curl -s https://api.outreach.io/api/v2/sequences \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json"
```
### Add Prospect to Sequence
```bash
curl -s -X POST https://api.outreach.io/api/v2/sequenceStates \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{
"data": {
"type": "sequenceState",
"relationships": {
"prospect": { "data": { "type": "prospect", "id": 42 } },
"sequence": { "data": { "type": "sequence", "id": 7 } }
}
}
}'
```
### List Mailings for a Sequence
```bash
curl -s "https://api.outreach.io/api/v2/mailings?filter[sequence][id]=7" \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json"
```
### List Accounts
```bash
curl -s https://api.outreach.io/api/v2/accounts \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json"
```
### List Tasks
```bash
curl -s "https://api.outreach.io/api/v2/tasks?filter[status]=incomplete" \
-H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \
-H "Content-Type: application/vnd.api+json"
```
## Key Metrics
### Prospect Data
- `firstName`, `lastName` - Name
- `emails` - Email addresses
- `title` - Job title
- `company` - Company name
- `tags` - Prospect tags
- `engagedAt` - Last engagement timestamp
### Sequence Data
- `name` - Sequence name
- `enabled` - Whether sequence is active
- `sequenceType` - Type (e.g., interval, date-based)
- `stepCount` - Number of steps
- `openCount`, `clickCount`, `replyCount` - Engagement metrics
### Mailing Data
- `mailingType` - Type of mailing
- `state` - Delivery state
- `openCount`, `clickCount` - Engagement
- `deliveredAt`, `openedAt`, `clickedAt` - Timestamps
## Parameters
### Prospects
- `page[number]` - Page number (default: 1)
- `page[size]` - Results per page (default: 25, max: 1000)
- `filter[emails]` - Filter by email
- `filter[firstName]` - Filter by first name
- `filter[lastName]` - Filter by last name
- `sort` - Sort field (e.g., `createdAt`, `-updatedAt`)
### Sequences
- `filter[name]` - Filter by sequence name
- `filter[enabled]` - Filter by active status
### Mailings
- `filter[sequence][id]` - Filter by sequence ID
- `filter[prospect][id]` - Filter by prospect ID
### Tasks
- `filter[status]` - Filter by status (e.g., `incomplete`, `complete`)
- `filter[taskType]` - Filter by type (e.g., `call`, `email`, `action_item`)
## When to Use
- Managing outbound sales sequences and cadences
- Adding prospects to automated email sequences
- Tracking prospect engagement across touchpoints
- Managing sales tasks and follow-ups
- Coordinating multi-channel outreach campaigns
- Monitoring sequence performance and reply rates
## Rate Limits
- 10,000 requests per hour per user
- Burst limit: 100 requests per 10 seconds
- Rate limit headers returned: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
- 429 responses when limits exceeded
## Relevant Skills
- cold-email
- revops
- sales-enablement
- email-sequence
+208
View File
@@ -0,0 +1,208 @@
# Pendo
Product analytics and in-app guidance platform for tracking user behavior, measuring feature adoption, and delivering targeted in-app messages.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Features, Pages, Guides, Visitors, Accounts, Reports, Metadata |
| MCP | - | Not available |
| CLI | ✓ | [pendo.js](../clis/pendo.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: Integration Key
- **Header**: `x-pendo-integration-key: {key}`
- **Get key**: Settings > Integrations at https://app.pendo.io
## Common Agent Operations
### List Features
```bash
GET https://app.pendo.io/api/v1/feature
```
### Get Feature Details
```bash
GET https://app.pendo.io/api/v1/feature/{featureId}
```
### List Pages
```bash
GET https://app.pendo.io/api/v1/page
```
### Get Page Details
```bash
GET https://app.pendo.io/api/v1/page/{pageId}
```
### List Guides
```bash
GET https://app.pendo.io/api/v1/guide?state=public
```
### Get Guide Details
```bash
GET https://app.pendo.io/api/v1/guide/{guideId}
```
### Get Visitor Data
```bash
GET https://app.pendo.io/api/v1/visitor/{visitorId}
```
### Search Visitors
```bash
POST https://app.pendo.io/api/v1/aggregation
{
"response": { "mimeType": "application/json" },
"request": {
"pipeline": [
{ "source": { "visitors": null } },
{ "filter": "lastVisitedAt > 1700000000000" }
]
}
}
```
### Get Account Data
```bash
GET https://app.pendo.io/api/v1/account/{accountId}
```
### Search Accounts
```bash
POST https://app.pendo.io/api/v1/aggregation
{
"response": { "mimeType": "application/json" },
"request": {
"pipeline": [
{ "source": { "accounts": null } },
{ "filter": "metadata.auto.lastupdated > 1700000000000" }
]
}
}
```
### Run Funnel Report
```bash
POST https://app.pendo.io/api/v1/aggregation
{
"response": { "mimeType": "application/json" },
"request": {
"pipeline": [
{ "source": { "visitors": null, "timeSeries": { "period": "dayRange", "first": 1700000000000, "last": 1700600000000 } } },
{ "identified": "visitorId" },
{ "filter": "pageId == \"page-id-1\"" },
{ "filter": "pageId == \"page-id-2\"" }
]
}
}
```
### List Metadata Fields
```bash
GET https://app.pendo.io/api/v1/metadata/schema/visitor
GET https://app.pendo.io/api/v1/metadata/schema/account
GET https://app.pendo.io/api/v1/metadata/schema/parentAccount
```
## Key Metrics
### Feature Data
- `id` - Feature ID
- `name` - Feature name
- `kind` - Feature type
- `elementPath` - CSS selector for the tracked element
- `pageId` - Associated page ID
- `numEvents` - Event count
- `numVisitors` - Unique visitor count
### Page Data
- `id` - Page ID
- `name` - Page name
- `rules` - URL matching rules
- `numEvents` - Pageview count
- `numVisitors` - Unique visitor count
### Guide Data
- `id` - Guide ID
- `name` - Guide name
- `state` - Guide state (draft, staged, public, disabled)
- `launchMethod` - How the guide is triggered
- `steps` - Guide step definitions
- `numSteps` - Number of steps
- `numViews` - Total views
- `numVisitors` - Unique visitors who saw the guide
### Visitor Data
- `visitorId` - Unique visitor identifier
- `lastVisitedAt` - Last visit timestamp
- `firstVisit` - First visit timestamp
- `numEvents` - Total event count
- `metadata` - Custom visitor metadata
### Account Data
- `accountId` - Unique account identifier
- `lastVisitedAt` - Last visit from any account member
- `numVisitors` - Number of visitors in the account
- `metadata` - Custom account metadata
## Parameters
### Guide Filtering
- `state` - Filter by state: draft, staged, public, disabled
### Aggregation Queries
- `source` - Data source: visitors, accounts, features, pages, guides
- `filter` - Expression-based filtering
- `sort` - Sort results
- `limit` - Max results to return
- `timeSeries` - Time range with period, first, last
### Metadata Kinds
- `visitor` - Visitor metadata schema
- `account` - Account metadata schema
- `parentAccount` - Parent account metadata schema
## When to Use
- Tracking feature adoption and usage patterns
- Building and managing in-app onboarding guides
- Analyzing user behavior across pages and features
- Segmenting users by engagement level
- Running funnel analysis on user journeys
- Identifying at-risk accounts based on usage decline
- A/B testing in-app messages and tooltips
## Rate Limits
- Rate limits vary by plan
- Standard: 500 requests per minute
- Aggregation queries: may take longer for large datasets
- Use pagination for large result sets
## Relevant Skills
- analytics-tracking
- onboarding-cro
- churn-prevention
- ab-test-setup
+150
View File
@@ -0,0 +1,150 @@
# Similarweb
Competitive traffic intelligence platform providing website analytics, traffic sources, keyword data, and competitor insights.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Traffic, Search, Referrals, Competitors, Geography |
| MCP | - | Not available |
| CLI | ✓ | [similarweb.js](../clis/similarweb.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: API Key
- **Query param**: `?api_key={key}`
- **Get key**: Account Settings > API at https://account.similarweb.com
## Common Agent Operations
### Total Visits
```bash
GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/visits?api_key={key}&start_date=2024-01&end_date=2024-03&country=us&granularity=monthly
```
### Pages Per Visit
```bash
GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/pages-per-visit?api_key={key}&start_date=2024-01&end_date=2024-03
```
### Average Visit Duration
```bash
GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/average-visit-duration?api_key={key}&start_date=2024-01&end_date=2024-03
```
### Bounce Rate
```bash
GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/bounce-rate?api_key={key}&start_date=2024-01&end_date=2024-03
```
### Traffic Sources Breakdown
```bash
GET https://api.similarweb.com/v1/website/example.com/traffic-sources/overview?api_key={key}&start_date=2024-01&end_date=2024-03
```
### Top Referral Sites
```bash
GET https://api.similarweb.com/v1/website/example.com/traffic-sources/referrals?api_key={key}&start_date=2024-01&end_date=2024-03
```
### Organic Keywords
```bash
GET https://api.similarweb.com/v1/website/example.com/search/organic-search-keywords?api_key={key}&start_date=2024-01&end_date=2024-03
```
### Paid Keywords
```bash
GET https://api.similarweb.com/v1/website/example.com/search/paid-search-keywords?api_key={key}&start_date=2024-01&end_date=2024-03
```
### Similar Sites / Competitors
```bash
GET https://api.similarweb.com/v1/website/example.com/similar-sites/similarsites?api_key={key}
```
### Category Ranking
```bash
GET https://api.similarweb.com/v1/website/example.com/category-rank/category-rank?api_key={key}
```
### Traffic by Country
```bash
GET https://api.similarweb.com/v1/website/example.com/geo/traffic-by-country?api_key={key}&start_date=2024-01&end_date=2024-03
```
## Key Metrics
### Traffic & Engagement
- `visits` - Total visits for the period
- `pages_per_visit` - Average pages viewed per visit
- `average_visit_duration` - Average session duration in seconds
- `bounce_rate` - Percentage of single-page visits
### Traffic Sources
- `search` - Organic + paid search percentage
- `social` - Social media traffic percentage
- `direct` - Direct traffic percentage
- `referrals` - Referral traffic percentage
- `mail` - Email traffic percentage
- `display_ads` - Display advertising percentage
### Search Keywords
- `search_term` - Keyword text
- `share` - Traffic share percentage
- `volume` - Search volume
- `cpc` - Cost per click
- `position` - Average ranking position
### Geography
- `country` - Country code
- `share` - Traffic share from that country
## Parameters
### Common Parameters
- `start_date` - Start month (YYYY-MM format)
- `end_date` - End month (YYYY-MM format)
- `country` - Two-letter country code (e.g., us, gb, de)
- `granularity` - Data granularity: monthly, weekly, daily
### Search Parameters
- `limit` - Number of keywords to return
- `country` - Filter by country
## When to Use
- Analyzing competitor website traffic and engagement metrics
- Benchmarking your site against competitors
- Identifying top traffic sources for any website
- Discovering competitor organic and paid keywords
- Finding similar sites and competitive landscape
- Understanding geographic traffic distribution
- Auditing SEO performance relative to competitors
- Researching market share by traffic volume
## Rate Limits
- Rate limits vary by plan tier
- Standard: 10 requests/second
- Data availability depends on plan (3 months to 36 months historical)
- Some endpoints require Premium or Enterprise plans
## Relevant Skills
- seo-audit
- competitor-alternatives
- paid-ads
- content-strategy
+145
View File
@@ -0,0 +1,145 @@
# Supermetrics
Marketing data pipeline that connects 200+ marketing platforms. Pulls data from ad platforms, analytics, social, SEO, email, and more into a single query interface.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Query any connected data source, manage accounts |
| MCP | ✓ | [Claude connector](https://claude.com/connectors/supermetrics) |
| CLI | ✓ | [supermetrics.js](../clis/supermetrics.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: API Key
- **Query param**: `api_key={api_key}` or **Header**: `x-api-key: {api_key}`
- **Get key**: Supermetrics Hub > API settings at https://hub.supermetrics.com
## Common Agent Operations
### Query a Data Source
```bash
POST https://api.supermetrics.com/enterprise/v2/query/data/json
{
"ds_id": "GA4",
"ds_accounts": "123456789",
"date_range_type": "last_28_days",
"fields": [
{ "name": "sessions" },
{ "name": "pageviews" },
{ "name": "date" }
]
}
```
### Query with Filters
```bash
POST https://api.supermetrics.com/enterprise/v2/query/data/json
{
"ds_id": "AW",
"ds_accounts": "123-456-7890",
"date_range_type": "last_month",
"fields": [
{ "name": "campaign_name" },
{ "name": "clicks" },
{ "name": "impressions" },
{ "name": "cost" }
],
"max_rows": 100
}
```
### List Available Data Sources
```bash
GET https://api.supermetrics.com/enterprise/v2/datasources
```
### List Connected Accounts
```bash
GET https://api.supermetrics.com/enterprise/v2/datasources/accounts?ds_id=GA4
```
### List Teams
```bash
GET https://api.supermetrics.com/enterprise/v2/teams
```
### List Users
```bash
GET https://api.supermetrics.com/enterprise/v2/users
```
## Key Metrics
### Data Source IDs
- `GA4` - Google Analytics 4
- `GA4_PAID` - Google Analytics (paid)
- `AW` - Google Ads
- `FB` - Facebook Ads
- `LI` - LinkedIn Ads
- `TW_ADS` - Twitter Ads
- `IG_IA` - Instagram
- `FB_IA` - Facebook Pages
- `GSC` - Google Search Console
- `SE` - Semrush
- `MC` - Mailchimp
- `HubSpot` - HubSpot
### Date Range Values
- `last_28_days` - Last 28 days
- `last_month` - Previous calendar month
- `this_month` - Current month to date
- `custom` - Custom range (requires `start_date` and `end_date`)
## Parameters
### Query
- `ds_id` - Data source identifier (required)
- `ds_accounts` - Account ID for the data source (required)
- `date_range_type` - Date range preset or "custom" (required)
- `fields` - Array of field objects with `name` property (required)
- `filter` - Filter expression for narrowing results
- `max_rows` - Maximum number of rows to return
- `start_date` - Start date for custom range (YYYY-MM-DD)
- `end_date` - End date for custom range (YYYY-MM-DD)
### Common Fields by Source
- **GA4**: `sessions`, `pageviews`, `users`, `bounce_rate`, `date`, `source`, `medium`, `page_path`
- **Google Ads**: `campaign_name`, `clicks`, `impressions`, `cost`, `conversions`, `ctr`, `cpc`
- **Facebook Ads**: `campaign_name`, `impressions`, `clicks`, `spend`, `reach`, `cpm`, `cpc`
- **LinkedIn Ads**: `campaign_name`, `impressions`, `clicks`, `cost`, `conversions`
- **GSC**: `query`, `clicks`, `impressions`, `ctr`, `position`, `page`
## When to Use
- Pulling cross-platform marketing data into a single report
- Comparing performance across ad platforms (Google, Meta, LinkedIn, TikTok)
- Aggregating analytics data from multiple sources
- Automating marketing reporting workflows
- Building unified dashboards across marketing channels
- Extracting SEO data alongside paid media metrics
## Rate Limits
- Rate limits vary by plan
- Enterprise API: typically 100 requests/minute
- Query results may be paginated for large datasets
- Recommended: use `max_rows` to control response size
## Relevant Skills
- analytics-tracking
- paid-ads
- seo-audit
- content-strategy
- social-content
+191
View File
@@ -0,0 +1,191 @@
# ZoomInfo
B2B contact database and intent data platform with 100M+ business contacts and company intelligence for sales and marketing teams.
## Capabilities
| Integration | Available | Notes |
|-------------|-----------|-------|
| API | ✓ | Contact Search, Company Search, Enrichment, Intent Data, Scoops |
| MCP | ✓ | [Claude connector](https://claude.com/connectors/zoominfo) |
| CLI | ✓ | [zoominfo.js](../clis/zoominfo.js) |
| SDK | - | REST API only |
## Authentication
- **Type**: JWT Token (Bearer)
- **Flow**: POST `/authenticate` with username + password, receive JWT token
- **Header**: `Authorization: Bearer {jwt_token}`
- **Env vars**: `ZOOMINFO_USERNAME` + `ZOOMINFO_PRIVATE_KEY` or `ZOOMINFO_ACCESS_TOKEN`
- **Get credentials**: Contact ZoomInfo sales or admin portal at https://app.zoominfo.com
## Common Agent Operations
### Authenticate
```bash
POST https://api.zoominfo.com/authenticate
{
"username": "user@company.com",
"password": "private-key-here"
}
```
### Contact Search
```bash
POST https://api.zoominfo.com/search/contact
{
"jobTitle": ["VP Marketing"],
"companyName": ["Acme Corp"],
"managementLevel": ["VP"],
"rpp": 25,
"page": 1
}
```
### Contact Enrichment
```bash
POST https://api.zoominfo.com/enrich/contact
{
"matchEmail": ["jane@acme.com"]
}
```
### Company Search
```bash
POST https://api.zoominfo.com/search/company
{
"companyName": ["Acme"],
"industry": ["Software"],
"employeeCountMin": 50,
"revenueMin": 10000000,
"rpp": 25,
"page": 1
}
```
### Company Enrichment
```bash
POST https://api.zoominfo.com/enrich/company
{
"matchCompanyWebsite": ["acme.com"]
}
```
### Intent Data Lookup
```bash
POST https://api.zoominfo.com/lookup/intent
{
"topicId": ["marketing-automation"],
"companyId": ["123456"]
}
```
### Scoops Lookup
```bash
POST https://api.zoominfo.com/lookup/scoops
{
"companyId": ["123456"],
"rpp": 25,
"page": 1
}
```
## Key Metrics
### Contact Data
- `firstName`, `lastName` - Name
- `jobTitle` - Job title
- `email` - Verified email
- `phone` - Direct phone
- `linkedinUrl` - LinkedIn profile
- `companyName` - Company name
- `managementLevel` - Seniority level
- `department` - Department
### Company Data
- `companyName` - Company name
- `website` - Website URL
- `employeeCount` - Employee count
- `industry` - Industry
- `revenue` - Annual revenue
- `techStack` - Technologies used
- `fundingAmount` - Total funding
- `companyCity`, `companyState`, `companyCountry` - Location
### Intent Data
- `topicName` - Intent topic
- `signalScore` - Signal strength
- `audienceStrength` - Audience engagement level
- `firstSeenDate`, `lastSeenDate` - Signal timeframe
## Parameters
### Contact Search
- `jobTitle` - Array of job titles
- `companyName` - Array of company names
- `managementLevel` - Array: C-Level, VP, Director, Manager, Staff
- `department` - Array: Marketing, Sales, Engineering, Finance, etc.
- `personLocationCity` - Array of cities
- `personLocationState` - Array of states
- `personLocationCountry` - Array of countries
- `rpp` - Results per page (default: 25, max: 100)
- `page` - Page number (default: 1)
### Contact Enrichment
- `matchEmail` - Array of email addresses
- `personId` - Array of ZoomInfo person IDs
- `matchFirstName` + `matchLastName` + `matchCompanyName` - Alternative lookup
### Company Search
- `companyName` - Array of company names
- `industry` - Array of industries
- `employeeCountMin` / `employeeCountMax` - Employee count range
- `revenueMin` / `revenueMax` - Revenue range
- `companyLocationCity` - Array of cities
- `rpp` - Results per page
- `page` - Page number
### Company Enrichment
- `matchCompanyWebsite` - Array of domains
- `companyId` - Array of ZoomInfo company IDs
### Intent Data
- `topicId` - Array of intent topic IDs
- `companyId` - Array of company IDs
## When to Use
- Identifying in-market accounts with intent signals
- Building targeted contact lists by role, seniority, and company
- Enriching leads with verified contact data and firmographics
- Finding decision-makers at target accounts for ABM
- Tracking company news and leadership changes via scoops
- Prioritizing outreach based on buyer intent signals
## Rate Limits
- Rate limits vary by plan and endpoint
- Standard: ~200 requests/minute
- Bulk endpoints: batched requests recommended
- Authentication tokens expire after ~12 hours
## Relevant Skills
- cold-email
- revops
- sales-enablement
- competitor-alternatives