workflow-automation
List, inspect, create, update, and delete HubSpot workflows (v4 flows API) from the `hubspot` agent CLI, not the `hs` developer CLI.
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.
Full skill instructions
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.null or an empty string ("") when unset/blank. tonumber aborts on "". Always guard with select(. != null and . != "") | tonumber.--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> (or read the stages array embedded in hubspot pipelines list/get). hubspot pipelines is app-token-only — see Auth section; it 403s under user OAuth.hubspot_owner_id is a numeric string. Resolve to a name with hubspot owners list (fields: id, firstName, lastName, email). hubspot owners list works under both user OAuth (hubspot auth login) and a service key.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)"'
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
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'
The hubspot reports family runs HubSpot's saved reports and CRM-SQL reports server-side, so you don't have to recompute aggregates client-side:
reports list / get <id> — browse and inspect saved reports.reports fetch-dataset <id> — re-execute a saved report server-side and return its dataset.reports create "<CRM SQL>" — create a report from a CRM-SQL query (a companion skill can generate the SQL); reports insights <id> generates AI insights (async, polls to completion).reports update <id> / clone <id> / favorite <id> / unfavorite <id> — manage report metadata.reports delete <id> — irreversible, digest-gated: --dry-run first, then --digest <hash> --confirm "<report name>" (confirm = the report's name).Run hubspot reports --help for the full surface and the CRM-SQL grammar.
hubspot sequences enrollments <contact_id> returns a contact's Sales Hub sequence enrollment history (read-only) — useful for attributing wins/losses to outreach. Each row: {"contactId":789,"enrollments":[{"sequenceName":"Q4 Outbound","state":"FINISHED","enrolledAt":"...","currentStepOrder":5,"totalSteps":6}]}. Join it to a deal's associated contacts for a "was this deal worked through a sequence?" view. See hubspot sequences enrollments --help.
hubspot pipelines stages: each stage's metadata carries isClosed/probability (e.g. jq -r 'select(.metadata.probability=="1.0") | .id'). hs_is_closed_won on the deal itself also works.hubspot_owner_id and resolve names from hubspot owners list client-side.hubspot pipelines is app-token-only and 403s under user OAuth. hubspot owners list works under user OAuth. Keep raw IDs + warn; do not fail the report when pipelines is unavailable.null or ""; always guard tonumber with select(. != null and . != "").List, inspect, create, update, and delete HubSpot workflows (v4 flows API) from the `hubspot` agent CLI, not the `hs` developer CLI.
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.
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`.
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`.
Retrieve activity history (calls, emails, notes, meetings, tasks) for a CRM record and assemble pre-call briefs.
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.
Assign and reassign CRM record ownership, audit who-owns-what across object types, and handle rep transitions. Built on `bulk-operations`.
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`.
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.
Build the product catalog, assemble quotes (line items + associations to deals), and track invoices and subscriptions through to revenue.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience