Getting Started with Hex Stream Terminal
Welcome to the Custom Terminal, a fun and extensible Python terminal that accepts both raw hex streams and human-readable string commands. This guide will get you cloned, set up, and sending your first commands.

A. Overview
The Custom Terminal is a Python-based terminal emulator with two operating modes. It was originally designed to interpret raw hex streams as drawing commands, and has since been extended to also accept human-readable string commands via the --readable flag. Both modes ultimately resolve to the same hex execution pipeline under the hood, ensuring consistent, validated behaviour.
# Hex mode (default)
0x01 0x03 0x50 0x18 0x01 0xFF
# Readable mode (--readable)
screen_setup 80 24 16colors
Both examples above are equivalent: they set up an 80Γ24 screen in 16-color mode. The terminal translates readable commands into hex before executing them.
B. Prerequisites & Setup
1. Requirements
- Python 3.8 or higher: Download from python.org.
- argparse: standard library, included with Python.
- sys: standard library, included with Python.
- A terminal emulator:
bash,zsh,cmd, orPowerShell.
No pip installs needed
This project uses only Python standard library modules. No pip install step is required. As long as you have Python 3.8+, you're ready to go.
2. Clone the Repository
git clone https://github.com/VictorCodebase/CustomTerminal.git
cd CustomTerminalThat's it. No build step, no virtual environment required. The project runs directly with Python.
C. Running the Terminal
The entry point is terminal.py. Run it with or without the --readable flag depending on which mode you want.
Hex mode (default):
python terminal.pyHuman-readable string mode:
python terminal.py --readableOnce running, the terminal will wait for your input. Type a command (or paste a hex stream) and press Enter to execute it.
D. Hex Mode
In hex mode (the default), you input raw hex byte streams separated by spaces or commas. The terminal parses each stream, validates it, and executes the corresponding commands.
Stream Structure
Every valid command stream begins with a command byte (0x01β0x08), followed by the correct number of argument bytes, and ends with 0xFF (end-of-file marker). Multiple commands can be chained in a single input.
Example: set up screen then draw a character:
0x01 0x03 0x50 0x18 0x01 0xFF 0x02 0x04 0x00 0x00 0x07 0x41 0xFFThis sets up an 80Γ24 screen in 16-color mode, then draws the character A in white at position (0, 0).
E. Understanding the Hex Stream
Every hex stream follows a strict but simple structure. Once you understand the anatomy of a single command stream, you can read and write any input the terminal accepts.
Stream Anatomy
A single command stream is made up of three parts, in order, with the full input terminated by a final 0xFF end-of-file byte:
Example: screen_setup 80 24 16colors as a single-command stream
Always the first byte in a command stream. Ranges from 0x01 to 0x08, each mapping to one of the eight available commands. The terminal uses this byte to know which command to run and what to expect in the bytes that follow.
The second byte declares how many instruction bytes follow. The terminal reads exactly this many bytes as the command's arguments, no more and no less. This is how the parser knows where one command ends and the next begins, enabling reliable command chaining.
Some commands have a fixed, strict length requirement (e.g. screen_setup always requires exactly 3 argument bytes). Others have a minimum length. For example, draw_line requires at least enough bytes to encode the position, color, and one character, but the length byte can extend this for variable content. render_text accepts up to 99 argument bytes, allowing long text strings.
| Command | Length type | Arg count |
|---|---|---|
| screen_setup | strict | 3 |
| draw_char | strict | 4 |
| draw_line | strict | 6 |
| render_text | variable | 3 + text length (max 99) |
| cursor_move | strict | 2 |
| draw_at_cursor | strict | 2 |
| clear_screen | strict | 0 |
| render | strict | 0 |
The remaining bytes carry the actual arguments, in the exact order defined by the command's signature. Each byte is interpreted as an ASCII character value and converted accordingly: 0x50 becomes the integer 80, 0x41 becomes the character A, and 0x07 becomes the color index for white in the current color mode. The expected type (int, string, or char) is determined by the command's signature for that argument position.
The entire input must be terminated with a single 0xFF byte. This signals to the parser that there are no further commands to read. A missing 0xFF will cause a validation error.
Command Chaining
Multiple commands can be chained together in a single input. After the terminal has consumed the argument bytes declared by a command's length byte, it expects the very next byte to be another command ID (or the 0xFF end-of-file marker). The argument length byte makes this unambiguous: the parser always knows exactly how many bytes belong to the current command before it looks ahead.
Two chained commands: screen_setup then draw_char, terminated by 0xFF
Error handling is applied independently at each command boundary. If an argument count is wrong or an unknown command byte is encountered, the error is caught and reported precisely for that command, without corrupting the interpretation of surrounding commands in the chain.
Worked Example Streams
The streams below are real examples you can try. Each is broken down byte by byte so you can follow exactly how the parser reads them. Paste any of the raw streams into the terminal to see them in action.
Setup β draw character β draw line
0x01 0x03 0x50 0x18 0x01 0x02 0x04 0x00 0x00 0x07 0x41 0x03 0x06 0x3C 0x02 0x03 0x0A 0x07 0x2A 0xFF| Bytes | Role | Interpretation |
|---|---|---|
| 0x01 | Command ID | screen_setup |
| 0x03 | Arg length | 3 argument bytes follow |
| 0x50 | Arg 1 β width | 0x50 = 80 β screen width: 80 |
| 0x18 | Arg 2 β height | 0x18 = 24 β screen height: 24 |
| 0x01 | Arg 3 β color mode | 0x01 β 16colors |
| 0x02 | Command ID | draw_char |
| 0x04 | Arg length | 4 argument bytes follow |
| 0x00 | Arg 1 β x | 0x00 = 0 |
| 0x00 | Arg 2 β y | 0x00 = 0 |
| 0x07 | Arg 3 β color | 0x07 β white (16colors index) |
| 0x41 | Arg 4 β char | 0x41 = ASCII 'A' |
| 0x03 | Command ID | draw_line |
| 0x06 | Arg length | 6 argument bytes follow |
| 0x3C | Arg 1 β x | 0x3C = 60 |
| 0x02 | Arg 2 β y | 0x02 = 2 |
| 0x03 | Arg 3 β length | 0x03 = 3 |
| 0x0A | Arg 4 β extra | 0x0A = 10 |
| 0x07 | Arg 5 β color | 0x07 β white |
| 0x2A | Arg 6 β char | 0x2A = ASCII '*' |
| 0xFF | End of File | Stream complete |
Stream 1 + cursor move at the end
0x01 0x03 0x50 0x18 0x01 0x02 0x04 0x00 0x00 0x07 0x41 0x03 0x06 0x3C 0x02 0x03 0x0A 0x07 0x2A 0x05 0x02 0x14 0x05 0xFFIdentical to Stream 1, with one additional command appended before 0xFF:
| Bytes | Role | Interpretation |
|---|---|---|
| 0x05 | Command ID | cursor_move |
| 0x02 | Arg length | 2 argument bytes follow |
| 0x14 | Arg 1 β x | 0x14 = 20 |
| 0x05 | Arg 2 β y | 0x05 = 5 |
| 0xFF | End of File | Stream complete |
This demonstrates how chaining works: appending the next command's bytes, with the parser reading the cursor_move command ID immediately after consuming draw_line's 6 declared argument bytes.
Setup β render text ("hello brother")
0x01 0x03 0x50 0x18 0x01 0x04 0x10 0x28 0x02 0x07 0x68 0x65 0x6C 0x6C 0x6F 0x20 0x62 0x72 0x6F 0x74 0x68 0x65 0x72 0xFF| Bytes | Role | Interpretation |
|---|---|---|
| 0x01 0x03 0x50 0x18 0x01 | screen_setup | 80 Γ 24, 16colors (same as above) |
| 0x04 | Command ID | render_text |
| 0x10 | Arg length | 16 argument bytes follow |
| 0x28 | Arg 1 β x | 0x28 = 40 |
| 0x02 | Arg 2 β y | 0x02 = 2 |
| 0x07 | Arg 3 β color | 0x07 β white |
| 0x68 0x65 0x6C 0x6C 0x6F | Text bytes 1β5 | h e l l o |
| 0x20 | Text byte 6 | 0x20 = ASCII space |
| 0x62 0x72 0x6F 0x74 0x68 0x65 0x72 | Text bytes 7β13 | b r o t h e r |
| 0xFF | End of File | Stream complete. Total: "hello brother" (13 bytes) + 3 positional args = 16 β |
Notice how the length byte 0x10 (=16) tells the parser exactly how many bytes of text to consume, even though text is variable-length. The arg count is the key: 3 fixed args (x, y, color) + 13 character bytes = 16.
Full composition: text, two lines, and "Merry Christmas!!"
0x01 0x03 0x50 0x18 0x01 0x04 0x10 0x28 0x02 0x07 0x68 0x65 0x6C 0x6C 0x6F 0x20 0x62 0x72 0x6F 0x74 0x68 0x65 0x72 0x03 0x06 0x3C 0x02 0x03 0x0A 0x07 0x2A 0x03 0x06 0x0A 0x02 0x0A 0x16 0x01 0x2A 0x04 0x14 0x1E 0x0C 0x06 0x4D 0x65 0x72 0x72 0x79 0x20 0x43 0x68 0x72 0x69 0x73 0x74 0x6D 0x61 0x73 0x21 0x21 0xFF| Bytes | Command | Interpretation |
|---|---|---|
| 0x01 0x03 0x50 0x18 0x01 | screen_setup | 80 Γ 24, 16colors |
| 0x04 0x10 0x28 0x02 0x07 β¦0x72 | render_text | x=40, y=2, white, "hello brother" (13 chars, total 16 args) |
| 0x03 0x06 0x3C 0x02 0x03 0x0A 0x07 0x2A | draw_line | x=60, y=2, length=3, extra=10, white, '*' |
| 0x03 0x06 0x0A 0x02 0x0A 0x16 0x01 0x2A | draw_line | x=10, y=2, length=10, extra=22, red (0x01), '*' |
| 0x04 0x14 0x1E 0x0C 0x06 β¦0x21 0x21 | render_text | x=30, y=12, cyan (0x06), "Merry Christmas!!" (17 chars, total 20 args) |
| 0xFF | End of File | 5 chained commands, stream complete |
The "Merry Christmas!!" text breakdown
The final render_text has length byte 0x14 = 20. That's: 3 fixed args (x=0x1E=30, y=0x0C=12, color=0x06=cyan) + 17 text character bytes = 20 β. The text 0x4D 0x65 0x72 0x72 0x79 0x20 0x43 0x68 0x72 0x69 0x73 0x74 0x6D 0x61 0x73 0x21 0x21 decodes as M-e-r-r-y-[space]-C-h-r-i-s-t-m-a-s-!-! = "Merry Christmas!!"
Common ASCII β Hex Reference
Instruction bytes for characters and numbers are plain ASCII. Here are frequently used values:
| Char / Value | Hex | Decimal |
|---|---|---|
| Space ( ) | 0x20 | 32 |
| ! | 0x21 | 33 |
| * | 0x2A | 42 |
| 0 | 0x30 | 48 |
| 9 | 0x39 | 57 |
| A | 0x41 | 65 |
| Z | 0x5A | 90 |
| a | 0x61 | 97 |
| z | 0x7A | 122 |
| Width 80 | 0x50 | 80 |
| Height 24 | 0x18 | 24 |
| Height 30 | 0x1E | 30 |
F. Readable (String) Mode
Launched with python terminal.py --readable, this mode accepts natural command strings. Each string command is validated, converted to its hex equivalent internally, then executed through the same hex pipeline.
Example session:
screen_setup 80 24 16colors
draw_char 5 3 green @
render_text 10 5 cyan Hello World
renderCommands are entered one per line. Each command name must match exactly (case-sensitive) and be followed by the correct number of arguments in order.
G. Command Reference
All accepted commands are listed below. Commands apply to both hex mode and readable mode. The signature column shows the argument order for readable mode.
Initialises the screen with the given dimensions and color mode. Must be called before any draw commands.
Signature
screen_setup <width: int> <height: int> <color_mode: string>Example
screen_setup 80 24 16colorsDraws a single character at the specified (x, y) position in the given color.
Signature
draw_char <x: int> <y: int> <color: string> <char: char>Example
draw_char 0 0 white ADraws a horizontal line of the given length starting at (x, y), filled with the specified character and color.
Signature
draw_line <x: int> <y: int> <length: int> <color: string> <char: char>Example
draw_line 60 2 10 white *Renders a string of text starting at (x, y) in the given color. The text can span multiple words. All remaining tokens after the color argument are treated as the text.
Signature
render_text <x: int> <y: int> <color: string> <text: string...>Example
render_text 40 2 white hello brotherMoves the cursor to the specified (x, y) position without drawing anything.
Signature
cursor_move <x: int> <y: int>Example
cursor_move 20 5Draws a single character at the current cursor position in the given color. Use cursor_move first to position the cursor.
Signature
draw_at_cursor <color: string> <char: char>Example
draw_at_cursor white AClears the entire screen. Takes no arguments.
Signature
clear_screen Example
clear_screenFlushes and renders everything drawn so far to the screen. Call this after your drawing commands to display output.
Signature
render Example
renderH. Color Modes
The screen_setup command accepts one of three color mode strings. The mode you choose determines which color names are valid for subsequent draw commands.
monochrome
Two colors only: black and white.
16colors
Standard 16-color terminal palette.
256colors
Extended 256-color mode. Refer to the repository for the complete color mapping.
I. How It Works (Pipelines)
Under the hood, both modes pass through rigorous validation and parsing before execution. Understanding the pipeline helps you debug unexpected errors.
Hex Pipeline
String (Readable) Pipeline
J. Full Examples
Example 1: Hello World (readable mode)
Set up an 80Γ24 screen, write "Hello World" in cyan at position (10, 5), then render.
screen_setup 80 24 16colors
render_text 10 5 cyan Hello World
renderExample 2: Hello World (hex mode)
The exact equivalent as a hex stream:
0x01 0x03 0x50 0x18 0x01 0xFF 0x04 0x63 0x0A 0x05 0x06 0x48 0x65 0x6C 0x6C 0x6F 0x20 0x57 0x6F 0x72 0x6C 0x64 0xFF 0x08 0x00 0xFFExample 3: Drawing a border (readable mode)
Set up a screen and draw a line of asterisks along the top.
screen_setup 80 24 16colors
draw_line 0 0 80 white *
renderExample 4: Cursor positioning (readable mode)
Move the cursor then draw at the cursor's position.
screen_setup 80 24 16colors
cursor_move 40 12
draw_at_cursor yellow #
renderK. Troubleshooting & FAQ
Q: The terminal says my hex value is invalid.
A: Make sure every token starts with 0x and is followed by exactly two hex digits (e.g. 0x0A, not 0xA). Check that you haven't included extra spaces or stray characters.
Q: I get an 'incorrect argument count' error.
A: Double-check the command signature in the Command Reference section. Each command expects a specific number of arguments. For render_text, remember that all words after the color argument are treated as the text (no quotes needed).
Q: My color name isn't recognised.
A: Color names are case-sensitive and must match exactly (e.g. bright_blue not BrightBlue). Also confirm that the color you're using is available in the color mode you initialised with screen_setup.
Q: Nothing is showing up on screen.
A: Make sure you call render after your drawing commands. Without render, buffered draw operations are not displayed.
Q: Python says 'module not found' for argparse.
A: argparse is part of the Python standard library since Python 3.2. Ensure you're running Python 3.8 or higher with python --version.