feat(conversation): add conversation list command#35
Merged
Conversation
`tdc thread reply/create --notify` already accepts custom group IDs (partitioned via resolveNotifyIds → groups), but the help text only mentioned "user IDs". Since Comms group IDs are non-numeric base58 strings that look nothing like user IDs, the capability was effectively undiscoverable from --help. Mention groups in both option descriptions and add a group-notify example to the reply command's existing example block (alongside --close / --file). No behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per AGENTS.md, src/lib/skills/content.ts must track command-description and example changes. The reply/create --notify examples said "users" only; make them mention groups and add a group-notify example (base58 group ID next to a numeric user ID). Regenerated skills/comms-cli/SKILL.md via `sync:skill`; check:skill-sync passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
List DMs and group conversations filtered by participant, name, or kind. Reuses the existing plumbing: getAllConversations becomes getConversationsByState(state), and listConversationsWithUser becomes the shared renderConversationList. Defaults to active conversations; --state opts into archived. SKILL_CONTENT and README updated, SKILL.md regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
doistbot
reviewed
Jun 18, 2026
doistbot
left a comment
Member
There was a problem hiding this comment.
This PR adds tdc conversation list with kind, participant, name, state, and limit filters, nicely reusing the existing conversation fetch and render plumbing.
Few things worth tightening:
- Participant resolution misses removed users:
--participantresolves against the active workspace roster only, so--state archived --participant "Former User"fails even when that archived conversation exists. Resolve participants withincludeRemoved: trueso archived DMs/groups are findable. - Avoidable full-workspace user read in JSON mode: The list path always builds a workspace-wide user-name map via
renderConversationList, even for--json/--ndjsonwithout--fullwhere those names are filtered out. Short-circuit machine-readable output before name expansion. - Concurrency / fail-fast:
getConversationsByStateandresolveUserRefsare independent and could run concurrently withPromise.all, orresolveUserRefscould be moved first to fail fast on an ambiguous or invalid participant before fetching conversations. - Type safety and defaults in shared helpers:
kindandstateonConversationListOptionsare typed as plainstringrather than exported unions, andgetConversationsByState()defaultsstateto'all'(the most expensive option) — makingstaterequired and passing'all'explicitly fromwith.tswould avoid a future footgun.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (2)
- [P3] src/commands/conversation/helpers.ts:25:
kindandstateare typed as plainstrings here even though the command constrains them to a fixed set of values. That weakens the handler contract compared with nearby patterns likearchiveFilter?: ArchiveFilterand forceslist.tsto re-narrow them, where bad non-Commander inputs currently fall back toundefined/activeinstead of failing. Export shared unions for these fields and use them onConversationListOptionsso callers can't pass arbitrary strings. - [P3] src/commands/conversation/helpers.ts:80: Giving
getConversationsByState()a default of'all'hides the most expensive behavior behind an omitted argument. Both current callers already know which state they want (listwantsactive,with --include-groupswantsall), so makingstaterequired and passing'all'explicitly fromwith.tswould keep that choice at the call site and avoid an easy future footgun where a new caller accidentally fetches archived conversations too.
scottlovegrove
approved these changes
Jun 18, 2026
scottlovegrove
left a comment
Collaborator
There was a problem hiding this comment.
Needs doistbot's feedback addressing before merging, but all good from my side.
Three doistbot P2s on the conversation list command: - Resolve --participant against the full roster (includeRemoved) so a participant who has left the workspace still matches; the renderer already shows removed participants, and archived DMs often include them. - Resolve participants and fetch conversations concurrently (Promise.all) rather than sequentially — they're independent once the workspace is known. - Skip the workspace-wide user-map fetch for --json/--ndjson without --full, where participantNames are filtered back out anyway (also speeds up conversation with machine output, which shares the renderer). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
doist-release-bot Bot
added a commit
that referenced
this pull request
Jun 18, 2026
## [1.7.0](v1.6.5...v1.7.0) (2026-06-18) ### Features * **conversation:** add conversation list command ([#35](#35)) ([46a000e](46a000e))
Contributor
|
🎉 This PR is included in version 1.7.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Adds
tdc conversation listso you can find a DM or group conversation by who's in it, its title, or whether it's a 1:1 or a group. There was no way to list conversations from the CLI before:conversation unreadshows only unread, andconversation with <user>finds only your 1:1 with one person.It reuses the existing plumbing rather than adding new infrastructure: the shared fetch (
getAllConversations→getConversationsByState) and renderer (listConversationsWithUser→renderConversationList) were generalised, andconversation withnow calls the renamed helpers with no behaviour change. Flag shapes follow the family: a value-enum flag per mutually-exclusive dimension (--kind,--state), likechannel list --stateandsearch --type, with active-by-default lists and archived as opt-in.DM content search stays out of scope; it already works via
tdc search --type messages/--conversation/--toand just needs better docs (separate change).Usage
Flags:
--workspace,--participant,--name,--kind <group|direct>,--state <active|all|archived>(default active),--snippet,--limit,--json/--ndjson/--full.Test plan
type-check,lint:check,check:skill-sync, andtest(815 pass, 13 new) all green.--nameprints the empty message; an ambiguous--participantsurfaces a cleanAMBIGUOUS_USERerror.For the maintainer
getConversationsisn't server-paginated (same asgetThreads/getChannels), so this is one fetch plus client-side--limit. Any server-side cap on conversation count for very large workspaces? Not a blocker;with --include-groupsalready relies on the same single-call behaviour.