Skip to content
deal-management logo

Deal Management

deal-management

Run the full deal lifecycle from CLI — discover pipelines/stages, qualify MQLs into deals with associations, advance/reassign in bulk, hunt stalled deals, and close.

hubspot/agent-cli-skills1.6Kinstalls26stars

SKILL.md

Full skill instructions

Resources

FileWhen to use
resources/​lifecycle-stage-progression.mdLifecycle stage API values + the contact-side updates that pair with deal moves.
resources/​stalled-deal-queries.mdFilter 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 <command> --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,"stages":[...]}
# {"id":"a1b2c3d4-0000-0000-0000-000000000000","label":"Enterprise Pipeline","displayOrder":1,"stages":[...]}

hubspot pipelines stages --type deals --pipeline default --format jsonl
# {"id":"appointmentscheduled","label":"Appointment Scheduled","displayOrder":0,"metadata":{"isClosed":"false","probability":"0.2"}}
# {"id":"qualifiedtobuy","label":"Qualified To Buy","displayOrder":1,"metadata":{"isClosed":"false","probability":"0.4"}}
# ...
# {"id":"closedwon","label":"Closed Won","displayOrder":5,"metadata":{"isClosed":"true","probability":"1.0"}}
# {"id":"closedlost","label":"Closed Lost","displayOrder":6,"metadata":{"isClosed":"true","probability":"0.0"}}

pipelines list/get embed each pipeline's stages, so a single call gives you the stage id -> label map you need to translate a deal's dealstage GUID:

hubspot pipelines get --type deals default --format jsonl \
  | jq -r '.stages[] | "\(.id)\t\(.label)"'

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:<contact_id> --to companies   # → <company_id>

hubspot objects create --type deals \
  --property "dealname=Acme Corp - Inbound" \
  --property pipeline=default --property dealstage=qualifiedtobuy \
  --property amount=0 --property hubspot_owner_id=<owner_id>
# returns {"id":"<deal_id>","ok":true,...}

hubspot associations create --from deals:<deal_id> --to contacts:<contact_id>
hubspot associations create --from deals:<deal_id> --to companies:<company_id>

# lifecycle promote — objects update is irreversible, so dry-run then confirm with the contact ID
hubspot objects update --type contacts <contact_id> \
  --property lifecyclestage=salesqualifiedlead --property hs_lead_status=OPEN_DEAL --dry-run
hubspot objects update --type contacts <contact_id> \
  --property lifecyclestage=salesqualifiedlead --property hs_lead_status=OPEN_DEAL \
  --digest <hash> --confirm <contact_id>

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 <(jq -r '.id' /​tmp/​mqls.jsonl) <(jq -r '.id' /​tmp/​deals.jsonl) \
| jq -cR 'split("\t") | {from:("deals:" + .[1]), to:("contacts:" + .[0])}' \
| hubspot associations create

# 5. promote lifecycle on every contact — objects update is irreversible, so dry-run then re-pipe with the digest/​confirm
jq -c '{id, properties:{lifecyclestage:"salesqualifiedlead", hs_lead_status:"OPEN_DEAL"}}' /​tmp/​mqls.jsonl \
| hubspot objects update --type contacts --dry-run \
| tee /​tmp/​promote.preview.jsonl

digest=$(jq -r 'select(.digest != null) | .digest' /​tmp/​promote.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /​tmp/​promote.preview.jsonl)   # batch: row count

jq -c '{id, properties:{lifecyclestage:"salesqualifiedlead", hs_lead_status:"OPEN_DEAL"}}' /​tmp/​mqls.jsonl \
| hubspot objects update --type contacts --digest "$digest" --confirm "$confirm"

Company associations need a separate per-contact pass via hubspot associations list --from contacts:<id> --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

# move every deal in one stage to the next — 1. preview
hubspot objects search --type deals --filter "dealstage=qualifiedtobuy" \
| jq -c '{id, properties:{dealstage:"presentationscheduled"}}' \
| hubspot objects update --type deals --dry-run \
| tee /​tmp/​advance.preview.jsonl

# 2. lift the digest + confirm (present at every size)
digest=$(jq -r 'select(.digest != null) | .digest' /​tmp/​advance.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /​tmp/​advance.preview.jsonl)   # batch: row count; single: record ID

# 3. execute — re-pipe the SAME inputs plus --digest/​--confirm
hubspot objects search --type deals --filter "dealstage=qualifiedtobuy" \
| jq -c '{id, properties:{dealstage:"presentationscheduled"}}' \
| hubspot objects update --type deals --digest "$digest" --confirm "$confirm"

# reassign open deals from one rep to another — same three steps
OLD=$(hubspot owners list --format jsonl | jq -r 'select(.email=="[email protected]") | .id')
NEW=$(hubspot owners list --format jsonl | jq -r 'select(.email=="[email protected]") | .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 \
| tee /​tmp/​deal-reassign.preview.jsonl
digest=$(jq -r 'select(.digest != null) | .digest' /​tmp/​deal-reassign.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /​tmp/​deal-reassign.preview.jsonl)
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 --digest "$digest" --confirm "$confirm"

The dry-run emits a digest at every size (confirm = the row count for a batch, the record ID for a single); re-pipe with --digest <hash> --confirm <value> lifted from the preview line. 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<$(date -v-30d +%Y-%m-%d) AND hs_is_closed!=true" \
  --properties dealname,dealstage,closedate,hubspot_owner_id,hs_last_activity_date

Pipe the result into an update (extend close dates, move stage, set a flag) or into task creation. For follow-up tasks/​calls/​notes against stalled deals, see the sales-execution skill — don't duplicate activity-object property handling here.

# extend close dates for everything past due
hubspot objects search --type deals \
  --filter "closedate<$(date +%Y-%m-%d) AND hs_is_closed!=true" \
| jq -c '{id, properties:{closedate:"2026-06-30"}}' \
| hubspot objects update --type deals --dry-run

5. Close

Closing is a stage update + closedate (YYYY-MM-DD). hs_is_closed and hs_is_closed_won are read-only — HubSpot derives them from the stage.

# single — objects update is irreversible, so dry-run then confirm with the deal ID
hubspot objects update --type deals <deal_id> \
  --property dealstage=closedwon --property closedate=2026-05-15 --dry-run
hubspot objects update --type deals <deal_id> \
  --property dealstage=closedwon --property closedate=2026-05-15 --digest <hash> --confirm <deal_id>

# bulk — preview first, then re-pipe with the digest/​confirm from the preview (confirm = row count)
hubspot objects search --type deals --filter "dealstage=contractsent AND hubspot_owner_id=<owner_id>" \
| jq -c '{id, properties:{dealstage:"closedwon", closedate:"2026-05-15"}}' \
| hubspot objects update --type deals --dry-run \
| tee /​tmp/​close.preview.jsonl

digest=$(jq -r 'select(.digest != null) | .digest' /​tmp/​close.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /​tmp/​close.preview.jsonl)

hubspot objects search --type deals --filter "dealstage=contractsent AND hubspot_owner_id=<owner_id>" \
| jq -c '{id, properties:{dealstage:"closedwon", closedate:"2026-05-15"}}' \
| hubspot objects update --type deals --digest "$digest" --confirm "$confirm"

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 create output, not in the same pipe.
  • lifecyclestage is forward-only in most portal settings — backward transitions may be rejected.
  • closedate is a date string (YYYY-MM-DD). Datetime activity props (hs_last_activity_date) also accept a date string for </> comparisons.
  • hubspot sequences reads Sales Hub sequences (list / get / enrollments) but is read-only — the CLI cannot enroll a contact in a sequence, so create a follow-up task via sales-execution instead. sequences enrollments <contact_id> is useful for win/​loss context on a deal's contacts.

More skills from hubspot

workflow-automation logo
hubspot/agent-cli-skills

workflow-automation

List, inspect, create, update, and delete HubSpot workflows (v4 flows API) from the `hubspot` agent CLI, not the `hs` developer CLI.

26 1.6K
View
sales-reporting logo
hubspot/agent-cli-skills

sales-reporting

Daily briefings, pipeline snapshots, and win/loss analysis from the terminal — closing-this-week, open pipeline by stage/owner, and closed-won vs closed-lost over a period.

26 1.6K
View
custom-object-management logo
hubspot/agent-cli-skills

custom-object-management

Discover, create, update, and delete custom CRM object schemas. Use when defining a new object type, inspecting existing schemas, or removing one. Record CRUD on custom objects is identical to standard objects — see `bulk-operations`.

26 1.6K
View
data-enrichment logo
hubspot/agent-cli-skills

data-enrichment

Match external CSV/JSONL records to CRM contacts (by email) or companies (by domain) and write enriched data back in one pass using `hubspot objects upsert`.

26 1.6K
View
communication-history logo
hubspot/agent-cli-skills

communication-history

Retrieve activity history (calls, emails, notes, meetings, tasks) for a CRM record and assemble pre-call briefs.

26 1.6K
View
sales-execution logo
hubspot/agent-cli-skills

sales-execution

Log sales activities — calls, notes, meetings, tasks — against contacts and deals, with the mandatory create-then-associate step that makes them visible in the CRM.

26 1.6K
View
team-ownership logo
hubspot/agent-cli-skills

team-ownership

Assign and reassign CRM record ownership, audit who-owns-what across object types, and handle rep transitions. Built on `bulk-operations`.

26 1.6K
View
customer-retention logo
hubspot/agent-cli-skills

customer-retention

Identify inactive/at-risk customers via CRM filters and create follow-up tasks at scale. Builds on `bulk-operations`; defers activity-creation specifics to `sales-execution`.

26 1.5K
View
audience-targeting logo
hubspot/agent-cli-skills

audience-targeting

Build a targeted contact segment by filtering on lifecycle, engagement, jobtitle, geography, or firmographics — then export it as JSONL for a campaign or downstream tool.

26 1.5K
View
quote-to-cash logo
hubspot/agent-cli-skills

quote-to-cash

Build the product catalog, assemble quotes (line items + associations to deals), and track invoices and subscriptions through to revenue.

26 1.5K
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View