{"id":46683949,"url":"https://github.com/wiedymi/restty","last_synced_at":"2026-03-09T01:03:01.282Z","repository":{"id":337016708,"uuid":"1152037954","full_name":"wiedymi/restty","owner":"wiedymi","description":"Powerful, lightweight web terminal. Batteries included. Powered by libghostty-vt, WebGPU, and text-shaper.","archived":false,"fork":false,"pushed_at":"2026-02-19T09:29:30.000Z","size":35565,"stargazers_count":224,"open_issues_count":1,"forks_count":10,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-19T13:52:56.513Z","etag":null,"topics":["ghostty","pty","terminal","text-shaping","typescript","wasm","webgl2","webgpu","zig"],"latest_commit_sha":null,"homepage":"https://restty.pages.dev/","language":"TypeScript","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/wiedymi.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":null,"dco":null,"cla":null}},"created_at":"2026-02-07T09:08:16.000Z","updated_at":"2026-02-19T09:29:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/wiedymi/restty","commit_stats":null,"previous_names":["wiedymi/restty"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/wiedymi/restty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiedymi%2Frestty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiedymi%2Frestty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiedymi%2Frestty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiedymi%2Frestty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wiedymi","download_url":"https://codeload.github.com/wiedymi/restty/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wiedymi%2Frestty/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30279765,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-08T20:45:49.896Z","status":"ssl_error","status_checked_at":"2026-03-08T20:45:49.525Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["ghostty","pty","terminal","text-shaping","typescript","wasm","webgl2","webgpu","zig"],"created_at":"2026-03-09T01:02:55.835Z","updated_at":"2026-03-09T01:03:01.258Z","avatar_url":"https://github.com/wiedymi.png","language":"TypeScript","funding_links":["https://github.com/sponsors/vivy-company"],"categories":["Core \u0026 Libraries"],"sub_categories":[],"readme":"# restty\n\n[![Version](https://img.shields.io/npm/v/restty?style=flat-square)](https://www.npmjs.com/package/restty)\n[![Downloads](https://img.shields.io/npm/dm/restty)](https://www.npmjs.com/package/restty)\n[![Package Size](https://img.shields.io/npm/unpacked-size/restty?style=flat-square)](https://www.npmjs.com/package/restty)\n[![CI](https://img.shields.io/github/actions/workflow/status/wiedymi/restty/ci.yml?branch=main\u0026style=flat-square)](https://github.com/wiedymi/restty/actions/workflows/ci.yml)\n[![Publish](https://img.shields.io/github/actions/workflow/status/wiedymi/restty/publish.yml?style=flat-square\u0026label=publish)](https://github.com/wiedymi/restty/actions/workflows/publish.yml)\n[![Demo](https://img.shields.io/badge/demo-restty.pages.dev-0ea5e9?style=flat-square)](https://restty.pages.dev/)\n\n[![GitHub](https://img.shields.io/badge/-GitHub-181717?style=flat-square\u0026logo=github\u0026logoColor=white)](https://github.com/wiedymi)\n[![Twitter](https://img.shields.io/badge/-Twitter-1DA1F2?style=flat-square\u0026logo=twitter\u0026logoColor=white)](https://x.com/wiedymi)\n[![Email](https://img.shields.io/badge/-Email-EA4335?style=flat-square\u0026logo=gmail\u0026logoColor=white)](mailto:contact@wiedymi.com)\n[![Discord](https://img.shields.io/badge/-Discord-5865F2?style=flat-square\u0026logo=discord\u0026logoColor=white)](https://discord.gg/zemMZtrkSb)\n[![Support me](https://img.shields.io/badge/-Support%20me-ff69b4?style=flat-square\u0026logo=githubsponsors\u0026logoColor=white)](https://github.com/sponsors/vivy-company)\n\nPowerful, lightweight browser terminal. Batteries included.\n\nLive demo: `https://restty.pages.dev/`\n\nPowered by:\n\n- `libghostty-vt` (WASM terminal core)\n- `WebGPU` (with WebGL2 fallback)\n- `text-shaper` (shaping + raster)\n\n## Release Status\n\n`restty` is in an early release stage.\n\n- Known issue: kitty image protocol handling can still fail in some edge cases.\n- API note: high-level APIs are usable now, but some APIs may still change to improve DX.\n\nIf you hit an issue, please open one on GitHub with repro steps.\n\n## Install\n\n```bash\nnpm i restty\n```\n\n## Quick Start\n\n```html\n\u003cdiv id=\"terminal\"\u003e\u003c/div\u003e\n```\n\n```ts\nimport { Restty } from \"restty\";\n\nconst restty = new Restty({\n  root: document.getElementById(\"terminal\") as HTMLElement,\n});\n\nrestty.connectPty(\"ws://localhost:8787/pty\");\n```\n\nThat is the primary API: `new Restty(...)`.\n`restty` creates pane DOM, canvas, and hidden IME input for you.\n\n## Common Tasks\n\n### Apply a built-in theme\n\n```ts\nimport { getBuiltinTheme } from \"restty\";\n\nconst theme = getBuiltinTheme(\"Aizen Dark\");\nif (theme) restty.applyTheme(theme);\n```\n\n### Parse and apply a Ghostty theme file\n\n```ts\nimport { parseGhosttyTheme } from \"restty\";\n\nconst theme = parseGhosttyTheme(`\nforeground = #c0caf5\nbackground = #1a1b26\ncursor-color = #c0caf5\n`);\n\nrestty.applyTheme(theme, \"inline\");\n```\n\n### Split panes and operate per pane\n\n```ts\nrestty.splitActivePane(\"vertical\");\nrestty.splitActivePane(\"horizontal\");\n\nfor (const pane of restty.panes()) {\n  pane.connectPty(\"ws://localhost:8787/pty\");\n}\n```\n\n### Use active-pane convenience methods\n\n```ts\nrestty.setFontSize(15);\nrestty.sendInput(\"ls -la\\n\");\nrestty.copySelectionToClipboard();\n```\n\n### Provide custom fonts\n\nBy default, restty uses a local-first font preset with CDN fallback. To fully control fonts, disable the preset and pass `fontSources`.\n\n```ts\nconst restty = new Restty({\n  root: document.getElementById(\"terminal\") as HTMLElement,\n  appOptions: {\n    fontPreset: \"none\",\n  },\n  fontSources: [\n    {\n      type: \"url\",\n      url: \"https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@v2.304/fonts/ttf/JetBrainsMono-Regular.ttf\",\n      label: \"JetBrains Mono\",\n    },\n    {\n      type: \"local\",\n      matchers: [\"jetbrains mono nerd font\", \"fira code nerd font\"],\n      label: \"Local fallback\",\n    },\n  ],\n});\n```\n\nUpdate fonts at runtime (all panes):\n\n```ts\nawait restty.setFontSources([\n  { type: \"local\", matchers: [\"sf mono\"], required: true },\n  {\n    type: \"url\",\n    url: \"https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@v3.4.0/patched-fonts/NerdFontsSymbolsOnly/SymbolsNerdFontMono-Regular.ttf\",\n  },\n]);\n```\n\n### Touch behavior (pan-first by default)\n\nOn touch devices, restty defaults to pan-first scrolling with long-press selection.\n\n```ts\nconst restty = new Restty({\n  root: document.getElementById(\"terminal\") as HTMLElement,\n  appOptions: {\n    // \"long-press\" (default) | \"drag\" | \"off\"\n    touchSelectionMode: \"long-press\",\n    // Optional tuning knobs:\n    touchSelectionLongPressMs: 450,\n    touchSelectionMoveThresholdPx: 10,\n  },\n});\n```\n\n### Plugin system (native)\n\nUse plugins when you want to extend restty behavior without patching core.\n\n```ts\nimport type { ResttyPlugin } from \"restty\";\n\nconst metricsPlugin: ResttyPlugin = {\n  id: \"example/metrics\",\n  apiVersion: 1,\n  activate(ctx) {\n    const paneCreated = ctx.on(\"pane:created\", ({ paneId }) =\u003e {\n      console.log(\"pane created\", paneId);\n    });\n    const outgoing = ctx.addInputInterceptor(({ text }) =\u003e text.replace(/\\t/g, \"  \"));\n    const lifecycle = ctx.addLifecycleHook(({ phase, action }) =\u003e {\n      console.log(\"lifecycle\", phase, action);\n    });\n    const stage = ctx.addRenderStage({\n      id: \"metrics/tint\",\n      mode: \"after-main\",\n      uniforms: [0.12],\n      shader: {\n        wgsl: `\nfn resttyStage(color: vec4f, uv: vec2f, time: f32, params0: vec4f, params1: vec4f) -\u003e vec4f {\n  return vec4f(min(vec3f(1.0), color.rgb + vec3f(params0.x, 0.0, 0.0)), color.a);\n}\n`,\n      },\n    });\n    return () =\u003e {\n      paneCreated.dispose();\n      outgoing.dispose();\n      lifecycle.dispose();\n      stage.dispose();\n    };\n  },\n};\n\nawait restty.use(metricsPlugin, { sampleRate: 1 });\nconsole.log(restty.pluginInfo(\"example/metrics\"));\nrestty.unuse(\"example/metrics\");\n```\n\nDeclarative loading (manifest + registry):\n\n```ts\nawait restty.loadPlugins(\n  [{ id: \"example/metrics\", options: { sampleRate: 1 } }],\n  {\n    \"example/metrics\": () =\u003e metricsPlugin,\n  },\n);\n```\n\nSee `docs/plugins.md` for full plugin authoring details.\n\n### Shader stages\n\nShader stages let you extend the final frame pipeline with WGSL/GLSL passes.\n\nGlobal stages:\n\n```ts\nrestty.setShaderStages([\n  {\n    id: \"app/crt-lite\",\n    mode: \"after-main\",\n    backend: \"both\",\n    uniforms: [0.24, 0.12],\n    shader: {\n      wgsl: `\nfn resttyStage(color: vec4f, uv: vec2f, time: f32, params0: vec4f, params1: vec4f) -\u003e vec4f {\n  let v = clamp(params0.x, 0.0, 0.8);\n  let centered = (uv - vec2f(0.5, 0.5)) * 2.0;\n  let vignette = max(0.0, 1.0 - v * dot(centered, centered));\n  return vec4f(color.rgb * vignette, color.a);\n}\n`,\n    },\n  },\n]);\n\nconst stage = restty.addShaderStage({\n  id: \"app/mono\",\n  mode: \"after-main\",\n  uniforms: [1.0],\n  shader: {\n    wgsl: `\nfn resttyStage(color: vec4f, uv: vec2f, time: f32, params0: vec4f, params1: vec4f) -\u003e vec4f {\n  let l = dot(color.rgb, vec3f(0.2126, 0.7152, 0.0722));\n  return vec4f(l * 0.12, l * 0.95, l * 0.35, color.a);\n}\n`,\n  },\n});\n\nstage.setEnabled(false);\nrestty.removeShaderStage(\"app/mono\");\n```\n\n### xterm compatibility layer\n\nFor migration from xterm.js-style app code, use `restty/xterm`:\n\n```ts\nimport { Terminal } from \"restty/xterm\";\n\nconst term = new Terminal({ cols: 100, rows: 30 });\nterm.open(document.getElementById(\"terminal\") as HTMLElement);\n\nterm.onData((data) =\u003e console.log(\"input\", data));\nterm.onResize(({ cols, rows }) =\u003e console.log(\"resize\", cols, rows));\n\nterm.write(\"hello\");\nterm.writeln(\" world\");\nterm.resize(120, 40);\nterm.loadAddon({\n  activate() {},\n  dispose() {},\n});\n```\n\nCompatibility scope:\n\n- Good for common embed/migration flows.\n- Not full xterm internals parity (buffer/parser/marker ecosystem APIs are not all implemented).\n- Prefer native `Restty` API for long-term integrations.\n\n## API Snapshot\n\nPrimary class:\n\n- `new Restty({ root, ...options })`\n- `createRestty(options)`\n\nXterm compatibility:\n\n- `import { Terminal } from \"restty/xterm\"`\n- Supports `open`, `write`, `writeln`, `resize`, `focus`, `blur`, `clear`, `reset`, `onData`, `onResize`, `options`, `loadAddon`, `dispose`\n\nPane access:\n\n- `panes()` / `pane(id)` / `activePane()` / `focusedPane()` / `forEachPane(visitor)`\n- `splitActivePane(\"vertical\" | \"horizontal\")` / `splitPane(id, direction)` / `closePane(id)`\n\nActive-pane convenience:\n\n- `connectPty(url)` / `disconnectPty()` / `isPtyConnected()`\n- `setRenderer(\"auto\" | \"webgpu\" | \"webgl2\")`\n- `setFontSize(number)` / `setFontSources([...])`\n- `applyTheme(theme)` / `resetTheme()`\n- `setMouseMode(\"auto\" | \"on\" | \"off\")`\n- `sendInput(text)` / `sendKeyInput(text)`\n- `copySelectionToClipboard()` / `pasteFromClipboard()`\n- `resize(cols, rows)` / `focus()` / `blur()`\n- `updateSize(force?)`\n- `destroy()`\n\nPlugin host:\n\n- `use(plugin, options?)` / `loadPlugins(manifest, registry)` / `unuse(pluginId)` / `plugins()` / `pluginInfo(pluginId?)`\n- plugin context supports `on(...)`, `addInputInterceptor(...)`, `addOutputInterceptor(...)`, `addLifecycleHook(...)`, `addRenderHook(...)`, `addRenderStage(...)`\n\nShader stages:\n\n- `setShaderStages(stages)` / `getShaderStages()`\n- `addShaderStage(stage)` / `removeShaderStage(id)`\n\n## Advanced / Internal Modules\n\nUse these only when you need lower-level control:\n\n- `restty/internal`: full internal barrel (unstable; includes low-level modules like WASM/input/pty helpers)\n\n## Local Development\n\n```bash\ngit clone https://github.com/wiedymi/restty.git\ncd restty\ngit submodule update --init --recursive\nbun install\nbun run build:themes\nbun run playground\n```\n\nOpen `http://localhost:5173`.\n\n## Code Layout\n\n- `src/surface/`: public API (`Restty`), pane manager orchestration, plugin host, xterm shim.\n- `src/runtime/`: terminal runtime/render loop implementation.\n- `src/renderer/`, `src/input/`, `src/pty/`, `src/fonts/`, `src/theme/`, `src/wasm/`, `src/selection/`: subsystem modules.\n- `src/app/`: compatibility re-export layer while internals are refactored.\n\n## Repository Commands\n\n```bash\nbun run build         # build package output\nbun run test          # full tests\nbun run test:ci       # CI-safe test target\nbun run lint          # lint\nbun run format:check  # formatting check\nbun run build:assets  # static playground bundle (playground/public/playground.js)\nbun run playground    # one-command local dev (PTY + playground dev server)\nbun run pty           # PTY websocket server only\n```\n\n## Documentation\n\n- `docs/README.md` - docs index\n- `docs/usage.md` - practical integration guide\n- `docs/xterm-compat.md` - xterm migration shim\n- `docs/how-it-works.md` - runtime flow\n- `docs/internals/` - implementation notes and architecture\n- `THIRD_PARTY_NOTICES.md` - third-party credits and notices\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwiedymi%2Frestty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwiedymi%2Frestty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwiedymi%2Frestty/lists"}