# Scripting

Saltbush Terminal runs Python and Lua scripts. A script is attached to the tab it starts from and drives that session: it sends text, waits for prompts, reads the screen, changes settings, opens tabs, transfers files and shows dialogs.

- **Script → Run…** chooses a `.py` or `.lua` file and optional arguments.
- **Script → Cancel** stops the current tab's script and leaves the connection open.
- **Script → Start/Stop Recording** records what you type and saves it as a Lua or Python script.
- **Script > Run** (F12) remembers the last script, its arguments and its folder, also after a restart; **Script > Run Last Script Again** (Ctrl+F12) runs it on the active tab, and Shift+F12 cancels a running script.
- **Script → Activity** shows script output and errors. Errors open it automatically. Select text with the mouse and press Ctrl+C to copy it (also while a script is writing), or right-click for **Copy** and **Copy All**; the **Copy All** button copies everything. Click the terminal again to type there.
- **Session Options → Logon → Logon script** runs a script whenever the session connects. A logon script sees everything received since the connection started; a script started with Script → Run starts with what arrives after it starts.

Python scripts run with the system `python3`; the `saltbush` module uses only the standard library and is supplied automatically. Lua 5.4 is built in, so no Lua installation is needed.

The interface is the same as Lantern Terminal's. Scripts written for Lantern (`from lantern import app`, `require("lantern")`) and scripts written for the SyncTERM-Lua fork run unchanged.

## Python

```python
from saltbush import app

tab = app.GetScriptTab()
tab.Screen.Send("printf '%s%s\\n' 'terminal-' 'ready'\r")
if not tab.Screen.WaitForString("terminal-ready", 5):
    raise TimeoutError("Endpoint did not respond")
print(tab.Caption, tab.Screen.Rows, tab.Screen.Columns)
```

## Lua

```lua
local app = require("saltbush").app
local tab = app:GetScriptTab()
tab.Screen:Send("printf '%s%s\\n' 'terminal-' 'ready'\r")
assert(tab.Screen:WaitForString("terminal-ready", 5), "Endpoint did not respond")
print(tab.Caption, tab.Screen.Rows, tab.Screen.Columns)
```

Lua uses colon syntax for methods and dot syntax for properties. `arg[0]` is the script file and `arg[1]` the first argument; `...` holds the arguments too. Lua's standard libraries are available, and the script's folder is on `package.path`.

Build test prompts from pieces, as above, so a wait does not match its own echoed command line.

## Interface

### Application
- `app.Version`, `app.Arguments.Count`, `app.Arguments.GetArg(0)` (zero-based).
- `app.GetScriptTab()` (the tab the script started from), `app.GetActiveTab()`, `app.GetTabCount()`, `app.GetTab(1)` (one-based), `app.GetProfiles()` (Lua), `app.Sleep(milliseconds)`, `app.Quit()`.
- `app.Clipboard.Text` (read and write).
- `app.Dialog.MessageBox(text, title)`, `app.Dialog.Prompt(text, title, default, password)` (Lua also returns a cancelled flag), `app.Dialog.FileOpenDialog(title)`.

### Tabs
- Properties: `id`, `Caption` (writable), `Index`, `Screen`, `Session`.
- Methods: `Activate()`, `Clone()`, `Close()`, `ConnectSftp()`, `ResetCaption()`.
- Zmodem: `ZmodemUpload(files)` queues files that are sent when the host starts its Zmodem receiver (returns the number queued), `ZmodemCancel()`, `ZmodemActive()`. Downloads start by themselves.

### Screen
- `Rows`, `Columns`, `CurrentRow`, `CurrentColumn` (one-based), `Selection`, `MatchIndex`.
- `Send(text)` sends text in the session's character set. `SendBytes(data)` sends exact bytes.
- `WaitForString(text, timeout, ignoreCase, milliseconds)` returns true or false. `WaitForStrings(list, ...)` returns the one-based index of the first match, or 0 on timeout. `ReadString(list, timeout, ignoreCase)` returns the text before the match.
- Timeouts are in seconds unless `milliseconds` is true; 0 waits until a match, a disconnect or cancellation. A disconnect while waiting raises an error.
- `IgnoreCase`, `IgnoreEscape` (strip escape sequences from captured text), `Synchronous` (always true).
- `Get(top, left, bottom, right)` and `Get2(...)` read part of the screen (`Get2` keeps line breaks). `Clear()`, `WaitForCursor(timeout)`, `SendKeys("text{ENTER}")` ({ENTER} {TAB} {ESC} {UP} {DOWN} {LEFT} {RIGHT}), `SendSpecial(action)` (actions as in Key Mappings: disconnect, reconnect, copy, paste, clear, log, break, find, next-tab, previous-tab).

Sending marks the received stream first, so a reply that arrives at once is still there for the next wait. A successful wait uses up the text up to its match.

### Session and configuration
- `Connected` (true once logged in), `Status` (Lua), `Path` (saved session id), `Logging`, `LogFileName` (writable), `Config`.
- `Disconnect()`, `Connect(profile)` and `ConnectInTab(profile)` open a new tab from a saved session id or a table/dict of options, `Log(enabled, append, raw)`.
- `Config.GetOption(name)`, `Config.SetOption(name, value)`, `Config.Save()` (saves a saved session), `Config.ConnectInTab()`. Option names are the setting names in the settings file (`font_size`, `log_path`, `hostname` …); Lantern's names (`fontSize`, `logPath`, `host`, `keyFile` …) also work.

### BBS Directory
Low-level requests (Python `_rpc(op, **values)`, Lua `lantern.rpc(op, tab, values)`), the same as Lantern Terminal's:
- `bbsdir.open`, `bbsdir.update` (downloads the lists in the background), `bbsdir.state` (`visible`, `busy`, `status`, `lists`, and `entries` in display order, each with its `group`).
- `bbsdir.add` with `entry = {name, protocol, host, port, username, description}` returns the new `id`.
- `bbsdir.connect` and `bbsdir.save` with `name` (or `id`); for a BBS listed for SSH without a user name, `via = "telnet"` or `via = "ssh"` with `username` answers the question the window would ask. They return `ok` and `message`.
- `bbsdir.remove` and `bbsdir.accept` (Use new address) with `name` or `id`.

## SyncTERM-Lua scripts

The first time a Lua script uses one of the SyncTERM-Lua globals (`send`, `sendln`, `expect`, `expect_pattern`, `expect_regex`, `must_expect`, `waitfor`, `readline`, `quiet`, `sleep`, `idle`, `set_timeout`, `on`, `off`, `screen`, `screen_size`, `cursor`, `buffer`, `clear`, `raw`, `connected`, `disconnect`, `quit`, `now`, `message`, `input`, `confirm`, `log`, `regex`, `bbs`, `before`, `matched`, `script_path`), the compatibility layer is loaded. Received text has escape sequences removed (unless `raw(true)`), `expect` uses up text up to its match, the default timeout is 30 seconds and the script stops when the connection closes. On a CP437 session the script sees the BBS's original CP437 bytes. `bbs.password` is always nil. `log` and `print` write to the Activity pane.

## Safety

Scripts run with your user's permissions. Each run of the program has a private connection (a socket in a folder only you can open, plus a random token passed to the scripts it starts); other users cannot drive your sessions. Do not put passwords in scripts, and review recordings before sharing them. Input typed at password prompts is not recorded.
