# App Veil Source: https://docs.tuple.app/application-preferences/macos-preferences-app-veil App Veil hides windows from your pair while you're sharing your screen. It can also hide all system notifications while you're pairing with the "Hide all notifications" setting. A shared screen with an app window veiled from other call participants Hidden windows have a dashed orange border around them to remind you they're not shared: A veiled window outlined with a dashed orange border You can configure this feature in Tuple's Settings. By default, we hide Messages, and Keychain Access. We also hide Telegram, and 1Password if you have those apps installed. # Reveal veiled windows To show a veiled window to your pairing partner, click on the dropdown menu at the bottom right of the window. Select "Reveal This Window" and the veiled window will become visible. A veiled window menu with Reveal This Window selected When the window is revealed, the label at the bottom right will say "Visible" and the outline of the window will be purple. A revealed window labeled Visible with a purple outline # Audio Source: https://docs.tuple.app/application-preferences/macos-preferences-audio To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on macOS You can also use the keyboard shortcut **cmd + ,** to bring up the menu. Then select the Audio tab: Settings window with the Audio tab selected on macOS ### Input/Output Device You can select audio devices that you want Tuple to use for input and output, or leave it as the System Default. If you explicitly set these devices here, then the device you select will always be used if it's available. You can see which device is selected even if disconnected by looking at the text below each dropdown. **Note:** We recommend that you use the System Default values to avoid confusion. This is because most controls (e.g. keyboard shortcuts for volume/muting) will affect the system's default devices, which might lead to confusion if you have Tuple set to override it. If you have a consistent pairing setup which is different than your usual devices, or if you want to route audio through a third-party app (e.g. [Krisp.ai](https://krisp.ai/)), then you could set these manually, but please be aware that this may cause unexpected behavior with macOS audio controls as they only control the system default devices. ### Suppress my background noise To remove unwanted noises from the background of your audio, you can use this setting to toggle our noise suppression feature. ### Play ringtones You can disable this setting if you prefer not to hear any ringing sounds when receiving a call and rely on the popup instead. This setting also controls join sounds for when people are entering or leaving active calls/rooms. ### Start muted If you prefer to use a different service for audio during your pairing sessions, you can enable this setting to have Tuple always start calls muted. ### Notify me when These checkboxes control the in-call notifications that fire from your audio state. Both are enabled by default. * **Speaking while muted** — shows the [speaking-while-muted notification](/pairing-with-tuple/speaking-while-muted-notification) when Tuple detects your voice while your microphone is muted. * **Audio output is muted** — shows a notification while your call audio output is muted, so you don't miss your pair speaking. The card auto-dismisses when you unmute. Choosing **Never Show Again** from a notification's cancel menu turns the matching checkbox off here. Re-enable a notification by ticking the checkbox again. # General Source: https://docs.tuple.app/application-preferences/macos-preferences-general To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on macOS You can also use the keyboard shortcut **cmd + ,**. This will bring up the menu: Settings window with the General tab selected on macOS ## Appearance Select Light mode, Dark mode, or tell Tuple to pick up your system preferences. ## Menu bar icon Choose which icon Tuple displays in the macOS menu bar. You can pick between the default **Tuple Logo** and **Pear Buddy**. The icon adapts to your system appearance and tints white during active calls. ## Behavior **Launch Tuple at login** When this feature is enabled, Tuple will automatically launch when you log into your computer after a restart or explicit logout. Note that it will not launch if your computer is merely locked or asleep. **Always show dock icon** With this enabled, Tuple will always have an icon in the dock / command-tab carousel. When disabled, a Tuple icon will only appear in those locations when you're on a call. **Enable call feedback popup** When this feature is enabled, the call feedback form will show up after you conclude a Tuple call. ## Privacy **Show my friends who I'm pairing with** If selected, people you and your pair both know will be able to see your call's participants in their contact list. You can learn more [here](/pairing-with-tuple/showing-who-youre-pairing-with). **Report call statistics** When this setting is on, we get occasional snapshots of the metrics from calls such as frame rate, latency, etc. # Hotkeys Source: https://docs.tuple.app/application-preferences/macos-preferences-hotkeys To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on macOS You can also use the keyboard shortcut **cmd + ,** to bring up the menu. Then select the Hotkeys tab: Settings window with the Hotkeys tab selected on macOS Here you can set custom hotkeys to trigger various actions when using Tuple. Just click "Record Shortcut" and enter the sequence you'd like to use for each action. You can scroll for even more options than shown in this image. # Integrations Source: https://docs.tuple.app/application-preferences/macos-preferences-integrations To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on macOS You can also use the keyboard shortcut **cmd + ,** to bring up the menu. Then select the Integrations tab: Settings window with the Integrations tab selected on macOS This tab allows you to easily enable Tuple's Slack and Google Calendar integrations (the Apple Calendar integration is enabled by default). If you're on macOS 26 or later, you'll also see a pane to disable the Spotlight integration. You can also enable the **CLI Server** here. Select it to install the `tuple` command-line tool and to allow or revoke its access to your calls and contacts. Learn more on the [CLI page](/cli/overview). You can learn more about our integrations [here](/managing-your-account/adding-integrations). # Screen share Source: https://docs.tuple.app/application-preferences/macos-preferences-screen-share To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on macOS You can also use the keyboard shortcut **cmd + ,** to bring up the menu. Then select the Screen Share tab: Settings window with the Screen Share tab selected on macOS ## Viewing resolution This setting dictates the resolution that will be used when someone else shares their screen with you. If you adjust this setting while a session is in progress, it will be utilized in the next call. On a call where faster interactions are necessary, you might want to set this lower; if you don't need to do a lot of typing or clicking but want high viewing quality, then set it higher. ## Upscale content On Apple Silicon Macs, Tuple can upscale incoming screen share video to make text and UI elements look sharper on your display. This is a local, per-viewer setting — it only affects what *you* see and has no impact on the resolution other participants receive. You can toggle this from the Screen Share preferences pane, or directly during a call from the **Upscale Content** option in the screen share window's settings menu. ## When Sharing My Screen ### Open links sent to my clipboard If you don't like the fact that guests can force a URL to open on your computer, you can disable this. URLs will still be sent to your clipboard, but you have to explicitly paste them into a browser to open them. ## Annotations ### Persist until right click If selected, any drawings created with the paint mouse mode will persist until you right click. You can also change this while on a call via the settings dropdown on the screen share window. ## Participant Cursors ### Always show other cursors If this setting is enabled, you'll see other participants' cursors all the time. If it's disabled, you'll only see them if another participant enters control mode. ### Automatically show name labels If this setting is enabled, you'll see name labels appear on other participants' cursors whenever they enter a call, take control, or begin annotating on the screen. If it's disabled, name labels will only be shown if you hover over another participant's cursor. # Transcription Source: https://docs.tuple.app/application-preferences/macos-preferences-transcription Tuple's local transcription feature is in **alpha**. Aspects of the feature might change as we continue to iterate on feedback. To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on macOS You can also use the keyboard shortcut **cmd + ,** to bring up the menu. Then select the Transcription tab: Transcription settings tab This tab controls everything related to [transcribing calls](/pairing-with-tuple/transcribing-calls): which on-device model is used, whether transcription starts automatically, and how to bring an AI agent into your calls. Transcription runs entirely on your machine, and transcripts are saved to a local database. ## Enabling transcription The master switch at the top of the pane turns transcription on or off for the account. While it's off, the rest of the pane is dimmed and the **Transcribe** call control is unavailable. Flipping the switch on requires a downloaded model: * **A model is already downloaded and selected** — transcription activates immediately. * **No model is downloaded yet** — Tuple opens a prompt to install one. Download a model to enable the feature. You can also reach the same model picker later via the [Supported models](#supported-models) list to change models without toggling the master switch. ## Supported models Tuple ships with a list of pre-optimized transcription models that you can download on demand. These are [GGML](https://github.com/ggml-org/ggml) models designed to be used by [whisper.cpp](https://github.com/ggml-org/whisper.cpp), which have been further optimized to utilize [CoreML](https://developer.apple.com/documentation/coreml). The base models have all been sourced from the [main whisper.cpp repository](https://huggingface.co/ggerganov/whisper.cpp/tree/main). | Model | Description | Size | Required VRAM | GGML Model | | --------- | -------------------------------------- | ------- | ------------- | -------------------------- | | **Turbo** | Fast, accurate, multilingual | 1.75 GB | 6 GB | `ggml-large-v3-turbo-q5_0` | | **Light** | English only, slower, less VRAM needed | 650 MB | 2 GB | `ggml-small.en` | These models are optimized to run on Apple Silicon machines, and have **not** been tested on or optimized for Intel-based machines. Hosted models are stored in `~/Library/Application Support/app.tuple.app/Transcription Models/`. Switching models leaves the previously downloaded model on disk; use **Remove** in the picker to delete one. If a download fails, or if a model file is corrupt or can't be installed, a red error icon appears next to the model in the picker. Click the icon to see the specific problem and the recommended fix: * **Model is corrupted** — remove the model and download it again (or, for a custom model, choose a new file). * **Download failed** — the download didn't complete; try again. * **Not enough disk space** — free up space on your disk and try the download again. Tuple does not retry failed downloads automatically — start the download again from the picker. ### Custom model If you'd rather use your own [GGML](https://github.com/ggml-org/ggml) model, the **Custom model** row at the bottom of the picker lets you choose a `.bin` or `.gguf` model file from disk with the **Choose…** button. The file you pick becomes your selected model right away. Custom models need a matching `.mlmodelc` CoreML sidecar installed alongside the GGML file. Tuple's built-in models include this sidecar automatically; a custom GGML file does not. If the CoreML bundle isn't present next to the model, Whisper throws an error when Tuple tries to load it. ## Transcript storage Transcripts are stored in a local database on your machine, not as individual files. The Transcription tab shows the database path in the **Transcript DB** field. The database location can't be changed from settings. To read, search, and export past calls, use the `tuple` CLI — see [Accessing your transcripts](/pairing-with-tuple/transcribing-calls#accessing-your-transcripts) and [Searching past calls](/pairing-with-tuple/searching-past-calls). ## Automatically transcribe calls When this checkbox is on, Tuple starts transcribing the call as soon as you join, just as if you'd clicked the **Transcribe** call control yourself. Other participants will be informed that you've started transcribing. This checkbox is **on by default**. It's only available when transcription is enabled and a model is installed. ## Bringing an agent into your calls The Transcription tab includes a **Connect to Agent** card explaining how to attach an AI agent (Claude Code, Codex, or any [harness](/cli/connect#supported-harnesses)) to a call. Copy the `tuple` command it shows and run it in your terminal during a call; the agent then follows the live transcript and participates in real time. If the Tuple CLI isn't installed yet, the card shows an **Install** button instead. See [Connect an AI agent](/cli/connect) for full details. # Triggers Source: https://docs.tuple.app/application-preferences/macos-preferences-triggers Triggers let you customize Tuple by running your own code in response to call lifecycle events. Learn more in the [Triggers documentation](/triggers/building-a-trigger). # Webcam Source: https://docs.tuple.app/application-preferences/macos-preferences-webcam To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on macOS You can also use the keyboard shortcut **cmd + ,** to bring up the menu. Then select the Webcam tab: Settings window with the Webcam tab selected on macOS ### Preferred device Which device Tuple should use by default when sharing your webcam video. ### Resolution This is the resolution for webcam video shared while you are pairing. Setting the resolution will automatically change the resolution on any current and future calls. If you have a high-speed connection and want maximum webcam quality, set this higher. If you're working with a relatively poor connection, set your preference to a lower resolution to reduce latency on the call. The settings correspond to the following resolution: * Low: 160x120 * Medium: 320x240 * High: 640x480 ### When in a call: #### Enable my webcam automatically When this setting is on, Tuple will automatically share your webcam video when you join or start a call. #### Show preview before sharing If selected, Tuple will ask you to confirm a preview of your webcam before sharing. If deselected, clicking the "Share Webcam" button will share your webcam immediately. ## Background blur and video effects On supported Macs, macOS includes free background blur and other camera effects that work with Tuple. While your webcam is active, open the camera indicator in the menu bar and enable **Portrait**. On macOS Ventura, open **Control Center**, click **Video Effects**, then enable **Portrait**. Availability depends on your Mac and camera; see [Apple's requirements](https://support.apple.com/105117). See [Using effects on your webcam](/pairing-with-tuple/sharing-a-webcam-video#using-effects-on-your-webcam) for screenshots and the full walkthrough. # App Veil Source: https://docs.tuple.app/application-preferences/windows-preferences-app-veil To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on Windows You can also use the keyboard shortcut **ctrl+,**. This will bring up the settings window. From here, you can select the **App Veil** tab: Settings window with the App Veil tab selected on Windows In this tab, you can select the apps that you want to be hidden when you share your screen. Click the button marked "Add..." to bring up a list of apps on your machine: App Veil dialog listing installed apps that can be added to the veil list Click the "Add" button next to an app to add it to the list of things to veil. If you need to temporarily unveil a veiled app, you can toggle it off via the main settings view: App Veil settings with a veiled app toggled on or off When you share your screen with a veiled app, this is what you'll see: Your own view of a shared screen containing a veiled app on Windows ...and this is what others on the call will see: What other call participants see when a Windows app is veiled # Audio Source: https://docs.tuple.app/application-preferences/windows-preferences-audio To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on Windows You can also use the keyboard shortcut **ctrl+,**. This will bring up the settings window. You can then select the **Audio** tab: Settings window with the Audio tab selected on Windows ### Input/Output Device You can select audio devices that you want Tuple to use for input and output, or leave it as the System Default. If you explicitly set these devices here, then the device you select will always be used if it's available. You can see which device is selected even if disconnected by looking at the text below each dropdown. **Note:** We recommend that you use the System Default values to avoid confusion. This is because most controls (e.g. keyboard shortcuts for volume/muting) will affect the system's default devices, which might lead to confusion if you have Tuple set to override it. If you have a consistent pairing setup which is different than your usual devices, or if you want to route audio through a third-party app (e.g. [Krisp.ai](https://krisp.ai/)), then you could set these manually, but please be aware that this may cause unexpected behavior with system audio controls as they only control the default devices. ### Start muted If you prefer to use a different service for audio during your pairing sessions, you can enable this setting to have Tuple always start calls muted. ### Legacy Audio Engine Tuple for Windows utilizes a custom subsystem for audio input and output. However, if you're experiencing issues, you can choose to enable the older, legacy audio subsystem. This shouldn't happen often - please contact [support](mailto:support@tuple.app) if you're experiencing problems. # General Source: https://docs.tuple.app/application-preferences/windows-preferences-general To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on Windows You can also use the keyboard shortcut **ctrl+,**. This will bring up the settings window: Settings window with the General tab selected on Windows ### **Appearance** Select if you want Tuple to use light or dark mode. ### **Show who I'm pairing with** If selected, people you and your pair both know will be able to see your call's participants in their contact list. You can learn more [here](/pairing-with-tuple/showing-who-youre-pairing-with). ### **Persistent Drawings** If selected, any drawings created with the paint mouse mode will persist until you right click. ### **Launch at login** If selected, Tuple will launch when you log in. # Network Source: https://docs.tuple.app/application-preferences/windows-preferences-network To open your preferences, click your avatar at the bottom left of the Tuple popover and select **Settings** from the dropdown: Tuple avatar menu with Settings selected on Windows You can also use the keyboard shortcut **ctrl+,**. This will bring up the settings window. You can then select the **Network** tab: Settings window with the Network tab selected on Windows ### Force TURN Enabling this will force your calls to use a [TURN server](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT). This is useful if you're on a network that has known P2P issues. ### Force Media Server Enabling this will force Tuple to use a media server for screen sharing. Again, useful on networks with known P2P issues. # Command reference Source: https://docs.tuple.app/cli/commands This page documents every command in the `tuple` CLI. For an introduction and installation instructions, see the [Tuple CLI overview](/cli/overview). ## Global flags These flags work on every command: | Flag | Description | | ------------------------------- | --------------------------------------------------------------------------------- | | `--format default\|json\|table` | Output format. Default is human-friendly; `json` is machine-readable. | | `-H`, `--host PATH` | Path to the Tuple app socket (Unix socket on macOS/Linux, named pipe on Windows). | ## Person arguments Many commands take a `` argument — that's a free-text query, not an ID. The CLI runs a substring match against your contacts' names and emails: * Exactly one match: the command runs. * Zero matches: the command errors. * Multiple matches: the command errors and lists the candidates. ## `tuple contacts` Manage your Tuple contacts. ### `contacts list [query] [flags]` List contacts, sorted by favorites, then status (online > busy > offline), then name. ```text theme={null} tuple contacts list [query] [-q, --query ] [--status online|busy|offline] [--kind teammate|external] [--favorited] [--recent] [-f, --follow] ``` | Flag | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `-q, --query ` | Search by name or email. Equivalent to the positional `[query]` argument — supply one or the other, not both. | | `--status` | Keep only contacts in the given presence state. Accepts `online`, `busy`, or `offline`. `available` is a synonym for `online`. | | `--kind` | Keep only `teammate` (people in your org) or `external` contacts. | | `--favorited` | Keep only favorited contacts. Use `--favorited=false` to keep only unfavorited contacts. | | `--recent` | Keep only people you've contacted recently. | | `-f, --follow` | Stream updates as availability changes. Cannot be combined with any filter flag or a query. | All filter flags compose with AND. An empty result is a success (exit `0`, empty table or `[]`). Invalid `--status` or `--kind` values error with a non-zero exit. ```bash theme={null} tuple contacts list # everyone tuple contacts list ben # filter by name/email substring (positional) tuple contacts list --query ben # same, via the flag tuple contacts list --status online # everyone currently online tuple contacts list --status available --kind teammate tuple contacts list --favorited --recent # favorites you've talked to lately tuple contacts list --favorited=false # everyone who isn't favorited tuple contacts list --follow # stream updates (no filters allowed) ``` ### `contacts get ` Show full details for a single contact. ```bash theme={null} tuple contacts get ben@example.com ``` ### `contacts favorite ` / `contacts unfavorite ` Pin or unpin a contact at the top of your list. ```bash theme={null} tuple contacts favorite alice ``` ### `contacts remove ` Remove someone from your contacts. ### `contacts invite ` Send a Tuple invite to an email address. ```bash theme={null} tuple contacts invite teammate@example.com ``` ## `tuple call` Start, join, and control calls. All call subcommands accept `--call ` to target a specific call; the default is `current` (the active call). ### `call current` Print the call you're currently in. Use this when a script or agent needs the active call id without parsing the full `tuple state` payload. The default output is just the bare call id, so no JSON parser is required. When you're not in a call, the command writes nothing to stdout, prints `not in a call` to stderr, and exits non-zero — branch on the exit code regardless of `--format`. ```bash theme={null} # Capture the current call id in a script — no JSON parser needed id="$(tuple call current)" # Branch on whether you're in a call if tuple call current >/dev/null 2>&1; then echo "in a call"; fi ``` Output formats: * **default** — the bare call id followed by a newline. * **`--format json`** — the full current-call object, byte-for-byte identical to `tuple state`'s `current_call` field (including `local`, `tracks`, `capacity`, `sfu_backed`, `room`, and `recorder`). * **`--format table`** — a short human summary with id, participant count, and participant emails. ### `call start ` Start a 1:1 call with the matched contact. ```bash theme={null} tuple call start ben ``` ### `call join ` Join a contact's active call, or a room by slug or URL. ```bash theme={null} tuple call join alice tuple call join my-team-room tuple call join https://tuple.app/room/my-team-room ``` The argument is resolved as a contact first. If no contact matches, the last path segment of the URL is treated as a room slug. ### `call add ` / `call remove ` Add or remove a participant from the active call. ```bash theme={null} tuple call add carol tuple call remove carol ``` ### `call hang-up` Leave the active call. ### `call mute` / `call unmute` Mute or unmute yourself. ```bash theme={null} tuple call mute && some-long-command && tuple call unmute ``` ## `tuple rooms` List, join, favorite, and create [Tuple rooms](/pairing-with-tuple/using-rooms). Commands that target an existing room accept a slug such as `acme/general` or a full room URL. ### `rooms list [flags]` List your personal rooms and the rooms for every team you belong to. Results are sorted by occupied rooms, favorites, then name. ```bash theme={null} tuple rooms list tuple rooms list --kind personal --occupied tuple rooms list --favorited --members tuple rooms list --limit -1 ``` | Flag | Description | | -------------------- | -------------------------------------------------------------------------------------------- | | `--kind ` | Show only `personal` or `team` rooms. | | `--occupied` | Show only rooms with someone currently present. | | `--favorited[=BOOL]` | Show only favorited rooms, or pass `--favorited=false` to show only unfavorited rooms. | | `--limit ` | Maximum rooms to return. The default cap is `100`; pass `-1` for all rooms. | | `--members` | Include member names. By default, the command shows only the number of members in each room. | ### `rooms join ` Join a room without first trying to resolve the argument as a contact. This is useful for personal rooms, where joining alone is valid. ```bash theme={null} tuple rooms join acme/general ``` ### `rooms favorite ` / `rooms unfavorite ` Pin or unpin a room. ### `rooms create ` Create a team room. ```bash theme={null} tuple rooms create "Engineering touchbase" ``` ## `tuple screen` Inspect the screen being shared in a call. ### `screen capture -o ` Save a JPEG screenshot of the shared screen. ```bash theme={null} tuple screen capture -o screenshot.jpg tuple screen capture -o - # write JPEG bytes to stdout ``` Defaults: `--call current`, `--user current` (whoever is currently sharing). ## `tuple transcription` Control [call transcription](/pairing-with-tuple/transcribing-calls), stream its output, and search, show, or export the transcripts of past calls. The stored-transcript subcommands (`list`, `search`, `export`) work without an active call — the Tuple daemon indexes every transcribed call into a local database as it records. For a walkthrough, see [Searching past calls](/pairing-with-tuple/searching-past-calls). ### `transcription start` / `transcription stop` Start or stop transcription for the active call. ```bash theme={null} tuple transcription start ``` ### `transcription show [call-id] [--follow|--wait] [--interval=DURATION] [--watch-words=…] [--timeout=DURATION] [--cursor=TAG] [--recording UUID] [--with-events] [--without-speech] [--with-speech-markers]` Read a call's transcription records. With no argument, `show` targets the active call; pass any unique prefix of a call ID (as printed by [`transcription list`](#transcription-list) or [`transcription search`](#transcription-search-query)) to read a stored transcript instead. This single command replaces the older `current`, `events`, `text`, and `stream` surfaces. To read exactly one recording session — for example, the specific session a [`call-transcription-complete` trigger](/triggers/api-reference#call-transcription-complete) reports — pass `--recording `. See [Targeting a recording session](#targeting-a-recording-session). `show` has three consumption modes: * **Snapshot** (default): print all records so far, sorted by timestamp, then exit. Good for one-shot reads of a finished call or a quick look at the live one. * **`--follow`**: stream continuously until the call ends, in arrival order. Use this under a per-line wake mechanism (such as Claude Code's Monitor); a plain backgrounded `--follow` won't wake an idle agent. * **`--wait`**: block until the next batch of records, print it, then exit. Loop this in the foreground to follow a call from any agent. Each run resumes where the last left off using a per-call cursor, so there are no gaps or repeats. This is the universal fallback when no per-line wake mechanism is available. If you pass a stored call ID with `--follow` or `--wait` and that call isn't active, `show` prints a one-shot snapshot instead so scripts targeting a specific call degrade cleanly. Human output is one line per record (timestamped, speaker-prefixed for speech). With `--format json` (see [Output formats](#global-flags)), each record is emitted as an NDJSON line — pipe it into log aggregators, agent runners, or `jq`. #### Content toggles By default `show` prints finalized speech. Combine these flags to widen or narrow the feed: | Flag | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--with-events` | Include lifecycle event records (transcription started, user joined, screen sharing started, and so on). | | `--without-speech` | Suppress finalized speech records. Combine with `--with-events` to read just lifecycle events. | | `--with-speech-markers` | Include `transcription_started` / `transcription_dropped` markers. Defaults to on with `--follow` and `--wait` so live readers can see gaps; off for snapshots. Pass `--with-speech-markers=false` to suppress in a live session. | A selection that would emit nothing (for example `--without-speech` alone) is rejected with an error. #### Following flags | Flag | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `-f`, `--follow` | Stream in real time until the call ends. Mutually exclusive with `--wait`. | | `--wait` | Block until the next batch, print it, then exit. Loop to follow without a per-line wake mechanism. Mutually exclusive with `--follow`. | | `--interval DURATION` | Buffer output and flush at most once per interval (e.g. `60s`). Combine with `--wait` to gather one batch per interval; combine with `--follow` to flush a buffered window per interval. | | `--watch-words a,b,c` | Case-insensitive words or phrases that flush a batch early when spoken. Requires `--interval` with `--wait` or `--follow`. Use this so an agent responds immediately when addressed. A word can also be `@` to read watch words from a file (one per line — a live follower reloads them when the file changes), or the literal `*` to match every finalized speech record. See [Switching watch-words mid-stream](#switching-watch-words-mid-stream). | | `--timeout DURATION` | With `--wait`, return an empty result after this much silence so a loop can re-check. Default `30s`. `0` blocks until the next speech. | | `--cursor TAG` | With `--wait`, the named read position saved between runs. Default `default`. Give each concurrent follower its own tag — one reader per tag. | | `--recording UUID` | Read one recording session instead of a whole call (see [Targeting a recording session](#targeting-a-recording-session)). Composes with `--follow` and `--wait`; the stream ends when the session does. | The first `--wait` run for a call (no saved cursor) returns the whole backlog at once as a catch-up. Later runs return only what's new since the previous cursor save. When the call ends, `--wait` and `--follow` emit a terminal `{"kind":"status","status":"call_ended"}` line so the loop knows to stop. ```bash theme={null} # Quick snapshot of the active call's transcript: tuple transcription show # Read a stored call by ID prefix: tuple transcription show a1b2c3d4 # Watch a live call and see lifecycle events as well as speech: tuple transcription show --follow --with-events # Follow loop suitable for any agent harness: while tuple transcription show --wait --interval 60s --watch-words "claude,hey claude"; do : # process the batch this run just printed done ``` #### Switching watch-words mid-stream `--watch-words` accepts three kinds of values, mixed freely in the same comma-separated list: * A literal word or phrase (e.g. `claude`, `hey claude`) — case-insensitive. * `@` — read watch words from a file, one per line. Blank lines are ignored. * `*` — match every finalized speech record. Use this to surface all speech to an agent. When any `@` reference is used with `--follow` or `--wait`, `show` watches those files and reloads them in-place whenever they change. The reload happens mid-stream: the current batch is flushed immediately, and the new watch words take effect on the very next record. There is no restart, no PID lookup, and no signal — whoever wants to switch modes just rewrites the file (atomically, e.g. temp-file-then-rename). This is the mechanism a long-running follower uses to toggle listening modes on a live call — for example, from wake-word-only to react-to-everything and back: ```bash theme={null} # Point a follower at a shared file: echo "hey claude" > /tmp/tuple-watch tuple transcription show --follow --interval 60s --watch-words @/tmp/tuple-watch & # Later, switch that same follower to react to all speech — # no restart, no signal, the follower reloads automatically: echo "*" > /tmp/tuple-watch # Switch back to wake-word-only the same way: echo "hey claude" > /tmp/tuple-watch ``` If a reload fails to read or parse the file, `show` logs the error to stderr and keeps the previous watch words rather than dying mid-call. ### `transcription list` List calls that have a stored transcript, most recent first. Each row shows the start time, the call's title (if set), a short call ID, the transcript segment count, and the participants Tuple resolved from the recording. ```bash theme={null} tuple transcription list tuple transcription list --participant mikey --after 2026-05-11 ``` | Flag | Description | | ---------------------- | ------------------------------------------------------------------- | | `--participant ` | Only calls with a participant whose name or email contains `` | | `--after ` | Only calls starting on or after `` (e.g. `2026-05-19`) | | `--before ` | Only calls starting on or before `` | | `--limit ` | Maximum calls (default `100`; pass `-1` for all) | ### `transcription search ` Full-text search across every indexed transcript. The query uses [SQLite FTS5 syntax](https://www.sqlite.org/fts5.html#full_text_query_syntax): bare terms are ANDed, `"quoted phrases"` match exactly, and `OR`/`NOT` are supported. ```bash theme={null} tuple transcription search "feature flag" tuple transcription search rollback --speaker alex -C 2 ``` | Flag | Description | | ---------------------- | --------------------------------------------------------------------------------------------------------------- | | `--call ` | Only this call. Repeatable; accepts `current` or short call ID prefixes. | | `--recording ` | Only segments from one recording session (see [Targeting a recording session](#targeting-a-recording-session)). | | `--speaker ` | Only segments spoken by someone whose name or email contains `` | | `--participant ` | Only calls with a participant whose name or email contains `` | | `--after ` | Only segments on or after `` | | `--before ` | Only segments on or before `` | | `-C`, `--context ` | Show `n` segments of conversation around each match | | `--limit ` | Maximum matches (default `50`; pass `-1` for all) | To print one call's full transcript, pass its short ID to `transcription show` (documented above) — `show` reads a stored call when given any unique call-ID prefix. ### `transcription export ` Write one flat file per call into ``, named `@.`. Calls without transcript segments are skipped, and existing files are overwritten — re-running refreshes the export. ```bash theme={null} tuple transcription export ~/Documents/tuple-transcripts tuple transcription export ~/Documents/tuple-transcripts --after 2026-05-01 --format md ``` | Flag | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------ | | `--format ` | `md` (default), `text`, or `ndjson`. | | `--call ` | Only this call. Repeatable; accepts `current` or short call ID prefixes. | | `--recording ` | Only one recording session's segments (see [Targeting a recording session](#targeting-a-recording-session)). | | `--participant ` | Only calls with a participant whose name or email contains `` | | `--after ` | Only calls starting on or after `` | | `--before ` | Only calls starting on or before `` | ### `transcription delete ` Permanently delete one stored call's recordings, transcript segments, and events from the local database. The command accepts the short call IDs printed by `transcription list` and `transcription search`. This cannot be undone. ```bash theme={null} tuple transcription delete a1b2c3d4 ``` ### Targeting a recording session Every time transcription starts on a call, Tuple mints a UUID for that *recording session*. `show`, `search`, and `export` accept `--recording ` to scope to one session instead of a whole call. This matters when transcription is stopped and restarted mid-call — each start/stop is a distinct session with its own started/complete pair. The UUID is only ever exposed to you through the `TUPLE_TRIGGER_RECORDING_ID` environment variable in [`call-transcription-started`](/triggers/api-reference#call-transcription-started) and [`call-transcription-complete`](/triggers/api-reference#call-transcription-complete) triggers. You cannot discover it elsewhere — `transcription list` and the [MCP tools](/cli/mcp) deliberately do not expose session-level IDs. A recording UUID identifies its session on its own, so `--call` is redundant alongside `--recording` and is ignored. With `show`, `--recording` composes with `--follow` and `--wait`: the stream ends when that session ends, even if the call continues. The canonical use is a `call-transcription-complete` trigger that hands the UUID straight to the CLI to grab exactly that session's transcript: ```sh theme={null} #!/bin/sh # ~/.tuple/triggers/summarize/call-transcription-complete tuple transcription export ~/Documents/tuple-transcripts \ --recording "$TUPLE_TRIGGER_RECORDING_ID" ``` ## `tuple notifications` Post notification cards into an active call. Agents and scripts use these to nudge the user, or to ask a yes/no question, without leaving Tuple. Use `notify` for a fire-and-forget notice or `ask` for a decision card with Accept/Reject buttons that blocks until the user answers. ### `notifications notify` Post a fire-and-forget notice — a status update or nudge the user doesn't need to act on. `--title`, `--body`, and `--sender` are required. ```bash theme={null} tuple notifications notify \ --title "Tests passed" \ --body "go test ./... is green" \ --sender "Claude" ``` | Flag | Required | Notes | | ----------------------- | -------- | ------------------------------------------------------------------------------------- | | `--title` | yes | Shown bold at the top. | | `--body` | yes | The notice body. | | `--sender` | yes | Shown as the "from" name. | | `--ephemeral` | no | Auto-dismiss after the default 10s of going unnoticed. Interacting resets the timer. | | `--interaction-timeout` | no | Auto-dismiss after this long without interaction (`2s`–`60s`; implies `--ephemeral`). | By default the notice persists until the user dismisses it or you cancel it. ### `notifications ask` Post a decision card and block until the user responds. Prints the outcome (`accepted`, `rejected`, or `canceled`) and exits `0` **only** when the user accepted — any other outcome exits non-zero, so a script can gate a consequential action on an explicit yes. The card stays up until the user answers or it's withdrawn; there is no timeout. ```bash theme={null} if tuple notifications ask \ --title "Run tests?" \ --body "tuple wants to run go test ./..." \ --sender "Claude" \ --accept-label "Run" \ --reject-label "Skip" then go test ./... fi ``` | Flag | Required | Notes | | ---------------- | -------- | ------------------------------------------------- | | `--title` | yes | Shown bold at the top. | | `--body` | yes | The question body. | | `--sender` | yes | Shown as the "from" name. | | `--accept-label` | no | Custom accept-button label. Defaults to `Accept`. | | `--reject-label` | no | Custom reject-button label. Defaults to `Reject`. | ### `notifications list [--follow] [--interval=DURATION]` List notification events (posted, accepted, rejected, canceled). Run with `--follow` (`-f`) after posting to watch for the user's response; `--interval` buffers streamed output and applies only with `--follow`. ```bash theme={null} tuple notifications list --follow ``` ### `notifications cancel ` Withdraw a card you posted before the user has answered. Only external notifications (the ones you posted) can be canceled — accepting or rejecting is user-driven. ## `tuple whoami` Print the identity of the authenticated Tuple user. Use this when a script or agent needs the signed-in user's email or ID without parsing the full `tuple state` payload. The default output is the bare email address followed by a newline, so no JSON parser is required. ```bash theme={null} # Capture the current user email in a script — no JSON parser needed email="$(tuple whoami)" # Get the user id for filtering yourself out of a roster id=$(tuple whoami --format json | jq .id) ``` Output formats: * **default** — the bare email address followed by a newline. Exits non-zero with `current user has no email — is Tuple logged in?` on stderr when the daemon has no identity. * **`--format json`** — the full identity object: `id`, `email`, `full_name`, `short_name`. * **`--format table`** — a labeled four-row block with ID, Email, Name, and Short name. ## `tuple state [--follow]` Print a **bounded orientation summary** of the Tuple app — a constant-size snapshot designed for agents and scripts that need to know what's going on without paging the full team into their context. The default output collapses every collection to counts: * `in_call` — boolean shortcut for "am I on a call right now". * `call` — the normalized call object, byte-for-byte identical to `tuple call current --format json`, or `null` when you're not in a call. * `user` — your `id`, `email`, and `short_name`. * `contacts` — `total` / `online` / `busy` counts, plus a `favorites` block with `total`, `online`, and `online_names` (a capped sample of up to 10 online favorites — enough to actually name who you could pair with). * `rooms` — `personal`, `team`, and `occupied` counts. * `invites` — number of pending invitations. * `connection` — `websocket_state` and `is_internet_connected`. When the websocket isn't `connected`, the presence-derived counts are last-known. * `platform`, `app_version`, `available_update_version` — install and update status. * `see` — a drill-down map naming the entity command that returns each collapsed section in full, so a script never has to guess where the detail lives: ```json theme={null} { "contacts": "tuple contacts list", "favorites_online": "tuple contacts list --favorited --status online", "rooms": "tuple rooms list", "call": "tuple call current", "user": "tuple whoami" } ``` **Breaking change.** `tuple state` previously dumped the daemon's full state payload — the entire contacts list and every room's member roster. It now always returns the bounded summary above; there is no `--full` opt-in. If you were reading `contacts`, `rooms`, or `current_call` arrays from `tuple state`, move to the entity commands (`tuple contacts list`, `tuple call current`, `tuple rooms list`, `tuple whoami`) — or to a streaming surface that still carries the full payload: `tuple state --follow` or the [`tuple://state` MCP resource](/cli/mcp#resources). Use `--follow` to stream incremental state events from the daemon as they happen — useful for dashboards or AI agents that need to react to call lifecycle changes. The stream carries the **full** payload (the same shape `GET /state` returns), not the summary: ```bash theme={null} tuple state --follow --format json | jq -c 'select(.type == "current_call")' ``` ## `tuple connect [purpose]` Launch an AI coding agent into your active Tuple call with a generated context prompt. See the [Connect an AI agent page](/cli/connect) for the full walkthrough. ```bash theme={null} tuple connect # interactive picker tuple connect "help debug this bug" # picker, with starting purpose tuple connect --harness claude # skip the picker tuple connect --print # write the prompt to stdout tuple connect --config # edit per-agent settings ``` | Flag | Description | | --------------------- | ----------------------------------------------------------------------------------------------------------------- | | `--harness ` | Builtin key (`claude`, `codex`, `cursor`, `copilot`, `opencode`, `pi`, `droid`), custom name, or executable path. | | `--print` | Write the context prompt to stdout instead of launching an agent. | | `--model ` | Model to pass to the harness's model flag. Overrides saved per-harness settings. | | `--reasoning ` | Reasoning effort to pass to the harness's effort flag. Overrides saved per-harness settings. | | `--config` | Open the TUI to edit per-harness settings saved to `~/.tuple/cli.toml`. | ## `tuple mcp` Run an MCP server over stdin/stdout, or register Tuple with an AI coding agent. See the [MCP server page](/cli/mcp) for details. ```bash theme={null} tuple mcp # run the server (used by agents) tuple mcp install claude # register with Claude Code tuple mcp install # interactive picker ``` # Config file Source: https://docs.tuple.app/cli/config The `tuple` CLI keeps user-level preferences in `~/.tuple/cli.toml`. If the file exists, the CLI reads it on every invocation. The file is created automatically the first time the CLI saves something to it. The containing directory is created with permissions `0700` and the file is written with `0600`. The format is [TOML](https://toml.io). ## Top-level keys | Key | Type | Description | | ----------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `default_harness` | string | Agent key used by `tuple connect` when `--harness` is not passed. Skips the interactive picker. | | `mcp_installed` | table of `bool` | Tracks which agents Tuple has already installed itself into as an MCP server, so it won't re-offer. Keyed by agent name (`claude`, etc.). | | `[harness.]` | table | Per-agent configuration. The `` is the agent name. See [Harness entries](#harness-entries). | ## Harness entries Each `[harness.]` table describes one agent. The same table is used for three different purposes; which keys you set determines the kind of entry: * **Settings-only entry** — set `model`, `reasoning`, or `extra_args` (but neither `command` nor `url`). This applies launch settings to a built-in agent without defining a new agent. * **Custom command agent** — set `command` to an argv template. Use `{PROMPT}` as a placeholder for the prompt text. If you omit `{PROMPT}`, the prompt is appended as a final positional argument. * **Custom URL agent** — set `url` to a URL template with `{PROMPT}` (URL-encoded) as the placeholder. The URL is opened via the platform opener instead of exec'ing a process. `command` and `url` are mutually exclusive on a single entry. If both (or neither) are set on a custom entry, the entry is ignored. Custom entries with the same key as a built-in agent (`claude`, `codex`, `cursor`, `copilot`, `opencode`, `pi`, `droid`) shadow the built-in. ### Keys | Key | Type | Description | | ------------ | --------------- | ---------------------------------------------------------------------------------------------------- | | `command` | array of string | Argv template. First element is the executable; `{PROMPT}` is replaced with the prompt text. | | `url` | string | URL template. `{PROMPT}` is replaced with the URL-encoded prompt and opened via the platform opener. | | `model` | string | Value substituted into the agent's model flag (`--model`, etc.) at launch time. | | `reasoning` | string | Value substituted into the agent's reasoning-effort flag at launch time. | | `extra_args` | array of string | Verbatim argv elements inserted before the prompt args on every launch. | The `model` and `reasoning` values are dropped silently for agents that do not support those flags (`cursor` and `opencode` have no reasoning flag, `droid` has neither). `tuple connect` prints a warning on stderr when this happens. `--model` and `--reasoning` flags on `tuple connect` override the saved values for a single invocation. ## Example ```toml theme={null} # ~/.tuple/cli.toml # Always launch Claude Code when running `tuple connect` without --harness. default_harness = "claude" # Agents Tuple has already installed itself into as an MCP server (so it won't re-offer). [mcp_installed] claude = true codex = true # Settings-only entry: applies to the built-in claude harness. [harness.claude] model = "opus" reasoning = "high" extra_args = ["--verbose"] # Custom agent that launches a script with the prompt as its single argument. [harness.review] command = ["/usr/local/bin/review-agent", "--mode", "interactive", "{PROMPT}"] # Custom URL agent that opens a deep link in the browser. [harness.cowork] url = "https://claude.ai/new?q={PROMPT}" ``` ## What's next * [Connect an AI agent](/cli/connect) — the primary consumer of this file. * [Command reference](/cli/commands) — every command and flag. * [MCP server](/cli/mcp) — register Tuple with an AI coding agent. # Connect an AI agent Source: https://docs.tuple.app/cli/connect `tuple connect` brings an AI coding agent into your current Tuple call. It's the fastest way to put Claude Code, Codex, Cursor, Copilot, opencode, Pi, Droid, or your own custom agent on the call with you — no MCP install, no hand-written prompt. ## When to use it * You're on a call and want an AI agent listening alongside you to help debug, make tickets on your behalf, or answer questions you ask out loud. * You want a scripted way to launch an agent into a call from a keybinding or workflow runner. * You're an agent that needs to drop into the active call yourself — pipe `tuple connect --print` into your own context. If you only want the agent to control Tuple programmatically (start calls, list contacts, search past transcripts), use the [MCP server](/cli/mcp) instead. `connect` is for live, in-call participation. If you're already running an MCP-aware agent and just want to attach it to the current call, invoke the [`/tuple:connect`](/cli/mcp#tuple-connect) slash command instead — it builds the same sidekick context without launching a new harness. ## Prerequisites * The Tuple desktop app is running and signed in. See the [CLI overview](/cli/overview) for install and authorization steps. * The harness you pick is on your `PATH` (e.g. `claude`, `codex`, `cursor-agent`). `tuple connect` doesn't install the harness for you. ## Quick start ```bash theme={null} tuple connect # interactive picker tuple connect "help me debug this bug" # picker, with a starting purpose tuple connect --harness claude # skip the picker tuple connect --print # write the prompt to stdout tuple connect --config # edit per-agent settings ``` The first run on a TTY walks you through choosing a default harness and (optionally) saving a model and reasoning effort to `~/.tuple/cli.toml`. ## How it works When you run `tuple connect`, Tuple: 1. **Resolves call state.** If you're not on a call, you can start one or join a room. If transcription isn't running, the interactive picker offers to start it so the agent has something to follow. (In `--harness` and `--print` modes, transcription is not started for you.) 2. **Picks a harness.** On a TTY you get a picker; with `--harness` it's chosen directly. 3. **Builds a context prompt** that includes the active call's ID, the participants, the harness's model/reasoning settings, and a tutorial section teaching the agent the relevant `tuple` commands — how to follow the live transcript, capture the screen, post a notification card to ask you a yes/no question, and search past calls. 4. **Launches the harness** with the prompt as its initial message. On Unix, Tuple `exec`s the harness so it inherits your terminal directly. The generated prompt embeds state guidance, so even if the call state changes (you join a different room, transcription is stopped), the agent has instructions for recovering on its own. ## Supported harnesses | Key | Display name | How it's launched | | ---------- | -------------- | ---------------------------- | | `claude` | Claude Code | `claude -- ` | | `codex` | OpenAI Codex | `codex -- ` | | `cursor` | Cursor Agent | `cursor-agent ` | | `copilot` | GitHub Copilot | `copilot -i ` | | `opencode` | opencode | `opencode --prompt ` | | `pi` | Pi | `pi ` | | `droid` | Factory Droid | `droid ` | You can also point `--harness` at any executable path: ```bash theme={null} tuple connect --harness /usr/local/bin/my-agent ``` Custom harnesses, including URL-based deep-link agents (e.g. Claude Cowork), can be defined in `~/.tuple/cli.toml` — see [Custom harnesses](#custom-harnesses) below. ## Modes `tuple connect` picks a mode automatically, but you can force one: ### Interactive (default on a TTY) Shows the harness picker, resolves call state, then launches. ```bash theme={null} tuple connect tuple connect "review the diff I'm about to open" ``` ### Scripted (`--harness`) Skips the picker. Useful for keybindings, scripts, or trigger handlers. ```bash theme={null} tuple connect --harness claude tuple connect --harness claude --model sonnet --reasoning high ``` `--model` and `--reasoning` override any saved per-harness settings for this launch. Unsupported flags for a harness produce a stderr warning and are ignored. ### Print (`--print` or non-TTY) Builds the prompt and writes it to stdout instead of launching anything. Use it when you're piping the prompt into an agent yourself, or when an agent invokes `tuple connect` from inside its own session to learn about Tuple's CLI surface. ```bash theme={null} tuple connect --print > /tmp/prompt.txt tuple connect --print --harness claude # include the claude-specific section ``` Combining `--print` with `--harness` includes the per-harness guidance in the output but does not launch the harness. ### Config (`--config`) Opens an interactive TUI for editing the model, reasoning effort, and extra arguments saved per harness in `~/.tuple/cli.toml`. You can read more about the [CLI config file](/cli/config). ```bash theme={null} tuple connect --config ``` `--config` uses the same settings walkthrough as the interactive `tuple connect` flow: for harnesses with a known model or reasoning option set (such as Claude Code's `opus` / `sonnet` / `haiku`), you get a constrained picker with an **Other…** escape hatch into free text. Harnesses without a known option set fall through to a free-text prompt. After the walkthrough, `--config` asks whether to make the harness your default before saving and exiting — it never launches the agent. ## Flags | Flag | Description | | --------------------- | ------------------------------------------------------------------------------------------------- | | `--harness ` | Builtin key, custom name, or executable path. Skips the picker. | | `--print` | Write the context prompt to stdout instead of launching an agent. | | `--model ` | Model to pass to the harness's model flag. Overrides the saved per-harness model for this launch. | | `--reasoning ` | Reasoning effort to pass to the harness's effort flag. Overrides the saved per-harness value. | | `--config` | Open the TUI to edit per-agent settings saved to `~/.tuple/cli.toml`. | Global flags work as documented in the [command reference](/cli/commands). ### Custom harnesses Add a `[harness.]` entry with `command` (an argv template) or `url` (a deep-link template). Both use `{PROMPT}` as the placeholder. ```toml theme={null} [harness.my-agent] command = ["my-agent", "--initial-message", "{PROMPT}"] [harness.cowork] url = "https://claude.ai/cowork?message={PROMPT}" ``` Custom names appear in the picker alongside the builtins, and you can target them with `--harness my-agent`. ## Following the transcript The prompt generated by `tuple connect` teaches the agent to follow the live call with [`tuple transcription show`](/cli/commands#transcription-show-call-id-follow-wait-interval-duration-watch-words-timeout-duration-cursor-tag-with-events-without-speech-with-speech-markers). It can use two modes: * **`--follow`** — continuous stream of records (NDJSON with `--format json`), one line per speech segment or event. Best for harnesses that have a per-line wake mechanism (such as Claude Code's Monitor). * **`--wait`** — block until the next batch of transcript arrives, print it, then exit. Loop it in the foreground to follow a call from any harness, without burning tokens on every utterance. ```bash theme={null} # Foreground follow loop the agent can run in any harness: while tuple transcription show --wait --interval 60s --watch-words "claude,hey claude"; do : # process the batch the loop just printed done ``` `--interval` gathers transcript for up to the given duration before flushing, so the agent wakes once per minute (or whatever you choose) instead of on every utterance. `--watch-words` flushes the batch early — and ends the relaxed interval — when a configured word or phrase is spoken, so the agent responds immediately when you address it by name. ## Examples Drop Claude Code onto the active call with a starting task: ```bash theme={null} tuple connect --harness claude "summarize what we decide in the next 10 minutes" ``` Launch Codex with a specific model and reasoning level from a keybinding: ```bash theme={null} tuple connect --harness codex --model gpt-5 --reasoning high ``` Pipe the prompt into your own agent runner: ```bash theme={null} tuple connect --print | my-agent-runner --stdin ``` Edit which model each harness uses without touching `cli.toml` by hand: ```bash theme={null} tuple connect --config ``` # MCP server Source: https://docs.tuple.app/cli/mcp `tuple mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io) server over stdin/stdout. It exposes Tuple's call, contact, screen, transcription, notification, and past-call transcript capabilities to AI coding agents such as Claude Code, Claude Desktop, and OpenAI Codex. The MCP server is a sibling of the [`tuple` CLI](/cli/commands): both require the Tuple desktop app to be running and signed in. ## Install with a coding agent Run `tuple mcp install` and pick an agent, or specify one directly: ```bash theme={null} tuple mcp install # interactive picker tuple mcp install claude # Claude Code CLI tuple mcp install claude-desktop # Claude Desktop app tuple mcp install codex # OpenAI Codex ``` You can install Tuple in multiple agents at once: ```bash theme={null} tuple mcp install claude codex ``` By default the server is registered as `tuple`. Pass `--name` to use a different identifier in your agent config. Each install command writes to that agent's standard config: | Agent | Config file | | ---------------- | ---------------------------------------- | | `claude` | `~/.claude.json` | | `claude-desktop` | Claude Desktop's per-OS config directory | | `codex` | `~/.codex/config.toml` | Restart the agent after installing. ## Tools Once installed, the agent can call these tools. Names mirror the CLI commands. ### State and contacts | Tool | What it does | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `get_state` | Bounded orientation summary: `in_call`, normalized `call`, user identity, contact and room counts (with a capped sample of online favorites), pending invites, and connection status. A `see` map points at the entity tools (`list_contacts`, `list_rooms`, `get_active_call`, `get_current_user`) for full drill-down. Constant-size regardless of team size. | | `get_current_user` | Identity of the authenticated user (id, email, full name, short name). Use instead of `get_state` when you only need to know who is logged in. | | `list_contacts` | List or search contacts. Optional `query`, `status` (`online`/`busy`/`offline`, with `available` as a synonym for `online`), `kind` (`teammate`/`external`), `favorited`, and `recent` filter params compose with AND. | | `favorite_contact` / `unfavorite_contact` | Pin or unpin a contact. | | `remove_contact` | Remove a contact (destructive; agents prompt to confirm). | | `invite_contact` | Send a Tuple invite by email (mutating; agents prompt to confirm). | ### Calls | Tool | What it does | | ---------------------------------------- | ------------------------------------------------ | | `start_call` | Start a 1:1 call with a contact. | | `join_call` | Join a contact's active call or a room URL/slug. | | `end_call` | Leave the active call. | | `mute` / `unmute` | Mute or unmute your microphone. | | `add_participant` / `remove_participant` | Manage participants in the active call. | | `capture_screen` | Return a JPEG of the currently shared screen. | ### Rooms | Tool | What it does | | ----------------------------------- | --------------------------------------------------------------------------------------------------------- | | `list_rooms` | List personal and team rooms. Supports kind, occupied, favorite, member-detail, and result-limit filters. | | `join_room` | Join a room by slug or URL without resolving the argument as a contact. | | `favorite_room` / `unfavorite_room` | Pin or unpin a room. | | `create_room` | Create a team room. | ### Transcription and notifications | Tool | What it does | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `start_transcription` / `stop_transcription` | Toggle transcription for a call. | | `get_transcription` | Return transcription segments plus human-readable transcript text. | | `get_transcription_events` | Return structured lifecycle events for a call. | | `notify_user` | Post a fire-and-forget notice (title, body, sender). | | `request_user_decision` | Post a yes/no card (title, body, sender, optional accept/reject labels) and block until the user answers. | | `list_notifications` | Get the history of notification events for a call. | | `cancel_notification` | Withdraw a card the agent posted earlier. | ### Stored transcripts (past calls) These tools query the local transcript database of past transcribed calls. They work without an active call. See [Searching past calls](/pairing-with-tuple/searching-past-calls) for the underlying CLI. | Tool | What it does | | ---------------------- | ---------------------------------------------------------------------------------------------- | | `transcription_search` | Full-text search across past call transcripts, with filters and optional conversation context. | | `transcription_list` | List past recorded calls, newest first, with dates, participants, and transcript sizes. | | `transcription_show` | Return a past call's full transcript across all its recording sessions. | | `transcription_export` | Write past call transcripts to disk as Markdown, text, or NDJSON. | | `delete_stored_call` | Permanently delete a stored call's recordings, transcript, and events. | Destructive and mutating tools (`remove_contact`, `invite_contact`, `end_call`, `delete_stored_call`, etc.) carry MCP annotations that drive the client's confirmation prompt — the user approves them at call time. ## Prompts The server also registers a small set of [MCP prompts](https://modelcontextprotocol.io/specification/server/prompts). MCP-aware agents surface these as slash commands — for example, Claude Code shows them under `/tuple:` once Tuple is installed. | Slash command | Arguments | What it does | | ------------------------ | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/tuple:connect` | `call_id` (defaults to `current`), `tail` (default `100`) | Drop the agent into a live Tuple call as a silent sidekick. Returns recent lifecycle events and the trailing transcript so the agent can join mid-call without re-reading the whole history, plus instructions for following the unified stream, capturing the shared screen, and knowing when to speak. | | `/tuple:summarize_call` | `call_id` (defaults to `current`) | Pull the current call's full transcript and events and ask the agent for a structured summary: key discussion points, decisions, action items, and open follow-ups. | | `/tuple:find_discussion` | `topic` (required, FTS5 syntax), `participant`, `after`, `before` | Search past recorded calls for a topic and synthesize where, when, and by whom it was discussed, with conversation excerpts around each match. | ### `/tuple:connect` Use `/tuple:connect` from inside an MCP-aware agent (Claude Code, Claude Desktop, Codex, or anything else that renders MCP prompts as slash commands) when you're already on a Tuple call and want the agent to silently follow along. The returned prompt: * Subscribes the agent to the unified events + transcript stream so it wakes on signal, not on a timer. * Maps participant IDs to names with `tuple state`. * Teaches the agent when to speak (direct address, terminal input, call end, mid-call checkpoint) and when to stay quiet. * Embeds the last `tail` lifecycle events and transcript lines so a mid-call join doesn't replay the whole history. If you'd rather launch a new agent process onto the call from your shell — instead of invoking it from an already-running agent — use [`tuple connect`](/cli/connect), which builds the same context and execs the harness for you. ```text theme={null} /tuple:connect /tuple:connect tail=200 /tuple:connect call_id=01HFE… tail=50 ``` ## Resources The server also exposes a set of [subscribable MCP resources](https://modelcontextprotocol.io/specification/server/resources). Agents can read them once for a snapshot, or subscribe for live updates. | URI | Content | | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tuple://state` | Full app state (current user, current call, contacts, rooms, websocket). Subscribable. This is the full-payload surface — `get_state` returns the bounded summary instead. | | `tuple://call/{callId}/transcription` | NDJSON transcription segments for a call. Subscribable. | | `tuple://call/{callId}/transcription.json` | Structured JSON snapshot of the same segments. | | `tuple://call/{callId}/events` | NDJSON transcription lifecycle events. Subscribable. | | `tuple://call/{callId}/events.json` | Structured JSON snapshot of the events. | | `tuple://call/{callId}/stream` | Merged NDJSON of events and transcript text. Subscribable. | | `tuple://call/{callId}/stream.json` | Structured JSON snapshot of the merged stream. | | `tuple://call/{callId}/notifications` | NDJSON notification events. Subscribable. | | `tuple://call/{callId}/notifications.json` | Structured JSON snapshot of notifications. | | `tuple://transcription/calls` | Index of past recorded calls (formatted text). | | `tuple://transcription/calls.json` | Structured JSON snapshot of the past-call index. | | `tuple://transcription/call/{callId}/transcript` | Full transcript of a past recorded call (formatted text). | | `tuple://transcription/call/{callId}/transcript.json` | Structured JSON snapshot of the past-call transcript. | Use `current` as `{callId}` to target the active call. Subscriptions deliver `ResourceUpdated` notifications when data changes; clients then re-read the resource for the new snapshot. The `tuple://transcription/...` resources are read-on-demand snapshots (not subscribable); the server also surfaces the most recent past calls as concrete, human-titled `transcription-…` resources so clients can attach one as context. # CLI Overview Source: https://docs.tuple.app/cli/overview Tuple's CLI is considered **alpha**. Aspects of the feature might change as we continue to iterate on feedback. The `tuple` CLI is a command-line tool that controls a running Tuple desktop app. You can use it to start and join calls, manage contacts, capture the shared screen, stream transcriptions, search transcripts of past calls, post notifications into a call, and run an MCP server so AI coding agents can drive Tuple on your behalf. The CLI talks to the desktop app over a local socket - **there's no network call to Tuple's servers**. The app must be running and signed in for commands to work. ## Installing the CLI The `tuple` binary ships inside the Tuple app bundle. To make it available on your `PATH`: 1. Open **Settings → Integrations** (or press **cmd + ,** and select the **Integrations** tab). 2. Select **CLI Server** from the integrations list. 3. Click **Install CLI**. Tuple symlinks the bundled binary into `/usr/local/bin/tuple`. If `/usr/local/bin` is not writable by your user, Tuple prompts for your administrator password. Once installed, run `tuple --help` from any terminal to see the full command tree. ## Authorizing the CLI Tuple gates access behind an explicit authorization decision. Right after you click **Install CLI**, Tuple makes a harmless probe through the newly installed binary so the authorization prompt appears while you're still in the install flow. If that probe doesn't run for any reason, the prompt will instead appear the first time you run a `tuple` command from your terminal. Your choice is stored in the macOS Keychain. You can revoke or re-grant access at any time from **Settings → Integrations → CLI Server**. Revoking takes effect immediately - any CLI command running at the time will stop. While authorization is unset, individual `tuple` commands wait for your decision rather than failing. If you dismiss the prompt without choosing, the request stays parked until you make a decision or quit Tuple. ## Configuration file The CLI stores user-level preferences — your default harness for [`tuple connect`](/cli/commands), custom harness definitions, per-harness launch settings, and which agents you've already been offered `tuple mcp install` for — in `~/.tuple/cli.toml`. ```bash theme={null} tuple connect # reads ~/.tuple/cli.toml ``` The file is created on first write with permissions `0600` (parent directory `0700`). A missing file is not an error — the CLI falls back to built-in defaults. Example contents after picking Claude Code as your default harness and saving a model override: ```toml theme={null} default_harness = "claude" [harness.claude] model = "claude-sonnet-4" ``` ## What's next * [Command reference](/cli/commands) - every command, flag, and subcommand. * [Connect an AI agent](/cli/connect) - launch an AI coding agent into the active call. * [Config file](/cli/config) - configure the CLI via a dotfile * [MCP server](/cli/mcp) - let AI agents drive Tuple. * [Searching past calls](/pairing-with-tuple/searching-past-calls) - query and export transcripts with `tuple transcription`. # Installation Source: https://docs.tuple.app/getting-started/installation After you download and open Tuple, we'll prompt you for access to your microphone, camera, screen, and accessibility tools. Clicking "Ask for Access" for each option (Microphone, Camera, Accessibility, and Screen Recording) will take you to your System Preferences, where you can grant access: Permission prompts for microphone, camera, accessibility, and screen recording access Once your permissions are set up, you'll be prompted to sign in through your web browser: Browser sign-in page for logging in to Tuple Once signed in on your web browser, you'll see a dialog asking if you'd like to be redirected back to Tuple. It'll look something like this: Browser dialog asking whether to open the sign-in link in Tuple Open that link with the Tuple app, and you'll be signed in! When that's done, you'll see the Tuple icon appear in the menu bar. Go ahead and click it, and you'll be able to [share your screen!](/pairing-with-tuple/sharing-your-screen) Tuple popover open from the menu bar after installation #### Troubleshooting Sometimes permissions become misconfigured (for example when installing Tuple on a new laptop restored from a previous backup). If you are having trouble giving permissions to Tuple, you can try running the following commands in terminal to reset permissions: ``` tccutil reset All app.tuple.app defaults delete app.tuple.app ``` Once you have run these commands, please restart Tuple before continuing. # Tuple for Linux (alpha) Source: https://docs.tuple.app/getting-started/tuple-for-linux Tuple for Linux is currently in alpha and under active development. The client is primarily command-line driven. To install, visit [the Linux install page](https://tuple.app/linux?utm_source=docs) for the latest download command and architecture options. Join the [Slack channel](https://tuple.app/linux/slack) to provide feedback, report bugs, and ask questions. ## Display server support Tuple runs natively on both **X11** and **Wayland**. The client automatically detects your session type using the `XDG_SESSION_TYPE` environment variable. All core features, including screen sharing, the drawing overlay, and UI windows, work on both display servers. ## Current feature set The Tuple client on Linux is **extremely barebones** at the moment. We'll be working hard to flesh it out in the coming weeks — your patience is appreciated! ### What works * Logging in and running Tuple as a daemon — you'll appear available for your contacts / teammates * Receiving an incoming direct call * Starting a call * Joining a call via a URL * Hearing (and being heard by) others using the system default audio device * Viewing a shared screen * Sharing your own screen * Viewing drawing annotations on a shared screen * Drawing on the shared screen * Remotely controlling someone else's screen * Initiating a direct call from the command line * Listing and managing contacts and favorites * Muting and unmuting your microphone ### Not yet implemented * UI for making calls, managing contacts, and settings (everything is done via the command line for now) * Being remotely controlled while sharing * Viewing text or highlight click annotations on the shared screen * Changing audio devices * Sharing or viewing webcams ## Commands The commands below are specific to the Linux client. They're separate from the newer [Tuple CLI](/cli/overview), which currently only ships with the macOS client. When the Tuple CLI reaches Linux, the Linux client will adopt its interface. | Command | Description | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `tuple login` | Initiates the login process. Outputs a URL to log in via the web; the last step emits an authorization code to be used in `tuple auth`. | | `tuple auth $AUTH_CODE` | Authorizes the local Tuple client. Takes the code emitted via the web login flow as its only parameter. | | `tuple logout` | Logs you out of Tuple. | | `tuple call` | Starts a direct call with one of your contacts. Lists your favorited contacts who are currently online and prompts you to pick one. | | `tuple new` | Starts a new call and automatically adds you as a participant. Prints the URL of the call to the console so you can share it with others. | | `tuple end` | Ends the current call if it's just you and one other person; leaves the call if others are still on it (or you're in a room). | | `tuple join $CALL_URL` | Joins a call that's in progress (or a room). Takes the URL of the call or room as its only parameter. | | `tuple ls` | Lists your contacts. | | `tuple favorite $USER_ID` | Favorites a contact. | | `tuple unfavorite $USER_ID` | Removes a contact from your favorites. | | `tuple share` | Starts sharing your entire viewable display — not just one monitor. | | `tuple unshare` | Stops sharing your screen. | | `tuple mute` | Mutes your microphone during a call. | | `tuple unmute` | Unmutes your microphone during a call. | | `tuple on` | Starts running the Tuple daemon. The daemon also starts automatically when most other commands are run (including `auth`, `new`, and `join`). | | `tuple off` | Stops the Tuple daemon. | | `tuple ui` | Displays a simple debugging UI. | | `tuple settings` | Lists all settings. | | `tuple set $name $value` | Sets the setting named `$name` to the given `$value`. | ## Settings | Setting | Type | Values | Description | | -------------- | ---- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `host-overlay` | bool | `0`, `1` | Enables the "host overlay", which is where annotations are drawn. Transparency doesn't work correctly with certain window managers / compositors, which can cause this window to render incorrectly. Use this setting to disable it if needed. | | `capture` | enum | `auto`, `x11`, `portal` | Controls which screen capture mechanism Tuple uses. `auto` selects the best method for your session type. `x11` forces X11 capture. `portal` forces the XDG Desktop Portal (PipeWire) capture used on Wayland. | ### Screen capture methods You can also cycle through capture mechanisms during a call by clicking the capture button in the call UI. The button appears next to the screen share button and displays the current capture mechanism (`auto`, `x11`, or `portal`). Clicking it cycles to the next option. ### Fonts Tuple runs `fc-match sans-serif:fontformat=TrueType` to find your system's "sans-serif" font. ## Troubleshooting screen capture If screen capture fails, Tuple displays the operation that failed and either the system error name or its `errno` value. You can dismiss the popup by clicking **Dismiss** or pressing **Enter**. Common causes of screen capture failures on Linux include: * **Wayland**: The XDG Desktop Portal or PipeWire service is not running, or the portal stream disconnected. Make sure `xdg-desktop-portal` and `pipewire` are installed and active on your system. * **X11**: Tuple could not query screen information. Verify that your X11 display server is running and accessible. If the error persists, check the [log file](#logs) for the same operation and error details, then share them with the team in the [Slack channel](https://tuple.app/linux/slack). ## Logs Tuple logs to: ``` $XDG_DATA_HOME/tuple/0/log.txt ``` If the `$XDG_DATA_HOME` environment variable is not set, logs are written to: ``` $HOME/.local/share/tuple/0/log.txt ``` These are useful to include in any bug reports. # Adding Integrations Source: https://docs.tuple.app/managing-your-account/adding-integrations Tuple supports integrations that make signing in and jumping on calls even easier. You can manage your integrations on [your integration preferences page](https://production.tuple.app/profile#integrations). ## CLI Server You can install a `tuple` command-line tool to start calls, manage contacts, follow transcription, and drive AI agents over MCP from your terminal. Learn more on the [CLI page](/cli/overview). ## Slack Want to add a `/tuple` command to Slack? Check out our [Slack integration](https://tuple.app/slack?utm_source=docs). ## Google Calendar If you're a Gcal user, you can add Tuple call URLs directly in your calendar when setting up a meeting. To set it up, head to the [Google Calendar integration page](https://tuple.app/gcal?utm_source=docs). ## Apple Calendar If you use Apple Calendar on macOS, you can also add Tuple call URLs directly to your calendar events. You can learn more on the [Apple Calendar integration page](https://tuple.app/ical?utm_source=docs). ## Spotlight On macOS 26 and later, you can use Spotlight to start calls or join rooms. Learn more on the [Spotlight integration page](/pairing-with-tuple/spotlight-integration). ## Custom Lifecycle Hooks We built Triggers into Tuple so that you can leverage any of the existing lifecycle events (starting a call, joining a call, etc.) to perform custom actions or code. Read more on the [Triggers page](https://tuple.app/triggers?utm_source=docs). # Resetting your password Source: https://docs.tuple.app/managing-your-account/resetting-your-password Need a new password? No sweat. Here's what you want: [reset your password on Tuple](https://production.tuple.app/password_resets/new). If that doesn't work for some reason, please [email support](mailto:support@tuple.app). # Setting your profile picture avatar Source: https://docs.tuple.app/managing-your-account/setting-your-profile-picture-avatar Avatars (or "Profile pictures") in Tuple are set using a service called [Gravatar](https://gravatar.com). If you're not familiar, configuring your Gravatar (for free) lets any service retrieve that avatar knowing only your email address. **Setting an avatar makes it easier for your colleagues to find you, so we recommend taking the time to do it.** As a bonus, services you sign up for in the future can pull in your avatar automatically, saving you steps later. Why not [do it right now](https://gravatar.com)? **Note:** You will need to create the Gravatar account with the same email address as your Tuple account in order for Tuple to be able to sync the image. # Upgrading a guest account Source: https://docs.tuple.app/managing-your-account/upgrading-a-guest-account If you were invited to pair with someone on Tuple but didn't sign up for a paid account, you're likely on our Guest team. To create your own team, you can sign into the native app where you'll see a banner that looks like this: Banner in the Tuple app prompting you to upgrade your guest account Click "Upgrade", and we'll walk you through the rest! If you're not signed in or don't have the native app downloaded, you can visit [this page](https://production.tuple.app/account_upgrade/new) to start the same process. # Using Tuple as an unpaid user Source: https://docs.tuple.app/managing-your-account/using-tuple-as-an-unpaid-user To pair with Tuple, at least one of the users in the session must be a paid user on a Team (or a trialing user). To invite a user to be on the Tuple guest team so that you can pair with them, head to the invitations tab and select the option "Outside collaborators": Invite flow with the Outside collaborators option selected If you think you should have a paid account on a team, reach out to the Team Owner of your team and they can invite you via a team link. If you would like to move to a paid team of your own, you can read more about how to do that here: [Upgrading a guest account](/managing-your-account/upgrading-a-guest-account). If you get stuck and need help, our support team is only an email away at [support@tuple.app](mailto:support@tuple.app)! # Viewing your call history Source: https://docs.tuple.app/managing-your-account/viewing-your-call-history If you'd like to review your recent call history, you can view recent calls on [your usage page](https://production.tuple.app/profile#usage). The call history is currently displayed in the UTC timezone. ## Missed calls The usage page lists calls you were on, not calls you missed. On macOS, missed calls surface in a few places: * A macOS notification appears when someone calls you and you don't answer. The notification includes a **Call Back** button. * A badge on the menu bar Tuple icon indicates unseen missed calls. * Opening the Tuple popover clears the badge and shows a missed-call banner at the top of your contacts list. Each banner shows who you missed and when, with **Call Back** and dismiss buttons. Missed calls aren't stored server-side, so they don't appear in the web usage page or in [Local call history](/pairing-with-tuple/local-call-history) (which only lists calls you [transcribed](/pairing-with-tuple/transcribing-calls) on that machine). # Agreements and Policies Source: https://docs.tuple.app/overview/agreements-and-policies We've documented our terms, security practices, and privacy policies on our website. You can find those here: * [Security practices](https://tuple.app/security?utm_source=docs) * [Privacy policy](https://tuple.app/privacy?utm_source=docs) * [Terms of service](https://tuple.app/terms?utm_source=docs) # Adding more call participants Source: https://docs.tuple.app/pairing-with-tuple/adding-more-call-participants Sometimes when pairing, having more than two people in a session can be a huge help. Tuple allows **up to** **10 participants** on any call. When in a call, anyone can add another participant with just two clicks. First, click "Add another participant": Call controls with the Add another participant button highlighted Then, click **Add** next to the person you want to invite to the call: Participant picker with the Add button next to a teammate # Audio device swapping Source: https://docs.tuple.app/pairing-with-tuple/audio-device-swapping **tl;dr: For everyday audio device swapping, you probably want to use the macOS controls (Settings > Sound) rather than the dropdowns in Tuple's preferences. (You can do this quickly by Option-clicking the speaker icon in your menu bar.)** There is a potentially-confusing caveat when setting your audio device in Tuple's preferences. Imagine you have your Airpods set as your output in the macOS Sound preferences. Now, you override those settings in Tuple's preferences and choose Bose QCIIs as the output. With these preferences, your volume up, volume down, and mute keys on your keyboard will affect the Airpods, *not* the Bose QCIIs. This is because the keyboard controls always affect the System Default device (the one you've chosen in System Preferences > Sound). We provide the dropdowns in our preferences to support folks with complex audio setups, such as using tools like [Krisp](https://krisp.ai). For most users, we recommend ensuring your audio setup uses the Mac System Default preferences: macOS sound settings with the system default audio devices selected # Collaborative Painting Source: https://docs.tuple.app/pairing-with-tuple/collaborative-painting With Tuple, all participants in a pairing session can draw on the shared screen. If you're viewing a screen, make sure you're using the "Paint" cursor mode by selecting it from the [Guest Toolbar](/pairing-with-tuple/interacting-with-a-shared-screen). If you're sharing your screen, you can configure a hotkey in your [preferences](/application-preferences) to enable Paint mode, or click the icon in the menu bar: Tuple menu bar menu with Paint mode enabled Press the hotkey again, press "Esc", or click the Tuple icon in your menu bar to return to normal. In either role, just click and drag to paint, and use right-click to clear your paint. Hold **Shift** while drawing to constrain your stroke to a straight line. In your Preferences, you can choose between waiting until a right-click to clear the paint, or if you want it to clear after a few seconds automatically. ## Paint colors Each participant is automatically assigned a unique color when they join a call. Your paint strokes, text annotations, and highlight clicks all use this color so everyone can tell who drew what. The color cannot be changed manually — Tuple picks colors with good contrast so each person's drawings are easy to distinguish. Your color may change between calls. # Copying and pasting from one computer to another Source: https://docs.tuple.app/pairing-with-tuple/copying-and-pasting-from-one-computer-to-another-sharing-your-clipboard Tuple supports copying and pasting text between computers. Super helpful when you're working on code together! Cross-machine clipboard actions come from the person viewing the shared screen. When you are sharing your screen, your copy and paste shortcuts continue to use only your own clipboard. To use this feature, you can cmd + c any text value and use cmd + v to paste it: Copying text on one computer and pasting it on the paired computer **When you're viewing a screen, you can:** * Copy text from your computer, mouse over the Tuple window, and paste the text on their computer. * Select something on your pair's computer, copy it, and paste it to your computer. * Copy text from your pair's computer and paste it elsewhere on their computer, without changing their clipboard. * Copy text from your computer and paste it elsewhere on your computer, without changing their clipboard. **When you're the person sharing their screen, you can:** * Copy text from your computer and paste it elsewhere on your computer, without changing your pair's clipboard. * Work with your clipboard normally as if you weren't pairing. Each person has their own independent clipboard when pairing. All data is encrypted end-to-end (more about our [security practices](https://tuple.app/security?utm_source=docs)). **Note:** Tuple disables command keys by default so that you don't accidentally command-tab on your pair's computer when you meant to command-tab away from Tuple. If you'd like to paste text on your pair's machine, make sure the Cmd Keys keyboard setting is enabled (more about [keyboard modes](/pairing-with-tuple/interacting-with-a-shared-screen)). **Note:** This feature only works when a user uses cmd+c to select text. For example, it will not work when copying text via a JavaScript backed button on a webpage. Tuple supports copying and pasting text between computers on Windows. Cross-machine clipboard actions come from the person viewing the shared screen, so this works whether you're pairing with another Windows user or with someone on macOS. When you are sharing your screen, your own copy and paste shortcuts continue to use only your own clipboard. Each person has their own independent clipboard when pairing. **When you're viewing a shared screen, you can:** * Copy text from your computer, mouse over the Tuple viewing window, and paste it on the host's computer. * Select something on the host's computer, copy it, and paste it on your own computer. * Copy text from the host's computer and paste it elsewhere on their computer, without changing their clipboard. * Copy text from your computer and paste it elsewhere on your computer, without changing the host's clipboard. ### Sending keystrokes to the host For paste to reach the host machine, your keystrokes need to be forwarded. Make sure the **Keyboard** mode is selected in the guest toolbar at the top of the shared screen window — in **No Keyboard** mode, your keystrokes stay on your machine and won't paste on the host. Keystrokes are only forwarded while the **Remote control** mouse mode is active; in Highlight Click or Paint mode, they stay on your machine. See [Interacting with a shared screen](/pairing-with-tuple/interacting-with-a-shared-screen) for more on mouse and keyboard modes. When you're pairing across operating systems, Tuple does not translate modifier keys. If the host is on macOS and you want to trigger a shortcut that uses the command key (⌘), press the Windows key (⊞) on your keyboard instead — so paste becomes ⊞+V when pasting onto a Mac host. ### Limitations * This feature only works when text is copied with a standard copy shortcut. Copies triggered by a JavaScript button on a webpage, for example, won't sync between machines. * Only text is synced across machines — files and images are not transferred through the shared clipboard. All clipboard data is encrypted end-to-end (more about our [security practices](https://tuple.app/security?utm_source=docs)). # How to stream pair programming sessions Source: https://docs.tuple.app/pairing-with-tuple/how-to-stream-pair-programming-sessions ## How to stream pairing sessions with OBS We typically recommend using [OBS](https://obsproject.com/) to stream your screen with the Tuple call active. To capture the audio from you and your pair, you could use [Loopback](https://rogueamoeba.com/loopback/) to pipe the multiple audio streams into [OBS](https://obsproject.com/). To show your own self-view in the stream, you can [persist your self-view](/pairing-with-tuple/sharing-a-webcam-video#self-view). ## How to stream pairing sessions with StreamYard * Set up [StreamYard](https://streamyard.com/) and invite your pair to a new stream in StreamYard * Once your pair has joined the StreamYard call, call them in Tuple * Everyone turn off webcams and mute yourselves in Tuple * Everyone go to Tuple audio settings and make sure Voice Boost is off ([macOS only](/application-preferences/macos-preferences-audio); it should work without changes to your settings if you're using the Windows client) * Either pair can share their screen on Tuple, which will allow the other to annotate and control as expected * Share your audio and webcam over StreamYard * Either pair can share their screen over StreamYard to show the pairing session * We recommend having the same person who's sharing their screen via Tuple to share their screen via StreamYard to reduce UI chrome on your stream ## How to stream pairing sessions with Riverside * Set up [Riverside](https://riverside.fm/) and invite your pair to join you in the recording studio in Riverside * Once your pair has joined your Riverside studio, call them in Tuple * Everyone turn off webcams and mute yourselves in Tuple * Everyone go to Tuple audio settings and make sure Voice Boost is off ([macOS only](/application-preferences/macos-preferences-audio); it should work without changes to your settings if you're using the Windows client) * Either pair can share their screen on Tuple, which will allow the other to annotate and control as expected * Share your audio and webcam over Riverside * Either pair can share their screen over Riverside to show the pairing session * We recommend having the same person who's sharing their screen via Tuple to share their screen via Riverside to reduce UI chrome on your stream # Interacting with a shared screen Source: https://docs.tuple.app/pairing-with-tuple/interacting-with-a-shared-screen Shared screen window on macOS showing the guest toolbar across the top When someone else on a call is sharing their screen, you can use the toolbar at the top of the viewing window to understand what's going on in the call and interact with the shared screen. *Note: If you fullscreen this window, you can access the toolbar by moving your cursor to the top of the screen.* ## Participant Display Participant display in the top-left corner of the shared screen window In the top left of the window is the participant display. Here, you'll see an overview of who's on the call with you, whose screen you're looking at, and who currently has control over the mouse and keyboard. When a user has control over the mouse and keyboard, they'll have a colored ring around their avatar: Animated avatar ring showing which participant currently has mouse and keyboard control You can click on the "+" icon to add more people to a call (either by adding them directly, or by copying a link to the call): Participant display with the Add people button highlighted Clicking on any user's avatar allows you to request that they share their screen or webcam, mute their audio, or kick them off the call: Participant avatar menu with options to request a screen share, request a webcam, mute audio, or remove a participant Selecting **Mute Audio** immediately mutes the other participant's microphone. They receive an on-screen notification letting them know they were muted, with the option to unmute themselves. ## Shared Screen Tools Tuple has a number of tools that allow you to interact with a shared screen: ### Remote Control Remote Control tool selected in the shared screen toolbar When the remote control tool is enabled, all of your mouse and keyboard input events will be forwarded to the host machine. The only exception is command-tab (see settings below). ### Paint Paint tool drawing annotations across the shared screen This tool allows you to draw on the shared screen; your drawing can be seen by all participants. Your paint color is [automatically assigned](/pairing-with-tuple/collaborative-painting#paint-colors) so each participant's drawings are easy to tell apart. By default, anything you draw will fade away automatically after a few seconds, though this behavior can be changed (see settings below). You can also hold shift to draw a straight line. ### Highlight Click Highlight Click tool creating a pulsing indicator on the shared screen This tool allows you to create a pulsing indicator on the host's screen for a few seconds. Use this mode to quickly draw your pair's attention to something without getting in the way too much. ### Text Annotation Text Annotation tool placing a text label on the shared screen This tool allows you to place a text field anywhere within the shared screen, and type some text. You can also insert emojis using the system emoji picker (`Ctrl+Cmd+Space`). ### Reactions Reactions tool showing emoji and full-screen reactions in a call Tuple contains a number of reactions you can send while on a call. There are a handful of emojis that can be sent, as well as some bigger, full-screen reactions. ### Send GIF You can send animated GIFs during a call to bring a bit of fun into your pairing session (powered by [Klipy](https://klipy.com/)). Click the **Send Reaction** button in the toolbar, then select **Send GIF...** at the bottom of the menu. This opens a GIF picker where you can search for something specific. There are three ways to send a GIF: * **Click to send:** hover over a GIF to reveal the send button, then click it to place the GIF on the shared screen. * **Double-click:** double-click any GIF to send it immediately. * **Drag to place:** drag a GIF out of the picker and drop it anywhere on the shared screen to place it at that exact spot. GIF playback is synchronized across all call participants. Your recently sent GIFs are saved so you can quickly reuse them. You can also navigate the GIF picker with your keyboard. Use the **arrow keys** to browse results and press **Return** to send the selected GIF. ### Zoom Tool Zoom tool controls for zooming and panning around a shared screen The zoom tool allows you to zoom in (and pan around) on a shared screen. **Zooming** When using the zoom tool, there are a number of ways to zoom in and out: * **Left click** will zoom in; both **Option + left click** and **right click** will zoom out * **Double click** zooms all the way in; **double click** when zoomed all the way in will zoom all the way back out again * **Pinch in** (on a trackpad) will zoom in; **pinch out** will zoom out * **Command + scroll wheel / two finger scroll** will zoom in and out (the zoom direction will respect your "Natural scrolling" setting in macOS) * The **zoom buttons** in the lower right hand corner of the screen also allow you to zoom in and out **Panning** There are also a few different ways to pan with the zoom tool selected (once you've zoomed in): * Holding **space** while clicking and dragging will allow you to pan (your cursor will change into a hand) * Using the **scroll wheel** will pan up and down; if your mouse has a trackball that supports horizontal scrolling, that will work as well * **Holding down the scroll wheel** (also sometimes called the “third button”) will pan * You can use **two fingers** to pan (on a trackpad) * Panning can also be toggled on using the **pan button** in the lower right hand corner **Universal shortcuts** There are a handful of keyboard shortcuts that you can use, regardless of which tool you have selected (the only exception is when you're in remote control mode, since the keypresses would be forwarded to the remote machine): * **Command + `+`** will zoom in, **Command + `-`** will zoom out * **Command + 0** will make the shared screen the same resolution as your native resolution * **Command + 9** will zoom to fit the shared screen into the screen share window These commands can also be found in the **View** menu in the menu bar. **Zooming and panning while using other tools** It's also possible to zoom and pan when you have the drawing, highlight click, and text annotation tools selected: * **Option + left click** will zoom in; **Option + command + left click** or **Option + right click** will zoom out * **Option + double click** zooms all the way in; **Option + double click** when zoomed all the way in will zoom all the way back out again * Holding **Option + scroll wheel / two finger scroll** will zoom in and out * Holding **Option + pinching in** (on a trackpad) will zoom in; **Option + pinch out** will zoom out * You can hold **Option** and use **two fingers** to pan (on a trackpad) * Holding **space** while clicking and dragging will allow you to pan * **Option + holding down the scroll wheel** (also sometimes called the “third button”) will pan **Caveats** * Note that the zoom tool **won't** appear unless the screen you're viewing can be zoomed in on; if the shared screen is equal or lower to your native resolution, the tool won't be shown. * You're not able to zoom in further than your own native resolution * You won't be able to pan around if you haven't zoomed in ### Send Link Send Link tool sending a URL to the host computer This tool allows you to send your pair a URL, which will open automatically in their browser. This can be disabled by the call initiator in their Preferences. ## A/V Controls A/V controls for microphone, webcam, devices, and starting your own screen share The shared screen window also contains controls for enabling and disabling your microphone and webcam. You can also change your active audio and camera devices directly from this menu. Lastly, these controls also allow you to easily begin sharing your own screen. ## Settings Guest settings menu with stream resolution, paint persistence, and command-tab forwarding options ### Stream resolution The initial Stream Resolution setting is based on the call initiator's preference but can be temporarily modified by the guest during a call. If you aren't doing a lot of typing or clicking on the shared screen, you should crank this up to get the sharpest image. However, if you want very snappy responses to your keystrokes, then you can get lower latency by reducing the resolution of the video. ### Paint persistence You can choose whether you want drawings done with the paint tool to fade after 2 seconds, or persist until you right click. ### Forwarding command-tab You can toggle whether you want the command-tab key sequence to be sent to the host or not. ### Upscale Content On Apple Silicon Macs, you can toggle **Upscale Content** to sharpen the incoming screen share video. This runs locally on your machine, so it only changes how the shared screen looks on your display — it doesn't affect the resolution other participants receive. This toggle and the [Upscale content](/application-preferences/macos-preferences-screen-share#upscale-content) checkbox in the Screen Share preferences pane control the same setting, so changes in either place stay in sync. If the menu item doesn't appear, your Mac isn't on Apple Silicon or upscaling is disabled for your account. ## A note about fullscreen Tuple only allows fullscreen when the macOS setting 'Displays have separate spaces' is turned on. You can find that setting in System Preferences > Mission Control: macOS Mission Control settings with Displays have separate Spaces enabled Or in macOS 13.0+ in System Preferences > Desktop and Dock: macOS Desktop & Dock settings with Displays have separate Spaces enabled Shared screen window on Windows showing the guest toolbar across the top When **viewing** a shared screen, you can control the interaction using the guest toolbar at the top of the viewing window. ## Mouse modes Mouse modes toolbar with remote control and highlight click options Tuple supports multiple ways of using your mouse to interact with a shared screen: #### Remote control In this mode, you can use your mouse to interact with the shared screen. #### Highlight Click When using Highlight Click, clicks will create a pulsing indicator for a few seconds on the host's screen. Use this mode to quickly draw your partner's attention without getting too much in the way. Highlight Click creating a pulsing indicator on the shared screen ## Paint mode In Paint mode, you can click and drag to draw on your partner's screen. Anything you draw will fade away automatically after a few seconds. Paint mode drawing on the shared screen ## Keyboard modes Keyboard modes toolbar with No Keyboard and Keyboard options #### No Keyboard Your keystrokes will never be sent to the host machine. Use this mode if you will be switching back and forth between viewing your partner's screen and doing your own typing elsewhere, so you don't accidentally interfere. #### Keyboard This mode will transmit all of your keystrokes to the shared machine. Note that Tuple does *not* translate modifier keys when connecting to a host that's on macOS. For example, if you want to issue a keyboard shortcut on macOS that utilizes the command key (⌘), you could use the Windows key (⊞) instead. ## Reactions We have many reactions you can send while viewing a screen. Reactions panel with emoji and animated call reactions #### Emoji Reactions 🔥 Send 🔥, ❤️, 👍, 🤯, 👏, or 🤔 to your pair. #### Confetti 🎉 Essential for a pairing session. When you and your pair have accomplished something truly great, send some confetti to celebrate. #### Ship it 🚢 Code looking good to go? Use this one. #### Table Flip (╯°□°)╯︵ ┻━┻ Legacy code got you flipping out? Send this reaction. #### This is fine 🐶🔥 Everything a dumpster fire? Send this animation of [KC Green](https://kcgreendotcom.com/index.html)'s classic comic. ## Stream resolution Stream Resolution menu in the shared screen toolbar The initial Stream Resolution setting is based on the call initiator's preference but can be temporarily modified by the guest during a call. # Joining a call via a link/URL Source: https://docs.tuple.app/pairing-with-tuple/joining-a-call-via-a-linkurl Instead of starting a call by calling a contact directly, calls can be started or joined via a link. Call links are useful for scheduled calendar events, or inviting someone to join you on a call via a chat app like Slack. Tuple allows **up to 10 participants** on any call. Your personal call link is located at the top of the Link tab: Link tab showing your personal call link options * Hovering and clicking "Copy call link" will copy the call link to your clipboard so that you can paste and share it. * To join your own call, you can select "Join call and copy link." This will start the call, and allow you to add or approve any further participants. It will also copy the link to your clipboard. * If you'd like to create a new link, you can do so by selecting "Generate new call link." When a contact joins your call via your link, you will receive an approval notification that lets you add them to the call: Notification asking you to approve someone joining your call ## Joining another user's call via link When you join a call via a link, you will be conditionally added to the call pending the owner's approval. After they join the call, and approve you joining, you will be added to the call. # Joining an existing call Source: https://docs.tuple.app/pairing-with-tuple/joining-an-existing-call If a friend is currently pairing, you will see the yellow icon next to their avatar and a description of who they are pairing with below (more about showing who you are pairing with [here](/pairing-with-tuple/showing-who-youre-pairing-with)). If you would like to join this pairing session, you can do so by clicking the **Join** call icon: Contact list with the Join call button highlighted beside an active call This will notify the call participant that you would like to join their call by showing them a notification like this: Notification showing that someone wants to join your current call # Local call history Source: https://docs.tuple.app/pairing-with-tuple/local-call-history The **Local History** lists every call you've [transcribed](/pairing-with-tuple/transcribing-calls) on your machine. You can search past calls and open transcripts in your agent of choice. Local history is built from the same local transcript database the Tuple daemon writes as each call records — nothing is fetched from Tuple's servers. (For the team-wide history on the web, see [Viewing your call history](/managing-your-account/viewing-your-call-history).) ## Opening Local History Local History lives in its own tab in the app sidebar, alongside **People**, **Rooms**, and **Links**. Click the **Local History** icon to open it: Call History Tab Calls only appear here after you [transcribe](/pairing-with-tuple/transcribing-calls) them locally. ## Searching past calls Use the search box at the top to filter by **participants and transcripts**: * **Participant names** match as you type, including short prefixes — typing `cap` finds calls with "Capel". * **Transcript content** is full-text searched once any term is at least three characters long. For richer search from the terminal — phrase queries, speaker filters, date ranges, and exporting transcripts — see [Searching past calls](/pairing-with-tuple/searching-past-calls). ## Opening a transcript in an agent Click a call to expand its **Open in agent** panel. Copy the command, then run it in your terminal to open that call's transcript in Claude Code, Codex, or any agent — handy for summarizing the call or acting on what was discussed. If the Tuple CLI isn't installed yet, the panel shows an **Install CLI** button instead. See [Connect an AI agent](/cli/connect) for what an agent can do once it's attached. ## Deleting a transcript To remove a call from your device, right-click it and choose **Delete transcript**. This permanently deletes that call's recording, transcript, and events from your local database. It affects only this machine and can't be undone. You can also delete a stored call with [`tuple transcription delete `](/cli/commands#transcription-delete-call-id) or the `delete_stored_call` MCP tool. # Pairing with Someone on Tuple as a Guest Source: https://docs.tuple.app/pairing-with-tuple/pairing-with-someone-on-tuple-as-a-guest If you want to join a Tuple session but don't have an account, you can join as a **guest** — no credit card or trial required. Here’s how: ## 1. Request an Invite Link To get started, **ask the person you want to pair with to send you a Guest Invite Link**. They [can generate this from their Tuple app](/team-management/inviting-users) by going to the **Invite tab** and selecting "Invite as outside collaborator". This link is your key to joining them — don’t skip this step! ## 2. Use the Invite Link Once you receive the link: * Click it in your browser. * You’ll be guided through setting up a free guest account. You’ll only be able to pair with the person who invited you, and you won’t need to enter any payment info. ## 3. Download the Tuple App After completing the guest account setup, you’ll be prompted to download the Tuple app: * [Download for macOS](https://tuple.app/download/mac?utm_source=docs) * [Download for Windows](https://tuple.app/download/windows?utm_source=docs) Install the app and log in — you’ll be ready to join your inviter's session. ### Notes * Guest accounts are free, but limited: you can only pair with someone who has a paid Tuple account. * If you want to start your own team or host sessions, [start a 14-day trial](https://production.tuple.app/onboarding). * Already using a company email? You might be able to join your company’s Tuple account. # Requesting a share Source: https://docs.tuple.app/pairing-with-tuple/requesting-a-share While you're on an active Tuple call, you can request that another user shares their screen, or webcam: Call participant menu with options to request a screen share or webcam share This will pop up a share nudge notification on the user's screen: Share request notification prompting the other participant to start sharing ## Muting a participant You can also select **Mute Audio** from the participant menu to immediately mute another participant's microphone. The muted participant sees a notification telling them who muted them, with buttons to **Unmute** or dismiss. If the participant is already muted, no action is taken. The **Mute Audio** option appears in the participant menu on the webcam grid, the call window toolbar, and the in-call popover. # Searching past calls Source: https://docs.tuple.app/pairing-with-tuple/searching-past-calls Tuple keeps a local index of every call you've [transcribed](/pairing-with-tuple/transcribing-calls), so you can easily reference what you talked about on past calls. The index lives in a SQLite database that the Tuple daemon writes as each call records, and you query it from the `tuple` CLI. **Everything stays on your machine** - the transcript database is local. ## When to use this Reach for `tuple transcription` when you want to: * Find the call where you and your pair discussed a specific decision, error message, or library name. * Pull up the transcript of a recent call without remembering exactly when it happened. * Export transcripts to Markdown so you can drop them into your team's notes or feed them to an LLM. ## Listing recorded calls Use `tuple transcription list` to see calls in the database, most recent first: ```sh theme={null} tuple transcription list ``` Each row shows the call's start time (in local time), a short call ID, the number of transcript segments captured, and the participants Tuple resolved from the recording. Filter the list with any combination of: | Flag | Description | | ---------------------- | ---------------------------------------------------------------- | | `--participant ` | Only calls where a participant's name or email contains `` | | `--after ` | Only calls starting on or after `` (e.g. `2026-05-19`) | | `--before ` | Only calls starting on or before `` | | `--limit ` | Maximum calls to list (default `100`; pass `-1` for all) | Example — every call you had with someone named Mikey in the last month: ```sh theme={null} tuple transcription list --participant mikey --after 2026-05-11 ``` Pass `--format json` (a CLI-wide flag) to get the same data as JSON for scripting. ## Searching transcripts `tuple transcription search` runs a full-text search across every indexed transcript: ```sh theme={null} tuple transcription search "feature flag" ``` The query uses [SQLite FTS5 syntax](https://www.sqlite.org/fts5.html#full_text_query_syntax): bare terms are ANDed together, `"quoted phrases"` match exactly, and `OR` and `NOT` work as you'd expect. Matched terms in the output are highlighted with `[[ ]]` markers turned into bold text. Each result shows the call ID, timestamp, speaker, and a snippet around the match. Refine results with: | Flag | Description | | ---------------------- | --------------------------------------------------------------------- | | `--call ` | Only this call. Repeatable; accepts short call ID prefixes. | | `--speaker ` | Only segments spoken by someone whose name or email contains `` | | `--participant ` | Only calls with a participant whose name or email contains `` | | `--after ` | Only segments on or after `` | | `--before ` | Only segments on or before `` | | `-C, --context ` | Show `n` segments of conversation around each match | | `--limit ` | Maximum matches (default `50`; pass `-1` for all) | Example — find every time your teammate mentioned "rollback" with two lines of context on either side: ```sh theme={null} tuple transcription search rollback --speaker alex -C 2 ``` ## Showing a full transcript Once you've found the call you want, pass its short ID to `tuple transcription show` to print the full transcript: ```sh theme={null} tuple transcription show a1b2c3d4 ``` The ID can be any unique prefix of the full call ID — the same short IDs `list` and `search` print. The output is a sequence of timestamped speaker lines. Add `--format json` for the raw segments. ## Exporting transcripts `tuple transcription export` writes one file per call into a directory, named `@.`: ```sh theme={null} tuple transcription export ~/Documents/tuple-transcripts ``` Choose the output format with `--format`: | Format | Description | | -------------- | -------------------------------------------------------------------------------------------------------- | | `md` (default) | YAML frontmatter (call ID, start/end times, participants) followed by the transcript | | `text` | Plain header lines followed by the transcript | | `ndjson` | One transcript segment per line, each including `call_id` — so `cat *.ndjson` yields one combined stream | Filter which calls get exported with the same `--call`, `--participant`, `--after`, and `--before` flags as `list`. Calls without transcript segments are skipped, and existing files are overwritten — re-running the command refreshes the export. Example — export every transcribed call from May 2026 as Markdown: ```sh theme={null} tuple transcription export ~/Documents/tuple-transcripts \ --after 2026-05-01 --before 2026-05-31 --format md ``` # Sharing a webcam video Source: https://docs.tuple.app/pairing-with-tuple/sharing-a-webcam-video Pairing well is all about communication, and there's so much information sent through someone's facial expressions. Seeing your pair's face while working really makes you feel more connected to them. ## Sharing a webcam To add a webcam video, click the "Start Webcam" icon in the popover UI: Call controls showing the Start Webcam option You'll be shown a preview of your webcam's feed so you can adjust your lighting or background: Preview window showing your webcam feed before sharing starts **Note:** this preview won't be seen by your pair, even if you're already sharing your desktop with them. Once you click "Start Sharing", the preview disappears and your video stream will be shown to your pair. When you aren't sharing your webcam feed, you can bring up the camera preview via an option in the dropdown: Webcam dropdown menu with the preview option and available camera devices ### Conversation mode & coding mode Tuple has two different ways to see the webcams of other participants on the call: **coding mode** and **conversation mode.** **Coding mode** is the default mode, and is designed to be used in a pairing session where code is the primary focus: * In this mode, the webcam window will float above all other windows. * This mode allows participant webcams to be laid out either in a horizontal row or vertical column (this can be changed from the triple-dot menu). * In this mode, the webcam window resizes proportionally. * Buttons and other UI elements are hidden by default and show up when your cursor hovers the window. Coding mode webcam window floating above the screen share Conversely, you can use **conversation mode** in sessions where more of the focus is on discussion amongst the participants: * In this mode, the webcam window will behave like a normal window (i.e. it will go behind other windows when it loses focus, the window can be resized arbitrarily, etc). * This mode lays out participant webcams in a grid, which allows each individual webcam view to be larger. Conversation mode webcam window arranged in a grid layout To switch between the two modes, click the toggle button in the top-right of the webcam window: Toggle button for switching between coding mode and conversation mode ## Showing your own webcam self-view While on a call, you can show or hide your own self view. This won't change whether other participants can see your webcam or not. To show or hide your self view, click on the menu in the top-right corner of the webcam window, and click "Show/Hide Self View". Webcam window menu with the Show or Hide Self View option When you're in **coding mode**, you have the ability to choose between a small or large self view. *Small* will display your own video feed as a minimized thumbnail placed in the bottom-right corner of the webcam window, which helps save space when you're pairing with someone. *Big* sets your self-view to be the same size as the other tiles. Coding mode self-view size options for small and big tiles ## Participant controls Click another participant's webcam tile to open its menu. Depending on the participant's current state, you can: * **Ask to Share Screen** * **Ask to Share Webcam** * **Ask to Unmute** * **Mute Audio** * **Kick** them from the call These controls are available in coding mode and conversation mode. ## Changing the webcam in use while sharing You can swap out the webcam in use when you are sharing a webcam by selecting the camera you'd prefer to share by selecting it from the dropdown: Webcam dropdown menu listing the available camera devices while sharing ## Using effects on your webcam On supported Macs, macOS provides free background blur and other camera effects that work with Tuple. While your webcam is active, open the camera indicator in the menu bar and enable **Portrait**. Availability depends on your Mac and camera; see [Apple's requirements](https://support.apple.com/105117). Menu bar webcam indicator with video effects controls On macOS Ventura, open **Control Center**, click **Video Effects**, then enable **Portrait**. Tuple also supports virtual cameras. You can use third-party software such as [Krisp.ai](https://krisp.ai/) or [OBS](https://obsproject.com/) when you need effects beyond those included with macOS. ## Setting your webcam resolution preference You can set the webcam resolution in your [preferences](/application-preferences/macos-preferences-webcam). Setting the resolution will automatically change the resolution on any current calls. You might need to tweak your resolution depending on your network connection. If you have a high-speed connection and want maximum webcam quality, set your preference to high. If you're working with a relatively poor connection, set your preference to medium: Preferences screen showing webcam resolution options ### Sharing a webcam To add a webcam video, click the "Start Webcam" icon in the popover UI: Call controls showing the Start Webcam option on Windows You'll be shown a preview of your webcam's feed so you can adjust your lighting or background: **Note:** this preview won't be seen by your pair, even if you're already sharing your desktop with them. Once you click "Start Sharing", the preview disappears and your video stream will be shown to your pair. When you aren't sharing your webcam feed, you can bring up the camera preview via an option in the dropdown: Webcam dropdown menu with preview and camera device options on Windows ### Showing your own webcam self-view While on a call, you can show or hide your own self view. This won't change whether other participants can see your webcam or not. To show or hide your self view, click on the menu in the top-left corner of the webcam window, and toggle "Show Self View". Webcam window menu with the Show Self View toggle on Windows ### Changing the Webcam in use while sharing You can swap out the webcam in use when you are sharing a webcam by selecting the camera you'd prefer to share by selecting it from the dropdown: Camera device dropdown for switching webcams on Windows # Sharing your screen Source: https://docs.tuple.app/pairing-with-tuple/sharing-your-screen To start a Tuple call, click the "Call" icon to the right of one of your contact's names: Tuple contact list with the Call button highlighted beside an available teammate **Note:** You'll only be able to start a call if the user is online and available (indicated by a green dot next to their avatar). The dot next to each contact reflects their current presence. Tuple sorts your contact list by favorites, then presence (online, then busy, then offline), then name: * **Online** (green dot) — signed in and available to pair. * **Busy** — signed in but currently on a Tuple call. * **Offline** — no dot; Tuple isn't running or the person is signed out. Presence is updated in real time as people sign in, start calls, and leave calls. The same states are available from the terminal via [`tuple contacts list --status`](/cli/commands#contacts-list-query-flags). Once you have started a call you'll be able to share your screen by clicking "Share screen": Tuple in-call menu with the Share screen button highlighted ## Sharing part of your screen To start a call sharing only part of your screen, **right-click (or Control-click)** on your contact's name: Tuple contact context menu with Share Part of Screen highlighted If you're already on a call, you can share part of your screen by selecting the sub-option "Share Part of Screen": Tuple in-call menu with Share Part of Screen highlighted You'll then see a dotted line indicating which part of the screen you're about to share, along with a toolbar at the bottom of your screen. You can drag the handles of the rectangle to change its size, or drag the rectangle itself to move it around your screen: A resizable dotted rectangle showing the selected part of the screen to share The toolbar provides several options for adjusting your selection: * **Pick a window** — Click the window icon in the toolbar (or press `Space`) to enter pick-window mode. Hover over any window to highlight it, then click to snap the selection rectangle to that window's bounds. Press `Space` again to return to manual rectangle mode, or press `Escape` to cancel. *Note*: this just sets the screen share boundaries - if the window moves or gets hidden, the boundary won't change. * **Pin to half screen** — Click one of the directional icons to snap the selection to the left, right, top, or bottom half of your screen. * **Aspect ratio** — Choose a preset aspect ratio (16:10, 16:9, or 4:3) to optimize your sharing area for your pair's display. * **Resize without changing dimensions** — Hold `Shift` and drag a corner handle to scale the selection proportionally. Press **Share** once you are satisfied with your choices. You'll then see the red corners indicating what your pair can see: Red corner indicators showing the active shared area after starting a partial screen share If you'd like to change what part of your screen you're sharing, you can opt to "Change Selection": Tuple menu showing the Change Selection option while sharing part of your screen ## Sharing your screen immediately when starting a call If you'd like to share your screen immediately when starting a call, you can hold **alt** to toggle the call button mode. When **alt** is held, the call button will show the option to start a screen sharing call, which will share your screen immediately when your pair answers: Tuple call button changing to Start Screen Share when Option is held ## Choosing which display to share If you have multiple displays and choose to start a call with screen sharing, Tuple will prompt you to pick one before the call starts. Just click anywhere on the display you want to share: Tuple prompting you to choose which display to share before the call starts If you're not seeing a menu bar on your external display at all, check that you have the "Displays have separate Spaces" setting enabled in Desktop & Dock at the bottom of the page: macOS Desktop & Dock settings with Displays have separate Spaces enabled ## Changing displayed screens during a call If you're already sharing a display but want to switch to a different one, you can swap to a specific display by opening the Tuple app menu and clicking "Switch Screen" or by using the quick switcher: Tuple menu showing the Switch Screen option and display quick switcher ## Changing which call participant is sharing their screen If another call participant is sharing their screen, you can swap to sharing your screen by opening the Tuple app menu and clicking "Share Screen": Tuple menu showing the Share Screen option while another participant is sharing ## Stopping sharing your screen When you would like to stop sharing your screen, you can stop by opening the Tuple app menu and clicking "Stop Sharing": Tuple menu showing the Stop Sharing option If you're currently sharing an external display screen, you'll need to move your mouse to that display, and open the Tuple app menu on that specific display to see the "Stop Sharing" button. Alternatively, you can find the ability to stop sharing any screen in the dropdown from the Tuple app menu: Tuple app menu dropdown showing screen switching and sharing controls for the active display **Note:** To access the Tuple app menu on an external monitor, you will also need to have "displays have separate spaces" enabled (see **Changing displayed screens during a call** above). # Showing Who You're Pairing With Source: https://docs.tuple.app/pairing-with-tuple/showing-who-youre-pairing-with By default, Tuple will show who you're pairing with to people who are friends or teammates with both you and your pair: Contact list showing the names of both people in a call (*Since I'm friends with Mikey and Joel, I can see when they're on a call together.*) If a friend is pairing with people you aren't friends with or a teammate of, you'll see an anonymized version: Contact list showing an anonymized call for people you do not know (*I'm not friends with Spencer's friends, so his call is shrouded in mystery.*) Naturally, you can disable this behavior in preferences: Preference for showing who your teammates are pairing with # Sound Check Source: https://docs.tuple.app/pairing-with-tuple/sound-check If you're currently on a call, you can open the quick sound preferences view by pressing the caret icon in the audio mute/unmute icon: Quick sound preferences menu opened from the call audio controls ## Troubleshooting If you're having issues getting audio to work, here are some steps you can take: 1. Make sure your audio devices are turned on and any hardware volume settings are on 2. Open the Sound settings in System Preferences and make sure the correct devices are selected, and set Tuple to use the System Default 3. Restart Tuple ## Using Tuple with Multi-line audio interfaces Tuple should enumerate all of the possible input options provided by your audio interface device. It will treat each line on the device as a separate input device that you can choose: Audio input list showing multiple lines from a multi-input audio interface # Speaking-while-muted notification Source: https://docs.tuple.app/pairing-with-tuple/speaking-while-muted-notification Tuple alerts you when it detects you're speaking while your microphone is muted. If you accidentally speak while muted during a call, Tuple displays a notification to let you know. This helps you avoid talking without being heard by your pair. ## How it works When you're muted during a call, Tuple listens for your voice in the background. If it detects sustained speech for about half a second, a "You're muted" notification appears in the in-call notification stack anchored under the Tuple status item. The notification gives you two options: * **Unmute Me** — immediately unmutes your microphone so your pair can hear you. * **Got it** — dismisses the notification. The alert can still fire again later in the call (subject to the cooldown below). If you close the notification without clicking either button, the feature stays active and will notify you again the next time it detects speech while muted. ## Cooldown After the notification fires, there is a two-minute cooldown before it can trigger again. This prevents repeated alerts if you mute and unmute frequently during a call. ## Disabling the notification Dismissing the notification with **Got it** no longer silences future alerts for the rest of the call — suppression is now explicit. To stop receiving the notification, use the cancel pill's menu on the card: * **Snooze for This Call** — suppresses the speaking-while-muted notification for the remainder of the current call only. It re-enables automatically on your next call. * **Never Show Again** — turns the notification off permanently. This is the same setting as **Notify me when: Speaking while muted** in [Audio preferences](/application-preferences/macos-preferences-audio), and you can re-enable it there at any time. ## How it works When you're muted during a call, Tuple listens for your voice in the background. If it detects sustained speech for about half a second, a "You're muted" prompt appears with the message "It sounds like you're speaking." The prompt gives you two options: * **Unmute Me** — immediately unmutes your microphone so your pair can hear you. * **Got it** — dismisses the prompt and disables the alert for the rest of the current call. If you close the prompt without clicking either button, the feature stays active and will notify you again the next time it detects speech while muted. ## Cooldown After the prompt fires, there is a five-minute cooldown before it can trigger again. This prevents repeated alerts if you mute and unmute frequently during a call. ## Disabling the notification Click **Got it** on the prompt to turn it off for the rest of the current call. The feature reactivates automatically on your next call. This feature is not available on Linux. # Spotlight Integration Source: https://docs.tuple.app/pairing-with-tuple/spotlight-integration ### Supported Intents If you're using macOS 26 (Tahoe) or later, you can use Spotlight to control Tuple. The following intents are supported: * **Searching for contacts by name:** You can enter a contact's name; selecting them will highlight them in the Tuple popover. You can hit enter again to call them. * **Searching for rooms by name:** Similarly, you can enter a room name to highlight it in the popover. Hitting enter again will join the room. * **"Call...":** Selecting the "call..." intent will prompt you to enter a contact name from within Spotlight. Hitting enter will then directly call that person. * **"Join...":** Selecting the "join..." intent will prompt you to select a room from within Spotlight. Hitting enter will then join that room. ### Disabling the Integration You can also disable the integration if you don't want your Tuple contacts to clutter up your Spotlight results. You can do so via the [Integrations settings tab](/application-preferences/macos-preferences-integrations). ### Using in Shortcuts You can also use the intents above to create custom shortcuts using the [Shortcuts app](https://support.apple.com/guide/shortcuts-mac/intro-to-shortcuts-apdf22b0444c/mac): Shortcuts app showing Tuple actions built from Spotlight intents These shortcuts will also be available in Spotlight: Spotlight showing Tuple shortcuts in the results list # Transcribing calls Source: https://docs.tuple.app/pairing-with-tuple/transcribing-calls Tuple's local transcription feature is in **alpha**. Aspects of the feature might change as we continue to iterate on feedback. Tuple can transcribe what you and your pair say during a call. Transcription runs **entirely on your machine** — nothing is sent to an external service. ## Starting and stopping transcription The **Transcribe** button appears in the call controls popover, the screen share window, and the webcam window. Transcription screen share control Transcription webcam control Transcription popover control ## Notifying participants about transcription When you start transcription, every other participant receives a privacy notice. Transcription starts immediately — the notice is informational, not a gate. The notice stays on screen until the participant acknowledges it or leaves the call. If you join a call that's already being transcribed, you'll receive the same notice. While transcription is active, each transcribing participant shows a **Transcribing** indicator in the popover participant list and in the screen share window's participant list. ### Transcribing solo You can transcribe a call by yourself, even before any other participants have joined. This is useful for capturing notes, dictation, or working with an agent (see [Connecting an agent](#connecting-an-agent)). ## First-run model download Tuple transcribes using an on-device Whisper model. If you attempt to start transcription without a model downloaded, you'll be prompted to download one. Click **Download** to fetch the default "Turbo" model. When the download finishes, transcription starts automatically. To choose a different model before downloading, click **Open Settings…** instead. Learn more about [supported models](/application-preferences/macos-preferences-transcription#supported-models). ## Connecting an agent When transcription is active, the panel shows a `tuple connect` command. Copy it and run it in a terminal to attach an AI agent (Claude Code, Codex, Cursor, Copilot, or any [harness](/cli/connect#supported-harnesses)) to the live call. If the Tuple CLI isn't installed yet, the panel shows an **Install CLI** button instead. See [Connect an AI agent](/cli/connect) for full details on what agents can do once connected. ## Reading the transcript live Tuple doesn't display the running transcript in the app itself — the in-call panel only shows the `tuple connect` command and the transcribing indicator. To read the transcript as it's being produced, use the CLI: ```sh theme={null} tuple transcription show --follow ``` `--follow` streams new speech segments in arrival order until the call ends. See [`transcription show`](/cli/commands#transcription-show-call-id-follow-wait-interval-duration-watch-words-timeout-duration-cursor-tag-with-events-without-speech-with-speech-markers) for other modes (one-shot snapshots, `--wait` batching, `--watch-words`, and per-recording streams). ## Accessing your transcripts Transcripts are stored in a local database on your machine. Use the `tuple` CLI to search, list, and export past calls. See [Searching past calls](/pairing-with-tuple/searching-past-calls). ## Automating with triggers Tuple fires `call-transcription-started` and `call-transcription-complete` [triggers](/triggers/api-reference) when transcription begins and ends. Use a trigger script to post-process transcripts, summarize the call, or save it into your team's notes system. Browse the [Triggers Directory](https://tuple.app/triggers/directory?event=call-transcription-started\&event=call-transcription-complete) to see what others have built. # Using Rooms Source: https://docs.tuple.app/pairing-with-tuple/using-rooms Rooms are like persistent calls with names. They can be useful for recurring events (standups, social time, etc.) or specific teams (engineering touchbase, design reviews, leadership, etc.). Rooms live in their own tab: Rooms tab showing the list of shared team rooms Rooms are owned collectively by the whole team, meaning: * Everyone in your team can **see all rooms** and who is in them * Everyone in your team can **create, rename, or delete** rooms *** ## Your personal room The Rooms tab also includes a **"Your personal room"** card pinned at the top of the list. Clicking the card does two things at once: it **joins your personal room** *and* **copies your personal-room link** to your clipboard, so you can paste it into a chat or calendar invite. The card reuses your **existing** personal-room link — the same one already in your calendar invites or recurring events — rather than minting a new URL each time. Links you've previously shared keep working, and any open/closed change you make below applies to those same links. ### Open vs closed (door control) While you're in your personal room, a **door control** row sits just below the call controls. Use it to change who can join, mid-call: * **Open** — your contacts and teammates can join freely. * **Closed** — joining requires your approval. Visitors see "Room closed · request to join" and you'll be prompted to admit them. Toggling open/closed writes through to your personal-room link immediately, so anyone using a previously shared link will get the new permission the next time they try to join. The door control is **owner-only**: it appears for personal rooms you own. Visitors to someone else's personal room and participants in team rooms don't see it. If your team enforces approval through the call-link settings, the door is locked to **Closed** and can't be toggled from the call. *** ## Joining a room To enter a room, simply click on it. **You can only be in a single room (or call) at once**: if you enter a room while you're already in another call, you'll get prompted for a confirmation that you want to leave it. Confirmation dialog asking whether you want to leave your current call and join a room Like any other call, **rooms have a capacity of 10 participants**. *** ## Viewing and updating room info To **copy a room's link**, click the dropdown menu icon in its top-right corner and select the "Copy Room Link" action. If you want to **rename or delete a room**, you can find those actions in the "More" submenu. *** ## Creating a room To create a new room, **click on the + icon** in the top-right corner of the app. You'll be prompted to pick a name for the room, and that's it! Note that duplicate names are not allowed: you can't create a room (or rename an existing one) with a name that's already in use. Dialog for creating a new room by name Rooms are **ordered alphabetically**. If you want to enforce a custom ordering, you can use leading numbers or letters, like for instance "1. Engineering", "2. Support", and "3. Marketing". We currently support **up to 40 rooms per team**. *** ## Favoriting rooms You can **favorite rooms** by selecting the "Favorite Room" action in the room's dropdown menu. Favorited rooms stay pinned to the top of the Rooms tab. Room dropdown menu with Favorite Room selected *** ## Subscribing to rooms *(macOS only)* You can **subscribe to rooms** to get notified when your teammates are in a given room. Using the "Subscribe" submenu in the room's dropdown, you can select whether you want to show a badge on the Tuple menu bar icon, receive a push notification, or both. Room dropdown menu showing subscription notification options *** ## Auto-removal of inactive rooms To keep your team's room list manageable, Tuple automatically removes rooms that haven't been used in a while. ### Removal criteria * Rooms that haven't had any calls for 180 days are automatically removed * Rooms created within the last 30 days are never removed, even if unused * Rooms with an active call are never removed ### What removal means * Rooms are removed permanently. If a room is removed and you'd like to use it again, you'll need to create one with that name again. ### Adjusting or disabling auto-removal Team owners and managers can configure this in [Team Settings → Room Settings](https://production.tuple.app/team_management/rooms): * Change the inactivity period (30 to 365 days) * Disable auto-removal entirely *** ## Managing rooms from the CLI The [Tuple CLI](/cli/commands#tuple-rooms) can list, join, favorite, and create rooms. The MCP server exposes the same actions to AI coding agents. *** We're still exploring ways to make Rooms more valuable. If you have any feedback that would make this feature more useful to your team, [let us know](mailto:support@tuple.app?subject=Rooms%20Feedback)! # Using Tuple for Interviews Source: https://docs.tuple.app/pairing-with-tuple/using-tuple-for-interviews Pairing is a great way to interview developers. Pairing with candidates using Tuple is even better! Invite candidates as **outside collaborators**, not teammates. This does not add them to your team or consume a paid seat. Candidates without a Tuple account can create a free guest account without starting a trial or entering payment information. When you *schedule* the interview, send the candidate your outside-collaborator invite link or add them by email. They can accept with an existing Tuple account or create a [free guest account](/managing-your-account/using-tuple-as-an-unpaid-user). Guest invite link flow for adding an interview candidate to Tuple You'll be added as contacts for one another, but they will *not* be able to see the rest of your Tuple team in their contact list. You can then send them a call link or call them directly at the time of the interview. Note: candidates who create free guest accounts will *not* be able to join Room links. To remove them after an interview, right-click them in your contact list and select **Remove**. # Using your keyboard to navigate Tuple Source: https://docs.tuple.app/pairing-with-tuple/using-your-keyboard-to-navigate-tuple Prefer to navigate with your keyboard instead of your mouse? Tuple supports keyboard shortcuts. To access the in-app list of shortcuts, open the Tuple menu by clicking your avatar image in the bottom left of the popover: Tuple avatar menu with Shortcuts highlighted You can also access this view by pressing the **?** key while the Tuple window is in focus. Selecting **Shortcuts** will bring up the overview: Keyboard shortcuts overview window in Tuple Scroll down to review all shortcuts currently available. # Adding Details To Your Invoice Source: https://docs.tuple.app/team-management/adding-details-to-your-invoice If you are the Owner of a team with an active subscription, you can add extra information — a billing address, tax information, or a VAT ID — to your invoice. Beneath the Users List, select "Manage Billing": Team management page with the Manage Billing button highlighted Then, click on "Update information": Stripe billing portal with the Update information button highlighted That will bring you to a page where you can add your address and tax information to your invoices. Stripe invoice information form for address and tax details If you need an old invoice voided in favor of an updated version, please [write to us](mailto:support@tuple.app)! # Cancelling a subscription Source: https://docs.tuple.app/team-management/cancelling-a-subscription If you are the **Team Owner** for a team with an **active** or trial subscription, you can access the billing portal by selecting "Manage billing on Stripe" on [your team management page](https://production.tuple.app/team_management/settings). You will see the following "Manage billing on Stripe" button: Team management page with the Manage billing on Stripe button highlighted This will take you to a page where you can set your subscription to cancel, renew it if you change your mind, and download past statements: Stripe billing portal showing plan management and cancellation options # Single Sign-On (SSO) Source: https://docs.tuple.app/team-management/configuring-single-sign-on-sso How to configure SAML SSO for your Tuple team Tuple supports SAML Single Sign-On as part of the Standard and Enterprise plans. SSO lets your team authenticate through your identity provider instead of managing separate Tuple credentials. ## Provider-specific guides Includes optional SCIM provisioning Google Admin console setup Formerly Azure AD SAML Test Connector setup ## General SSO setup If your identity provider is not listed above, you can configure SAML SSO manually using the values below. Email addresses in and Tuple must match exactly. For example, `dev+tuple@company.com` does not match `dev@company.com`. Verify your team's email addresses before enabling SSO. Tuple falls back to the SAML NameID when no email attribute is present in the response, so if your provider's NameID defaults to something other than email (such as a username or UPN), set it to the user's email address as well. ### What you need from your identity provider * Your SSO IdP Entity ID * Your SSO target URL that performs authentication * Your auth certificate or its SHA1 fingerprint * Attributes that include `first_name`, `last_name`, and `email` ### Tuple's SAML endpoints | Field | Value | | ------------------------------------ | -------------------------------------------------- | | Entity ID | `https://production.tuple.app/users/saml/metadata` | | Assertion Consumer Service (ACS) URL | `https://production.tuple.app/users/saml/auth` | ### Enable SAML in Tuple Navigate to the **Settings** tab of the [team management dashboard](https://production.tuple.app/team_management/settings). Only [team owners](/team-management/team-owner-and-managers) can enable SAML. To find out who your team owner is, check [your profile](https://production.tuple.app/profile#team). Under **Sign-in methods**, set **Required Authentication Provider** to **SAML SSO**. The **Update SAML Configuration** form appears: SAML configuration form in Tuple Fill in the values with your metadata: Select the **Email Domain** that SAML should apply to. Only domains with confirmed team members are available. Click **Save as draft**. Your draft is saved as a **Pending Update** alongside your current sign-in method, so no one on your team is affected yet. Pending SAML update showing Test and Publish actions Click **Test** to verify the configuration end-to-end. Tuple signs you in through so you can confirm that authentication succeeds before the change affects anyone else on your team. Once the test succeeds, click **Publish** to make the configuration live. Active Tuple sessions persist, but new sign-ins are routed through . Use **Edit** to tweak the draft, or **Discard** to throw it away without publishing. ## Managing your SAML configuration Tuple keeps a single **Active Configuration** plus any **Pending Update** draft you're working on. Changes are staged as drafts so you can test them before they affect the rest of your team. ### Update an existing configuration On the **Settings** tab of the team management dashboard, click **Update configuration** on the Active Configuration card. The **Update SAML Configuration** form opens pre-filled with your current values. Leave the **Certificate** field empty to keep the existing certificate, or upload a new file to replace it. The current fingerprint is shown above the upload field. Click **Save as draft** to create a Pending Update. From there, **Test** the draft, then **Publish** it when you're ready. You can also **Edit** the draft to make further changes, or **Discard** it to throw it away. ### Archived configurations When you publish a new SAML configuration, the previous one is automatically archived. Expand **archived SAML configurations** on the Settings tab to see past configurations: Archived SAML configurations with Restore and Delete actions * **Restore** -- bring an archived configuration back so you can test and publish it again. This is useful if you need to roll back to a previous identity provider setup. * **Delete** -- permanently remove an archived configuration. ## Reference The first time a user authenticates through your identity provider, Tuple provisions an account for them. If you are on a per-seat billing plan, billing begins for that seat immediately. Tuple finds or creates the account using the email address in the SAML response, falling back to the NameID when no email attribute is present. An unrecognized address creates a new account rather than failing, and an address that belongs to a member of a different team fails with "Not a member of the team associated with the Identity Provider." Both symptoms usually mean the identity provider is sending a different identifier (such as a UPN) than the user's Tuple email address. You can disable a user's access in your identity provider, but deprovisioning their Tuple account (and stopping billing for that seat) must be done on the [team management page](https://production.tuple.app/team_management/members) by your team owner. For automated provisioning, see [SCIM provisioning](/team-management/scim-provisioning). Tuple has three roles: **team owner**, **team manager**, and **user**. * **Team owners** can manage team settings, add and remove users, promote managers, and update billing information. * **Team managers** can manage team settings, add and remove users, and promote other managers. * **Users** can make and receive calls and share team invite links. See [Team Owner and Managers](/team-management/team-owner-and-managers) for the full permissions breakdown. Accounts provisioned through your identity provider are created as users. The team owner is typically the person who first created your team on Tuple. Contact [support@tuple.app](mailto:support@tuple.app) if you need to transfer ownership. Email addresses in your identity provider and Tuple must match exactly. For example, `dev+tuple@company.com` does not match `dev@company.com`. Verify your team's email addresses before enabling SSO. Tuple reads the email from the SAML email attribute, checking several common claim names automatically. If no email attribute is present, Tuple uses the NameID value instead. If your provider's NameID isn't the user's email address -- notably Microsoft Entra ID, where it defaults to the UPN -- see the [Microsoft Entra ID guide](/team-management/sso-azure) for how to keep them consistent. ## Questions? [Email us](mailto:support@tuple.app) and we'll help you get set up. # Exporting Team Usage Source: https://docs.tuple.app/team-management/exporting-team-usage Any Team Owner or Team Manager can pull a usage report for their team. ## Downloading your team's CSV user report 1. Head to [your team management page](https://production.tuple.app/team_management/usage) as a Team Owner or Team Manager. 2. Click the "Download user report (CSV)" button: Team settings page with the Download user report CSV button highlighted The downloaded team usage report has five columns: **Email** - The user's email address. **Name** - The user's name. **Last pairing session date** - The last date and time when this user participated in a Tuple call. **Account created date** - When this user joined the team (or was added via SCIM). **Role** - "Team Manager" or "Team Owner" if the user holds that role. ## Downloading your team's JSON usage report 1. Head to [your team management page](https://production.tuple.app/team_management/usage) as a Team Owner or Team Manager. 2. Click the "Download usage report (JSON)" button. This report will include all non-deleted team members, as well as call records going back **six months**: Team settings page with the Download usage report JSON button highlighted The downloaded team usage report adheres to the following JSON schema: ``` { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "team_members": { "type": "array", "items": { "type": "object", "properties": { "user_id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "last_sign_in": { "type": ["string", "null"], "format": "date-time" }, "created_at": { "type": "string", "format": "date-time" }, "last_call_at": { "type": ["string", "null"], "format": "date-time" } }, "required": ["user_id", "name", "email", "created_at"] } }, "calls": { "type": "array", "items": { "type": "object", "properties": { "call_id": { "type": "string", "format": "uuid" }, "participants": { "type": "array", "items": { "type": "object", "properties": { "user_id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string", "format": "email" }, "duration_minutes": { "type": "integer", "minimum": 0 } }, "required": ["user_id", "name", "email", "duration_minutes"] } }, "duration_minutes": { "type": "integer", "minimum": 0 }, "started_at": { "type": "string", "format": "date-time" }, "ended_at": { "type": "string", "format": "date-time" } }, "required": ["call_id", "participants", "duration_minutes", "started_at", "ended_at"] } } }, "required": ["team_members", "calls"] } ``` # Inviting users Source: https://docs.tuple.app/team-management/inviting-users To invite a user to pair with you, navigate to the Invite tab: Invite tab showing teammate and outside collaborator invite options You can invite either **teammates** or **outside collaborators.** If you add someone as a **teammate,** they'll be added to your team once they accept your invitation and create an account. You'll also be added as contacts for one another. Note that adding a teammate means that you'll start paying for their seat at the start of the next billing cycle. If you add someone as an **outside collaborator**, they will *not* be added to your team. Once they accept your invitation, you'll be added as contacts for one another, and you'll be able to call one another. However, the person you invited won't be able to call anyone else on your team. Inviting an outside collaborator does not add them to your team or consume one of your team's paid seats. They can accept with an existing Tuple account—including an account paid for by another team—or create a free guest account. Regardless of whether you're inviting someone as a **teammate** or **outside collaborator**, you can either enter their email to send them an invitation, or you can send them the invitation link directly. # Managing Team billing Source: https://docs.tuple.app/team-management/managing-team-billing If you are the **Team Owner** for a team with an **active** or trial subscription, you can access the billing portal by selecting "Manage billing on Stripe" on [your team management page](https://production.tuple.app/team_management/settings). You will see the following "Manage billing on Stripe" button: Team management page with the Manage billing on Stripe button highlighted This will take you to a Stripe billing portal page where you can make modifications to your existing subscription, and download past statements. ## To add a new payment method: If there is currently no payment method on file (if your team is in a trial), you can add a new payment method by clicking on "Add payment method": Stripe billing portal with the Add payment method button highlighted ## To update the existing payment method: In order to delete a credit card on file, you will need to add a new one first. You can add a new payment method by using the button mentioned above. ## To cancel your subscription: You can set your subscription to cancel at the end of the current period by selecting Cancel plan: Stripe billing portal showing plan management and cancellation options ## To update the billing email address (that receives invoices): Update the value under BILLING INFORMATION Email: Billing information section with the invoice email field highlighted # OSS and startup plans Source: https://docs.tuple.app/team-management/oss-and-startup-plans Special pricing for open source teams and early-stage startups. ## Open source plan If you're part of an open source project, you may qualify for free Tuple licenses. Learn more and apply at [tuple.app/oss](https://tuple.app/oss?utm_source=docs). ## Startup plan If you're at a startup, you may qualify for a discount on Tuple. Learn more and apply at [tuple.app/startups](https://tuple.app/startups?utm_source=docs). # Pairing Insights Source: https://docs.tuple.app/team-management/pairing-insights See how your team pairs, which communities form around shared work, and who connects them. Pairing Insights gives Team Owners and Team Managers a visual summary of how people on the team pair together. Use it to spot who is pairing with whom, which sub-teams have formed naturally, and which teammates bridge otherwise disconnected groups. ## Opening Pairing Insights 1. Sign in to [tuple.app](https://tuple.app?utm_source=docs) as a Team Owner or Team Manager. 2. Open your team and go to **Pairing Insights** in the team sidebar. 3. Use the period toggle at the top right to change the time window the report covers. If your team has never made a call on Tuple, the page shows an empty state instead of the report. ## Reading the report Pairing Insights is made up of a few panels, each driven by the same time window you selected at the top of the page. **Quick stats** summarizes total calls, total pairing time, and the number of unique pairs for the selected period. **Pairing activity** charts how much your team paired across the period. **Peak hours** shows when during the day your team tends to pair, in your local timezone. **Network map** is an interactive graph of every teammate who paired during the period. Each dot is a person, and each line is a pair of teammates who had at least one call together. Thicker lines mean more calls between that pair. **Communities** lists the clusters of teammates that consistently pair with one another. Tuple detects communities automatically from call history — you don't configure them. **Connectors** lists teammates who bridge two or more communities. These are the people most responsible for cross-pollination on your team. ## Using the network map The network map is an infinite canvas you can pan and zoom: * Drag with the left mouse button to pan. * Scroll or pinch to zoom. * Middle-click and drag to pan without selecting. * Click **Fit** to reframe the whole graph in view. Hover a node to see a card with that teammate's pairing stats for the period. Hover a community to see its members and size. Click a connector in the **Connectors** panel to highlight that teammate's node on the map; click the close button on the card to clear the selection. Node size reflects engagement — teammates who pair with many different partners, or pair often, appear larger. Nodes are colored by community. Teammates who only pair inside a small, isolated pair (a dyad) appear as outline-only nodes on the outer ring of the map, rather than being forced into a larger community. ## Sharing a report You can share a read-only view of a Pairing Insights report with anyone, including people outside your team, by generating a share link: 1. Open Pairing Insights and pick the period you want to share. 2. Click the **Share** button next to the period toggle. 3. Copy the generated link and send it to whomever you want to view the report. Share links are valid for 90 days. You can revoke a share link at any time from the same share button; a revoked link stops working immediately. Each share link is tied to a specific team and period, so changing the period after sharing creates a new link. ## Permissions Pairing Insights is visible to Team Owners and Team Managers. Regular team members don't see the page or the sidebar link. Anyone with an active share link can view the shared report, even if they aren't signed in. # Reactivating a subscription Source: https://docs.tuple.app/team-management/reactivating-a-subscription If you are the **Owner** of a team with an active subscription, you can initiate a reactivation for your account by signing in to [the Tuple web app](https://production.tuple.app). When you sign in, you'll see a message that looks like this: Account banner prompting you to reactivate your subscription Click "Reactivate your subscription", and you'll be good to go! Once your account is reactivated, you can head to your [Billing Portal](/team-management/managing-team-billing) to review and modify your account details. # Removing users Source: https://docs.tuple.app/team-management/removing-users As a team owner or manager, you have the capability to remove users from your team at any time. Head to your [team directory](https://production.tuple.app/team_management/members), then click the three-dot menu next to any user you'd like to remove and click "Remove": Team member menu with the Remove action visible **Note:** if your Tuple team is configured to use SSO and SCIM, you will not see the option to "Remove" as Tuple will delegate deprovisioning to the identity provider. # Requesting a Team Invite Link Source: https://docs.tuple.app/team-management/requesting-a-team-invite-link If you're trying to join your team on Tuple, you'll need someone on your team to send you a **team invite link**. Anyone on your team can do this - they don't have to be a team owner. Here's how one of your teammates can get this link for you: Have someone on your team open the Tuple popover in the menu bar: Tuple popover opened from the menu bar Navigate to the **Invite** tab: Invite tab selected in the Tuple popover Be sure the "team invite link" option is selected: Invite screen with the team invite link option selected Have them copy the link, and send it to you via chat / text / email: Invite screen with the Copy link button for the team invite link Clicking on that link should allow you to join your team when you sign up for Tuple. If you've already created an account but haven't joined a team yet, you **won't** have to make a new one; clicking on the link allows you to join your team with the account you've already created. # Microsoft Entra ID Source: https://docs.tuple.app/team-management/scim-entra-id How to set up SCIM provisioning in Tuple with Microsoft Entra ID This guide walks through configuring SCIM provisioning with Microsoft Entra ID (formerly Azure AD) to automatically manage Tuple user accounts. SCIM provisioning requires an existing [Microsoft Entra ID SAML integration](/team-management/sso-azure). Complete the SSO setup first. ## Prerequisites You need the SCIM credentials from your [Team Management page](https://production.tuple.app/team_management/invitations) in Tuple. If you don't see them, contact [support@tuple.app](mailto:support@tuple.app) to enable SCIM for your team. ## Setup In the [Microsoft Entra admin center](https://entra.microsoft.com), navigate to your Tuple enterprise application. Select **Provisioning** in the left sidebar. Provisioning in the left sidebar Click **Get started**, then set **Provisioning Mode** to **Automatic**. Under **Admin Credentials**, fill in the following fields: **Tenant URL** ``` https://production.tuple.app/scim/v2 ``` **Secret Token** -- your SCIM username and SCIM password from the Tuple Team Management page, joined with a colon: ``` : ``` For example, if your SCIM username is `acme` and your SCIM password is `abc123`, enter `acme:abc123`. The password alone is not a valid token. Click **Test Connection** to verify Entra ID can reach Tuple's SCIM endpoint. A success message confirms the credentials are valid. Expand **Mappings** and select **Provision Microsoft Entra ID Users**. Ensure the following attributes are mapped: | Microsoft Entra ID attribute | SCIM attribute | | ---------------------------- | ------------------------------ | | `mail` | `emails[type eq "work"].value` | | `givenName` | `name.givenName` | | `surname` | `name.familyName` | | `mail` | `userName` | Tuple matches users by `userName` and stores it as the account's email address. Map `mail` to `userName`, not `userPrincipalName`: a UPN that differs from the user's email address creates duplicate accounts and breaks SAML sign-in, which [identifies users by email](/team-management/sso-azure). Click **Save**. Return to the **Provisioning** overview. Set **Provisioning Status** to **On** and click **Save**. Entra ID runs an initial sync cycle, then syncs changes approximately every 40 minutes. # Okta Source: https://docs.tuple.app/team-management/scim-okta How to set up SCIM provisioning in Tuple with Okta This guide walks through configuring SCIM provisioning with Okta to automatically manage Tuple user accounts. SCIM provisioning requires an existing [Okta SAML integration](/team-management/sso-okta). Complete the SSO setup first. ## Prerequisites Your Okta app must be [configured at creation time](https://support.okta.com/help/s/article/SCIM-Provisioning-Enabled-in-Custom-App?language=en_US) to use SCIM. If you have an existing app that was not configured for SCIM, contact [Okta Support](https://support.okta.com/help/s/opencase). ## Setup Send an email to [support@tuple.app](mailto:support@tuple.app) to enable SCIM for your team. Once approved, your SCIM credentials appear on the [Team Management page](https://production.tuple.app/team_management/invitations) in Tuple: SCIM credentials on the Team Management page In your Tuple application in Okta, enable SCIM provisioning: Enable SCIM provisioning In **Provisioning**, configure with the following values: Provisioning settings **SCIM connector base URL** ``` https://production.tuple.app/scim/v2 ``` **Unique identifier field for users** ``` email ``` Enable the following features: * Import new users and Profile Updates * Push New Users * Push Profile Updates Use the credentials from your Team Management page. Once the integration step is successful, enable features **To App**: Enable SCIM features # OneLogin Source: https://docs.tuple.app/team-management/scim-onelogin How to set up SCIM provisioning in Tuple with OneLogin This guide walks through configuring SCIM provisioning with OneLogin to automatically manage Tuple user accounts. SCIM provisioning requires an existing [OneLogin SAML integration](/team-management/sso-onelogin). Complete the SSO setup first. ## Prerequisites You need the SCIM credentials from your [Team Management page](https://production.tuple.app/team_management/invitations) in Tuple. If you don't see them, contact [support@tuple.app](mailto:support@tuple.app) to enable SCIM for your team. ## Setup In OneLogin, navigate to **Apps > Add Apps**. Search for **SCIM Provisioner with SAML (Core Schema)** and select it. Add SCIM Provisioner app Enter "Tuple" as the **Display Name** and click **Save**. Go to the **Configuration** tab. Fill in the SAML fields from your existing [OneLogin SAML integration](/team-management/sso-onelogin), then fill in the SCIM fields: SCIM configuration fields **SCIM Base URL** ``` https://production.tuple.app/scim/v2 ``` **SCIM Bearer Token** -- your SCIM username and SCIM password from the Tuple Team Management page, joined with a colon: ``` : ``` For example, if your SCIM username is `acme` and your SCIM password is `abc123`, enter `acme:abc123`. The password alone is not a valid token. Leave **SCIM JSON Template** and **Custom Headers** empty. Click **Enable** on the Configuration tab. OneLogin sends a test request to verify the SCIM endpoint. A successful connection shows **API Status** as **Enabled**. SCIM connection enabled Go to the **Provisioning** tab and check **Enable provisioning**. Select the actions you want OneLogin to handle: * Create user * Delete user * Update user Click **Save**. # SCIM provisioning Source: https://docs.tuple.app/team-management/scim-provisioning Automatically provision and deprovision Tuple user accounts from your identity provider SCIM (System for Cross-domain Identity Management) automatically provisions and deprovisions user accounts in Tuple when you update them in your identity provider. This eliminates manual user management and ensures access stays in sync. ## Getting credentials SCIM credentials are issued per-team. To enable SCIM: 1. Send an email to [support@tuple.app](mailto:support@tuple.app) requesting SCIM provisioning. 2. Once approved, your credentials appear on the [Team Management page](https://production.tuple.app/team_management/invitations). SCIM credentials on the Team Management page Your credentials are a SCIM username and a SCIM password. Providers that authenticate with basic auth, like Okta, use them directly. Providers that ask for a single bearer token or secret token, like Microsoft Entra ID and OneLogin, use both values joined with a colon: `:`. ## Provider-specific guides Okta SCIM connector Formerly Azure AD SCIM Provisioner with SAML ## How SCIM works with Tuple | Action in your identity provider | Result in Tuple | | -------------------------------- | ----------------------------------------- | | Assign user to Tuple app | Account created, seat billing begins | | Remove user from Tuple app | Account deprovisioned, seat billing stops | | Update user profile | Name and email updated in Tuple | ## Tuple's SCIM endpoints | Field | Value | | ----------------------- | ---------------------------------------------------- | | SCIM connector base URL | `https://production.tuple.app/scim/v2` | | Unique identifier field | `userName`, stored as the user's Tuple email address | Map `userName` to the same email address your SAML configuration sends. When the two disagree -- for example, SCIM sending a UPN while SAML sends `mail` -- each flow matches a different account and users end up duplicated. ## Questions? [Email us](mailto:support@tuple.app) if you need help setting up SCIM provisioning. # Sign In With Google Source: https://docs.tuple.app/team-management/sign-in-with-google Tuple gives users the ability to sign up and sign in with Google. Team owners have the ability to select Google, Microsoft, or SAML SSO as a required authentication provider; if none is selected, users can choose how they want to authenticate. The setting can be changed in the [team settings tab](https://production.tuple.app/teams/14#settings): Team settings showing the required authentication provider options ## FAQs **What happens if I attempt to transfer a user into a team where they don't meet the auth requirements (i.e. if a team requires Google auth, but the user in question hasn't used it)?** The transfer request will be blocked. **How can I completely remove my Google connection?** You can visit Google's interface for managing third-party connections [here](https://myaccount.google.com/connections). **If a team has auto-join functionality enabled, what will happen if a user signs up but doesn't meet the auth requirements for the team?** The user will be prevented from joining the team. **When a team makes Google auth required, what happens to users who had previously been using email and password?** Existing users are shown an error message when attempting to log in which prompts them to sign in with Google. Users are ***not*** logged out from existing sessions. **When a team switches from Google auth to a different required auth provider, what happens to users who had previously been using Google auth?** Existing users are shown an error message which prompts them to sign in with an email / password. If they don’t have a password set, they’ll need to go through the reset password flow. Again - users are ***not*** logged out from existing sessions. **Can users use a Google account to sign up or sign in if the email address is unverified?** No - users must have a verified email on Google in order to use it. **If a user changes the email address associated with a Tuple account, will that automatically update or invalidate the linkage with the associated Google account?** No. Users who change their email address in Tuple do not automatically have their link to their Google account broken. For example, someone could sign up via Google using the email address `first@tuple.app`, then change the address associated with their Tuple account to `second@tuple.app`. In this case, they would still be able to sign in via the Google account `first@tuple.app`. If a different user actually had the Google account `second@tuple.app`, they wouldn't be able to use Google auth until the first user switched their email address back to `first@tuple.app` (or to something else entirely). # Sign in With Microsoft Source: https://docs.tuple.app/team-management/sign-in-with-microsoft Tuple gives users the ability to sign up and sign in with Microsoft. Team owners have the ability to select Google, Microsoft, or SAML SSO as a required authentication provider; if none is selected, users can choose how they want to authenticate. The setting can be changed in the [team settings tab](https://production.tuple.app/teams/14#settings): Team settings showing the required authentication provider options ## FAQs **What happens if I attempt to transfer a user into a team where they don't meet the auth requirements (i.e. if a team requires Microsoft auth, but the user in question hasn't used it)?** The transfer request will be blocked. **If a team has auto-join functionality enabled, what will happen if a user signs up but doesn't meet the auth requirements for the team?** The user will be prevented from joining the team. **When a team makes Microsoft auth required, what happens to users who had previously been using email and password?** Existing users are shown an error message when attempting to log in which prompts them to sign in with Microsoft. Users are ***not*** logged out from existing sessions. **When a team switches from Microsoft auth to a different required auth provider, what happens to users who had previously been using Microsoft auth?** Existing users are shown an error message which prompts them to sign in with an email / password. If they don’t have a password set, they’ll need to go through the reset password flow. Again - users are ***not*** logged out from existing sessions. **If a user changes the email address associated with a Tuple account, will that automatically update or invalidate the linkage with the associated Microsoft account?** No. Users who change their email address in Tuple do not automatically have their link to their Microsoft account broken. For example, someone could sign up via Microsoft using the email address `first@tuple.app`, then change the address associated with their Tuple account to `second@tuple.app`. In this case, they would still be able to sign in via the Microsoft account `first@tuple.app`. If a different user actually had the Microsoft account `second@tuple.app`, they wouldn't be able to use Microsoft auth until the first user switched their email address back to `first@tuple.app` (or to something else entirely). # Microsoft Entra ID Source: https://docs.tuple.app/team-management/sso-azure How to configure SAML SSO with Microsoft Entra ID (Azure AD) for your Tuple team This guide walks through configuring SAML SSO with Microsoft Entra ID (formerly Azure AD) as your identity provider. Email addresses in and Tuple must match exactly. For example, `dev+tuple@company.com` does not match `dev@company.com`. Verify your team's email addresses before enabling SSO. Tuple falls back to the SAML NameID when no email attribute is present in the response, so if your provider's NameID defaults to something other than email (such as a username or UPN), set it to the user's email address as well. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com) as at least a Cloud Application Administrator. Navigate to **Identity > Applications > Enterprise applications** and click **New application**. Then click **Create your own application**. Enter "Tuple" as the application name, select **Integrate any other application you don't find in the gallery (Non-gallery)**, and click **Create**. Before configuring SSO, assign the users who need access to Tuple. In your new Tuple application, go to **Users and groups** and add the users or groups that should have SSO access. In your Tuple application, navigate to **Single sign-on** in the left sidebar and select **SAML** as the sign-on method. SAML-based Sign-on configuration page in Microsoft Entra ID Click **Edit** on the **Basic SAML Configuration** card and fill in the following fields: **Identifier (Entity ID)** ``` https://production.tuple.app/users/saml/metadata ``` **Reply URL (Assertion Consumer Service URL)** ``` https://production.tuple.app/users/saml/auth ``` Click **Save**. Click **Edit** on the **Attributes & Claims** card. Add two custom claims so Tuple receives the user's first and last name. Click **Add new claim** and create each of the following: | Name | Source attribute | | ------------ | ---------------- | | `first_name` | `user.givenname` | | `last_name` | `user.surname` | Leave the **Namespace** field empty for both claims. Tuple identifies the signing-in user by the email address in the SAML response, so two more settings matter: * The default email claim (`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`) must keep `user.mail` as its source attribute. * The **Unique User Identifier (Name ID)** claim should also be sourced from `user.mail`. Entra ID defaults it to `user.userprincipalname`, and a UPN like `u0123456@company.com` often differs from the user's email address. If the email claim is missing from the response, Tuple falls back to the Name ID value. When that value isn't the user's email address, sign-in either fails with "Not a member of the team associated with the Identity Provider" or creates a duplicate account under the wrong address. Users whose `mail` attribute is empty in Entra ID hit this even when the claims are configured correctly, so make sure every assigned user has `mail` populated. If you also use [SCIM provisioning](/team-management/scim-entra-id), map `mail` to `userName` there so SAML and SCIM identify users by the same address. On the **SAML Certificates** card, download the **Certificate (Base64)** file. Then, in the **Set up Tuple** card, copy the following values: * **Login URL** -- this is your IdP authentication URL * **Microsoft Entra Identifier** -- this is your IdP entity ID Navigate to the **Settings** tab of the [team management dashboard](https://production.tuple.app/team_management/settings). Only [team owners](/team-management/team-owner-and-managers) can enable SAML. To find out who your team owner is, check [your profile](https://production.tuple.app/profile#team). Under **Sign-in methods**, set **Required Authentication Provider** to **SAML SSO**. The **Update SAML Configuration** form appears: SAML configuration form in Tuple Fill in the values with your metadata: Select the **Email Domain** that SAML should apply to. Only domains with confirmed team members are available. Click **Save as draft**. Your draft is saved as a **Pending Update** alongside your current sign-in method, so no one on your team is affected yet. Pending SAML update showing Test and Publish actions Click **Test** to verify the configuration end-to-end. Tuple signs you in through so you can confirm that authentication succeeds before the change affects anyone else on your team. Once the test succeeds, click **Publish** to make the configuration live. Active Tuple sessions persist, but new sign-ins are routed through . Use **Edit** to tweak the draft, or **Discard** to throw it away without publishing. # Google Workspace Source: https://docs.tuple.app/team-management/sso-google How to configure SAML SSO with Google Workspace for your Tuple team This guide walks through configuring SAML SSO with Google Workspace as your identity provider. Email addresses in and Tuple must match exactly. For example, `dev+tuple@company.com` does not match `dev@company.com`. Verify your team's email addresses before enabling SSO. Tuple falls back to the SAML NameID when no email attribute is present in the response, so if your provider's NameID defaults to something other than email (such as a username or UPN), set it to the user's email address as well. Sign in to your Google Workspace Admin console. Navigate to **Apps > Web and mobile apps > Add app > Add custom SAML app**. Navigate to add a custom SAML app Name the app "Tuple" and optionally upload an icon, which you can [download here](https://s3.wasabisys.com/tuple/images/tuple-sso.png). App details You are shown the Google Identity Provider details. Copy the values from this screen: Google Identity Provider details Navigate to the **Settings** tab of the [team management dashboard](https://production.tuple.app/team_management/settings). Only [team owners](/team-management/team-owner-and-managers) can enable SAML. To find out who your team owner is, check [your profile](https://production.tuple.app/profile#team). Under **Sign-in methods**, set **Required Authentication Provider** to **SAML SSO**. The **Update SAML Configuration** form appears: SAML configuration form in Tuple Fill in the values with your metadata: Select the **Email Domain** that SAML should apply to. Only domains with confirmed team members are available. Click **Save as draft**. Your draft is saved as a **Pending Update** alongside your current sign-in method, so no one on your team is affected yet. Pending SAML update showing Test and Publish actions Click **Test** to verify the configuration end-to-end. Tuple signs you in through so you can confirm that authentication succeeds before the change affects anyone else on your team. Once the test succeeds, click **Publish** to make the configuration live. Active Tuple sessions persist, but new sign-ins are routed through . Use **Edit** to tweak the draft, or **Discard** to throw it away without publishing. Return to the Google Workspace Admin console and fill in the following fields: Service provider details **ACS URL** ``` https://production.tuple.app/users/saml/auth ``` **Entity ID** ``` https://production.tuple.app/users/saml/metadata ``` Tuple requires two additional attributes: `first_name` and `last_name`. Attribute mapping After finishing the install wizard, click **Test SAML Login** to verify the configuration. Test SAML Login # Okta Source: https://docs.tuple.app/team-management/sso-okta How to configure SAML SSO with Okta for your Tuple team This guide walks through configuring SAML SSO with Okta as your identity provider. Email addresses in and Tuple must match exactly. For example, `dev+tuple@company.com` does not match `dev@company.com`. Verify your team's email addresses before enabling SSO. Tuple falls back to the SAML NameID when no email attribute is present in the response, so if your provider's NameID defaults to something other than email (such as a username or UPN), set it to the user's email address as well. After signing in to your Okta account, click **Applications** in the navigation bar and then click **Create App Integration**. Create App Integration Select **SAML 2.0** as the sign-in method. Select SAML 2.0 sign-in method Name the app "Tuple" and upload an icon, which you can [download here](https://s3.wasabisys.com/tuple/images/tuple-sso.png). General Settings Fill in the following fields: Configure SAML **Single sign on URL** ``` https://production.tuple.app/users/saml/auth ``` **Audience URI (SP Entity ID)** ``` https://production.tuple.app/users/saml/metadata ``` There are three additional attributes that Tuple requires: `email`, `first_name`, and `last_name`. After finishing the install wizard, click **View SAML Setup Instructions** on the Sign On tab. View Setup Instructions This provides the metadata needed to configure SAML in Tuple: * Identity Provider Single Sign-On URL * Identity Provider Issuer URL * Downloaded certificate file View certificate Navigate to the **Settings** tab of the [team management dashboard](https://production.tuple.app/team_management/settings). Only [team owners](/team-management/team-owner-and-managers) can enable SAML. To find out who your team owner is, check [your profile](https://production.tuple.app/profile#team). Under **Sign-in methods**, set **Required Authentication Provider** to **SAML SSO**. The **Update SAML Configuration** form appears: SAML configuration form in Tuple Fill in the values with your metadata: Select the **Email Domain** that SAML should apply to. Only domains with confirmed team members are available. Click **Save as draft**. Your draft is saved as a **Pending Update** alongside your current sign-in method, so no one on your team is affected yet. Pending SAML update showing Test and Publish actions Click **Test** to verify the configuration end-to-end. Tuple signs you in through so you can confirm that authentication succeeds before the change affects anyone else on your team. Once the test succeeds, click **Publish** to make the configuration live. Active Tuple sessions persist, but new sign-ins are routed through . Use **Edit** to tweak the draft, or **Discard** to throw it away without publishing. ## SCIM provisioning Okta supports automated user provisioning via SCIM. See [SCIM provisioning with Okta](/team-management/scim-okta) for setup instructions. # OneLogin Source: https://docs.tuple.app/team-management/sso-onelogin How to configure SAML SSO with OneLogin for your Tuple team This guide walks through configuring SAML SSO with OneLogin as your identity provider. Email addresses in and Tuple must match exactly. For example, `dev+tuple@company.com` does not match `dev@company.com`. Verify your team's email addresses before enabling SSO. Tuple falls back to the SAML NameID when no email attribute is present in the response, so if your provider's NameID defaults to something other than email (such as a username or UPN), set it to the user's email address as well. After signing in to your OneLogin account, click **Applications > Applications** in the top navigation bar. Add SSO SAML App In the search field, enter `SAML Test` and select **SAML Test Connector (Advanced)** from the results. Locate App From Search Fill in any required metadata, upload company logos, and save the new application. SAML Metadata After saving, click **Configuration** in the left-hand sidebar. Tuple Metadata Fill in the following fields: **Audience (EntityID)** ``` https://production.tuple.app/users/saml/metadata ``` **Recipient** ``` https://production.tuple.app/users/saml/auth ``` **ACS (Consumer) URL Validator** ``` https:\/\/production.tuple.app\/users\/saml\/auth ``` **ACS (Consumer) URL** ``` https://production.tuple.app/users/saml/auth ``` **Login URL** ``` https://production.tuple.app ``` Navigate to the **Parameters** section in the sidebar and click the plus button to add new fields. Tuple requires three fields in the SSO response: `email`, `first_name`, and `last_name`. Add User Parameters When adding each field, check the **Include in SAML Assertion** checkbox. Check assertion Repeat for `first_name` and `last_name`. Adding First Name Once all required parameters are added, the screen looks like this: All Required Tuple Params Download your X.509 certificate. Click **SSO** in the sidebar and find the link to **View Details**: View certificate Click **Download** to save the certificate file. Download certificate Return to the **SSO** screen and locate the **Issuer URL** and **SAML 2.0 Endpoint (HTTP)**. Entity ID and auth URL Navigate to the **Settings** tab of the [team management dashboard](https://production.tuple.app/team_management/settings). Only [team owners](/team-management/team-owner-and-managers) can enable SAML. To find out who your team owner is, check [your profile](https://production.tuple.app/profile#team). Under **Sign-in methods**, set **Required Authentication Provider** to **SAML SSO**. The **Update SAML Configuration** form appears: SAML configuration form in Tuple Fill in the values with your metadata: Select the **Email Domain** that SAML should apply to. Only domains with confirmed team members are available. Click **Save as draft**. Your draft is saved as a **Pending Update** alongside your current sign-in method, so no one on your team is affected yet. Pending SAML update showing Test and Publish actions Click **Test** to verify the configuration end-to-end. Tuple signs you in through so you can confirm that authentication succeeds before the change affects anyone else on your team. Once the test succeeds, click **Publish** to make the configuration live. Active Tuple sessions persist, but new sign-ins are routed through . Use **Edit** to tweak the draft, or **Discard** to throw it away without publishing. # Subscribing Annually Source: https://docs.tuple.app/team-management/subscribing-annually You can subscribe to Tuple on an annual basis for a discount of two months' time! If you are the team owner, you can opt to upgrade your team's subscription here: [production.tuple.app/annual](https://production.tuple.app/annual) Need help or have further questions? Reach out to us at [support@tuple.app](mailto:support@tuple.app). # Team Owner and Managers Source: https://docs.tuple.app/team-management/team-owner-and-managers Tuple features two team management roles, Team Owners, and Team Managers. Their respective abilities include the following: | Permission | Team Owner | Team Manager | | ------------------------------------------------------------------------- | ---------- | ------------ | | Add/remove users from team | ✔️ | ✔️ | | Manage team settings | ✔️ | ✔️ | | Promote other users to Team Manager | ✔️ | ✔️ | | [Manage API tokens](/team-management/usage-api-alpha#managing-api-tokens) | ✔️ | ✔️ | | Manage billing portal/information | ✔️ | ✖️ | | Transfer team ownership | ✔️ | ✖️ | **Note:** Tuple currently only supports one single Team Owner, but you can add unlimited Team Managers. ### Team Owners Team Owners can transfer ownership to another user, and/or promote/remove Team Managers from the [Team Management page](https://production.tuple.app/team_management/members). Team management page showing ownership transfer and manager actions # Usage API (alpha) Source: https://docs.tuple.app/team-management/usage-api-alpha Access Tuple call data programmatically and manage API tokens from the dashboard. Some teams want to integrate Tuple call data with tools like [DX](https://getdx.com/) and Jellyfish. To that end, we offer a usage API. To get access, [reach out to support](mailto:support@tuple.app). ## Endpoint ``` GET /api/v2/call_usage ``` Returns completed calls for the authenticated team, sorted by `ended_at` ascending. ## Authentication Requests must include a Bearer token in the `Authorization` header: ``` Authorization: Bearer sk-tuple-... ``` Team owners and managers can create and manage API tokens directly from the dashboard. See [Managing API tokens](#managing-api-tokens) below. ## Query parameters | **Parameter** | **Required** | **Description** | | ------------- | ------------ | ----------------------------------------------------------------------------------- | | `since` | Yes | ISO 8601 datetime. Only returns calls that ended at or after this time. | | `until` | No | ISO 8601 datetime. Only returns calls that ended before this time. Defaults to now. | | `page` | No | Page number for pagination. Defaults to 1. | | `per_page` | No | Number of calls per page. Defaults to 100, maximum 1000. | ## Response Returns a JSON array of call objects. Default 100 calls per page (max 1000). ```json theme={null} [ { "id": 12345, "started_at": "2026-03-15T14:30:00.000Z", "ended_at": "2026-03-15T15:00:00.000Z", "duration_seconds": 1800, "call_participations": [ { "id": 67890, "joined_at": "2026-03-15T14:30:00.000Z", "left_at": "2026-03-15T15:00:00.000Z", "user": { "email": "alice@example.com" } } ] } ] ``` ### Pagination headers Follows [RFC 8288 (Web Linking)](https://datatracker.ietf.org/doc/html/rfc8288). | **Header** | **Description** | | --------------- | ------------------------------------------------------------------------------- | | `X-Total-Count` | Total number of calls matching the query. | | `Link` | Standard `Link` header with `rel="next"` and `rel="prev"` URLs when applicable. | ## Rate limiting 60 requests per minute per team. Returns `429 Too Many Requests` when exceeded. ## Error responses | **Status** | **Cause** | | -------------------------- | ---------------------------------------------------------------- | | `401 Unauthorized` | Missing or invalid Bearer token. | | `422 Unprocessable Entity` | Malformed `since` or `until` parameter (must be valid ISO 8601). | | `429 Too Many Requests` | Rate limit exceeded. | ## Example ```bash theme={null} curl -H "Authorization: Bearer sk-tuple-abc123..." \ "https://production.tuple.app/api/v2/call_usage?since=2026-03-01T00:00:00Z&until=2026-03-31T00:00:00Z" ``` ## Managing API tokens Team owners and managers can create, view, and revoke API tokens from the Tuple dashboard. ### Create a token Go to your [team management page](https://production.tuple.app/team_management/api_tokens). Click **Create token** and give it a descriptive name (for example, "DX integration" or "Jellyfish sync"). Copy the token immediately after creation. The full token is only shown once and cannot be retrieved later. ### Revoke a token To revoke a token, open the menu next to it on the API tokens page and click **Revoke**. Any integration using that token will immediately lose access. ### Token security * Tokens begin with `sk-tuple-` and do not expire. * Only the last four characters of each token are visible after creation. * You can create multiple tokens to give each integration its own credential, making it easy to revoke access for a single integration without affecting others. * Treat tokens like passwords. Do not commit them to source control or share them in plain text. ## Notes * Only **completed** calls are returned (in-progress calls are excluded). * Calls are filtered by `ended_at`, not `started_at`. # API reference Source: https://docs.tuple.app/triggers/api-reference ## Universal arguments When Tuple executes your trigger, it passes in useful information via environment variables. These take the form `TUPLE_TRIGGER_[name]`. These arguments vary by trigger; however, there are two universal arguments that are available to every trigger that gets executed: | Argument | Details | | -------------------------------------- | -------------- | | `TUPLE_TRIGGER_CURRENT_USER_EMAIL` | Your email | | `TUPLE_TRIGGER_CURRENT_USER_FULL_NAME` | Your full name | ## Events The complete list of lifecycle events that you can respond to with triggers. ### Call initiated `call-initiated` Fired when you start an outgoing call to another person via the popover interface. Calling someone via the popover UI | Argument | Details | | -------------------------------- | ------------------------------------------- | | `TUPLE_TRIGGER_CALLEE_EMAIL` | The email of the person you're calling. | | `TUPLE_TRIGGER_CALLEE_FULL_NAME` | The full name of the person you're calling. | ### Call incoming `call-incoming` Fired when another participant calls your machine. Receiving an incoming call | Argument | Details | | -------------------------------- | ---------------------------------------- | | `TUPLE_TRIGGER_CALLER_EMAIL` | The email of the person calling you. | | `TUPLE_TRIGGER_CALLER_FULL_NAME` | The full name of the person calling you. | ### Call rejected `call-rejected` Fired when the person you're calling rejects your call. | Argument | Details | | -------------------------------- | ------------------------------------------- | | `TUPLE_TRIGGER_CALLEE_EMAIL` | The email of the person you're calling. | | `TUPLE_TRIGGER_CALLEE_FULL_NAME` | The full name of the person you're calling. | ### Call timed out `call-timed-out` Fired when your outgoing call times out. | Argument | Details | | -------------------------------- | ------------------------------------------- | | `TUPLE_TRIGGER_CALLEE_EMAIL` | The email of the person you're calling. | | `TUPLE_TRIGGER_CALLEE_FULL_NAME` | The full name of the person you're calling. | ### Call connected `call-connected` Fired when your call is fully connected. (Receives no additional arguments) ### Call ended `call-ended` Fired when your call ends. | Argument | Details | | --------------------------- | -------------------------------------------------- | | `TUPLE_TRIGGER_CALL_LENGTH` | The duration that you were on the call in seconds. | ### Call transcription started `call-transcription-started` Fired when a call transcription starts. Transcription can be stopped and restarted within a single call, and each recording session fires its own started/complete pair — use `TUPLE_TRIGGER_RECORDING_ID` to address the exact session that just started rather than guessing from "the most recent call." | Argument | Details | | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TUPLE_TRIGGER_CALL_ID` | The ID of the call this transcription belongs to. | | `TUPLE_TRIGGER_RECORDING_ID` | A UUID identifying the specific recording session that just started. Pass to `tuple transcription show --recording ` to read exactly this session. | | `TUPLE_TRIGGER_CALL_ARTIFACTS_DIRECTORY` | The filesystem path to the directory containing the call transcription artifacts. | ### Call transcription complete `call-transcription-complete` Fired when a call transcription finishes, either because the call ended or transcription was disabled. Each started/complete pair is scoped to one recording session; use `TUPLE_TRIGGER_RECORDING_ID` so a summarizer trigger operates on that session only and doesn't double-summarize a call whose transcription was restarted. | Argument | Details | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TUPLE_TRIGGER_CALL_ID` | The ID of the call this transcription belongs to. | | `TUPLE_TRIGGER_RECORDING_ID` | A UUID identifying the specific recording session that just finished. Pass to `tuple transcription show --recording ` to read exactly this session's transcript. | | `TUPLE_TRIGGER_CALL_ARTIFACTS_DIRECTORY` | The filesystem path to the directory containing the call transcription artifacts. | A minimal `call-transcription-complete` handler that exports just the session that finished: ```sh theme={null} #!/bin/sh tuple transcription export ~/Documents/tuple-transcripts \ --recording "$TUPLE_TRIGGER_RECORDING_ID" ``` ### Room joined `room-joined` Fired when you or someone else joins one of your team's rooms. | Argument | Details | | ------------------------- | -------------------------------------------------------------------------------------------- | | `TUPLE_TRIGGER_IS_SELF` | "true" when this is being fired because you joined a room. "false" when it was someone else. | | `TUPLE_TRIGGER_ROOM_NAME` | The name of the room being joined. | | `TUPLE_TRIGGER_EMAIL` | The email of the person joining the room. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person joining the room. | ### Room left `room-left` Fired when you or someone else leaves one of your team's rooms. | Argument | Details | | ------------------------- | ---------------------------------------------------------------------------------------- | | `TUPLE_TRIGGER_IS_SELF` | "true" when this event was fired because you left a room. "false" when someone else did. | | `TUPLE_TRIGGER_ROOM_NAME` | The name of the room being left. | | `TUPLE_TRIGGER_EMAIL` | The email of the person leaving the room. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person leaving the room. | ### Screen share started `screen-share-started` Fired when you or someone else starts sharing their screen on a call. | Argument | Details | | ------------------------- | ------------------------------------------------------------------------------------------------------- | | `TUPLE_TRIGGER_IS_SELF` | "true" when the event was fired because you started sharing your screen. "false" when someone else did. | | `TUPLE_TRIGGER_EMAIL` | The email of the person sharing their screen. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person sharing their screen. | ### Screen share ended `screen-share-ended` Fired when you or someone else stops sharing their screen on a call. | Argument | Details | | ------------------------- | --------------------------------------------------------------------------- | | `TUPLE_TRIGGER_IS_SELF` | "true" when you stopped sharing your screen. "false" when someone else did. | | `TUPLE_TRIGGER_EMAIL` | The email of the person stopping their screen share. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person stopping their screen share. | ### Webcam share started `webcam-share-started` Fired when you or someone else starts sharing their webcam on a call. | Argument | Details | | ------------------------- | --------------------------------------------------------------------------- | | `TUPLE_TRIGGER_IS_SELF` | "true" when you started sharing your webcam. "false" when someone else did. | | `TUPLE_TRIGGER_EMAIL` | The email of the person sharing their webcam. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person sharing their webcam. | ### Webcam share ended `webcam-share-ended` Fired when you or someone else stops sharing their webcam on a call. | Argument | Details | | ------------------------- | --------------------------------------------------------------------------- | | `TUPLE_TRIGGER_IS_SELF` | "true" when you stopped sharing your webcam. "false" when someone else did. | | `TUPLE_TRIGGER_EMAIL` | The email of the person no longer sharing their webcam. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person no longer sharing their webcam. | ### Participant joined `participant-joined` Fired whenever someone joins your call, whether it's a call starting, someone being added, or someone joining the room you're in. | Argument | Details | | ------------------------- | -------------------------------------------------- | | `TUPLE_TRIGGER_EMAIL` | The email of the person that joined your call. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person that joined your call. | ### Participant left `participant-left` Fired whenever someone leaves your call. | Argument | Details | | ------------------------- | ------------------------------------------------ | | `TUPLE_TRIGGER_EMAIL` | The email of the person that left your call. | | `TUPLE_TRIGGER_FULL_NAME` | The full name of the person that left your call. | # Best practices Source: https://docs.tuple.app/triggers/best-practices Below are a collection of best practices when developing your own triggers. ## Code organization **Organize your code by use case.** Group scripts for a specific use case into a descriptive folder, so that all behavior for that use case is in one place. For example: ``` ~/.tuple/triggers ├── README.md ├── do-not-disturb │ ├── call-connected │ └── call-ended ├── clean-desktop │ ├── screen-share-started │ └── screen-share-stop ├── hide-messages │ ├── screen-share-started │ └── screen-share-stop ├── pause-spotify │ ├── call-connected ``` As two of these triggers respond to the same two events, they could technically be in one script and live in the root directory. However, we don't recommend that approach. Keeping triggers in separate subdirectories simplifies each file, and makes it easier to remove a set of behaviors when you don't want it anymore. ## Logging **Log generously.** The last thing you want is to notice that one of your scripts is broken, and realize that you have no logs to go look through. All stdout and stderr from your scripts are collected in `triggers.log`, so print anything and everything that you think might be helpful when debugging a future problem. ## State persistence **Persist state sparingly.** If you do need to persist state between triggers, we recommend using a local file in an easily parsable format like JSON. If you've broken your scripts up by use case, then you can keep this state file in the same directory as the script without worrying about overwriting it in another script. For example: ```ruby theme={null} #!/usr/bin/env ruby require "json" # Load previous state state = File.exists?("state.json") ? JSON.parse(File.read("state.json")) : {} # your script logic here # Write out current state for next run File.open("state.json", "w") { |f| f.write(state.to_json) } ``` # Building a trigger Source: https://docs.tuple.app/triggers/building-a-trigger Triggers live in the `~/.tuple/triggers` directory. We recommend placing each trigger you create in its own subdirectory in that location. Let's create a new trigger that announces out loud the name of the person who is calling you: ```sh theme={null} mkdir ~/.tuple/triggers/call-announcer touch ~/.tuple/triggers/call-announcer/call-incoming chmod +x ~/.tuple/triggers/call-announcer/call-incoming ``` Notice that we: 1. Created a new subdirectory for the trigger. 2. Created a file with the name of the event we want to trigger on—in this case, the `call-incoming` event, which fires whenever you receive a call. 3. Made that file executable. This is important: Tuple will only run scripts you've marked as executable. You can author triggers in whatever programming language you're most comfortable with. We'll use bash for this example since our trigger logic is quite simple: ```sh theme={null} #!/bin/sh PERSON_CALLING=$TUPLE_TRIGGER_CALLER_FULL_NAME say "${PERSON_CALLING} would like to pair with you" ``` Notice that we reference a `TUPLE_TRIGGER_CALLER_FULL_NAME` variable in this script. When Tuple calls your trigger, it will sometimes pass in useful information in the form of environment variables. You can see which variables are passed in (and their definitions) for every event in the [API reference](/triggers/api-reference). **That's it!** Tuple will now *trigger* this code at a key lifecycle *event*. Whenever you receive an incoming call, your computer will kindly let you know who is giving you a ring. Well, almost. For triggers to work, they need to be enabled. You can enable triggers by going to Tuple Preferences ⌘ , → **Triggers** and clicking on the **Enable** button. Or, if you're on macOS, simply [click here](tuple://enable-triggers). ## Testing the trigger Asking your coworker to call you over and over while you get your trigger logic right would be a drag. Instead, let's use Tuple's built-in tooling to ensure our trigger functions as expected. Open Tuple Preferences → **Triggers** and open the Simulator by clicking on **Test Triggers**. Triggers settings Find the event we're trying to trigger (`call-incoming`), select it, and click **Simulate**. Trigger simulator You should now be hearing your friendly host announce the fake caller. **Running into issues? Feel free to reach out to [support@tuple.app](mailto:support@tuple.app).** ## Key takeaways ### 1. Location matters All of your trigger code needs to live in `~/.tuple/triggers`. We recommend you nest your code in subdirectories in order to keep scripts logically organized. That might look like this: ``` ~/.tuple/triggers ├── hide-messages │ ├── screen-share-started │ └── screen-share-stop ├── pause-spotify │ ├── call-connected └── triggers.log ``` Or, if you prefer, you can simply place all of your triggers in the root: ``` ~/.tuple/triggers ├── screen-share-started ├── screen-share-stop ├── call-connected └── triggers.log ``` ### 2. Triggers must be named correctly Every trigger filename you want executed needs to be prefixed with one of the available [lifecycle event names](/triggers/api-reference). Here is a non-exhaustive list of some of the most common triggers: * `call-connected` — triggered when a call connects * `room-joined` — triggered when a participant joins a room * `screen-share-started` — triggered when any participant shares their screen As long as your filename starts with the trigger name, you can add whatever text you want after it: **Acceptable** * `call-connected` — bare name of the trigger * `room-joined.notify-slack` — everything after `room-joined` is ignored * `room-joined.pause-spotify` — everything after `room-joined` is ignored Triggers with incorrect filenames will be ignored. For example: **Not correct, will be ignored** * `spotify.call-connected` — trigger name being used as a postfix and **not** a prefix * `callconnected` — incorrect trigger name (should be `call-connected`) ### 3. Files must be executable Trigger files need to be executable and **not** publicly writable. We recommend using `chmod 0755` or `chmod +x` to accomplish this. ## Next steps Next up: useful tips for [testing and debugging](/triggers/testing-and-debugging) your triggers while you develop them. # Security Source: https://docs.tuple.app/triggers/security ## File permissions Tuple will only execute triggers which are owned by the user who is running Tuple, marked as executable, and *not* publicly writable. This is done to ensure that Tuple doesn't execute a script that has been modified by an unprivileged application or piece of code on your machine. ## OS permissions ### macOS On macOS, Tuple executes triggers from a separate XPC service so that it can maintain a separate set of OS permissions from the main Tuple application. This means that by default, triggers have no special OS permissions unless you grant them. Triggers OS permissions prompt Note that granting an OS permission for one trigger will grant it for all triggers. ### Installing from the web directory Installing a trigger from the [web directory](https://tuple.app/triggers/directory?utm_source=docs) downloads it to `~/.tuple/triggers` and reveals its source in Finder so you can review it. If triggers are off, Tuple asks what to do after installation: * Click **Enable Triggers** to turn them on immediately. * Click **Manage in Settings…** to open the **Triggers** preference pane without enabling them. * Click **Not Now** to leave the trigger installed but inactive. ## Community triggers The [directory](https://tuple.app/triggers/directory) contains triggers that have been written by other users of Tuple, and made available for convenient installation. The Tuple team reviews each submission to the directory, and makes a best effort to ensure that they are safe and correct. To see the source code for any of the triggers listed in the directory, head over to [tupleapp/community-triggers](https://github.com/tupleapp/community-triggers) and check out the `triggers/` subdirectory. # Submitting a trigger Source: https://docs.tuple.app/triggers/submitting-a-trigger If you'd like to share your trigger with other Tuple users, you can submit it to [the official directory](https://tuple.app/triggers/directory). Community triggers that are shown in the directory live in a GitHub repository, [tupleapp/community-triggers](https://github.com/tupleapp/community-triggers). Each trigger is reviewed by someone at Tuple and given our blessing. ## Language requirements For your trigger to be included in the directory, it must be written in either Bash, Ruby, Python, JavaScript, or AppleScript. Other languages aren't supported at this time, but we'll gladly take suggestions—drop us an email at [support@tuple.app](mailto:support@tuple.app). ## Adding metadata As well as your trigger's code, we need three extra things to publish it in the directory: an **icon**, a **README**, and a **`config.json`** configuration file. ### Icon This must: * Be in PNG format * Exist at `assets/icon.png` * Be exactly 512×512 pixels in size (DALL·E is pretty good at generating these.) ### README This must: * Be in Markdown * Exist at `README.md` * Contain a description of what your trigger does, and how to use it ### `config.json` This must be a JSON file located at `config.json` containing metadata related to your trigger that we'll show in the directory. It's a JSON file in the following format: ```json theme={null} { "name": "Trigger Name", "description": "Trigger Description", "platforms": ["macos", "windows"], "language": "bash" } ``` It must have the following content: | Key | Type | Description | | ------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `name` | `string` | A snappy, concise name for your trigger, e.g. "Drop tables" | | `description` | `string` | A short description of what your trigger does, e.g. "Drops all your database tables in production" | | `platforms` | array of `macos`, `linux`, `windows` | The platforms your trigger will run on | | `language` | one of `bash`, `python`, `nodejs`, `ruby`, `applescript` | The scripting language your trigger is written in | ## Submitting your trigger Once you've prepared your metadata, you're ready to submit your trigger to us for review. To do that: * Fork the [tupleapp/community-triggers](https://github.com/tupleapp/community-triggers) repository into your own GitHub account * Add your trigger, along with its metadata, to a new folder in the `triggers` directory * Open a pull request to merge your fork into the main repository From there, we'll run some automated tests to make sure your trigger is valid, and then we'll review it. If everything looks good, we'll merge your pull request and your trigger will be published in the directory. # Testing and debugging Source: https://docs.tuple.app/triggers/testing-and-debugging ## Simulating events during development While developing a trigger, it's likely you'll want to invoke it many times. To make this easier, we've given you the ability to simulate each of Tuple's lifecycle events populated with dummy data: Trigger simulator This will invoke the lifecycle event you chose and open a Terminal window displaying all relevant output. ## Debugging live triggers Once your triggers run on actual Tuple calls, you might notice they're not behaving exactly as you'd like. To diagnose the issue, take a look at `~/.tuple/triggers/triggers.log`. This file contains all of the output from the trigger runner, as well as any stdout/stderr from your scripts. Here's some sample output. In the example below, we see two scripts running. The first works correctly and exits with status code 0. The second has a typo and reports the issue via `STDERR`. ``` # ~/.tuple/triggers/triggers.log [2023-10-25T12:19:11.447] Got trigger 'screen-share-started', searching for matching user scripts [2023-10-25T12:19:11.448] Found 2 matching scripts [2023-10-25T12:19:11.448] Running 'screen-share-started' [2023-10-25T12:19:11.453] Finished 'screen-share-started' with exit code: 0 [2023-10-25T12:19:11.453] Running 'clean-desktop/screen-share-started' ~/.tuple/triggers/clean-desktop/screen-share-started: line 3: defaulst: command not found [2023-10-25T12:19:11.458] Finished 'clean-desktop/screen-share-started' with exit code: 127 etc... ``` ## Triggers are not firing on macOS Tuple runs triggers in a background helper. If macOS disables **TupleTriggers** in **System Settings → General → Login Items & Extensions**, Tuple turns the **Triggers** preference off and shows an alert. Click **Open Login Items**, enable **TupleTriggers**, then return to Tuple and enable triggers again. # Audio device swapping Source: https://docs.tuple.app/troubleshooting/audio-device-swapping **tl;dr: For everyday audio device swapping, you probably want to use the macOS controls (Settings > Sound) rather than the dropdowns in Tuple's preferences. (You can do this quickly by Option-clicking the speaker icon in your menu bar.)** A potentially-confusing caveat applies when setting your audio device in Tuple's preferences. Imagine you have your Airpods set as your output in the macOS Sound preferences. Now, you override those settings in Tuple's preferences and choose Bose QCIIs as the output. With these preferences, your volume up, volume down, and mute keys on your keyboard will affect the Airpods, *not* the Bose QCIIs. This is because the keyboard controls always affect the System Default device (the one you've chosen in System Preferences > Sound). We provide the dropdowns in our preferences to support folks with complex audio setups, such as using tools like [Krisp](https://krisp.ai). For most users, we recommend ensuring your audio setup uses the Mac System Default preferences: macOS sound settings with the system default input and output devices selected # Audio Issues Source: https://docs.tuple.app/troubleshooting/audio-issues If your audio is not working with Tuple, please investigate the following: 1. Check for [conflicting audio settings](/pairing-with-tuple/audio-device-swapping). 2. Check for old audio drivers/applications that might be causing problems. # AWDL interference Source: https://docs.tuple.app/troubleshooting/awdl AWDL (Apple Wireless Direct Link) powers macOS features such as AirDrop, Handoff, Sidecar, AirPlay, and Continuity. It shares your Mac's Wi-Fi radio, which can add latency, jitter, or packet loss to a Tuple call. This applies only to macOS on Wi-Fi. A wired Ethernet call does not use the same radio. ## Why Tuple shows an AWDL warning Tuple includes an **AWDL** item in call-quality details when AWDL is active. Because `awdl0` is active on many Macs, Tuple shows a warning only when the call is also experiencing network trouble. ## How to reduce interference The most reliable fix is to use wired Ethernet. If you need to stay on Wi-Fi, you can temporarily disable AWDL from Terminal: ```bash theme={null} sudo ifconfig awdl0 down ``` Disabling AWDL also disables AirDrop, Handoff, Sidecar, AirPlay, and Continuity. macOS might turn AWDL back on after a restart, network change, or use of one of those features. If call quality remains degraded, see [Reducing latency](/troubleshooting/reducing-latency) and [Fixing connectivity issues](/troubleshooting/fixing-connectivity-issues). # Crashes Source: https://docs.tuple.app/troubleshooting/crashes If Tuple is crashing during usage, please try the following: 1. Confirm that you have the [latest version](/troubleshooting/upgrading) of Tuple. 2. Restart your computer. This can fix issues with a system macOS service that can get into a bad state unrelated to Tuple. 3. If a restart did not help, make a copy of `~/Library/Preferences/app.tuple.app.plist` and send that to us at [support@tuple.app](mailto:support@tuple.app) 4. Open up the Console app and click "Start Streaming", then try to reproduce the issue. Highlight all of the logs in Console and send them to us at [support@tuple.app](mailto:support@tuple.app) If Tuple will not open, please try the following: 1. Confirm that you have the [latest version](/troubleshooting/upgrading) of Tuple. 2. Restart your computer. This can fix issues with a system macOS service that can get into a bad state unrelated to Tuple. 3. If a restart did not help, make a copy of `~/Library/Preferences/app.tuple.app.plist` and send that to us at [support@tuple.app](mailto:support@tuple.app) 4. Run `defaults delete app.tuple.app` in the terminal, and then try to run Tuple again. 5. If there is still a problem, send output of Console app when you open Tuple to [support@tuple.app](mailto:support@tuple.app). If Tuple is beach balling, please try the following: 1. Confirm that you have the [latest version](/troubleshooting/upgrading) of Tuple. 2. Restart your computer. This can fix issues with a system macOS service that can get into a bad state unrelated to Tuple. 3. If restarting did not fix the issue, run a process sample of Tuple while the issue is happening by opening Activity Monitor, double clicking on Tuple, and clicking "Sample" in the bottom left and send that to us at [support@tuple.app](mailto:support@tuple.app). # Fixing Connectivity Issues Source: https://docs.tuple.app/troubleshooting/fixing-connectivity-issues Certain network connection states can lead to degraded performance. Below are some common ones, along with suggestions for mitigating problems that can arise from them. ## TCP TCP (Transmission Control Protocol) is a reliable, connection-oriented protocol that guarantees data delivery by establishing a connection, checking for errors, and retransmitting lost packets in the correct order. In contrast, UDP (User Datagram Protocol) is a faster, connectionless protocol that sends data without establishing a connection or guaranteeing delivery, making it ideal for calls, where speed matters more than perfect reliability. Tuple will utilize UDP to transmit audio, video, and screen data whenever it can. However, there are situations where UDP can't be used, so Tuple will fall back to using TCP instead. If you see that a connection is using TCP, there are a few things you can do: * **Check firewall settings:** Restrictive firewall settings can block UDP traffic. Especially if you're in a corporate environment with restrictive network settings, it's important to ensure that your firewall is correctly configured. You can learn more about configuring your firewall [here](/troubleshooting/networking-and-firewall-issues). * **Switch networks:** Certain networks (such as public hotspots, or tethering to your phone) might have restrictions around UDP traffic. If you suspect this is the case, try switching networks. * **Reduce bandwidth usage:** TCP's head-of-line blocking mechanism can create significantly degraded performance in cases where there's a lot of contention for bandwidth. To mitigate this, you can conserve bandwidth by disabling your webcam, or only sharing a portion of your screen. * **Enable UPnP on your router**: if your router supports UPnP (Universal Plug and Play), you can try to use it to automatically allow UDP traffic through. You can learn more about it [here](https://en.wikipedia.org/wiki/Universal_Plug_and_Play). ## TURN TURN (Traversal Using Relays around NAT) is a protocol that uses relay servers to forward traffic between peers when direct peer-to-peer connections fail. Instead of connecting directly to each other, both peers connect to the TURN server, which acts as an intermediary to relay all audio, video, and data between them. Tuple will attempt to use peer-to-peer connections for calls of 3 or fewer; if those fail, it will use TURN as a fallback. If you see that TURN is being used on a call, you can try the following: * **Check firewall settings:** Restrictive firewall settings can block peer connections. You can learn more about configuring your firewall [here](/troubleshooting/networking-and-firewall-issues). * **Switch networks:** Certain networks (such as public hotspots, or tethering to your phone) might also have restrictions around establishing peer connections. If you suspect this is the case, try switching networks. * **Enable UPnP on your router**: if your router supports UPnP (Universal Plug and Play), you can try to use it to automatically allow peer connections. You can learn more about it [here](https://en.wikipedia.org/wiki/Universal_Plug_and_Play). # High CPU usage Source: https://docs.tuple.app/troubleshooting/high-cpu-usage If you are consistently seeing higher CPU usage numbers please: 1. Quit and restart Tuple. 2. Restart your computer. This can fix issues with a system macOS service that can get into a bad state unrelated to Tuple. If you continue to see issues, please help us diagnose your issues by gathering the following items for us: 1. Filter Activity Monitor by "Tuple," open the **CPU** tab, and send a screenshot to [support@tuple.app](mailto:support@tuple.app). 2. Run a process sample on the "Tuple" process by double-clicking it in Activity Monitor, press the "Sample" button in the bottom left, then send that to [support@tuple.app](mailto:support@tuple.app). # Minimum Disk Space Requirements Source: https://docs.tuple.app/troubleshooting/minimum-disk-space-requirements ### TL;DR Tuple needs at least **32 GB** of free disk space in order to operate correctly. Without this much space available, screen sharing sessions may terminate unexpectedly. If you've encountered an error where screen sharing terminated due to low space, you might also have to restart your computer to get Tuple to continue to work properly. ### Why? On macOS, Tuple uses ScreenCaptureKit to capture your screen. ScreenCaptureKit is an API provided by Apple which handles screen capturing in a separate process. For this to work efficiently, it needs to be able to share memory with Tuple. Tuple does not record your screen or write any intermediate files to disk, but it appears that there are cache files used internally by macOS in order to provide Tuple with image data when we queue data for encoding on the CPU. If `replayd`, the daemon on macOS responsible for doing this, runs out of disk space when writing its cache, the process will terminate its connection to Tuple prematurely. This causes other strange side effects, too: macOS may still think screen recording is active even after Tuple is terminated (the side effects of this appear to be worse on macOS 14). If Tuple catches this error while you're sharing your screen, it will attempt to restart `replayd` - however, in the event that it's unable to do this, you'll also see an error message telling you to restart your machine. Doing so should get `replayd` back into a good state. Unfortunately, we do not have control over this behavior on macOS -- the best thing we can do is side step the issue by recommending that you have at least **32 GB** of free disk space. If you've followed our suggestions and still experience issues, please reach out to us with your computer's specs. We could use your help fine tuning our heuristics! # Networking and Firewall Issues Source: https://docs.tuple.app/troubleshooting/networking-and-firewall-issues Tuple needs to be able to access various resources. If you are using a VPN, or have a particularly strict firewall, Tuple may appear degraded. Please verify that Tuple has access to the following: ### 1. Tuple API Backend Tuple needs to be able to access our API for authentication and user list population. Please verify Tuple can access: * [production.tuple.app](http://production.tuple.app) ### 2. TURN Relay Servers When it's not possible to obtain a peer-to-peer connection through local or public networks, Tuple uses Cloudflare to route media data through TURN servers. Please verify Tuple can access Cloudflare's [current IP addresses](https://developers.cloudflare.com/realtime/turn/faq/#i-need-to-allowlist-whitelist-cloudflare-realtime-turn-ip-addresses-which-ip-addresses-should-i-use). ### 3. Signaling servers Tuple uses signaling to facilitate the initiation of calls between peers and to provide availability (active/inactive/busy) status updates to clients. Please verify Tuple can access: * tuple-production.firebaseio.com * production.stream.tuple.app * securetoken.googleapis.com * [www.googleapis.com](http://www.googleapis.com) ### 4. STUN servers Tuple uses STUN to obtain a peer-to-peer connection through local or public networks. Please verify Tuple can access: * stun.l.google.com * stun1.l.google.com * [stun2.l.google.com](http://stun2.l.google.com) ### 5. Background updates, Logs, Analytics, and Crash reporting When it can, Tuple tries to do background app updates. Tuple also monitors certain events and analytics data in order to constantly improve performance. Please verify Tuple can access: * [d32ifkf9k9ezcg.cloudfront.net](http://d32ifkf9k9ezcg.cloudfront.net) * [ingest.sentry.io](http://ingest.sentry.io/) * [api.mixpanel.com](http://api.mixpanel.com) * [graphite-prod-10-prod-us-central-0.grafana.net/](http://graphite-prod-10-prod-us-central-0.grafana.net/) * [logs.us-east-1.amazonaws.com](http://logs.us-east-1.amazonaws.com) ### 6. Ports for peer-to-peer Traffic If allowed, Tuple (using WebRTC) will create peer-to-peer connections. The connections will be UDP or TCP traffic, but the ports used are dynamic. Here are some firewall rules that are recommended for using WebRTC service: *Minimum Requirement:* TCP ports 80 and 443 are open. Some firewall rules only allow for TCP traffic over port 443, make sure that all traffic can pass over this port. *Best Experience:* In addition to the minimum requirements being met, we recommend that UDP ports 1025 - 65535 be open. *Explicit Firewall rules for your IT department:* 443/tcp ALLOW Anywhere 80/tcp ALLOW Anywhere 53/udp ALLOW Anywhere 443/tcp (v6) ALLOW Anywhere (v6) 80/tcp (v6) ALLOW Anywhere (v6) 53/udp (v6) ALLOW Anywhere (v6) 53/udp ALLOW OUT Anywhere 80/tcp ALLOW OUT Anywhere 443/tcp ALLOW OUT Anywhere 443/udp ALLOW OUT Anywhere 53/udp (v6) ALLOW OUT Anywhere (v6) 80/tcp (v6) ALLOW OUT Anywhere (v6) 443/tcp (v6) ALLOW OUT Anywhere (v6) 443/udp (v6) ALLOW OUT Anywhere (v6) ## VPN and Firewall Related Lag Tuple calls are peer-to-peer connections, and some VPNs can cause network traffic to be routed through the internet very inefficiently. Please ensure your VPN settings do not cause this: * Pick a VPN endpoint that is in a more local geographic location to the participants on the call. * Ensure that the VPN is allowing UDP traffic. If possible, set a VPN configuration which would avoid sending this traffic through the VPN. * Updating your firewall to bypass SSL decryption will prevent unnecessary added lag. * Allow a reasonable MTU (Maximum Transmission Unit) for streaming video. * Review any intelligent filtering software in use to ensure it's not affecting packet transmission. ### If Tuple is stuck "Connecting..." Tuple can get stuck connecting when you're using a VPN. If Tuple was previously working, please verify that your VPN settings have not changed. If you have ruled out a VPN settings change as the root cause, please: 1. Confirm that you have the [latest version](/troubleshooting/upgrading) of Tuple 2. Confirm that Tuple has access to the Tuple API Backend (listed above). 3. Restart your computer. This can fix issues with a system macOS service that can get into a bad state unrelated to Tuple 4. If restarting did not fix the issue, run a process sample of Tuple while the issue is happening by opening Activity Monitor, double clicking on Tuple, and clicking "Sample" in the bottom left and send that to us at [support@tuple.app](mailto:support@tuple.app). # Not receiving notifications Source: https://docs.tuple.app/troubleshooting/not-receiving-notifications Having trouble getting Tuple notifications to show up on macOS? Navigate to **System Settings > Notifications > Tuple**. Your settings should look something like this: macOS notification settings for Tuple Then you should see notifications in the center, such as one like this if you miss a Tuple call: Missed Tuple call notification in Notification Center If you are still not seeing notifications, check that the following setting is enabled at the bottom of the profile in **System Settings > Notifications > The app you want to receive notifications for:** Notification settings showing Allow notifications when mirroring enabled # Preventing Launch at Login Source: https://docs.tuple.app/troubleshooting/preventing-launch-at-login Turning off "Launch at login" in your [General preferences](/application-preferences/macos-preferences-general) should prevent Tuple from launching on log in. If you find that Tuple is continuing to launch, there may be something else on your machine launching the application. #### On macOS: Make sure that under *System Preferences -> Users & Groups -> Login Items*, Tuple doesn't exist in that list. Make sure that's true for all other users on that computer too (though only root users should make a difference, let's make very sure Tuple isn't getting started some place we don't expect!). Check to see if Tuple is in the launchctl items. In terminal run: ``` launchctl list | grep -i tuple ``` You can then remove the Tuple launch item by running: ``` launchctl remove app.tuple.app-LaunchAtLoginHelper ``` # Quiet microphone on Windows Source: https://docs.tuple.app/troubleshooting/quiet-microphone-on-windows Troubleshoot low microphone volume when using Tuple on Windows. If others on your call can barely hear you, the most likely cause is a low input level on your microphone. Tuple on Windows relies on the system-level mic gain and cannot boost it directly. Work through these steps to diagnose and fix the issue. ## Check your Windows input volume 1. Open **Settings > System > Sound**. 2. Under **Input**, select the microphone you are using. 3. Drag the **Volume** slider to an appropriate level (80-100% is a good starting point). ## Confirm the correct input device is selected Make sure both Windows and Tuple agree on which microphone to use. 1. In Tuple, open **Settings** (`ctrl+,`) and select the **Audio** tab. 2. Verify the **Input Device** is set to **System Default**, or explicitly set it to the microphone you want to use. 3. In Windows **Settings > System > Sound**, confirm the same device is set as the default input. If Tuple is set to a specific device that differs from the Windows default, system volume controls and sliders will affect the default device rather than the one Tuple is using. See [Audio preferences on Windows](/application-preferences/windows-preferences-audio) for details. ## Verify microphone privacy permissions Windows can block apps from accessing the microphone entirely. 1. Open **Settings > Privacy & security > Microphone**. 2. Make sure **Microphone access** is turned on. 3. Confirm that **Let desktop apps access your microphone** is enabled. ## Test your microphone before joining a call Before joining a call, verify your mic is working at the system level. Open **Settings > System > Sound** and speak into your microphone. The input level indicator next to your device should respond clearly. If it barely moves, your input volume is still too low. ## Still too quiet? If you have followed every step above and your audio is still quiet: * Try a different microphone to rule out a hardware issue. * Check whether your microphone manufacturer provides a companion app with its own gain control. * Contact [support@tuple.app](mailto:support@tuple.app) with your Tuple version and microphone model so we can investigate further. # Recording on Tuple Source: https://docs.tuple.app/troubleshooting/recording-on-tuple For screen recording or transcription tools, see the options below. Remember to get consent from others in your pairing session before recording them! ## Transcribe your session audio ### Superwhisper If you're a Superwhisper user, you can use a dedicated Tuple mode with a specialized prompt for the agent of your choice to [summarize your pairing session](https://superwhisper.com/documentation/meeting-notes). It includes nice details like speaker detection, but less capabilities directly in the tool for things like searching your notes or asking questions. ### Granola If you're a [Granola](https://www.granola.ai/) user, you can capture the audio output and input from your system and summarize what you and your pair were discussing. You can ask questions about the call, share with your team, etc. ### Notion AI If you're a Notion user, you can activate [their AI Meeting Notes feature](https://www.notion.com/help/ai-meeting-notes), which will capture the audio output and input from your system and summarize what you and your pair were discussing. You can ask questions of the transcript and store it alongside your other meeting notes. ## Manually record a pairing session ### QuickTime If you want the simplest screen recording solution, check out [QuickTime](https://support.apple.com/guide/quicktime-player/welcome/mac). Macs ship with this program pre-installed, so you can easily create a screen recording without installing separate software. For a recording with audio, make sure to select your microphone input before you hit record on a new recording, or you will end up without any audio at all :) As a heads up, though Tuple tries very hard not to turn your computer into a fan while you’re on a call, we have had reports that QuickTime can cause lift-off after a while during longer recordings. ### ScreenFlow If you’d like to use a more advanced screen recording software, and especially if you’d like to have multiple audio inputs in your screen recording, check out [ScreenFlow](https://www.telestream.net/screenflow/overview.htm). You can get a free trial which allows you to create unlimited recordings without a license, but the recordings will be watermarked. ## Highlight a feature or bug for our support team ### QuickTime You can also create a screen recording using [QuickTime](https://support.apple.com/guide/quicktime-player/welcome/mac) in order to email it to our [support](mailto:support@tuple.app). If you have issues with email attachment size limitation, check out other solutions below. ### Loom You can use [Loom](https://www.loom.com/)’s free tier to easily record and send our [support](mailto:support@tuple.app) a link to a video without having to worry about email upload size limitations. ### CleanShot X If you already have a [CleanShot](https://cleanshot.com/) license, you can make use of their excellent recording functionality. Unfortunately, they do not have a free tier at the moment. # Reducing latency Source: https://docs.tuple.app/troubleshooting/reducing-latency The internet as a whole is struggling with the increased demand for video streaming; lag and latency on Tuple calls is most often caused by strained internet connections. Most degraded internet related issues can be fixed by taking the following steps: 1. Set a lower [stream resolution](/pairing-with-tuple/interacting-with-a-shared-screen#stream-resolution) and or [webcam resolution](/application-preferences/macos-preferences-webcam) to reduce the number of bits being sent and hopefully reduce local congestion. 2. Use a wired ethernet connection. This will reduce packet loss significantly which can really improve Tuple's performance. WiFi setups can be quite noisy and introduce a lot of local packet loss. If you can't use an ethernet connection, make sure you are as close as possible to the WiFi access point. If you're nearby other WiFi networks, you might be competing on the same channel which can lead to dropped packets. Depending on your router software, you may be able to manually change to a different channel which might not be as crowded. 3. Ensure all computers are plugged into a power supply. MacBooks sometimes throttle CPU usage when running on battery power. We're judicious about our CPU utilization, but doing a call while on battery can affect your latency. 4. Pick either screen sharing or webcam sharing instead of both at the same time to reduce bits being sent and avoid congestion. 5. Drop your system resolution before your call. This tip is particularly salient if your guest has a much smaller screen than you. If you're on a 5K iMac and they're on a 12" Macbook, not only will sending that many pixels cause higher latency, but the text on your guest's side will be near-unreadable. **Eliminate competing bandwidth consumers** Bandwidth drops can cause latency, and are usually caused by other programs or other people using the same network. For instance, an automated network backup that always causes available bandwidth to drop dramatically. You can get an idea of what's using bandwidth on your computer with the Network tab of the built-in Activity Monitor app, and you should be able to get an idea of what's using your entire network bandwidth (if you have other people or devices on your network) by visiting your router's settings page. **Using Tuple with a VPN** Latency can also occur when you are pairing over a VPN, or through a particularly strict firewall. VPNs can introduce latency various ways, especially through how they route the calls. Read more about troubleshooting your VPN setup [here](/troubleshooting/networking-and-firewall-issues). **But my internet speed test says my connection is ok?** Many internet speed tests only test upload and download speed serially, instead of in parallel. While you are on a Tuple call, you are both uploading and downloading simultaneously. Some internet connections can be deeply affected by doing both at once. Please make sure to try out a test that simulates uploading and downloading in parallel for a more accurate result. If you're on macOS Monterey or above, for example, you can check out the networkQuality command line tool: Terminal output from the macOS networkQuality command **But it works on Zoom?** Tuple uses peer-to-peer connections, instead of going through an intermediate server, so call quality can be affected by how the call is routed which is outside of Tuple's control. It's very likely the connection is going over a completely different network than Zoom. # Supported platforms Source: https://docs.tuple.app/troubleshooting/supported-platforms ### Currently supported Tuple fully supports the two most recent versions of **macOS**. The third most recent version is considered "deprecated", which means that we do not fix bugs exclusively associated with this version. Any older versions are considered "unsupported", which means that they will not receive automatic updates, may break without notice, and may not be able to run Tuple at all. Currently, our support for macOS is as follows: * macOS 26 (Tahoe): **Supported** * macOS 15 (Sequoia): **Supported** * macOS 14 (Sonoma): **Deprecated** * macOS 13 and lower: **Unsupported** Apple Silicon (MX chips) are fully supported. Details on getting started with macOS can be found [here](/getting-started). ### In beta **Tuple for Windows** is currently in public beta. We support Windows 10 and 11 on `x86_64`. **Note:** Windows builds on `Arm64` aren't officially supported, but our provided builds *might* work. Use at your own risk - certain things might not work as expected. ### In alpha **Tuple for Linux** is currently in alpha and under active development. The Linux client has limited functionality today and is primarily command-line driven. See [the Linux page](https://tuple.app/linux?utm_source=docs) for the latest install command and architecture details. # Upgrading Source: https://docs.tuple.app/troubleshooting/upgrading If you start a call on an outdated version, you will be prompted to upgrade, which is a simple one-click process. When upgrading, you have the option to upgrade automatically in the future, which will happen silently when updates are shipped. You can trigger a manual upgrade by clicking on your avatar to bring up the menu and selecting "Check for updates": Tuple avatar menu with Check for updates highlighted The current version of your client will be displayed to the right of "Check for updates". You can also view the current version for your Tuple client by selecting "About Tuple" from this menu. If Tuple will not open and you need to check your installed version, you can right click on the Application icon in a finder window, select Get Info, and then look for the Version number.