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
9 changes: 9 additions & 0 deletions skills/comms-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ tdc channel threads <ref> --since 2026-01-01 # Filter by last-updated date (ISO)
tdc channel threads <ref> --limit 20 # Max threads per page (default: 50)
tdc channel threads <ref> --limit 20 --cursor <cursor-from-prev> # Paginate
tdc channel threads <ref> --json # { results, nextCursor } with isUnread + url
tdc channel members <channel-ref> # List a channel's members + groups fully in the channel
tdc channel members <ref> --json # JSON with id, name, workspaceId, members
tdc channel members add <ref> alice group:Design # Add users and/or expand group:<ref> members
tdc channel members add <ref> a@d.com id:789 --json # Add refs, output result as JSON
tdc channel members remove <ref> alice group:Frontend # Remove users and/or group members
tdc channel members set <ref> group:Squad --apply # Replace membership with the resolved set
tdc channel members set <ref> alice bob # Dry-run by default; refuses to remove you (--include-self to override)
tdc groups # List workspace groups
tdc groups --search "frontend" # Filter groups by name (case-insensitive)
tdc groups --json # JSON output
Expand All @@ -251,6 +258,8 @@ If a channel is not found in `tdc channels`, widen with broader listings such as

`tdc channel threads` returns every thread in the channel; pagination filters (`--limit`, `--cursor`, `--since`, `--until`, `--unread`) are applied client-side after fetch. `--archive-filter` is applied server-side. Results are sorted newest-first by last activity. In `--json` / `--ndjson`, the response includes a `nextCursor` string (opaque) you can pass via `--cursor` to fetch the next page; NDJSON emits the cursor as a final `{ "_meta": true, "nextCursor": "..." }` line.

For `tdc channel members add/remove/set`, refs accept user identifiers (`id:N`, email, name) or `group:<ref>`, which expands to the group's current members. Group expansion is one-shot — it is not a persistent link, so users added to the group later will not auto-join the channel. `set` replaces membership with the resolved set and is dry-run by default (pass `--apply` to mutate); it refuses to remove the acting user unless `--include-self` is passed.

## Reactions

```bash
Expand Down
9 changes: 9 additions & 0 deletions src/commands/channel/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type ChannelMutationOptions, mutateChannelMembership } from './membership-helpers.js'

export async function addChannelMembers(
channelRef: string,
refs: string[],
options: ChannelMutationOptions,
): Promise<void> {
return mutateChannelMembership(channelRef, refs, 'add', options)
}
96 changes: 95 additions & 1 deletion src/commands/channel/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Command, Option } from 'commander'
import { withCaseInsensitiveChoices } from '../../lib/completion.js'
import { addChannelMembers } from './add.js'
import { createChannel } from './create.js'
import { listChannels } from './list.js'
import { listChannelMembers } from './members.js'
import { removeChannelMembers } from './remove.js'
import { setChannelMembers } from './set.js'
import { showChannelThreads } from './threads.js'
import { updateChannel } from './update.js'

export function registerChannelCommand(program: Command): void {
const channel = program
.command('channel')
.alias('channels')
.description('Channel operations (list, create, update, threads)')
.description('Channel operations (list, create, update, threads, members)')

channel
.command('list [workspace-ref]', { isDefault: true })
Expand Down Expand Up @@ -131,4 +135,94 @@ Notes:
and --unread are applied client-side; --archive-filter is applied server-side.`,
)
.action(showChannelThreads)

const members = channel
.command('members')
.description('Channel membership operations (list, add, remove, set)')

members
.command('list <channel-ref>', { isDefault: true })
.description("List a channel's members and groups fully present in the channel")
.option('--json', 'Output as JSON')
.option('--ndjson', 'Output as newline-delimited JSON')
.option('--full', 'Include all fields in JSON output')
.addHelpText(
'after',
`
Examples:
tdc channel members 12345
tdc channel members "general" --json
tdc channel members add 12345 alice group:Design
tdc channel members remove 12345 alice
tdc channel members set 12345 group:Squad --apply

Notes:
"Groups fully in channel" lists groups whose entire current membership is
already in the channel — a hint, not a persistent link.`,
)
.action(listChannelMembers)

members
.command('add <channel-ref> [refs...]')
.description('Add users and/or groups to a channel')
.option('--dry-run', 'Show what would change without changing')
.option('--json', 'Output result as JSON')
.option('--full', 'Include the full updated channel in JSON output')
.addHelpText(
'after',
`
Examples:
tdc channel members add 12345 alice@doist.com bob@doist.com
tdc channel members add "general" group:Frontend
tdc channel members add 12345 alice group:Design id:789 --json

Notes:
Refs accept user identifiers (id:N, email, name) or "group:<ref>" to expand
a group to its current members. Group expansion is one-shot — users added
later to the group will not auto-join the channel.`,
)
.action(addChannelMembers)

members
.command('remove <channel-ref> [refs...]')
.description('Remove users and/or groups from a channel')
.option('--dry-run', 'Show what would change without changing')
.option('--json', 'Output result as JSON')
.option('--full', 'Include the full updated channel in JSON output')
.addHelpText(
'after',
`
Examples:
tdc channel members remove 12345 alice@doist.com
tdc channel members remove "general" group:Frontend

Notes:
Refs accept user identifiers (id:N, email, name) or "group:<ref>" to expand
a group to its current members.`,
)
.action(removeChannelMembers)

members
.command('set <channel-ref> [refs...]')
.description('Replace channel membership with the resolved set of refs')
.option('--apply', 'Actually mutate (otherwise dry-run)')
.option('--include-self', 'Allow set to remove the acting user')
.option('--dry-run', 'Force dry-run (default behaviour)')
.option('--json', 'Output result as JSON')
.option('--full', 'Include the full updated channel in JSON output')
.addHelpText(
'after',
`
Examples:
tdc channel members set 12345 group:Frontend group:Design
tdc channel members set "general" alice bob carol --apply
tdc channel members set 12345 group:Squad --apply --include-self

Notes:
Dry-run by default. Pass --apply to mutate.
Refuses to remove the acting user unless --include-self is also passed.
Group expansion is one-shot — users added later to a referenced group will
not auto-join the channel.`,
)
.action(setChannelMembers)
}
Loading
Loading