Skip to content

Latest commit

 

History

History
311 lines (223 loc) · 7.88 KB

File metadata and controls

311 lines (223 loc) · 7.88 KB

trec

trec records terminal sessions into binary .trec files. It can replay a recording or generate a small C or Python script from the recorded input.

The project is still early. The commands below are meant to be run from the repository checkout. C examples include headers with -Iinclude; Python examples use PYTHONPATH=python and TREC_LIBRARY=$PWD/libtrec.so so they load the local binding and library.

Requirements

  • Linux
  • make
  • gcc
  • Python 3 for tests, the dump tool, and the Python binding
  • clang-format for make format

Build

make

This builds:

  • ./trec
  • ./libtrec.a
  • ./libtrec.so

Other common targets:

make test
make format
make clean

Quick start

Record a shell session:

./trec record --file session.trec

record starts $SHELL. Use the shell as usual, then press Ctrl-D to stop recording.

Replay the recorded input in a fresh shell:

./trec replay --file session.trec

Print the recorded output without running the commands again:

./trec replay --output-replay --file session.trec

Generate and run a C script:

./trec codegen --file session.trec --output session.c
cc -Wall -Wextra -g -Iinclude -L. -o session session.c -ltrec -lutil
./session

Generate and run a Python script:

./trec codegen --language python --file session.trec --output session.py
PYTHONPATH=python TREC_LIBRARY=$PWD/libtrec.so python3 session.py

Recording

./trec record [OPTIONS]
Option Meaning
-f, --file <file> Output file. Default: session.trec
-h, --help Show help

Recording stores input, output, resize events, and the shell exit status. It always starts $SHELL; if $SHELL is not set, it uses /bin/sh.

Replay

./trec replay [OPTIONS]
Option Meaning
-f, --file <file> Input .trec file. Required
--output-replay Print recorded output instead of injecting input into a shell
-h, --help Show help

Default replay starts a new $SHELL and sends the recorded input to it. It runs against the current filesystem, commands, and environment, so output can differ from the original session.

--output-replay prints the recorded output with recorded timing. It does not start a shell.

Codegen

./trec codegen [OPTIONS]
Option Meaning
-f, --file <file> Input .trec file. Required
-o, --output <file> Output file. Required
-l, --language <lang> Output language: c or python. Default: c
--timing-mode <mode> Timing mode: merge, interval, recorded, or none
--merge-threshold-ms <ms> Merge action gaps below this value. Default: 500
--interval-ms <ms> Fixed interval for interval timing mode
--no-timing Alias for --timing-mode none
--include-output-comments Include recorded output snippets as comments
--include-raw-comments Include undecoded input comments
--record-file <file> Make the generated script record its own run
--strict Fail if an input sequence cannot be decoded
-h, --help Show help

Basic C output:

./trec codegen --file demo.trec --output demo.c
cc -Wall -Wextra -g -Iinclude -L. -o demo demo.c -ltrec -lutil
./demo

Basic Python output:

./trec codegen --language python --file demo.trec --output demo.py
PYTHONPATH=python TREC_LIBRARY=$PWD/libtrec.so python3 demo.py

Record a generated script's run:

./trec codegen --file demo.trec --output demo.c --record-file demo-rerun.trec

If --record-file is omitted, generated scripts do not record their run.

Generated C sets the record file before starting the session:

struct trec_session s = {0};

trec_session_set_record_file(&s, "demo-rerun.trec");
trec_session_start_shell(&s, rows, cols);

Generated Python uses the same order:

with Session(record_file="demo-rerun.trec") as s:
    s.start_shell(rows, cols)

Decoded input becomes script calls:

trec_send_text(&s, "echo hello");
trec_send_key(&s, TREC_KEY_ENTER);
trec_click_at(&s, 10, 5, TREC_MOUSE_LEFT);
trec_drag(&s, 2, 3, 8, 9, TREC_MOUSE_LEFT);
trec_resize(&s, 40, 120);
trec_sleep_ms(&s, 250);
with Session() as s:
    s.start_shell()
    s.send_text("echo hello")
    s.send_key(Key.ENTER)
    s.click_at(10, 5, MouseButton.LEFT)

If input cannot be decoded, codegen writes raw bytes unless --strict is set.

Timing modes

The default mode is merge. It drops short gaps below 500 ms and combines adjacent text input:

./trec codegen --file demo.trec --output demo.c

Use a different merge threshold:

./trec codegen --timing-mode merge --merge-threshold-ms 1000 --file demo.trec --output demo.c

Use a fixed delay between actions:

./trec codegen --timing-mode interval --interval-ms 50 --file demo.trec --output demo.c

Preserve recorded action gaps:

./trec codegen --timing-mode recorded --file demo.trec --output demo.c

Emit no sleeps:

./trec codegen --timing-mode none --file demo.trec --output demo.c
./trec codegen --no-timing --file demo.trec --output demo.c

Runtime screen APIs

Scripts can wait for text, find text, click a found position, or read the visible screen.

C:

uint16_t x = 0;
uint16_t y = 0;

if (trec_expect_text(&s, "Submit", 3000) == 1 &&
    trec_find_text(&s, "Submit", &x, &y) == 1) {
    trec_click_at(&s, x, y, TREC_MOUSE_LEFT);
}

Python:

with Session() as s:
    s.start_shell()
    if s.expect_text("Submit", 3000):
        pos = s.find_text("Submit")
        if pos is not None:
            x, y = pos
            s.click_at(x, y)

        for x, y in s.find_all_text("Submit"):
            print("Submit at", x, y)

trec_find_text and Session.find_text() return the first match. trec_find_all_text and Session.find_all_text() return all visible matches. trec_snapshot_text and Session.snapshot_text() return the visible screen as plain text. trec_snapshot and Session.snapshot() return cell data, including size, cursor position, text, width, attributes, and supported colors.

Python binding

The Python binding uses ctypes and loads libtrec.so.

make
PYTHONPATH=python TREC_LIBRARY=$PWD/libtrec.so python3 script.py

Example:

from trec import Key, Session

with Session() as session:
    session.start_shell()
    session.send_text("printf 'button\\n'\n")
    session.expect_text("button", 3000)
    pos = session.find_text("button")
    if pos is not None:
        x, y = pos
        session.click_at(x, y)
    print(session.snapshot_text())
    session.send_text("exit\n")
    raise SystemExit(session.close())

Use Session as a context manager. If the with block raises, the session terminates the child process and restores the terminal state.

Inspect .trec files

Use tools/trec_dump.py:

python3 tools/trec_dump.py demo.trec
python3 tools/trec_dump.py --payload-limit 80 demo.trec
python3 tools/trec_dump.py --no-hex demo.trec
python3 tools/trec_dump.py --format json demo.trec
python3 tools/trec_dump.py --format yaml demo.trec

Mouse input

Mouse input is recorded when the application inside the shell enables mouse reporting. Decoded mouse input can produce trec_click_at, trec_mouse_down, trec_mouse_up, trec_drag, and trec_mouse_scroll calls.

Current limitations

  • Linux-only alpha
  • unstable .trec file format
  • unstable generated script APIs
  • no package manager installation yet
  • recording launches $SHELL; it does not accept a target command
  • terminal behavior is tested mainly against xterm-like terminals

TODO

  • snapshot image export
  • terminal profiles for iTerm2, Ghostty, WezTerm, kitty, and others
  • image protocols such as Sixel and kitty image protocol
  • Windows and macOS support

License

trec is licensed under Apache-2.0. The vendored vendor/libvterm dependency uses the MIT license in vendor/libvterm/LICENSE.