hubspot/agent-cli-skills

team-ownership

Assign and reassign CRM record ownership, audit who-owns-what across object types, and handle rep transitions.

View source

How to use

  1. Copy the installation command and review the source package before installing it in your own agent environment.
  2. Check the source document for required tools, credentials and supported hosts.
  3. Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/hubspot/agent-cli-skills --skill team-ownership

Before you start

  • Installation does not include a model subscription, API allowance or permission to access your files.
  • Repository stars and installation counts describe activity, not an independent quality or security test.
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Prereq: read bulk-operations/SKILL.md first. JSONL piping, pagination, dry-run/digest/confirm, and hubspot history recovery live there. Reshape patterns live in bulk-operations/resources/json-patterns.md.

hubspot_owner_id is a string field on contacts, companies, deals, and tickets. Owners are CRM users — hubspot owners list returns them; there is no teams object, so team-level views are client-side groupings by hubspot_owner_id.

1. Resolve email → owner ID

Never hardcode IDs — they are portal-specific. Resolve, then cache:

bash
FROM_ID=$(hubspot owners list | jq -r 'select(.email=="sarah@company.com") | .id')
TO_ID=$(hubspot owners list | jq -r 'select(.email=="mike@company.com")  | .id')

2. Find records for an owner

Same filter across all four object types. Add object-specific --properties for context. Unowned records use the !property form.

bash
hubspot objects search --type contacts  --filter "hubspot_owner_id=$FROM_ID" --properties email,firstname,lifecyclestage
hubspot objects search --type companies --filter "hubspot_owner_id=$FROM_ID" --properties name,domain
hubspot objects search --type deals     --filter "hubspot_owner_id=$FROM_ID" --properties dealname,dealstage,amount
hubspot objects search --type tickets   --filter "hubspot_owner_id=$FROM_ID" --properties subject,hs_pipeline_stage

# Records with no owner at all
hubspot objects search --type deals --filter "!hubspot_owner_id" --properties dealname,amount
100 hits — page with the --after loop from bulk-operations. Counting only: pipe to wc -l.

3. Bulk reassign — search → update

Reshape each search row into {id, properties:{hubspot_owner_id}} and pipe to objects update. objects update is irreversible, so dry-run first: the dry-run emits a digest + apply_command_hint at every size (confirm = the record ID for one record, the row count for a batch). Then re-run with --digest/--confirm (see bulk-operations/SKILL.md § "Safe destructive workflow").

bash
# Dry-run
hubspot objects search --type contacts --filter "hubspot_owner_id=$FROM_ID" \
| jq -c --arg to "$TO_ID" '{id, properties:{hubspot_owner_id:$to}}' \
| hubspot objects update --type contacts --dry-run \
| tee /tmp/reassign.preview.jsonl

# Execute — lift the digest + confirm from the preview line (required at every size)
digest=$(jq -r 'select(.digest != null) | .digest' /tmp/reassign.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /tmp/reassign.preview.jsonl)   # single: record ID; batch: row count

hubspot objects search --type contacts --filter "hubspot_owner_id=$FROM_ID" \
| jq -c --arg to "$TO_ID" '{id, properties:{hubspot_owner_id:$to}}' \
| hubspot objects update --type contacts --digest "$digest" --confirm "$confirm"

Single-record assignment — no stdin, no jq. Still irreversible, so dry-run first (confirm = the record ID):

bash
hubspot objects update --type contacts 12345 --property hubspot_owner_id=$TO_ID --dry-run
hubspot objects update --type contacts 12345 --property hubspot_owner_id=$TO_ID --digest <hash> --confirm 12345

4. Rep-leaves workflow

Loop over the four object types the rep touches:

bash
FROM_ID=$(hubspot owners list | jq -r 'select(.email=="leaving@company.com")    | .id')
TO_ID=$(hubspot  owners list | jq -r 'select(.email=="taking-over@company.com") | .id')

for type in contacts companies deals tickets; do
  echo "── $type ──"
  hubspot objects search --type "$type" --filter "hubspot_owner_id=$FROM_ID" \
  | jq -c --arg to "$TO_ID" '{id, properties:{hubspot_owner_id:$to}}' \
  | hubspot objects update --type "$type" --dry-run
done

Review each digest line, then re-run per type with the --digest/--confirm lifted from that type's preview line (required at every size; confirm = the row count for a batch, the record ID for a single). Mis-reassigned? hubspot history --since 1h lists the affected IDs.

5. Team-level views (client-side grouping)

Group records by hubspot_owner_id, join to owners list for human-readable emails:

bash
hubspot objects search --type deals --filter "dealstage!=closedwon AND dealstage!=closedlost" \
  --properties hubspot_owner_id --format json \
| jq '.data | group_by(.properties.hubspot_owner_id)
       | map({owner_id: .[0].properties.hubspot_owner_id, count: length})' \
> /tmp/by-owner.json

hubspot owners list \
| jq --slurpfile by /tmp/by-owner.json -r \
     '. as $o | $by[0][] | select(.owner_id==$o.id) | "\($o.email)\t\(.count)"'

Related by task

from this repository

More skills

All skills