Skip to content

feat: add search subcommands for tasks and emails with filter/sort support #53

Description

@jack-tolley

Problem

hspt tasks list and hspt emails list return paginated, unfiltered results with no way to query by status, owner, direction, or date. Finding open tasks for a specific owner requires paginating through the entire task history — potentially thousands of records — and filtering client-side.

# What you'd want (doesn't exist upstream):
hspt tasks search --filter "hs_task_status=NOT_STARTED" --filter "hubspot_owner_id=77999105"
hspt emails search --filter "hs_email_direction=EMAIL" --sort "hs_timestamp:desc"

# What you have to do instead:
hspt tasks list --limit 100  # oldest-first, no status filter, repeat with --after...

Precedent

contacts search and deals search already use the HubSpot CRM Search API. The filter/sort infrastructure in internal/cmd/shared/ already handles all the heavy lifting:

  • ParseFilters() — parses --filter flags into SearchFilterGroup structs
  • ParseSort() — parses --sort flags into SearchSort structs
  • Auto-converts ISO dates to Unix milliseconds
  • Supports shorthand (prop=val, prop>=val) and explicit operators (prop:BETWEEN:low:high)

Proposed API

# Tasks: open tasks for a specific owner
hspt tasks search --filter "hs_task_status=NOT_STARTED" --filter "hubspot_owner_id=77999105" --sort "hs_timestamp:asc" --limit 50 -o json

# Tasks: overdue tasks
hspt tasks search --filter "hs_task_status=NOT_STARTED" --filter "hs_timestamp<=2026-03-17" --sort "hs_timestamp:asc"

# Emails: outbound emails
hspt emails search --filter "hs_email_direction=EMAIL" --sort "hs_timestamp:desc" --limit 20

# Emails: by subject
hspt emails search --filter "hs_email_subject:CONTAINS_TOKEN:Dev Academy" --limit 10

Both commands accept --filter, --sort, --limit, --after, and --properties flags — identical to contacts search and deals search.

Implementation notes

Each new subcommand follows the contacts search pattern (internal/cmd/contacts/contacts.go:newSearchCmd):

  1. Add newSearchCmd(opts) to the command's Register() function
  2. Accept the standard search flags
  3. Call shared.ParseFilters() and shared.ParseSort()
  4. Call client.SearchObjects(api.ObjectTypeTasks, req) / client.SearchObjects(api.ObjectTypeEmails, req)
  5. Render via v.Render()

The implementation is primarily wiring — no new infrastructure needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions