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>(or read thestagesarray embedded inhubspot pipelines list/get).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:
Deals closing in the next 7 days:
Deals updated in the last 24h:
Open-pipeline summary line:
2. Pipeline snapshot
By stage — count and amount per dealstage:
By owner:
To label owner IDs with names, dump the owners file once and join:
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:
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".
Revenue by close month (won deals):
4. Saved reports
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-runfirst, then--digest <hash> --confirm "<report name>"(confirm = the report's name).
Run hubspot reports --help for the full surface and the CRM-SQL grammar.
5. Win/loss context: sequence enrollments
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.
Known limitations
- Won/lost stages are identifiable from
hubspot pipelines stages: each stage'smetadatacarriesisClosed/probability(e.g.jq -r 'select(.metadata.probability=="1.0") | .id').hs_is_closed_wonon the deal itself also works. - 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 . != "").

