{"id":50999034,"url":"https://github.com/bombshell-dev/tty","last_synced_at":"2026-06-20T12:34:26.622Z","repository":{"id":344870305,"uuid":"1182910315","full_name":"bombshell-dev/tty","owner":"bombshell-dev","description":"Platform independent 2D layout engine for terminal applications based on Clay","archived":false,"fork":false,"pushed_at":"2026-06-17T22:20:14.000Z","size":2567,"stargazers_count":20,"open_issues_count":41,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-18T00:13:35.607Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bombshell-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"open_collective":"bombshell-dev"}},"created_at":"2026-03-16T04:37:34.000Z","updated_at":"2026-06-17T22:21:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bombshell-dev/tty","commit_stats":null,"previous_names":["cowboyd/clayterm","bombshell-dev/clayterm","bombshell-dev/tty"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/bombshell-dev/tty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Ftty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Ftty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Ftty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Ftty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bombshell-dev","download_url":"https://codeload.github.com/bombshell-dev/tty/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bombshell-dev%2Ftty/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34570538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-20T12:34:24.329Z","updated_at":"2026-06-20T12:34:26.617Z","avatar_url":"https://github.com/bombshell-dev.png","language":"C","funding_links":["https://opencollective.com/bombshell-dev"],"categories":[],"sub_categories":[],"readme":"# @bomb.sh/tty\n\nA low-level, platform-independent terminal renderer and event parser for\nJavaScript. You can use `@bomb.sh/tty` directly, or as the foundation for your\nown framework.\n\n## Features\n\n**Declarative terminal UI** — Build terminal interfaces the same way you'd build\na web page. `@bomb.sh/tty` uses [Clay](https://github.com/nicbarker/clay) under\nthe hood, giving you flexbox-like layout, pointer detection, and scroll\ncontainers — all rendered to the terminal as box-drawing characters and ANSI\nescape sequences.\n\n**Zero I/O** — `@bomb.sh/tty` never reads stdin or writes stdout. You feed it\nbytes and get bytes back. This makes it trivially embeddable in any framework,\nany runtime, any event loop. There are no opinions about how you do I/O, just\npure computation.\n\n**Runs everywhere** — The entire engine is compiled to WebAssembly, so\n`@bomb.sh/tty` will run anywhere JavaScript runs with no native dependencies,\nand no build step for consumers.\n\n### Examples\n\nSee this keyboard example and more in the [examples folder](examples/README.md).\nThis demo uses `@bomb.sh/tty` for all layout and input parsing.\n\n#### Keyboard Events\n\nThe input parser decodes raw terminal bytes into structured events. Here you can\nsee each key event as the string \"hello world\" is typed.\n\n![Keyboard events demo](examples/keyboard/keyboard-key-events.gif)\n\n#### Pointer Events\n\nHere we see hover styles applied to UI elements in response to the pointer\nstate. Clay drives the hit testing; no manual coordinate math required.\n\n![Pointer events demo](examples/keyboard/keyboard-pointer-events.gif)\n\n## Architecture\n\n`@bomb.sh/tty` does not do any I/O itself. On the ouput side, it converts UI\nelements into a raw sequence of bytes and pointer events, and on the input side,\nit converts a stream of raw bytes into structured events.\n\n### Output\n\nWith every frame, the entire UI tree is packed into a flat byte array and sent\nto WASM in a single call. On the C side, Clay runs layout, render commands are\nwalked into a cell buffer, and the buffer is diffed against the previous frame.\nOnly the cells that actually changed produce output. The result is an ANSI\nescape sequence that can be written directly to stdout. One trip to WASM per\nframe, double buffered, and only the bytes that need to change hit the output\nstream.\n\nBecause the WASM module is pure computation with no I/O, it runs anywhere\nWebAssembly does: Deno, Node, Bun, browsers, or any other runtime.\n\n```\n TypeScript                        WASM (C)\n+---------------+                +---------------------------+\n|               |  Uint32Array   |                           |\n| UI ops...     | =============\u003e | Clay layout               |\n|               |                |   -\u003e render commands      |\n+---------------+                |   -\u003e cell buffer (back)   |\n                                 |   -\u003e diff against (front) |\n                                 |   -\u003e escape bytes         |\n+---------------+                |                           |\n|               | ANSI byte array|                           |\n| stdout.write  | \u003c============= |                           |\n|               |                |                           |\n+---------------+                +---------------------------+\n```\n\n### Input\n\nRaw bytes from stdin are fed into a WASM-based parser that recognizes VT/ANSI\nescape sequences, UTF-8 codepoints, and mouse protocols (VT200, SGR, urxvt). The\nparser maintains its own internal buffer so partial sequences that arrive across\nread boundaries are reassembled automatically. A lone ESC byte is held for a\nconfigurable latency window (default 25ms) before being emitted, giving\nmulti-byte sequences time to arrive.\n\n```\n TypeScript                        WASM (C)\n+---------------+                +---------------------------+\n|               |  raw byte array|                           |\n| stdin.read    | =============\u003e | trie match (keys/seqs)    |\n|               |                |   -\u003e mouse protocol       |\n|               |                |   -\u003e UTF-8 decode         |\n+---------------+                |   -\u003e ESC codes            |\n                                 |                           |\n+---------------+                |                           |\n|               |  events[]      |                           |\n| KeyEvent      | \u003c============= |                           |\n| MouseDownEvent|                |                           |\n| MouseUpEvent  |                +---------------------------+\n| MouseMoveEvent|\n| WheelEvent    |\n| ResizeEvent   |\n+---------------+\n```\n\n## Usage\n\n### Rendering\n\nTo render this:\n\n```\n╭───────────────╮\n│ Hello, World! │\n╰───────────────╯\n```\n\n```typescript\nimport { close, createTerm, grow, open, rgba, text } from \"@bomb.sh/tty\";\n\nlet term = await createTerm({ width: 80, height: 24 });\n\nlet { output } = term.render([\n  open(\"root\", {\n    layout: { width: grow(), height: grow(), direction: \"ttb\" },\n  }),\n  open(\"greeting\", {\n    layout: { padding: { left: 1, right: 1 } },\n    border: {\n      color: rgba(0, 255, 0),\n      left: 1,\n      right: 1,\n      top: 1,\n      bottom: 1,\n    },\n    cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 },\n  }),\n  text(\"Hello, World!\"),\n  close(),\n  close(),\n]);\n\nprocess.stdout.write(output);\n```\n\n### Pointer detection\n\nPass pointer state to `render()` to have `@bomb.sh/tty` do hit detection and\nreturn pointer events in addition to the byte sequence.\n\n```typescript\nlet { output, events } = term.render(\n  [\n    open(\"root\", {\n      layout: { width: grow(), height: grow(), direction: \"ltr\" },\n    }),\n    open(\"sidebar\", {\n      layout: { width: fixed(20), height: grow() },\n      bg: rgba(30, 30, 40),\n    }),\n    text(\"Sidebar\"),\n    close(),\n    open(\"main\", {\n      layout: { width: grow(), height: grow() },\n    }),\n    text(\"Main content\"),\n    close(),\n    close(),\n  ],\n  {\n    pointer: { x: mouseX, y: mouseY, down: mouseDown },\n  },\n);\n\nfor (let event of events) {\n  // { type: \"pointerenter\", id: \"sidebar\" }\n  // { type: \"pointerleave\", id: \"sidebar\" }\n  // { type: \"pointerclick\", id: \"main\" }\n  console.log(event);\n}\n\nprocess.stdout.write(output);\n```\n\n### Input parsing\n\n```typescript\nimport { createInput } from \"@bomb.sh/tty/input\";\n\nlet input = await createInput({ escLatency: 25 });\n\nprocess.stdin.setRawMode(true);\nlet timer: ReturnType\u003ctypeof setTimeout\u003e | undefined;\n\nprocess.stdin.on(\"data\", (buf) =\u003e {\n  clearTimeout(timer);\n\n  let { events, pending } = input.scan(new Uint8Array(buf));\n\n  for (let event of events) {\n    dispatch(event);\n  }\n\n  // if a lone ESC is pending, wait and re-scan to flush it\n  if (pending) {\n    timer = setTimeout(() =\u003e {\n      let flush = input.scan();\n      for (let event of flush.events) {\n        dispatch(event);\n      }\n    }, pending.delay);\n  }\n});\n```\n\n## Development\n\nFor local source builds, toolchain setup, and `clay` submodule instructions, see\n[BUILD.md](BUILD.md).\n\nQuick local validation:\n\n```sh\nmake\ndeno task test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbombshell-dev%2Ftty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbombshell-dev%2Ftty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbombshell-dev%2Ftty/lists"}