Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# csshx

This repository contains two related cluster-SSH tools:

| | path | language | platform |
|-|-|-|-|
| **Original csshX** (Gavin Brock's 2011 release) | [`csshX`](csshX) | Perl 5 | macOS only |
| **csshx-latest** — modern rewrite | [`csshx-latest/`](csshx-latest/) | Python 3.10+ (stdlib only) | macOS / Linux |

If you want the classic, terminal-coupled, Perl version of csshX, run
[`./csshX --man`](csshX). The historical install/usage notes live in
[`README.txt`](README.txt).

The rest of this file is a quick-start for the new
[`csshx-latest/`](csshx-latest/) rewrite. For the full architecture
diagram, backend support matrix, and design notes, see
[`csshx-latest/README.md`](csshx-latest/README.md).

---

## csshx-latest — quick start

A modern, terminal-agnostic cluster-SSH tool. PTY end-to-end (no
TIOCSTI), with pluggable backends for WaveTerm, tmux, iTerm2,
Terminal.app, Kitty, WezTerm, and a Manual fallback that prints attach
commands you can paste into any terminal.

### Install

```bash
cd csshx-latest/
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[test]'
```

Or with `uv`:

```bash
cd csshx-latest/
uv venv && uv pip install -e '.[test]'
```

### Run

```bash
csshx-latest web01 web02 web03
csshx-latest --launcher tmux web0{1..5}
csshx-latest --login deploy --ssh-args "-i ~/.ssh/cluster_key" host1 host2
csshx-latest --launcher manual host1 host2 # prints attach commands
```

Type in the master TUI to broadcast to every host at once. Click any
host's terminal block to type to just that host. Press **Ctrl-Q** to
exit.

`--launcher` choices: `auto` (default), `waveterm`, `tmux`, `iterm2`,
`terminal`, `kitty`, `wezterm`, `manual`. With `auto`, csshx-latest
auto-detects via `$TERM_PROGRAM`, `$KITTY_PID`, `$TMUX`, etc., and
falls back to `manual` if it doesn't recognize the environment (no
surprise tmux sessions).

### Tests

```bash
cd csshx-latest/
pytest -q
```

The package itself is Unix-only (uses `pty`, `termios`, `tty`,
`fcntl`); tests assume a Unix-like host.

### License

The original Perl csshX is released under the same terms as Perl
itself (Artistic + GPL — see [`README.txt`](README.txt)). The
`csshx-latest/` rewrite is MIT.
42 changes: 20 additions & 22 deletions csshX
Original file line number Diff line number Diff line change
Expand Up @@ -516,35 +516,33 @@ sub open_window {
my $cmd = join ' ', map { s/(["'])/\\$1/g; "'$_'" } @args;

# don't exec if debugging so we can see errors
unless ($config->debug) {
if (get_shell =~ /fish$/) {
$cmd = "clear; and exec $cmd" unless $config->debug;
} else {
$cmd = "clear && exec $cmd" unless $config->debug;
}
}
$cmd = "clear && exec $cmd" unless $config->debug;

# Hide the command from any shell history
$cmd = 'history -d $(($HISTCMD-1)) && '.$cmd if get_shell =~ m{/(ba)?sh$};
# TODO - (t)csh, ksh, zsh

my $tabobj = $terminal->doScript_in_($cmd, undef) || return;

# Get the window and tab IDs from the Apple Event itself
my $tab_ed = $tabobj->qualifiedSpecifier; # Undocumented call
my $tab_id = $tab_ed->descriptorForKeyword_(OSType 'seld')->int32Value-1;
my $win_ed = $tab_ed->descriptorForKeyword_(OSType 'from');
my $win_id = $win_ed->descriptorForKeyword_(OSType 'seld')->int32Value.'';

# Create an object unless we were passed one
my $obj = ref $pack ? $pack : $pack->SUPER::new();
$obj->set_windowid($win_id);
$obj->set_tabid($tab_id);

return $obj;
my $tty = $tabobj->tty->UTF8String || return;

my $windows = $terminal->windows;
# Quickly check if the tty even exists, since the next code is REALLY slow
#return unless grep { $tty eq $_ } @{Foundation::perlRefFromObjectRef $windows->valueForKey_("tty")};
for (my $n=0; $n<$windows->count; $n++) {
my $window = $windows->objectAtIndex_($n);
my $tabs = $window->tabs;
for (my $m=0; $m<$tabs->count; $m++) {
my $tab = $tabs->objectAtIndex_($m);
if ($tab->tty && ($tab->tty->UTF8String eq $tty)) {
my $obj = ref $pack ? $pack : $pack->SUPER::new();
$obj->set_windowid("".$window->id);
$obj->set_tabid($m);
return $obj;
}
}
}
}


sub set_windowid { *{$_[0]}->{windowid} = $_[1]; }
sub windowid { *{$_[0]}->{windowid}; }

Expand Down Expand Up @@ -1190,7 +1188,7 @@ sub parse_user_host_port {
package CsshX::Launcher;

use base qw(CsshX::Socket::Selectable);
use POSIX qw(tmpnam);
use File::Temp qw(tmpnam);
use FindBin qw($Bin $Script);;

sub new {
Expand Down
2 changes: 1 addition & 1 deletion csshX.iterm
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,7 @@ sub parse_user_host_port {
package CsshX::Launcher;

use base qw(CsshX::Socket::Selectable);
use POSIX qw(tmpnam);
use File::Temp qw(tmpnam);
use FindBin qw($Bin $Script);;

sub new {
Expand Down
11 changes: 11 additions & 0 deletions csshx-latest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
__pycache__/
*.pyc
.pytest_cache/
.venv/
venv/
.eggs/
*.egg-info/
build/
dist/
.coverage
.tox/
Loading