Skip to content

feat: add group support to --notify and new tw groups command#129

Open
rfgamaral wants to merge 1 commit intomainfrom
ricardo/group-support
Open

feat: add group support to --notify and new tw groups command#129
rfgamaral wants to merge 1 commit intomainfrom
ricardo/group-support

Conversation

@rfgamaral
Copy link
Copy Markdown
Member

@rfgamaral rfgamaral commented Apr 3, 2026

Overview

Adds group support to the CLI: a new tw groups command and transparent group resolution in --notify.

Previously, passing a group ID to --notify on tw thread create or tw thread reply silently failed: the ID was sent as a user recipient, and the API ignored it. Now --notify fetches workspace groups, partitions IDs into users (recipients) and groups (groups), and passes both to the API. No syntax changes; existing usage works transparently.

Already verified with a live ping to the Frontend Product group: https://twist.com/a/1585/ch/607587/t/7519962/c/101564420

Test plan

Warning

This will create a real thread in Twist and tag real people. Skip this if the live test above is sufficient.

# 1. Find a channel
tw channels --json | jq '.[0]'
# Note the channel id

# 2. Find a group
tw groups --json | jq '.[0]'
# Note the group id

# 3. Create a thread tagging the group (--json to get the thread id)
tw thread create <channel-id> "Testing group notify" "Please ignore, testing CLI group support" --notify <group-id> --json | jq .id
# Note the thread id

# 4. Reply to the thread tagging the group again
tw thread reply <thread-id> "Follow-up test, still ignore" --notify <group-id>

# 5. Clean up: delete the thread from the Twist UI

@rfgamaral rfgamaral self-assigned this Apr 3, 2026
@rfgamaral rfgamaral added the 👀 Show PR PR must be reviewed before or after merging label Apr 3, 2026
@rfgamaral rfgamaral marked this pull request as ready for review April 3, 2026 17:05
@doistbot doistbot requested a review from scottlovegrove April 3, 2026 17:05
@rfgamaral rfgamaral requested review from a team and removed request for a team April 3, 2026 17:06
@rfgamaral rfgamaral added 🙋 Ask PR PR must be reviewed before merging and removed 👀 Show PR PR must be reviewed before or after merging labels Apr 3, 2026
const client = await getTwistClient()
const thread = await client.threads.getThread(threadId)
await assertChannelIsPublic(thread.channelId, thread.workspaceId)
const groupsPayload = groups ? { groups } : {}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should fetch all the actual group names before spitting this out. We can do this via the SDK's batch() command, that way we're not just dumping out an array of ids that will mean nothing to the user.

Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces fantastic group support to the CLI with a new tw groups command and transparent group resolution for the --notify flag. The approach smoothly partitions recipient IDs without altering existing syntax, integrating naturally into the existing workflow. A few minor adjustments are noted to ensure empty states emit valid machine-readable output, to preserve the offline nature of --dry-run by deferring API calls, and to accurately reflect the new search functionality in the skill content examples.

Share FeedbackReview Logs

tw users # List workspace users
tw users --search <text> # Filter by name/email
tw channels # List workspace channels
tw groups # List workspace groups
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Documentation mismatch: Update the skill content examples to reflect the correct usage of the groups command with the --search flag, maintaining parity with the users command examples above it.

Suggested change
tw groups # List workspace groups
tw groups # List workspace groups
tw groups --search "frontend" # Filter groups by name (case-insensitive)

groups = groups.filter((g) => g.name.toLowerCase().includes(query))
}

if (groups.length === 0) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] The machine-readable modes stop being machine-readable on an empty workspace. Because this empty check runs before the --json/--ndjson branches, tw groups --json prints No groups found. instead of [] (and --ndjson prints prose instead of no records), which will break jq/shell pipelines for a valid empty result. Handle the empty case inside the format-specific branches so JSON/NDJSON always emit parseable output.

const recipients = options.notify ? parseUserIdRefs(options.notify) : undefined
const allIds = options.notify ? parseUserIdRefs(options.notify) : undefined

const client = await getTwistClient()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] This makes tw thread create ... --dry-run depend on live API access. Moving getTwistClient()/getChannel() (and, when --notify is set, getWorkspaceGroups()) above the dry-run guard changes the command from a local preview into a networked operation, so it can now fail offline or with a stale token before printing anything. The other dry-run paths in this CLI return before API work; this one should keep that behavior as well.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is debatable, and is not the route we have taken with the Todoist CLI. I would say leave this as-is in this PR is fine, but can you open an issue for us to look into the other dry run commands, cos to me, a dry run should do everything except the final part (similar to what npm pack --dry-run does.

if (notifyValue === 'EVERYONE' || notifyValue === 'EVERYONE_IN_THREAD') {
const isSpecialRecipient = notifyValue === 'EVERYONE' || notifyValue === 'EVERYONE_IN_THREAD'

const client = await getTwistClient()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] tw thread reply --dry-run now does a full getThread first, and may also fetch workspace groups, before it reaches the dry-run guard. That means a no-op preview can now fail because the network is unavailable or the auth token is expired, which is a regression from the existing dry-run pattern used elsewhere in the CLI. Please keep the dry-run branch ahead of these API lookups.

Add tw groups command to list and search workspace groups.

Make --notify on thread create/reply auto-resolve IDs by partitioning
them into users (recipients) and groups based on workspace group data.
No syntax changes required; existing usage works transparently.

- Add partitionNotifyIds helper in refs.ts
- Add getWorkspaceGroups in api.ts
- Add group entity type to output.ts for JSON formatting
- Update dry run output to show users and groups separately
- Update skill content and SKILL.md
@scottlovegrove scottlovegrove force-pushed the ricardo/group-support branch from 91a498e to 25aab12 Compare April 3, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Ask PR PR must be reviewed before merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants