sales-reporting
hubspot/agent-cli-skills
Daily briefings, pipeline snapshots, and win/loss analysis from the terminal.
What is sales-reporting?
Generates sales reports directly from HubSpot via the command line, including deals closing this week, open pipeline by stage and owner, and closed-won vs closed-lost analysis. Use this to pull quick snapshots of pipeline health, track deal velocity, and measure win rates without leaving the terminal.
- Query deals closing in the next 7 days with owner and amount
- Summarize open pipeline by stage and by owner with deal counts and total value
- Analyze closed-won vs closed-lost deals over a custom date range
- Calculate win rates and win value by sales rep
- Generate revenue reports by close month for won deals
- Filter and aggregate deals using HubSpot's search API with jq reshaping
How to install sales-reporting
npx skills add https://github.com/hubspot/agent-cli-skills --skill sales-reporting- HubSpot account with API access (user OAuth or service key)
- hubspot CLI installed and authenticated (hubspot auth login or service key configured)
- jq installed for JSON reshaping and aggregation
- GNU or macOS date command for date range calculations
How to use sales-reporting
- 1.Run npx skills add https://github.com/hubspot/agent-cli-skills --skill sales-reporting to install
- 2.Authenticate with HubSpot: hubspot auth login (user OAuth) or configure a service key
- 3.Use hubspot objects search --type deals with --filter and --properties flags to query deals
- 4.Pipe results to jq to group, aggregate, and format output (see examples for daily briefing, pipeline by stage/owner, and win/loss analysis)
- 5.For owner names, run hubspot owners list once and join the output to replace numeric owner IDs
Use cases
- Morning standup: pull deals closing this week and deals updated in the last 24 hours
- Pipeline health check: view open deals grouped by stage or owner to spot bottlenecks
- Quarterly win/loss review: compare closed-won and closed-lost deals for a period and calculate win rates by rep
- Monthly revenue forecast: aggregate closed-won deals by close month to track bookings
- Sales rep performance: rank reps by win rate, deal count, and won value
- Sales managers and directors reviewing pipeline and team performance
- Individual sales reps tracking their own deal velocity and win rate
- Revenue operations teams building automated reporting workflows
- Sales leadership preparing for forecasting and board reviews
sales-reporting FAQ
HubSpot CRM properties can be unset as either null or an empty string. Always guard tonumber with select(. != null and . != "") to avoid errors.
Run hubspot pipelines stages --type deals --pipeline <id> to get the portal-specific stage mapping. Note: this command requires an app token and will 403 under user OAuth.
Yes. Use closedate filters in the --filter flag, e.g., closedate>=2026-04-01 AND closedate<2026-07-01. Adjust the date format to YYYY-MM-DD.
Run hubspot owners list to dump owner data, then join by ID in jq or save to a TSV file for reference.
Results of exactly 100 rows are almost always truncated. Use the bulk-operations skill's pagination rules to fetch all records before aggregating.
Full instructions (SKILL.md)
Source of truth, from hubspot/agent-cli-skills.
name: sales-reporting description: 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. triggers:
- "daily briefing"
- "pipeline snapshot"
- "deals closing this week"
- "deals by owner"
- "win rate"
- "closed won"
- "closed lost"
- "win/loss analysis"
- "revenue by month"
- "pipeline by stage"
Source of truth
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.
Property and output shape notes
- All CRM property values come back as strings in JSONL — booleans included.
hs_is_closed_wonis returned as"true"/"false"(string);amountis a numeric string. Usetonumberfor arithmetic; compare booleans as strings (== "true") when filtering client-side. - Numeric properties can be
nullor an empty string ("") when unset/blank.tonumberaborts on"". Always guard withselect(. != null and . != "") | tonumber. - In
--filterexpressions,hs_is_closed_won=trueandhs_is_closed!=truework — the API parses the value. --propertiesreturns the standard nested shape:{"id":"123","properties":{"amount":"5000","dealname":"..."}}. Reference fields as.properties.amountin jq.- Stage IDs in
dealstageare portal-specific. Map them withhubspot pipelines stages --type deals --pipeline <id>.hubspot pipelinesis app-token-only — see Auth section; it 403s under user OAuth. hubspot_owner_idis a numeric string. Resolve to a name withhubspot owners list(fields:id,firstName,lastName,email).hubspot owners listworks under both user OAuth (hubspot auth login) and a service key.
1. Daily briefing
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 and . != "") | tonumber] | add // 0 | round)}
| "Open pipeline: \(.count) deals, $\(.value)"'
2. Pipeline snapshot
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 and . != "") | 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 and . != "") | 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
3. Win/loss analysis
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 and . != "") | 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 and . != "") | tonumber] | add // 0 | round)})
| sort_by(.month) | .[] | "\(.month)\tdeals: \(.count)\trevenue: $\(.revenue)"' \
| column -t -s$'\t'
Known limitations
hubspot pipelines stagesdoes not expose stage probability — won/lost stages can't be auto-identified from the stages list. Usehs_is_closed_wonon deals instead.- No team object — group by
hubspot_owner_idand resolve names fromhubspot owners listclient-side. hubspot pipelinesis app-token-only and 403s under user OAuth.hubspot owners listworks under user OAuth. Keep raw IDs + warn; do not fail the report when pipelines is unavailable.- Numeric CRM properties can be
nullor""; always guardtonumberwithselect(. != null and . != "").
Related skills
More from hubspot/agent-cli-skills and the wider catalog.

team-ownership
Assign, reassign, and audit CRM record ownership across contacts, companies, deals, and tickets.

ticket-resolution
Create, triage, advance, and close HubSpot support tickets with pipeline discovery and bulk operations.

workflow-automation
Manage HubSpot v4 workflows (list, create, update, delete) via the hubspot agent CLI.

audience-targeting
Build targeted contact segments by filtering on lifecycle, engagement, job title, geography, and firmographics—then export as JSONL.

hf-cli
Hugging Face Hub CLI for managing models, datasets, spaces, buckets, and infrastructure.

huggingface-best
Find the best AI model for your task by querying HuggingFace benchmark leaderboards and filtering by device constraints.