API docs: chrismaughan.com/Draxul β or generate locally with python scripts/gen_api_docs.py.
An experimental Dark Factory Agentic project with deep visualization of generated code.
- GPU-accelerated terminal host β cross-platform (Vulkan on Windows, Metal on macOS) terminal for PowerShell, WSL, Bash, Zsh, Git, and more
- Neovim GUI frontend β a full-featured replacement for nvim-qt, with deep
nvim --embedintegration over msgpack-RPC - Agentic shell server β a per-user server owns the shells, Spaces, and agents; GPU clients attach, detach, and reconnect without losing anything
- Plugin host β a versioned C plugin ABI hands raw Vulkan/Metal frames to dynamically loaded product plugins, with Kanban and Markdown panes built in
This repository is the terminal / agentic / host core. The larger GPU products
are native plugins in their own repositories, mounted here as git submodules
under plugins/ and loaded at runtime over the C ABI:
- draxul-megacity β the interactive city view of a codebase: a living 3D city where buildings represent code, with live performance and coverage overlays. The city is a human metaphor for the code an agent is building. (Richard Wettel tried this back in 2007 β https://wettel.github.io/codecity.html β we are giving it another go, plus a BioView organism mode.)
- draxul-satview β satellite and sky visualization: SGP4-propagated CelesTrak catalogs on a 3D globe, Hipparcos starfield, ephemeris Moon/Sun/planets, HDR atmosphere.
- draxul-scoreview β MusicXML piano practice: Verovio notation, MIDI and microphone judging, and an adaptive practice stream.
- draxul-rezonality β fault-tolerant live Vulkan/Metal graphics: watched shaders and scenegraphs, multipass surfaces, OBJ/glTF models, cameras, PBR materials, and HDR environments rendered inside a pane.
None of the code has been human-written. Draxul is 100% agentically coded using multiple agents on Claude, Codex, and Gemini. Code reviews, feature updates, and planning are managed by agents with a human arbiter. A part-time project, built in less than 3 weeks at time of writing β several person-years of equivalent effort.
Click any image to view full size.
![]() Terminal host β multi-pane with htop |
![]() Diagnostics panel |
Product visuals live with their plugins: the city and coverage galleries are in draxul-megacity, and SatView / ScoreView imagery belongs to draxul-satview and draxul-scoreview.
For the full user-facing feature reference β config options, keybindings, terminal behaviour, mouse support, scrollback, and more β see docs/features.md. Product plugin documentation lives in each plugin's own repository (draxul-megacity, draxul-satview, draxul-scoreview).
- Terminal emulator β run
zsh,bash,powershell, or any shell; cross-platform - Neovim GUI β full ext_linegrid UI with deep Neovim integration
- FreeType + HarfBuzz text pipeline with a dynamic glyph atlas
- Font fallback for Nerd Font, emoji, and plugin glyph coverage
- Configurable GUI shortcuts for the diagnostics panel, clipboard copy/paste, and font zoom
- Mouse input support for click, drag, and wheel events
- HiDPI / Retina-aware rendering with correct DPI font scaling
- Shared logging with console/file fallback and category filtering
- Thin app layer with separate window, renderer, font, grid, and Neovim modules
- Built-in diagnostics panel (toggle with
F12) with live renderer, layout, and startup timing
- CMake 3.25+
- Visual Studio 2022
- Vulkan SDK with
glslc nvimonPATH(optional β required only for Neovim mode)
- CMake 3.25+
- Xcode Command Line Tools
nvimonPATH(optional β required only for Neovim mode)
All other dependencies are fetched automatically with CMake FetchContent.
Clone with submodules β the product plugins (MegaCity, SatView, ScoreView)
live in their own repositories mounted under plugins/:
git clone --recurse-submodules https://github.com/cmaughan/DraxulIn an existing checkout, git submodule update --init fetches them. A checkout
without the submodules still configures and builds β you get the core terminal
with Kanban/Markdown and no product plugins.
The recommended path is do.py; Debug + Ninja is the default development cache:
py do.py build debug
py do.py run debug
py do.py test debugUse Release for the final build/startup confirmation:
py do.py run releaseDebug development and the final Release confirmation use the same commands:
python3 do.py build debug
python3 do.py run debug
python3 do.py test debug
python3 do.py run releaseWith no arguments, Draxul discovers or starts the per-user Draxul server and opens
its shared default shell Session. Closing the UI leaves the server, shells, Spaces,
and agents running; the next UI reconnects to them. Core hosts use --host;
standalone products are created as plugin panes or tabs through the shared server.
.\build-ninja-release\draxul.exe # shared PowerShell Session
.\build-ninja-release\draxul.exe --host nvim # embedded Neovim
.\build-ninja-release\draxul.exe --host powershell # shared PowerShell Session
.\build-ninja-release\draxul.exe plugin get dev.draxul.megacity --json
.\build-ninja-release\draxul.exe tab create --space <space-id> --name MegaCity --plugin dev.draxul.megacity --plugin-config '{"mode":"city","source":"C:/dev/linux"}' --json
.\build-ninja-release\draxul.exe --server-status
.\build-ninja-release\draxul.exe --list-sessions # live server Sessions
.\build-ninja-release\draxul.exe --delete-session --session work --yes
.\build-ninja-release\draxul.exe --shutdown-server --yes # confirms live-shell shutdownThe server also exposes a Windows notification-area status menu with Open Draxul, status counts, log access, and guarded stop actions.
./build/draxul.app/Contents/MacOS/draxul # shared login-shell Session
./build/draxul.app/Contents/MacOS/draxul --host nvim # embedded Neovim
./build/draxul.app/Contents/MacOS/draxul --host zsh # shared Zsh Session
./build/draxul.app/Contents/MacOS/draxul tab create --space <space-id> --name BioView --plugin dev.draxul.megacity --plugin-config '{"mode":"biology","source":"/Users/me/dev/linux"}' --jsonOr launch via Finder / open:
open ./build/draxul.appSupported --host values include nvim, markdown, kanban, zsh, bash,
powershell / pwsh (Windows), and wsl (Windows). MegaCity and BioView use
the stable plugin ID dev.draxul.megacity; mode selects the view and source
sets the local Tree-sitter scan root.
Draxul stores user settings in a config.toml file under the platform app-config directory.
Programming ligatures are enabled by default. Set enable_ligatures = false if you prefer raw per-character glyphs.
GUI-level shortcuts can be remapped under a [keybindings] table:
enable_ligatures = true
[keybindings]
toggle_diagnostics = "F12"
copy = "Ctrl+Shift+C"
paste = "Ctrl+Shift+V"
font_increase = "Ctrl+="
font_decrease = "Ctrl+-"
font_reset = "Ctrl+0"These bindings affect only Draxul-handled GUI actions. Normal Neovim input and keymaps still go through Neovim.
The root do.py script is the recommended entry point for common tasks:
./do.py run # build (if needed) and launch Draxul
./do.py test # Debug build + parallel core suite in the same cache
./do.py test --satview # Add SatView's test suite
./do.py test --products # Add every product test suite
./do.py test --all # Complete unit inventory
./do.py smoke --skip-build # reuse that built app for the startup smoke test
./do.py clean # remove repository-root build/ and build-* directories
./do.py basic # run basic-view render snapshot compare
./do.py cmdline # run cmdline-view render snapshot compare
./do.py unicode # run unicode-view render snapshot compare
./do.py panel # run panel-view render snapshot compare
./do.py renderall # run all four render snapshot compares
./do.py blessbasic # bless basic-view reference image
./do.py blesscmdline # bless cmdline-view reference image
./do.py blessunicode # bless unicode-view reference image
./do.py blesspanel # bless panel-view reference image
./do.py blessall # bless all four reference images
./do.py api # build local Doxygen API docs
./do.py docs # build all documentation artifacts
./do.py shot # regenerate the README hero screenshot
./do.py coverage # macOS: export build/coverage.lcov and refresh db/coverage.lcovOn Windows, use py do.py <command>.
py do.py build relwithdebinfo # Windows: optimized build with PDB symbols
py do.py run # Debug build + run (Ninja on Windows)
py do.py test # Same Debug cache + parallel core suite
py do.py test --scoreview # Core + ScoreView suites
py do.py test --products # Core + every product suite
py do.py test --all # Complete unit inventory
py do.py smoke --skip-build # Startup check without another build
py do.py run release # Final Release build + startup confirmation
py do.py run release --vs # Explicit VS-generator check when needed
py do.py run --console # Attach a debug console (Windows)
py do.py clean # Remove build/ and build-*/do.py clean removes repository-root build trees named build/ or build-* (including the Visual Studio, Ninja, and tooling build directories). It leaves deploy packages, render outputs, render references, databases, source files, and similarly named regular files untouched; running it when no matching build directory exists succeeds.
The repository includes lightweight native tests for grid logic, redraw parsing, input translation, RPC behavior, renderer state, and Unicode width conformance against headless Neovim.
For the normal edit-test loop, do.py build, do.py run, and do.py test share the same selected generator, configuration, and build tree. They default to Debug and Ninja on Windows. do.py test builds the core test aggregate and its dependenciesβincluding the app required by the core test contractβthen runs only the core, app, Markdown/Kanban, and Python workflow tests with bounded parallelism. It does not launch the app or run smoke/render comparisons.
Product tests are opt-in and additive: use --megacity, --satview, --scoreview, or --rezonality when changing that product or a shared seam it consumes. Use --products for changes to shared plugin SDK/support/renderer code that can affect every product. --all builds the historical draxul-tests aggregate and runs the complete unit inventory. The individual product flags and --products keep the build scoped to their named aggregates; --all is the explicit broad acceptance path. The Rezonality scope additionally runs its real PBR edit/error/recovery integration and fixed robot render comparison; those cases are omitted from core-only runs and are not registered when the submodule target is unavailable.
Before committing, run do.py test debug with any relevant product flag, then do.py smoke --skip-build so smoke reuses the app already produced in that cache. Run only the relevant render shortcut for rendering-affecting changes. Finish a completed feature or bug fix with do.py run release and confirm startup; headless automation can use do.py run release --console -- --smoke-test.
On Windows, a long-lived Draxul server keeps its selected cache's draxul-server.exe open. If the Debug app has since been relinked, run process-launch tests before starting that server or safely stop that exact server after checking for connected clients and live terminals. Do not switch the entire test pass to another generator merely to avoid the helper lock.
The t.sh, t.bat, and scripts/run_tests.* wrappers remain available for an explicitly requested broad or multi-configuration validation pass. They are not the normal edit-build-test path and should not be stacked with equivalent do.py validation.
Default is Debug:
scripts\run_tests.batOther modes:
scripts\run_tests.bat release
scripts\run_tests.bat both
scripts\run_tests.bat --reconfigure
scripts\run_tests.bat --verbose
scripts\run_tests.bat --unitDefault is Debug:
./scripts/run_tests.shOther modes:
./scripts/run_tests.sh release
./scripts/run_tests.sh both
./scripts/run_tests.sh --reconfigure
./scripts/run_tests.sh --verbose
./scripts/run_tests.sh --unitThe test scripts reuse the existing CMake cache when possible and only reconfigure when needed. Their default is broad validation; --unit still selects the complete CTest unit inventory, whereas the normal do.py test path is core-scoped unless a product flag is supplied. By default they use failure-only CTest output so CI logs stay readable; pass --verbose when you want full per-test output locally.
The CTest suite also includes:
- an app startup smoke test when
nvimis available onPATH - a render snapshot regression test when the platform reference image exists under
tests/render/reference/
Draxul can now run deterministic render-snapshot tests by capturing pixels directly from the renderer output instead of taking a desktop screenshot.
Example compare run:
.\build\Debug\draxul.exe --console --render-test D:\dev\draxul\tests\render\basic-view.tomlBless a new reference image:
.\build\Debug\draxul.exe --console --render-test D:\dev\draxul\tests\render\basic-view.toml --bless-render-testUpdate the documentation screenshot for the current platform:
python .\scripts\update_screenshot.pyNotes:
update_screenshot.pyuses the presentation-orientedtests/render/readme-hero.tomlscenario by default, so it captures your normal Neovim theme and statusline instead of the clean-u NONE --nopluginregression setup.- The deterministic render regression scenarios remain under
tests/render/and continue to use fixed startup settings for stable compare/bless behavior.
Behavior:
- the scenario fixes window size, font, and Neovim startup commands
- Draxul waits for redraw activity to settle
- the renderer reads back the presented frame
- output is compared against a platform-specific reference image
actual,diff, andreportartifacts are written undertests/render/out/
Reference images live under tests/render/reference/ with platform suffixes like basic-view.windows.bmp and basic-view.macos.bmp.
Current scenarios:
basic-view: line numbers, signcolumn, cursorline, and baseline text layoutcmdline-view: bottom-row command-line renderingunicode-view: graphemes, emoji, wide glyphs, and Nerd Font/plugin iconspanel-view: visible diagnostics panel layout and content
Draxul now uses a shared repo-local logger across the app, RPC/process layer, windowing, font stack, and renderers.
Environment controls:
$env:DRAXUL_LOG = "debug"
$env:DRAXUL_LOG_CATEGORIES = "app,rpc,font"
$env:DRAXUL_LOG_FILE = "logs\\draxul.log"Notes:
- Default level is
info. - Categories are comma-separated.
- GUI launches without a console will fall back to a log file automatically.
- The DPI diagnostics in the window layer are now
debug-only instead of always-on.
Press F12 to toggle the built-in diagnostics panel. This can be remapped via config.toml under [keybindings].
The panel is a bottom-aligned dockable window rendered at native physical-pixel resolution using the same font as the terminal. It exposes live runtime state across three tabs:
Window β window and terminal region dimensions, display DPI, cell size, and grid dimensions
Renderer β last frame time, rolling average frame time, dirty-cell count, and glyph atlas occupancy, count, and reset statistics
Startup β per-phase initialisation timing (Config, Window + Renderer, Font, ImGui, Host) and total wall-clock time
The panel does not intercept any keyboard input β the terminal remains fully interactive while it is visible.
draxul/
βββ app/ # App startup and main orchestration
βββ libs/
β βββ draxul-types/ # Shared POD types and event structs
β βββ draxul-window/ # Window abstraction and SDL implementation
β βββ draxul-renderer/ # Public renderer API and platform backends
β βββ draxul-font/ # Font loading, shaping, glyph cache
β βββ draxul-grid/ # Cell grid and highlight state
β βββ draxul-nvim/ # Neovim process, RPC, redraw handling, input
βββ plugins/
β βββ megacity/ # Submodule β github.com/cmaughan/draxul-megacity
β βββ satview/ # Submodule β github.com/cmaughan/draxul-satview
β βββ scoreview/ # Submodule β github.com/cmaughan/draxul-scoreview
β βββ spinning-triangle/ # In-repo reference plugin and ABI test vehicle
β βββ support/ # Generic plugin-support code (ImGui host)
βββ sdk/ # Public C plugin ABI (Draxul::PluginSDK)
βββ shaders/ # Vulkan and Metal shader sources
βββ fonts/ # Bundled font assets copied next to the app
βββ tests/ # Native test executable and fixture helpers
βββ scripts/ # Build/test convenience scripts
For a guided human-facing overview of the repo structure, generated diagrams, and validation entry points, see docs/module-map.md.
GitHub Actions builds and tests the project on:
- Windows
- macOS
The workflow runs automatically for pushes and pull requests to main, and can also be started manually.
The workflow uses the same repo-local test scripts as local development, including the startup smoke test.
- Windows uses a multi-config Visual Studio generator through
CMakePresets.json. - The renderer boundary is owned by
draxul-renderer; app code should not include backend-private headers. - Grapheme handling is much better than the original single-codepoint path, but broad Unicode width conformance against Neovim is still future hardening work.
- Visual regression testing now prefers direct swapchain/drawable readback over desktop screenshots so comparisons stay deterministic across window-manager state.
Regenerate with the prompt in plans/prompts/architecture_diagram.md.
Regenerate with python scripts/build_docs.py.
The published API reference is available at chrismaughan.com/draxul. Generate an up-to-date local copy with python scripts/gen_api_docs.py --output docs/api.
To generate locally:
python scripts/gen_api_docs.pyThis writes a local Doxygen site to docs/api/index.html.
Reference image:
What the render smoke does:
- launches a deterministic Neovim UI scenario at a fixed size with fixed fonts and commands
- waits for redraw activity to settle instead of capturing a half-initialized frame
- reads pixels back from the renderer output directly, not from the desktop compositor
- compares the captured image against a blessed platform reference
- writes
actual,diff, andreportartifacts undertests/render/out/
Why this is useful:
- it catches visual regressions that ordinary unit tests miss, such as tofu, broken fallback fonts, missing line numbers, layout shifts, or highlight mistakes
- the
diffartifact makes it obvious what changed and roughly how much changed - the
reportgives a mechanical pass/fail threshold instead of relying on guesswork --bless-render-testgives a controlled way to accept intentional visual changes




