a73x

c884437a

feat: scaffold muxd; prove ghostty-vt drives headless

a73x   2026-08-07 06:52

Commit message
feat: scaffold muxd; prove ghostty-vt drives headless

Zig 0.15.2 pinned (ghostty requires 0.15.x); LLD linking works around
the self-hosted linker's missing .sframe support with gcc-16 crt1.o.

Makefile
Old New
@@ -0,0 +1,17 @@
1 # ghostty (pinned in build.zig.zon) hard-requires Zig 0.15.x; the system
2 # default zig is 0.17-dev. Override with ZIG=... if yours lives elsewhere.
3 ZIG ?= $(HOME)/Downloads/zig-x86_64-linux-0.15.2/zig
4
5 .PHONY: build test e2e clean
6
7 build:
8 $(ZIG) build
9
10 test:
11 $(ZIG) build test
12
13 e2e:
14 $(ZIG) build e2e
15
16 clean:
17 rm -rf zig-out .zig-cache
build.zig
Old New
@@ -0,0 +1,64 @@
1 const std = @import("std");
2
3 pub fn build(b: *std.Build) void {
4 const target = b.standardTargetOptions(.{});
5 const optimize = b.standardOptimizeOption(.{});
6
7 const ghostty_dep = b.lazyDependency("ghostty", .{
8 .target = target,
9 .optimize = optimize,
10 });
11
12 const engine_mod = b.createModule(.{
13 .root_source_file = b.path("src/engine.zig"),
14 .target = target,
15 .optimize = optimize,
16 });
17 if (ghostty_dep) |dep| {
18 engine_mod.addImport("ghostty-vt", dep.module("ghostty-vt"));
19 }
20
21 const pty_mod = b.createModule(.{
22 .root_source_file = b.path("src/pty.zig"),
23 .target = target,
24 .optimize = optimize,
25 .link_libc = true,
26 });
27
28 const debug_mod = b.createModule(.{
29 .root_source_file = b.path("src/debug.zig"),
30 .target = target,
31 .optimize = optimize,
32 });
33 debug_mod.addImport("engine", engine_mod);
34
35 const exe_mod = b.createModule(.{
36 .root_source_file = b.path("src/main.zig"),
37 .target = target,
38 .optimize = optimize,
39 .link_libc = true,
40 });
41 exe_mod.addImport("engine", engine_mod);
42 exe_mod.addImport("pty", pty_mod);
43 exe_mod.addImport("debug", debug_mod);
44
45 const exe = b.addExecutable(.{ .name = "muxd", .root_module = exe_mod });
46 // Zig 0.15's self-hosted x86_64 linker can't handle the .sframe
47 // sections emitted by gcc >= 16 crt1.o on this system; LLD can.
48 exe.use_llvm = true;
49 exe.use_lld = true;
50 b.installArtifact(exe);
51
52 const test_step = b.step("test", "Run unit tests");
53 for ([_]*std.Build.Module{ engine_mod, pty_mod, debug_mod }) |mod| {
54 const t = b.addTest(.{ .root_module = mod });
55 t.use_llvm = true;
56 t.use_lld = true;
57 test_step.dependOn(&b.addRunArtifact(t).step);
58 }
59
60 const e2e = b.addSystemCommand(&.{"test/e2e.sh"});
61 e2e.addArtifactArg(exe);
62 const e2e_step = b.step("e2e", "Run end-to-end test");
63 e2e_step.dependOn(&e2e.step);
64 }
build.zig.zon
Old New
@@ -0,0 +1,18 @@
1 .{
2 .name = .mux,
3 .version = "0.0.1",
4 .fingerprint = 0xb3d8f5dcb89714b5,
5 .minimum_zig_version = "0.15.2",
6 .paths = .{
7 "build.zig",
8 "build.zig.zon",
9 "src",
10 },
11 .dependencies = .{
12 .ghostty = .{
13 .url = "git+https://github.com/ghostty-org/ghostty#853183e911b70ff7b61057f52fc7b47ea4934238",
14 .hash = "ghostty-1.3.2-dev-5UdBC7VOBgVv0iA-qLRtBnau_zLIv7iGGLdnEiW6fUYU",
15 .lazy = true,
16 },
17 },
18 }
src/debug.zig
No textual changes available.
src/engine.zig
Old New
@@ -0,0 +1,19 @@
1 //! Authoritative headless terminal engine. Wraps ghostty-vt's Terminal
2 //! and TerminalStream behind the small surface muxd needs.
3 const std = @import("std");
4 const vt = @import("ghostty-vt");
5
6 test "ghostty-vt boots headless and text lands in the grid" {
7 const alloc = std.testing.allocator;
8 var term = try vt.Terminal.init(alloc, .{ .cols = 80, .rows = 24 });
9 defer term.deinit(alloc);
10
11 var stream: vt.TerminalStream = .initAlloc(alloc, .{ .terminal = &term });
12 defer stream.deinit();
13
14 stream.nextSlice("hello");
15
16 const s = try term.plainString(alloc);
17 defer alloc.free(s);
18 try std.testing.expectEqualStrings("hello", s);
19 }
src/main.zig
Old New
@@ -0,0 +1 @@
1 pub fn main() !void {}
src/pty.zig
No textual changes available.