twist-cli · v3.0.13 · 2026-09-28 · sha256 fe9542abfd33bc07
twist-cli v3.0.13A
Immutable. This exact content never changes and is served at /api/v1/blob/fe9542abfd33bc07.
---
name: twist-cli
description: "Twist messaging CLI. View and respond to inbox threads, channel threads, direct messages, mentions, and group conversations; search, react, archive, mute, and manage workspaces. Use when the user mentions Twist, asks about their inbox, mentions, threads, DMs, channels, or wants to read or send Twist messages."
license: MIT
metadata:
author: Doist
version: "3.0.13"
---
# Twist CLI (tw)
Access Twist messaging via the `tw` CLI. Use when the user asks about their Twist workspaces, threads, messages, or wants to interact with Twist in any way.
## Setup
```bash
tw auth login # OAuth login (opens browser, read-write)
tw auth login --read-only # OAuth login with read-only scope
tw auth login --callback-port <n># Override the local OAuth callback port (default 8766)
tw auth login --json # Emit a JSON envelope for scripted / agent use
tw auth login --ndjson # Emit an NDJSON envelope for scripted / agent use
tw auth token # Save API token manually (prompts securely; scope unknown, assumed write-capable)
tw auth status # Verify authentication + show mode
tw auth status --json # Full status payload as JSON (--ndjson also supported)
tw auth status --user <ref> # Target a specific stored account (id, id:<n>, or display name)
tw --user <ref> auth <status|logout|token view> # Equivalent to passing --user after the subcommand; other commands accept the flag but ignore it
tw auth logout # Remove saved token and auth metadata
tw auth logout --json # Emits `{"ok": true}` (--ndjson is silent)
tw auth logout --user <ref> # Target a specific stored account; mismatched ref errors with ACCOUNT_NOT_FOUND
tw auth token view # Print the saved token to stdout (pipe-safe; refuses if TWIST_API_TOKEN is set)
tw auth token view --user <ref> # Print the saved token for a specific stored account
tw account [list|current|use <ref>|remove <ref>] # Manage stored accounts; all support --json/--ndjson
# current's payload is {id, label, authMode, authScope, source:"config"} | {source:"env"} | {source:"legacy"}
tw auth login # Re-running auth login with a different OAuth grant adds a NEW account; default stays pinned unless none was set
tw workspaces # List available workspaces
tw workspace use <ref> # Set current workspace
tw completion install # Install shell completions
tw config view # Show the current CLI configuration file (token masked)
tw config set <key> <value> # Set a user preference (e.g. unarchive-new-threads true)
tw doctor # Diagnose CLI setup and environment issues
tw update # Update CLI to latest version
tw changelog # Show recent changelog entries
```
Stored auth uses the system credential manager when available. If secure storage is unavailable, `tw` warns and falls back to `~/.config/twist-cli/config.json`. `TWIST_API_TOKEN` always takes priority over the stored token, and legacy plaintext config tokens are migrated automatically when secure storage is available.
In read-only mode (`tw auth login --read-only`), commands that modify Twist data (reply, archive, react, delete, etc.) are blocked by the CLI. Externally provided tokens (`TWIST_API_TOKEN` or `tw auth token`) are treated as unknown scope and assumed write-capable.
## View by URL
```bash
tw view <url> # View any Twist entity by URL
```
Routes automatically based on URL structure:
- Message URL → `tw msg view`
- Conversation URL → `tw conversation view`
- Thread+comment URL → `tw thread view` (comment ID extracted from URL)
- Thread URL → `tw thread view`
All target command flags pass through (e.g. `--json`, `--raw`, `--full`).
## Inbox
```bash
tw inbox # Show inbox threads
tw inbox --unread # Only unread threads
tw inbox --archive-filter all # Show active + done threads
tw inbox --archive-filter archived # Show only done threads
tw inbox --channel <filter> # Filter by channel name (fuzzy)
tw inbox --since <date> # Filter by date (ISO format)
tw inbox --limit <n> # Max items (default: 50)
```
## Threads
```bash
tw thread <thread-ref> # View thread (shorthand for view)
tw thread view <thread-ref> # View thread with comments
tw thread view <ref> --comment <id> # View a specific comment
tw thread view <url-with-/c/id> # Comment ID extracted from URL
tw thread view <ref> --unread # Show only unread comments
tw thread view <ref> --context 3 # Include 3 read comments before unread
tw thread view <ref> --limit 20 # Limit number of comments
tw thread view <ref> --since <date> # Comments newer than date
tw thread view <ref> --raw # Show raw markdown
tw thread create <channel-ref> "Title" "content" # Create a new thread
tw thread create <channel-ref> "Title" "content" --json # Create and return as JSON
tw thread create <channel-ref> "Title" "content" --json --full # Include all thread fields
tw thread create <channel-ref> "Title" "content" --notify 123,456 # Notify specific users
tw thread create <channel-ref> "Title" "content" --unarchive # Land thread in author's Inbox (overrides default Twist auto-archive)
tw thread create <channel-ref> "Title" "content" --no-unarchive # Force archive even when userSettings.unarchiveNewThreads=true
tw thread create <channel-ref> "Title" "content" --dry-run # Preview without posting
tw thread create <channel-ref> "Title" --file ./a.png # Attach a file (repeatable; content optional)
tw thread reply <ref> "content" # Post a comment (notifies EVERYONE_IN_THREAD by default)
tw thread reply <ref> "content" --notify EVERYONE # Notify all workspace members
tw thread reply <ref> "content" --notify 123,id:456 # Notify specific user IDs
tw thread reply <ref> "content" --json # Post and return comment as JSON
tw thread reply <ref> "content" --json --full # Include all comment fields
tw thread reply <ref> "content" --close # Reply and close the thread
tw thread reply <ref> "content" --reopen # Reply and reopen a closed thread
tw thread reply <ref> "content" --file ./a.png # Attach a file (repeatable; content optional)
tw thread done <ref> # Archive thread (mark done)
tw thread done <ref> --json # Archive and return status as JSON
tw thread mute <ref> # Mute thread for 60 minutes (default)
tw thread mute <ref> --minutes 480 # Mute for custom duration
tw thread mute <ref> --json # Mute and return { id, mutedUntil } as JSON
tw thread mute <ref> --json --full # Mute and return full thread as JSON
tw thread unmute <ref> # Unmute a muted thread
tw thread unmute <ref> --json # Unmute and return { id, mutedUntil } as JSON
tw thread delete <ref> # Preview thread deletion (requires --yes to execute)
tw thread delete <ref> --yes # Permanently delete a thread
tw thread delete <ref> --yes --json # Delete and return status as JSON
tw thread rename <ref> "New title" # Rename a thread (change its title)
tw thread rename <ref> "New title" --json # Rename and return { id, title } as JSON
tw thread rename <ref> "New title" --json --full # Rename and return full thread as JSON
tw thread update <ref> "New body" # Update a thread's body (the first post)
echo "New body" | tw thread update <ref> # Update body from stdin
tw thread update <ref> "New body" --dry-run # Preview without updating
tw thread update <ref> "New body" --json # Update and return { id, content } as JSON
tw thread update <ref> "New body" --json --full # Update and return full thread as JSON
```
Default `--notify` for reply is EVERYONE_IN_THREAD, which may notify more people than intended. Before posting, confirm with the user whether specific people should be notified instead (via `--notify <user-ids>`). Options: EVERYONE, EVERYONE_IN_THREAD, or comma-separated ID refs.
`--notify` automatically resolves IDs: group IDs are routed to the `groups` API field, user IDs to `recipients`. No special syntax needed.
## Thread Comments
```bash
tw comment <comment-ref> # View a comment (shorthand for view)
tw comment view <comment-ref> # View a single thread comment
tw comment view <comment-ref> --raw # Show raw markdown
tw comment view <comment-ref> --json # Output as JSON
tw comment view <comment-ref> --ndjson # Output as newline-delimited JSON
tw comment view <comment-ref> --json --full # Include all fields in JSON output
tw comment update <comment-ref> "new content" # Update a thread comment
tw comment update <comment-ref> "content" --json # Update and return updated comment as JSON
tw comment update <comment-ref> "content" --json --full # Include all comment fields
tw comment delete <comment-ref> # Delete a thread comment
tw comment delete <comment-ref> --json # Delete and return status as JSON
```
## Conversations (DMs/Groups)
```bash
tw conversation unread # List unread conversations
tw conversation <conversation-ref> # View conversation (shorthand for view)
tw conversation view <conversation-ref> # View conversation messages
tw conversation with <user-ref> # Find your 1:1 DM with a user
tw conversation with <user-ref> --snippet # Include the latest message preview
tw conversation with <user-ref> --include-groups # List any conversations with that user
tw conversation reply <ref> "content" # Send a message
tw conversation reply <ref> "content" --json # Send and return message as JSON
tw conversation reply <ref> "content" --json --full # Include all message fields
tw conversation reply <ref> "content" --file ./a.png # Attach a file (repeatable; content optional)
tw conversation done <ref> # Archive conversation
tw conversation done <ref> --json # Archive and return status as JSON
tw conversation mute <ref> # Mute conversation for 60 minutes (default)
tw conversation mute <ref> --minutes 480 # Mute for custom duration
tw conversation mute <ref> --json # Mute and return { id, mutedUntil } as JSON
tw conversation mute <ref> --json --full # Mute and return full conversation as JSON
tw conversation unmute <ref> # Unmute a muted conversation
tw conversation unmute <ref> --json # Unmute and return { id, mutedUntil } as JSON
```
Alias: `tw convo` works the same as `tw conversation`.
## Conversation Messages
```bash
tw msg <message-ref> # View a message (shorthand for view)
tw msg view <message-ref> # View a single conversation message
tw msg update <ref> "content" # Edit a conversation message
tw msg update <ref> "content" --json # Edit and return updated message as JSON
tw msg update <ref> "content" --json --full # Include all message fields
tw msg delete <ref> # Delete a conversation message
tw msg delete <ref> --json # Delete and return status as JSON
```
Alias: `tw message` works the same as `tw msg`.
## Search
```bash
tw mentions # Show content mentioning current user
tw mentions --since 2026-04-01 --all # Fetch every mention since a date
tw mentions --type threads --json # Limit mentions to threads
tw search "query" # Search content
tw search "query" --type threads # Filter: threads, messages, or all
tw search "query" --author <ref> # Filter by author
tw search "query" --to <ref> # Messages sent to user
tw search "query" --title-only # Search thread titles only
tw search "query" --mention-me # Results mentioning current user
tw search "query" --conversation <refs> # Limit to conversations (comma-separated refs)
tw search "query" --since <date> # Content from date
tw search "query" --until <date> # Content until date
tw search "query" --channel <refs> # Filter by channel refs (comma-separated)
tw search "query" --limit <n> # Max results (default: 50)
tw search "query" --cursor <cur> # Pagination cursor
tw search "query" --all # Fetch all result pages
```
## Users, Channels & Groups
```bash
tw user # Show current user info
tw user --json # JSON output
tw user --json --full # Include all fields in JSON output
tw users # List active workspace users
tw users --search <text> # Filter by name/email
tw users --include-removed # Include users removed from the workspace
tw channels # List active joined workspace channels (alias of: tw channel list)
tw channels --state all # Include archived joined channels too
tw channels --scope discoverable # Active public channels you can see but have not joined
tw channels --scope public --state all --json # All visible public channels, with joined status
tw channel threads <channel-ref> # List threads in a channel (fuzzy name, id:, numeric ID, or URL)
tw channel threads "general" --unread # Only unread threads
tw channel threads <ref> --archive-filter all # Include archived threads (active|archived|all)
tw channel threads <ref> --since 2026-01-01 # Filter by last-updated date (ISO)
tw channel threads <ref> --limit 20 # Max threads per page (default: 50)
tw channel threads <ref> --limit 20 --cursor <cursor-from-prev> # Paginate
tw channel threads <ref> --json # { results, nextCursor } with isUnread + url
tw channel create "Name" # Create a new (public) channel in the default/current workspace
tw channel create "Name" --workspace <ref> # Target a different workspace
tw channel create "Name" --private # Create a private channel
tw channel create "Name" --description "..." # Set channel description
tw channel create "Name" --json --full # Output created channel as JSON (all fields)
tw channel delete <channel-ref> --yes # Delete a channel (requires --yes; usually admin-only on Twist)
tw channel delete <ref> --workspace <ref> --yes # Target a different workspace
tw channel delete <ref> --dry-run # Preview deletion
tw channel archive <channel-ref> # Archive a channel (no-op if already archived)
tw channel unarchive id:<id> # Unarchive a channel (pass id:/numeric ref for archived channels)
tw channel members <channel-ref> # List members + groups whose membership ⊆ channel
tw channel members <channel-ref> --json # JSON with id, name, members, groupsFullyInChannel
tw channel members add <channel-ref> <ref...> # Add users and/or group:<ref> (group expansion is one-shot)
tw channel members add <channel-ref> alice group:Frontend # Mix of user refs and group: prefix
tw channel members add <channel-ref> id:123 --dry-run # Preview the diff
tw channel members remove <channel-ref> <ref...> # Remove users and/or group:<ref>
tw channel members set <channel-ref> <ref...> # Replace membership; dry-run by default
tw channel members set <channel-ref> group:Squad --apply # Mutate. Refuses to remove self unless --include-self
tw groups # List workspace groups
tw groups --search "frontend" # Filter groups by name (case-insensitive)
tw groups --json # JSON output
tw groups --json --full # Include all fields in JSON output
tw groups view <group-ref> # Show group with member details
tw groups view <ref> --json # JSON output with id, name, workspaceId, members
tw groups view <ref> --json --full # Include all fields in JSON output
tw groups create "Name" # Create a new group
tw groups create "Name" --users alice@doist.com,bob@doist.com # Create with members
tw groups create "Name" --json # Output created group as JSON
tw groups rename <group-ref> "New name" # Rename a group
tw groups rename <ref> "Name" --json # Output renamed group as JSON
tw groups delete <group-ref> --yes # Delete a group (requires --yes)
tw groups delete <ref> --dry-run # Preview deletion
tw groups add-user <group-ref> user1 user2 # Add users to a group
tw groups add-user <ref> a@d.com,b@d.com # Comma-separated refs
tw groups add-user <ref> id:123 --json # Output result as JSON
tw groups remove-user <group-ref> user1 user2 # Remove users from a group
tw groups remove-user <ref> id:123,id:456 # Comma-separated ID refs
```
If a channel is not found in `tw channels`, widen with broader listings such as `tw channels --scope public`, then `tw channels --scope public --state all`. Check `tw channels --help` for other available filters.
`tw 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.
`tw channel members add`, `remove`, and `set` accept user refs (id:N, email, name) and `group:<ref>` mixed in any order. Groups are expanded to their current users at call time — the group is **not** persistently linked to the channel, so users added later to the group will not auto-join. `set` replaces the channel's membership with the resolved ref set; it is dry-run by default, pass `--apply` to mutate and `--include-self` to allow the diff to remove the acting user. `tw channel members` (with no subcommand) shows the user list plus a "groups fully in channel" hint — groups whose entire membership is currently present in the channel.
## Away Status
```bash
tw away # Show current away status
tw away set <type> [until] # Set away (type: vacation, parental, sickleave, other)
tw away set vacation 2026-03-20 # Away until March 20
tw away set vacation 2026-03-20 --from 2026-03-15 # Custom start date
tw away clear # Clear away status
```
## Reactions
```bash
tw react thread <ref> 👍 # Add reaction to thread
tw react comment <ref> +1 # Add reaction (shortcode)
tw react message <ref> heart # Add reaction to DM message
tw react thread <ref> 👍 --json # Output result as JSON
tw unreact thread <ref> 👍 # Remove reaction
tw unreact thread <ref> 👍 --json # Output result as JSON
```
Supported shortcodes: +1, -1, heart, tada, smile, laughing, thinking, fire, check, x, eyes, pray, clap, rocket, wave
## Shell Completions
```bash
tw completion install # Install tab completions (prompts for shell)
tw completion install bash # Install for specific shell
tw completion install zsh
tw completion install fish
tw completion uninstall # Remove completions
```
### Diagnostics
```bash
tw doctor # Run local + network diagnostics
tw doctor --offline # Skip Twist and npm network checks
tw doctor --json # JSON output with per-check results
```
### Configuration
```bash
tw config view # Pretty-printed config, token masked, labels actual token source
tw config view --json # Raw JSON, token masked
tw config view --show-token # Include the full token
tw config set unarchive-new-threads true # Persist: always unarchive new threads so they land in your Inbox
tw config set unarchive-new-threads false # Persist: keep Twist's default (thread auto-archived for author)
```
User preferences are stored under `userSettings` in the config file. Currently supported keys: `unarchive-new-threads`. The flag on `tw thread create` (`--unarchive` / `--no-unarchive`) overrides this default per-invocation.
### Update
```bash
tw update # Update CLI to latest version
tw update --check # Check for updates without installing, show channel
tw update --check --json # Same, JSON envelope
tw update --check --ndjson # Same, newline-delimited JSON envelope
tw update --channel # Show current update channel
tw update switch --stable # Switch to stable release channel
tw update switch --pre-release # Switch to pre-release (next) channel
tw update switch --pre-release --json # Same, JSON envelope
tw update switch --pre-release --ndjson # Same, newline-delimited JSON envelope
```
### Changelog
```bash
tw changelog # Show last 5 versions
tw changelog -n 3 # Show last 3 versions
tw changelog --count 10 # Show last 10 versions
```
## Global Options
```bash
--no-spinner # Disable loading animations
--progress-jsonl # Machine-readable progress events (JSONL to stderr)
--progress-jsonl=<path> # Same, but write events to <path> instead of stderr
--progress-jsonl <path> # Same as above (space-separated form also accepted)
--accessible # Add text labels to color-coded output (also: TW_ACCESSIBLE=1)
--non-interactive # Disable interactive prompts (auto-detected when stdin is not a TTY)
--interactive # Force interactive mode even when stdin is not a TTY
```
## Output Formats
All list/view commands support:
```bash
--json # Output as JSON
--ndjson # Output as newline-delimited JSON (for streaming)
--full # Include all fields (default shows essential fields only)
```
## Dry Run
Mutating commands accept `--dry-run` to preview the operation without making the change. Where a command performs pre-flight validation (e.g. fetching the target thread to check channel access or ownership), those checks still run in dry-run — only the mutating write is skipped. Commands that have no pre-flight validation parse the reference and print the preview without hitting the API. The preview is structured:
```
[dry-run] Would <action>:
<Key>: <resolved value>
...
Run without --dry-run to execute.
```
## Reference System
Commands accept flexible references:
- **Numeric IDs**: `123` or `id:123`
- **Twist URLs**: Full `https://twist.com/...` URLs (parsed automatically)
- **Fuzzy names**: For workspaces/users - `"My Workspace"` or partial matches
## Piping Content
Commands that accept content (`thread create`, `thread reply`, `comment update`, `conversation reply`, `msg update`) auto-detect piped stdin:
```bash
cat notes.md | tw thread reply <ref>
tw thread create <channel-ref> "Title" < body.md
echo "Quick reply" | tw conversation reply <ref>
```
If no content argument is provided and no stdin is piped, the CLI opens `$EDITOR` for interactive input. In non-TTY environments (e.g. when called by an agent or in a pipeline), the editor is automatically skipped and the command fails fast with an actionable error message. Use `--non-interactive` to force this behavior even in a TTY, or `--interactive` to override auto-detection.
## Common Workflows
**View by URL (auto-routes to the right command):**
```bash
tw view https://twist.com/a/1585/ch/100/t/200 # View thread
tw view https://twist.com/a/1585/ch/100/t/200/c/300 # View comment
tw view https://twist.com/a/1585/msg/400 # View conversation
tw view https://twist.com/a/1585/msg/400/m/500 --json # View message as JSON
```
**Check inbox and respond:**
```bash
tw inbox --unread --json
tw thread view <id> --unread
tw thread reply <id> "Thanks, I'll look into this."
tw thread done <id>
```
**Search and review:**
```bash
tw mentions --since 2026-04-01 --all --json
tw search "deployment" --type threads --json
tw thread view <thread-id>
```
**Check DMs:**
```bash
tw conversation unread --json
tw conversation view <conversation-id>
tw conversation with "Alice Example"
tw conversation reply <id> "Got it, thanks!"
```