Install
$ agentstack add skill-hubspot-agent-cli-skills-deal-management ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Resources
| File | When to use | |---|---| | resources/lifecycle-stage-progression.md | Lifecycle stage API values + the contact-side updates that pair with deal moves. | | resources/stalled-deal-queries.md | Filter cookbook for stalled / no-activity / past-close-date deals with dynamic dates. |
Foundations
Read bulk-operations/SKILL.md first — JSONL piping, batch read, pagination, and the dry-run/digest/confirm flow live there. Reshape recipes are in bulk-operations/resources/json-patterns.md. hubspot --help is the source of truth. Object types are plural (contacts, deals, companies). For property reference: hubspot properties list --type deals — don't hardcode property tables.
1. Discover pipelines and stages
Pipeline and stage IDs are portal-specific. Always discover at runtime — never hardcode across portals.
hubspot pipelines list --type deals --format jsonl
# {"id":"default","label":"Sales Pipeline","displayOrder":0}
# {"id":"a1b2c3d4-0000-0000-0000-000000000000","label":"Enterprise Pipeline","displayOrder":1}
hubspot pipelines stages --type deals --pipeline default --format jsonl
# {"id":"appointmentscheduled","label":"Appointment Scheduled","displayOrder":0}
# {"id":"qualifiedtobuy","label":"Qualified To Buy","displayOrder":1}
# ...
# {"id":"closedwon","label":"Closed Won","displayOrder":5}
# {"id":"closedlost","label":"Closed Lost","displayOrder":6}
Grab a specific stage ID by label:
QUALIFIED=$(hubspot pipelines stages --type deals --pipeline default --format jsonl \
| jq -r 'select(.label=="Qualified To Buy") | .id')
The IDs shown above (appointmentscheduled, closedwon, etc.) are HubSpot's standard default deal pipeline stages — but discover yours every run since portals can rename or remove them.
2. Qualify an MQL into a deal
Find connected MQLs without a deal, then for each: create the deal, associate to contact + company, promote lifecycle.
# 1. find ready MQLs
hubspot objects search --type contacts \
--filter "lifecyclestage=marketingqualifiedlead AND hs_lead_status=CONNECTED AND num_associated_deals=0" \
--properties email,firstname,lastname,company,hubspot_owner_id
# 2. for one contact: company lookup, deal create, associate, promote
hubspot associations list --from contacts: --to companies # →
hubspot objects create --type deals \
--property "dealname=Acme Corp - Inbound" \
--property pipeline=default --property dealstage=qualifiedtobuy \
--property amount=0 --property hubspot_owner_id=
# returns {"id":"","ok":true,...}
hubspot associations create --from deals: --to contacts:
hubspot associations create --from deals: --to companies:
hubspot objects update --type contacts \
--property lifecyclestage=salesqualifiedlead --property hs_lead_status=OPEN_DEAL
Bulk pattern — many MQLs at once
objects create returns one result line per stdin line, in input order. Capture both streams and join by line for associations:
# 1. snapshot MQLs to a file (preserves order for the join)
hubspot objects search --type contacts \
--filter "lifecyclestage=marketingqualifiedlead AND hs_lead_status=CONNECTED AND num_associated_deals=0" \
--properties email,firstname,lastname,company,hubspot_owner_id \
> /tmp/mqls.jsonl
# 2. one deal per MQL — output preserves order
jq -c '{properties:{
dealname: ((.properties.firstname // "") + " " + (.properties.lastname // "") + " - " + (.properties.company // "Unknown")),
pipeline:"default", dealstage:"qualifiedtobuy", amount:"0", dealtype:"newbusiness",
hubspot_owner_id:(.properties.hubspot_owner_id // "")
}}' /tmp/mqls.jsonl \
| hubspot objects create --type deals > /tmp/deals.jsonl
# 3. abort if any create failed — paste would zip null deal IDs onto real contacts
jq -e 'select(.ok==false)' /tmp/deals.jsonl > /dev/null && { echo "Some deal creates failed — inspect /tmp/deals.jsonl" >&2; exit 1; }
# 4. pair contact new deal by line for the association call
paste --to companies` — a contact may have zero or many companies.
Pre-qualification checks are just filters on the search: has email, has a company, no open deal, has an owner — all in the `--filter` already. See `resources/lifecycle-stage-progression.md` for the full stage progression and contact-side updates.
## 3. Advance or reassign in bulk
```bash
# move every deal in one stage to the next — preview, then re-run without --dry-run
hubspot objects search --type deals --filter "dealstage=qualifiedtobuy" \
| jq -c '{id, properties:{dealstage:"presentationscheduled"}}' \
| hubspot objects update --type deals --dry-run
# reassign open deals from one rep to another
OLD=$(hubspot owners list --format jsonl | jq -r 'select(.email=="old@co.com") | .id')
NEW=$(hubspot owners list --format jsonl | jq -r 'select(.email=="new@co.com") | .id')
hubspot objects search --type deals --filter "hubspot_owner_id=$OLD AND hs_is_closed!=true" \
| jq -c "{id, properties:{hubspot_owner_id:\"$NEW\"}}" \
| hubspot objects update --type deals --dry-run
For >100 rows, the dry-run emits a digest line; re-pipe with --digest --confirm . Full flow in bulk-operations/SKILL.md.
4. Find stalled deals
Filter cookbook with dynamic dates lives in resources/stalled-deal-queries.md. The core query:
# open deals with no activity in 30 days (macOS / Linux date examples in resources)
hubspot objects search --type deals \
--filter "hs_last_activity_date \
--property dealstage=closedwon --property closedate=2026-05-15
# bulk — preview first
hubspot objects search --type deals --filter "dealstage=contractsent AND hubspot_owner_id=" \
| jq -c '{id, properties:{dealstage:"closedwon", closedate:"2026-05-15"}}' \
| hubspot objects update --type deals --dry-run
Win/loss analysis (close reasons, win rate, ARR roll-up) is in the sales-reporting skill.
Known constraints
- Bulk MQL → deal needs a two-pass shell flow: associations must be built from
objects createoutput, not in the same pipe. lifecyclestageis forward-only in most portal settings — backward transitions may be rejected.closedateis a date string (YYYY-MM-DD). Datetime activity props (hs_last_activity_date) also accept a date string for `` comparisons.- No sequences/cadences API in the CLI — create a follow-up task via
sales-executioninstead.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: HubSpot
- Source: HubSpot/agent-cli-skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.