diff --git a/.agents/skills/verify-libghostty-vt-node/SKILL.md b/.agents/skills/verify-libghostty-vt-node/SKILL.md
index c05a4cf..caee26b 100644
--- a/.agents/skills/verify-libghostty-vt-node/SKILL.md
+++ b/.agents/skills/verify-libghostty-vt-node/SKILL.md
@@ -17,7 +17,7 @@ S=.agents/skills/verify-libghostty-vt-node/scripts
## Requirements
-- The toolchain that `mise.toml` pins: Node `26.12.1` and Zig `0.15.2` on `PATH`. `mise install` installs them. If `mise exec` fails on your host, put the two install directories on `PATH` directly, for example `~/.local/share/mise/installs/node/26.12.1/bin` and `~/.local/share/mise/installs/zig/0.15.2`.
+- The toolchain that `mise.toml` pins: Node `26.12.1` and Zig `0.17.0` on `PATH`. `mise install` installs them. If `mise exec` fails on your host, put the two install directories on `PATH` directly, for example `~/.local/share/mise/installs/node/26.12.1/bin` and `~/.local/share/mise/installs/zig/0.17.0`.
- A C++17 compiler and Python for `node-gyp`.
- A checkout that mise trusts. In an untrusted copy, a mise shim that a build step calls fails with `Config files in .../mise.toml are not trusted`, and `launch.sh` exits `1`.
- The network on the first launch: `npm ci`, the Ghostty clone in `npm run build:libghostty`, and the dependency install in the consumer project.
diff --git a/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh b/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh
index 90d3d2c..5951eb6 100755
--- a/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh
+++ b/.agents/skills/verify-libghostty-vt-node/scripts/launch.sh
@@ -102,7 +102,7 @@ lib_dir="${LIBGHOSTTY_VT_PREFIX:-$ROOT/vendor/libghostty-vt}"
lib_stamp="$lib_dir/.verify-libghostty-vt-node.sha256"
lib_inputs="$( { cat scripts/build-libghostty-vt.sh; env | grep -E '^(GHOSTTY_|LIBGHOSTTY_VT_)' | sort || true; } | sha_stdin)"
if [[ ! -f "$lib_dir/lib/libghostty-vt.a" || "$(cat "$lib_stamp" 2>/dev/null)" != "$lib_inputs" ]]; then
- step "npm run build:libghostty (missing, or built from other inputs: needs zig 0.15.2 and the network)"
+ step "npm run build:libghostty (missing, or built from other inputs: needs zig 0.17.0 and the network)"
run npm run build:libghostty
echo "$lib_inputs" > "$lib_stamp"
fi
diff --git a/README.md b/README.md
index 46b4118..9952b76 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ This bootstrap was written against:
- libghostty Doxygen:
- Node-API docs:
+The build now pins Ghostty `v1.4.0` (`83fb95624b0439ee660c2679b43b38c8b63769cd`), which needs Zig 0.17.0.
+
The binding talks to `libghostty-vt` directly through the C API. It has no runtime dependency on `go-libghostty`.
## API
@@ -63,8 +65,6 @@ All dimensions are validated as positive integers. Using a terminal after `dispo
`snapshot()` reports the cursor position (`cursorRow`, `cursorCol`) and its DECTCEM visibility (`cursorVisible`, toggled by `ESC[?25h` and `ESC[?25l`). With `includeCells: true`, each cell reports its text, width, optional `foreground` and `background` colors, and these SGR attributes: `bold`, `faint`, `italic`, `underline`, `inverse`, `invisible`, and `strikethrough`. An attribute is present only when it is set. `inverse` is the raw SGR 7 flag: `foreground` and `background` are not swapped, so consumers apply reverse video with their own default colors.
-The pinned Ghostty ignores the DECSTR soft reset (`ESC[!p`). After it, a hidden cursor stays hidden and SGR attributes stay set. Programs that reset with `ESC c` (RIS) are unaffected.
-
### Mouse encoding
`encodeMouse` returns a `Buffer` containing the terminal input bytes for one
@@ -191,7 +191,7 @@ Manual dispatch and direct `v*.*.*` tag pushes are available for recovery, but m
The native layer currently uses these verified `libghostty-vt` C APIs:
-- terminal lifecycle and stream processing: `ghostty_terminal_new`, `ghostty_terminal_vt_write`, `ghostty_terminal_resize`, `ghostty_terminal_free`
+- terminal lifecycle and stream processing: `ghostty_terminal_new`, `ghostty_terminal_set`, `ghostty_terminal_vt_write`, `ghostty_terminal_resize`, `ghostty_terminal_free`
- metadata and state: `ghostty_terminal_get`, `ghostty_build_info`
- mode-aware mouse input: `ghostty_mouse_encoder_*`, `ghostty_mouse_event_*`
- plain/HTML debug formatting: `ghostty_formatter_terminal_new`, `ghostty_formatter_format_alloc`
diff --git a/dogfood/README.md b/dogfood/README.md
index 7658283..91ce66d 100644
--- a/dogfood/README.md
+++ b/dogfood/README.md
@@ -1,6 +1,8 @@
# Dogfood Proof Bundle
-`dogfood/bootstrap/` records the commands and local outputs from the repository bootstrap. The bundle is intentionally plain text so a reviewer can inspect what was attempted without reproducing the environment first.
+`dogfood/bootstrap/` records the commands and local outputs from the repository bootstrap, rerun after each Ghostty pin change. The bundle is intentionally plain text so a reviewer can inspect what was attempted without reproducing the environment first.
+
+To rerun it, run `dogfood/regenerate.sh` from a checkout with the `mise.toml` toolchain. It runs `commands.sh` step by step and rewrites the output files and `environment.txt`.
Expected files:
diff --git a/dogfood/bootstrap/build-libghostty-output.txt b/dogfood/bootstrap/build-libghostty-output.txt
index 86c1a6d..deffa46 100644
--- a/dogfood/bootstrap/build-libghostty-output.txt
+++ b/dogfood/bootstrap/build-libghostty-output.txt
@@ -1,63 +1,11 @@
+$ npm run build:libghostty
-> @coder/libghostty-vt-node@0.1.0 build:libghostty
+> @coder/libghostty-vt-node@0.1.0-beta.1 build:libghostty
> scripts/build-libghostty-vt.sh
From https://github.com/ghostty-org/ghostty
- * branch 48ccec182a932c2ec04c344d45a5fc553861cb13 -> FETCH_HEAD
-HEAD is now at 48ccec182 ci: fix vouch-check-issue to checkout the template file (#12412)
-Building libghostty-vt at Ghostty commit 48ccec182a932c2ec04c344d45a5fc553861cb13
-error: undefined symbol: __availability_version_check
- note: referenced by /Users/thomask33/.cache/zig/o/8cfa15f510d85730135433e2f42ddfd8/libcompiler_rt.a(libcompiler_rt_zcu.o):___isPlatformVersionAtLeast
-error: undefined symbol: _abort
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.abort
-error: undefined symbol: _arc4random_buf
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_crypto.tlcsprng.tlsCsprngFill
-error: undefined symbol: _bzero
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_hash_map.HashMapUnmanaged(usize,*debug.SelfInfo.Module__struct_3895,hash_map.AutoContext(usize),80).initMetadatas
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_hash_map.HashMapUnmanaged([]const u8,u64,hash_map.StringContext,80).initMetadatas
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_hash_map.HashMapUnmanaged([]const u8,debug.SelfInfo.Module__struct_3895.OFileInfo,hash_map.StringContext,80).initMetadatas
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.wait
- note: referenced 14 more times
-error: undefined symbol: _clock_gettime
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.clock_gettime
-error: undefined symbol: _dispatch_queue_create
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.init
-error: undefined symbol: _dispatch_semaphore_create
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.init
-error: undefined symbol: _dispatch_semaphore_signal
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.eventCallback
-error: undefined symbol: _dispatch_semaphore_wait
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.wait
-error: undefined symbol: _dispatch_time
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_Build.Watch.FsEvents.wait
-error: undefined symbol: _exit
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.exit
-error: undefined symbol: _fcopyfile
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_fs.File.Writer.sendFile
-error: undefined symbol: _fork
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.fork
-error: undefined symbol: _free
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_heap.CAllocator.alignedFree
-error: undefined symbol: _getcwd
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.getcwd
-error: undefined symbol: _getenv
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.getenvZ
-error: undefined symbol: _isatty
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.isatty
-error: undefined symbol: _malloc_size
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_heap.CAllocator.alignedAllocSize
-error: undefined symbol: _posix_memalign
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_heap.CAllocator.alignedAlloc
-error: undefined symbol: _realpath$DARWIN_EXTSN
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.realpathZ
-error: undefined symbol: _sigaction
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.sigaction
-error: undefined symbol: _sigemptyset
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.sigemptyset
-error: undefined symbol: _sysctlbyname
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.sysctlbynameZ
-error: undefined symbol: _waitpid
- note: referenced by .zig-cache/o/0ef0335be7a527a9e3eb98418e651f74/build_zcu.o:_posix.waitpid
-error: failed to build libghostty-vt from Ghostty 48ccec182a932c2ec04c344d45a5fc553861cb13
-Verify that Zig 0.15.2 can build a simple project on this machine and that the platform SDK is discoverable.
-On macOS, check `xcrun --show-sdk-path` and the Xcode command line tools selected by `xcode-select -p`.
+ * branch 83fb95624b0439ee660c2679b43b38c8b63769cd -> FETCH_HEAD
+HEAD is now at 83fb95624 Bump version to 1.4.0
+Building libghostty-vt at Ghostty commit 83fb95624b0439ee660c2679b43b38c8b63769cd
+Installed libghostty-vt into /home/coder/.mux/src/libghostty-vt-node/tough-seals-think/vendor/libghostty-vt
+exit=0
diff --git a/dogfood/bootstrap/build-native-output.txt b/dogfood/bootstrap/build-native-output.txt
index a5fa5f6..7d3763b 100644
--- a/dogfood/bootstrap/build-native-output.txt
+++ b/dogfood/bootstrap/build-native-output.txt
@@ -1,30 +1,31 @@
+$ npm run build:native
-> @coder/libghostty-vt-node@0.1.0 build:native
+> @coder/libghostty-vt-node@0.1.0-beta.1 build:native
> node-gyp rebuild
gyp info it worked if it ends with ok
-gyp info using node-gyp@12.3.0
-gyp info using node@25.9.0 | darwin | arm64
-gyp info find Python using Python version 3.14.4 found at "/opt/homebrew/opt/python@3.14/bin/python3.14"
+gyp info using node-gyp@12.4.0
+gyp info using node@26.12.1 | linux | x64
+gyp info find Python using Python version 3.14.4 found at "/usr/bin/python3"
-gyp info spawn /opt/homebrew/opt/python@3.14/bin/python3.14
+gyp info spawn /usr/bin/python3
gyp info spawn args [
-gyp info spawn args '/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp/gyp/gyp_main.py',
+gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/gyp/gyp_main.py',
gyp info spawn args 'binding.gyp',
gyp info spawn args '-f',
gyp info spawn args 'make',
gyp info spawn args '-I',
-gyp info spawn args '/Users/thomask33/Projects/libghostty-vt-node/build/config.gypi',
+gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build/config.gypi',
gyp info spawn args '-I',
-gyp info spawn args '/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp/addon.gypi',
+gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/addon.gypi',
gyp info spawn args '-I',
-gyp info spawn args '/Users/thomask33/Library/Caches/node-gyp/25.9.0/include/node/common.gypi',
+gyp info spawn args '/home/coder/.cache/node-gyp/26.12.1/include/node/common.gypi',
gyp info spawn args '-Dlibrary=shared_library',
gyp info spawn args '-Dvisibility=default',
-gyp info spawn args '-Dnode_root_dir=/Users/thomask33/Library/Caches/node-gyp/25.9.0',
-gyp info spawn args '-Dnode_gyp_dir=/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp',
-gyp info spawn args '-Dnode_lib_file=/Users/thomask33/Library/Caches/node-gyp/25.9.0/<(target_arch)/node.lib',
-gyp info spawn args '-Dmodule_root_dir=/Users/thomask33/Projects/libghostty-vt-node',
+gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/26.12.1',
+gyp info spawn args '-Dnode_gyp_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp',
+gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/26.12.1/<(target_arch)/node.lib',
+gyp info spawn args '-Dmodule_root_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think',
gyp info spawn args '-Dnode_engine=v8',
gyp info spawn args '--depth=.',
gyp info spawn args '--no-parallel',
@@ -32,26 +33,18 @@ gyp info spawn args '--generator-output',
gyp info spawn args 'build',
gyp info spawn args '-Goutput_dir=.'
gyp info spawn args ]
-@coder/libghostty-vt-node: missing header ghostty/vt.h
-
-Expected a local libghostty-vt installation. Either:
- 1. run `npm run build:libghostty`, or
- 2. set LIBGHOSTTY_VT_PREFIX=/path/to/prefix, or
- 3. set LIBGHOSTTY_VT_INCLUDE_DIR and LIBGHOSTTY_VT_STATIC_LIB.
-
-Checked include dir: /Users/thomask33/Projects/libghostty-vt-node/vendor/libghostty-vt/include
-Checked static lib: /Users/thomask33/Projects/libghostty-vt-node/vendor/libghostty-vt/lib/libghostty-vt.a
-gyp: Call to 'node scripts/resolve-libghostty-vt.mjs include' returned exit status 1 while in binding.gyp. while trying to load binding.gyp
-gyp ERR! configure error
-gyp ERR! stack Error: `gyp` failed with exit code: 1
-gyp ERR! stack at ChildProcess. (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp/lib/configure.js:317:18)
-gyp ERR! stack at ChildProcess.emit (node:events:509:20)
-gyp ERR! stack at ChildProcess._handle.onexit (node:internal/child_process:294:12)
-gyp ERR! System Darwin 25.4.0
-gyp ERR! command "/Users/thomask33/.local/share/mise/installs/node/25.9.0/bin/node" "/Users/thomask33/Projects/libghostty-vt-node/node_modules/.bin/node-gyp" "rebuild"
-gyp ERR! cwd /Users/thomask33/Projects/libghostty-vt-node
-gyp ERR! node -v v25.9.0
-gyp ERR! node-gyp -v v12.3.0
-gyp ERR! $npm_package_name @coder/libghostty-vt-node
-gyp ERR! $npm_package_version 0.1.0
-gyp ERR! not ok
+gyp info spawn make
+gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ]
+make: Entering directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build'
+ CC(target) Release/obj.target/nothing/node_modules/node-addon-api/nothing.o
+rm -f Release/obj.target/node_modules/node-addon-api/nothing.a Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list; mkdir -p `dirname Release/obj.target/node_modules/node-addon-api/nothing.a`
+ar crs Release/obj.target/node_modules/node-addon-api/nothing.a @Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list
+ COPY Release/nothing.a
+ CXX(target) Release/obj.target/libghostty_vt_node/native/addon.o
+ CXX(target) Release/obj.target/libghostty_vt_node/native/libghostty_vt_shim.o
+ CXX(target) Release/obj.target/libghostty_vt_node/native/terminal.o
+ SOLINK_MODULE(target) Release/obj.target/ghostty_vt_node.node
+ COPY Release/ghostty_vt_node.node
+make: Leaving directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build'
+gyp info ok
+exit=0
diff --git a/dogfood/bootstrap/commands.sh b/dogfood/bootstrap/commands.sh
index 094d29a..0496955 100755
--- a/dogfood/bootstrap/commands.sh
+++ b/dogfood/bootstrap/commands.sh
@@ -3,9 +3,9 @@ set -u
npm install
npm run build
-npm test
npm run build:libghostty
npm run build:native
+npm test
npm run smoke
npm pack --dry-run
node -e "import('./dist/index.js').then((m)=>console.log(JSON.stringify(m.getNativeInfo(), null, 2))).catch((err)=>{ console.log(JSON.stringify({ ok: false, error: err.message, cause: err.cause ? String(err.cause.message || err.cause) : undefined }, null, 2)); process.exitCode = 0; })"
diff --git a/dogfood/bootstrap/environment.txt b/dogfood/bootstrap/environment.txt
index 8bfca00..f46257d 100644
--- a/dogfood/bootstrap/environment.txt
+++ b/dogfood/bootstrap/environment.txt
@@ -1,10 +1,7 @@
-date=2026-04-24T14:06:15Z
-cwd=/Users/thomask33/Projects/libghostty-vt-node
-node=v25.9.0
-npm=11.12.1
-zig=0.15.2
-platform=Darwin Mac.fritz.box 25.4.0 Darwin Kernel Version 25.4.0: Thu Mar 19 19:31:56 PDT 2026; root:xnu-12377.101.15~1/RELEASE_ARM64_T6030 arm64
-xcode_path=/Applications/Xcode.app/Contents/Developer
-sdkroot=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
-ghostty_commit=48ccec182a932c2ec04c344d45a5fc553861cb13
-go_libghostty_commit=542c76ff595ad533348e23aac74489ad095e0b36
+date=2026-10-30T01:39:42Z
+node=v26.12.1
+npm=11.20.0
+zig=0.17.0
+platform=Linux 6.8.0-139-generic x86_64
+ghostty_commit=83fb95624b0439ee660c2679b43b38c8b63769cd
+git_head=834fc2d9d467a5af04f196a8d9491e2b77544b33
diff --git a/dogfood/bootstrap/native-info.json b/dogfood/bootstrap/native-info.json
index 32036e0..934790b 100644
--- a/dogfood/bootstrap/native-info.json
+++ b/dogfood/bootstrap/native-info.json
@@ -1,5 +1,7 @@
{
- "ok": false,
- "error": "Failed to load @coder/libghostty-vt-node native addon. Build it with `npm run build:native`, or use an npm package that includes a matching prebuild. If building locally, provide libghostty-vt with `npm run build:libghostty` or LIBGHOSTTY_VT_PREFIX.",
- "cause": "No native build was found for platform=darwin arch=arm64 runtime=node abi=141 uv=1 armv=8 libc=glibc node=25.9.0\n loaded from: /Users/thomask33/Projects/libghostty-vt-node\n"
+ "napiVersion": 10,
+ "ghosttyVersion": "0.1.0-dev",
+ "packageVersion": "0.1.0-beta.1",
+ "platform": "linux",
+ "arch": "x64"
}
diff --git a/dogfood/bootstrap/npm-pack-output.txt b/dogfood/bootstrap/npm-pack-output.txt
index 04200a3..3f29e35 100644
--- a/dogfood/bootstrap/npm-pack-output.txt
+++ b/dogfood/bootstrap/npm-pack-output.txt
@@ -1,38 +1,47 @@
+$ npm pack --dry-run
npm notice
-npm notice 📦 @coder/libghostty-vt-node@0.1.0
+npm notice package: @coder/libghostty-vt-node@0.1.0-beta.1
npm notice Tarball Contents
+npm notice 968B CHANGELOG.md
npm notice 1.1kB LICENSE
-npm notice 5.6kB README.md
+npm notice 16.9kB README.md
npm notice 1.2kB binding.gyp
-npm notice 418B dist/index.d.ts
-npm notice 376B dist/index.d.ts.map
-npm notice 3.2kB dist/index.js
-npm notice 3.1kB dist/index.js.map
+npm notice 648B dist/index.d.ts
+npm notice 485B dist/index.d.ts.map
+npm notice 8.6kB dist/index.js
+npm notice 7.7kB dist/index.js.map
npm notice 325B dist/loader.d.ts
npm notice 334B dist/loader.d.ts.map
npm notice 2.0kB dist/loader.js
npm notice 1.7kB dist/loader.js.map
-npm notice 1.7kB dist/types.d.ts
-npm notice 1.9kB dist/types.d.ts.map
+npm notice 3.3kB dist/types.d.ts
+npm notice 3.1kB dist/types.d.ts.map
npm notice 44B dist/types.js
npm notice 102B dist/types.js.map
npm notice 346B native/addon.cc
npm notice 2.3kB native/libghostty_vt_shim.cc
npm notice 549B native/libghostty_vt_shim.hh
-npm notice 19.6kB native/terminal.cc
-npm notice 1.8kB native/terminal.hh
-npm notice 1.5kB package.json
+npm notice 18.3kB native/README.md
+npm notice 35.5kB native/terminal.cc
+npm notice 2.1kB native/terminal.hh
+npm notice 2.0kB package.json
+npm notice 12.7MB prebuilds/linux-x64/@coder+libghostty-vt-node.node
+npm notice 3.8kB scripts/assemble-prebuilds.mjs
npm notice 2.9kB scripts/build-libghostty-vt.sh
+npm notice 2.7kB scripts/create-release-tag.mjs
+npm notice 11.2kB scripts/README.md
npm notice 1.8kB scripts/resolve-libghostty-vt.mjs
-npm notice 755B scripts/verify-prebuilds.mjs
+npm notice 1.9kB scripts/resolve-npm-publish-tag.mjs
+npm notice 1.5kB scripts/verify-prebuilds.mjs
npm notice Tarball Details
npm notice name: @coder/libghostty-vt-node
-npm notice version: 0.1.0
-npm notice filename: coder-libghostty-vt-node-0.1.0.tgz
-npm notice package size: 14.9 kB
-npm notice unpacked size: 54.8 kB
-npm notice shasum: 3f0dcbb66cc2931a58ee4954b68d00717b2e4d9e
-npm notice integrity: sha512-GA2ucjo7iJW24[...]bQWFxo7mLm8KA==
-npm notice total files: 24
+npm notice version: 0.1.0-beta.1
+npm notice filename: coder-libghostty-vt-node-0.1.0-beta.1.tgz
+npm notice package size: 3.7 MB
+npm notice unpacked size: 12.9 MB
+npm notice shasum: 745acaff5d026b0ffa6dd5fca645c2134867e848
+npm notice integrity: sha512-zphUs2JiLK6LS[...]zSI7BzBU3xt6Q==
+npm notice total files: 31
npm notice
-coder-libghostty-vt-node-0.1.0.tgz
+coder-libghostty-vt-node-0.1.0-beta.1.tgz
+exit=0
diff --git a/dogfood/bootstrap/session.log b/dogfood/bootstrap/session.log
index 0635915..61121e7 100644
--- a/dogfood/bootstrap/session.log
+++ b/dogfood/bootstrap/session.log
@@ -1,41 +1,203 @@
-^D
-> @coder/libghostty-vt-node@0.1.0 test
-> vitest run
-
-[?25l
- RUN v4.1.5 /Users/thomask33/Projects/libghostty-vt-node
-
-
- Test Files 2 passed (2)
- Tests 2 passed | 4 skipped (6)
- Start at 16:06:30
- Duration 118ms (transform 49ms, setup 0ms, import 71ms, tests 4ms, environment 0ms)
-
-[?25hâ ™[1G[0K
-> @coder/libghostty-vt-node@0.1.0 smoke
-> tsx examples/smoke.ts
-
-/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39
- throw new Error(
- ^
-
-Error: Failed to load @coder/libghostty-vt-node native addon. Build it with `npm run build:native`, or use an npm package that includes a matching prebuild. If building locally, provide libghostty-vt with `npm run build:libghostty` or LIBGHOSTTY_VT_PREFIX.
- at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39:11)
- at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23)
- ... 3 lines matching cause stack trace ...
- at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
- [cause]: Error: No native build was found for platform=darwin arch=arm64 runtime=node abi=141 uv=1 armv=8 libc=glibc node=25.9.0
- loaded from: /Users/thomask33/Projects/libghostty-vt-node
-
- at load.resolve.load.path (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:60:9)
- at load (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:22:30)
- at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:34:19)
- at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23)
- at (/Users/thomask33/Projects/libghostty-vt-node/examples/smoke.ts:3:14)
- at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
- at async node:internal/modules/esm/loader:639:26
- at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
-}
-
-Node.js v25.9.0
-â ™[1G[0K
\ No newline at end of file
+$ npm install
+
+> @coder/libghostty-vt-node@0.1.0-beta.1 install
+> node -e "console.log('native build is explicit: run npm run build:native')"
+
+native build is explicit: run npm run build:native
+
+up to date, audited 93 packages in 491ms
+
+22 packages are looking for funding
+ run `npm fund` for details
+
+found 0 vulnerabilities
+npm warn install-scripts 1 package has install scripts not yet covered by allowScripts:
+npm warn install-scripts esbuild@0.28.2 (postinstall: node install.js)
+npm warn install-scripts
+npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve ` to allow.
+exit=0
+$ npm run build
+
+> @coder/libghostty-vt-node@0.1.0-beta.1 build
+> tsc -p tsconfig.json
+
+exit=0
+$ npm run build:libghostty
+
+> @coder/libghostty-vt-node@0.1.0-beta.1 build:libghostty
+> scripts/build-libghostty-vt.sh
+
+From https://github.com/ghostty-org/ghostty
+ * branch 83fb95624b0439ee660c2679b43b38c8b63769cd -> FETCH_HEAD
+HEAD is now at 83fb95624 Bump version to 1.4.0
+Building libghostty-vt at Ghostty commit 83fb95624b0439ee660c2679b43b38c8b63769cd
+Installed libghostty-vt into /home/coder/.mux/src/libghostty-vt-node/tough-seals-think/vendor/libghostty-vt
+exit=0
+$ npm run build:native
+
+> @coder/libghostty-vt-node@0.1.0-beta.1 build:native
+> node-gyp rebuild
+
+gyp info it worked if it ends with ok
+gyp info using node-gyp@12.4.0
+gyp info using node@26.12.1 | linux | x64
+gyp info find Python using Python version 3.14.4 found at "/usr/bin/python3"
+
+gyp info spawn /usr/bin/python3
+gyp info spawn args [
+gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/gyp/gyp_main.py',
+gyp info spawn args 'binding.gyp',
+gyp info spawn args '-f',
+gyp info spawn args 'make',
+gyp info spawn args '-I',
+gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build/config.gypi',
+gyp info spawn args '-I',
+gyp info spawn args '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp/addon.gypi',
+gyp info spawn args '-I',
+gyp info spawn args '/home/coder/.cache/node-gyp/26.12.1/include/node/common.gypi',
+gyp info spawn args '-Dlibrary=shared_library',
+gyp info spawn args '-Dvisibility=default',
+gyp info spawn args '-Dnode_root_dir=/home/coder/.cache/node-gyp/26.12.1',
+gyp info spawn args '-Dnode_gyp_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/node_modules/node-gyp',
+gyp info spawn args '-Dnode_lib_file=/home/coder/.cache/node-gyp/26.12.1/<(target_arch)/node.lib',
+gyp info spawn args '-Dmodule_root_dir=/home/coder/.mux/src/libghostty-vt-node/tough-seals-think',
+gyp info spawn args '-Dnode_engine=v8',
+gyp info spawn args '--depth=.',
+gyp info spawn args '--no-parallel',
+gyp info spawn args '--generator-output',
+gyp info spawn args 'build',
+gyp info spawn args '-Goutput_dir=.'
+gyp info spawn args ]
+gyp info spawn make
+gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ]
+make: Entering directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build'
+ CC(target) Release/obj.target/nothing/node_modules/node-addon-api/nothing.o
+rm -f Release/obj.target/node_modules/node-addon-api/nothing.a Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list; mkdir -p `dirname Release/obj.target/node_modules/node-addon-api/nothing.a`
+ar crs Release/obj.target/node_modules/node-addon-api/nothing.a @Release/obj.target/node_modules/node-addon-api/nothing.a.ar-file-list
+ COPY Release/nothing.a
+ CXX(target) Release/obj.target/libghostty_vt_node/native/addon.o
+ CXX(target) Release/obj.target/libghostty_vt_node/native/libghostty_vt_shim.o
+ CXX(target) Release/obj.target/libghostty_vt_node/native/terminal.o
+ SOLINK_MODULE(target) Release/obj.target/ghostty_vt_node.node
+ COPY Release/ghostty_vt_node.node
+make: Leaving directory '/home/coder/.mux/src/libghostty-vt-node/tough-seals-think/build'
+gyp info ok
+exit=0
+$ npm test
+
+> @coder/libghostty-vt-node@0.1.0-beta.1 test
+> vitest run
+
+
+[1m[30m[46m RUN [49m[39m[22m [36mv4.1.12 [39m[90m/home/coder/.mux/src/libghostty-vt-node/tough-seals-think[39m
+
+ [32m✓[39m test/load.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 4[2mms[22m[39m
+ [32m✓[39m test/terminal.test.ts [2m([22m[2m33 tests[22m[2m)[22m[32m 19[2mms[22m[39m
+ [32m✓[39m test/workers.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 170[2mms[22m[39m
+ [32m✓[39m test/mouse-safety.test.ts [2m([22m[2m12 tests[22m[2m)[22m[33m 309[2mms[22m[39m
+ [32m✓[39m test/scripts.test.ts [2m([22m[2m24 tests[22m[2m)[22m[33m 952[2mms[22m[39m
+
+[2m Test Files [22m [1m[32m5 passed[39m[22m[90m (5)[39m
+[2m Tests [22m [1m[32m73 passed[39m[22m[90m (73)[39m
+[2m Start at [22m 01:39:39
+[2m Duration [22m 1.08s[2m (transform 95ms, setup 0ms, import 205ms, tests 1.45s, environment 0ms)[22m
+
+exit=0
+$ npm run smoke
+
+> @coder/libghostty-vt-node@0.1.0-beta.1 smoke
+> tsx examples/smoke.ts
+
+native info
+{
+ "napiVersion": 10,
+ "ghosttyVersion": "0.1.0-dev",
+ "packageVersion": "0.1.0-beta.1",
+ "platform": "linux",
+ "arch": "x64"
+}
+visible text
+hello
+ red text
+ cursor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+mouse bytes
+[27,91,60,48,59,53,59,54,77]
+snapshot summary
+{
+ "cols": 80,
+ "rows": 24,
+ "cursorRow": 2,
+ "cursorCol": 10,
+ "isAltScreen": false,
+ "visibleLineCount": 24,
+ "cellCount": 19
+}
+exit=0
+$ npm pack --dry-run
+npm notice
+npm notice package: @coder/libghostty-vt-node@0.1.0-beta.1
+npm notice Tarball Contents
+npm notice 968B CHANGELOG.md
+npm notice 1.1kB LICENSE
+npm notice 16.9kB README.md
+npm notice 1.2kB binding.gyp
+npm notice 648B dist/index.d.ts
+npm notice 485B dist/index.d.ts.map
+npm notice 8.6kB dist/index.js
+npm notice 7.7kB dist/index.js.map
+npm notice 325B dist/loader.d.ts
+npm notice 334B dist/loader.d.ts.map
+npm notice 2.0kB dist/loader.js
+npm notice 1.7kB dist/loader.js.map
+npm notice 3.3kB dist/types.d.ts
+npm notice 3.1kB dist/types.d.ts.map
+npm notice 44B dist/types.js
+npm notice 102B dist/types.js.map
+npm notice 346B native/addon.cc
+npm notice 2.3kB native/libghostty_vt_shim.cc
+npm notice 549B native/libghostty_vt_shim.hh
+npm notice 18.3kB native/README.md
+npm notice 35.5kB native/terminal.cc
+npm notice 2.1kB native/terminal.hh
+npm notice 2.0kB package.json
+npm notice 12.7MB prebuilds/linux-x64/@coder+libghostty-vt-node.node
+npm notice 3.8kB scripts/assemble-prebuilds.mjs
+npm notice 2.9kB scripts/build-libghostty-vt.sh
+npm notice 2.7kB scripts/create-release-tag.mjs
+npm notice 11.2kB scripts/README.md
+npm notice 1.8kB scripts/resolve-libghostty-vt.mjs
+npm notice 1.9kB scripts/resolve-npm-publish-tag.mjs
+npm notice 1.5kB scripts/verify-prebuilds.mjs
+npm notice Tarball Details
+npm notice name: @coder/libghostty-vt-node
+npm notice version: 0.1.0-beta.1
+npm notice filename: coder-libghostty-vt-node-0.1.0-beta.1.tgz
+npm notice package size: 3.7 MB
+npm notice unpacked size: 12.9 MB
+npm notice shasum: 745acaff5d026b0ffa6dd5fca645c2134867e848
+npm notice integrity: sha512-zphUs2JiLK6LS[...]zSI7BzBU3xt6Q==
+npm notice total files: 31
+npm notice
+coder-libghostty-vt-node-0.1.0-beta.1.tgz
+exit=0
diff --git a/dogfood/bootstrap/smoke-output.txt b/dogfood/bootstrap/smoke-output.txt
index 2632c64..abf84f6 100644
--- a/dogfood/bootstrap/smoke-output.txt
+++ b/dogfood/bootstrap/smoke-output.txt
@@ -1,27 +1,51 @@
+$ npm run smoke
-> @coder/libghostty-vt-node@0.1.0 smoke
+> @coder/libghostty-vt-node@0.1.0-beta.1 smoke
> tsx examples/smoke.ts
-/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39
- throw new Error(
- ^
-
-Error: Failed to load @coder/libghostty-vt-node native addon. Build it with `npm run build:native`, or use an npm package that includes a matching prebuild. If building locally, provide libghostty-vt with `npm run build:libghostty` or LIBGHOSTTY_VT_PREFIX.
- at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:39:11)
- at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23)
- ... 3 lines matching cause stack trace ...
- at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
- [cause]: Error: No native build was found for platform=darwin arch=arm64 runtime=node abi=141 uv=1 armv=8 libc=glibc node=25.9.0
- loaded from: /Users/thomask33/Projects/libghostty-vt-node
-
- at load.resolve.load.path (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:60:9)
- at load (/Users/thomask33/Projects/libghostty-vt-node/node_modules/node-gyp-build/node-gyp-build.js:22:30)
- at loadNative (/Users/thomask33/Projects/libghostty-vt-node/src/loader.ts:34:19)
- at createTerminal (/Users/thomask33/Projects/libghostty-vt-node/src/index.ts:127:23)
- at (/Users/thomask33/Projects/libghostty-vt-node/examples/smoke.ts:3:14)
- at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
- at async node:internal/modules/esm/loader:639:26
- at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
+native info
+{
+ "napiVersion": 10,
+ "ghosttyVersion": "0.1.0-dev",
+ "packageVersion": "0.1.0-beta.1",
+ "platform": "linux",
+ "arch": "x64"
}
+visible text
+hello
+ red text
+ cursor
-Node.js v25.9.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+mouse bytes
+[27,91,60,48,59,53,59,54,77]
+snapshot summary
+{
+ "cols": 80,
+ "rows": 24,
+ "cursorRow": 2,
+ "cursorCol": 10,
+ "isAltScreen": false,
+ "visibleLineCount": 24,
+ "cellCount": 19
+}
+exit=0
diff --git a/dogfood/bootstrap/test-output.txt b/dogfood/bootstrap/test-output.txt
index 9e41cfa..9c0d831 100644
--- a/dogfood/bootstrap/test-output.txt
+++ b/dogfood/bootstrap/test-output.txt
@@ -1,13 +1,20 @@
+$ npm test
-> @coder/libghostty-vt-node@0.1.0 test
+> @coder/libghostty-vt-node@0.1.0-beta.1 test
> vitest run
- RUN v4.1.5 /Users/thomask33/Projects/libghostty-vt-node
+[1m[30m[46m RUN [49m[39m[22m [36mv4.1.12 [39m[90m/home/coder/.mux/src/libghostty-vt-node/tough-seals-think[39m
+ [32m✓[39m test/load.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 4[2mms[22m[39m
+ [32m✓[39m test/terminal.test.ts [2m([22m[2m33 tests[22m[2m)[22m[32m 19[2mms[22m[39m
+ [32m✓[39m test/workers.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 170[2mms[22m[39m
+ [32m✓[39m test/mouse-safety.test.ts [2m([22m[2m12 tests[22m[2m)[22m[33m 309[2mms[22m[39m
+ [32m✓[39m test/scripts.test.ts [2m([22m[2m24 tests[22m[2m)[22m[33m 952[2mms[22m[39m
- Test Files 2 passed (2)
- Tests 2 passed | 4 skipped (6)
- Start at 16:06:17
- Duration 137ms (transform 62ms, setup 0ms, import 89ms, tests 4ms, environment 0ms)
+[2m Test Files [22m [1m[32m5 passed[39m[22m[90m (5)[39m
+[2m Tests [22m [1m[32m73 passed[39m[22m[90m (73)[39m
+[2m Start at [22m 01:39:39
+[2m Duration [22m 1.08s[2m (transform 95ms, setup 0ms, import 205ms, tests 1.45s, environment 0ms)[22m
+exit=0
diff --git a/dogfood/regenerate.sh b/dogfood/regenerate.sh
new file mode 100755
index 0000000..cf2e872
--- /dev/null
+++ b/dogfood/regenerate.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+set -uo pipefail
+cd "$(git rev-parse --show-toplevel)"
+D=dogfood/bootstrap
+want_node="v$(sed -n 's/^node = "\(.*\)"$/\1/p' mise.toml)"
+got_node="$(npm exec -c 'node --version')"
+if [ "$got_node" != "$want_node" ]; then
+ echo "error: npm scripts run node $got_node, but mise.toml pins $want_node" >&2
+ exit 1
+fi
+failed=0
+run() {
+ local out="$1"; shift
+ local status=0
+ { echo "\$ $*"; "$@"; } > "$D/.step" 2>&1 || status=$?
+ echo "exit=$status" >> "$D/.step"
+ cat "$D/.step" >> "$D/session.log"
+ if [ -n "$out" ]; then cp "$D/.step" "$D/$out"; fi
+ rm -f "$D/.step"
+ if [ "$status" -ne 0 ]; then echo "error: '$*' exited $status" >&2; failed=1; fi
+}
+: > "$D/session.log"
+run "" npm install
+run "" npm run build
+run build-libghostty-output.txt npm run build:libghostty
+run build-native-output.txt npm run build:native
+run test-output.txt npm test
+run smoke-output.txt npm run smoke
+run npm-pack-output.txt npm pack --dry-run
+node -e "import('./dist/index.js').then((m)=>console.log(JSON.stringify(m.getNativeInfo(), null, 2))).catch((err)=>{ console.log(JSON.stringify({ ok: false, error: err.message, cause: err.cause ? String(err.cause.message || err.cause) : undefined }, null, 2)); process.exitCode = 0; })" > "$D/native-info.json"
+if grep -q '"ok": false' "$D/native-info.json"; then echo "error: dist/index.js failed to load the addon" >&2; failed=1; fi
+{
+ echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ echo "node=$got_node"
+ echo "npm=$(npm --version)"
+ echo "zig=$(npm exec -c 'zig version')"
+ echo "platform=$(uname -srm)"
+ echo "ghostty_commit=$(sed -n 's/^GHOSTTY_COMMIT="${GHOSTTY_COMMIT:-\(.*\)}"$/\1/p' scripts/build-libghostty-vt.sh)"
+ echo "git_head=$(git rev-parse HEAD)"
+} > "$D/environment.txt"
+exit "$failed"
diff --git a/mise.lock b/mise.lock
index 97efd01..a27e3a2 100644
--- a/mise.lock
+++ b/mise.lock
@@ -67,33 +67,33 @@ checksum = "sha256:b3efd5c70cba3308ed46e7e699a9b8aac075bbc6b027fcb39c84c31acc25e
url = "https://nodejs.org/dist/v26.12.1/node-v26.12.1-win-x64.zip"
[[tools.zig]]
-version = "0.15.2"
+version = "0.17.0"
backend = "core:zig"
[tools.zig."platforms.linux-arm64"]
-checksum = "sha256:958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f"
-url = "https://ziglang.org/download/0.15.2/zig-aarch64-linux-0.15.2.tar.xz"
+checksum = "sha256:9e8d11661d4ae3bd57702a3832781e23ad151dde5798e16a5ccd503f65234ff8"
+url = "https://ziglang.org/download/0.17.0/zig-aarch64-linux-0.17.0.tar.xz"
[tools.zig."platforms.linux-arm64-musl"]
-checksum = "sha256:958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f"
-url = "https://ziglang.org/download/0.15.2/zig-aarch64-linux-0.15.2.tar.xz"
+checksum = "sha256:9e8d11661d4ae3bd57702a3832781e23ad151dde5798e16a5ccd503f65234ff8"
+url = "https://ziglang.org/download/0.17.0/zig-aarch64-linux-0.17.0.tar.xz"
[tools.zig."platforms.linux-x64"]
-checksum = "sha256:02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239"
-url = "https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz"
+checksum = "sha256:1cbe9df9f27e6b78d14ccbca43b6703a404ef79ef1c463de901d7f088d4e2026"
+url = "https://ziglang.org/download/0.17.0/zig-x86_64-linux-0.17.0.tar.xz"
[tools.zig."platforms.linux-x64-musl"]
-checksum = "sha256:02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239"
-url = "https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz"
+checksum = "sha256:1cbe9df9f27e6b78d14ccbca43b6703a404ef79ef1c463de901d7f088d4e2026"
+url = "https://ziglang.org/download/0.17.0/zig-x86_64-linux-0.17.0.tar.xz"
[tools.zig."platforms.macos-arm64"]
-checksum = "sha256:3cc2bab367e185cdfb27501c4b30b1b0653c28d9f73df8dc91488e66ece5fa6b"
-url = "https://ziglang.org/download/0.15.2/zig-aarch64-macos-0.15.2.tar.xz"
+checksum = "sha256:b607e9b9234790a008116ae5bdb71c6243b84b9fb42a53a9e70fde41c06c536a"
+url = "https://ziglang.org/download/0.17.0/zig-aarch64-macos-0.17.0.tar.xz"
[tools.zig."platforms.macos-x64"]
-checksum = "sha256:375b6909fc1495d16fc2c7db9538f707456bfc3373b14ee83fdd3e22b3d43f7f"
-url = "https://ziglang.org/download/0.15.2/zig-x86_64-macos-0.15.2.tar.xz"
+checksum = "sha256:4f9a1c5269aa17ebda5e6d3c2b89d6cbf36f7d2b22a0306e9ab98f25f95529c6"
+url = "https://ziglang.org/download/0.17.0/zig-x86_64-macos-0.17.0.tar.xz"
[tools.zig."platforms.windows-x64"]
-checksum = "sha256:3a0ed1e8799a2f8ce2a6e6290a9ff22e6906f8227865911fb7ddedc3cc14cb0c"
-url = "https://ziglang.org/download/0.15.2/zig-x86_64-windows-0.15.2.zip"
+checksum = "sha256:b5663f69581dcf391293fbf16c06cb80d81d806545ce618b4d0bab7f0eb8c428"
+url = "https://ziglang.org/download/0.17.0/zig-x86_64-windows-0.17.0.zip"
diff --git a/mise.toml b/mise.toml
index 7eba29e..509444a 100644
--- a/mise.toml
+++ b/mise.toml
@@ -1,7 +1,7 @@
[tools]
communique = "1.0.3"
node = "26.12.1"
-zig = "0.15.2"
+zig = "0.17.0"
[tasks.ci]
description = "Install dependencies, build libghostty-vt/native/TypeScript, run tests, and run smoke."
diff --git a/native/README.md b/native/README.md
index f83b0ed..d58ad8b 100644
--- a/native/README.md
+++ b/native/README.md
@@ -20,7 +20,7 @@ JavaScript exports:
- `createTerminal(options: { cols, rows, scrollbackLimit? })`: returns a new `GhosttyVtTerminal` [native/terminal.cc:CreateTerminal]
- `getNativeInfo()`: returns `{ napiVersion, ghosttyVersion?, ghosttyCommit? }`. `napiVersion` comes from `napi_get_version` at run time, not from the compiled `NAPI_VERSION`. `ghosttyVersion` comes from `GHOSTTY_BUILD_INFO_VERSION_STRING` and `ghosttyCommit` from `GHOSTTY_BUILD_INFO_VERSION_BUILD` [native/terminal.cc:GetNativeInfo]
- `feed(data: string | Buffer | Uint8Array)`: writes the bytes (a string as UTF-8) [native/terminal.cc:TerminalWrap::Feed]
-- `resize(cols, rows)`: calls `ghostty_terminal_resize(terminal, cols, rows, 0, 0)` [native/terminal.cc:TerminalWrap::Resize]
+- `resize(cols, rows)`: calls `ghostty_terminal_resize(terminal, cols, rows, 0, 0)`. When the call makes the grid narrower and taller at once, it first resizes to the new rows at the old width, because Ghostty v1.4.0 pulls scrollback back into view only for that order [native/terminal.cc:TerminalWrap::Resize]
- `encodeMouse(event: { action, button?, x, y, modifiers? }, options: { geometry, anyButtonPressed?, trackLastCell? })`: returns a `Buffer` with the mouse input bytes for the tracking mode and format that the terminal output selected. The buffer is empty when tracking is off or the mode drops the event. `geometry` has `screenWidth`, `screenHeight`, `cellWidth`, `cellHeight`, `paddingTop`, `paddingBottom`, `paddingRight`, and `paddingLeft`, all required here [native/terminal.cc:TerminalWrap::EncodeMouse]
- `snapshot(options?: { includeScrollback?, includeCells? })`: returns `{ cols, rows, cursorRow, cursorCol, cursorVisible, isAltScreen, visibleLines, scrollbackLines?, cells? }`. Each entry of `visibleLines` and `scrollbackLines` is `{ row, text }`. `cursorVisible` is always set, from `GHOSTTY_TERMINAL_DATA_CURSOR_VISIBLE` (DECTCEM). `isAltScreen` is true when the active screen is `GHOSTTY_TERMINAL_SCREEN_ALTERNATE` [native/terminal.cc:TerminalWrap::BuildSnapshot]
- A snapshot cell is `{ row, col, text, width }`. A styled cell can also carry `bold`, `italic`, `underline`, `faint`, `inverse`, `invisible`, and `strikethrough` (each set only when true) and `foreground` / `background` as `#rrggbb`. `inverse` is the raw SGR 7 flag: `foreground` and `background` are not swapped [native/terminal.cc:TerminalWrap::BuildCellObject]
@@ -39,7 +39,7 @@ C++ declarations:
- `createTerminal` and the constructor throw `TypeError("createTerminal options must be an object")` unless they get exactly one object argument [native/terminal.cc:CreateTerminal].
- `cols` and `rows`: a non-number throws `TypeError(" must be a positive integer")`. `NaN`, an infinity, a value below 1 or above 65535, or a non-integer throws `RangeError(" must be a positive 16-bit integer")`. The range check runs before the value is cast to `uint16_t` [native/terminal.cc:PositiveUint16].
-- `scrollbackLimit` is skipped when it is absent or `undefined`, and then defaults to 0. A non-number throws `TypeError`. `NaN`, an infinity, a negative value, a non-integer, or a value too large for `size_t` (2^64 or more on 64-bit targets) throws `RangeError`. Both use `"scrollbackLimit must be a non-negative integer"`, and the range check runs before the cast [native/terminal.cc:NonNegativeSize].
+- `scrollbackLimit` is skipped when it is absent or `undefined`, and then defaults to 0. A non-number throws `TypeError`. `NaN`, an infinity, a negative value, a non-integer, or a value too large for `size_t` (2^64 or more on 64-bit targets) throws `RangeError`. Both use `"scrollbackLimit must be a non-negative integer"`, and the range check runs before the cast [native/terminal.cc:NonNegativeSize]. The value becomes Ghostty's scrollback byte limit (`GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES`), so 0 keeps no scrollback. Ghostty prunes by whole pages, so a small nonzero limit still keeps some history [native/terminal.cc:TerminalWrap::TerminalWrap].
- Most failed libghostty-vt calls throw an `Error` with `ResultMessage` text, for example `"ghostty_terminal_new failed: GHOSTTY_OUT_OF_MEMORY"` [native/terminal.cc:ThrowResult]. These do not throw: a failed palette read leaves the color out, a failed `ghostty_build_info` leaves the field out, a first `ghostty_grid_ref_graphemes` call that does not return `GHOSTTY_OUT_OF_SPACE` with `required > 0` falls back to the single codepoint, and the code checks no result from `ghostty_terminal_vt_write` [native/terminal.cc:TerminalWrap::ResolveStyleColor] [native/terminal.cc:TerminalWrap::ReadCellText] [native/terminal.cc:TerminalWrap::Feed].
- Every method except `dispose` and `encodeMouse` calls `RequireTerminal` first. After dispose it throws `Error("GhosttyVtTerminal has been disposed")` [native/terminal.cc:TerminalWrap::RequireTerminal]. With `NAPI_CPP_EXCEPTIONS`, `RequireTerminal` reports a disposed terminal by throwing.
- `encodeMouse` reads and checks every argument first, and only then calls `RequireTerminal` and `RequireMouseEncoder` (`"GhosttyVtTerminal has been disposed"`). `Get()` runs getters, also getters on `Object.prototype` for keys that the caller leaves out, and such JS can dispose the terminal or call `encodeMouse` again. After the handles are taken, no JS runs until the call returns [native/terminal.cc:TerminalWrap::EncodeMouse].
@@ -64,7 +64,7 @@ C++ declarations:
## Side Effects
-- The constructor allocates a native terminal with `ghostty_terminal_new(nullptr, &created, options)` (default allocator) and stores it in `terminal_`. Then it allocates a mouse encoder with `ghostty_mouse_encoder_new(nullptr, ...)` and stores it in `mouse_encoder_`. When that fails, it frees the terminal, sets `terminal_` to `nullptr`, and throws the `ResultMessage` error [native/terminal.cc:TerminalWrap::TerminalWrap].
+- The constructor allocates a native terminal with `ghostty_terminal_new(nullptr, &created, cols, rows)` (default allocator) and sets its scrollback byte limit with `ghostty_terminal_set`. When that fails, it frees the terminal and throws the `ResultMessage` error. Otherwise it stores the terminal in `terminal_`. Then it allocates a mouse encoder with `ghostty_mouse_encoder_new(nullptr, ...)` and stores it in `mouse_encoder_`. When that fails, it frees the terminal, sets `terminal_` to `nullptr`, and throws the `ResultMessage` error [native/terminal.cc:TerminalWrap::TerminalWrap].
- `DisposeNative` frees them with `ghostty_mouse_encoder_free` and `ghostty_terminal_free` [native/terminal.cc:TerminalWrap::DisposeNative]. The destructor calls `DisposeNative`, so garbage collection of an undisposed object also frees both [native/terminal.cc:TerminalWrap::~TerminalWrap].
- `feed` changes terminal state through `ghostty_terminal_vt_write` and sets `mouse_modes_dirty_` [native/terminal.cc:TerminalWrap::Feed]. `encodeMouse` changes the encoder options and its last-cell state, and allocates one `GhosttyMouseEvent` per call that passes its checks, which it frees on every path after that [native/terminal.cc:TerminalWrap::EncodeMouse]. `resize` changes the size through `ghostty_terminal_resize` [native/terminal.cc:TerminalWrap::Resize].
- `Format` creates a formatter, calls `ghostty_formatter_format_alloc`, frees the formatter, then frees the output buffer with `ghostty_free(nullptr, ptr, len)` [native/terminal.cc:TerminalWrap::Format].
@@ -76,5 +76,5 @@ C++ declarations:
- Run (after `npm ci`): `npm run build:libghostty && npm run build:native && npm test`
- Every native test skips when the addon does not load (`test/native-support.ts:nativeSupport`), so check the test output for skips.
- `mise run ci-ubsan` builds the addon with UBSan (Linux, GCC), then runs the tests and smoke. Undefined behavior aborts the run. The task also fails when the addon's object files contain no UBSan checks, which means the flags did not reach g++ (`mise.toml:tasks.ci-ubsan`).
-- `test/workers.test.ts` checks the per-environment state in child processes with `worker_threads`. `test/terminal.test.ts` covers `feed`, `snapshot`, `resize`, `dispose`, the `RangeError` cases of the `createTerminal` checks in the addon, the `encodeMouse` bytes of each mouse format, `cursorVisible`, and the cell style fields. `test/mouse-safety.test.ts` runs the `encodeMouse` limits and the getter re-entry cases against both the package API and the raw addon object. It skips when the addon is missing, like the other native suites. Its child-process cases load `src/` through `tsx`, so they need no `dist/` build. No vitest test covers `formatPlain`, `formatHtml`, `includeScrollback`, or the typed-array errors.
+- `test/workers.test.ts` checks the per-environment state in child processes with `worker_threads`. `test/terminal.test.ts` covers `feed`, `snapshot`, `resize`, `dispose`, the `RangeError` cases of the `createTerminal` checks in the addon, the `encodeMouse` bytes of each mouse format, `cursorVisible`, the cell style fields, DECSTR, and whether `scrollbackLimit` keeps history. `test/mouse-safety.test.ts` runs the `encodeMouse` limits and the getter re-entry cases against both the package API and the raw addon object. It skips when the addon is missing, like the other native suites. Its child-process cases load `src/` through `tsx`, so they need no `dist/` build. No vitest test covers `formatPlain`, `formatHtml`, the content of `scrollbackLines`, or the typed-array errors.
- For changes to `TerminalWrap` or `InitAll`, also run the TLA+ lifecycle model: [`.agents/skills/verify-native-lifecycle/SKILL.md`](https://github.com/coder/libghostty-vt-node/blob/main/.agents/skills/verify-native-lifecycle/SKILL.md).
diff --git a/native/terminal.cc b/native/terminal.cc
index e095007..e0507cc 100644
--- a/native/terminal.cc
+++ b/native/terminal.cc
@@ -225,17 +225,19 @@ TerminalWrap::TerminalWrap(const Napi::CallbackInfo& info)
scrollback = NonNegativeSize(env, options.Get("scrollbackLimit"), "scrollbackLimit");
}
- GhosttyTerminalOptions native_options = {};
- native_options.cols = cols;
- native_options.rows = rows;
- native_options.max_scrollback = scrollback;
-
GhosttyTerminal created = nullptr;
- const GhosttyResult result = ghostty_terminal_new(nullptr, &created, native_options);
+ GhosttyResult result = ghostty_terminal_new(nullptr, &created, cols, rows);
if (result != GHOSTTY_SUCCESS) {
ThrowResult(env, "ghostty_terminal_new", result);
}
assert(created != nullptr);
+ // The byte limit, not the line limit, matches the pre-1.4 max_scrollback,
+ // which Ghostty counted in bytes and which disables scrollback at 0.
+ result = ghostty_terminal_set(created, GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES, &scrollback);
+ if (result != GHOSTTY_SUCCESS) {
+ ghostty_terminal_free(created);
+ ThrowResult(env, "ghostty_terminal_set(scrollback max bytes)", result);
+ }
terminal_ = created;
GhosttyMouseEncoder mouse_encoder = nullptr;
@@ -324,7 +326,19 @@ Napi::Value TerminalWrap::Resize(const Napi::CallbackInfo& info) {
const uint16_t cols = PositiveUint16(env, info[0], "cols");
const uint16_t rows = PositiveUint16(env, info[1], "rows");
- const GhosttyResult result = ghostty_terminal_resize(terminal, cols, rows, 0, 0);
+ uint16_t old_cols = 0;
+ uint16_t old_rows = 0;
+ GhosttyResult result = ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_COLS, &old_cols);
+ if (result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_get(cols)", result);
+ result = ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_ROWS, &old_rows);
+ if (result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_get(rows)", result);
+ // Ghostty v1.4.0 does not pull scrollback back into view when one resize
+ // both narrows and grows the grid, but it does when the rows grow first.
+ if (cols < old_cols && rows > old_rows) {
+ result = ghostty_terminal_resize(terminal, old_cols, rows, 0, 0);
+ if (result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_resize", result);
+ }
+ result = ghostty_terminal_resize(terminal, cols, rows, 0, 0);
if (result != GHOSTTY_SUCCESS) {
ThrowResult(env, "ghostty_terminal_resize", result);
}
@@ -383,11 +397,11 @@ Napi::Value TerminalWrap::EncodeMouse(const Napi::CallbackInfo& info) {
GhosttyTerminal terminal = RequireTerminal(env);
GhosttyMouseEncoder encoder = RequireMouseEncoder(env);
- bool utf8_mouse = false;
- const GhosttyResult mode_result = ghostty_terminal_mode_get(
- terminal, GHOSTTY_MODE_UTF8_MOUSE, &utf8_mouse);
- if (mode_result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_mode_get", mode_result);
- if (utf8_mouse) {
+ GhosttyTerminalModeConfig utf8_mouse = {GHOSTTY_MODE_UTF8_MOUSE, false};
+ const GhosttyResult mode_result =
+ ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_MODE, &utf8_mouse);
+ if (mode_result != GHOSTTY_SUCCESS) ThrowResult(env, "ghostty_terminal_get(mode)", mode_result);
+ if (utf8_mouse.value) {
// Mode bits cannot identify the last selected format. Conservatively guard
// these exceptional cells whenever 1005 is set, even if another format was
// selected later. Ordinary geometry and SGR-only large grids are unchanged.
diff --git a/scripts/README.md b/scripts/README.md
index 2d88d48..ac71044 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -19,8 +19,8 @@ In the `.mjs` scripts, a failed `assert` or a thrown `Error` ends the process wi
## Invariants
-- The default Ghostty commit is `48ccec182a932c2ec04c344d45a5fc553861cb13`. `GHOSTTY_COMMIT` overrides it [scripts/build-libghostty-vt.sh:GHOSTTY_COMMIT].
-- `build-libghostty-vt.sh` exits 1 with `"error: missing required command ''"` and `"Install it first, then rerun npm run build:libghostty."` when `git` or `zig` is not on `PATH`. For `zig` it also prints `"This Ghostty commit requires Zig 0.15.2. With mise: mise install"` [scripts/build-libghostty-vt.sh:need].
+- The default Ghostty commit is `83fb95624b0439ee660c2679b43b38c8b63769cd`. `GHOSTTY_COMMIT` overrides it [scripts/build-libghostty-vt.sh:GHOSTTY_COMMIT].
+- `build-libghostty-vt.sh` exits 1 with `"error: missing required command ''"` and `"Install it first, then rerun npm run build:libghostty."` when `git` or `zig` is not on `PATH`. For `zig` it also prints `"This Ghostty commit requires Zig 0.17.0. With mise: mise install"` [scripts/build-libghostty-vt.sh:need].
- `build-libghostty-vt.sh` exits 1 when `zig version` differs from `minimum_zig_version` in Ghostty's `build.zig.zon`. It skips this check when that file names no version [scripts/build-libghostty-vt.sh:required_zig].
- `build-libghostty-vt.sh` runs `zig build -Demit-lib-vt=true -Dsimd=$SIMD -Doptimize=$OPTIMIZE --prefix $PREFIX install`, with `--sysroot ` on Darwin when `xcrun` returns a path. A failed build exits 1 [scripts/build-libghostty-vt.sh:zig_args].
- After the build, `$PREFIX/include/ghostty/vt.h` and `$PREFIX/lib/libghostty-vt.a` must exist. Otherwise the script exits 1 [scripts/build-libghostty-vt.sh:header].
diff --git a/scripts/build-libghostty-vt.sh b/scripts/build-libghostty-vt.sh
index a49b5cc..08da6c6 100755
--- a/scripts/build-libghostty-vt.sh
+++ b/scripts/build-libghostty-vt.sh
@@ -3,7 +3,7 @@ set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
GHOSTTY_REPO="${GHOSTTY_REPO:-https://github.com/ghostty-org/ghostty.git}"
-GHOSTTY_COMMIT="${GHOSTTY_COMMIT:-48ccec182a932c2ec04c344d45a5fc553861cb13}"
+GHOSTTY_COMMIT="${GHOSTTY_COMMIT:-83fb95624b0439ee660c2679b43b38c8b63769cd}"
GHOSTTY_SOURCE_DIR="${GHOSTTY_SOURCE_DIR:-$ROOT/vendor/ghostty}"
PREFIX="${LIBGHOSTTY_VT_PREFIX:-$ROOT/vendor/libghostty-vt}"
SIMD="${LIBGHOSTTY_VT_SIMD:-false}"
@@ -14,7 +14,7 @@ need() {
echo "error: missing required command '$1'" >&2
echo "Install it first, then rerun npm run build:libghostty." >&2
if [[ "$1" == "zig" ]]; then
- echo "This Ghostty commit requires Zig 0.15.2. With mise: mise install" >&2
+ echo "This Ghostty commit requires Zig 0.17.0. With mise: mise install" >&2
fi
exit 1
fi
diff --git a/test/terminal.test.ts b/test/terminal.test.ts
index b4d599f..57c8ad7 100644
--- a/test/terminal.test.ts
+++ b/test/terminal.test.ts
@@ -133,20 +133,55 @@ describeIfNative("GhosttyVtTerminal", () => {
}
});
- // Ghostty added DECSTR in f473f31. Moving the pin past it makes this fail:
- // then drop the README's soft-reset limit and assert the reset instead.
- it("ignores the DECSTR soft reset with the pinned Ghostty", () => {
+ it("shows the cursor and resets the pen on DECSTR, keeping the screen", () => {
const term = createTerminal({ cols: 80, rows: 24 });
try {
- term.feed("\x1b[?25l\x1b[7m\x1b[!pX");
+ term.feed("\x1b[?25l\x1b[7mA\x1b[!pX");
const snapshot = term.snapshot({ includeCells: true });
- expect(snapshot.cursorVisible).toBe(false);
- expect(cellsByText(snapshot).get("X")?.inverse).toBe(true);
+ const cells = cellsByText(snapshot);
+ expect(snapshot.cursorVisible).toBe(true);
+ expect(cells.get("A")?.inverse).toBe(true);
+ expect(cells.get("X")).not.toHaveProperty("inverse");
+ expect(snapshot.visibleLines[0]?.text).toBe("AX");
} finally {
term.dispose();
}
});
+ it.each([
+ { scrollbackLimit: undefined, keepsHistory: false },
+ { scrollbackLimit: 0, keepsHistory: false },
+ { scrollbackLimit: 1, keepsHistory: true },
+ ])("keeps scrollback only for a nonzero scrollbackLimit ($scrollbackLimit)", ({ scrollbackLimit, keepsHistory }) => {
+ const options: CreateTerminalOptions = { cols: 80, rows: 24 };
+ if (scrollbackLimit !== undefined) options.scrollbackLimit = scrollbackLimit;
+ const term = createTerminal(options);
+ try {
+ term.feed(Array.from({ length: 200 }, (_, i) => `line ${i}\r\n`).join(""));
+ const scrollback = term.snapshot({ includeScrollback: true }).scrollbackLines ?? [];
+ expect(scrollback.length > 0).toBe(keepsHistory);
+ } finally {
+ term.dispose();
+ }
+ });
+
+ it("pulls history back when one resize makes the terminal narrower and taller", () => {
+ const resized = (steps: [number, number][]) => {
+ const term = createTerminal({ cols: 80, rows: 24, scrollbackLimit: 10_000 });
+ try {
+ term.feed(Array.from({ length: 40 }, (_, i) => `out ${i}\r\n`).join("") + "$ ");
+ for (const [cols, rows] of steps) term.resize(cols, rows);
+ return term.snapshot({ includeScrollback: true });
+ } finally {
+ term.dispose();
+ }
+ };
+ const oneStep = resized([[80, 10], [60, 30]]);
+ expect(oneStep.cursorRow).toBe(29);
+ expect(oneStep.visibleLines[29]?.text).toBe("$");
+ expect(oneStep).toEqual(resized([[80, 10], [80, 30], [60, 30]]));
+ });
+
it("updates snapshot dimensions on resize", () => {
const term = createTerminal({ cols: 80, rows: 24 });
try {