From 0565ed39546a982b7f47d0dd0ba341bc41cc092f Mon Sep 17 00:00:00 2001 From: Jason Rayne Date: Wed, 25 Jun 2025 12:47:38 -0700 Subject: refactor: replace ghostty wrapper with proper CLI actions for terminfo cache management - Add +list-ssh-cache and +clear-ssh-cache CLI actions - Remove ghostty() wrapper functions from all shell integrations - Improve variable naming in shell scripts for readability Addresses @00-kat's feedback about CLI discoverability and naming consistency. The new CLI actions follow established Ghostty patterns and are discoverable via `ghostty --help`, while maintaining clean separation of concerns between shell logic and cache management. --- src/cli/action.zig | 12 ++++++++ src/cli/clear_ssh_cache.zig | 40 +++++++++++++++++++++++++ src/cli/list_ssh_cache.zig | 38 ++++++++++++++++++++++++ src/cli/ssh_cache.zig | 71 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/cli/clear_ssh_cache.zig create mode 100644 src/cli/list_ssh_cache.zig create mode 100644 src/cli/ssh_cache.zig (limited to 'src/cli') diff --git a/src/cli/action.zig b/src/cli/action.zig index 009afb4c9..1d1c3bfa0 100644 --- a/src/cli/action.zig +++ b/src/cli/action.zig @@ -9,6 +9,8 @@ const list_keybinds = @import("list_keybinds.zig"); const list_themes = @import("list_themes.zig"); const list_colors = @import("list_colors.zig"); const list_actions = @import("list_actions.zig"); +const list_ssh_cache = @import("list_ssh_cache.zig"); +const clear_ssh_cache = @import("clear_ssh_cache.zig"); const edit_config = @import("edit_config.zig"); const show_config = @import("show_config.zig"); const validate_config = @import("validate_config.zig"); @@ -41,6 +43,12 @@ pub const Action = enum { /// List keybind actions @"list-actions", + /// List hosts with Ghostty SSH terminfo installed + @"list-ssh-cache", + + /// Clear Ghostty SSH terminfo cache + @"clear-ssh-cache", + /// Edit the config file in the configured terminal editor. @"edit-config", @@ -155,6 +163,8 @@ pub const Action = enum { .@"list-themes" => try list_themes.run(alloc), .@"list-colors" => try list_colors.run(alloc), .@"list-actions" => try list_actions.run(alloc), + .@"list-ssh-cache" => @import("list_ssh_cache.zig").run(alloc), + .@"clear-ssh-cache" => @import("clear_ssh_cache.zig").run(alloc), .@"edit-config" => try edit_config.run(alloc), .@"show-config" => try show_config.run(alloc), .@"validate-config" => try validate_config.run(alloc), @@ -192,6 +202,8 @@ pub const Action = enum { .@"list-themes" => list_themes.Options, .@"list-colors" => list_colors.Options, .@"list-actions" => list_actions.Options, + .@"list-ssh-cache" => list_ssh_cache.Options, + .@"clear-ssh-cache" => clear_ssh_cache.Options, .@"edit-config" => edit_config.Options, .@"show-config" => show_config.Options, .@"validate-config" => validate_config.Options, diff --git a/src/cli/clear_ssh_cache.zig b/src/cli/clear_ssh_cache.zig new file mode 100644 index 000000000..062af4221 --- /dev/null +++ b/src/cli/clear_ssh_cache.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const args = @import("args.zig"); +const Action = @import("action.zig").Action; +const ssh_cache = @import("ssh_cache.zig"); + +pub const Options = struct { + pub fn deinit(self: Options) void { + _ = self; + } + + /// Enables `-h` and `--help` to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } +}; + +/// Clear the Ghostty SSH terminfo cache. +/// +/// This command removes the cache of hosts where Ghostty's terminfo has been installed +/// via the ssh-terminfo shell integration feature. After clearing, terminfo will be +/// reinstalled on the next SSH connection to previously cached hosts. +/// +/// Use this if you need to force reinstallation of terminfo or clean up old entries. +pub fn run(alloc: Allocator) !u8 { + var opts: Options = .{}; + defer opts.deinit(); + + { + var iter = try args.argsIterator(alloc); + defer iter.deinit(); + try args.parse(Options, alloc, &opts, &iter); + } + + const stdout = std.io.getStdOut().writer(); + try ssh_cache.clearCache(alloc, stdout); + + return 0; +} diff --git a/src/cli/list_ssh_cache.zig b/src/cli/list_ssh_cache.zig new file mode 100644 index 000000000..a799d95bd --- /dev/null +++ b/src/cli/list_ssh_cache.zig @@ -0,0 +1,38 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const args = @import("args.zig"); +const Action = @import("action.zig").Action; +const ssh_cache = @import("ssh_cache.zig"); + +pub const Options = struct { + pub fn deinit(self: Options) void { + _ = self; + } + + /// Enables `-h` and `--help` to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } +}; + +/// List hosts with Ghostty SSH terminfo installed via the ssh-terminfo shell integration feature. +/// +/// This command shows all remote hosts where Ghostty's terminfo has been successfully +/// installed through the SSH integration. The cache is automatically maintained when +/// connecting to remote hosts with `shell-integration-features = ssh-terminfo` enabled. +pub fn run(alloc: Allocator) !u8 { + var opts: Options = .{}; + defer opts.deinit(); + + { + var iter = try args.argsIterator(alloc); + defer iter.deinit(); + try args.parse(Options, alloc, &opts, &iter); + } + + const stdout = std.io.getStdOut().writer(); + try ssh_cache.listCachedHosts(alloc, stdout); + + return 0; +} diff --git a/src/cli/ssh_cache.zig b/src/cli/ssh_cache.zig new file mode 100644 index 000000000..c3484735a --- /dev/null +++ b/src/cli/ssh_cache.zig @@ -0,0 +1,71 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Child = std.process.Child; + +/// Get the path to the shared cache script +fn getCacheScriptPath(alloc: Allocator) ![]u8 { + // Use GHOSTTY_RESOURCES_DIR if available, otherwise assume relative path + const resources_dir = std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR") catch { + // Fallback: assume we're running from build directory + return try alloc.dupe(u8, "src/shell-integration/shared/ghostty-ssh-cache"); + }; + defer alloc.free(resources_dir); + + return try std.fs.path.join(alloc, &[_][]const u8{ resources_dir, "shell-integration", "shared", "ghostty-ssh-cache" }); +} + +/// List cached hosts by calling the external script +pub fn listCachedHosts(alloc: Allocator, writer: anytype) !void { + const script_path = try getCacheScriptPath(alloc); + defer alloc.free(script_path); + + var child = Child.init(&[_][]const u8{ script_path, "list" }, alloc); + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stdout = try child.stdout.?.readToEndAlloc(alloc, std.math.maxInt(usize)); + defer alloc.free(stdout); + + const stderr = try child.stderr.?.readToEndAlloc(alloc, std.math.maxInt(usize)); + defer alloc.free(stderr); + + _ = try child.wait(); + + // Output the results regardless of exit code + try writer.writeAll(stdout); + if (stderr.len > 0) { + try writer.writeAll(stderr); + } + + // Script handles its own success/error messaging, so we don't need to check exit code +} + +/// Clear cache by calling the external script +pub fn clearCache(alloc: Allocator, writer: anytype) !void { + const script_path = try getCacheScriptPath(alloc); + defer alloc.free(script_path); + + var child = Child.init(&[_][]const u8{ script_path, "clear" }, alloc); + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stdout = try child.stdout.?.readToEndAlloc(alloc, std.math.maxInt(usize)); + defer alloc.free(stdout); + + const stderr = try child.stderr.?.readToEndAlloc(alloc, std.math.maxInt(usize)); + defer alloc.free(stderr); + + _ = try child.wait(); + + // Output the results regardless of exit code + try writer.writeAll(stdout); + if (stderr.len > 0) { + try writer.writeAll(stderr); + } + + // Script handles its own success/error messaging, so we don't need to check exit code +} -- cgit v1.2.3