- commit
- 98fa966
- parent
- fac9795
- author
- Eric Bower
- date
- 2026-03-23 22:49:44 -0400 EDT
feat(kill): accepts multiple args and matches session prefixes Examples: - zmx kill one - zmx kill one two three - zmx kill d. - ZMX_SESSION_PREFIX="d." zmx kill
7 files changed,
+74,
-17
+2,
-0
1@@ -1,2 +1,4 @@
2 .git
3 zig-out
4+.zig-cache
5+.jj
+5,
-0
1@@ -4,6 +4,11 @@ Use spec: https://common-changelog.org/
2
3 ## Staged
4
5+### Changed
6+
7+- `zmx kill` now supports multiple args and it will kill sessions that match a prefix
8+ - e.g. `zmx kill d.` will kill all sessions that match that prefix
9+
10 ### Fixed
11
12 - `zmx list` will send "no sessions found" to stderr instead of stdout
+1,
-1
1@@ -13,7 +13,7 @@ ENV PATH=/usr/local/zig:$PATH
2
3 WORKDIR /app
4
5-COPY build.zig build.zig.zon src/ /app/
6+COPY . /app/
7
8 RUN zig build
9
+1,
-1
1@@ -73,7 +73,7 @@ Commands:
2 [r]un <name> [command...] Send command without attaching, creating session if needed
3 [d]etach Detach all clients from current session (ctrl+\ for current client)
4 [l]ist [--short] List active sessions
5- [k]ill <name> Kill a session and all attached clients
6+ [k]ill <name>... Kill a session and all attached clients
7 [hi]story <name> [--vt|--html] Output session scrollback (--vt or --html for escape sequences)
8 [w]ait <name>... Wait for session tasks to complete
9 [c]ompletions <shell> Completion scripts for shell integration (bash, zsh, or fish)
M
pico.sh
+3,
-5
1@@ -6,14 +6,12 @@ set -xeo pipefail
2 export ZMX_SESSION_PREFIX="ci-"
3
4 zmx run build podman build -t zig .
5-zmx wait build
6+zmx wait
7
8 zmx run fmt podman run --rm -it -v "$(pwd)":/app zig zig fmt --check .
9 zmx run test podman run --rm -it -v "$(pwd)":/app zig zig build test --summary all
10-zmx wait fmt test
11+zmx wait
12
13-zmx kill build
14-zmx kill fmt
15-zmx kill test
16+zmx kill
17
18 echo "success!"
+60,
-8
1@@ -75,11 +75,6 @@ pub fn main() !void {
2 return printCompletions(shell);
3 } else if (std.mem.eql(u8, cmd, "detach") or std.mem.eql(u8, cmd, "d")) {
4 return detachAll(&cfg);
5- } else if (std.mem.eql(u8, cmd, "kill") or std.mem.eql(u8, cmd, "k")) {
6- const session_name = args.next() orelse "";
7- const sesh = try socket.getSeshName(alloc, session_name);
8- defer alloc.free(sesh);
9- return kill(&cfg, sesh);
10 } else if (std.mem.eql(u8, cmd, "history") or std.mem.eql(u8, cmd, "hi")) {
11 var session_name: ?[]const u8 = null;
12 var format: util.HistoryFormat = .plain;
13@@ -169,6 +164,53 @@ pub fn main() !void {
14 };
15 std.log.info("socket path={s}", .{daemon.socket_path});
16 return run(&daemon, cmd_args_raw.items);
17+ } else if (std.mem.eql(u8, cmd, "kill") or std.mem.eql(u8, cmd, "k")) {
18+ var stderr_buffer: [1024]u8 = undefined;
19+ var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
20+ const stderr = &stderr_writer.interface;
21+
22+ var args_raw: std.ArrayList([]const u8) = .empty;
23+ defer {
24+ for (args_raw.items) |sesh| {
25+ alloc.free(sesh);
26+ }
27+ args_raw.deinit(alloc);
28+ }
29+ while (args.next()) |session_name| {
30+ const sesh = try socket.getSeshName(alloc, session_name);
31+ try args_raw.append(alloc, sesh);
32+ }
33+ // if no args are provided we assume they want to wait for all sessions matching the
34+ // prefix.
35+ if (args_raw.items.len == 0) {
36+ const prefix = socket.getSeshPrefix();
37+ if (prefix.len == 0) {
38+ return error.SessionNameRequired;
39+ }
40+ try args_raw.append(alloc, try alloc.dupe(u8, prefix));
41+ }
42+ var sessions = try util.get_session_entries(alloc, cfg.socket_dir);
43+ defer {
44+ for (sessions.items) |session| {
45+ session.deinit(alloc);
46+ }
47+ sessions.deinit(alloc);
48+ }
49+ for (sessions.items) |session| {
50+ for (args_raw.items) |prefix| {
51+ if (std.mem.startsWith(u8, session.name, prefix)) {
52+ kill(&cfg, session.name) catch |err| {
53+ try stderr.print(
54+ "failed to kill session={s}: {s}\n",
55+ .{ session.name, @errorName(err) },
56+ );
57+ try stderr.flush();
58+ };
59+ break;
60+ }
61+ }
62+ }
63+ return;
64 } else if (std.mem.eql(u8, cmd, "wait") or std.mem.eql(u8, cmd, "w")) {
65 var args_raw: std.ArrayList([]const u8) = .empty;
66 defer {
67@@ -181,6 +223,15 @@ pub fn main() !void {
68 const sesh = try socket.getSeshName(alloc, session_name);
69 try args_raw.append(alloc, sesh);
70 }
71+ // if no args are provided we assume they want to wait for all sessions matching the
72+ // prefix.
73+ if (args_raw.items.len == 0) {
74+ const prefix = socket.getSeshPrefix();
75+ if (prefix.len == 0) {
76+ return error.SessionNameRequired;
77+ }
78+ try args_raw.append(alloc, prefix);
79+ }
80 return wait(&cfg, args_raw);
81 } else {
82 return help();
83@@ -760,7 +811,7 @@ fn help() !void {
84 \\ [r]un <name> [command...] Send command without attaching, creating session if needed
85 \\ [d]etach Detach all clients from current session (ctrl+\ for current client)
86 \\ [l]ist [--short] List active sessions
87- \\ [k]ill <name> Kill a session and all attached clients
88+ \\ [k]ill <name>... Kill a session and all attached clients
89 \\ [hi]story <name> [--vt|--html] Output session scrollback (--vt or --html for escape sequences)
90 \\ [w]ait <name>... Wait for session tasks to complete
91 \\ [c]ompletions <shell> Completion scripts for shell integration (bash, zsh, or fish)
92@@ -891,7 +942,7 @@ fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void {
93 }
94 }
95
96- std.Thread.sleep(1000 * std.time.ns_per_ms);
97+ std.Thread.sleep(3000 * std.time.ns_per_ms);
98 }
99 }
100
101@@ -1004,13 +1055,14 @@ fn kill(cfg: *Cfg, session_name: []const u8) !void {
102 w.interface.flush() catch {};
103 return;
104 };
105+
106 defer posix.close(result.fd);
107 ipc.send(result.fd, .Kill, "") catch |err| switch (err) {
108 error.BrokenPipe, error.ConnectionResetByPeer => return,
109 else => return err,
110 };
111
112- var buf: [4096]u8 = undefined;
113+ var buf: [100]u8 = undefined;
114 var w = std.fs.File.stdout().writer(&buf);
115 try w.interface.print("killed session {s}\n", .{session_name});
116 try w.interface.flush();
+2,
-2
1@@ -1,7 +1,7 @@
2 const std = @import("std");
3 const posix = std.posix;
4
5-pub fn seshPrefix() []const u8 {
6+pub fn getSeshPrefix() []const u8 {
7 return std.posix.getenv("ZMX_SESSION_PREFIX") orelse "";
8 }
9
10@@ -10,7 +10,7 @@ pub fn getSeshNameFromEnv() []const u8 {
11 }
12
13 pub fn getSeshName(alloc: std.mem.Allocator, sesh: []const u8) ![]const u8 {
14- const prefix = seshPrefix();
15+ const prefix = getSeshPrefix();
16 if (prefix.len == 0 and sesh.len == 0) {
17 return error.SessionNameRequired;
18 }