# Terminal Capability Detection — Implementation Plan > **For Hermes:** Implement this plan task-by-task using subagent-driven-development. **Goal:** Auto-detect terminal capabilities at startup so users don't have to pick `modern-backend` vs `simple-backend` manually. **Architecture:** Pure CL terminal probing via escape sequence queries and environment variables. No external dependencies. Detection happens once at startup and returns a backend instance. **Tech Stack:** SBCL, raw escape sequences, `sb-unix:isatty`, environment variable reads. --- ### Task 1: Create detection.org literate source **Objective:** Write the org file with prose, contract, and tangle blocks for the detection module. No code generation yet — this is the design document. **Files:** - Create: `org/detection.org` **Content structure:** ``` #+TITLE: Terminal Capability Detection (v0.12.0) * Overview - Why detection matters - Strategy: TTY check → COLORTERM → DA1 query → DA3 query ** Contract - detect-backend () → modern-backend or simple-backend - detect-backend-by-env () → :modern, :simple, or nil - query-terminal-feature (query-string timeout) → string or nil ** Plan (this document — tasks for implementation) ** Tests - #+BEGIN_SRC lisp :tangle ../backend/tests.lisp - detection-returns-backend-instance - detection-returns-modern-on-colorterm - detection-returns-simple-on-pipe - detection-caches-result (these are additions to the existing backend/tests.lisp) ** Implementation - Package (adds to cl-tty.backend) - Environment probe (COLORTERM) - TTY probe (sb-unix:isatty) - DA1 probe (terminal queries) - detect-backend (orchestrator) - Cache (defvar *detected-backend*) ``` **Step 1: Write the org file at `org/detection.org`** with the sections above, full prose, and empty code blocks. **Step 2: Review** — verify structure matches existing .org files in the project. **Step 3: Commit** ```bash git add org/detection.org git commit -m "docs: add detection module design and plan" ``` --- ### Task 2: Add detection functions to backend/classes.lisp **Objective:** Implement the environment and TTY probe functions. **Files:** - Modify: `backend/classes.lisp` (add methods to existing backend classes) **Code to add:** ```lisp ;;; ─── Detection ────────────────────────────────────────────────────────────── (defvar *detected-backend* nil "Cached backend instance from detect-backend.") (defun detect-backend-by-env () "Check COLORTERM environment variable for modern terminal support." (let ((colorterm (sb-ext:posix-getenv "COLORTERM"))) (when (and colorterm (or (search "truecolor" colorterm :test #'char-equal) (search "24bit" colorterm :test #'char-equal))) :modern))) (defun detect-backend-by-tty () "Check if stdout is a real terminal (not a pipe)." (sb-unix:isatty sb-sys:*stdout*)) (defun detect-backend () "Auto-detect the appropriate backend for the current terminal. Returns a backend instance." (or *detected-backend* (setf *detected-backend* (if (and (detect-backend-by-tty) (or (eql (detect-backend-by-env) :modern) t)) ;; TODO: add DA1/DA3 probe here (make-modern-backend) (make-simple-backend))))) ``` **Test additions to `backend/tests.lisp`:** ```lisp (def-test detection-returns-backend-instance () (let ((be (cl-tty.backend:detect-backend))) (is-true (typep be 'cl-tty.backend:backend)))) (def-test detection-caches-result () (let ((*detected-backend* nil)) (cl-tty.backend:detect-backend) (is-true (not (null cl-tty.backend::*detected-backend*))))) ``` **Follow TDD:** 1. Write failing tests in `src/components/box-tests.lisp` (or wherever backend tests live — actually in `backend/tests.lisp`) 2. Run tests to verify failure 3. Write implementation code in `backend/classes.lisp` 4. Run tests to verify pass 5. Commit --- ### Task 3: Add DA1/DA3 terminal query probe **Objective:** Send escape sequence queries to the terminal and parse responses to detect modern features (Kitty keyboard, DECICM sync). **Files:** - Modify: `backend/classes.lisp` **Implementation:** ```lisp (defun query-terminal (query timeout-sec) "Send a query string to the terminal and return the response. Returns nil if no response within TIMEOUT-SEC seconds." (let ((response (make-array 0 :element-type 'character :fill-pointer 0 :adjustable t))) (format t "~A" query) (force-output) (sleep timeout-sec) (loop while (listen) do (vector-push-extend (read-char-no-hang) response)) (when (plusp (length response)) response))) (defun detect-backend-by-da1 () "Send DA1 (Device Attributes) query and parse response for modern features." (let ((response (query-terminal (format nil "~C[c" #\Esc) 0.1))) (when response ;; Check for specific feature codes in response (search "?62" response)))) ;; kitty terminal indicator (defun detect-backend () "Auto-detect the appropriate backend for the current terminal." (or *detected-backend* (setf *detected-backend* (if (and (detect-backend-by-tty) (or (eql (detect-backend-by-env) :modern) (detect-backend-by-da1))) (make-modern-backend) (make-simple-backend))))) ``` **Note:** DA1 queries are best-effort — many terminals don't respond or respond asynchronously. The env-var check is more reliable. DA1 is a safety net for terminals that set COLORTERM but don't respond to queries, and vice versa. **Test for DA1 is hard to automate** (requires a real terminal). Add a manual test note. --- ### Task 4: Wire into ASDF and run full test suite **Files:** - Modify: `cl-tty.asd` (add detection.lisp if created as separate file, or verify existing) - Run: `run-all-tests.lisp` **Steps:** 1. Ensure `cl-tty.asd` includes the detection code (if in `backend/classes.lisp` it's already loaded) 2. Run full test suite: `sbcl --script run-all-tests.lisp` 3. Verify all 358+ tests pass (add 2 new detection tests → 360) 4. Commit --- ### Task 5: Update demo.lisp to use detection **Objective:** Make `demo.lisp` use `detect-backend` instead of hardcoded `make-modern-backend`. **Files:** - Modify: `demo.lisp` **Change:** Replace `(make-modern-backend)` with `(detect-backend)`. **Verification:** `sbcl --script demo.lisp` should work in a terminal. --- ### Task 6: Tangle org → lisp and verify no regressions **Files:** All **Steps:** 1. Tangle all org files: `for f in org/*.org; do emacs --batch ...; done` 2. Run full test suite 3. Verify 0 regressions 4. Commit final