# Tape Scripting

URL: https://tuios.dev/docs/tape-scripting

> Script TUIOS with .tape files, and build project layouts with .tuios.tape.

A tape is a plain text file of TUIOS commands, one per line: open a window, type into it, switch workspace, wait for output. You can write tapes by hand or [record them](https://tuios.dev/docs/tape-recording).

**Try it live:** the [Automation track](https://tuios.dev/learn?t=automation) plays a tape in your browser.

```bash
# demo.tape
EnableTiling
NewWindow
Type "htop"
Enter
SmartSplit
Type "git log --oneline"
Enter
```

## Running a tape

| Command                      | What it does                                                     |
| ---------------------------- | ---------------------------------------------------------------- |
| `tuios tape play <file>`     | Starts a standalone TUIOS and plays the tape in it               |
| `tuios tape validate <file>` | Parses the tape and lists its commands, without running anything |
| `tuios tape exec <file>`     | Plays the tape in a running session that has a client attached   |

`tuios tape list`, `show`, `delete` and `dir` manage recorded tapes. See [Tape Recording](https://tuios.dev/docs/tape-recording#managing-recordings).

### tape play

```bash
tuios tape play demo.tape
```

`play` starts its own TUIOS, not attached to a daemon session, and runs the tape in it. It reads your config file, and `--theme` and `--show-keys` work as they do for `tuios`.

- Animations start off, so playback is the same on every machine. A tape can turn them back on with `EnableAnimations`.
- Playback runs one command at a time and waits for running animations to finish before the next one.
- Ctrl+P pauses and resumes playback.
- A progress indicator shows the current command and the total.
- A command that fails shows an error notification, and playback continues with the next line.
- TUIOS stays open when the tape ends. Quit it as usual, with Ctrl+B q.

### tape validate

```bash
tuios tape validate demo.tape
```

Prints each parsed command, or the parse errors with line numbers. It exits non-zero if the tape does not parse. A tape that validates can still fail at run time, for example `LoadLayout` with a name that does not exist.

### tape exec

```bash
tuios tape exec demo.tape             # the most recently active session
tuios tape exec -s work demo.tape     # a named session
```

`exec` sends the tape to the daemon, which hands it to the TUI client attached to that session. The client runs it and shows its progress on screen. The command returns when the tape has finished.

> **exec needs an attached client**
>
> A detached session has no renderer to run a tape. `tuios tape exec` against one fails with `tape scripts need an attached client`. For a detached session, use the control commands directly. See [Scripting a detached session](https://tuios.dev/docs/tape-scripting#scripting-a-detached-session).

`exec` differs from `play` in three ways:

- **`Wait` and `WaitUntilRegex` are skipped** without a warning. Only `Sleep` pauses an `exec` run.
- **It does not wait for a new pane.** In a daemon session, `NewWindow` and `SmartSplit` ask the daemon for a pane, and the pane arrives a moment later. Put a `Sleep` after them, or the next `Type` goes to the old pane.
- **Commands run 50 ms apart**, with no wait for animations.

|                                    | `tape play`            | `tape exec`                 |
| ---------------------------------- | ---------------------- | --------------------------- |
| Needs a running session            | No                     | Yes, with a client attached |
| Runs in                            | A new standalone TUIOS | The attached client         |
| `Sleep`                            | Yes                    | Yes                         |
| `Wait`, `WaitUntilRegex`           | Yes                    | Skipped                     |
| Waits for a new pane before typing | Yes                    | No                          |

### Scripting a detached session

For CI or a script that drives a headless session, skip tapes and use the control commands. They work with no client attached, and `wait-for` blocks on real output instead of a fixed sleep:

```bash
#!/bin/sh
set -eu

tuios new ci --detach
tuios new-window -s ci tests
tuios send-text -s ci -w tests 'npm test
'
tuios wait-for window-output -s ci -w tests --pattern 'Tests passed' --timeout 120000
tuios kill-session ci
```

`wait-for` exits non-zero on timeout, so the script fails if the tests never print the marker. See [Sessions](https://tuios.dev/docs/sessions) and the [Control Protocol](https://tuios.dev/docs/control-protocol) for the full set of commands.

A single tape command can also be sent with `tuios run-command`. It takes a few commands a tape cannot, such as `Split horizontal`, `SetTheme` and `ShowNotification`. Run `tuios run-command --list` to see them.

## Syntax

### Lines and comments

One command per line. `#` starts a comment, on its own line or after a command:

```bash
# Build the editor pane
NewWindow  # comments can follow a command
```

Keywords are not case-sensitive: `NewWindow`, `newwindow` and `NEWWINDOW` are the same command. Arguments keep their case.

### Strings

Double quotes, single quotes and backticks all work:

```bash
Type "hello world"
Type 'hello world'
Type `hello world`
```

### Durations

A number followed by a unit, as Go writes durations: `ms`, `s`, `m`, `h`. Decimals are allowed.

```bash
Sleep 500ms
Sleep 2s
Sleep 1.5s
```

### Repeat counts

A key command takes an optional count and sends the key that many times:

```bash
Down 5
Backspace 10
Enter 2
```

Counts work on `Enter`, `Space`, `Tab`, `Backspace`, `Delete`, `Escape`, `Up`, `Down`, `Left`, `Right`, `Home` and `End`. Other commands parse a trailing number but do not repeat. `ToggleTiling 3` toggles once, and `NewWindow 3` opens one window named `3`.

### The @ delay

The parser accepts a delay between a command and its count, as in `Down@100ms 3` or `Type@50ms "text"`. The delay is stored and never used: `Down@100ms 3` sends three Down keys at once. Use `Sleep` lines for pacing. `Sleep@200ms` is a parse error.

## Commands

### Modes

| Command                | Effect                           |
| ---------------------- | -------------------------------- |
| `WindowManagementMode` | Switch to window management mode |
| `TerminalMode`         | Switch to terminal mode          |

Key commands such as `Type` and `Enter` write to the focused pane in either mode. A mode command changes what TUIOS shows and how it treats the keys you press. Recorded tapes include them so the replay ends in the same state.

### Windows

| Command                    | Effect                                                                                   |
| -------------------------- | ---------------------------------------------------------------------------------------- |
| `NewWindow`                | Open a window                                                                            |
| `CloseWindow`              | Close the focused window                                                                 |
| `NextWindow`, `PrevWindow` | Move focus                                                                               |
| `FocusWindow <id>`         | Focus a window by name or ID. The argument is a bare word or number, not a quoted string |
| `RenameWindow "name"`      | Name the focused window                                                                  |
| `MinimizeWindow`           | Minimize the focused window                                                              |
| `RestoreWindow`            | Restore the focused window, if it is minimized                                           |

```bash
NewWindow
RenameWindow "server"
Type "npm run dev"
Enter
```

`NewWindow "name"`, `CloseWindow "name"`, `MinimizeWindow "name"` and `RestoreWindow "name"` validate, but the tape parser drops the string. The command acts on the focused window, and `NewWindow "name"` opens an unnamed window. Use `RenameWindow` after `NewWindow`, or `tuios run-command NewWindow "name"`.

Minimizing moves focus to another window, so a bare `RestoreWindow` right after `MinimizeWindow` does not bring the minimized one back.

### Tiling and layout

| Command                                         | Effect                                                                                              |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `EnableTiling`, `DisableTiling`, `ToggleTiling` | Turn tiling on or off                                                                               |
| `SmartSplit`                                    | Split the focused pane and open a new pane in the space. TUIOS picks the direction. Needs tiling on |
| `RotateSplit`                                   | Rotate the direction of the focused split. Needs tiling on                                          |
| `EqualizeSplits`                                | Reset every split to an even ratio. Needs tiling on                                                 |
| `ToggleZoom`                                    | Zoom the focused pane, or unzoom it                                                                 |
| `SnapLeft`, `SnapRight`                         | Floating: snap the focused window to half the screen. Tiled: focus the neighbour on that side       |
| `SnapFullscreen`                                | Floating only. Currently snaps to the top-left quarter, not the full screen                         |
| `SaveLayout "name"`                             | Save the current layout under a name                                                                |
| `LoadLayout "name"`                             | Apply a saved layout                                                                                |

See [BSP Tiling](https://tuios.dev/docs/bsp-tiling) for how splits work, and `tuios layout list` for saved layouts.

> **Split does not work in a tape**
>
> `Split horizontal` validates, but the parser drops the direction, so it always fails at run time with `Split needs horizontal or vertical`. Use `SmartSplit` in a tape, or `tuios run-command Split horizontal` from the shell.

### Workspaces

| Command                      | Effect                                     |
| ---------------------------- | ------------------------------------------ |
| `SwitchWorkspace <n>`        | Show workspace `n`                         |
| `MoveToWorkspace <n>`        | Move the focused window to workspace `n`   |
| `MoveAndFollowWorkspace <n>` | Move the focused window and switch with it |

`n` runs from 1 to the number of workspaces, 9 by default.

### Keys and text

| Command                                                                   | Sends                 |
| ------------------------------------------------------------------------- | --------------------- |
| `Type "text"`                                                             | The text, all at once |
| `Enter`, `Space`, `Tab`, `Backspace`, `Delete`, `Escape`                  | That key              |
| `Up`, `Down`, `Left`, `Right`, `Home`, `End`                              | That key              |
| `Ctrl+c`, `Alt+x`, `Ctrl+Alt+t`, `Shift+Left`, `Ctrl+Enter`, `Ctrl+Space` | A key with modifiers  |

These all write bytes to the focused pane. They do not go through TUIOS keybindings: `Ctrl+b` sends byte `0x02` to the pane and does not start the TUIOS prefix. The one exception is `Alt+1` to `Alt+9`, which switch workspace.

After a modifier, the parser accepts a letter or word, a number, an arrow key, `Home`, `End`, `Enter` or `Space`. `Tab`, `Escape`, `Backspace` and `Delete` are not accepted, so `Shift+Tab` and `Ctrl+Backspace` are parse errors (`expected key after modifier, got Tab`). To send one of those, use `tuios send-keys "shift+tab"` while the pane is in terminal mode.

### Timing

| Command                         | Effect                                                               |
| ------------------------------- | -------------------------------------------------------------------- |
| `Sleep <duration>`              | Pause                                                                |
| `Wait <duration>`               | Same as `Sleep`. A duration is required                              |
| `WaitUntilRegex "pattern" [ms]` | Pause until the focused pane's screen matches the regular expression |

```bash
Type "make build"
Enter
WaitUntilRegex "BUILD (OK|FAILED)" 60000
```

`WaitUntilRegex` checks the visible screen of the focused pane. The timeout is in milliseconds and defaults to 5000. On timeout it shows a warning and playback continues, so it does not stop a failing tape. Both `Wait` and `WaitUntilRegex` are skipped by `tuios tape exec`.

### Animations and other commands

| Command                                                     | Effect                                                     |
| ----------------------------------------------------------- | ---------------------------------------------------------- |
| `EnableAnimations`, `DisableAnimations`, `ToggleAnimations` | Turn animations on or off                                  |
| `CommandPalette`                                            | Open the command palette                                   |
| `Screenshot`                                                | Save the focused pane as an image, like `tuios screenshot` |

### Parsed but ignored

These keywords parse and validate, and then do nothing:

| Command             | Note                                                   |
| ------------------- | ------------------------------------------------------ |
| `Set <key> <value>` | No effect. To change a setting, use `tuios set-config` |
| `Output <file>`     | No effect                                              |
| `Source <file>`     | Does not include another tape                          |
| `Focus <id>`        | No effect. Use `FocusWindow`                           |

## Examples

### Three panes

```bash
DisableAnimations
EnableTiling

NewWindow
RenameWindow "editor"
Type "nvim ."
Enter

SmartSplit
RenameWindow "server"
Type "npm run dev"
Enter
WaitUntilRegex "ready" 30000

SmartSplit
RenameWindow "tests"
Type "npm test -- --watch"
Enter

FocusWindow editor
```

### Two workspaces

```bash
NewWindow
Type "cd ~/project"
Enter

SwitchWorkspace 2
NewWindow
Type "htop"
Enter

SwitchWorkspace 1
```

## Project tapes

A project tape is a file named `.tuios.tape` in a project directory. When your shell inside TUIOS enters that directory, TUIOS offers to build a session for the project from it, much like `direnv` offers to load an `.envrc`.

A tape can type any command into a shell, so an untrusted project tape never runs on its own. TUIOS reads it once, hashes it and shows it to you. It runs only after you review it and choose to run it.

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/docs/tape-scripting)*

### Reviewing and trusting

1. `cd` into a directory with a `.tuios.tape`. A notification appears and the dock shows a `tape ?` badge. Nothing has run.
2. Press Ctrl+B T t, or choose **Tape: review the project tape** in the command palette. The dialog shows the path, the trust status, what the tape builds and its full content.
3. Choose an action:

| Key | Action                                     |
| --- | ------------------------------------------ |
| r   | Run once, without remembering the decision |
| t   | Trust this exact file and run it           |
| n   | Never ask about this path again            |
| Esc | Not now. The badge stays                   |

Trust is stored per path and SHA-256 content hash in `tuios/tape-trust.toml` under your data directory. Any edit to the file makes it untrusted again, so a `git pull` that changes the tape brings the review back. TUIOS runs the same bytes it showed you and does not read the file again.

A tape is ineligible, and can only be dismissed, if it is not a regular file you own, is group- or world-writable, or is larger than 64 KiB. A pane whose shell is connected to another machine over SSH is ignored, because TUIOS cannot read or verify the remote file.

### Writing a project tape

```bash
# .tuios.tape
Session "myproject"
Require "pnpm"

RenameWindow "edit"
Type "nvim ." Enter

Split vertical
RenameWindow "serve"
Run "pnpm dev"

Split horizontal
RenameWindow "sh"

Focus "edit"
```

The optional header comes first:

| Directive                          | Meaning                                                           | Default            |
| ---------------------------------- | ----------------------------------------------------------------- | ------------------ |
| `Session "name"`                   | Name of the session to build                                      | The directory name |
| `Scope session` or `Scope current` | Build a new session, or apply the tape to the current one         | `session`          |
| `Workspace <n>`                    | Workspace to build in                                             | None               |
| `Require "cmd"`                    | Skip the tape with a notice if `cmd` is not on `PATH`. Can repeat | None               |

The body uses a smaller language than a normal tape, made for building layouts:

| Command                                | Effect                                               |
| -------------------------------------- | ---------------------------------------------------- |
| `Type "text" [Enter]`                  | Type into the focused pane, and press Enter if given |
| `Run "cmd"`                            | Same as `Type "cmd" Enter`                           |
| `Enter`                                | Press Enter                                          |
| `Split vertical` or `Split horizontal` | Split the focused pane (`v` and `h` also work)       |
| `NewWindow ["name"]`                   | Open a new pane                                      |
| `RenameWindow "name"`                  | Name the focused pane. `Rename` also works           |
| `Focus "name"`                         | Focus a pane by name                                 |
| `Sleep <duration>`                     | Pause                                                |
| `EnableTiling`, `DisableTiling`        | Turn tiling on or off                                |

Unknown lines are skipped. TUIOS waits after each `Split` and `NewWindow` so the new pane is ready before the next line types into it.

With `Scope session`, running the tape creates the session, opens a pane at the project root, builds the layout and switches you to it. If a session with that name already exists, TUIOS switches to it and does not build it again. Session scope needs a daemon-backed TUIOS; without one, the tape runs in the current session.

### Autorun

The `[tape]` table in the config controls detection. See [the tape table](https://tuios.dev/docs/configuration#the-tape-table).

```toml
[tape]
autorun = "ask"       # off, ask or auto
auto_review = false   # open the review dialog on detection
```

- `off`: no detection at all.
- `ask` (the default): show the notification and badge, and run nothing until you choose.
- `auto`: a trusted, unchanged tape runs when you enter the directory. An untrusted or edited tape behaves as in `ask`.

`TUIOS_TAPE_AUTORUN=off` overrides the setting for one run. A path you marked Never stays silent in every mode.

## Related

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/docs/tape-scripting)*
