Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1363,3 +1363,11 @@ it closes open opportunities unless `--no-cascade` is supplied. A positional
End the campaign first with `dm mail campaigns end <id> --yes`. Archiving an active, paused, ready or draft campaign returns `409 CAMPAIGN_NOT_ENDED` without changing it.

`dm mail campaigns archive <id> --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.
4 changes: 4 additions & 0 deletions src/commands/prospects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1654,6 +1654,8 @@ prospectsCmd
.option('--favorites', 'Only starred prospects')
.option('--list <listId>', 'Only members of this list')
.option('--tag <tagId>', 'Only prospects with this tag, e.g. tag_31')
.option('--include-tags <ids>', 'Match any of these comma-separated tag IDs')
.option('--exclude-tags <ids>', 'Exclude any of these comma-separated tag IDs at the same time')
.option('--search <term>', 'Address words for properties; name or city for people')
.option('--sort <order>', 'newest (default) or oldest')
.option('-p, --page <n>', 'Page number')
Expand Down
11 changes: 10 additions & 1 deletion tests/commands/prospects.test.ts
Original file line number Diff line number Diff line change
@@ -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' }),
Expand Down Expand Up @@ -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');
});
Loading