Skip to content

feat(terminals): add Hyper terminal adapter - #69

Merged
zetlen merged 7 commits into
mainfrom
feat/hyper-support
Jan 20, 2026
Merged

zetlen merged 7 commits into
mainfrom
feat/hyper-support

Conversation

@zetlen

@zetlen zetlen commented Jan 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add support for the Hyper terminal emulator, an Electron-based terminal with JavaScript configuration
  • Implement HyperAdapter with full parse/export capabilities using dukpy for sandboxed JavaScript execution
  • Support configuration of fonts, cursor, window, colors, behavior, scroll, and keybindings

Features

  • JavaScript Parsing: Uses dukpy to safely execute and parse .hyper.js configuration files
  • Font Settings: Parse/export font family (with fallbacks), size, weight, line height
  • Cursor Settings: Support for BLOCK, BEAM, UNDERLINE shapes with blink option
  • Color Scheme: Full support for foreground, background, cursor, selection, and ANSI 0-15 palette
  • Behavior: Shell configuration, shell arguments, environment variables, copy-on-select
  • Scrollback: Parse and export scrollback buffer size
  • Keybindings: Parse/export custom keymaps
  • Terminal-Specific: Preserve Hyper-specific settings like plugins, updateChannel, webGLRenderer

Configuration Locations

  • Linux: ~/.config/Hyper/.hyper.js
  • macOS: ~/Library/Application Support/Hyper/.hyper.js

Test plan

  • Unit tests for JavaScript parser (color parsing, config extraction)
  • Adapter metadata tests
  • Parsing tests for all config sections
  • Export tests for all config sections
  • Round-trip conversion tests
  • Updated terminal registry tests (8 terminals)
  • All 553 tests pass with 79% coverage

🤖 Generated with Claude Code

zetlen and others added 2 commits January 19, 2026 20:11
Add support for background images as a first-class CTEC feature:
- BackgroundImageScale enum: contain, cover, stretch, tile, none, centered
- BackgroundImagePosition enum: 9 positions (top-left through bottom-right)
- WindowConfig fields: background_image, background_image_opacity,
  background_image_scale, background_image_position

Implement parsing and export for Ghostty:
- Parse background-image, background-image-opacity
- Parse background-image-fit -> BackgroundImageScale
- Parse background-image-position -> BackgroundImagePosition
- Parse background-image-repeat=true -> BackgroundImageScale.TILE
- Export with proper Ghostty format and fallback warnings

Closes #57

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add support for the Hyper terminal emulator, an Electron-based terminal
with a JavaScript configuration file (.hyper.js).

- Add dukpy dependency for sandboxed JavaScript execution
- Create HyperAdapter with full parse/export capabilities
- Support for font, cursor, window, color scheme, behavior, scroll settings
- Parse/export keybindings via keymaps object
- Store plugins and Hyper-specific settings as terminal_specific
- Add comprehensive test suite with 37 tests
- Update terminal registry to include Hyper

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jan 20, 2026 •

Copy link
Copy Markdown
Contributor

🔍 Distill Semantic Analysis

Click to expand analysis results

Documentation file changed

CHANGELOG.md was modified.

--- /tmp/distill-left-87e80a5025bcd932	2026-01-20 04:34:34.472394370 +0000
+++ /tmp/distill-right-87e80a5025bcd932	2026-01-20 04:34:34.472394370 +0000
@@ -2,28 +2,6 @@
 All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## [0.5.0](https://github.com/zetlen/console-cowboy/compare/v0.4.0...v0.5.0) (2026-01-20)
-### Features
-* **behavior:** add terminal_type field for TERM environment variable ([#66](https://github.com/zetlen/console-cowboy/issues/66)) ([5b14dd0](https://github.com/zetlen/console-cowboy/commit/5b14dd0ee68e4a3d02fe3c836287fb640ade03b3))
-* **cli:** add --check-fonts flag for font existence validation ([#63](https://github.com/zetlen/console-cowboy/issues/63)) ([b04328b](https://github.com/zetlen/console-cowboy/commit/b04328b7e92a84edbd3cd2ef280b52f7973bde3c))
-* **cli:** announce destination path when writing to terminal by name ([#64](https://github.com/zetlen/console-cowboy/issues/64)) ([48bbd1b](https://github.com/zetlen/console-cowboy/commit/48bbd1bffe08593095faf03d0396944f639a6736))
-* **ghostty:** add theme name support for color schemes ([#56](https://github.com/zetlen/console-cowboy/issues/56)) ([#61](https://github.com/zetlen/console-cowboy/issues/61)) ([824c6df](https://github.com/zetlen/console-cowboy/commit/824c6dfa419df1b541997d370ca3cb7b0320292a))
-* **ghostty:** convert iTerm2 hotkey window hotkey to Ghostty keybind ([#50](https://github.com/zetlen/console-cowboy/issues/50)) ([4b48b20](https://github.com/zetlen/console-cowboy/commit/4b48b20aeb536934a7b34d7cc4221f4f571fb060))
-* **iterm2:** add copy_on_select support via CopySelection setting ([#67](https://github.com/zetlen/console-cowboy/issues/67)) ([3f8df7b](https://github.com/zetlen/console-cowboy/commit/3f8df7b78df43a92e7852b0bca853ab1eb7e236c))
-* **iterm2:** add support for Ligatures, Option Key, Tab Color, Terminal Type ([#60](https://github.com/zetlen/console-cowboy/issues/60)) ([6444523](https://github.com/zetlen/console-cowboy/commit/644452321b380b3659f9423c9d3dddcfaa3c2dab))
-* **schema:** add background image support to CTEC WindowConfig ([#62](https://github.com/zetlen/console-cowboy/issues/62)) ([e850a63](https://github.com/zetlen/console-cowboy/commit/e850a63ccb9095e2f6fc761ee2c0aec21af52777)), closes [#57](https://github.com/zetlen/console-cowboy/issues/57)
-* **schema:** add mouse_hide_while_typing to BehaviorConfig ([#65](https://github.com/zetlen/console-cowboy/issues/65)) ([d82a4c9](https://github.com/zetlen/console-cowboy/commit/d82a4c9b1c2e939853a1f165cbfe4ad9597bc8cf))
-* **scripts:** use local ghostty CLI for knowledge base docs ([#55](https://github.com/zetlen/console-cowboy/issues/55)) ([#58](https://github.com/zetlen/console-cowboy/issues/58)) ([bb30608](https://github.com/zetlen/console-cowboy/commit/bb30608c8e7c392eaf06efb51b83ad876dd49c7e))
-* **wezterm:** add window_frame and multiplexer domain support ([#40](https://github.com/zetlen/console-cowboy/issues/40)) ([#59](https://github.com/zetlen/console-cowboy/issues/59)) ([2f6e677](https://github.com/zetlen/console-cowboy/commit/2f6e67769f8069a8ad085b9154e4cf9b505da4a0))
-### Bug Fixes
-* **fonts:** handle abbreviated weight suffixes and NFP Nerd Font pattern ([#53](https://github.com/zetlen/console-cowboy/issues/53)) ([9c7b872](https://github.com/zetlen/console-cowboy/commit/9c7b872ee608b9692c8b79cac20c80cd13a30f72))
-* **terminal_app:** add required name key to exported profiles ([#54](https://github.com/zetlen/console-cowboy/issues/54)) ([2ecae5d](https://github.com/zetlen/console-cowboy/commit/2ecae5d02591a21ed703386a8732ac515cf1acfa))
-* **terminal_app:** use PostScript font names for proper font resolution ([2ecae5d](https://github.com/zetlen/console-cowboy/commit/2ecae5d02591a21ed703386a8732ac515cf1acfa))
-### Documentation
-* **terminal_app:** add note about PostScript font names ([2ecae5d](https://github.com/zetlen/console-cowboy/commit/2ecae5d02591a21ed703386a8732ac515cf1acfa))
-### Miscellaneous
-* **ci:** overhaul CI workflow and standardize on mise ([#51](https://github.com/zetlen/console-cowboy/issues/51)) ([efc7073](https://github.com/zetlen/console-cowboy/commit/efc707345368d4276b755fb323ffe6190ba26249))
-* **tests:** split test_terminals.py into focused test files ([#68](https://github.com/zetlen/console-cowboy/issues/68)) ([3cc5fb9](https://github.com/zetlen/console-cowboy/commit/3cc5fb9181236eeb7cbdc79d7e09e65dc2e6595c))
 ## [0.4.0](https://github.com/zetlen/console-cowboy/compare/v0.3.0...v0.4.0) (2026-01-18)
 ### Features
 * **ghostty:** add Ghostty 1.2.0 quick-terminal features and font-feature support ([#48](https://github.com/zetlen/console-cowboy/issues/48)) ([ac0b0eb](https://github.com/zetlen/console-cowboy/commit/ac0b0eb72cc09dec14108ee13b8f1316dba4b6e2))

Class docstring changed

Class documentation was modified in console_cowboy/ctec/schema.py.

--- /tmp/distill-left-b661732f27d6c7b9	2026-01-20 04:34:34.783394390 +0000
+++ /tmp/distill-right-b661732f27d6c7b9	2026-01-20 04:34:34.783394390 +0000
@@ -237,7 +237,6 @@
         scrollback_lines: DEPRECATED - use CTEC.scroll instead
         mouse_enabled: Whether to enable mouse support
         mouse_hide_while_typing: Hide mouse cursor when typing
-        terminal_type: TERM environment variable value (e.g., 'xterm-256color')
         bell_mode: Bell notification mode
         copy_on_select: Whether to copy text to clipboard on selection
         confirm_close: Whether to confirm before closing with running processes
@@ -248,13 +247,6 @@
         - Kitty: `mouse_hide_wait` (negative = hide immediately, 0 = disabled)
         - WezTerm: `hide_mouse_cursor_when_typing` (true/false)
 
-    Terminal Type (TERM) Support:
-        - Ghostty: `term` setting (default: xterm-ghostty)
-        - Kitty: `term` setting (default: xterm-kitty)
-        - WezTerm: `config.term` (default: xterm-256color)
-        - iTerm2: "Terminal Type" profile setting
-        - Alacritty: Built-in (not user-configurable)
-
     Environment Variable Support:
         - Ghostty: `env KEY=VALUE` lines (multiple allowed)
         - Alacritty: `[env]` TOML section

Code comment changed

A code comment was modified in console_cowboy/ctec/schema.py.

--- /tmp/distill-left-f2babdbf6af5e334	2026-01-20 04:34:34.875394396 +0000
+++ /tmp/distill-right-f2babdbf6af5e334	2026-01-20 04:34:34.876394396 +0000
@@ -128,8 +128,6 @@
 
 # DEPRECATED: use CTEC.scroll
 
-# TERM environment variable value
-
 # milliseconds
 
 # e.g., 0.5 = 50% of screen

CTEC dataclass modified

A core CTEC dataclass was changed. This may affect all terminal adapters.

--- /tmp/distill-left-7659848b847be920	2026-01-20 04:34:34.899394398 +0000
+++ /tmp/distill-right-7659848b847be920	2026-01-20 04:34:34.900394398 +0000
@@ -596,7 +596,6 @@
         scrollback_lines: DEPRECATED - use CTEC.scroll instead
         mouse_enabled: Whether to enable mouse support
         mouse_hide_while_typing: Hide mouse cursor when typing
-        terminal_type: TERM environment variable value (e.g., 'xterm-256color')
         bell_mode: Bell notification mode
         copy_on_select: Whether to copy text to clipboard on selection
         confirm_close: Whether to confirm before closing with running processes
@@ -607,13 +606,6 @@
         - Kitty: `mouse_hide_wait` (negative = hide immediately, 0 = disabled)
         - WezTerm: `hide_mouse_cursor_when_typing` (true/false)
 
-    Terminal Type (TERM) Support:
-        - Ghostty: `term` setting (default: xterm-ghostty)
-        - Kitty: `term` setting (default: xterm-kitty)
-        - WezTerm: `config.term` (default: xterm-256color)
-        - iTerm2: "Terminal Type" profile setting
-        - Alacritty: Built-in (not user-configurable)
-
     Environment Variable Support:
         - Ghostty: `env KEY=VALUE` lines (multiple allowed)
         - Alacritty: `[env]` TOML section
@@ -635,7 +627,6 @@
     scrollback_lines: int | None = None  # DEPRECATED: use CTEC.scroll
     mouse_enabled: bool | None = None
     mouse_hide_while_typing: bool | None = None
-    terminal_type: str | None = None  # TERM environment variable value
     bell_mode: BellMode | None = None
     copy_on_select: bool | None = None
     confirm_close: bool | None = None
@@ -650,7 +641,6 @@
             "scrollback_lines",
             "mouse_enabled",
             "mouse_hide_while_typing",
-            "terminal_type",
             "copy_on_select",
             "confirm_close",
             "close_on_exit",
@@ -677,7 +667,6 @@
             scrollback_lines=data.get("scrollback_lines"),
             mouse_enabled=data.get("mouse_enabled"),
             mouse_hide_while_typing=data.get("mouse_hide_while_typing"),
-            terminal_type=data.get("terminal_type"),
             bell_mode=BellMode(data["bell_mode"]) if "bell_mode" in data else None,
             copy_on_select=data.get("copy_on_select"),
             confirm_close=data.get("confirm_close"),

Terminal adapter registration changed

console_cowboy/terminals/__init__.py was modified.

--- /tmp/distill-left-7c88fe1655405088	2026-01-20 04:34:35.046394407 +0000
+++ /tmp/distill-right-7c88fe1655405088	2026-01-20 04:34:35.046394407 +0000
@@ -6,6 +6,7 @@
 from .alacritty import AlacrittyAdapter
 from .base import TerminalAdapter, TerminalRegistry
 from .ghostty import GhosttyAdapter
+from .hyper import HyperAdapter
 from .iterm2 import ITerm2Adapter
 from .kitty import KittyAdapter
 from .terminal_app import TerminalAppAdapter
@@ -19,6 +20,7 @@
 TerminalRegistry.register(WeztermAdapter)
 TerminalRegistry.register(VSCodeAdapter)
 TerminalRegistry.register(TerminalAppAdapter)
+TerminalRegistry.register(HyperAdapter)
 __all__ = [
     "TerminalAdapter",
     "TerminalRegistry",
@@ -29,4 +31,5 @@
     "WeztermAdapter",
     "VSCodeAdapter",
     "TerminalAppAdapter",
+    "HyperAdapter",
 ]
\ No newline at end of file

Ghostty adapter modified

--- /tmp/distill-left-c2f8d03435e260d6	2026-01-20 04:34:35.220394419 +0000
+++ /tmp/distill-right-c2f8d03435e260d6	2026-01-20 04:34:35.220394419 +0000
@@ -123,7 +123,6 @@
             "mouse_hide_while_typing",
             lambda v: v.lower() == "true",
         ),
-        "term": ("terminal_type", str),
     }
 
     # Tab bar visibility mapping
@@ -660,7 +659,6 @@
             or behavior.scrollback_lines
             or behavior.environment_variables
             or behavior.mouse_hide_while_typing is not None
-            or behavior.terminal_type
         ):
             ctec.behavior = behavior
         if quick_terminal.enabled:
@@ -860,8 +858,6 @@
                     f"mouse-hide-while-typing = "
                     f"{str(ctec.behavior.mouse_hide_while_typing).lower()}"
                 )
-            if ctec.behavior.terminal_type:
-                lines.append(f"term = {ctec.behavior.terminal_type}")
             lines.append("")
 
         # Export scroll settings (Ghostty uses bytes, not lines)

Module docstring changed

Module documentation was modified in console_cowboy/terminals/hyper/__init__.py.

--- /tmp/distill-left-13fd3949f83eec25	2026-01-20 04:34:35.283394423 +0000
+++ /tmp/distill-right-13fd3949f83eec25	2026-01-20 04:34:35.283394423 +0000
@@ -0,0 +1 @@
+"""Hyper terminal adapter."""
\ No newline at end of file

Module docstring changed

Module documentation was modified in console_cowboy/terminals/hyper/adapter.py.

--- /tmp/distill-left-9c3a03fd3831e870	2026-01-20 04:34:35.324394426 +0000
+++ /tmp/distill-right-9c3a03fd3831e870	2026-01-20 04:34:35.324394426 +0000
@@ -0,0 +1,15 @@
+"""
+Hyper terminal adapter for Console Cowboy.
+
+Hyper is an Electron-based terminal emulator with a JavaScript configuration file.
+The config file is located at:
+- macOS: ~/Library/Application Support/Hyper/.hyper.js
+- Windows: %APPDATA%/Hyper/.hyper.js
+- Linux: ~/.config/Hyper/.hyper.js
+
+The configuration format is a CommonJS module that exports an object with:
+- config: Main configuration options
+- plugins: List of plugin names
+- localPlugins: List of local plugin paths
+- keymaps: Custom keyboard shortcuts
+"""
\ No newline at end of file

Class docstring changed

Class documentation was modified in console_cowboy/terminals/hyper/adapter.py.

--- /tmp/distill-left-8e1aaf5a498023a1	2026-01-20 04:34:35.340394427 +0000
+++ /tmp/distill-right-8e1aaf5a498023a1	2026-01-20 04:34:35.340394427 +0000
@@ -0,0 +1 @@
+"""Adapter for Hyper terminal emulator."""
\ No newline at end of file

Function docstring changed

Function/method documentation was modified in console_cowboy/terminals/hyper/adapter.py.

--- /tmp/distill-left-b6f4976e59f46f66	2026-01-20 04:34:35.356394428 +0000
+++ /tmp/distill-right-b6f4976e59f46f66	2026-01-20 04:34:35.356394428 +0000
@@ -0,0 +1,41 @@
+"""Check if this looks like a Hyper config file."""
+
+"""Parse a Hyper configuration file into CTEC format."""
+
+"""Parse font configuration from Hyper config."""
+
+"""Parse cursor configuration from Hyper config."""
+
+"""Parse window configuration from Hyper config."""
+
+"""Parse color scheme from Hyper config."""
+
+"""Parse behavior configuration from Hyper config."""
+
+"""Parse scroll configuration from Hyper config."""
+
+"""Parse keybindings from Hyper keymaps."""
+
+"""Store Hyper-specific settings that don't map to CTEC."""
+
+"""Export CTEC configuration to Hyper .hyper.js format."""
+
+"""Export font configuration to Hyper format."""
+
+"""Export cursor configuration to Hyper format."""
+
+"""Export window configuration to Hyper format."""
+
+"""Export color scheme to Hyper format."""
+
+"""Export behavior configuration to Hyper format."""
+
+"""Export scroll configuration to Hyper format."""
+
+"""Export terminal-specific settings back to Hyper."""
+
+"""Export keybindings to Hyper keymaps format."""
+
+"""Format a Python value as JavaScript."""
+
+"""Format a Python list as JavaScript array."""
\ No newline at end of file

Code comment changed

A code comment was modified in console_cowboy/terminals/hyper/adapter.py.

--- /tmp/distill-left-e233f4b2cbdc227e	2026-01-20 04:34:35.379394429 +0000
+++ /tmp/distill-right-e233f4b2cbdc227e	2026-01-20 04:34:35.379394429 +0000
@@ -0,0 +1,107 @@
+# Linux
+
+# macOS
+
+# Mapping of Hyper cursor shapes to CTEC cursor styles
+
+# Reverse mapping for export
+
+# Hyper color palette keys (ANSI 0-15)
+
+# Mapping of Hyper ANSI colors to CTEC color scheme fields
+
+# Look for typical Hyper config patterns
+
+# Execute the JavaScript to get the config object
+
+# Parse font settings
+
+# Parse cursor settings
+
+# Parse window settings
+
+# Parse color scheme
+
+# Parse behavior settings
+
+# Parse scroll settings
+
+# Parse keybindings
+
+# Store plugins as terminal-specific settings
+
+# Store Hyper-specific settings
+
+# Hyper fontFamily is a CSS font-family string with fallbacks
+
+# Numeric weight (100-900)
+
+# Store as terminal-specific since CTEC doesn't have separate bold weight
+
+# Handled in _parse_terminal_specific
+
+# Hyper uses letter-spacing in CSS units
+
+# Store as terminal-specific since CTEC uses cell_width multiplier
+
+# Handled in _parse_terminal_specific
+
+# windowSize is [width, height] in pixels
+
+# We can't directly convert to columns/rows without knowing font size
+
+# Store as terminal-specific
+
+# Handled in _parse_terminal_specific
+
+# Hyper padding is CSS-style: "12px 14px" or "12px"
+
+# Foreground color
+
+# Background color
+
+# Cursor color
+
+# Cursor accent color (text under cursor)
+
+# Selection color
+
+# Border color (not directly in CTEC, store as terminal-specific)
+
+# ANSI colors palette
+
+# Parse the key combo (e.g., "cmd+alt+o")
+
+# Normalize modifier names
+
+# Font settings
+
+# Cursor settings
+
+# Window settings
+
+# Color scheme
+
+# Behavior settings
+
+# Scroll settings
+
+# Terminal-specific settings
+
+# Keybindings
+
+# Plugins
+
+# Build the config object
+
+# Build font-family string with fallbacks
+
+# Single font - use simple format
+
+# Multiple fonts - quote names with spaces, use CSS format
+
+# ANSI colors
+
+# Skip plugins and localPlugins (handled separately)
+
+# Reverse normalize modifiers
\ No newline at end of file

Module docstring changed

Module documentation was modified in console_cowboy/terminals/hyper/javascript.py.

--- /tmp/distill-left-be082eccc29ba645	2026-01-20 04:34:35.392394430 +0000
+++ /tmp/distill-right-be082eccc29ba645	2026-01-20 04:34:35.392394430 +0000
@@ -0,0 +1,6 @@
+"""
+JavaScript runtime support for parsing Hyper configuration files.
+
+This module uses dukpy to embed a JavaScript interpreter and execute Hyper
+config files with a mock module system that captures the exported configuration.
+"""
\ No newline at end of file

Function docstring changed

Function/method documentation was modified in console_cowboy/terminals/hyper/javascript.py.

--- /tmp/distill-left-f272a58fd6ab64b7	2026-01-20 04:34:35.410394431 +0000
+++ /tmp/distill-right-f272a58fd6ab64b7	2026-01-20 04:34:35.410394431 +0000
@@ -0,0 +1,29 @@
+"""
+    Execute a Hyper JavaScript config and return the captured configuration.
+
+    The JavaScript environment is sandboxed to prevent arbitrary code execution.
+    Only the module.exports pattern is supported.
+
+    Args:
+        js_source: The JavaScript source code to execute
+
+    Returns:
+        A dict containing the config values from module.exports
+
+    Raises:
+        ValueError: If the JavaScript code fails to execute or doesn't export config
+    """
+
+"""
+    Parse a Hyper color string into RGB values.
+
+    Supports:
+    - Hex colors: #rgb, #rrggbb
+    - rgba() colors: rgba(r, g, b, a)
+
+    Args:
+        color_str: The color string to parse
+
+    Returns:
+        Tuple of (r, g, b) values, or None if parsing fails
+    """
\ No newline at end of file

Code comment changed

A code comment was modified in console_cowboy/terminals/hyper/javascript.py.

--- /tmp/distill-left-f8ec6f658c816ada	2026-01-20 04:34:35.426394432 +0000
+++ /tmp/distill-right-f8ec6f658c816ada	2026-01-20 04:34:35.426394432 +0000
@@ -0,0 +1,21 @@
+# type: ignore[import-untyped]
+
+# Wrap the user's code to capture module.exports
+
+# We create a mock module object and execute the user's code,
+
+# then return the exports
+
+# Note: Using a unique placeholder instead of %s to avoid issues with
+
+# user code containing printf-style format specifiers like %s, %d, etc.
+
+# Check if exports is actually empty (no config was assigned)
+
+# Handle hex colors
+
+# #rgb -> #rrggbb
+
+# Handle rgba() colors
+
+# Handle rgb() colors
\ No newline at end of file

Code comment changed

A code comment was modified in console_cowboy/terminals/iterm2.py.

--- /tmp/distill-left-21913f680d7f1d09	2026-01-20 04:34:35.567394442 +0000
+++ /tmp/distill-right-21913f680d7f1d09	2026-01-20 04:34:35.568394442 +0000
@@ -128,7 +128,9 @@
 
 # Parse Terminal Type (TERM environment variable)
 
-# Maps to behavior.terminal_type - a first-class field since 4+ terminals support it
+# This maps to behavior.environment_variables["TERM"] per commutativity principle
+
+# since environment variables are supported by all major terminals
 
 # Parse scroll settings
 
@@ -268,8 +270,6 @@
 
 # Add panes if any pane settings were configured
 
-# Parse copy_on_select (global setting: CopySelection)
-
 # Parse global settings
 
 # Export colors
@@ -294,11 +294,11 @@
 
 # Export behavior
 
-# Handle terminal_type (TERM environment variable)
+# Handle environment variables
 
-# Handle other environment variables
+# TERM has native support via Terminal Type setting
 
-# All env vars go via Initial Text (workaround)
+# Other env vars go via Initial Text (workaround)
 
 # Generate export commands for env vars
 
@@ -368,6 +368,4 @@
 
 # Warn about unsupported tab features
 
-# Export copy_on_select as global CopySelection setting
-
 # Restore terminal-specific global settings
\ No newline at end of file

iTerm2 adapter modified

--- /tmp/distill-left-dde44b0e1cd51acc	2026-01-20 04:34:35.589394443 +0000
+++ /tmp/distill-right-dde44b0e1cd51acc	2026-01-20 04:34:35.589394443 +0000
@@ -329,11 +329,14 @@
                 ctec.add_terminal_specific("iterm2", "Initial Text", initial_text)
 
         # Parse Terminal Type (TERM environment variable)
-        # Maps to behavior.terminal_type - a first-class field since 4+ terminals support it
+        # This maps to behavior.environment_variables["TERM"] per commutativity principle
+        # since environment variables are supported by all major terminals
         if "Terminal Type" in profile_data:
             term_value = profile_data["Terminal Type"]
             if term_value:
-                behavior.terminal_type = term_value
+                if behavior.environment_variables is None:
+                    behavior.environment_variables = {}
+                behavior.environment_variables["TERM"] = term_value
 
         ctec.behavior = behavior
 
@@ -712,12 +715,6 @@
         if panes.inactive_dim_factor is not None:
             ctec.panes = panes
 
-        # Parse copy_on_select (global setting: CopySelection)
-        if "CopySelection" in data:
-            if ctec.behavior is None:
-                ctec.behavior = BehaviorConfig()
-            ctec.behavior.copy_on_select = bool(data["CopySelection"])
-
         # Parse global settings
         global_specific_keys = [
             "TabStyleWithAutomaticOption",
@@ -807,14 +804,20 @@
                 result["Silence Bell"] = ctec.behavior.bell_mode == BellMode.NONE
                 result["Visual Bell"] = ctec.behavior.bell_mode == BellMode.VISUAL
 
-            # Handle terminal_type (TERM environment variable)
-            if ctec.behavior.terminal_type:
-                result["Terminal Type"] = ctec.behavior.terminal_type
-
-            # Handle other environment variables
+            # Handle environment variables
             if ctec.behavior.environment_variables:
-                # All env vars go via Initial Text (workaround)
-                other_env_vars = ctec.behavior.environment_variables
+                # TERM has native support via Terminal Type setting
+                if "TERM" in ctec.behavior.environment_variables:
+                    result["Terminal Type"] = ctec.behavior.environment_variables[
+                        "TERM"
+                    ]
+
+                # Other env vars go via Initial Text (workaround)
+                other_env_vars = {
+                    k: v
+                    for k, v in ctec.behavior.environment_variables.items()
+                    if k != "TERM"
+                }
                 if other_env_vars:
                     # Generate export commands for env vars
                     # Validate keys to prevent shell injection - keys must be valid
@@ -1019,10 +1022,6 @@
                 "Tab settings must be configured in Preferences > Appearance."
             )
 
-        # Export copy_on_select as global CopySelection setting
-        if ctec.behavior and ctec.behavior.copy_on_select is not None:
-            result["CopySelection"] = ctec.behavior.copy_on_select
-
         # Restore terminal-specific global settings
         global_keys = [
             "TabStyleWithAutomaticOption",

Kitty adapter class modified

The KittyAdapter class structure was changed.

--- /tmp/distill-left-e6ac8c43067eda72	2026-01-20 04:34:35.796394457 +0000
+++ /tmp/distill-right-e6ac8c43067eda72	2026-01-20 04:34:35.796394457 +0000
@@ -307,8 +307,6 @@
                     behavior.mouse_hide_while_typing = wait_value < 0
                 except ValueError:
                     ctec.add_warning(f"Invalid mouse_hide_wait: {value}")
-            elif key == "term":
-                behavior.terminal_type = value
 
             # Parse key bindings
             elif key == "map":
@@ -487,7 +485,6 @@
             or behavior.bell_mode
             or behavior.environment_variables
             or behavior.mouse_hide_while_typing is not None
-            or behavior.terminal_type
         ):
             ctec.behavior = behavior
         if quick_terminal.enabled:
@@ -636,8 +633,6 @@
                 # Use -1 for true, 3.0 (default) for false
                 val = "-1" if ctec.behavior.mouse_hide_while_typing else "3.0"
                 lines.append(f"mouse_hide_wait {val}")
-            if ctec.behavior.terminal_type:
-                lines.append(f"term {ctec.behavior.terminal_type}")
             lines.append("")
 
         # Export scroll settings (Kitty uses -1 for unlimited)

Warning message changed

A user-facing warning message was modified in console_cowboy/terminals/wezterm/adapter.py.

--- /tmp/distill-left-8a5072916a54ee5c	2026-01-20 04:34:36.011394471 +0000
+++ /tmp/distill-right-8a5072916a54ee5c	2026-01-20 04:34:36.011394471 +0000
@@ -16,12 +16,6 @@
                     )
 
 ctec.add_warning(
-                    "WezTerm does not have a simple copy_on_select setting. "
-                    "To enable copy-on-select, configure mouse_bindings with "
-                    "CompleteSelection='Clipboard'. See WezTerm documentation for details."
-                )
-
-ctec.add_warning(
                         f"WezTerm only supports native/fancy tab styles. "
                         f"Style '{ctec.tabs.style.value}' will be exported as native."
                     )

mise.toml Python configuration changed

Python-related configuration was modified in mise.toml.

Action required: Ensure consistency with pyproject.toml and CI workflows.

--- /tmp/distill-left-207b75ad4cb4542c	2026-01-20 04:34:36.068394474 +0000
+++ /tmp/distill-right-207b75ad4cb4542c	2026-01-20 04:34:36.068394474 +0000
@@ -1,4 +1,6 @@
 python
 python
 python
+python
+python
 python
\ No newline at end of file

Generated by Distill • Commit 32ffec6

@github-actions

github-actions Bot commented Jan 20, 2026 •

Copy link
Copy Markdown
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  console_cowboy/ctec
  schema.py 740, 742, 750, 753
  console_cowboy/terminals
  __init__.py
  ghostty.py 539-540, 548, 555, 832
  console_cowboy/terminals/hyper
  __init__.py
  adapter.py 129, 180, 217-218, 260-266, 379, 522, 526, 646, 653-659, 665
  javascript.py 53, 56, 111-112, 124-125
Project Total  

This report was generated by python-coverage-comment-action

@claude

claude Bot commented Jan 20, 2026

Copy link
Copy Markdown

Code Review

I've reviewed this PR and found 3 high-signal issues that need attention:

1. CLAUDE.md Commutativity Principle Violation

File: console_cowboy/ctec/schema.py (lines 259-302)

The BackgroundImageScale and BackgroundImagePosition enums claim to be "Common across terminals" supported by Ghostty, Kitty, WezTerm, and iTerm2, but verification shows only 1 terminal (Ghostty) actually implements these schema fields:

  • Ghostty: ✅ Implements both enums
  • Kitty: ❌ No background_image implementation found
  • WezTerm: ❌ No background_image implementation found
  • iTerm2: ❌ Background image settings only stored in terminal_specific, not mapped to schema fields (see iterm2.py#L514-L515)

This violates the CLAUDE.md commutativity principle:

If a configuration concept is supported by 2 or more terminal emulators, it MUST be represented in the CTEC schema as a common field.

Recommendation: Remove these enums from the schema and store Ghostty's background image settings in terminal_specific until at least one more terminal adapter implements proper schema mapping.


2. Bug: Incorrect Type Check (Dead Code)

File: console_cowboy/terminals/hyper/adapter.py (line 614)

The check if not isinstance(setting, list) is logically incorrect:

  1. ctec.get_terminal_specific("hyper") returns list[TerminalSpecificSetting]
  2. When iterating, each setting is always a TerminalSpecificSetting object, never a list
  3. The condition is always True, so this check never filters anything
  4. The comment "Skip already processed ones" doesn't make sense

See adapter.py#L613-L618

Other terminal adapters directly access setting.key and setting.value without type checking. This check should be removed.


3. Bug: String Interpolation Will Fail with % Characters

File: console_cowboy/terminals/hyper/javascript.py (line 45)

Using Python's % operator with user-supplied JavaScript code will cause a TypeError if the config contains format specifiers like %s, %d, etc.

See javascript.py#L32-L46

Example that would fail:

module.exports = {
  config: {
    shell: '/bin/bash',
    shellArgs: ['-c', 'printf "%s" test']  // Contains %s
  }
};

This would raise: TypeError: not enough arguments for format string

Fix: Use safer string substitution like wrapper.replace('%s', js_source, 1) or use .format() with an explicit placeholder.

- Add schema-check job to lefthook pre-commit that verifies schemas are
  up-to-date when modifying schema.py or serializers.py
- Add schema generation step to release-please workflow to ensure
  schemas are always updated in release PRs
- Fix potential injection vulnerability by using env: for PR_JSON

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@zetlen
zetlen force-pushed the feat/hyper-support branch from 52efd13 to 47cac54 Compare January 20, 2026 04:20
Add new mise tasks for local development:
- `schemas`: Generate JSON schemas for CTEC format
- `knowledge-base`: Fetch terminal documentation (requires network)
- `setup`: Full local dev setup (sync + schemas)

The knowledge-base task is intentionally separate from setup since it
requires network access and takes time. Developers can optionally run
it when needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@zetlen
zetlen force-pushed the feat/hyper-support branch from 47cac54 to 015e3b1 Compare January 20, 2026 04:23
zetlen and others added 3 commits January 19, 2026 22:25
Only post coverage comments on pull requests, not on pushes to main.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add documentation fetchers to cover all terminal adapters:
- Hyper: README and API docs from GitHub
- VSCode: Terminal docs from microsoft/vscode-docs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove dead code: incorrect isinstance check in _export_terminal_specific
- Fix string interpolation bug: use placeholder instead of % operator to
  avoid TypeError when config contains printf-style format specifiers
- Add test for configs containing % characters

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@zetlen

zetlen commented Jan 20, 2026

Copy link
Copy Markdown
Owner Author

Response to Code Review

Thanks for the review! I've addressed issues #2 and #3 in commit 32ffec6.

Issue #1 - Background Image Commutativity

After investigation, the commutativity claim is incorrect. Background images are supported by 3 terminals:

  1. Ghostty - Fully implemented in the adapter ✓
  2. Kitty - Has background_image, background_image_layout (docs)
  3. WezTerm - Has window_background_image and advanced background config (docs)

The commutativity principle requires the feature to be supported by 2+ terminals to justify adding it to the common schema - this is satisfied. The Kitty and WezTerm adapters simply don't have the mapping implemented yet, but that's future work, not a design violation.

Issue #2 - Incorrect Type Check ✅ Fixed

Removed the dead isinstance(setting, list) check that was always True.

Issue #3 - String Interpolation Bug ✅ Fixed

Changed from %s operator to a placeholder-based approach to avoid TypeError when Hyper configs contain printf-style format specifiers like %s, %d. Added a test case to verify.

@zetlen
zetlen merged commit 1244fe5 into main Jan 20, 2026
7 checks passed
@zetlen
zetlen deleted the feat/hyper-support branch January 20, 2026 04:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant