Usage
Quick answer
- 01What is it?
- CRM integration patterns for Close CRM, HubSpot, and Salesforce. Use when: Close CRM, HubSpot, Salesforce, CRM API, lead sync, deal sync, activity logging, CRM webhook, pipeline automation, contact enrichment. Its edge is a particular angle on data and source management, giving the agent tighter constraints than a plain usage request.
- 02Inputs
- Context for data and source management: your goals, audience, constraints, and any source material the skill asks for.
- 03Output
- A ready-to-use result for data and source management: the analysis, copy, or recommendations the agent produces.
Add this skill
Install as a package
Installs this one skill package for your coding agent, including any supporting files that skill ships with — not every skill in the repository. Read the tutorial.
$ npx skills add manojbajaj95/claude-gtm-plugin --skill crm-integrationSkill instructions
The instruction file for this skill. The skill also includes other files you need to install to use it.
- Close CRM - Daily driver for SMB sales (simplest API, best value)
- HubSpot - Marketing + Sales alignment with rich ecosystem
- Salesforce - Enterprise requirements and complex workflows
- Cross-CRM Sync - Bidirectional sync with conflict resolution
Key deliverables:
- API client setup with proper authentication
- CRUD operations for leads, contacts, deals, activities
- Webhook handlers for real-time sync
- Pipeline automation and reporting </objective>
Workspace Context
Read bootstrap context before asking questions: strategy/brand.md for brand, audience, offer, channels, tools, constraints, and metrics; about/me.md for personal voice; content/ideas.md and content/calendar.md for content planning. Use legacy product-marketing context files only as fallback. Save generated drafts to content/<platform>/drafts/YYYY-MM-DD_short-topic-slug.md, and route durable learnings back to strategy/brand.md, about/me.md, or content/ideas.md.
Operating Contract
This skill is self-contained for its frontmatter scope: use its local instructions, references, scripts, and assets as the playbook; ask only for missing task-specific inputs; hand off to adjacent skills instead of expanding scope; and return an actionable artifact, decision, plan, draft, or diagnostic.
<quick_start> Close CRM (API Key Auth):
import httpx
class CloseClient:
BASE_URL = "https://api.close.com/api/v1"
def __init__(self, api_key: str):
self.client = httpx.Client(
base_url=self.BASE_URL,
auth=(api_key, ""), # Basic auth, password empty
timeout=30.0,
)
def create_lead(self, data: dict) -> dict:
response = self.client.post("/lead/", json=data)
response.raise_for_status()
return response.json()
def search_leads(self, query: str) -> list:
response = self.client.post("/data/search/", json={
"query": {"type": "query_string", "value": query},
"results_limit": 100
})
return response.json()["data"]
# Usage
close = CloseClient(os.environ["CLOSE_API_KEY"])
leads = close.search_leads("company:Coperniq")
HubSpot (Python SDK):
from hubspot import HubSpot
from hubspot.crm.contacts import SimplePublicObjectInputForCreate
client = HubSpot(access_token=os.environ["HUBSPOT_ACCESS_TOKEN"])
# Create contact
contact = client.crm.contacts.basic_api.create(
SimplePublicObjectInputForCreate(properties={
"email": "user@example.com",
"firstname": "Jane",
"lastname": "Smith"
})
)
print(f"Created: {contact.id}")
Salesforce (JWT Bearer):
import jwt
from datetime import datetime, timedelta
class SalesforceClient:
def __init__(self, client_id: str, username: str, private_key: str):
self.auth_url = "https://login.salesforce.com"
self._authenticate(client_id, username, private_key)
def _authenticate(self, client_id, username, private_key):
payload = {
"iss": client_id,
"sub": username,
"aud": self.auth_url,
"exp": int((datetime.utcnow() + timedelta(minutes=3)).timestamp())
}
assertion = jwt.encode(payload, private_key, algorithm="RS256")
response = httpx.post(f"{self.auth_url}/services/oauth2/token", data={
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion
})
self.access_token = response.json()["access_token"]
self.instance_url = response.json()["instance_url"]
</quick_start>
<success_criteria> A CRM integration is successful when:
- API authentication works without errors
- CRUD operations complete for all entity types
- Rate limits are respected (Close: 100 req/10s, HubSpot: varies by tier)
- Webhooks fire and process correctly
- Data syncs bidirectionally without duplicates </success_criteria>
<crm_comparison>
Platform Comparison
| Feature | Close | HubSpot | Salesforce |
|---|---|---|---|
| Auth | API Key | OAuth 2.0 / Private App | JWT Bearer |
| Rate Limit | 100 req/10s | 100-200 req/10s by tier | 100k req/day |
| Best For | SMB sales, simplicity | Marketing + Sales | Enterprise |
| Starting Price | $49/user/mo | Free (limited) | $25/user/mo |
| API Access | All plans | Starter+ ($45+) | All plans |
| Webhooks | All plans | Pro+ ($800+) | All plans |
Entity Mapping
| Concept | Close | HubSpot | Salesforce |
|---|---|---|---|
| Company | lead | company | Account |
| Person | contact | contact | Contact / Lead |
| Deal | opportunity | deal | Opportunity |
| Activity | activity | engagement | Task / Event |
| Custom Field | custom.cf_xxx | properties | Field__c |
Pipeline Stage Mapping
| Stage | Close | HubSpot | Salesforce |
|---|---|---|---|
| New | Lead | appointmentscheduled | Prospecting |
| Qualified | Contacted | qualifiedtobuy | Qualification |
| Demo | Opportunity | presentationscheduled | Needs Analysis |
| Proposal | Proposal | decisionmakerboughtin | Proposal/Price Quote |
| Won | Won | closedwon | Closed Won |
| Lost | Lost | closedlost | Closed Lost |
| </crm_comparison> |
<close_patterns>
Close CRM (Daily Driver)
Query Language (for Smart Views)
# Leads with no activity in 30 days
'sort:date_updated asc date_updated < "30 days ago"'
# High-value opportunities
'opportunities.value >= 50000 opportunities.status_type:active'
# Custom field filtering
'custom.cf_industry = "MEP Contractor"'
# Multiple trade types (your ICP)
'custom.cf_trades:HVAC OR custom.cf_trades:Electrical'
Core Operations
# Create lead with contacts
lead = close.create_lead({
"name": "ABC Mechanical",
"url": "https://abcmech.com",
"contacts": [{
"name": "John Smith",
"title": "Owner",
"emails": [{"email": "john@abcmech.com", "type": "office"}],
"phones": [{"phone": "555-1234", "type": "office"}]
}],
"custom.cf_tier": "Gold",
"custom.cf_source": "sales-agent"
})
# Create opportunity
opp = close._request("POST", "/opportunity/", json={
"lead_id": lead["id"],
"value": 50000,
"confidence": 50,
"status_id": "stat_xxx" # Pipeline stage
})
# Log activity
close._request("POST", "/activity/note/", json={
"lead_id": lead["id"],
"note": "Initial discovery call - interested in demo"
})
Rate Limit Headers (RFC-compliant)
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704067200
See
reference/close-deep-dive.mdfor query language, Smart Views, sequences, and reporting. </close_patterns>
<hubspot_patterns>
HubSpot Integration
Python SDK Pattern
from hubspot import HubSpot
from hubspot.crm.deals import SimplePublicObjectInputForCreate
from hubspot.crm.contacts import PublicObjectSearchRequest
client = HubSpot(access_token=os.environ["HUBSPOT_ACCESS_TOKEN"])
# Create deal with association
deal = client.crm.deals.basic_api.create(
SimplePublicObjectInputForCreate(properties={
"dealname": "Enterprise Deal",
"amount": "50000",
"dealstage": "appointmentscheduled",
"pipeline": "default"
})
)
# Search contacts by email domain
search = PublicObjectSearchRequest(
filter_groups=[{
"filters": [{
"propertyName": "email",
"operator": "CONTAINS",
"value": "@example.com"
}]
}],
properties=["email", "firstname", "lastname"],
limit=50
)
results = client.crm.contacts.search_api.do_search(search)
Association Types
| From | To | Type ID |
|---|---|---|
| Contact | Company | 1 |
| Contact | Deal | 4 |
| Company | Deal | 6 |
| Deal | Contact | 3 |
See
reference/hubspot-patterns.mdfor batch operations, custom properties, and workflows. </hubspot_patterns>
<salesforce_patterns>
Salesforce Integration
SOQL Query Patterns
-- Parent-child relationship (Contacts of Account)
SELECT Id, Name, (SELECT LastName, Email FROM Contacts)
FROM Account WHERE Industry = 'Technology'
-- Child-parent relationship
SELECT Id, FirstName, Account.Name, Account.Industry
FROM Contact WHERE Account.Industry = 'Technology'
-- Semi-join (Accounts with open Opportunities)
SELECT Id, Name FROM Account
WHERE Id IN (SELECT AccountId FROM Opportunity WHERE IsClosed = false)
REST API v59.0
def create_opportunity(self, data: dict) -> dict:
"""Required: Name, StageName, CloseDate."""
response = self.client.post(
f"{self.instance_url}/services/data/v59.0/sobjects/Opportunity/",
headers={"Authorization": f"Bearer {self.access_token}"},
json=data
)
return response.json()
# Composite API (batch up to 200 records)
def composite_create(self, records: list) -> dict:
return self.client.post(
f"{self.instance_url}/services/data/v59.0/composite/sobjects",
json={"allOrNone": False, "records": records}
)
See
reference/salesforce-patterns.mdfor JWT setup, Platform Events, and bulk API. </salesforce_patterns>
<webhook_patterns>
Webhook Handlers
Close Webhook (FastAPI)
from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib
app = FastAPI()
@app.post("/webhooks/close")
async def close_webhook(request: Request):
body = await request.body()
signature = request.headers.get("Close-Sig")
expected = hmac.new(
CLOSE_WEBHOOK_SECRET.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
raise HTTPException(401, "Invalid signature")
data = await request.json()
event_type = data["event"]["event_type"]
handlers = {
"lead.created": handle_lead_created,
"opportunity.status_changed": handle_opp_stage_change,
}
if handler := handlers.get(event_type):
await handler(data["event"]["data"])
return {"status": "ok"}
Close Webhook Events
lead.created, lead.updated, lead.deleted, lead.status_changed
contact.created, contact.updated
opportunity.created, opportunity.status_changed
activity.note.created, activity.call.created, activity.email.created
unsubscribed_email.created
</webhook_patterns>
<sync_architecture>
Cross-CRM Sync
Architecture
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Close │────▶│ Sync Layer │◀────│ HubSpot │
│ (Primary) │◀────│ (Postgres) │────▶│ (Marketing)│
└─────────────┘ └──────────────┘ └─────────────┘
Sync Record Schema
CREATE TABLE crm_sync_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
entity_type VARCHAR(50) NOT NULL,
close_id VARCHAR(100) UNIQUE,
hubspot_id VARCHAR(100) UNIQUE,
salesforce_id VARCHAR(100) UNIQUE,
email VARCHAR(255),
company_name VARCHAR(255),
last_synced_at TIMESTAMPTZ,
sync_source VARCHAR(50),
sync_hash VARCHAR(64)
);
CREATE INDEX idx_sync_email ON crm_sync_records(email);
Conflict Resolution
from enum import Enum
class ConflictStrategy(Enum):
CLOSE_WINS = "close" # Close is source of truth
LAST_WRITE_WINS = "lww" # Most recent update wins
def resolve_conflict(close_record, hubspot_record, strategy):
if strategy == ConflictStrategy.CLOSE_WINS:
merged = close_record.copy()
for key, value in hubspot_record.items():
if key not in merged or not merged[key]:
merged[key] = value
return merged
See
reference/sync-patterns.mdfor deduplication, migration scripts, and bulk sync. </sync_architecture>
<file_locations>
Reference Files
CRM-Specific:
reference/close-deep-dive.md- Query language, Smart Views, sequences, reportingreference/hubspot-patterns.md- SDK patterns, batch operations, workflowsreference/salesforce-patterns.md- JWT auth, SOQL, Platform Events, bulk API
Operations:
reference/sync-patterns.md- Cross-CRM sync, deduplication, migrationreference/automation.md- Webhook setup, sequences, workflows
Templates:
templates/close-client.py- Full Close API clienttemplates/hubspot-client.py- HubSpot SDK wrappertemplates/sync-service.py- Cross-CRM sync service </file_locations>
User wants CRM integration: → Ask which CRM (Close recommended for simplicity) → Provide auth setup + basic CRUD
User wants Close CRM:
→ Provide API key setup, query language
→ Reference: reference/close-deep-dive.md
User wants HubSpot:
→ Provide SDK setup, search patterns
→ Reference: reference/hubspot-patterns.md
User wants Salesforce:
→ Provide JWT auth, SOQL patterns
→ Reference: reference/salesforce-patterns.md
User wants sync between CRMs:
→ Provide sync architecture, conflict resolution
→ Reference: reference/sync-patterns.md
User wants webhooks: → Provide handler pattern for specified CRM → Include signature verification </routing>
<env_setup>
Environment Variables
# Close CRM
export CLOSE_API_KEY="api_xxx"
export CLOSE_WEBHOOK_SECRET="whsec_xxx"
# HubSpot
export HUBSPOT_ACCESS_TOKEN="pat-xxx"
# Salesforce
export SF_CLIENT_ID="xxx"
export SF_USERNAME="user@company.com"
export SF_PRIVATE_KEY_PATH="./salesforce.key"
export SF_INSTANCE_URL="https://yourorg.my.salesforce.com"
# Dependencies: httpx pyjwt hubspot-api-client python-dotenv
</env_setup>
<example_session>
Example Session
User: "I need to push enriched leads from my sales-agent to Close CRM"
Claude:
- Here's the pattern for pushing enriched data to Close:
async def push_to_close(close_client, enriched_data: dict) -> str:
lead_data = {
"name": enriched_data["company_name"],
"url": enriched_data.get("website"),
"custom.cf_tier": enriched_data["tier"],
"custom.cf_source": "sales-agent",
"contacts": [{
"name": c["name"],
"title": c.get("title"),
"emails": [{"email": c["email"]}] if c.get("email") else []
} for c in enriched_data.get("contacts", [])]
}
result = close_client.create_lead(lead_data)
return result["id"]
-
Make sure you have these custom fields in Close:
cf_tier(choices: Gold, Silver, Bronze)cf_source(choices: sales-agent, inbound, referral)
-
Rate limit: 100 requests per 10 seconds. Add
asyncio.sleep(0.1)between requests for bulk imports. </example_session>
Supporting file: config.json
{
"name": "crm-integration",
"version": "1.0.0",
"category": "Business",
"description": "Unified CRM integration patterns for Close CRM, HubSpot, and Salesforce",
"requires": ["close-api-or-hubspot-oauth-or-salesforce-jwt"],
"depends_on": [],
"integrates_with": [
"hubspot-revops-skill",
"sales-revenue-skill"
],
"activation_triggers": [
"Close CRM",
"HubSpot",
"Salesforce",
"CRM API",
"lead sync",
"deal sync"
]
}
Supporting file: reference/close-deep-dive.md
Close CRM Deep Dive
Your daily driver — power features, query language, and automation patterns.
Close Query Language
Close uses a powerful query language for searching and filtering. Master this for Smart Views.
Basic Operators
| Operator | Example | Description |
|---|---|---|
: | company:Coperniq | Contains |
= | status="Potential" | Exact match |
> < >= <= | opportunities.value>10000 | Numeric comparison |
is: | is:contacted | Boolean states |
has: | has:email | Field exists |
not: | not:contacted | Negation |
Field References
# Lead fields
lead_id, display_name, status_label, created_by
date_created, date_updated, description
# Contact fields
contact.name, contact.email, contact.phone, contact.title
# Opportunity fields
opportunities.status_label, opportunities.value
opportunities.confidence, opportunities.date_won
# Activity fields
activities.date_created, activities.note
calls.duration, emails.status
# Custom fields
custom.cf_xxxxxx
Power Queries
# Leads with no activity in 30 days
'sort:date_updated asc date_updated < "30 days ago"'
# High-value opportunities in pipeline
'opportunities.value >= 50000 opportunities.status_type:active'
# Leads with email but no calls
'has:email not:call'
# Specific custom field value
'custom.cf_industry = "MEP Contractor"'
# Multiple trade types (your ICP)
'custom.cf_trades:HVAC OR custom.cf_trades:Electrical OR custom.cf_trades:Plumbing'
# Leads created this month with opportunities
'created >= "first day of this month" has:opportunity'
# Stalled deals (no activity in 14 days, still active)
'opportunities.status_type:active sort:activities.date desc activities.date < "14 days ago"'
Smart Views (Saved Searches)
Sales Process Views
# New This Week
query: 'created >= "7 days ago" sort:date_created desc'
# My Active Pipeline
query: 'opportunities.status_type:active opportunities.user_id:me sort:opportunities.value desc'
# Needs Follow-Up (No activity 7+ days)
query: 'opportunities.status_type:active activities.date < "7 days ago" sort:activities.date asc'
# Closing This Month
query: 'opportunities.expected_close_date >= "first day of this month" opportunities.expected_close_date <= "last day of this month"'
# Lost Recently (Win-back candidates)
query: 'opportunities.status_type:lost opportunities.date_lost >= "30 days ago" sort:opportunities.date_lost desc'
Lead Quality Views
# Gold Tier (Multi-trade, has website)
query: 'custom.cf_tier = "Gold" sort:date_created desc'
# Missing Info (No email or phone)
query: 'not:email not:phone sort:date_created desc'
# Recently Enriched
query: 'custom.cf_enriched_date >= "7 days ago" sort:custom.cf_enriched_date desc'
API Power Features
Bulk Lead Update
async def bulk_update_leads(
client: CloseClient,
query: str,
updates: dict,
dry_run: bool = True
) -> dict:
"""Bulk update leads matching a query."""
# Search for matching leads
results = client.search_leads(query, limit=1000)
if dry_run:
return {"would_update": len(results), "leads": [r["id"] for r in results]}
updated = []
failed = []
for lead in results:
try:
client._request("PUT", f"/lead/{lead['id']}/", json=updates)
updated.append(lead["id"])
except Exception as e:
failed.append({"id": lead["id"], "error": str(e)})
await asyncio.sleep(0.1) # Rate limiting
return {"updated": len(updated), "failed": failed}
# Usage: Update all leads from a specific source
await bulk_update_leads(
client,
query='custom.cf_source = "dealer-scraper"',
updates={"custom.cf_tier": "Bronze"},
dry_run=False
)
Activity Timeline Builder
def get_lead_timeline(client: CloseClient, lead_id: str) -> list:
"""Get complete activity timeline for a lead."""
activities = []
# Get all activity types
for activity_type in ["note", "call", "email", "sms", "meeting"]:
endpoint = f"/activity/{activity_type}/"
params = {"lead_id": lead_id, "_limit": 100}
result = client._request("GET", endpoint, params=params)
for item in result["data"]:
activities.append({
"type": activity_type,
"date": item.get("date_created"),
"user": item.get("user_name"),
"content": item.get("note") or item.get("subject") or item.get("body_text"),
"direction": item.get("direction"),
"duration": item.get("duration"),
})
# Sort by date
activities.sort(key=lambda x: x["date"], reverse=True)
return activities
Opportunity Pipeline Stats
def get_pipeline_stats(client: CloseClient) -> dict:
"""Get current pipeline statistics."""
# Get all opportunity statuses
statuses = client._request("GET", "/status/opportunity/")["data"]
stats = {}
total_value = 0
weighted_value = 0
for status in statuses:
if status["type"] == "active":
# Query opportunities in this status
opps = client._request(
"GET", "/opportunity/",
params={"status_id": status["id"], "_limit": 1000}
)["data"]
count = len(opps)
value = sum(o.get("value", 0) or 0 for o in opps)
# Weighted by confidence
weighted = sum(
(o.get("value", 0) or 0) * (o.get("confidence", 50) / 100)
for o in opps
)
stats[status["label"]] = {
"count": count,
"value": value,
"weighted": weighted,
"avg_value": value / count if count else 0
}
total_value += value
weighted_value += weighted
stats["_totals"] = {
"total_value": total_value,
"weighted_value": weighted_value
}
return stats
Sequences & Workflows
Email Sequence via API
def enroll_in_sequence(
client: CloseClient,
lead_id: str,
sequence_id: str,
contact_id: str
) -> dict:
"""Enroll a contact in an email sequence."""
return client._request(
"POST", "/sequence_subscription/",
json={
"sequence_id": sequence_id,
"lead_id": lead_id,
"contact_id": contact_id,
"sender_account_id": "emailacct_xxx", # Your sending account
"sender_email": "tim@coperniq.ai",
"sender_name": "Tim Kipper"
}
)
def pause_sequence(client: CloseClient, subscription_id: str) -> dict:
"""Pause a sequence subscription."""
return client._request(
"PUT", f"/sequence_subscription/{subscription_id}/",
json={"status": "paused"}
)
Workflow Triggers (Webhooks)
# Create webhook subscription
def create_webhook(client: CloseClient, url: str, events: list) -> dict:
"""Subscribe to Close webhooks."""
return client._request(
"POST", "/webhook/",
json={
"url": url,
"events": events
}
)
# Common events to subscribe to:
WEBHOOK_EVENTS = [
"lead.created",
"lead.updated",
"lead.deleted",
"lead.status_changed",
"contact.created",
"contact.updated",
"opportunity.created",
"opportunity.updated",
"opportunity.status_changed",
"activity.note.created",
"activity.call.created",
"activity.email.created",
]
Reporting Queries
Daily Sales Report
def daily_sales_report(client: CloseClient, date: str = None) -> dict:
"""Generate daily sales activity report."""
if not date:
date = datetime.now().strftime("%Y-%m-%d")
report = {
"date": date,
"calls": 0,
"call_duration_minutes": 0,
"emails_sent": 0,
"leads_created": 0,
"opportunities_created": 0,
"opportunities_won": 0,
"revenue_won": 0,
}
# Calls today
calls = client._request(
"GET", "/activity/call/",
params={"date_created__gte": f"{date}T00:00:00", "_limit": 1000}
)["data"]
report["calls"] = len(calls)
report["call_duration_minutes"] = sum(c.get("duration", 0) for c in calls) / 60
# Emails sent today
emails = client._request(
"GET", "/activity/email/",
params={"date_created__gte": f"{date}T00:00:00", "direction": "outgoing", "_limit": 1000}
)["data"]
report["emails_sent"] = len(emails)
# Leads created today
leads = client.search_leads(f'created >= "{date}"', limit=1000)
report["leads_created"] = len(leads)
# Opportunities won today
won_opps = client._request(
"GET", "/opportunity/",
params={"date_won__gte": f"{date}T00:00:00", "status_type": "won", "_limit": 1000}
)["data"]
report["opportunities_won"] = len(won_opps)
report["revenue_won"] = sum(o.get("value", 0) or 0 for o in won_opps)
return report
Pipeline Velocity
def calculate_velocity(client: CloseClient, days: int = 90) -> dict:
"""Calculate pipeline velocity over a period."""
cutoff = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
# Get won opportunities in period
won = client._request(
"GET", "/opportunity/",
params={"date_won__gte": cutoff, "status_type": "won", "_limit": 1000}
)["data"]
# Get lost opportunities in period
lost = client._request(
"GET", "/opportunity/",
params={"date_lost__gte": cutoff, "status_type": "lost", "_limit": 1000}
)["data"]
total_closed = len(won) + len(lost)
win_rate = len(won) / total_closed if total_closed else 0
avg_deal_size = sum(o.get("value", 0) or 0 for o in won) / len(won) if won else 0
# Calculate average cycle time
cycle_times = []
for opp in won:
created = datetime.fromisoformat(opp["date_created"].replace("Z", "+00:00"))
won_date = datetime.fromisoformat(opp["date_won"].replace("Z", "+00:00"))
cycle_times.append((won_date - created).days)
avg_cycle = sum(cycle_times) / len(cycle_times) if cycle_times else 0
# Get current pipeline
active = client._request(
"GET", "/opportunity/",
params={"status_type": "active", "_limit": 1000}
)["data"]
pipeline_count = len(active)
# Velocity = (# Opps × Win Rate × Avg Deal) / Cycle Days
velocity = (pipeline_count * win_rate * avg_deal_size) / avg_cycle if avg_cycle else 0
return {
"period_days": days,
"win_rate": round(win_rate * 100, 1),
"avg_deal_size": round(avg_deal_size, 2),
"avg_cycle_days": round(avg_cycle, 1),
"pipeline_count": pipeline_count,
"monthly_velocity": round(velocity * 30, 2), # Monthly projection
}
Integration with sales-agent
# After enrichment, push to Close
async def push_enriched_lead_to_close(
close_client: CloseClient,
enriched_data: dict
) -> str:
"""Push an enriched lead from sales-agent to Close."""
lead_data = {
"name": enriched_data["company_name"],
"url": enriched_data.get("website"),
"custom.cf_tier": enriched_data["tier"],
"custom.cf_source": "sales-agent",
"custom.cf_enriched_date": datetime.now().isoformat(),
"custom.cf_trades": enriched_data.get("trades", []),
"custom.cf_employee_count": enriched_data.get("employee_count"),
"custom.cf_annual_revenue": enriched_data.get("revenue_estimate"),
}
# Add contacts
contacts = []
for contact in enriched_data.get("contacts", []):
contacts.append({
"name": contact["name"],
"title": contact.get("title"),
"emails": [{"email": contact["email"], "type": "office"}] if contact.get("email") else [],
"phones": [{"phone": contact["phone"], "type": "office"}] if contact.get("phone") else [],
})
if contacts:
lead_data["contacts"] = contacts
result = close_client.create_lead(lead_data)
return result["id"]
Custom Field Setup for Coperniq
Recommended custom fields for your MEP contractor ICP:
COPERNIQ_CUSTOM_FIELDS = [
{"name": "Tier", "type": "choices", "choices": ["Gold", "Silver", "Bronze"]},
{"name": "Trades", "type": "choices", "choices": ["HVAC", "Electrical", "Plumbing", "Fire Protection", "Solar"]},
{"name": "Employee Count", "type": "number"},
{"name": "Annual Revenue", "type": "number"},
{"name": "Tech Stack", "type": "text"},
{"name": "Source", "type": "choices", "choices": ["dealer-scraper", "sales-agent", "inbound", "referral", "trade-show"]},
{"name": "Enriched Date", "type": "date"},
{"name": "ICP Score", "type": "number"}, # 0-100 from sales-agent
{"name": "Current PM Software", "type": "text"},
{"name": "Pain Points", "type": "text"},
]
Common questions
How do I install Usage in Cursor, Claude Code, or Codex?
Run npx skills add manojbajaj95/claude-gtm-plugin --skill crm-integration in the project where you want it, then ask your agent for the skill by name. The --skill flag installs only Usage, not every skill in the repository.
Where does Usage come from and what license is it under?
Usage comes from the manojbajaj95/claude-gtm-plugin repository on GitHub. That repository has 74 GitHub stars. The skill is published under the MIT license.
Prefer plain text? Read the Usage guide as markdown.
Related skills
More from manojbajaj95