A terminal drawing editor for developers who want editable diagrams, UI mocks, and text graphics without leaving the terminal.
@termdraw/app— the standalone terminal app with thetermdrawcommand@termdraw/opentui— embeddable OpenTUI components and renderables@termdraw/pi— Pi package that opens termDRAW in a Pi overlay
Requirements:
- Bun 1.3+
- A terminal with mouse support
npm install --global @termdraw/apptermdrawDraw something, then press Enter or Ctrl+S to export the rendered art to stdout.
Press Ctrl+D to save the editable diagram as a native .td.json document. If you opened a diagram with --load, termDRAW reuses that path by default; otherwise it prompts for one inside the app.
# open an editable native document from a file
termdraw --load architecture.td.json
# open an editable native document from stdin
# requires a controlling terminal for the interactive editor session
cat architecture.td.json | termdraw --load -
# save the rendered art directly to a file
termdraw --load architecture.td.json --output diagram.txt
# save plain text directly to a file
termdraw --output diagram.txt
# export a fenced Markdown code block
termdraw --fenced > diagram.md
# copy to the clipboard with Enter instead of exporting (Ctrl+S still exports)
termdraw --clipboard --fenced
# show CLI help
termdraw --helptermDRAW! outputs terminal text, not SVG or bitmap graphics.
With --clipboard, Enter copies the rendered drawing to the system clipboard and keeps the editor
open, so you can copy, refine, and copy again; Ctrl+S still exports and exits. Copying uses the OSC
52 escape sequence, which the terminal handles rather than the host OS, so it also works over SSH.
Terminals send no acknowledgement for OSC 52, and one that does not support it (or has it disabled)
ignores the request silently. Inside tmux, set set-clipboard to on or external.
Use native .td.json documents when you want to reopen and keep editing a drawing. Plain-text output remains an export format and does not preserve the original object metadata.
pi install npm:@termdraw/piThen inside Pi:
/termdraw
npm install @termdraw/opentui @opentui/core @opentui/react reactimport { createCliRenderer } from "@opentui/core";
import { createRoot } from "@opentui/react";
import { TermDrawApp } from "@termdraw/opentui";
const renderer = await createCliRenderer({
useMouse: true,
enableMouseMovement: true,
autoFocus: true,
screenMode: "alternate-screen",
});
createRoot(renderer).render(
<TermDrawApp
width="100%"
height="100%"
autoFocus
initialDocument={existingDocument}
diagramPath="architecture.td.json"
onSave={(art) => {
console.log(art);
}}
onSaveDiagram={async (document, path) => {
await Bun.write(path, `${JSON.stringify(document, null, 2)}\n`);
}}
onCancel={() => {
renderer.destroy();
}}
/>,
);Also exported from @termdraw/opentui:
TermDrawAppTermDrawEditorTermDrawTermDrawAppRenderableTermDrawEditorRenderableTermDrawRenderableformatSavedOutputbuildHelpTextparseDrawDocument
- App package:
packages/app - OpenTUI package:
packages/opentui - Pi package:
packages/pi
Contributions are welcome.
Before opening a PR:
- keep the change focused
- run
bun run check - add or update tests when editor behavior changes
- open an issue first for larger UX or API changes
Please report security issues privately through GitHub Security Advisories:
MIT. See LICENSE.
