feat(tasks,emails): add search subcommands with filter/sort support#57
feat(tasks,emails): add search subcommands with filter/sort support#57piekstra wants to merge 3 commits into
Conversation
Automated PR ReviewReviewed commit: Summary
go:implementation-tests (1 finding)Minor -
|
| Field | Value |
|---|---|
| Model | claude-sonnet-4-6 |
| Reviewers | documentation:docs, go:implementation-tests, policies:conventions |
| Engine | claude_cli · claude-sonnet-4-6 |
| Reviewed by | cr · piekstra-dev |
| Duration | 3m 59s wall · 3m 57s compute |
| Cost | ~$0.52 (est.) |
| Tokens | 20 in / 12.5k out |
Per-workstream usage
| Workstream | Model | In | Out | Cache read | Cache create | Cost | Duration |
|---|---|---|---|---|---|---|---|
| orchestrator-selection | claude-sonnet-4-6 | 4 | 1.6k | 7.2k | 12.4k | ~$0.07 (est.) | 29s |
| documentation:docs | claude-sonnet-4-6 | 4 | 1.6k | 7.2k | 12.2k | ~$0.07 (est.) | 31s |
| go:implementation-tests | claude-sonnet-4-6 | 4 | 6.9k | 7.2k | 25.9k | ~$0.20 (est.) | 1m 59s |
| policies:conventions | claude-sonnet-4-6 | 4 | 2.1k | 7.2k | 24.2k | ~$0.12 (est.) | 45s |
| orchestrator-rollup | claude-sonnet-4-6 | 4 | 393 | 17.1k | 11.3k | ~$0.05 (est.) | 12s |
piekstra-dev
left a comment
There was a problem hiding this comment.
Automated PR review completed with outcome: approved.
Address review feedback on PR #57: - api/crm_test.go: the "search tasks sends filter, sort, and pagination" subtest previously only asserted the filterGroups/sorts keys existed, so a bug emitting an empty filter or dropping the operator would still pass. Add content assertions on the EQ filter (propertyName/operator/value) and the sort (propertyName/direction), matching the depth of the BETWEEN/IN subtest. - internal/cmd/shared: extract NewSearchCmd(opts, SearchCmdConfig) so the two near-identical tasks/emails newSearchCmd implementations delegate to a single shared constructor, keeping the reuse boundary consistent with the existing ParseFilters/ParseSort helpers. Object-specific pieces (object type, noun, descriptions, default properties, headers, row builder) are supplied via the config; no behavior change.
Automated PR ReviewReviewed commit: Summary
documentation:docs (1 finding)Minor -
|
| Field | Value |
|---|---|
| Model | claude-sonnet-4-6 |
| Reviewers | documentation:docs, go:implementation-tests, policies:conventions, structure:repo-health |
| Engine | claude_cli · claude-sonnet-4-6 |
| Reviewed by | cr · piekstra-dev |
| Duration | 4m 47s wall · 4m 45s compute |
| Cost | ~$0.66 (est.) |
| Tokens | 24 in / 13.3k out |
Per-workstream usage
| Workstream | Model | In | Out | Cache read | Cache create | Cost | Duration |
|---|---|---|---|---|---|---|---|
| orchestrator-selection | claude-sonnet-4-6 | 4 | 2.3k | 7.2k | 15.0k | ~$0.09 (est.) | 45s |
| documentation:docs | claude-sonnet-4-6 | 4 | 1.5k | 7.2k | 12.2k | ~$0.07 (est.) | 28s |
| go:implementation-tests | claude-sonnet-4-6 | 4 | 3.5k | 7.2k | 25.9k | ~$0.15 (est.) | 1m 13s |
| policies:conventions | claude-sonnet-4-6 | 4 | 3.3k | 7.2k | 23.4k | ~$0.14 (est.) | 1m 04s |
| structure:repo-health | claude-sonnet-4-6 | 4 | 2.2k | 7.2k | 19.3k | ~$0.11 (est.) | 45s |
| orchestrator-rollup | claude-sonnet-4-6 | 4 | 548 | 8.3k | 22.6k | ~$0.10 (est.) | 28s |
piekstra-dev
left a comment
There was a problem hiding this comment.
Automated PR review completed with outcome: comment.
Add `hspt tasks search` and `hspt emails search` backed by the HubSpot CRM Search API. Both accept repeatable `--filter` and `--sort` flags plus `--limit`, `--after`, and `--properties`, matching the existing search commands' look and feel. New shared parsing helpers (internal/cmd/shared/search.go): - ParseFilters: shorthand (prop=val, !=, >=, <=, >, <) and explicit operators (prop:OPERATOR:val, prop:BETWEEN:lo:hi, prop:IN:a,b,c, prop:HAS_PROPERTY). ISO-8601 dates on known date properties are auto-converted to Unix milliseconds. - ParseSort: prop, prop:asc, prop:desc (case-insensitive). The api.SearchFilter struct gains optional highValue (BETWEEN) and values (IN/NOT_IN) fields; this is additive and backward compatible. Tests cover the filter/sort parsers (shared) and request serialization for tasks/emails search including BETWEEN/IN encoding (api). Closes #53
Address review feedback on PR #57: - api/crm_test.go: the "search tasks sends filter, sort, and pagination" subtest previously only asserted the filterGroups/sorts keys existed, so a bug emitting an empty filter or dropping the operator would still pass. Add content assertions on the EQ filter (propertyName/operator/value) and the sort (propertyName/direction), matching the depth of the BETWEEN/IN subtest. - internal/cmd/shared: extract NewSearchCmd(opts, SearchCmdConfig) so the two near-identical tasks/emails newSearchCmd implementations delegate to a single shared constructor, keeping the reuse boundary consistent with the existing ParseFilters/ParseSort helpers. Object-specific pieces (object type, noun, descriptions, default properties, headers, row builder) are supplied via the config; no behavior change.
Calling require.NoError inside an httptest http.HandlerFunc runs t.FailNow off the test goroutine, which is undefined behavior. Use assert.NoError so the handler only marks failure and drains the body. Also clarify the emails search README example: hs_email_direction is a direction enum, so the comment now reads 'Logged emails by direction' rather than implying outbound-only semantics.
fbe75f7 to
1ebfa40
Compare
Closes #53
Summary
Adds
hspt tasks searchandhspt emails search, backed by the HubSpot CRM Search API, with generic filtering and sorting — so you can find open tasks for an owner, overdue tasks, outbound emails, etc. without paginating the entire history and filtering client-side.Both subcommands accept the same flags as the issue requested:
--filter(repeatable)--sort(repeatable)--limit,--after,--propertiesFilter syntax
Shorthand comparisons:
prop=valueEQprop!=valueNEQprop>=valueGTEprop<=valueLTEprop>valueGTprop<valueLTExplicit operators (uppercase, HubSpot convention):
prop:OPERATOR:value— e.g.hs_email_subject:CONTAINS_TOKEN:Dev Academyprop:BETWEEN:low:highprop:IN:a,b,c/prop:NOT_IN:a,b,cprop:HAS_PROPERTY/prop:NOT_HAS_PROPERTYISO-8601 dates supplied for known date properties (e.g.
hs_timestamp) are auto-converted to Unix milliseconds, sohs_timestamp<=2026-03-17works as expected.Sort syntax
prop,prop:asc,prop:desc(case-insensitive; defaults to ascending).Examples
Implementation notes
The issue references
shared.ParseFilters()/shared.ParseSort()as already existing, but they did not exist in this repo (onlyshared/format.gowas present, and the existingcontacts/deals/ticketssearch commands use hardcoded per-field flags). This PR introduces those generic helpers ininternal/cmd/shared/search.goand wires the two new subcommands to them.api.SearchFiltergains optionalhighValue(BETWEEN) andvalues(IN/NOT_IN) fields — additive and backward compatible; existing search commands are unaffected.newSearchCmdregistration pattern and the table/JSON/plain rendering viav.Render().Files changed
internal/cmd/shared/search.go(new) —ParseFilters/ParseSortinternal/cmd/shared/search_test.go(new) — parser unit testsinternal/cmd/tasks/tasks.go— register + implementsearchinternal/cmd/emails/emails.go— register + implementsearchapi/crm.go—SearchFilter.HighValue/.Valuesapi/crm_test.go— request-serialization tests for tasks/emails search (incl. BETWEEN/IN)README.md— document the new subcommandsTesting
All commands run from the repo root.
Manual smoke tests against
bin/hspt:hspt tasks search --help/hspt emails search --helpshow the new flags and examples.--sort "hs_timestamp:sideways"->invalid sort direction "sideways" ... (use asc or desc)--filter "hs_task_status"->invalid filter "hs_task_status": expected prop=value, ...(Live API calls were not exercised — no HubSpot token in this environment — but request construction is covered by the
apipackage tests.)