From 2839a228b067064d9494799ec543c37569d3680b Mon Sep 17 00:00:00 2001 From: David Lecko Date: Tue, 3 Nov 2026 14:32:34 -0600 Subject: [PATCH] Add include-tags and exclude-tags to Prospects list --- docs/commands.md | 8 ++++++++ src/commands/prospects.ts | 4 ++++ src/index.ts | 2 ++ tests/commands/prospects.test.ts | 11 ++++++++++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index 5eb8006..0e0807f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1360,3 +1360,11 @@ it closes open opportunities unless `--no-cascade` is supplied. A positional ## Archive a mail campaign `dm mail campaigns archive --yes` cancels remaining unsent mail, releases reserved funds, and stops mail-attention reminders while keeping campaign history. Confirm the cancellation before passing `--yes`. Use `dm mail campaigns list --status archived` to view archived campaigns. + + +### Include and exclude prospect tags together + +`dm prospects list --include-tags tag_3,tag_4 --exclude-tags tag_9` keeps +prospects with tag 3 or 4 and no tag 9. Both flags accept up to 50 tag IDs. +Exclusion wins when a prospect matches both sets. The existing `--tag` flag +continues to narrow by one tag and can be combined with these flags. diff --git a/src/commands/prospects.ts b/src/commands/prospects.ts index 470c3e6..4729a58 100644 --- a/src/commands/prospects.ts +++ b/src/commands/prospects.ts @@ -107,6 +107,8 @@ export async function prospectsList(options: { favorites?: boolean; list?: string; tag?: string; + includeTags?: string; + excludeTags?: string; search?: string; sort?: string; page?: string; @@ -127,6 +129,8 @@ export async function prospectsList(options: { favorites_only: options.favorites ? 'true' : undefined, list_id: options.list, tag_id: options.tag, + include_tag_ids: options.includeTags, + exclude_tag_ids: options.excludeTags, search: options.search, sort: options.sort, page: options.page ? parseInt(options.page, 10) : undefined, diff --git a/src/index.ts b/src/index.ts index 3f1db44..c6b38dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1653,6 +1653,8 @@ prospectsCmd .option('--favorites', 'Only starred prospects') .option('--list ', 'Only members of this list') .option('--tag ', 'Only prospects with this tag, e.g. tag_31') + .option('--include-tags ', 'Match any of these comma-separated tag IDs') + .option('--exclude-tags ', 'Exclude any of these comma-separated tag IDs at the same time') .option('--search ', 'Address words for properties; name or city for people') .option('--sort ', 'newest (default) or oldest') .option('-p, --page ', 'Page number') diff --git a/tests/commands/prospects.test.ts b/tests/commands/prospects.test.ts index c656fa4..bca8363 100644 --- a/tests/commands/prospects.test.ts +++ b/tests/commands/prospects.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { prospectsArchive, prospectsReactivate, prospectsGet } from '../../src/commands/prospects.js'; +import { prospectsList, prospectsArchive, prospectsReactivate, prospectsGet } from '../../src/commands/prospects.js'; vi.mock('../../src/lib/config.js', () => ({ readConfig: () => ({ apiKey: 'fixture-only', apiEnvironment: 'staging' }), @@ -188,3 +188,12 @@ describe('dm prospects list attribution filters', () => { }); }); }); + + +it('sends included and excluded tags in the same prospects request', async () => { + fetchMock.mockResolvedValue(jsonResponse(200, { data: [], pagination: { page: 1, per_page: 25, total: 0, has_more: false } })); + await prospectsList({ includeTags: 'tag_3,tag_4', excludeTags: 'tag_9', json: true }); + const params = new URL(String(fetchMock.mock.calls[0][0])).searchParams; + expect(params.get('include_tag_ids')).toBe('tag_3,tag_4'); + expect(params.get('exclude_tag_ids')).toBe('tag_9'); +});