Contributing
Prerequisites
- Rust >= 1.85 (tested with 1.96)
- Godot 4.4+ (tested with 4.7.2) with GDExtension support
- Linux (primary target), macOS (untested) or Windows 11 (untested).
- FFmpeg build dependencies for
./scripts/media-deps:curl, a C toolchain,make, andnasmon x86 Linux (without nasm the script warns and disables hand-written SIMD). Windows uses vcpkg (VCPKG_ROOT). The script builds the pinned FFmpeg the GDExtension links. - Python 3 (for
scripts/docs-checkandscripts/build-wiki).
Setup
Clone the repository
# Clone
git clone https://github.com/godot-pty/gpty.git gpty
cd gptyInstall git hooks (one-time per clone)
./scripts/install-hooksThis installs:
pre-commit(fast checks:fmt,lint,clippy)commit-msg(Conventional Commits enforcement)pre-push(full CI suite).
Run ./scripts/ci-check directly to validate changes before committing.
Build media dependencies (once per checkout)
The GDExtension links a pinned FFmpeg; build it before the first cargo build:
./scripts/media-deps
source dist/ffmpeg/<platform>-<arch>/env.sh # in every new shellci-check builds or sources this prefix automatically. The library must be beside the extension
for a bundled run; $ORIGIN/@loader_path is baked into the GDExtension, so a dev build falls
back to the system FFmpeg when the pinned one is not on LD_LIBRARY_PATH/DYLD_LIBRARY_PATH.
Build
Editor / day-to-day (res://bin/*.so should symlink to target/debug/):
ln -sfn ../../target/debug/libgpty_gdext.so godot/bin/libgpty_gdext.linux.x86_64.so
cargo build -p gpty-gdext && cd godot && godot -eRestart the editor after a rebuild so it remaps the .so.
One-shot standalone build (detects the host platform, builds gpty-gdext in release mode, stages it into godot/bin/, and exports into dist/):
./scripts/buildRequires Godot on PATH with export templates for exactly that version: a 4.7.2.stable editor needs 4.7.2.stable templates, and a symlinked older directory silently exports with the other engine build. The script fails early with the download command when they are missing. It also calls scripts/bundle-media after the export, so the host bundle carries the pinned FFmpeg runtime beside the GDExtension (run ./scripts/media-deps first).
The manual steps below are what the script does internally.
# Build the GDExtension library (required before running Godot)
cargo build -p gpty-gdext
# Release build + stage into godot/bin for export
cargo build -p gpty-gdext --release
cp target/release/libgpty_gdext.so godot/bin/libgpty_gdext.linux.x86_64.so
godot --headless --path godot --import
godot --headless --path godot --export-release "Linux/X11" ../dist/gptyClean
Remove build/test artifacts for a clean slate - stale IPC sockets, Godot import caches, and standalone dist/ outputs:
./scripts/clean # default: transient artifacts
./scripts/clean --dry-run # list what would be removed
./scripts/clean --all # also remove target/ and built gdext librariesSockets with a live listener (a running GUI or an in-flight test run) are never touched, and user data (user:// settings, profiles, layouts, history.db) is preserved. Tracked files such as dist/aur/ and godot/.gutconfig.json are left alone.
MCP
Generate the current tools manifest: cargo run --bin gpty -- schema --format mcp
Run
# Open in Godot editor
cd godot && godot -e
# Launch the GUI (backgrounded; add --headless for headless)
godot --path godot &The GUI starts an IPC server on $XDG_RUNTIME_DIR/gpty.sock (or GPTY_SOCKET env var if set).
Once running, control it with the CLI:
cargo run --bin gpty -- version
cargo run --bin gpty -- new-pane -t terminal
cargo run --bin gpty -- list-panes
cargo run --bin gpty -- inject T1 -t "echo hello"
cargo run --bin gpty -- daemon stopcargo run always builds the CLI from this checkout, but a cargo installed
gpty on PATH can shadow a release bundle’s CLI: after a version bump,
reinstall it (cargo install --path crates/gpty-cli --force). The stale binary
otherwise fails opaquely - a subcommand it lacks dies in clap before any IPC,
which is why the CLI warns once when it reads a running GUI whose version or
IPC protocol differs.
Standalone commands (no GUI needed):
cargo run --bin gpty -- schema # JSON Schema
cargo run --bin gpty -- schema --format mcp # MCP tools manifest
echo '{"jsonrpc":"2.0","id":1,"method":"initialize"}' | cargo run --bin gpty -- mcpTest
- Automated:
./scripts/ci-checkruns the full suite (Rust + GUT + audit). CI runs on every push tomainand every PR. - Manual pre-release: docs/content/docs/overview/testing.md - smoke tests for CLI bridge, daemon lifecycle, UI shortcuts, and error paths that require a running GUI.
- Test format:
Given / When / Thenwith expected output. See the manual checklist for examples.
# Rust tests only
cargo test --workspace # Tests across core, gdext, cli
cargo test -p gpty-core # Core library only
# Rust type-check (fast, no codegen)
cargo check
# Godot (GUT) tests only
godot --headless --path godot --import # Required before first run
./scripts/gut-check # runs GUT and fails if the run aborted (GUT exits 0 regardless)
# OMP extension unit tests (no omp install required)
(cd extensions/gpty-omp-events && node --test)
# Explicitly link the OMP observability plugin (user action; gpty never auto-installs)
omp plugin link "$(pwd)/extensions/gpty-omp-events"
# Workflow lint (valid YAML + a top-level permissions block in every workflow)
./scripts/lint-workflows
# Run all CI checks locally (fmt, workflow lint, clippy, tests, GUT, audit)
./scripts/ci-check
# Fast checks only (fmt, workflow lint, clippy)
./scripts/ci-check --fast
# Terminal renderer cost (needs a display; see the script header)
godot --path godot --disable-vsync -s res://tests/bench/draw_bench.gd
# Markdown preview cost: Rust render + parse + the label's shape pass
godot --headless --path godot -s res://tests/bench/markdown_bench.gdThe render benchmark fills a pane with a deterministic ANSI-rich screen, prints
the cost of a forced full repaint against an idle frame, and saves the rendered
frame to /tmp/gpty_draw_<BENCH_TAG>.png. Two revisions must render
byte-identical pixels, so diff the two PNGs before trusting a timing delta.
The Markdown benchmark prints render_ms / parse_ms / shape_ms /
total_ms per size for a repeated block; shape_ms is the frame after the
text lands, which is the pass a document’s markup can make superlinear (see
GptyMarkdown’s docs) and the number a change to the renderer’s output has to
be argued against. Run it against a release GDExtension
(cargo build --release -p gpty-gdext, then point the platform’s
godot/bin/libgpty_gdext.* symlink at target/release/) for the numbers a
user would see, and restore the debug symlink afterwards.
CLI (control a running GUI)
cargo run --bin gpty -- new-pane --pane-type terminal # Create a new terminal pane
cargo run --bin gpty -- new-pane --pane-type inspector # Private Inspector Q&A pane
cargo run --bin gpty -- new-pane --pane-type reasoning # Passive Reasoning pane
cargo run --bin gpty -- list-panes # List active panes
cargo run --bin gpty -- schema # JSON Schema for AI tools
cargo run --bin gpty -- schema --format mcp # MCP tools manifestVerbose logging: RUST_LOG=debug cargo run --bin gpty -- version
Project Structure
See Agent Guide for the full directory tree.
Code Style
See the agent guide for the complete conventions and patterns: Rust, GDScript, and pitfalls.
Pull Request Process
- Fork the repository.
- Create your feature branch.
- Make your changes.
- Test your changes (functionally).
- Run
./scripts/ci-check- ensure all checks pass - Add or update test cases as applicable.
- See Agent Guide for commit information.
- Submit a pull request.
Security
See the security rules for the full list, including Concept Engine ReDoS prevention and OSC 52 clipboard restrictions.
License
gPTY is licensed under the GNU General Public License, version 3 or later - see License. License exceptions adds section 7 permissions so plugins, extensions, adapters, and data files can stay permissive (Apache-2.0, MIT, or their authors’ own terms).
There is no CLA. By opening a pull request you confirm that you wrote the contribution (or otherwise have the right to submit it) and you license it to the project under those same terms: GPL-3.0-or-later, plus the exceptions in LICENSE-EXCEPTIONS.md.