Commit 0ba19cd

Amir B.  ·  2026-05-16 20:32:03 -0400 EDT
parent a28c87a
fix: handle run help flag (#152)

Extends the --help/-h check to all subcommands that accept a session
name or other positional args, so users don't accidentally create a
session named "--help" or trigger confusing errors.

Covers: list, completions, history, attach, send, print, write,
kill, wait, tail.
2 files changed,  +67, -2
+40, -2
  1@@ -98,10 +98,19 @@ pub fn main() !void {
  2     } else if (std.mem.eql(u8, cmd, "help") or std.mem.eql(u8, cmd, "h") or std.mem.eql(u8, cmd, "-h")) {
  3         return help();
  4     } else if (std.mem.eql(u8, cmd, "list") or std.mem.eql(u8, cmd, "l") or std.mem.eql(u8, cmd, "ls")) {
  5-        const short = if (args.next()) |arg| std.mem.eql(u8, arg, "--short") else false;
  6+        var short = false;
  7+        if (args.next()) |arg| {
  8+            if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
  9+                return help();
 10+            }
 11+            short = std.mem.eql(u8, arg, "--short");
 12+        }
 13         return list(&cfg, short);
 14     } else if (std.mem.eql(u8, cmd, "completions") or std.mem.eql(u8, cmd, "c")) {
 15         const arg = args.next() orelse return;
 16+        if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
 17+            return help();
 18+        }
 19         const shell = completions.Shell.fromString(arg) orelse return;
 20         return printCompletions(shell);
 21     } else if (std.mem.eql(u8, cmd, "detach") or std.mem.eql(u8, cmd, "d")) {
 22@@ -110,7 +119,9 @@ pub fn main() !void {
 23         var session_name: ?[]const u8 = null;
 24         var format: util.HistoryFormat = .plain;
 25         while (args.next()) |arg| {
 26-            if (std.mem.eql(u8, arg, "--vt")) {
 27+            if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
 28+                return help();
 29+            } else if (std.mem.eql(u8, arg, "--vt")) {
 30                 format = .vt;
 31             } else if (std.mem.eql(u8, arg, "--html")) {
 32                 format = .html;
 33@@ -124,6 +135,9 @@ pub fn main() !void {
 34         return history(&cfg, sesh, format);
 35     } else if (std.mem.eql(u8, cmd, "attach") or std.mem.eql(u8, cmd, "a")) {
 36         const session_name = args.next() orelse "";
 37+        if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
 38+            return help();
 39+        }
 40 
 41         var command_args: std.ArrayList([]const u8) = .empty;
 42         defer command_args.deinit(alloc);
 43@@ -163,6 +177,9 @@ pub fn main() !void {
 44         return attach(&daemon);
 45     } else if (std.mem.eql(u8, cmd, "run") or std.mem.eql(u8, cmd, "r")) {
 46         const session_name = args.next() orelse "";
 47+        if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
 48+            return help();
 49+        }
 50 
 51         var cmd_args_raw: std.ArrayList([]const u8) = .empty;
 52         defer cmd_args_raw.deinit(alloc);
 53@@ -203,6 +220,9 @@ pub fn main() !void {
 54         return run(&daemon, detached, cmd_args_raw.items);
 55     } else if (std.mem.eql(u8, cmd, "send") or std.mem.eql(u8, cmd, "s")) {
 56         const session_name = args.next() orelse "";
 57+        if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
 58+            return help();
 59+        }
 60         if (session_name.len == 0) return error.SessionNameRequired;
 61 
 62         var text_parts: std.ArrayList([]const u8) = .empty;
 63@@ -220,6 +240,9 @@ pub fn main() !void {
 64         return send(&cfg, sesh, socket_path, text_parts.items, .Input);
 65     } else if (std.mem.eql(u8, cmd, "print") or std.mem.eql(u8, cmd, "p")) {
 66         const session_name = args.next() orelse "";
 67+        if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
 68+            return help();
 69+        }
 70         if (session_name.len == 0) return error.SessionNameRequired;
 71 
 72         var text_parts: std.ArrayList([]const u8) = .empty;
 73@@ -249,6 +272,9 @@ pub fn main() !void {
 74         }
 75         var force = false;
 76         while (args.next()) |session_name| {
 77+            if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
 78+                return help();
 79+            }
 80             if (std.mem.eql(u8, session_name, "--force")) {
 81                 force = true;
 82                 continue;
 83@@ -292,6 +318,9 @@ pub fn main() !void {
 84             matchers.deinit(alloc);
 85         }
 86         while (args.next()) |session_name| {
 87+            if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
 88+                return help();
 89+            }
 90             const m = try parseSessionArg(alloc, session_name);
 91             try matchers.append(alloc, m);
 92         }
 93@@ -308,6 +337,9 @@ pub fn main() !void {
 94             matchers.deinit(alloc);
 95         }
 96         while (args.next()) |session_name| {
 97+            if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
 98+                return help();
 99+            }
100             const m = try parseSessionArg(alloc, session_name);
101             try matchers.append(alloc, m);
102         }
103@@ -375,8 +407,14 @@ pub fn main() !void {
104         _ = try tail(client_socket_fds, false, false);
105     } else if (std.mem.eql(u8, cmd, "write") or std.mem.eql(u8, cmd, "wr")) {
106         const session_name = args.next() orelse "";
107+        if (std.mem.eql(u8, session_name, "--help") or std.mem.eql(u8, session_name, "-h")) {
108+            return help();
109+        }
110         if (session_name.len == 0) return error.SessionNameRequired;
111         const file_path = args.next() orelse "";
112+        if (std.mem.eql(u8, file_path, "--help") or std.mem.eql(u8, file_path, "-h")) {
113+            return help();
114+        }
115         if (file_path.len == 0) return error.FilePathRequired;
116 
117         var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
+27, -0
 1@@ -50,6 +50,33 @@ load test_helper
 2   [ "$status" -ne 0 ]
 3 }
 4 
 5+@test "run --help shows help without creating a session" {
 6+  run "$ZMX" run --help
 7+  [ "$status" -eq 0 ]
 8+  [[ "$output" == *"Usage:"* ]]
 9+
10+  run "$ZMX" list --short
11+  [ "$status" -eq 0 ]
12+  [[ "$output" != *"--help"* ]]
13+}
14+
15+@test "subcommands handle --help and -h without side effects" {
16+  for cmd in attach send print write kill wait tail history list completions; do
17+    run "$ZMX" "$cmd" --help
18+    [ "$status" -eq 0 ]
19+    [[ "$output" == *"Usage:"* ]]
20+
21+    run "$ZMX" "$cmd" -h
22+    [ "$status" -eq 0 ]
23+    [[ "$output" == *"Usage:"* ]]
24+  done
25+
26+  run "$ZMX" list --short
27+  [ "$status" -eq 0 ]
28+  [[ "$output" != *"--help"* ]]
29+  [[ "$output" != *"-h"* ]]
30+}
31+
32 # ============================================================================
33 # Send (raw PTY input)
34 # ============================================================================