{"id":8193,"date":"2026-02-17T08:06:08","date_gmt":"2026-02-17T16:06:08","guid":{"rendered":"https:\/\/ahmadawais.com\/?p=8193"},"modified":"2026-02-17T10:58:11","modified_gmt":"2026-02-17T18:58:11","slug":"the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys","status":"publish","type":"post","link":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/","title":{"rendered":"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes"},"content":{"rendered":"<p>Most developers use a terminal for years before realizing they don&#8217;t actually know what a terminal <em>is<\/em>. They know how to use one. They couldn&#8217;t define one. This is fine, in the same way that most people drive cars without understanding combustion. But every so often, something breaks in a way that makes the distinction matter, and then you&#8217;re stuck.<\/p>\n<p>The terms &#8220;<strong>terminal<\/strong>,&#8221; &#8220;<strong>shell<\/strong>,&#8221; &#8220;<strong>tty<\/strong>,&#8221; and &#8220;<strong>console<\/strong>&#8221; get used interchangeably. They shouldn&#8217;t be, but the fact that they are tells you something interesting: these concepts are so tightly coupled in practice that generations of developers have gotten by without separating them. The useful question isn&#8217;t just &#8220;what do these words mean&#8221; but &#8220;why does knowing the difference change how you think?&#8221;<\/p>\n<p>This piece covers the full stack: from the vocabulary that trips people up, all the way down to building a TUI (Text User Interface) app from scratch. If you&#8217;ve ever wondered what vim is actually doing when it takes over your screen, or why Raw Mode exists, or what ANSI escape sequences are, this is the piece.<\/p>\n<h2 id=\"they-used-to-be-the-same-thing\">They used to be the same thing<a href=\"#they-used-to-be-the-same-thing\" class=\"heading-link\">#<\/a><\/h2>\n<p>This is the key insight. All four words originally described the same physical object.<\/p>\n<p>In the 1960s, you interacted with a computer by sitting at a machine with a keyboard and a printer. That machine had three names depending on who was talking about it:<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  \u2502  $ hello world_               \u2502   &lt;-- \"Console\" (physical\u2007device)\r\n  \u2502  \u2502                               \u2502   &lt;-- \"Terminal\" (end of a wire)\r\n  \u2502  \u2502                               \u2502   &lt;-- \"TTY\" (it's a teletypewriter)\r\n  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  \u2502 \u250c\u2500\u2500\u2500\u2510\u250c\u2500\u2500\u2500\u2510\u250c\u2500\u2500\u2500\u2510\u250c\u2500\u2500\u2500\u2510\u250c\u2500\u2500\u2500\u2510\r\n  \u2502  \u2502 \u2502 Q \u2502\u2502 W \u2502\u2502 E \u2502\u2502 R \u2502\u2502 T \u2502...\r\n  \u2502  \u2502 \u2514\u2500\u2500\u2500\u2518\u2514\u2500\u2500\u2500\u2518\u2514\u2500\u2500\u2500\u2518\u2514\u2500\u2500\u2500\u2518\u2514\u2500\u2500\u2500\u2518\r\n  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n              \u2502\r\n              \u2502  wire\r\n              \u2502\r\n      \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n      \u2502   MAINFRAME\r\n      \u2502   COMPUTER\r\n      \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>Three names, one thing. That&#8217;s why they blur together. As hardware evolved into software, each word drifted toward its own meaning. But they drifted slowly.<\/p>\n<hr \/>\n<h2 id=\"part-1-the-vocabulary\">Part 1: The Vocabulary<a href=\"#part-1-the-vocabulary\" class=\"heading-link\">#<\/a><\/h2>\n<h3 id=\"console\">Console<a href=\"#console\" class=\"heading-link\">#<\/a><\/h3>\n<p>The console is the physical input\/output device of a computer. Originally it meant a keyboard and display directly connected to the machine itself. From the OS&#8217;s perspective, it&#8217;s treated as &#8220;the system&#8217;s primary standard I\/O terminal.&#8221;<\/p>\n<p>On a server, it&#8217;s what you see when you walk up and plug in a monitor. On your laptop, it&#8217;s the text screen you&#8217;d land on if the graphical desktop crashed. It&#8217;s the terminal of last resort, the one the kernel writes panic messages to, because it&#8217;s guaranteed to exist when everything else is broken.<\/p>\n<pre class=\"lang:sh\">  Remote access (not console):\r\n\r\n    You --&gt; laptop --&gt; SSH --&gt; internet --&gt; server\r\n                                            \u2502\r\n                                       \u250c\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\r\n                                       \u2502 server\r\n                                       \u2514\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\r\n                                            \u2502\r\n  Console access (the real deal):\r\n                                            \u2502\r\n    You --&gt; keyboard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500&gt;\r\n            monitor  &lt;\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n    ^ This is the console. Direct. Physical.\r\n      Works\u2007even\u2007when\u2007the\u2007network\u2007is\u2007down.\r\n<\/pre>\n<p>The word has gotten looser over time. Browser dev tools are &#8220;the console.&#8221; macOS has a log viewer called Console. But the original meaning is always the same: the direct, local, hardware interface.<\/p>\n<h3 id=\"terminal\">Terminal<a href=\"#terminal\" class=\"heading-link\">#<\/a><\/h3>\n<p>A terminal is the thing you <em>see<\/em>. It handles input and output. Keystrokes go in, text comes out. That&#8217;s its entire job.<\/p>\n<p>When you open iTerm2, Alacritty, Windows Terminal, or GNOME Terminal, you&#8217;re running a <strong>terminal emulator<\/strong>: a program that does in software what the old hardware terminals did in circuits. The &#8220;emulator&#8221; part usually gets dropped, but it&#8217;s worth remembering, because it clarifies the abstraction. Your terminal app is pretending to be a device.<\/p>\n<p>Here&#8217;s the part that surprises people: the terminal doesn&#8217;t understand your commands. It has no idea what <code>ls<\/code> or <code>git push<\/code> means. It doesn&#8217;t parse anything. It&#8217;s a display surface with a keyboard attached.<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500 TERMINAL (what you see) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2502  Owns:\r\n  \u2502    - Rendering text on screen\r\n  \u2502    - Colors and fonts\r\n  \u2502    - Scrollback history\r\n  \u2502    - Copy-paste\r\n  \u2502    - Interpreting ANSI escape sequences\r\n  \u2502\r\n  \u2502  Does NOT own:\r\n  \u2502    - Understanding commands\r\n  \u2502    - Running programs\r\n  \u2502    - Tab completion\r\n  \u2502    - Your prompt\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>Major terminal emulators by platform:<\/p>\n<ul>\n<li>macOS: Terminal.app, iTerm2, Ghostty, Alacritty, kitty<\/li>\n<li>Linux: GNOME Terminal, Konsole<\/li>\n<li>Windows: Windows Terminal, ConEmu<\/li>\n<li>Web\/Node: xterm<\/li>\n<\/ul>\n<p>This is a clean separation, and clean separations are worth noticing. They tend to be load-bearing.<\/p>\n<h3 id=\"shell\">Shell<a href=\"#shell\" class=\"heading-link\">#<\/a><\/h3>\n<p>The shell is the program running <em>inside<\/em> your terminal. It&#8217;s the command-line interpreter, the thing that takes the text you type, figures out what program to run, runs it, and manages the lifecycle of that process.<\/p>\n<p>You type <code>git status<\/code>. Your terminal sends that text to the shell. The shell parses it, finds the <code>git<\/code> program, spawns a process, and wires up the I\/O. The output flows back through the terminal to your eyes. The shell handled the logic. The terminal handled the pixels.<\/p>\n<pre class=\"lang:sh\">  You type: git status\r\n        \u2502\r\n        \u25bc\r\n  \u250c\u2500 TERMINAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2502  Converts keystrokes to bytes\r\n  \u2502  Sends text to the shell\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                 \u2502  \"git status\\n\"\r\n                 \u25bc\r\n  \u250c\u2500 SHELL (bash, zsh, fish) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2502  Parses the command\r\n  \u2502  Finds \/usr\/bin\/git\r\n  \u2502  Spawns the process\r\n  \u2502  Manages its lifecycle\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                 \u2502  output bytes\r\n                 \u25bc\r\n  \u250c\u2500 TERMINAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2502  Receives output\r\n  \u2502  Renders it on your screen\r\n  \u2502\r\n  \u2502  On branch main\r\n  \u2502  nothing to commit\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>The shell&#8217;s main jobs are: command parsing (token splitting, redirect processing), environment variable management, process launching and control (job control), and providing scripting functionality.<\/p>\n<p>There are many shells, and which one you use is a meaningful choice.<\/p>\n<ul>\n<li><strong>Bash<\/strong> is the default, exists on virtually any Unix-like system.<\/li>\n<li><strong>Zsh<\/strong> is the default on macOS and popular for its extensibility.<\/li>\n<li><strong>PowerShell<\/strong> takes a different approach entirely on Windows.<\/li>\n<\/ul>\n<p>The crucial thing: your terminal and your shell are independent. You can swap either one without touching the other.<\/p>\n<pre class=\"lang:sh\">  Any terminal          works with         Any shell\r\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  iTerm2      \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2502  Bash\r\n  \u2502  Alacritty   \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2502  Zsh\r\n  \u2502  Kitty       \u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2502  Fish\r\n  \u2502  Win Terminal\u2502\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2502  PowerShell\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        They don't care about each other.\r\n<\/pre>\n<h3 id=\"cli-command-line-interface\">CLI (Command Line Interface)<a href=\"#cli-command-line-interface\" class=\"heading-link\">#<\/a><\/h3>\n<p>CLI is the broader category. It&#8217;s any interface where commands go in as text and output comes back as text, as opposed to a GUI. Shells, REPLs, and TUI apps are all types of CLI. When someone says &#8220;command line,&#8221; they usually mean the actual input line where you type a single command, like <code>ls -l<\/code> or <code>git commit -m \"msg\"<\/code>. The shell parses that string and executes it.<\/p>\n<h3 id=\"tty-teletypewriter\">TTY (TeleTYpewriter)<a href=\"#tty-teletypewriter\" class=\"heading-link\">#<\/a><\/h3>\n<p>TTY is the most technical of the four terms, and the one you can safely ignore the longest.<\/p>\n<p>In modern Unix, a TTY is a device file in the kernel that connects your terminal to your shell. It&#8217;s a general term for terminal devices in Unix-like OSes. Originally it was a device for connecting physical teletypewriters, but now it refers to terminal devices in general, including virtual terminals.<\/p>\n<p>Type <code>tty<\/code> right now and you&#8217;ll see something like <code>\/dev\/ttys003<\/code> or <code>\/dev\/pts\/1<\/code>. That&#8217;s your current session&#8217;s device file.<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502              \u2502     \u2502              \u2502\r\n  \u2502   Terminal   \u2502\u25c4\u2500\u2500\u2500\u25ba\u2502  TTY device  \u2502\u25c4\u2500\u2500\u2500\u25ba\u2502    Shell\r\n  \u2502  (iTerm2)    \u2502     \u2502 \/dev\/pts\/1   \u2502     \u2502   (bash)\r\n  \u2502              \u2502     \u2502              \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    user-space            kernel               user-space\r\n\r\n  The TTY sits in between. It's the kernel's\r\n  middleman that handles the data flow.\r\n<\/pre>\n<p>For everyday purposes, &#8220;tty&#8221; and &#8220;terminal&#8221; mean the same thing. The difference only shows up if you&#8217;re writing systems-level code or debugging something weird with signals.<\/p>\n<h3 id=\"pty-pseudo-terminal\">PTY (Pseudo Terminal)<a href=\"#pty-pseudo-terminal\" class=\"heading-link\">#<\/a><\/h3>\n<p>A PTY is a virtual terminal device used by terminal emulators. In practice, two device files operate as a pair:<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510         \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  PTY Manager    \u2502         \u2502  PTY Subsidiary\r\n  \u2502  (master side)  \u2502\u25c4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2502  (slave side)\r\n  \u2502                 \u2502\r\n  \u2502  Operated by    \u2502         \u2502  Where shells &amp;\r\n  \u2502  the terminal   \u2502         \u2502  TUI apps\r\n  \u2502  emulator       \u2502         \u2502  connect\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518         \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>In POSIX.1-2024, the master side is called &#8220;manager&#8221; and the slave side is called &#8220;subsidiary.&#8221;<\/p>\n<p>Since shells and TUI apps treat the subsidiary side as a &#8220;real terminal,&#8221; they can use the same API (termios, etc.) as physical terminals. Applications don&#8217;t need to know whether they&#8217;re talking to a physical or virtual terminal.<\/p>\n<p>Device file examples:<\/p>\n<ul>\n<li>Linux: <code>\/dev\/pts\/0<\/code>, <code>\/dev\/pts\/1<\/code>&#8230;<\/li>\n<li>macOS: <code>\/dev\/ttys000<\/code>, <code>\/dev\/ttys001<\/code>&#8230;<\/li>\n<\/ul>\n<h3 id=\"tui-text-user-interface\">TUI (Text User Interface)<a href=\"#tui-text-user-interface\" class=\"heading-link\">#<\/a><\/h3>\n<p>A TUI is a character-based interface that provides an interactive UI using the entire screen. Unlike a regular CLI (shell) that executes commands line by line, a TUI controls the entire screen and achieves a rich user experience using cursor movement, colors, borders, menus, and forms.<\/p>\n<p>Think of <code>vim<\/code>, <code>less<\/code>, <code>htop<\/code>, or <code>nmtui<\/code>. They take over your whole terminal window and redraw it constantly.<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2502  CLI (shell)               TUI (vim, htop)\r\n  \u2502\r\n  \u2502  $ ls -la                  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  total 48                  \u2502 htop - 3.2.1\r\n  \u2502  drwxr-xr-x  5 user ...    \u2502 CPU[||||||||  38.2%]\r\n  \u2502  -rw-r--r--  1 user ...    \u2502 Mem[|||||    512\/8G]\r\n  \u2502  $ _                       \u2502\r\n  \u2502                            \u2502 PID  USER  CPU%  MEM\r\n  \u2502  Text flows line by line   \u2502 1234 root  12.3  1.2\r\n  \u2502  downward. That's it.      \u2502 5678 user   8.1  0.9\r\n  \u2502                            \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502                            Full screen. Interactive.\r\n  \u2502                            Redraws constantly.\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>Here&#8217;s how CLI and TUI compare:<\/p>\n<table>\n<thead>\n<tr>\n<th>Aspect<\/th>\n<th>CLI (Shell)<\/th>\n<th>TUI (vim, htop)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Input<\/td>\n<td>Line-based (confirmed w\/ Enter)<\/td>\n<td>Key-based (immediate on press)<\/td>\n<\/tr>\n<tr>\n<td>Screen<\/td>\n<td>Text flows downward<\/td>\n<td>Full screen redrawn<\/td>\n<\/tr>\n<tr>\n<td>Cursor<\/td>\n<td>Moves to next line automatically<\/td>\n<td>Moves anywhere via ANSI escapes<\/td>\n<\/tr>\n<tr>\n<td>Echo<\/td>\n<td>On (chars shown as you type)<\/td>\n<td>Off (app draws its own UI)<\/td>\n<\/tr>\n<tr>\n<td>Terminal mode<\/td>\n<td>Canonical Mode<\/td>\n<td>Raw Mode<\/td>\n<\/tr>\n<tr>\n<td>Examples<\/td>\n<td>bash, zsh, fish, Python REPL<\/td>\n<td>vim, less, htop, nmtui<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr \/>\n<h2 id=\"part-2-the-plumbing-under-the-hood\">Part 2: The Plumbing Under the Hood<a href=\"#part-2-the-plumbing-under-the-hood\" class=\"heading-link\">#<\/a><\/h2>\n<p>This is where most articles stop. But if you ever want to build a TUI app, or even just understand what vim is doing to your terminal, you need to go one layer deeper.<\/p>\n<h3 id=\"posix-portable-operating-system-interface\">POSIX (Portable Operating System Interface)<a href=\"#posix-portable-operating-system-interface\" class=\"heading-link\">#<\/a><\/h3>\n<p>POSIX is a standard specification for maintaining compatibility across Unix-like systems. Established by IEEE, it defines APIs for system calls, terminal control, file I\/O, threads, and more. Most commands and system calls in macOS and Linux are POSIX-compliant.<\/p>\n<p>POSIX-compliant OSes include Linux (Ubuntu, Debian, Red Hat, etc.), macOS (Darwin), BSD systems (FreeBSD, OpenBSD), Solaris, and AIX. The latest specification is POSIX.1-2024 (IEEE Std 1003.1-2024).<\/p>\n<h3 id=\"posix-terminal-interface\">POSIX Terminal Interface<a href=\"#posix-terminal-interface\" class=\"heading-link\">#<\/a><\/h3>\n<p>The standard API defined by POSIX for terminal input\/output control. The <code>termios<\/code> structure and its related functions (<code>tcgetattr()<\/code>, <code>tcsetattr()<\/code>) are used to configure terminal modes, define special characters, and set baud rate. This standardization means the same code works across POSIX-compliant OSes.<\/p>\n<h3 id=\"unix-terminal-interface\">Unix Terminal Interface<a href=\"#unix-terminal-interface\" class=\"heading-link\">#<\/a><\/h3>\n<p>The traditional mechanism for terminal control in Unix-like OSes. The TTY driver manages terminal devices within the kernel, and the line discipline handles input\/output processing. POSIX is a standardization of this Unix terminal interface.<\/p>\n<h3 id=\"tty-line-discipline\">TTY Line Discipline<a href=\"#tty-line-discipline\" class=\"heading-link\">#<\/a><\/h3>\n<p>Line discipline is a software layer in the kernel that sits between the TTY driver and user processes, handling terminal input\/output processing.<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Terminal\r\n  \u2502  Emulator\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n           \u2502\r\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  PTY (master)\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n           \u2502\r\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  TTY Line Discipline\r\n  \u2502\r\n  \u2502  1. Line editing\r\n  \u2502     Backspace = delete char\r\n  \u2502     Ctrl+U   = delete entire line\r\n  \u2502     Ctrl+W   = delete word\r\n  \u2502\r\n  \u2502  2. Echo back\r\n  \u2502     Input chars automatically sent back to screen\r\n  \u2502     So the user can see what they're typing\r\n  \u2502\r\n  \u2502  3. Special character processing\r\n  \u2502     Ctrl+C  --&gt; SIGINT (kill process)\r\n  \u2502     Ctrl+Z  --&gt; SIGTSTP (suspend process)\r\n  \u2502     Ctrl+D  --&gt; EOF (end of input)\r\n  \u2502\r\n  \u2502  4. Character conversion\r\n  \u2502     Newline code conversion (CR &lt;--&gt; LF)\r\n  \u2502\r\n  \u2502  5. Input buffering\r\n  \u2502     Canonical mode:  buffer until Enter\r\n  \u2502     Non-canonical:   pass through immediately\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n           \u2502\r\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  PTY (subsidiary)\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n           \u2502\r\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Shell \/ TUI App\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>This is important to understand because TUI apps work by <em>disabling<\/em> most of these line discipline features. When you run vim, it tells the kernel: &#8220;stop doing line editing, stop echoing, stop interpreting Ctrl+C as a signal. Just give me the raw bytes.&#8221;<\/p>\n<h3 id=\"termios\">termios<a href=\"#termios\" class=\"heading-link\">#<\/a><\/h3>\n<p><code>termios<\/code> is the POSIX standard terminal control structure. It&#8217;s how you talk to the line discipline. It manages:<\/p>\n<pre class=\"lang:sh\">  termios structure\r\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2502  c_iflag  (Input flags)\r\n  \u2502    Newline conversion, flow control, etc.\r\n  \u2502\r\n  \u2502  c_oflag  (Output flags)\r\n  \u2502    Output processing settings\r\n  \u2502\r\n  \u2502  c_cflag  (Control flags)\r\n  \u2502    Baud rate, character size, etc.\r\n  \u2502\r\n  \u2502  c_lflag  (Local flags)\r\n  \u2502    Echo, canonical mode, signal generation, etc.\r\n  \u2502\r\n  \u2502  c_cc     (Special characters)\r\n  \u2502    Definitions for Ctrl+C, Ctrl+Z, EOF, etc.\r\n  \u2502\r\n  \u2502  VMIN \/ VTIME  (Timeout)\r\n  \u2502    Read control in non-canonical mode\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>In TUI development, termios is used to implement Raw Mode.<\/p>\n<h3 id=\"ioctl-input-output-control\">ioctl (Input\/Output Control)<a href=\"#ioctl-input-output-control\" class=\"heading-link\">#<\/a><\/h3>\n<p><code>ioctl<\/code> is a general-purpose device control system call. Through a file descriptor, it instructs device drivers to perform special operations that can&#8217;t be done with normal read\/write. Main operations include getting and setting termios, and getting window size.<\/p>\n<h3 id=\"tcgetattr-tcsetattr\">tcgetattr \/ tcsetattr<a href=\"#tcgetattr-tcsetattr\" class=\"heading-link\">#<\/a><\/h3>\n<p>High-level API functions for terminal control defined in the POSIX standard. They let you write more portable code than using <code>ioctl<\/code> directly.<\/p>\n<p>In Node.js, you don&#8217;t need to call these directly. The high-level equivalent is built in:<\/p>\n<pre class=\"lang:sh\">\/\/ High-level: Node.js handles termios internally\r\nprocess.stdin.setRawMode(true);  \/\/ Equivalent to MakeRaw \/ tcsetattr\r\nprocess.stdin.setRawMode(false); \/\/ Equivalent to Restore \/ tcsetattr\r\n<\/pre>\n<p>For low-level access to termios from JavaScript, you&#8217;d use a native addon or FFI. More on this in the implementation section.<\/p>\n<h2 id=\"the-full-i-o-flow\">The Full I\/O Flow<a href=\"#the-full-i-o-flow\" class=\"heading-link\">#<\/a><\/h2>\n<p>Here&#8217;s everything connected, from your fingers to the application and back:<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502 User \u2502\u2500\u2500\u2500\u2500&gt;\u2502 Terminal Emulator\r\n  \u2502      \u2502     \u2502 (iTerm2, xterm, etc.)\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                          \u2502\r\n               \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n               \u2502 PTY\r\n               \u2502 (manager &lt;-&gt; subsid.)\r\n               \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                          \u2502\r\n               \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n               \u2502 TTY + Line Discipline\r\n               \u2502 (ICANON\/ECHO\/ISIG flags)\r\n               \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                          \u2502\r\n               \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n               \u2502 TUI App\r\n               \u2502 (vim, htop)\r\n               \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                          \u2502\r\n          \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n          \u2502\r\n          \u25bc               \u25bc               \u25bc\r\n   termios config    Output (ANSI     Rendering\r\n   changes           escape seqs)     on screen\r\n   (Raw Mode, etc.)  back to          via Terminal\r\n                     Terminal          Emulator\r\n<\/pre>\n<hr \/>\n<h2 id=\"part-3-terminal-behavior-for-tui-development\">Part 3: Terminal Behavior for TUI Development<a href=\"#part-3-terminal-behavior-for-tui-development\" class=\"heading-link\">#<\/a><\/h2>\n<p>TUI apps don&#8217;t interpret commands like shells do. Instead, they directly handle terminal I\/O control. They use the termios API to change terminal mode settings and ANSI escape sequences to render and update the screen.<\/p>\n<p>In a normal shell environment, the terminal is set to canonical mode (<code>ICANON<\/code>), where input is buffered line by line and passed to the program after you press Enter.<\/p>\n<pre class=\"lang:sh\">$ stty -a | grep icanon\r\n# icanon isig iexten echo echoe echok echoke -echonl echoctl\r\n# ^ Canonical mode is ON\r\n<\/pre>\n<p>Characters you type are automatically echoed to the screen and sent to the shell when Enter is pressed:<\/p>\n<pre class=\"lang:sh\">$ ls\r\nexample.txt\r\n<\/pre>\n<p>But TUI apps like vim or less switch the terminal to non-canonical mode. Key input is passed to the application immediately, character by character, and the application handles its own rendering.<\/p>\n<pre class=\"lang:sh\">$ vim\r\n\r\n# In another terminal, check vim's terminal mode:\r\n$ ps aux | grep vim         # find vim's terminal\r\n$ stty -a &lt; \/dev\/ttys049 | grep icanon\r\n# -icanon -isig -iexten -echo -echoe echok echoke -echonl echoctl\r\n#  ^ Canonical mode is OFF. Echo is OFF. Signals are OFF.\r\n<\/pre>\n<h2 id=\"the-five-elements-of-tui-development\">The Five Elements of TUI Development<a href=\"#the-five-elements-of-tui-development\" class=\"heading-link\">#<\/a><\/h2>\n<p>To build a TUI app, you need to understand and control five things:<\/p>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2502  1. Terminal Mode Settings\r\n  \u2502     Canonical --&gt; Raw Mode\r\n  \u2502\r\n  \u2502  2. Input Processing\r\n  \u2502     Parsing keys, escape sequences, Ctrl+\r\n  \u2502\r\n  \u2502  3. Screen Control\r\n  \u2502     ANSI escape sequences for rendering\r\n  \u2502\r\n  \u2502  4. Terminal Size Management\r\n  \u2502     Detecting and responding to resizes\r\n  \u2502\r\n  \u2502  5. Buffering\r\n  \u2502     Batch writes to prevent flicker\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>Let&#8217;s go through each one.<\/p>\n<hr \/>\n<h2 id=\"1-terminal-mode-settings\">1. Terminal Mode Settings<a href=\"#1-terminal-mode-settings\" class=\"heading-link\">#<\/a><\/h2>\n<p>The line discipline has three operating modes. You switch between them by setting flags in the termios structure.<\/p>\n<h3 id=\"canonical-mode-cooked-mode\">Canonical Mode (Cooked Mode)<a href=\"#canonical-mode-cooked-mode\" class=\"heading-link\">#<\/a><\/h3>\n<p>This is the default. It&#8217;s what your shell uses. Input is buffered line by line, line editing works (Backspace, Ctrl+U, Ctrl+W), echo is on, and special characters like Ctrl+C generate signals.<\/p>\n<h3 id=\"non-canonical-mode\">Non-Canonical Mode<a href=\"#non-canonical-mode\" class=\"heading-link\">#<\/a><\/h3>\n<p>Canonical mode disabled (<code>ICANON<\/code> off). Input arrives character by character instead of line by line. Line editing is off. But echo and signal processing can optionally stay enabled.<\/p>\n<h3 id=\"raw-mode\">Raw Mode<a href=\"#raw-mode\" class=\"heading-link\">#<\/a><\/h3>\n<p>Almost everything disabled. No echo, no signals, no newline conversion. Input is passed to the application as a raw byte stream. This is what TUI apps use.<\/p>\n<table>\n<thead>\n<tr>\n<th>Aspect<\/th>\n<th>Canonical (Cooked)<\/th>\n<th>Non-Canonical<\/th>\n<th>Raw<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Input buffering<\/td>\n<td>Line-based<\/td>\n<td>Char-based<\/td>\n<td>Char-based<\/td>\n<\/tr>\n<tr>\n<td>Line editing<\/td>\n<td>Enabled<\/td>\n<td>Disabled<\/td>\n<td>Disabled<\/td>\n<\/tr>\n<tr>\n<td>Echo back<\/td>\n<td>Enabled<\/td>\n<td>Configurable<\/td>\n<td>Disabled<\/td>\n<\/tr>\n<tr>\n<td>Signals (Ctrl+C)<\/td>\n<td>Enabled<\/td>\n<td>Configurable<\/td>\n<td>Disabled<\/td>\n<\/tr>\n<tr>\n<td>Newline conv.<\/td>\n<td>Enabled<\/td>\n<td>Configurable<\/td>\n<td>Disabled<\/td>\n<\/tr>\n<tr>\n<td>Main uses<\/td>\n<td>Shell, interactive<\/td>\n<td>Custom CLI<\/td>\n<td>TUI, games<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><code>stty<\/code>&#8216;s <code>-cooked<\/code> is synonymous with <code>raw<\/code>. Raw Mode is the opposite of Cooked Mode.<\/p>\n<hr \/>\n<h2 id=\"2-input-processing\">2. Input Processing<a href=\"#2-input-processing\" class=\"heading-link\">#<\/a><\/h2>\n<p>Input processing means figuring out &#8220;what key was pressed.&#8221; Normal characters are simple, but arrow keys, function keys, and mouse events are sent as multi-byte escape sequences. Your TUI app needs to parse these.<\/p>\n<h3 id=\"special-key-escape-sequences\">Special Key Escape Sequences<a href=\"#special-key-escape-sequences\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Key         \u2502  Sequence    \u2502  Byte Sequence\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Up    (\u2191)   \u2502  ESC[A       \u2502  \\x1b[A\r\n  \u2502  Down  (\u2193)   \u2502  ESC[B       \u2502  \\x1b[B\r\n  \u2502  Right (\u2192)   \u2502  ESC[C       \u2502  \\x1b[C\r\n  \u2502  Left  (\u2190)   \u2502  ESC[D       \u2502  \\x1b[D\r\n  \u2502  Home        \u2502  ESC[H       \u2502  \\x1b[H\r\n  \u2502  End         \u2502  ESC[F       \u2502  \\x1b[F\r\n  \u2502  Page Up     \u2502  ESC[5~      \u2502  \\x1b[5~\r\n  \u2502  Page Down   \u2502  ESC[6~      \u2502  \\x1b[6~\r\n  \u2502  F1-F4       \u2502  ESC[OP-OS   \u2502  \\x1b[OP, etc.\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<h3 id=\"control-characters\">Control Characters<a href=\"#control-characters\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Character   \u2502  ASCII   \u2502  Description\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Ctrl+C      \u2502  3       \u2502  SIGINT\r\n  \u2502  Ctrl+D      \u2502  4       \u2502  EOF\r\n  \u2502  Ctrl+Z      \u2502  26      \u2502  SIGTSTP\r\n  \u2502  Enter       \u2502  13 \/ 10 \u2502  CR \/ LF\r\n  \u2502  Tab         \u2502  9       \u2502  Tab\r\n  \u2502  Backspace   \u2502  127 \/ 8 \u2502  DEL \/ BS\r\n  \u2502  ESC         \u2502  27      \u2502  Escape\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<hr \/>\n<h2 id=\"3-screen-control-ansi-escape-sequences\">3. Screen Control (ANSI Escape Sequences)<a href=\"#3-screen-control-ansi-escape-sequences\" class=\"heading-link\">#<\/a><\/h2>\n<p>Screen control means telling the terminal &#8220;what to display where.&#8221; ANSI escape sequences are special string commands that begin with the ESC character (<code>\\x1b<\/code> or <code>\\033<\/code>). They were standardized as ANSI X3.64 and implemented in VT100 terminals, which is why they&#8217;re everywhere.<\/p>\n<h3 id=\"cursor-control\">Cursor Control<a href=\"#cursor-control\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Sequence            \u2502  Description\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  ESC[H               \u2502  Move to home position (1,1)\r\n  \u2502  ESC[{row};{col}H    \u2502  Move to position (1-indexed)\r\n  \u2502  ESC[{n}A            \u2502  Move n rows up\r\n  \u2502  ESC[{n}B            \u2502  Move n rows down\r\n  \u2502  ESC[{n}C            \u2502  Move n columns right\r\n  \u2502  ESC[{n}D            \u2502  Move n columns left\r\n  \u2502  ESC[s               \u2502  Save cursor position\r\n  \u2502  ESC[u               \u2502  Restore cursor position\r\n  \u2502  ESC[?25l            \u2502  Hide cursor\r\n  \u2502  ESC[?25h            \u2502  Show cursor\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<h3 id=\"screen-clearing\">Screen Clearing<a href=\"#screen-clearing\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Sequence            \u2502  Description\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  ESC[2J              \u2502  Clear entire screen\r\n  \u2502  ESC[H               \u2502  Move cursor to home\r\n  \u2502  ESC[K               \u2502  Clear from cursor to end of line\r\n  \u2502  ESC[1K              \u2502  Clear from start of line to cur.\r\n  \u2502  ESC[2K              \u2502  Clear entire line\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<h3 id=\"basic-styles\">Basic Styles<a href=\"#basic-styles\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Sequence            \u2502  Description\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  ESC[0m              \u2502  Reset all\r\n  \u2502  ESC[1m              \u2502  Bold\r\n  \u2502  ESC[4m              \u2502  Underline\r\n  \u2502  ESC[7m              \u2502  Reverse\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<h3 id=\"foreground-colors-text\">Foreground Colors (Text)<a href=\"#foreground-colors-text\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Sequence            \u2502  Color\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  ESC[30m             \u2502  Black\r\n  \u2502  ESC[31m             \u2502  Red\r\n  \u2502  ESC[32m             \u2502  Green\r\n  \u2502  ESC[33m             \u2502  Yellow\r\n  \u2502  ESC[34m             \u2502  Blue\r\n  \u2502  ESC[35m             \u2502  Magenta\r\n  \u2502  ESC[36m             \u2502  Cyan\r\n  \u2502  ESC[37m             \u2502  White\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<h3 id=\"background-colors\">Background Colors<a href=\"#background-colors\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Sequence            \u2502  Color\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  ESC[40m             \u2502  Black\r\n  \u2502  ESC[41m             \u2502  Red\r\n  \u2502  ESC[42m             \u2502  Green\r\n  \u2502  ESC[43m             \u2502  Yellow\r\n  \u2502  ESC[44m             \u2502  Blue\r\n  \u2502  ESC[45m             \u2502  Magenta\r\n  \u2502  ESC[46m             \u2502  Cyan\r\n  \u2502  ESC[47m             \u2502  White\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<h3 id=\"extended-color-modes\">Extended Color Modes<a href=\"#extended-color-modes\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Sequence                    \u2502  Description\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  ESC[38;5;{n}m               \u2502  Foreground (n: 0-255)\r\n  \u2502  ESC[48;5;{n}m               \u2502  Background (n: 0-255)\r\n  \u2502  ESC[38;2;{r};{g};{b}m       \u2502  Foreground (RGB True Color)\r\n  \u2502  ESC[48;2;{r};{g};{b}m       \u2502  Background (RGB True Color)\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<h3 id=\"alternate-screen-buffer\">Alternate Screen Buffer<a href=\"#alternate-screen-buffer\" class=\"heading-link\">#<\/a><\/h3>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  Sequence            \u2502  Description\r\n  \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  ESC[?1049h          \u2502  Switch to alternate screen buffer\r\n  \u2502                      \u2502  (used by vim, less, etc.)\r\n  \u2502  ESC[?1049l          \u2502  Return to normal screen buffer\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n<\/pre>\n<p>This is why vim can take over your entire screen and then your terminal looks normal after you quit. It switches to an alternate buffer on startup and switches back on exit.<\/p>\n<hr \/>\n<h2 id=\"4-terminal-size-management\">4. Terminal Size Management<a href=\"#4-terminal-size-management\" class=\"heading-link\">#<\/a><\/h2>\n<p>TUI apps need to render according to terminal size, and they need to redraw when users resize the window. Here&#8217;s how that works:<\/p>\n<pre class=\"lang:sh\">  User resizes window\r\n        \u2502\r\n        \u25bc\r\n  Terminal Emulator\r\n        \u2502\r\n        \u2502  ioctl(TIOCSWINSZ) -- notify new rows\/cols\r\n        \u25bc\r\n  PTY (master --&gt; subsidiary)\r\n        \u2502\r\n        \u2502  Update winsize in kernel\r\n        \u25bc\r\n  Kernel TTY Layer\r\n        \u2502\r\n        \u2502  Send SIGWINCH signal\r\n        \u25bc\r\n  Process (bash, vim, less)\r\n        \u2502\r\n        \u2502  Receive signal\r\n        \u2502  Call ioctl(TIOCGWINSZ) to get new size\r\n        \u2502  Redraw\r\n        \u25bc\r\n  Updated screen\r\n<\/pre>\n<p>Terminal size is managed by the <code>winsize<\/code> structure held in the kernel&#8217;s TTY structure. When the size changes, the terminal emulator notifies via <code>ioctl(TIOCSWINSZ)<\/code>, and the kernel sends <code>SIGWINCH<\/code> to connected processes.<\/p>\n<hr \/>\n<h2 id=\"5-buffering\">5. Buffering<a href=\"#5-buffering\" class=\"heading-link\">#<\/a><\/h2>\n<p>When you&#8217;re sending many control sequences, sending them one by one is slow and causes visible flicker. By buffering all your writes and flushing them in one batch, you get smooth, flicker-free rendering. Every serious TUI app does this.<\/p>\n<hr \/>\n<h2 id=\"part-4-building-a-tui-in-javascript-typescript\">Part 4: Building a TUI in JavaScript\/TypeScript<a href=\"#part-4-building-a-tui-in-javascript-typescript\" class=\"heading-link\">#<\/a><\/h2>\n<p>Now let&#8217;s actually build one. We&#8217;ll implement all five elements using Node.js.<\/p>\n<h2 id=\"high-level-implementation\">High-Level Implementation<a href=\"#high-level-implementation\" class=\"heading-link\">#<\/a><\/h2>\n<p>This uses Node.js&#8217;s built-in <code>process.stdin.setRawMode()<\/code>. It abstracts away termios details.<\/p>\n<pre class=\"lang:typescript\">\/\/ Structure to manage terminal state\r\ninterface Terminal {\r\n  width: number;\r\n  height: number;\r\n  buffer: string;\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ 1. Terminal Mode Settings\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfunction initTerminal(): Terminal {\r\n  \/\/ Set to Raw Mode (equivalent to term.MakeRaw in Go)\r\n  process.stdin.setRawMode(true);\r\n  process.stdin.resume();\r\n  process.stdin.setEncoding(\"utf8\");\r\n\r\n  const { columns, rows } = process.stdout;\r\n\r\n  return {\r\n    width: columns || 80,\r\n    height: rows || 24,\r\n    buffer: \"\",\r\n  };\r\n}\r\n\r\nfunction restoreTerminal(): void {\r\n  write(\"\\x1b[?25h\"); \/\/ Show cursor\r\n  write(\"\\x1b[0m\");   \/\/ Reset color\r\n  flush();\r\n  process.stdin.setRawMode(false);\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ 4. Terminal Size Management\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfunction getTerminalSize(): { width: number; height: number } {\r\n  return {\r\n    width: process.stdout.columns || 80,\r\n    height: process.stdout.rows || 24,\r\n  };\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ 2. Input Processing\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\ninterface KeyEvent {\r\n  char: string | null;\r\n  key: string | null;\r\n}\r\n\r\nfunction parseKey(data: string): KeyEvent {\r\n  \/\/ Ctrl+C\r\n  if (data === \"\\x03\") {\r\n    return { char: null, key: \"CTRL_C\" };\r\n  }\r\n\r\n  \/\/ Escape sequences (arrow keys, etc.)\r\n  if (data === \"\\x1b[A\") return { char: null, key: \"UP\" };\r\n  if (data === \"\\x1b[B\") return { char: null, key: \"DOWN\" };\r\n  if (data === \"\\x1b[C\") return { char: null, key: \"RIGHT\" };\r\n  if (data === \"\\x1b[D\") return { char: null, key: \"LEFT\" };\r\n  if (data === \"\\x1b[H\") return { char: null, key: \"HOME\" };\r\n  if (data === \"\\x1b[F\") return { char: null, key: \"END\" };\r\n  if (data === \"\\x1b[5~\") return { char: null, key: \"PAGE_UP\" };\r\n  if (data === \"\\x1b[6~\") return { char: null, key: \"PAGE_DOWN\" };\r\n\r\n  \/\/ Bare ESC\r\n  if (data === \"\\x1b\") return { char: null, key: \"ESC\" };\r\n\r\n  \/\/ Normal character\r\n  return { char: data, key: null };\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ 3. Screen Control (ANSI Escape Sequences)\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nlet outputBuffer = \"\";\r\n\r\nfunction bufferWrite(s: string): void {\r\n  outputBuffer += s;\r\n}\r\n\r\nfunction clear(): void {\r\n  bufferWrite(\"\\x1b[2J\\x1b[H\");\r\n}\r\n\r\nfunction moveTo(row: number, col: number): void {\r\n  bufferWrite(`\\x1b[${row};${col}H`);\r\n}\r\n\r\nfunction setColor(fg: number): void {\r\n  bufferWrite(`\\x1b[${fg}m`);\r\n}\r\n\r\nfunction writeText(s: string): void {\r\n  bufferWrite(s);\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ 5. Buffering\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfunction flush(): void {\r\n  process.stdout.write(outputBuffer);\r\n  outputBuffer = \"\";\r\n}\r\n\r\nfunction write(s: string): void {\r\n  process.stdout.write(s);\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Main\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfunction main(): void {\r\n  \/\/ Check if running in a terminal\r\n  if (!process.stdin.isTTY) {\r\n    process.stderr.write(\"Error: Must be run in an interactive terminal\\n\");\r\n    process.exit(1);\r\n  }\r\n\r\n  \/\/ Initialize\r\n  const term = initTerminal();\r\n\r\n  \/\/ Always restore on exit\r\n  process.on(\"exit\", restoreTerminal);\r\n  process.on(\"SIGINT\", () =&gt; {\r\n    restoreTerminal();\r\n    process.exit(0);\r\n  });\r\n\r\n  \/\/ 4. Detect window size changes (SIGWINCH)\r\n  process.on(\"SIGWINCH\", () =&gt; {\r\n    const size = getTerminalSize();\r\n    term.width = size.width;\r\n    term.height = size.height;\r\n  });\r\n\r\n  \/\/ Cursor position\r\n  let x = Math.floor(term.width \/ 2);\r\n  let y = Math.floor(term.height \/ 2);\r\n\r\n  function render(): void {\r\n    \/\/ Clear screen\r\n    clear();\r\n\r\n    \/\/ Title\r\n    moveTo(1, Math.floor(term.width \/ 2) - 10);\r\n    setColor(36); \/\/ Cyan\r\n    writeText(\"TUI Demo (press 'q' to quit)\");\r\n\r\n    \/\/ Display information\r\n    moveTo(3, 2);\r\n    setColor(33); \/\/ Yellow\r\n    writeText(`Terminal Size: ${term.width}x${term.height}`);\r\n\r\n    moveTo(4, 2);\r\n    writeText(`Cursor Position: (${x}, ${y})`);\r\n\r\n    \/\/ Draw frame\r\n    for (let row = 5; row &lt; term.height - 1; row++) { moveTo(row, 1); setColor(34); \/\/ Blue writeText(\"|\"); moveTo(row, term.width); writeText(\"|\"); } \/\/ Display cursor marker moveTo(y, x); setColor(32); \/\/ Green writeText(\"\u25cf\"); \/\/ Instructions moveTo(term.height, 2); setColor(37); \/\/ White writeText(\"Arrow keys: move | q: quit\"); \/\/ Flush buffer (reflect to screen all at once) flush(); } \/\/ Initial render render(); \/\/ 2. Listen for key input process.stdin.on(\"data\", (data: string) =&gt; {\r\n    const { char, key } = parseKey(data);\r\n\r\n    \/\/ Process key\r\n    switch (key) {\r\n      case \"CTRL_C\":\r\n        restoreTerminal();\r\n        process.exit(0);\r\n        return;\r\n      case \"UP\":\r\n        if (y &gt; 5) y--;\r\n        break;\r\n      case \"DOWN\":\r\n        if (y &lt; term.height - 1) y++; break; case \"LEFT\": if (x &gt; 2) x--;\r\n        break;\r\n      case \"RIGHT\":\r\n        if (x &lt; term.width - 1) x++;\r\n        break;\r\n    }\r\n\r\n    if (char === \"q\" || char === \"Q\") {\r\n      restoreTerminal();\r\n      process.exit(0);\r\n      return;\r\n    }\r\n\r\n    render();\r\n  });\r\n}\r\n\r\nmain();\r\n\r\n<\/pre>\n<p>Save this as program.ts and run it:<\/p>\n<pre class=\"lang:sh\">npx tsx program.ts<\/pre>\n<p>Operation: arrow keys move the cursor marker (\u25cf), <code>q<\/code> or <code>Ctrl+C<\/code> quits.<\/p>\n<hr \/>\n<h2 id=\"low-level-implementation-direct-termios-manipulation\">Low-Level Implementation: Direct termios Manipulation<a href=\"#low-level-implementation-direct-termios-manipulation\" class=\"heading-link\">#<\/a><\/h2>\n<p>The high-level version uses <code>process.stdin.setRawMode(true)<\/code>, which handles termios internally. To see what&#8217;s happening underneath, here&#8217;s a low-level version using Node.js FFI to directly manipulate termios flags.<\/p>\n<pre class=\"lang:typescript\">import { execSync } from \"child_process\";\r\nimport process from \"process\";\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Low-level termios manipulation via stty\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n\/\/ termios flag reference (what each flag controls):\r\n\/\/\r\n\/\/  Input flags (c_iflag):\r\n\/\/    IGNBRK   - Ignore break condition\r\n\/\/    BRKINT   - Signal interrupt on break\r\n\/\/    PARMRK   - Mark parity errors\r\n\/\/    ISTRIP   - Strip 8th bit\r\n\/\/    INLCR    - Translate NL to CR on input\r\n\/\/    IGNCR    - Ignore CR on input\r\n\/\/    ICRNL    - Translate CR to NL on input\r\n\/\/    IXON     - Enable XON\/XOFF flow control\r\n\/\/\r\n\/\/  Output flags (c_oflag):\r\n\/\/    OPOST    - Post-process output\r\n\/\/\r\n\/\/  Local flags (c_lflag):\r\n\/\/    ECHO     - Echo input characters\r\n\/\/    ECHONL   - Echo NL even if ECHO is off\r\n\/\/    ICANON   - Canonical mode (line buffering)\r\n\/\/    ISIG     - Enable signals (Ctrl+C, Ctrl+Z)\r\n\/\/    IEXTEN   - Extended input processing\r\n\/\/\r\n\/\/  Control flags (c_cflag):\r\n\/\/    CSIZE    - Character size mask\r\n\/\/    PARENB   - Parity enable\r\n\/\/    CS8      - 8 bits per character\r\n\r\nfunction saveTerminalState(): string {\r\n  \/\/ Save all current termios settings\r\n  return execSync(\"stty -g\", { stdio: [\"inherit\", \"pipe\", \"pipe\"], encoding: \"utf8\" }).trim();\r\n}\r\n\r\nfunction restoreTerminalState(savedState: string): void {\r\n  \/\/ Restore saved termios settings\r\n  execSync(`stty ${savedState}`, { stdio: \"inherit\" });\r\n}\r\n\r\nfunction enableRawMode(): void {\r\n  execSync(\"stty raw -echo -isig -icanon -iexten -opost min 1 time 0\", {\r\n    stdio: \"inherit\",\r\n  });\r\n}\r\n\r\nfunction getTerminalSize(): { width: number; height: number } {\r\n  \/\/ Get window size\r\n  try {\r\n    const output = execSync(\"stty size\", { stdio: [\"inherit\", \"pipe\", \"pipe\"], encoding: \"utf8\" }).trim();\r\n    const [rows, cols] = output.split(\" \").map(Number);\r\n    return { width: cols || 80, height: rows || 24 };\r\n  } catch {\r\n    return { width: 80, height: 24 }; \/\/ Default values\r\n  }\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Screen control and buffering (same as high-level)\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nlet outputBuffer = \"\";\r\n\r\nfunction bufferWrite(s: string): void {\r\n  outputBuffer += s;\r\n}\r\n\r\nfunction flush(): void {\r\n  process.stdout.write(outputBuffer);\r\n  outputBuffer = \"\";\r\n}\r\n\r\nfunction clear(): void {\r\n  bufferWrite(\"\\x1b[2J\\x1b[H\");\r\n}\r\n\r\nfunction moveTo(row: number, col: number): void {\r\n  bufferWrite(`\\x1b[${row};${col}H`);\r\n}\r\n\r\nfunction setColor(fg: number): void {\r\n  bufferWrite(`\\x1b[${fg}m`);\r\n}\r\n\r\nfunction writeText(s: string): void {\r\n  bufferWrite(s);\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Input processing (same parsing logic)\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\ninterface KeyEvent {\r\n  char: string | null;\r\n  key: string | null;\r\n}\r\n\r\nfunction parseKey(buf: Buffer): KeyEvent {\r\n  \/\/ Ctrl+C (in raw mode with -isig, we get byte 3 directly)\r\n  if (buf[0] === 3) {\r\n    return { char: null, key: \"CTRL_C\" };\r\n  }\r\n\r\n  \/\/ Escape sequences\r\n  if (buf[0] === 27 &amp;&amp; buf[1] === 91) {\r\n    \/\/ ESC[ = 0x1b 0x5b\r\n    switch (buf[2]) {\r\n      case 65: return { char: null, key: \"UP\" };    \/\/ A\r\n      case 66: return { char: null, key: \"DOWN\" };  \/\/ B\r\n      case 67: return { char: null, key: \"RIGHT\" }; \/\/ C\r\n      case 68: return { char: null, key: \"LEFT\" };  \/\/ D\r\n    }\r\n  }\r\n\r\n  if (buf[0] === 27) return { char: null, key: \"ESC\" };\r\n\r\n  \/\/ Normal character\r\n  return { char: String.fromCharCode(buf[0]), key: null };\r\n}\r\n\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\/\/ Main\r\n\/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfunction main(): void {\r\n  \/\/ Save original termios state\r\n  const savedState = saveTerminalState();\r\n\r\n  \/\/ Enable Raw Mode (direct termios flag manipulation)\r\n  enableRawMode();\r\n\r\n  \/\/ Read stdin as raw bytes (not utf8 strings)\r\n  process.stdin.resume();\r\n\r\n  \/\/ Always restore on exit\r\n  const cleanup = (): void =&gt; {\r\n    bufferWrite(\"\\x1b[?25h\"); \/\/ Show cursor\r\n    bufferWrite(\"\\x1b[0m\");   \/\/ Reset color\r\n    flush();\r\n    restoreTerminalState(savedState);\r\n  };\r\n\r\n  process.on(\"exit\", cleanup);\r\n\r\n  \/\/ Detect window size changes (SIGWINCH)\r\n  let { width, height } = getTerminalSize();\r\n  process.on(\"SIGWINCH\", () =&gt; {\r\n    const size = getTerminalSize();\r\n    width = size.width;\r\n    height = size.height;\r\n  });\r\n\r\n  \/\/ Cursor position\r\n  let x = Math.floor(width \/ 2);\r\n  let y = Math.floor(height \/ 2);\r\n\r\n  function render(): void {\r\n    clear();\r\n\r\n    \/\/ Title\r\n    moveTo(1, Math.floor(width \/ 2) - 15);\r\n    setColor(36); \/\/ Cyan\r\n    writeText(\"TUI Demo (Low-level termios API)\");\r\n\r\n    \/\/ termios info\r\n    moveTo(3, 2);\r\n    setColor(33); \/\/ Yellow\r\n    writeText(\"Using stty (tcgetattr\/tcsetattr under the hood)\");\r\n\r\n    moveTo(4, 2);\r\n    writeText(`Terminal Size: ${width}x${height}`);\r\n\r\n    moveTo(5, 2);\r\n    writeText(`Cursor Position: (${x}, ${y})`);\r\n\r\n    \/\/ Explanation of configured flags\r\n    moveTo(7, 2);\r\n    setColor(37); \/\/ White\r\n    writeText(\"Raw Mode flags:\");\r\n    moveTo(8, 4);\r\n    writeText(\"- ICANON off: Line buffering disabled\");\r\n    moveTo(9, 4);\r\n    writeText(\"- ECHO off: Echo back disabled\");\r\n    moveTo(10, 4);\r\n    writeText(\"- ISIG off: Signal generation disabled\");\r\n    moveTo(11, 4);\r\n    writeText(\"- VMIN=1, VTIME=0: Read 1 byte immediately\");\r\n\r\n    \/\/ Draw frame\r\n    for (let row = 13; row &lt; height - 1; row++) { moveTo(row, 1); setColor(34); \/\/ Blue writeText(\"|\"); moveTo(row, width); writeText(\"|\"); } \/\/ Cursor marker moveTo(y, x); setColor(32); \/\/ Green writeText(\"\u25cf\"); \/\/ Instructions moveTo(height, 2); setColor(37); \/\/ White writeText(\"Arrow keys: move | q: quit\"); flush(); } render(); \/\/ Read raw bytes process.stdin.on(\"data\", (data: Buffer) =&gt; {\r\n    const { char, key } = parseKey(data);\r\n\r\n    switch (key) {\r\n      case \"CTRL_C\":\r\n        cleanup();\r\n        process.exit(0);\r\n        return;\r\n      case \"UP\":\r\n        if (y &gt; 13) y--;\r\n        break;\r\n      case \"DOWN\":\r\n        if (y &lt; height - 1) y++; break; case \"LEFT\": if (x &gt; 2) x--;\r\n        break;\r\n      case \"RIGHT\":\r\n        if (x &lt; width - 1) x++;\r\n        break;\r\n    }\r\n\r\n    if (char === \"q\" || char === \"Q\") {\r\n      cleanup();\r\n      process.exit(0);\r\n      return;\r\n    }\r\n\r\n    render();\r\n  });\r\n}\r\n\r\nmain();\r\n<\/pre>\n<p>With this low-level implementation, you can see what each termios flag specifically controls, how POSIX&#8217;s <code>tcgetattr<\/code>\/<code>tcsetattr<\/code> map to <code>stty<\/code> commands, and what <code>process.stdin.setRawMode(true)<\/code> is doing for you behind the scenes. For true native access in Node.js without shelling out, you&#8217;d use <code>node-ffi-napi<\/code> to call libc&#8217;s <code>tcgetattr<\/code>\/<code>tcsetattr<\/code> directly, or a native addon.<\/p>\n<hr \/>\n<h2 id=\"the-full-picture\">The Full Picture<a href=\"#the-full-picture\" class=\"heading-link\">#<\/a><\/h2>\n<pre class=\"lang:sh\">  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502                     YOUR SCREEN\r\n  \u2502\r\n  \u2502  \u250c\u2500 Terminal (iTerm2, Alacritty, etc.) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502  \u2502\r\n  \u2502  \u2502  $ grep -r \"TODO\" .\/src\r\n  \u2502  \u2502  src\/app.js:  \/\/ TODO fix this\r\n  \u2502  \u2502  src\/util.js: \/\/ TODO refactor\r\n  \u2502  \u2502  $  _\r\n  \u2502  \u2502\r\n  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  \u2502\r\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                        \u2502\r\n              \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n              \u2502    TTY device     Kernel layer\r\n              \u2502   \/dev\/pts\/1      (the plumbing)\r\n              \u2502\r\n              \u2502   Line Discipline\r\n              \u2502   termios flags\r\n              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                        \u2502\r\n              \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n              \u2502      Shell       Interprets commands\r\n              \u2502    (zsh, bash)   Runs programs\r\n              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n                        \u2502\r\n              \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n              \u2502   grep process   The actual program\r\n              \u2502                  your shell launched\r\n              \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n  And if all of this is on a monitor + keyboard\r\n  plugged directly into the machine?\r\n\r\n  Then the terminal is also the CONSOLE.\r\n<\/pre>\n<hr \/>\n<h2 id=\"quick-reference-when-things-break\">Quick Reference: When Things Break<a href=\"#quick-reference-when-things-break\" class=\"heading-link\">#<\/a><\/h2>\n<pre class=\"lang:sh\">  Problem                          Which layer?\r\n  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  Colors look wrong                Terminal\r\n  Tab completion broke             Shell\r\n  SSH session acting strange       TTY\r\n  GUI is dead, need to log in      Console\r\n  Script works in bash not zsh     Shell\r\n  Font rendering is ugly           Terminal\r\n  Ctrl+C not killing process       TTY \/ Line Discipline\r\n  TUI app not getting keypresses   termios (Raw Mode)\r\n  Screen flickers on redraw        Buffering\r\n  Layout breaks on window resize   SIGWINCH handling\r\n<\/pre>\n<hr \/>\n<h2 id=\"summary-what-we-covered\">Summary: What We Covered<a href=\"#summary-what-we-covered\" class=\"heading-link\">#<\/a><\/h2>\n<p>This was a summarized view of the full stack of terminal knowledge, from vocabulary to implementation:<\/p>\n<p><strong>Terminology and concepts<\/strong>: the relationships between Terminal, Shell, TTY, Console, Line Discipline, termios, PTY, POSIX, and how they all fit together.<\/p>\n<p><strong>Five elements of TUI development<\/strong>: terminal mode settings (Canonical\/Non-Canonical\/Raw), input processing (parsing escape sequences and control characters), screen control (ANSI escape sequences for cursor, color, clearing, alternate screen buffer), terminal size management (SIGWINCH), and buffering (preventing flicker).<\/p>\n<p><strong>Implementation<\/strong>: a high-level Node.js TUI using <code>process.stdin.setRawMode()<\/code>, and a low-level version directly manipulating termios flags via <code>stty<\/code> to show what the high-level API does underneath. Both demonstrate the same five elements.<\/p>\n<p>Understanding these layers means you know where to look when something breaks. And knowing where to look is half of debugging.<\/p>\n<hr \/>\n<h2 id=\"references\">References<a href=\"#references\" class=\"heading-link\">#<\/a><\/h2>\n<h3 id=\"specifications-and-standards\">Specifications and Standards<a href=\"#specifications-and-standards\" class=\"heading-link\">#<\/a><\/h3>\n<ul>\n<li><a href=\"https:\/\/pubs.opengroup.org\/onlinepubs\/9799919799\/basedefs\/V1_chap11.html\">POSIX.1-2024 &#8211; General Terminal Interface<\/a><\/li>\n<li>termios(3) &#8211; Linux manual page<\/li>\n<li><a href=\"https:\/\/docs.kernel.org\/driver-api\/tty\/tty_ldisc.html\">TTY Line Discipline &#8211; Linux Kernel Documentation<\/a><\/li>\n<\/ul>\n<h3 id=\"wikipedia\">Wikipedia<a href=\"#wikipedia\" class=\"heading-link\">#<\/a><\/h3>\n<ul>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Computer_terminal\">Computer terminal<\/a><\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Terminal_emulator\">Terminal emulator<\/a><\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Text-based_user_interface\">Text-based user interface<\/a><\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/POSIX_terminal_interface\">POSIX terminal interface<\/a><\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Pseudoterminal\">Pseudoterminal<\/a><\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/ANSI_escape_code\">ANSI escape code<\/a><\/li>\n<\/ul>\n<h3 id=\"tutorials-and-documentation\">Tutorials and Documentation<a href=\"#tutorials-and-documentation\" class=\"heading-link\">#<\/a><\/h3>\n<ul>\n<li><a href=\"https:\/\/en.wikibooks.org\/wiki\/Serial_Programming\/termios\">Serial Programming\/termios &#8211; Wikibooks<\/a><\/li>\n<li><a href=\"https:\/\/nodejs.org\/api\/tty.html\">Node.js TTY documentation<\/a><\/li>\n<li><a href=\"https:\/\/man7.org\/linux\/man-pages\/man2\/ioctl.2.html\">ioctl(2) &#8211; Linux manual page<\/a><\/li>\n<\/ul>\n<h3 id=\"courses\">Courses<a href=\"#courses\" class=\"heading-link\">#<\/a><\/h3>\n<p>I also happen to have a course on building Node.js based CLIs with 20+ projects. You can check it out at <a href=\"https:\/\/NodeCLI.com\">NodeCLI.com<\/a>, the first 12 videos are also available for free in the <a href=\"https:\/\/NodejsBeginner.com\">NodejsBeginner.com<\/a> course. The entire course exercises are open source in this <a href=\"https:\/\/github.com\/ahmadawais\/Node-CLI-Tips-Tricks\">Node.js CLI Tips and Tricks<\/a> repo. I recorded it in 2020, but most of the info should hold true.<\/p>\n<h2 id=\"whats-next\">What&#8217;s next?<a href=\"#whats-next\" class=\"heading-link\">#<\/a><\/h2>\n<p>Check out hundreds of open source CLIs I have published on my <a href=\"https:\/\/github.com\/AhmadAwais\">GitHub account<\/a> and we&#8217;re about to launch <a href=\"https:\/\/commandcode.ai\">Command Code<\/a>, the first coding agent that can learn your coding taste as you use it. And yes, it&#8217;s a CLI as well.<\/p>\n<p>Use your code for good!<br \/>\nPeace! \u270c\ufe0f<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most developers use a terminal for years before realizing they don&#8217;t actually know what a terminal is. They know how to use one. They couldn&#8217;t define one. This is fine, in the same way that most people drive cars without understanding combustion. But every so often, something breaks in a way that makes the distinction matter, and then you&#8217;re stuck. The terms &#8220;terminal,&#8221; &#8220;shell,&#8221; &#8220;tty,&#8221; and &#8220;console&#8221; get used interchangeably. They shouldn&#8217;t be, but the fact that they are tells you something interesting: these concepts are so tightly coupled in practice that generations of developers have gotten by without separating [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":8222,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"webmentions_disabled_pings":false,"webmentions_disabled":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[298],"tags":[297,262],"class_list":["post-8193","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-code","tag-ai","tag-cli"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes<\/title>\n<meta name=\"description\" content=\"Most developers use a terminal for years before realizing they don&#039;t actually know what a terminal is. They know how to use one. They couldn&#039;t define one.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes\" \/>\n<meta property=\"og:description\" content=\"Most developers use a terminal for years before realizing they don&#039;t actually know what a terminal is. They know how to use one. They couldn&#039;t define one.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/\" \/>\n<meta property=\"og:site_name\" content=\"Ahmad Awais\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/facebook.com\/AhmadAwais\" \/>\n<meta property=\"article:author\" content=\"https:\/\/facebook.com\/AhmadAwais\" \/>\n<meta property=\"article:published_time\" content=\"2026-02-17T16:06:08+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-17T18:58:11+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ahmadawais.com\/wp-content\/uploads\/2026\/02\/terminals.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Ahmad Awais\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@MrAhmadAwais\" \/>\n<meta name=\"twitter:site\" content=\"@MrAhmadAwais\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ahmad Awais\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/\"},\"author\":{\"name\":\"Ahmad Awais\",\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/#\\\/schema\\\/person\\\/1d1b9504182dca2315cf039fb4ebb85b\"},\"headline\":\"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\\\/Canonical Modes\",\"datePublished\":\"2026-02-17T16:06:08+00:00\",\"dateModified\":\"2026-02-17T18:58:11+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/\"},\"wordCount\":2549,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/#\\\/schema\\\/person\\\/1d1b9504182dca2315cf039fb4ebb85b\"},\"image\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/terminals.png\",\"keywords\":[\"AI\",\"cli\"],\"articleSection\":[\"Code\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/\",\"url\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/\",\"name\":\"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\\\/Canonical Modes\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/terminals.png\",\"datePublished\":\"2026-02-17T16:06:08+00:00\",\"dateModified\":\"2026-02-17T18:58:11+00:00\",\"description\":\"Most developers use a terminal for years before realizing they don't actually know what a terminal is. They know how to use one. They couldn't define one.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#primaryimage\",\"url\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/terminals.png\",\"contentUrl\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2026\\\/02\\\/terminals.png\",\"width\":1200,\"height\":630,\"caption\":\"Terminals\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/ahmadawais.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\\\/Canonical Modes\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/#website\",\"url\":\"https:\\\/\\\/ahmadawais.com\\\/\",\"name\":\"Ahmad Awais\",\"description\":\"Founder &amp; CEO of CommandCode.ai f\\\/k\\\/a Langbase | Google Developers Advisory Board (gDAB) founding member\",\"publisher\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/#\\\/schema\\\/person\\\/1d1b9504182dca2315cf039fb4ebb85b\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/ahmadawais.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/#\\\/schema\\\/person\\\/1d1b9504182dca2315cf039fb4ebb85b\",\"name\":\"Ahmad Awais\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/Ahmad-Awais-face.jpg\",\"url\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/Ahmad-Awais-face.jpg\",\"contentUrl\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/Ahmad-Awais-face.jpg\",\"width\":2299,\"height\":1705,\"caption\":\"Ahmad Awais\"},\"logo\":{\"@id\":\"https:\\\/\\\/ahmadawais.com\\\/wp-content\\\/uploads\\\/2020\\\/06\\\/Ahmad-Awais-face.jpg\"},\"description\":\"Founder & CEO of \u2318 Command Code coding agent with taste. Founded Langbase.com, AI cloud to build, deploy, and scale AI agents with tools & memory \u00b7 Creator of Command.new. \\\"Awais is an awesome example for developers\\\" \u2014 Satya Nadella, CEO of Microsoft. NASA Mars Ingenuity Helicopter mission code contributor 8th GitHub Stars Award recipient with 5x GitHub Stars Award (Listed as GitHub's #1 JavaScript trending developer). Google Developers Expert Web DevRel. Ex VP Eng (DevTools & DevRel) Rapid \u00b7 Google Developers Advisory Board (gDAB) founding member \u00b7 AI\\\/ML\\\/DevTools Angel Investor (Replit, Resend, Daytona, Gumroad and you?) \u276f AI\\\/ML Advisory Board San Francisco, DevNetwork. Award-winning Open Source Engineering leader authored hundreds of open-source dev-tools and software libraries used by millions of developers, including Shades of Purple code theme and corona-cli. Linux Foundation (Node.js Committee Lead), OpenAPI Business Governing Board. Taught 108K+ developers via NodeCLI.com and VSCode.pro course. 274 million views, blogging for 24 yrs. \u276f Read more about Ahmad Awais or come say hi on \ud835\udd4f @MrAhmadAwais.\",\"sameAs\":[\"https:\\\/\\\/AhmadAwais.com\\\/\",\"https:\\\/\\\/facebook.com\\\/AhmadAwais\",\"https:\\\/\\\/instagram.com\\\/MrAhmadAwais\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/MrAhmadAwais\\\/\",\"https:\\\/\\\/x.com\\\/MrAhmadAwais\",\"https:\\\/\\\/youtube.com\\\/AhmadAwais\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes","description":"Most developers use a terminal for years before realizing they don't actually know what a terminal is. They know how to use one. They couldn't define one.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/","og_locale":"en_US","og_type":"article","og_title":"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes","og_description":"Most developers use a terminal for years before realizing they don't actually know what a terminal is. They know how to use one. They couldn't define one.","og_url":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/","og_site_name":"Ahmad Awais","article_publisher":"https:\/\/facebook.com\/AhmadAwais","article_author":"https:\/\/facebook.com\/AhmadAwais","article_published_time":"2026-02-17T16:06:08+00:00","article_modified_time":"2026-02-17T18:58:11+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2026\/02\/terminals.png","type":"image\/png"}],"author":"Ahmad Awais","twitter_card":"summary_large_image","twitter_creator":"@MrAhmadAwais","twitter_site":"@MrAhmadAwais","twitter_misc":{"Written by":"Ahmad Awais","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#article","isPartOf":{"@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/"},"author":{"name":"Ahmad Awais","@id":"https:\/\/ahmadawais.com\/#\/schema\/person\/1d1b9504182dca2315cf039fb4ebb85b"},"headline":"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes","datePublished":"2026-02-17T16:06:08+00:00","dateModified":"2026-02-17T18:58:11+00:00","mainEntityOfPage":{"@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/"},"wordCount":2549,"commentCount":0,"publisher":{"@id":"https:\/\/ahmadawais.com\/#\/schema\/person\/1d1b9504182dca2315cf039fb4ebb85b"},"image":{"@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#primaryimage"},"thumbnailUrl":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2026\/02\/terminals.png","keywords":["AI","cli"],"articleSection":["Code"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/","url":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/","name":"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes","isPartOf":{"@id":"https:\/\/ahmadawais.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#primaryimage"},"image":{"@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#primaryimage"},"thumbnailUrl":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2026\/02\/terminals.png","datePublished":"2026-02-17T16:06:08+00:00","dateModified":"2026-02-17T18:58:11+00:00","description":"Most developers use a terminal for years before realizing they don't actually know what a terminal is. They know how to use one. They couldn't define one.","breadcrumb":{"@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#primaryimage","url":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2026\/02\/terminals.png","contentUrl":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2026\/02\/terminals.png","width":1200,"height":630,"caption":"Terminals"},{"@type":"BreadcrumbList","@id":"https:\/\/ahmadawais.com\/the-full-stack-of-terminals-explained-terminal-shell-tty-console-posix-ansi-escapes-ptys\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ahmadawais.com\/"},{"@type":"ListItem","position":2,"name":"The Full Stack of Terminals Explained: Terminal, Shell, TTY, Console, POSIX, ANSI Escapes, PTYs, Raw\/Canonical Modes"}]},{"@type":"WebSite","@id":"https:\/\/ahmadawais.com\/#website","url":"https:\/\/ahmadawais.com\/","name":"Ahmad Awais","description":"Founder &amp; CEO of CommandCode.ai f\/k\/a Langbase | Google Developers Advisory Board (gDAB) founding member","publisher":{"@id":"https:\/\/ahmadawais.com\/#\/schema\/person\/1d1b9504182dca2315cf039fb4ebb85b"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ahmadawais.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/ahmadawais.com\/#\/schema\/person\/1d1b9504182dca2315cf039fb4ebb85b","name":"Ahmad Awais","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2020\/06\/Ahmad-Awais-face.jpg","url":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2020\/06\/Ahmad-Awais-face.jpg","contentUrl":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2020\/06\/Ahmad-Awais-face.jpg","width":2299,"height":1705,"caption":"Ahmad Awais"},"logo":{"@id":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2020\/06\/Ahmad-Awais-face.jpg"},"description":"Founder & CEO of \u2318 Command Code coding agent with taste. Founded Langbase.com, AI cloud to build, deploy, and scale AI agents with tools & memory \u00b7 Creator of Command.new. \"Awais is an awesome example for developers\" \u2014 Satya Nadella, CEO of Microsoft. NASA Mars Ingenuity Helicopter mission code contributor 8th GitHub Stars Award recipient with 5x GitHub Stars Award (Listed as GitHub's #1 JavaScript trending developer). Google Developers Expert Web DevRel. Ex VP Eng (DevTools & DevRel) Rapid \u00b7 Google Developers Advisory Board (gDAB) founding member \u00b7 AI\/ML\/DevTools Angel Investor (Replit, Resend, Daytona, Gumroad and you?) \u276f AI\/ML Advisory Board San Francisco, DevNetwork. Award-winning Open Source Engineering leader authored hundreds of open-source dev-tools and software libraries used by millions of developers, including Shades of Purple code theme and corona-cli. Linux Foundation (Node.js Committee Lead), OpenAPI Business Governing Board. Taught 108K+ developers via NodeCLI.com and VSCode.pro course. 274 million views, blogging for 24 yrs. \u276f Read more about Ahmad Awais or come say hi on \ud835\udd4f @MrAhmadAwais.","sameAs":["https:\/\/AhmadAwais.com\/","https:\/\/facebook.com\/AhmadAwais","https:\/\/instagram.com\/MrAhmadAwais\/","https:\/\/www.linkedin.com\/in\/MrAhmadAwais\/","https:\/\/x.com\/MrAhmadAwais","https:\/\/youtube.com\/AhmadAwais"]}]}},"jetpack_featured_media_url":"https:\/\/ahmadawais.com\/wp-content\/uploads\/2026\/02\/terminals.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/posts\/8193","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/comments?post=8193"}],"version-history":[{"count":5,"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/posts\/8193\/revisions"}],"predecessor-version":[{"id":8223,"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/posts\/8193\/revisions\/8223"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/media\/8222"}],"wp:attachment":[{"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/media?parent=8193"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/categories?post=8193"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ahmadawais.com\/api\/wp\/v2\/tags?post=8193"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}