MCPInstall

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.

Installs
1.7K
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/hubspot/agent-cli-skills --skill deal-management
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads deal-management automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

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=="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 \
| 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.

FAQ

What does the deal-management skill do?

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.

How do I install deal-management?

Run npx skills add https://github.com/hubspot/agent-cli-skills --skill deal-management in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/deal-management/ (personal) or .claude/skills/deal-management/ (project).

Which AI agents can use deal-management?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is deal-management safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains deal-management?

It is published by hubspot in hubspot/agent-cli-skills. It has 1,719 recorded installs.

More from hubspot/agent-cli-skills

View all →

ticket-resolution

hubspot/agent-cli-skills

Create, triage, advance, and close HubSpot support tickets — pipeline discovery, contact/company association, priority queues, bulk stage moves, resolution close-out.

2K installs

crm-lookup

hubspot/agent-cli-skills

Find a specific CRM record by ID, email, domain, or name fragment, and traverse associations for the full account picture.

Business & Finance1.8K installs

bulk-operations

hubspot/agent-cli-skills

Foundation patterns for the hubspot CLI — JSONL piping, batch read, pagination, dry-run/digest/confirm for destructive ops, and hubspot history for recovery. Every other skill builds on this one.

1.8K installs

crm-data-quality

hubspot/agent-cli-skills

Find incomplete records, normalize field values in bulk, dedupe with hubspot objects merge, and audit custom properties. Builds on bulk-operations for JSONL piping and dry-run/digest/confirm.

Business & Finance1.8K installs

workflow-automation

hubspot/agent-cli-skills

List, inspect, create, update, and delete HubSpot workflows (v4 flows API) from the hubspot agent CLI, not the hs developer CLI. Classic v3 contact-based workflows are not covered.

sales-reporting

hubspot/agent-cli-skills

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.

Business & Finance1.7K installs

Popular other skills

View category →

hyperframes-cli

heygen-com/hyperframes

The HyperFrames CLI by job - scaffold, check, look at frames, edit the timeline, preview, render locally or in the cloud, publish, project history - plus reading its JSON and recovering from build or render failures.

1.3M installs

triage

mattpocock/skills

Move issues and external PRs through a state machine of triage roles, categorise, verify, grill if needed, and write agent-ready briefs.

1M installs

ask-matt

mattpocock/skills

Ask which skill or flow fits your situation. A router over the skills in this repo.

778.4K installs

to-questionnaire

mattpocock/skills

Turn a decision you can't fully answer into a questionnaire for someone else to fill in.

508.9K installs

wait-what

mattpocock/skills

Stop. That last message did not land: re-pitch it.

459.7K installs

loop-me

mattpocock/skills

Grill me about specs for the workflows I want to build, within this workspace.

447.9K installs