Scope is a multi-platform serial monitor with user-extensible features.
Why Scope • Installation • Quickstart • Features • Commands • Shortcuts • CLI • Config • Plugins • Comparison • Troubleshooting
Embedded and hardware work means living in a serial monitor — and most of them either do too little (screen, cat /dev/tty*) or lock the useful parts behind a paid GUI. Scope is a fast, keyboard-driven terminal serial monitor that puts the everyday essentials in one place: colored send/receive with timestamps, reusable @tags and hex input, search, session recording, auto-reconnect, an RTT interface for embedded targets, and Lua plugins when you need to script it. It runs the same on Linux, Windows, and macOS.
You can install Scope with cargo, download a pre-built binary, or build it from source.
- Platforms: Linux, Windows, and macOS (Apple Silicon).
- Building from source /
cargo install: Rust 1.92.0 or newer. - RTT interface: a debug probe supported by
probe-rs(e.g. J-Link, ST-Link, CMSIS-DAP).
cargo install scope-monitorEvery release ships pre-built binaries, a Windows installer, and one-line install scripts on the Releases page.
- Windows: run the
scope-monitor-x86_64-pc-windows-msvc.msiinstaller. It putsscopeon yourPATHand adds Start-Menu shortcuts — scope Serial, scope Serial (headless), scope RTT and scope RTT (headless) — each opening an interactive picker so you can choose a port/baud (or RTT target) with no command line (see Icon mode). - Linux / macOS (shell installer, places the binary on your
PATH):curl --proto '=https' --tlsv1.2 -LsSf https://github.com/matheuswhite/scope-rs/releases/latest/download/scope-monitor-installer.sh | sh
- Windows (PowerShell installer):
powershell -c "irm https://github.com/matheuswhite/scope-rs/releases/latest/download/scope-monitor-installer.ps1 | iex"
- Or download the raw binary/archive for your platform and place it on your
PATH.
git clone https://github.com/matheuswhite/scope-rs
cd scope-rs
cargo build --release
# the binary is at target/release/scopeOpen a serial port by passing it to the serial subcommand together with the baud rate:
scope serial COM3 115200 # Windows
scope serial /dev/ttyUSB0 115200 # Linux
scope list # not sure of the port? list the available onesWhen the command bar at the bottom turns green, Scope is connected: it starts capturing incoming messages and lets you type and send data. Type a message and press Enter to send it. That's it — everything below builds on this loop.
If you run serial (or rtt) without the positional arguments, Scope opens an interactive picker to choose the serial port and baud rate (or the RTT target and channel) before it starts — handy when launching from a Windows shortcut, or when you don't remember the port name:
scope serial # pick a port, then a baud rate, from a menu
scope --headless rtt # same idea for RTT, in headless modeUse ↑/↓ to move and Enter to confirm; press s to start without connecting (as serial/rtt with no picker used to), and q/Esc to quit. When stdin/stdout isn't an interactive terminal (a pipe, or a script), the picker is skipped and Scope starts disconnected as before.
Type a message on the command bar (at the bottom) and press Enter to send it through the serial port. Every message is terminated with \r\n; hold Alt while pressing Enter to send the text without the trailing \r\n.
You can also send raw bytes in hexadecimal. Type $ and write your bytes in a hexadecimal format. Inside a $ sequence you may use ,, _, -, . and spaces as separators between bytes (they are ignored when the bytes are sent), and a new $ starts another sequence. For example, $48 65 6c 6c 6f, $48,65,6c,6c,6f and $48$65$6c$6c$6f all send Hello.
Instead of retyping the same values over and over, you can define tags and reference them with @. Tags live in a YAML tag file (tags.yml by default, or the file passed with -t/--tag-file), written as a simple name: value map:
reset: $01 02 03
greeting: Hello, World!Typing @reset or @greeting on the command bar expands the tag to its value before sending. A tag name is delimited by whitespace, another @, or a ". Press Tab to autocomplete a tag name from the tag file, and the list is filtered as you type. The tag file is watched and hot-reloaded, so edits take effect without restarting Scope.
Note
Tag values are used verbatim. Prior to v0.3.0 Scope had a fixed "command" syntax; it was removed and superseded by these user-defined tags.
You can recall data you sent earlier: press Up Arrow and Down Arrow to navigate through the history of sent messages. The history is persisted between runs in a .scope_history file, so it survives restarts.
Use !send_file <path> to stream the contents of a file to the target over the active interface (serial or RTT). Scope reports the transfer progress and a final confirmation on the history box.
Scope colors the command bar to signal the status of the serial connection: red when disconnected, green when connected. The content read and written is colored too — received data follows the ANSI terminal color standard.
Data sent to the serial port always has a background to distinguish it from received data. Characters outside the printable ASCII range are shown in magenta and in hexadecimal, and a few common characters are printed as their escape representation, such as \n, \r and \0.
All data written or read carries a gray timestamp on the left, in the format HH:MM:SS.ms.
Press Ctrl+F to enter search mode and type a query to find it in the captured history. Press Enter or Down Arrow to jump to the next match and Up Arrow for the previous one. Ctrl+W toggles case sensitivity, and Esc leaves search mode.
Jumping to a specific line in a long history is tedious. Right-click a line to bookmark it (right-click again to remove the bookmark); a bookmarked line is marked by a yellow timestamp. Press Tab to jump to the next bookmark and Shift+Tab for the previous one, cycling around the ends — the same navigation feel as search. Bookmarks follow their line as the history scrolls and survive changing the filter (a bookmark hidden by a filter reappears once the line is shown again); Ctrl+L clears them along with the screen.
When received data floods the history — for example a stream of dbg lines — use the !filter and !mute commands to control which received messages are shown. The current filter is shown between square brackets at the top-right of the command bar (e.g. [.*]). The two commands share that slot, so setting one replaces the other.
!filter <pattern>— show only received lines matching the regex<pattern>(likegrep).!filter— show every received message again (reset to the default.*).!mute <pattern>— hide received lines matching<pattern>(the inverse of!filter, likegrep -v); everything else is shown. The indicator is prefixed with!, e.g.![^dbg].!mute— mute every received message (a warning is logged to make the state obvious).
Behavior to keep in mind:
- The pattern is a regex applied per line, and matching is not anchored. It succeeds when the pattern is found anywhere in the line, so
!filter dbgshows every line that containsdbg, not only those that start with it. Anchor with^/$to match the start / end of a line — e.g.!mute ^dbghides only lines that begin withdbg, and!mute "^\[00"hides lines beginning with[00. - Regex metacharacters must be escaped.
[opens a character class, so to match a literal[write\[(e.g.^\[matches a line starting with[). An invalid pattern is reported as an error and the previous filter is kept. - These commands only affect received (RX) data. Data you send and system log messages (errors, warnings, connection notices…) are always shown, regardless of the filter.
- Filtering is display-only. Every message is still written to the session record, the crash backup and any active recording — it hides lines from the live view, it does not drop them from disk. (So checking the saved
.txtwill show hidden lines; look at the live TUI to see the effect.) Removing the filter (!filter) brings hidden lines back into the view. - The filter resets to the default
.*at the start of each session. It is not persisted.
Click and drag with the mouse to select text in the history, then press Ctrl+C to copy the selection to the system clipboard. You can paste text into the command bar with your terminal's paste shortcut (Scope supports bracketed paste). Ctrl+L clears the screen.
You can change the serial port and its baud rate while the tool is open. Type !serial connect COM4 9600 to set the serial port to COM4 and the baud rate to 9600. You can also omit the port to change only the baud rate (!serial connect 9600) or omit the baud rate to change only the port (!serial connect COM4). To release the serial port, type !serial disconnect.
You can also set hardware/software flow control with !serial flow <none|sw|hw> (flow control applies to serial only). When you don't want to type the interface name, the generic !connect and !disconnect commands act on whichever interface is currently active.
Besides a serial port, Scope can talk to an embedded target over RTT using probe-rs. Start it from the CLI with scope rtt <target> <channel> (for example scope rtt STM32F303 0).
While connected you can:
!rtt connect [<target>] [<channel>]— connect or reconfigure the RTT session (same omit-arguments rules as!serial connect).!rtt disconnect— detach from the target.!rtt read <address> [<size>]— readsizebytes (default4) from the target's memory. The address may be hexadecimal (0x...) or decimal.
Scope has an auto-reconnect feature: when the serial port isn't available, it keeps trying to reconnect until the port comes back.
Note
Auto-reconnect has some known issues on the Windows version.
To save all the messages captured (and sent) since the start, press Ctrl+S. The history box blinks and a message is shown in the history. The filename appears at the top of the history box with a .txt extension, and the top-right corner shows the total size of the captured messages.
If you instead want to save only the messages captured from now on, use the record feature. Press Ctrl+R to start a record session: while recording, the history block turns yellow and Scope stores every message captured from that point. Press Ctrl+R again to stop. The right-corner indicator shows the size of the record session, and a new filename is created each time a record session starts. Both starting and stopping a session print a message in the history box marking when it happened.
You can rename the current session at runtime with !rename <name>; the save file (and any crash-recovery backup) follows the new name. To guard against an accidental close or a crash, Scope also mirrors the running session to a .bkp file under the user config directory (e.g. ~/.config/scope/backup/), keeping the most recent backups.
Passing --headless (e.g. scope --headless serial /dev/ttyUSB0 115200) drops the TUI and turns Scope into a transparent bridge between your terminal and the wire — like screen/minicom in raw mode. This is the mode to use when you are driving a shell over the link (so keystrokes and ANSI escapes go through untouched) or piping the output to another program (an AI assistant, a log file) that would otherwise choke on the TUI's box-drawing.
In headless mode:
- Received bytes are written straight to stdout, verbatim and immediately — no timestamps, no scrollback, no colouring. The device's own ANSI sequences control your terminal directly.
- Every key you type is sent raw to the device the moment you press it (no command-bar buffering, no local echo — the device echoes, as with any serial terminal).
- Log messages (connection notices, errors…) still appear inline, with a coloured background so they stand out from device output; they are never sent to the wire.
- Scroll, search and copy are delegated to your terminal emulator — they don't exist inside
Scopehere. - Nothing is written to disk — no session record, backup, or
!record. Redirect stdout yourself if you want a capture.
To reach the normal Scope commands (!plugin, !connect, @tags, $hex, history, …), press Ctrl+K: a blinking > prompt (black on yellow) appears and incoming output is held back while you type. Press Enter to run the command (or Esc / an empty Enter to cancel) and return to the raw bridge. Every command available in the normal command bar works here. To quit, press Ctrl+K then Ctrl+Q, or run the !exit (alias !quit) command.
Driving headless mode from an AI agent?
matheuswhite/scope-skillis a Claude Code skill that wraps this bridge — it connects, sends text /$hex/@tags/ commands, and reads the device's replies over serial or RTT, all without the TUI.
Scope runs on Linux, Windows, and macOS (Apple Silicon), with the same interface and behavior on each.
Anything typed on the command bar that starts with ! is a command. A line without ! is sent as data (after expanding @tags and $hex).
| Command | Description |
|---|---|
!serial connect [<port>] [<baud>] |
Connect/reconfigure the serial port. A numeric argument is the baud rate, otherwise it's the port; omit either to keep the current value. |
!serial disconnect |
Release the serial port. |
!serial flow <none|sw|hw> |
Set flow control: none, software (sw) or hardware (hw). |
!rtt connect [<target>] [<channel>] |
Connect/reconfigure the RTT session (numeric argument is the channel). |
!rtt disconnect |
Detach from the RTT target. |
!rtt read <address> [<size>] |
Read size bytes (default 4) from target memory. Address is hex (0x..) or decimal. |
!connect / !disconnect |
Connect / disconnect whichever interface is currently active. |
!rename <name> |
Rename the current session record file (and its backup). |
!filter <pattern> |
Show only received messages matching the regex <pattern> (applied line by line, unanchored), like grep. Call with no argument to show everything again (reset to the default .*). Display-only — the session record keeps every message. |
!mute <pattern> |
Hide received messages matching the regex <pattern> (the inverse of !filter, like grep -v). Call with no argument to mute every received message (a warning is logged). Display-only — the session record keeps every message. |
!send_file <path> |
Stream a file to the target over the active interface. |
!log <module> <level> |
Set the log level. <module> is system (sys) or a plugin name; <level> is one of debug, info, success, warning, error. |
!plugin load <file> |
Load a Lua plugin from a file (for this session only). |
!plugin reload <file> |
Reload a plugin from a file. |
!plugin install <file> |
Load a plugin and install it, so every subsequent Scope session auto-loads it. |
!plugin uninstall <name> |
Uninstall a plugin: remove it from the auto-load set and delete its staged copy (a running instance stays loaded for the session — use !plugin unload to stop it now). |
!plugin list |
List the installed (auto-loaded) plugins. |
!plugin unload <name> |
Unload a plugin by name (for this session; does not uninstall). |
!<plugin> <command> [args...] |
Call a command exported by a loaded plugin (see Plugins). |
Rows with a Config action name are remappable via the [shortcuts] table in
config.toml (see Configuration File). The rest — typing,
Enter, Esc, arrows, Home/End, Backspace/Delete, and the headless
Ctrl+K / Ctrl+Q chords — are fixed and cannot be rebound.
| Shortcut | Action | Config action |
|---|---|---|
Enter |
Send the message / run the command. In search mode: next match. | — |
Alt+Enter |
Send without the trailing \r\n. |
— |
Alt+Enter (Ctrl+Enter on Windows) |
In search mode: previous match. | — |
Up / Down |
Navigate the command history. In search mode: previous / next match. | — |
Ctrl+F |
Toggle search mode. | search_toggle |
Ctrl+W |
In search mode: toggle case sensitivity. | toggle_case |
Ctrl+E |
In search mode: toggle regular-expression matching. | toggle_regex |
Tab |
Autocomplete a @tag from the tag file (while the pop-up is up); otherwise jump to the next bookmark. |
next_bookmark |
Shift+Tab |
Jump to the previous bookmark. | prev_bookmark |
Ctrl+S |
Save the whole session to a .txt file. |
save |
Ctrl+R |
Start / stop a record session. | record |
Ctrl+C |
Copy the current selection to the clipboard. | copy |
| Terminal paste shortcut | Paste text into the command bar (via bracketed paste). | — |
Ctrl+L |
Clear the screen. | clear |
Esc |
Leave search mode, or quit Scope when in normal mode. |
— |
PageUp / PageDown |
Scroll the history one page up / down. | page_up / page_down |
Alt+PageUp / Alt+PageDown (Ctrl on Windows) |
Jump to the start / end of the history. | jump_start / jump_end |
Home / End |
Move the cursor to the start / end of the input. | — |
Ctrl+Left / Ctrl+Right (Alt on macOS) |
Move the cursor one word left / right. | word_left / word_right |
Backspace / Delete |
Delete the character before / at the cursor. | — |
| Mouse wheel | Scroll the history (hold Ctrl to scroll horizontally). |
— |
| Mouse drag | Select text (copy it with Ctrl+C). |
— |
| Mouse right-click | Toggle a bookmark on the clicked line (navigate with Tab / Shift+Tab). |
— |
Scope is invoked as scope [OPTIONS] <COMMAND>.
Commands:
| Command | Description |
|---|---|
serial [<port>] [<baudrate>] |
Open a serial port (e.g. scope serial COM3 115200). |
rtt [<target>] [<channel>] |
Attach to an RTT target via probe-rs (e.g. scope rtt STM32F303 0). |
list [-v|--verbose] |
List the available serial ports. |
ble <name> <mtu> |
(Not yet implemented.) |
Global options (given before the command):
| Option | Default | Description |
|---|---|---|
-c, --capacity <N> |
2000 |
Number of scrollback lines kept in memory. |
-t, --tag-file <PATH> |
tags.yml |
Path to the tag file (see Tags). |
-l, --latency <US> |
100 |
Polling latency in microseconds (clamped to 0..=100000). |
-n, --name <NAME> |
timestamp | Base name for the session record file. |
--headless |
off | Run without the TUI as a raw terminal↔wire bridge (see Headless mode). |
The options above can also be set in an optional config.toml placed in your platform config directory under scope/ (for example ~/.config/scope/config.toml on Linux). It supports the capacity and tag_file fields and an optional [shortcuts] table:
capacity = 5000
tag_file = "/home/user/.config/scope/tags.yml"
[shortcuts]
record = "Ctrl+G" # move record off Ctrl+R
next_bookmark = "F2"
prev_bookmark = "F3"
# every other action keeps its defaultValues resolve as CLI flag > config.toml > built-in default, so a flag always wins over the file, and the file wins over the defaults. A missing file (or a missing field) just falls back to the defaults; a malformed file or an unknown key is reported as an error. Paths are used verbatim — ~ and environment variables are not expanded, so use an absolute path.
The [shortcuts] table remaps any of the action/navigation keys listed in the Keyboard & Mouse Shortcuts table — the entries with a Config action name. Each entry is action = "Key+Combo"; there is no CLI flag, so a shortcut resolves as config.toml > built-in default, and every action you don't list keeps its default. The valid action names are:
copy, clear, save, record, search_toggle, toggle_case, toggle_regex, page_up, page_down, jump_start, jump_end, word_left, word_right, next_bookmark, prev_bookmark.
A key combo is zero or more modifiers followed by a key, joined with + (case-insensitive): modifiers Ctrl, Alt, Shift; keys are a single character (c, /, .), a named key (Space, Tab, Enter, Esc, Backspace, Delete, Insert, Home, End, PageUp, PageDown, Up, Down, Left, Right) or a function key (F1–F12). Shift+Tab means the "back-tab" key. Examples: "Ctrl+G", "F2", "Alt+Home", "Ctrl+Alt+End".
Modifiers are logical and translated to what your terminal actually delivers, so a config file is portable: Ctrl+Left keeps working on macOS (where the terminal sends it as Alt+Left), and the jump_start / jump_end defaults are Ctrl+PageUp / Ctrl+PageDown on Windows but Alt+PageUp / Alt+PageDown elsewhere. An unknown action name, an unparseable combo, a binding onto a fixed key (e.g. Enter, an arrow, a bare character, or the headless Ctrl+K / Ctrl+Q), or two actions bound to the same key are all reported as errors on startup.
Note: terminals send
Ctrl+I,Ctrl+J, andCtrl+MasTab,Enter, andEnterrespectively, so those three combos can't be used as shortcuts — they parse without error but the keystroke never reaches the app asCtrl+<letter>. Pick another key (e.g.Ctrl+G).
You can extend the basic functions of Scope with plugins! Plugins are scripts written in the lua language. The code below shows a plugin that prepends Received: to every received message. It also prints Hello, World! when the user types !echo hello (if the plugin file is echo.lua).
local log = require("scope").log
local fmt = require("scope").fmt
local M = {}
function M.on_serial_recv(msg)
log.info("Received: " .. fmt.to_str(msg))
end
function M.hello()
log.info("Hello, World!")
end
return MLoad a plugin with !plugin load <file> (and !plugin reload <file> / !plugin unload <name> to reload or unload it). To keep a plugin across sessions, use !plugin install <file>: it loads the plugin now and records it so every subsequent Scope session auto-loads it at start-up (see !plugin list to review the installed set, and !plugin uninstall <name> to remove one from it). To call one of your plugin's commands, type ! followed by the plugin name, the command name and its arguments — for example !echo hello. Inside a plugin you can react to lifecycle and I/O events (on_load, on_unload, on_serial_recv/on_serial_send, on_rtt_recv/on_rtt_send) and interact with Scope and the target: connect/disconnect, send data, read RTT memory, print messages, run shell commands and more. For the full guide see the Plugins Developer Guide.
Scope combines many features that are otherwise scattered across different tools. The table below compares them:
| Features | Scope (Free) | Docklight | Arduino | Tera Term | screen | esp-idf |
|---|---|---|---|---|---|---|
| Send Data | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Send in Hexadecimal | ✅ | ✅ | x | x | x | x |
| Tags / Macros | ✅[^1] | ✅ | x | x | x | x |
| Written History | ✅ | ✅[^2] | x | x | x | x |
| Auto Reconnect | ✅ | ✅ | x | ✅ | x | x |
| Colorful | ✅ | x | x | ✅ | ✅ | ✅ |
| Message Timestamp | ✅ | ✅ | x | x | x | x |
| Display non-printable chars | ✅ | ✅ | x | x | x | x |
| Plugins | ✅ | ✅ | x | x | x | x |
| Multiplatform | ✅ | Windows | ✅ | Windows | Linux | ✅ |
| Interface | TUI | GUI | GUI | GUI | Terminal | Terminal |
| Price | Free | €69 | Free | Free | Free | Free |
[^1]: User-defined @tags replaced the old fixed command syntax that was removed at v0.3.0
[^2]: The Docklight has a list of commands in lateral panel, so it doesn't need a command history
No serial ports are listed. Run scope list (or scope list -v) to see what Scope detects. If your device is missing, check the cable and drivers, and make sure you have permission to access the port — on Linux your user usually needs to be in the dialout group (sudo usermod -aG dialout $USER, then log out and back in).
Auto-reconnect misbehaves on Windows. This is a known limitation of the Windows build; reconnect works reliably on Linux and macOS.
RTT won't connect. The RTT interface needs a debug probe supported by probe-rs and the correct target chip name and channel — for example scope rtt STM32F303 0. Make sure the probe is connected and not held by another debugger.
My tag_file / config path isn't found. Path values are used verbatim: ~ and environment variables are not expanded. Use an absolute path (for example /home/user/.config/scope/tags.yml).
This project has 5 pillars that direct the development of this tool:
I. Intuitive Usage: The usage of the tool must be intuitive. This means the usability should follow the common behaviors of other popular tools. For example, the history navigation (Up Arrow and Down Arrow) follows the history navigation of an OS terminal like the Unix shell or Windows PowerShell.
II. Compactness and Orthogonality: The features must follow the compactness and orthogonality principles of Unix.
III. User-Centric Development: The development of this tool must deliver value to the user first, instead of pleasing the developers. For example, the scripting language used to extend the tool is a consolidated programming language rather than a new one, and critical bugs reported by users are prioritized over shipping new features.
IV. Multiplatform: All releases must work on Windows, Linux (zsh, bash and fish) and macOS.
V. Extensible: Support user scripts to extend the base functionality. These scripts are called plugins. For more information about plugins see the Plugins Developer Guide.
The roadmap, with upcoming releases, can be found in the GitHub project for this tool.
Contributions are welcome — take a look at the CONTRIBUTING guide.
For new feature requests and to report a bug, feel free to open a new issue on GitHub.
- Emilio Bottoni and José Gomes for the ideas that push this tool forward, for the testing that finds hidden bugs, and for good feature implementations.
Scope is distributed under the terms of both the MIT license and the Apache
License (Version 2.0), at your option.
See LICENSE-MIT and LICENSE-APACHE for details.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Scope by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.














