Gives you command-line access to your HubSpot sales pipeline with pre-built queries for the reports you'd actually run: what's closing this week, where deals are stuck by stage, win rates by rep, revenue by month. The jq patterns handle HubSpot's string-typed booleans and numeric fields correctly, and the pagination logic lives in the bulk-operations foundation so you won't silently truncate at 100 rows. Useful when you want numbers without opening the CRM, or when you're scripting daily digests and need consistent JSONL output. The owner-to-name mapping is manual but straightforward.
npx -y skills add hubspot/agent-cli-skills --skill sales-reporting --agent claude-codeInstalls into .claude/skills of the current project.
hubspot <command> --help is authoritative. Build on bulk-operations/SKILL.md — JSONL shape, batch-read rules, and pagination live there. Reshape patterns: bulk-operations/resources/json-patterns.md. search/list cap at 100 rows per call; a result of exactly 100 is almost always truncated — paginate via bulk-operations/SKILL.md before aggregating.
hs_is_closed_won is returned as "true"/"false" (string); amount is a numeric string. Use tonumber for arithmetic; compare booleans as strings (== "true") when filtering client-side.--filter expressions, hs_is_closed_won=true and hs_is_closed!=true work — the API parses the value.--properties returns the standard nested shape: {"id":"123","properties":{"amount":"5000","dealname":"..."}}. Reference fields as .properties.amount in jq.dealstage are portal-specific. Map them with hubspot pipelines stages --type deals --pipeline <id>.hubspot_owner_id is a numeric string. Resolve to a name with hubspot owners list (fields: id, firstName, lastName, email).Date windows differ between macOS and GNU date:
# macOS
TODAY=$(date +%Y-%m-%d); NEXT_7=$(date -v+7d +%Y-%m-%d); YESTERDAY=$(date -v-1d +%Y-%m-%d)
# Linux
TODAY=$(date +%Y-%m-%d); NEXT_7=$(date -d '7 days' +%Y-%m-%d); YESTERDAY=$(date -d '1 day ago' +%Y-%m-%d)
Deals closing in the next 7 days:
hubspot objects search --type deals \
--filter "closedate>$TODAY AND closedate<$NEXT_7 AND hs_is_closed!=true" \
--properties dealname,amount,closedate,hubspot_owner_id
Deals updated in the last 24h:
hubspot objects search --type deals \
--filter "hs_lastmodifieddate>$YESTERDAY AND hs_is_closed!=true" \
--properties dealname,amount,dealstage,hs_lastmodifieddate
Open-pipeline summary line:
hubspot objects search --type deals --filter "hs_is_closed!=true" --properties amount \
| jq -rs '{count: length, value: ([.[].properties.amount | select(. != null) | tonumber] | add // 0 | round)}
| "Open pipeline: \(.count) deals, $\(.value)"'
By stage — count and amount per dealstage:
hubspot objects search --type deals --filter "hs_is_closed!=true" \
--properties dealstage,amount \
| jq -rs '
group_by(.properties.dealstage)
| map({stage: .[0].properties.dealstage, count: length,
total: ([.[].properties.amount | select(. != null) | tonumber] | add // 0 | round)})
| sort_by(-.total) | .[] | "\(.stage)\tcount: \(.count)\tvalue: $\(.total)"' \
| column -t -s$'\t'
By owner:
hubspot objects search --type deals --filter "hs_is_closed!=true" \
--properties amount,hubspot_owner_id \
| jq -rs '
group_by(.properties.hubspot_owner_id)
| map({owner: .[0].properties.hubspot_owner_id, count: length,
total: ([.[].properties.amount | select(. != null) | tonumber] | add // 0 | round)})
| sort_by(-.total) | .[] | "owner \(.owner)\tdeals: \(.count)\tvalue: $\(.total)"' \
| column -t -s$'\t'
To label owner IDs with names, dump the owners file once and join:
hubspot owners list | jq -r '"\(.id)\t\(.firstName) \(.lastName) <\(.email)>"' > /tmp/owners.tsv
Filter on hs_is_closed_won=true for won; hs_is_closed=true AND hs_is_closed_won!=true for lost. Scope with closedate>=YYYY-MM-DD AND closedate<YYYY-MM-DD.
Closed won / lost in a period:
hubspot objects search --type deals \
--filter "hs_is_closed_won=true AND closedate>=2026-04-01 AND closedate<2026-07-01" \
--properties dealname,amount,closedate,hubspot_owner_id
hubspot objects search --type deals \
--filter "hs_is_closed=true AND hs_is_closed_won!=true AND closedate>=2026-04-01 AND closedate<2026-07-01" \
--properties dealname,amount,closedate,hubspot_owner_id
Win rate by rep — pull all closed deals in the period, group, divide. Note: hs_is_closed_won lands as a string, so compare == "true".
hubspot objects search --type deals \
--filter "hs_is_closed=true AND closedate>=2026-01-01" \
--properties hubspot_owner_id,hs_is_closed_won,amount \
| jq -rs '
group_by(.properties.hubspot_owner_id)
| map({owner: .[0].properties.hubspot_owner_id,
total: length,
won: ([.[] | select(.properties.hs_is_closed_won == "true")] | length),
won_value: ([.[] | select(.properties.hs_is_closed_won == "true")
| .properties.amount | select(. != null) | tonumber] | add // 0 | round)})
| map(. + {win_rate: ((.won / .total * 100) | round)})
| sort_by(-.won_value)
| .[] | "owner \(.owner)\twon: \(.won)/\(.total)\trate: \(.win_rate)%\twon: $\(.won_value)"' \
| column -t -s$'\t'
Revenue by close month (won deals):
hubspot objects search --type deals \
--filter "hs_is_closed_won=true AND closedate>=2026-01-01" \
--properties amount,closedate \
| jq -rs '
group_by(.properties.closedate[0:7])
| map({month: .[0].properties.closedate[0:7], count: length,
revenue: ([.[].properties.amount | select(. != null) | tonumber] | add // 0 | round)})
| sort_by(.month) | .[] | "\(.month)\tdeals: \(.count)\trevenue: $\(.revenue)"' \
| column -t -s$'\t'
hubspot pipelines stages does not expose stage probability — won/lost stages can't be auto-identified from the stages list. Use hs_is_closed_won on deals instead.hubspot_owner_id and resolve names from hubspot owners list client-side.sickn33/antigravity-awesome-skills
moizibnyousaf/ai-agent-skills
github/awesome-copilot