{"_id":"@cargo-cult/pi-tui","_rev":"7-3643cf6db4e81dcae1e0c9374c7973d3","name":"@cargo-cult/pi-tui","dist-tags":{"latest":"0.47.0"},"versions":{"0.40.0":{"name":"@cargo-cult/pi-tui","version":"0.40.0","keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"author":{"name":"Mario Zechner"},"license":"MIT","_id":"@cargo-cult/pi-tui@0.40.0","maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"homepage":"https://github.com/TUstudents/pi-mono#readme","bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"dist":{"shasum":"59e0a08e6ac9f80dfeab93d74ccdad99158bbfaf","tarball":"https://registry.npmjs.org/@cargo-cult/pi-tui/-/pi-tui-0.40.0.tgz","fileCount":90,"integrity":"sha512-LPDSsQfWyFgwajn3QQg2DDOVriKDd6RVAriEHBuBA8UyTrHl2LvBQUGKucoMjbrwJV3o6b7reuBMbugbxWWdjg==","signatures":[{"sig":"MEUCIQDFmazYp9vETx/8mG/N+rP6nEqfP3m7i0uOI80bybfSOwIgUiIhvNnu4RslZqjZ9gdqU0aKSv/ba4WesOHfJtnznEg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cargo-cult%2fpi-tui@0.40.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":979464},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"gitHead":"94ba25b6aee13ac8f98935b3536dab5192ed0399","scripts":{"dev":"tsgo -p tsconfig.build.json --watch --preserveWatchOutput","test":"node --test --import tsx test/*.test.ts","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"tustudents","email":"python@atoms.eu"},"repository":{"url":"git+https://github.com/TUstudents/pi-mono.git","type":"git","directory":"packages/tui"},"_npmVersion":"10.9.4","description":"Terminal User Interface library with differential rendering for efficient text-based applications","directories":{},"_nodeVersion":"22.21.1","dependencies":{"chalk":"^5.5.0","marked":"^15.0.12","mime-types":"^3.0.1","@types/mime-types":"^2.1.4","get-east-asian-width":"^1.3.0"},"_hasShrinkwrap":false,"devDependencies":{"@xterm/xterm":"^5.5.0","@xterm/headless":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-tui_0.40.0_1767983665259_0.5153161792940677","host":"s3://npm-registry-packages-npm-production"}},"0.40.1":{"name":"@cargo-cult/pi-tui","version":"0.40.1","keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"author":{"name":"Mario Zechner"},"license":"MIT","_id":"@cargo-cult/pi-tui@0.40.1","maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"homepage":"https://github.com/TUstudents/pi-mono#readme","bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"dist":{"shasum":"9292c12cbba6bb31a6f4456db1eb014bbb175378","tarball":"https://registry.npmjs.org/@cargo-cult/pi-tui/-/pi-tui-0.40.1.tgz","fileCount":90,"integrity":"sha512-qOqdv+Vjb3R1McAUXKIC3nz5RV1sJZdL9DAEyxmvYIkDUiVqoTlWYat+GsH5H2deyDNMpnBwnWO4eKmCWkZ8kQ==","signatures":[{"sig":"MEUCIQCfblGn1oifAkIvZDd4XCm0I4SXWqgm0Hy1nTmb+nbnrAIgft6dzuDIFICWL8ECu1HDyB+yaic2hLXUmi96da7thNg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cargo-cult%2fpi-tui@0.40.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":979464},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"gitHead":"4d64ff64d64172c9c7ac87db756bf6044503396f","scripts":{"dev":"tsgo -p tsconfig.build.json --watch --preserveWatchOutput","test":"node --test --import tsx test/*.test.ts","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"tustudents","email":"python@atoms.eu"},"repository":{"url":"git+https://github.com/TUstudents/pi-mono.git","type":"git","directory":"packages/tui"},"_npmVersion":"10.9.4","description":"Terminal User Interface library with differential rendering for efficient text-based applications","directories":{},"_nodeVersion":"22.21.1","dependencies":{"chalk":"^5.5.0","marked":"^15.0.12","mime-types":"^3.0.1","@types/mime-types":"^2.1.4","get-east-asian-width":"^1.3.0"},"_hasShrinkwrap":false,"devDependencies":{"@xterm/xterm":"^5.5.0","@xterm/headless":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-tui_0.40.1_1767984144234_0.9770758171397778","host":"s3://npm-registry-packages-npm-production"}},"0.42.0":{"name":"@cargo-cult/pi-tui","version":"0.42.0","keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"author":{"name":"Mario Zechner"},"license":"MIT","_id":"@cargo-cult/pi-tui@0.42.0","maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"homepage":"https://github.com/TUstudents/pi-mono#readme","bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"dist":{"shasum":"90f54db0237e2ec35209bfecea7c10e2c48fbb39","tarball":"https://registry.npmjs.org/@cargo-cult/pi-tui/-/pi-tui-0.42.0.tgz","fileCount":90,"integrity":"sha512-99d8aX4O8xPcAAYsINEbniOaxsQqYiSm5upTnhA9u+o7H2tgX92w+DWG8wMKIf/d/WKkcLbd53Vd/wsd5urJ1g==","signatures":[{"sig":"MEUCIGnLi6csHfYmUboeVquazoX/w+k0z3L1MapGkgtKUuAAAiEAjtzdkT5aJ80GLMBjoK1nV711mkynRhV8EwucIfVyASg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cargo-cult%2fpi-tui@0.42.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":979464},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"gitHead":"84996508517bf91a4cbe8fbaa09d5b12b19f38cf","scripts":{"dev":"tsgo -p tsconfig.build.json --watch --preserveWatchOutput","test":"node --test --import tsx test/*.test.ts","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"tustudents","email":"python@atoms.eu"},"repository":{"url":"git+https://github.com/TUstudents/pi-mono.git","type":"git","directory":"packages/tui"},"_npmVersion":"10.9.4","description":"Terminal User Interface library with differential rendering for efficient text-based applications","directories":{},"_nodeVersion":"22.21.1","dependencies":{"chalk":"^5.5.0","marked":"^15.0.12","mime-types":"^3.0.1","@types/mime-types":"^2.1.4","get-east-asian-width":"^1.3.0"},"_hasShrinkwrap":false,"devDependencies":{"@xterm/xterm":"^5.5.0","@xterm/headless":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-tui_0.42.0_1767985889130_0.04805265933645764","host":"s3://npm-registry-packages-npm-production"}},"0.42.1":{"name":"@cargo-cult/pi-tui","version":"0.42.1","keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"author":{"name":"Mario Zechner"},"license":"MIT","_id":"@cargo-cult/pi-tui@0.42.1","maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"homepage":"https://github.com/TUstudents/pi-mono#readme","bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"dist":{"shasum":"5730977e61b6a16e2cc34771acaac044255ddba7","tarball":"https://registry.npmjs.org/@cargo-cult/pi-tui/-/pi-tui-0.42.1.tgz","fileCount":90,"integrity":"sha512-Cc420lMhZwiDdKYKF5dxxQS5tL2ccKVMWtSC1a3Tje1CYlZkt1cG06vFYJnoXmtshNOEK6+zlPfYghpdf6rO5A==","signatures":[{"sig":"MEUCIQD5VEMAyiOo9PWcqSx77C8CU0j2HvGeGtvWMl+xQkj3ZQIgL6dAdBO7HcDwPYAge/B7FZfCdyZlhdfaYxFk/8bjKn8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cargo-cult%2fpi-tui@0.42.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":979464},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"gitHead":"5198ee434057587f815953b57e154b8f71e1db3c","scripts":{"dev":"tsgo -p tsconfig.build.json --watch --preserveWatchOutput","test":"node --test --import tsx test/*.test.ts","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"tustudents","email":"python@atoms.eu"},"repository":{"url":"git+https://github.com/TUstudents/pi-mono.git","type":"git","directory":"packages/tui"},"_npmVersion":"10.9.4","description":"Terminal User Interface library with differential rendering for efficient text-based applications","directories":{},"_nodeVersion":"22.21.1","dependencies":{"chalk":"^5.5.0","marked":"^15.0.12","mime-types":"^3.0.1","@types/mime-types":"^2.1.4","get-east-asian-width":"^1.3.0"},"_hasShrinkwrap":false,"devDependencies":{"@xterm/xterm":"^5.5.0","@xterm/headless":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-tui_0.42.1_1767986278954_0.38643638418298587","host":"s3://npm-registry-packages-npm-production"}},"0.45.3":{"name":"@cargo-cult/pi-tui","version":"0.45.3","keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"author":{"name":"Mario Zechner"},"license":"MIT","_id":"@cargo-cult/pi-tui@0.45.3","maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"homepage":"https://github.com/TUstudents/pi-mono#readme","bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"dist":{"shasum":"22518990d51917c1e999bbd56ceee4f5d91d6679","tarball":"https://registry.npmjs.org/@cargo-cult/pi-tui/-/pi-tui-0.45.3.tgz","fileCount":94,"integrity":"sha512-FcHF39Ll9lSTfy9LrCFx1op6AGXXaFxXMD29zquXq0e5fxqDoZDR/60TxP73k4V7eQ3MMqf3HTHCArn7ZRrCww==","signatures":[{"sig":"MEYCIQDSsVPZgS6EMlRKoMLnaQ7IQNbKmUnY9weTDAd44nX+ZwIhAOAnPDlpSXHjYC6eEVEqcq2NqPWVeMJbraOObc6Mqyi7","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cargo-cult%2fpi-tui@0.45.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1018157},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"gitHead":"c565ce93e848a6a833849cebebec6178585abed7","scripts":{"dev":"tsgo -p tsconfig.build.json --watch --preserveWatchOutput","test":"node --test --import tsx test/*.test.ts","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"tustudents","email":"python@atoms.eu"},"repository":{"url":"git+https://github.com/TUstudents/pi-mono.git","type":"git","directory":"packages/tui"},"_npmVersion":"10.9.4","description":"Terminal User Interface library with differential rendering for efficient text-based applications","directories":{},"_nodeVersion":"22.21.1","dependencies":{"chalk":"^5.5.0","marked":"^15.0.12","mime-types":"^3.0.1","@types/mime-types":"^2.1.4","get-east-asian-width":"^1.3.0"},"_hasShrinkwrap":false,"devDependencies":{"@xterm/xterm":"^5.5.0","@xterm/headless":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-tui_0.45.3_1768326682013_0.6337405339783198","host":"s3://npm-registry-packages-npm-production"}},"0.45.7":{"name":"@cargo-cult/pi-tui","version":"0.45.7","keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"author":{"name":"Mario Zechner"},"license":"MIT","_id":"@cargo-cult/pi-tui@0.45.7","maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"homepage":"https://github.com/TUstudents/pi-mono#readme","bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"dist":{"shasum":"ac4ee9c7191af7959f76c1af53297c6475e45887","tarball":"https://registry.npmjs.org/@cargo-cult/pi-tui/-/pi-tui-0.45.7.tgz","fileCount":94,"integrity":"sha512-ZcB9FBHJmF/+hCJYL3FUQTpcrzGEg50UYWc16X35twdao/4AOjJ4IPEOTIzoAvdt39Er+gTgSQdBlaBqwDXucA==","signatures":[{"sig":"MEUCIQDJmMdCnmFSSHE+5C+3c423QBCdzPzMLH56VJxpgw5GOQIgea01JxrvZ3wA+yciwrF8mmx1JCgpL3bjrNai6R25lgE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cargo-cult%2fpi-tui@0.45.7","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1079985},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"gitHead":"0854b993f8933023232e238f1e8eb14c6768817f","scripts":{"dev":"tsgo -p tsconfig.build.json --watch --preserveWatchOutput","test":"node --test --import tsx test/*.test.ts","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"tustudents","email":"python@atoms.eu"},"repository":{"url":"git+https://github.com/TUstudents/pi-mono.git","type":"git","directory":"packages/tui"},"_npmVersion":"10.9.4","description":"Terminal User Interface library with differential rendering for efficient text-based applications","directories":{},"_nodeVersion":"22.21.1","dependencies":{"chalk":"^5.5.0","marked":"^15.0.12","mime-types":"^3.0.1","@types/mime-types":"^2.1.4","get-east-asian-width":"^1.3.0"},"_hasShrinkwrap":false,"devDependencies":{"@xterm/xterm":"^5.5.0","@xterm/headless":"^5.5.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-tui_0.45.7_1768473934340_0.8675348579141775","host":"s3://npm-registry-packages-npm-production"}},"0.47.0":{"name":"@cargo-cult/pi-tui","version":"0.47.0","description":"Terminal User Interface library with differential rendering for efficient text-based applications","type":"module","main":"dist/index.js","scripts":{"clean":"rm -rf dist","build":"tsgo -p tsconfig.build.json","dev":"tsgo -p tsconfig.build.json --watch --preserveWatchOutput","test":"node --test --import tsx test/*.test.ts","prepublishOnly":"npm run clean && npm run build"},"keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"author":{"name":"Mario Zechner"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/TUstudents/pi-mono.git","directory":"packages/tui"},"engines":{"node":">=20.0.0"},"types":"./dist/index.d.ts","dependencies":{"@types/mime-types":"^2.1.4","chalk":"^5.5.0","get-east-asian-width":"^1.3.0","marked":"^15.0.12","mime-types":"^3.0.1"},"devDependencies":{"@xterm/headless":"^5.5.0","@xterm/xterm":"^5.5.0"},"_id":"@cargo-cult/pi-tui@0.47.0","gitHead":"639988d4bcbe25693775ce39cd905382eb58b7d4","bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"homepage":"https://github.com/TUstudents/pi-mono#readme","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-JOmIwBkbJbuqb2ZdcrZ0YFSObSpqMuQxy/BqGt499KT2O06F2b4/nzyKeqFCsC+WN4N2QA9HOF1I0CWaqy/sYg==","shasum":"aa53243955fac59dd216de0225846868d7b02bb7","tarball":"https://registry.npmjs.org/@cargo-cult/pi-tui/-/pi-tui-0.47.0.tgz","fileCount":94,"unpackedSize":1153092,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cargo-cult%2fpi-tui@0.47.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDlQjad++7w6LZwtY5p9Fur+LOYCpNfRmes+6/JYpXZmwIgTofNg2ih39oOCRhZHGWdqn9W5L+AFne3zYsxw2HpHPM="}]},"_npmUser":{"name":"tustudents","email":"python@atoms.eu"},"directories":{},"maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pi-tui_0.47.0_1768544542289_0.04505727485240385"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-09T18:34:25.163Z","modified":"2026-01-16T06:22:22.755Z","0.40.0":"2026-01-09T18:34:25.434Z","0.40.1":"2026-01-09T18:42:24.421Z","0.42.0":"2026-01-09T19:11:29.305Z","0.42.1":"2026-01-09T19:17:59.117Z","0.45.3":"2026-01-13T17:51:22.228Z","0.45.7":"2026-01-15T10:45:34.496Z","0.47.0":"2026-01-16T06:22:22.465Z"},"bugs":{"url":"https://github.com/TUstudents/pi-mono/issues"},"author":{"name":"Mario Zechner"},"license":"MIT","homepage":"https://github.com/TUstudents/pi-mono#readme","keywords":["tui","terminal","ui","text-editor","differential-rendering","typescript","cli"],"repository":{"type":"git","url":"git+https://github.com/TUstudents/pi-mono.git","directory":"packages/tui"},"description":"Terminal User Interface library with differential rendering for efficient text-based applications","maintainers":[{"name":"tustudents","email":"python@atoms.eu"}],"readme":"# @cargo-cult/pi-tui\n\nMinimal terminal UI framework with differential rendering and synchronized output for flicker-free interactive CLI applications.\n\n## Features\n\n- **Differential Rendering**: Three-strategy rendering system that only updates what changed\n- **Synchronized Output**: Uses CSI 2026 for atomic screen updates (no flicker)\n- **Bracketed Paste Mode**: Handles large pastes correctly with markers for >10 line pastes\n- **Component-based**: Simple Component interface with render() method\n- **Theme Support**: Components accept theme interfaces for customizable styling\n- **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container\n- **Inline Images**: Renders images in terminals that support Kitty or iTerm2 graphics protocols\n- **Autocomplete Support**: File paths and slash commands\n\n## Quick Start\n\n```typescript\nimport { TUI, Text, Editor, ProcessTerminal } from \"@cargo-cult/pi-tui\";\n\n// Create terminal\nconst terminal = new ProcessTerminal();\n\n// Create TUI\nconst tui = new TUI(terminal);\n\n// Add components\ntui.addChild(new Text(\"Welcome to my app!\"));\n\nconst editor = new Editor(tui, editorTheme);\neditor.onSubmit = (text) => {\n  console.log(\"Submitted:\", text);\n  tui.addChild(new Text(`You said: ${text}`));\n};\ntui.addChild(editor);\n\n// Start\ntui.start();\n```\n\n## Core API\n\n### TUI\n\nMain container that manages components and rendering.\n\n```typescript\nconst tui = new TUI(terminal);\ntui.addChild(component);\ntui.removeChild(component);\ntui.start();\ntui.stop();\ntui.requestRender(); // Request a re-render\n\n// Global debug key handler (Shift+Ctrl+D)\ntui.onDebug = () => console.log(\"Debug triggered\");\n```\n\n### Overlays\n\nOverlays render components on top of existing content without replacing it. Useful for dialogs, menus, and modal UI.\n\n```typescript\n// Show overlay with default options (centered, max 80 cols)\nconst handle = tui.showOverlay(component);\n\n// Show overlay with custom positioning and sizing\n// Values can be numbers (absolute) or percentage strings (e.g., \"50%\")\nconst handle = tui.showOverlay(component, {\n  // Sizing\n  width: 60,              // Fixed width in columns\n  width: \"80%\",           // Width as percentage of terminal\n  minWidth: 40,           // Minimum width floor\n  maxHeight: 20,          // Maximum height in rows\n  maxHeight: \"50%\",       // Maximum height as percentage of terminal\n\n  // Anchor-based positioning (default: 'center')\n  anchor: 'bottom-right', // Position relative to anchor point\n  offsetX: 2,             // Horizontal offset from anchor\n  offsetY: -1,            // Vertical offset from anchor\n\n  // Percentage-based positioning (alternative to anchor)\n  row: \"25%\",             // Vertical position (0%=top, 100%=bottom)\n  col: \"50%\",             // Horizontal position (0%=left, 100%=right)\n\n  // Absolute positioning (overrides anchor/percent)\n  row: 5,                 // Exact row position\n  col: 10,                // Exact column position\n\n  // Margin from terminal edges\n  margin: 2,              // All sides\n  margin: { top: 1, right: 2, bottom: 1, left: 2 },\n\n  // Responsive visibility\n  visible: (termWidth, termHeight) => termWidth >= 100  // Hide on narrow terminals\n});\n\n// OverlayHandle methods\nhandle.hide();              // Permanently remove the overlay\nhandle.setHidden(true);     // Temporarily hide (can show again)\nhandle.setHidden(false);    // Show again after hiding\nhandle.isHidden();          // Check if temporarily hidden\n\n// Hide topmost overlay\ntui.hideOverlay();\n\n// Check if any visible overlay is active\ntui.hasOverlay();\n```\n\n**Anchor values**: `'center'`, `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`, `'top-center'`, `'bottom-center'`, `'left-center'`, `'right-center'`\n\n**Resolution order**:\n1. `minWidth` is applied as a floor after width calculation\n2. For position: absolute `row`/`col` > percentage `row`/`col` > `anchor`\n3. `margin` clamps final position to stay within terminal bounds\n4. `visible` callback controls whether overlay renders (called each frame)\n\n### Component Interface\n\nAll components implement:\n\n```typescript\ninterface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  invalidate?(): void;\n}\n```\n\n| Method | Description |\n|--------|-------------|\n| `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. |\n| `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |\n| `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |\n\nThe TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.\n\n## Built-in Components\n\n### Container\n\nGroups child components.\n\n```typescript\nconst container = new Container();\ncontainer.addChild(component);\ncontainer.removeChild(component);\n```\n\n### Box\n\nContainer that applies padding and background color to all children.\n\n```typescript\nconst box = new Box(\n  1,                              // paddingX (default: 1)\n  1,                              // paddingY (default: 1)\n  (text) => chalk.bgGray(text)   // optional background function\n);\nbox.addChild(new Text(\"Content\"));\nbox.setBgFn((text) => chalk.bgBlue(text));  // Change background dynamically\n```\n\n### Text\n\nDisplays multi-line text with word wrapping and padding.\n\n```typescript\nconst text = new Text(\n  \"Hello World\",                  // text content\n  1,                              // paddingX (default: 1)\n  1,                              // paddingY (default: 1)\n  (text) => chalk.bgGray(text)   // optional background function\n);\ntext.setText(\"Updated text\");\ntext.setCustomBgFn((text) => chalk.bgBlue(text));\n```\n\n### TruncatedText\n\nSingle-line text that truncates to fit viewport width. Useful for status lines and headers.\n\n```typescript\nconst truncated = new TruncatedText(\n  \"This is a very long line that will be truncated...\",\n  0,  // paddingX (default: 0)\n  0   // paddingY (default: 0)\n);\n```\n\n### Input\n\nSingle-line text input with horizontal scrolling.\n\n```typescript\nconst input = new Input();\ninput.onSubmit = (value) => console.log(value);\ninput.setValue(\"initial\");\ninput.getValue();\n```\n\n**Key Bindings:**\n- `Enter` - Submit\n- `Ctrl+A` / `Ctrl+E` - Line start/end\n- `Ctrl+W` or `Alt+Backspace` - Delete word backwards\n- `Ctrl+U` - Delete to start of line\n- `Ctrl+K` - Delete to end of line\n- `Ctrl+Left` / `Ctrl+Right` - Word navigation\n- `Alt+Left` / `Alt+Right` - Word navigation\n- Arrow keys, Backspace, Delete work as expected\n\n### Editor\n\nMulti-line text editor with autocomplete, file completion, paste handling, and vertical scrolling when content exceeds terminal height.\n\n```typescript\ninterface EditorTheme {\n  borderColor: (str: string) => string;\n  selectList: SelectListTheme;\n}\n\nconst editor = new Editor(tui, theme);  // tui is required for height-aware scrolling\neditor.onSubmit = (text) => console.log(text);\neditor.onChange = (text) => console.log(\"Changed:\", text);\neditor.disableSubmit = true; // Disable submit temporarily\neditor.setAutocompleteProvider(provider);\neditor.borderColor = (s) => chalk.blue(s); // Change border dynamically\n```\n\n**Features:**\n- Multi-line editing with word wrap\n- Slash command autocomplete (type `/`)\n- File path autocomplete (press `Tab`)\n- Large paste handling (>10 lines creates `[paste #1 +50 lines]` marker)\n- Horizontal lines above/below editor\n- Fake cursor rendering (hidden real cursor)\n\n**Key Bindings:**\n- `Enter` - Submit\n- `Shift+Enter`, `Ctrl+Enter`, or `Alt+Enter` - New line (terminal-dependent, Alt+Enter most reliable)\n- `Tab` - Autocomplete\n- `Ctrl+K` - Delete line\n- `Ctrl+A` / `Ctrl+E` - Line start/end\n- Arrow keys, Backspace, Delete work as expected\n\n### Markdown\n\nRenders markdown with syntax highlighting and theming support.\n\n```typescript\ninterface MarkdownTheme {\n  heading: (text: string) => string;\n  link: (text: string) => string;\n  linkUrl: (text: string) => string;\n  code: (text: string) => string;\n  codeBlock: (text: string) => string;\n  codeBlockBorder: (text: string) => string;\n  quote: (text: string) => string;\n  quoteBorder: (text: string) => string;\n  hr: (text: string) => string;\n  listBullet: (text: string) => string;\n  bold: (text: string) => string;\n  italic: (text: string) => string;\n  strikethrough: (text: string) => string;\n  underline: (text: string) => string;\n  highlightCode?: (code: string, lang?: string) => string[];\n}\n\ninterface DefaultTextStyle {\n  color?: (text: string) => string;\n  bgColor?: (text: string) => string;\n  bold?: boolean;\n  italic?: boolean;\n  strikethrough?: boolean;\n  underline?: boolean;\n}\n\nconst md = new Markdown(\n  \"# Hello\\n\\nSome **bold** text\",\n  1,              // paddingX\n  1,              // paddingY\n  theme,          // MarkdownTheme\n  defaultStyle    // optional DefaultTextStyle\n);\nmd.setText(\"Updated markdown\");\n```\n\n**Features:**\n- Headings, bold, italic, code blocks, lists, links, blockquotes\n- HTML tags rendered as plain text\n- Optional syntax highlighting via `highlightCode`\n- Padding support\n- Render caching for performance\n\n### Loader\n\nAnimated loading spinner.\n\n```typescript\nconst loader = new Loader(\n  tui,                              // TUI instance for render updates\n  (s) => chalk.cyan(s),            // spinner color function\n  (s) => chalk.gray(s),            // message color function\n  \"Loading...\"                      // message (default: \"Loading...\")\n);\nloader.start();\nloader.setMessage(\"Still loading...\");\nloader.stop();\n```\n\n### CancellableLoader\n\nExtends Loader with Escape key handling and an AbortSignal for cancelling async operations.\n\n```typescript\nconst loader = new CancellableLoader(\n  tui,                              // TUI instance for render updates\n  (s) => chalk.cyan(s),            // spinner color function\n  (s) => chalk.gray(s),            // message color function\n  \"Working...\"                      // message\n);\nloader.onAbort = () => done(null); // Called when user presses Escape\ndoAsyncWork(loader.signal).then(done);\n```\n\n**Properties:**\n- `signal: AbortSignal` - Aborted when user presses Escape\n- `aborted: boolean` - Whether the loader was aborted\n- `onAbort?: () => void` - Callback when user presses Escape\n\n### SelectList\n\nInteractive selection list with keyboard navigation.\n\n```typescript\ninterface SelectItem {\n  value: string;\n  label: string;\n  description?: string;\n}\n\ninterface SelectListTheme {\n  selectedPrefix: (text: string) => string;\n  selectedText: (text: string) => string;\n  description: (text: string) => string;\n  scrollInfo: (text: string) => string;\n  noMatch: (text: string) => string;\n}\n\nconst list = new SelectList(\n  [\n    { value: \"opt1\", label: \"Option 1\", description: \"First option\" },\n    { value: \"opt2\", label: \"Option 2\", description: \"Second option\" },\n  ],\n  5,      // maxVisible\n  theme   // SelectListTheme\n);\n\nlist.onSelect = (item) => console.log(\"Selected:\", item);\nlist.onCancel = () => console.log(\"Cancelled\");\nlist.onSelectionChange = (item) => console.log(\"Highlighted:\", item);\nlist.setFilter(\"opt\"); // Filter items\n```\n\n**Controls:**\n- Arrow keys: Navigate\n- Enter: Select\n- Escape: Cancel\n\n### SettingsList\n\nSettings panel with value cycling and submenus.\n\n```typescript\ninterface SettingItem {\n  id: string;\n  label: string;\n  description?: string;\n  currentValue: string;\n  values?: string[];  // If provided, Enter/Space cycles through these\n  submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;\n}\n\ninterface SettingsListTheme {\n  label: (text: string, selected: boolean) => string;\n  value: (text: string, selected: boolean) => string;\n  description: (text: string) => string;\n  cursor: string;\n  hint: (text: string) => string;\n}\n\nconst settings = new SettingsList(\n  [\n    { id: \"theme\", label: \"Theme\", currentValue: \"dark\", values: [\"dark\", \"light\"] },\n    { id: \"model\", label: \"Model\", currentValue: \"gpt-4\", submenu: (val, done) => modelSelector },\n  ],\n  10,      // maxVisible\n  theme,   // SettingsListTheme\n  (id, newValue) => console.log(`${id} changed to ${newValue}`),\n  () => console.log(\"Cancelled\")\n);\nsettings.updateValue(\"theme\", \"light\");\n```\n\n**Controls:**\n- Arrow keys: Navigate\n- Enter/Space: Activate (cycle value or open submenu)\n- Escape: Cancel\n\n### Spacer\n\nEmpty lines for vertical spacing.\n\n```typescript\nconst spacer = new Spacer(2); // 2 empty lines (default: 1)\n```\n\n### Image\n\nRenders images inline for terminals that support the Kitty graphics protocol (Kitty, Ghostty, WezTerm) or iTerm2 inline images. Falls back to a text placeholder on unsupported terminals.\n\n```typescript\ninterface ImageTheme {\n  fallbackColor: (str: string) => string;\n}\n\ninterface ImageOptions {\n  maxWidthCells?: number;\n  maxHeightCells?: number;\n  filename?: string;\n}\n\nconst image = new Image(\n  base64Data,       // base64-encoded image data\n  \"image/png\",      // MIME type\n  theme,            // ImageTheme\n  options           // optional ImageOptions\n);\ntui.addChild(image);\n```\n\nSupported formats: PNG, JPEG, GIF, WebP. Dimensions are parsed from the image headers automatically.\n\n## Autocomplete\n\n### CombinedAutocompleteProvider\n\nSupports both slash commands and file paths.\n\n```typescript\nimport { CombinedAutocompleteProvider } from \"@cargo-cult/pi-tui\";\n\nconst provider = new CombinedAutocompleteProvider(\n  [\n    { name: \"help\", description: \"Show help\" },\n    { name: \"clear\", description: \"Clear screen\" },\n    { name: \"delete\", description: \"Delete last message\" },\n  ],\n  process.cwd() // base path for file completion\n);\n\neditor.setAutocompleteProvider(provider);\n```\n\n**Features:**\n- Type `/` to see slash commands\n- Press `Tab` for file path completion\n- Works with `~/`, `./`, `../`, and `@` prefix\n- Filters to attachable files for `@` prefix\n\n## Key Detection\n\nUse `matchesKey()` with the `Key` helper for detecting keyboard input (supports Kitty keyboard protocol):\n\n```typescript\nimport { matchesKey, Key } from \"@cargo-cult/pi-tui\";\n\nif (matchesKey(data, Key.ctrl(\"c\"))) {\n  process.exit(0);\n}\n\nif (matchesKey(data, Key.enter)) {\n  submit();\n} else if (matchesKey(data, Key.escape)) {\n  cancel();\n} else if (matchesKey(data, Key.up)) {\n  moveUp();\n}\n```\n\n**Key identifiers** (use `Key.*` for autocomplete, or string literals):\n- Basic keys: `Key.enter`, `Key.escape`, `Key.tab`, `Key.space`, `Key.backspace`, `Key.delete`, `Key.home`, `Key.end`\n- Arrow keys: `Key.up`, `Key.down`, `Key.left`, `Key.right`\n- With modifiers: `Key.ctrl(\"c\")`, `Key.shift(\"tab\")`, `Key.alt(\"left\")`, `Key.ctrlShift(\"p\")`\n- String format also works: `\"enter\"`, `\"ctrl+c\"`, `\"shift+tab\"`, `\"ctrl+shift+p\"`\n\n## Differential Rendering\n\nThe TUI uses three rendering strategies:\n\n1. **First Render**: Output all lines without clearing scrollback\n2. **Width Changed or Change Above Viewport**: Clear screen and full re-render\n3. **Normal Update**: Move cursor to first changed line, clear to end, render changed lines\n\nAll updates are wrapped in **synchronized output** (`\\x1b[?2026h` ... `\\x1b[?2026l`) for atomic, flicker-free rendering.\n\n## Terminal Interface\n\nThe TUI works with any object implementing the `Terminal` interface:\n\n```typescript\ninterface Terminal {\n  start(onInput: (data: string) => void, onResize: () => void): void;\n  stop(): void;\n  write(data: string): void;\n  get columns(): number;\n  get rows(): number;\n  moveBy(lines: number): void;\n  hideCursor(): void;\n  showCursor(): void;\n  clearLine(): void;\n  clearFromCursor(): void;\n  clearScreen(): void;\n}\n```\n\n**Built-in implementations:**\n- `ProcessTerminal` - Uses `process.stdin/stdout`\n- `VirtualTerminal` - For testing (uses `@xterm/headless`)\n\n## Utilities\n\n```typescript\nimport { visibleWidth, truncateToWidth, wrapTextWithAnsi } from \"@cargo-cult/pi-tui\";\n\n// Get visible width of string (ignoring ANSI codes)\nconst width = visibleWidth(\"\\x1b[31mHello\\x1b[0m\"); // 5\n\n// Truncate string to width (preserving ANSI codes, adds ellipsis)\nconst truncated = truncateToWidth(\"Hello World\", 8); // \"Hello...\"\n\n// Truncate without ellipsis\nconst truncatedNoEllipsis = truncateToWidth(\"Hello World\", 8, \"\"); // \"Hello Wo\"\n\n// Wrap text to width (preserving ANSI codes across line breaks)\nconst lines = wrapTextWithAnsi(\"This is a long line that needs wrapping\", 20);\n// [\"This is a long line\", \"that needs wrapping\"]\n```\n\n## Creating Custom Components\n\nWhen creating custom components, **each line returned by `render()` must not exceed the `width` parameter**. The TUI will error if any line is wider than the terminal.\n\n### Handling Input\n\nUse `matchesKey()` with the `Key` helper for keyboard input:\n\n```typescript\nimport { matchesKey, Key, truncateToWidth } from \"@cargo-cult/pi-tui\";\nimport type { Component } from \"@cargo-cult/pi-tui\";\n\nclass MyInteractiveComponent implements Component {\n  private selectedIndex = 0;\n  private items = [\"Option 1\", \"Option 2\", \"Option 3\"];\n  \n  public onSelect?: (index: number) => void;\n  public onCancel?: () => void;\n\n  handleInput(data: string): void {\n    if (matchesKey(data, Key.up)) {\n      this.selectedIndex = Math.max(0, this.selectedIndex - 1);\n    } else if (matchesKey(data, Key.down)) {\n      this.selectedIndex = Math.min(this.items.length - 1, this.selectedIndex + 1);\n    } else if (matchesKey(data, Key.enter)) {\n      this.onSelect?.(this.selectedIndex);\n    } else if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl(\"c\"))) {\n      this.onCancel?.();\n    }\n  }\n\n  render(width: number): string[] {\n    return this.items.map((item, i) => {\n      const prefix = i === this.selectedIndex ? \"> \" : \"  \";\n      return truncateToWidth(prefix + item, width);\n    });\n  }\n}\n```\n\n### Handling Line Width\n\nUse the provided utilities to ensure lines fit:\n\n```typescript\nimport { visibleWidth, truncateToWidth } from \"@cargo-cult/pi-tui\";\nimport type { Component } from \"@cargo-cult/pi-tui\";\n\nclass MyComponent implements Component {\n  private text: string;\n\n  constructor(text: string) {\n    this.text = text;\n  }\n\n  render(width: number): string[] {\n    // Option 1: Truncate long lines\n    return [truncateToWidth(this.text, width)];\n\n    // Option 2: Check and pad to exact width\n    const line = this.text;\n    const visible = visibleWidth(line);\n    if (visible > width) {\n      return [truncateToWidth(line, width)];\n    }\n    // Pad to exact width (optional, for backgrounds)\n    return [line + \" \".repeat(width - visible)];\n  }\n}\n```\n\n### ANSI Code Considerations\n\nBoth `visibleWidth()` and `truncateToWidth()` correctly handle ANSI escape codes:\n\n- `visibleWidth()` ignores ANSI codes when calculating width\n- `truncateToWidth()` preserves ANSI codes and properly closes them when truncating\n\n```typescript\nimport chalk from \"chalk\";\n\nconst styled = chalk.red(\"Hello\") + \" \" + chalk.blue(\"World\");\nconst width = visibleWidth(styled); // 11 (not counting ANSI codes)\nconst truncated = truncateToWidth(styled, 8); // Red \"Hello\" + \" W...\" with proper reset\n```\n\n### Caching\n\nFor performance, components should cache their rendered output and only re-render when necessary:\n\n```typescript\nclass CachedComponent implements Component {\n  private text: string;\n  private cachedWidth?: number;\n  private cachedLines?: string[];\n\n  render(width: number): string[] {\n    if (this.cachedLines && this.cachedWidth === width) {\n      return this.cachedLines;\n    }\n\n    const lines = [truncateToWidth(this.text, width)];\n\n    this.cachedWidth = width;\n    this.cachedLines = lines;\n    return lines;\n  }\n\n  invalidate(): void {\n    this.cachedWidth = undefined;\n    this.cachedLines = undefined;\n  }\n}\n```\n\n## Example\n\nSee `test/chat-simple.ts` for a complete chat interface example with:\n- Markdown messages with custom background colors\n- Loading spinner during responses\n- Editor with autocomplete and slash commands\n- Spacers between messages\n\nRun it:\n```bash\nnpx tsx test/chat-simple.ts\n```\n\n## Development\n\n```bash\n# Install dependencies (from monorepo root)\nnpm install\n\n# Run type checking\nnpm run check\n\n# Run the demo\nnpx tsx test/chat-simple.ts\n```\n","readmeFilename":"README.md"}