repos / zmx

session persistence for terminal processes
git clone https://github.com/neurosnap/zmx.git

commit
1dbbe3d
parent
e17899d
author
Eric Bower
date
2026-01-23 09:45:09 -0500 EST
feat(list): show `started_in` directory and original `cmd` if provided
2 files changed,  +71, -2
M src/ipc.zig
+8, -1
 1@@ -25,9 +25,16 @@ pub const Resize = packed struct {
 2     cols: u16,
 3 };
 4 
 5-pub const Info = packed struct {
 6+pub const MAX_CMD_LEN = 256;
 7+pub const MAX_CWD_LEN = 256;
 8+
 9+pub const Info = extern struct {
10     clients_len: usize,
11     pid: i32,
12+    cmd_len: u16,
13+    cwd_len: u16,
14+    cmd: [MAX_CMD_LEN]u8,
15+    cwd: [MAX_CWD_LEN]u8,
16 };
17 
18 pub fn expectedLength(data: []const u8) ?usize {
M src/main.zig
+63, -1
  1@@ -128,6 +128,7 @@ const Daemon = struct {
  2     running: bool,
  3     pid: i32,
  4     command: ?[]const []const u8 = null,
  5+    cwd: []const u8 = "",
  6     has_pty_output: bool = false,
  7     has_had_client: bool = false,
  8 
  9@@ -258,9 +259,37 @@ const Daemon = struct {
 10 
 11     pub fn handleInfo(self: *Daemon, client: *Client) !void {
 12         const clients_len = self.clients.items.len - 1;
 13+
 14+        // Build command string from args
 15+        var cmd_buf: [ipc.MAX_CMD_LEN]u8 = undefined;
 16+        var cmd_len: u16 = 0;
 17+        if (self.command) |args| {
 18+            for (args, 0..) |arg, i| {
 19+                if (i > 0) {
 20+                    if (cmd_len < ipc.MAX_CMD_LEN) {
 21+                        cmd_buf[cmd_len] = ' ';
 22+                        cmd_len += 1;
 23+                    }
 24+                }
 25+                const remaining = ipc.MAX_CMD_LEN - cmd_len;
 26+                const copy_len: u16 = @intCast(@min(arg.len, remaining));
 27+                @memcpy(cmd_buf[cmd_len..][0..copy_len], arg[0..copy_len]);
 28+                cmd_len += copy_len;
 29+            }
 30+        }
 31+
 32+        // Copy cwd
 33+        var cwd_buf: [ipc.MAX_CWD_LEN]u8 = undefined;
 34+        const cwd_len: u16 = @intCast(@min(self.cwd.len, ipc.MAX_CWD_LEN));
 35+        @memcpy(cwd_buf[0..cwd_len], self.cwd[0..cwd_len]);
 36+
 37         const info = ipc.Info{
 38             .clients_len = clients_len,
 39             .pid = self.pid,
 40+            .cmd_len = cmd_len,
 41+            .cwd_len = cwd_len,
 42+            .cmd = cmd_buf,
 43+            .cwd = cwd_buf,
 44         };
 45         try ipc.appendMessage(self.alloc, &client.write_buf, .Info, std.mem.asBytes(&info));
 46         client.has_pending_output = true;
 47@@ -357,6 +386,10 @@ pub fn main() !void {
 48         if (command_args.items.len > 0) {
 49             command = command_args.items;
 50         }
 51+
 52+        var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
 53+        const cwd = std.posix.getcwd(&cwd_buf) catch "";
 54+
 55         var daemon = Daemon{
 56             .running = true,
 57             .cfg = &cfg,
 58@@ -366,6 +399,7 @@ pub fn main() !void {
 59             .socket_path = undefined,
 60             .pid = undefined,
 61             .command = command,
 62+            .cwd = cwd,
 63         };
 64         daemon.socket_path = try getSocketPath(alloc, cfg.socket_dir, session_name);
 65         std.log.info("socket path={s}", .{daemon.socket_path});
 66@@ -382,6 +416,10 @@ pub fn main() !void {
 67         }
 68 
 69         const clients = try std.ArrayList(*Client).initCapacity(alloc, 10);
 70+
 71+        var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
 72+        const cwd = std.posix.getcwd(&cwd_buf) catch "";
 73+
 74         var daemon = Daemon{
 75             .running = true,
 76             .cfg = &cfg,
 77@@ -391,6 +429,7 @@ pub fn main() !void {
 78             .socket_path = undefined,
 79             .pid = undefined,
 80             .command = null,
 81+            .cwd = cwd,
 82         };
 83         daemon.socket_path = try getSocketPath(alloc, cfg.socket_dir, session_name);
 84         std.log.info("socket path={s}", .{daemon.socket_path});
 85@@ -436,6 +475,8 @@ const SessionEntry = struct {
 86     clients_len: ?usize,
 87     is_error: bool,
 88     error_name: ?[]const u8,
 89+    cmd: ?[]const u8 = null,
 90+    cwd: ?[]const u8 = null,
 91 
 92     fn lessThan(_: void, a: SessionEntry, b: SessionEntry) bool {
 93         return std.mem.order(u8, a.name, b.name) == .lt;
 94@@ -457,6 +498,8 @@ fn list(cfg: *Cfg) !void {
 95     defer {
 96         for (sessions.items) |session| {
 97             alloc.free(session.name);
 98+            if (session.cmd) |cmd| alloc.free(cmd);
 99+            if (session.cwd) |cwd| alloc.free(cwd);
100         }
101         sessions.deinit(alloc);
102     }
103@@ -483,12 +526,24 @@ fn list(cfg: *Cfg) !void {
104             };
105             posix.close(result.fd);
106 
107+            // Extract cmd and cwd from the fixed-size arrays
108+            const cmd: ?[]const u8 = if (result.info.cmd_len > 0)
109+                alloc.dupe(u8, result.info.cmd[0..result.info.cmd_len]) catch null
110+            else
111+                null;
112+            const cwd: ?[]const u8 = if (result.info.cwd_len > 0)
113+                alloc.dupe(u8, result.info.cwd[0..result.info.cwd_len]) catch null
114+            else
115+                null;
116+
117             try sessions.append(alloc, .{
118                 .name = name,
119                 .pid = result.info.pid,
120                 .clients_len = result.info.clients_len,
121                 .is_error = false,
122                 .error_name = null,
123+                .cmd = cmd,
124+                .cwd = cwd,
125             });
126         }
127     }
128@@ -505,7 +560,14 @@ fn list(cfg: *Cfg) !void {
129         if (session.is_error) {
130             try w.interface.print("session_name={s}\tstatus={s}\t(cleaning up)\n", .{ session.name, session.error_name.? });
131         } else {
132-            try w.interface.print("session_name={s}\tpid={d}\tclients={d}\n", .{ session.name, session.pid.?, session.clients_len.? });
133+            try w.interface.print("session_name={s}\tpid={d}\tclients={d}", .{ session.name, session.pid.?, session.clients_len.? });
134+            if (session.cwd) |cwd| {
135+                try w.interface.print("\tstarted_in={s}", .{cwd});
136+            }
137+            if (session.cmd) |cmd| {
138+                try w.interface.print("\tcmd={s}", .{cmd});
139+            }
140+            try w.interface.print("\n", .{});
141         }
142         try w.interface.flush();
143     }