multiSSH3 6.32__tar.gz → 6.33__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {multissh3-6.32/multiSSH3.egg-info → multissh3-6.33}/PKG-INFO +14 -12
  2. {multissh3-6.32 → multissh3-6.33}/README.md +13 -11
  3. {multissh3-6.32 → multissh3-6.33}/mssh_complete.py +114 -5
  4. {multissh3-6.32 → multissh3-6.33/multiSSH3.egg-info}/PKG-INFO +14 -12
  5. {multissh3-6.32 → multissh3-6.33}/multiSSH3.egg-info/SOURCES.txt +7 -0
  6. {multissh3-6.32 → multissh3-6.33}/multiSSH3.py +227 -34
  7. {multissh3-6.32 → multissh3-6.33}/tests/conftest.py +14 -0
  8. {multissh3-6.32 → multissh3-6.33}/tests/data/run_command_golden.json +189 -34
  9. {multissh3-6.32 → multissh3-6.33}/tests/test_cli_branches.py +1 -1
  10. {multissh3-6.32 → multissh3-6.33}/tests/test_cli_processing.py +2 -1
  11. multissh3-6.33/tests/test_closed_output.py +60 -0
  12. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_engine.py +1 -1
  13. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_keys.py +82 -6
  14. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_picker.py +45 -0
  15. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_shells.py +39 -0
  16. multissh3-6.33/tests/test_complete_start.py +102 -0
  17. {multissh3-6.32 → multissh3-6.33}/tests/test_config_cli.py +2 -2
  18. {multissh3-6.32 → multissh3-6.33}/tests/test_config_values.py +7 -5
  19. {multissh3-6.32 → multissh3-6.33}/tests/test_copy_id.py +2 -2
  20. {multissh3-6.32 → multissh3-6.33}/tests/test_curses_event_loop.py +66 -0
  21. {multissh3-6.32 → multissh3-6.33}/tests/test_history_replay.py +6 -0
  22. {multissh3-6.32 → multissh3-6.33}/tests/test_identities_only.py +1 -1
  23. {multissh3-6.32 → multissh3-6.33}/tests/test_ipmi_logins.py +18 -7
  24. {multissh3-6.32 → multissh3-6.33}/tests/test_main_lifecycle.py +2 -1
  25. multissh3-6.33/tests/test_no_hosts.py +46 -0
  26. multissh3-6.33/tests/test_password_handling.py +153 -0
  27. multissh3-6.33/tests/test_passwords_out_of_ps.py +109 -0
  28. {multissh3-6.32 → multissh3-6.33}/tests/test_run_command_branches.py +5 -3
  29. {multissh3-6.32 → multissh3-6.33}/tests/test_run_command_characterization.py +14 -8
  30. {multissh3-6.32 → multissh3-6.33}/tests/test_run_on_hosts_modes.py +7 -4
  31. {multissh3-6.32 → multissh3-6.33}/tests/test_runtime_branch_matrix.py +3 -2
  32. {multissh3-6.32 → multissh3-6.33}/tests/test_sshpass_exit.py +1 -1
  33. multissh3-6.33/tests/test_underscore_settings.py +83 -0
  34. multissh3-6.33/tests/test_wide_wrap.py +78 -0
  35. {multissh3-6.32 → multissh3-6.33}/LICENSE +0 -0
  36. {multissh3-6.32 → multissh3-6.33}/MANIFEST.in +0 -0
  37. {multissh3-6.32 → multissh3-6.33}/multiSSH3.egg-info/dependency_links.txt +0 -0
  38. {multissh3-6.32 → multissh3-6.33}/multiSSH3.egg-info/entry_points.txt +0 -0
  39. {multissh3-6.32 → multissh3-6.33}/multiSSH3.egg-info/top_level.txt +0 -0
  40. {multissh3-6.32 → multissh3-6.33}/pytest.ini +0 -0
  41. {multissh3-6.32 → multissh3-6.33}/setup.cfg +0 -0
  42. {multissh3-6.32 → multissh3-6.33}/setup.py +0 -0
  43. {multissh3-6.32 → multissh3-6.33}/tests/bench_complete.py +0 -0
  44. {multissh3-6.32 → multissh3-6.33}/tests/complete_shell_harness.py +0 -0
  45. {multissh3-6.32 → multissh3-6.33}/tests/shells/Dockerfile.bash5.1 +0 -0
  46. {multissh3-6.32 → multissh3-6.33}/tests/shells/Dockerfile.bash5.3 +0 -0
  47. {multissh3-6.32 → multissh3-6.33}/tests/shells/Dockerfile.centos7 +0 -0
  48. {multissh3-6.32 → multissh3-6.33}/tests/shells/Dockerfile.current +0 -0
  49. {multissh3-6.32 → multissh3-6.33}/tests/shells/Dockerfile.legacy +0 -0
  50. {multissh3-6.32 → multissh3-6.33}/tests/shells/Dockerfile.modern +0 -0
  51. {multissh3-6.32 → multissh3-6.33}/tests/shells/run_matrix.sh +0 -0
  52. {multissh3-6.32 → multissh3-6.33}/tests/test_cli_option_words.py +0 -0
  53. {multissh3-6.32 → multissh3-6.33}/tests/test_color_helpers.py +0 -0
  54. {multissh3-6.32 → multissh3-6.33}/tests/test_command_terminal.py +0 -0
  55. {multissh3-6.32 → multissh3-6.33}/tests/test_compact_hostnames.py +0 -0
  56. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_blesh.py +0 -0
  57. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_cli.py +0 -0
  58. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_context.py +0 -0
  59. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_diagnose.py +0 -0
  60. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_editor.py +0 -0
  61. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_eval.py +0 -0
  62. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_hosts.py +0 -0
  63. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_hoststate.py +0 -0
  64. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_index.py +0 -0
  65. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_lex.py +0 -0
  66. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_mssh_helpers.py +0 -0
  67. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_password_spellings.py +0 -0
  68. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_probe.py +0 -0
  69. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_store.py +0 -0
  70. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_suggest.py +0 -0
  71. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_terminal.py +0 -0
  72. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_untrusted.py +0 -0
  73. {multissh3-6.32 → multissh3-6.33}/tests/test_complete_zsh_options.py +0 -0
  74. {multissh3-6.32 → multissh3-6.33}/tests/test_config_validate.py +0 -0
  75. {multissh3-6.32 → multissh3-6.33}/tests/test_control_master_config.py +0 -0
  76. {multissh3-6.32 → multissh3-6.33}/tests/test_curses_branches.py +0 -0
  77. {multissh3-6.32 → multissh3-6.33}/tests/test_curses_tui.py +0 -0
  78. {multissh3-6.32 → multissh3-6.33}/tests/test_curses_wide_chars.py +0 -0
  79. {multissh3-6.32 → multissh3-6.33}/tests/test_expand_hostnames.py +0 -0
  80. {multissh3-6.32 → multissh3-6.33}/tests/test_file_trust.py +0 -0
  81. {multissh3-6.32 → multissh3-6.33}/tests/test_harness_smoke.py +0 -0
  82. {multissh3-6.32 → multissh3-6.33}/tests/test_helpers_extra.py +0 -0
  83. {multissh3-6.32 → multissh3-6.33}/tests/test_host.py +0 -0
  84. {multissh3-6.32 → multissh3-6.33}/tests/test_host_validity.py +0 -0
  85. {multissh3-6.32 → multissh3-6.33}/tests/test_hosts_file.py +0 -0
  86. {multissh3-6.32 → multissh3-6.33}/tests/test_ipmi_lockout_wait.py +0 -0
  87. {multissh3-6.32 → multissh3-6.33}/tests/test_key_path_home.py +0 -0
  88. {multissh3-6.32 → multissh3-6.33}/tests/test_login_drop_retry.py +0 -0
  89. {multissh3-6.32 → multissh3-6.33}/tests/test_narrow_terminal.py +0 -0
  90. {multissh3-6.32 → multissh3-6.33}/tests/test_option_no_effect.py +0 -0
  91. {multissh3-6.32 → multissh3-6.33}/tests/test_options_after_command.py +0 -0
  92. {multissh3-6.32 → multissh3-6.33}/tests/test_output_merge.py +0 -0
  93. {multissh3-6.32 → multissh3-6.33}/tests/test_output_robustness.py +0 -0
  94. {multissh3-6.32 → multissh3-6.33}/tests/test_packaging.py +0 -0
  95. {multissh3-6.32 → multissh3-6.33}/tests/test_run_command.py +0 -0
  96. {multissh3-6.32 → multissh3-6.33}/tests/test_run_command_exit.py +0 -0
  97. {multissh3-6.32 → multissh3-6.33}/tests/test_run_on_hosts.py +0 -0
  98. {multissh3-6.32 → multissh3-6.33}/tests/test_runtime_dir.py +0 -0
  99. {multissh3-6.32 → multissh3-6.33}/tests/test_smoke_optional.py +0 -0
  100. {multissh3-6.32 → multissh3-6.33}/tests/test_ssh_error_text.py +0 -0
  101. {multissh3-6.32 → multissh3-6.33}/tests/test_ssh_timeouts.py +0 -0
  102. {multissh3-6.32 → multissh3-6.33}/tests/test_start_run_on_hosts.py +0 -0
  103. {multissh3-6.32 → multissh3-6.33}/tests/test_stopped_commands.py +0 -0
  104. {multissh3-6.32 → multissh3-6.33}/tests/test_stream_reader.py +0 -0
  105. {multissh3-6.32 → multissh3-6.33}/tests/test_tui_ssh_notices.py +0 -0
  106. {multissh3-6.32 → multissh3-6.33}/tests/test_unavailable_hosts.py +0 -0
  107. {multissh3-6.32 → multissh3-6.33}/tests/vt_grid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 6.32
3
+ Version: 6.33
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -182,7 +182,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
182
182
  >
183
183
  > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link.
184
184
  >
185
- > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null`: no default hosts). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
185
+ > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null` is `all`). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
186
186
 
187
187
  ### Generating / Storing Config File
188
188
  To store / generate a config file with the current command line options, you can use
@@ -490,7 +490,7 @@ mssh --completion uninstall # remove it again
490
490
  mssh --completion bash # or print the script (bash, zsh or fish)
491
491
  ```
492
492
 
493
- Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
493
+ Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. A zsh completion dump (`.zcompdump`) written while the script was loaded still maps `mssh` to the removed `_mssh`; uninstall names such a file, for you to delete, and zsh makes a new one. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
494
494
 
495
495
  A new block is appended to your rc file; changing or removing it replaces the file in one step, so a full disk or quota leaves it as it was. A link stays a link (the file it points to is the one changed), and the file keeps its permissions and owner (not ACLs or other extended attributes). If the block's `# >>> mssh completion >>>` or `# <<< mssh completion <<<` line is missing or repeated, install and uninstall change nothing and print the line numbers to fix by hand. When mssh may not change the file (read-only, or linked into a read-only store as home-manager does, or not a regular file, such as a link to `/dev/null`), or cannot replace it safely to change a block (bind-mounted, hard-linked, or another user's), it leaves the file as it is and prints the lines to add, replace or delete by hand.
496
496
 
@@ -551,7 +551,7 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
551
551
  | Environment variable | Effect |
552
552
  | --- | --- |
553
553
  | `MSSH_COMPLETE_TIMEOUT` | Seconds the shell waits for an answer (default 0.3) |
554
- | `MSSH_COMPLETE_START_TIMEOUT` | bash only: seconds the shell waits for the Ctrl-R menu or the line editor to open (default 2); past that, Ctrl-R does what it did before and space is a space |
554
+ | `MSSH_COMPLETE_START_TIMEOUT` | seconds the shell waits for the Ctrl-R menu (and bash's line editor) to open (default 2); past that, Ctrl-R does what it did before and space is a space. Ctrl-C while it opens keeps the line. A menu that opens later leaves the terminal alone |
555
555
  | `MSSH_COMPLETE_GHOST_TIMEOUT` | ble.sh only: milliseconds a grey suggestion may take (default 300) |
556
556
  | `MSSH_COMPLETE_SHELL_HISTORY` | Lines of shell history sent along (default 1000) |
557
557
  | `MSSH_COMPLETE_EDITOR` | `1` turns on the plain-bash mssh line editor (`install bash editor` sets it) |
@@ -584,8 +584,8 @@ mssh -q -- web1 grep -r TODO /srv
584
584
  - You can specify the password for all hosts using this option. Although it is recommended to use SSH keys / store password in config file for authentication.
585
585
  - `-p` needs `sshpass`, which types the password when ssh asks for it and answers nothing else. When ssh asks again or asks about a host key, sshpass gives up without a word, and mssh says why next to the return code: 5 the password was not accepted, 6 ssh asked whether to trust a host key that is not in `known_hosts` (the host's own with `SSH_STRICT_HOST_KEY_CHECKING` true, else a ProxyJump host's), 7 `known_hosts` has a different key for the host's IP address. A command of yours that prints nothing can exit with the same codes, so the line says `unless the command itself exited with N`; with `SSH_STRICT_HOST_KEY_CHECKING` false and no ProxyJump (or ProxyCommand) in the ssh config, 6 and 7 can only be the command's and mssh adds nothing.
586
586
  - Warning: by default mssh does not check host keys, so the password goes to whichever machine answers at the host's address. See [Host Key Checking](#host-key-checking).
587
- - Note: a password given on the command line is plain text. It can be seen by other local users (e.g. in `ps` and your shell history), is echoed in the `> mssh ...` line mssh prints at start, and is recorded in the history file (`-hf`, default `~/.mssh_history`) so the command can be replayed (an IPMI password, `-ip`, is not). mssh creates a new history file readable by its owner only (mode 600); an existing history file keeps its permissions. Use `-I, --no_history` to keep a command out of the history, or set `DEFAULT_PASSWORD` in a config file (also plain text, so keep that file private) to avoid typing it on the command line.
588
- - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. Like a password typed on the command line, they reach `sshpass` and `ipmitool` as arguments, where other local users can briefly see them in `ps`.
587
+ - mssh never shows or stores the password: the `> mssh ...` line and the history file (`-hf`, default `~/.mssh_history`) list it as `-p -`, so running either again asks for it. Typed on mssh's own command line, though, it can be seen by other local users (in `ps`) and lands in your shell history. To keep it off the command line, `-p -` asks for it on the terminal (nothing is echoed; without a terminal, or without an answer, mssh stops before running anything), or set `MSSH_PASSWORD` in the environment, or `DEFAULT_PASSWORD` in a config file (plain text, so keep that file private). `-p` on the command line wins over `MSSH_PASSWORD`, which wins over `DEFAULT_PASSWORD`; neither of those two is listed in the `> mssh ...` line or the history, as a replay gets them again, and `--store_config_file` does not write `MSSH_PASSWORD` into the config.
588
+ - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. They reach `sshpass` and `ipmitool`, as a password typed on the command line does, through their environment (`sshpass -e`, `ipmitool -E` with `IPMI_PASSWORD` and `IPMITOOL_PASSWORD`), which other local users cannot read, not as arguments `ps` shows them. `redfishtool` has no such option: it gets its password as an argument, visible in `ps` while it runs.
589
589
 
590
590
  ### `-k, --key, --identity [KEY]` | _`DEFAULT_IDENTITY_FILE`_
591
591
  - You can specify the identity file or folder to use for public key authentication. If a folder is specified, it will search for a key file inside the folder.
@@ -711,15 +711,15 @@ mssh -f ./data/ allServers /tmp/data/
711
711
  - The password ipmitool (or redfishtool) logs in to the hosts' BMCs with, in `--ipmi` mode.
712
712
  - Defaults to `admin`. It does not fall back to `-p` / `DEFAULT_PASSWORD`: see the note under `-M, --ipmi`.
713
713
  - With `-im ssh_ipmitool` it is the ssh password instead (sent with sshpass).
714
- - Unlike `-p`, it is neither echoed in the `> mssh ...` line nor recorded in the history file, and when it is given, neither are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
714
+ - It is left out of the `> mssh ...` line and the history file (not even as `-p -` is for `-p`), and when it is given, so are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
715
715
 
716
716
  ### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
717
717
  - Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
718
- - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -P <password> -I lanplus power status`. Use `=`, as the value starts with `-`.
718
+ - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. Use `=`, as the value starts with `-`.
719
719
 
720
720
  ### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
721
721
  - How `--ipmi` reaches a host:
722
- - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -P <password> [IPMI_ARGS] <command>` on this machine.
722
+ - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -E [IPMI_ARGS] <command>` on this machine; `-E` reads the password from the environment mssh gives it.
723
723
  - `redfish`: runs `redfishtool -S Always -a -r <address> -u <user> -p <password> [IPMI_ARGS] <command>` on this machine (`Systems` without a command).
724
724
  - `ssh_ipmitool`: logs in to the host over ssh with the IPMI username and password (`-iu` / `-ip`, so `admin` / `admin` unless set) and runs `ipmitool <command>` there.
725
725
  - Any other value fails with `IPMI method ... not supported!`.
@@ -876,7 +876,7 @@ mssh -f ./data/ allServers /tmp/data/
876
876
 
877
877
  ### `-hf, --history_file HISTORY_FILE` | _`DEFAULT_HISTORY_FILE`_
878
878
  - The file to store command history.
879
- - By default, mssh store command history in `~/.mssh_history`.
879
+ - By default, mssh store command history in `~/.mssh_history`, readable by its owner only. Passwords are not stored (see `-p`).
880
880
  - The history file is a TSV ( tab separated values ) file with each line containing: timestamp, mssh_path, options, hosts, commands.
881
881
  - A line records the command so that replaying it (the Ctrl-R picker, `mssh-complete`) runs it the same way: the hosts as typed and every option that changes what runs, where, or what is shown; a flag when it is on, an option with a value when the value is not the default (the config files' `DEFAULT_*`). `-T`, `-uk` (or `-k` with a folder), `--script` and `-s PATH` are recorded as the timeout, key file and options they chose, `--config_file` and `--host_file` with their absolute paths. Not recorded: `-ip` (and with it the other IPMI options, see `-ip`), and `--generate_config_file`, `--store_config_file` and `--add_control_master_config`, which do something besides the run. The `> mssh ...` line mssh prints at start lists the same options.
882
882
 
@@ -970,7 +970,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
970
970
 
971
971
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
972
972
 
973
- Below is a sample help message output from multiSSH3 6.32 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
973
+ Below is a sample help message output from multiSSH3 6.33 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
974
974
 
975
975
  ```bash
976
976
  $ mssh -h
@@ -996,7 +996,7 @@ options:
996
996
  -u, --username USERNAME
997
997
  The general username to use to connect to the hosts via ssh. Will get overwrote by individual username@host if specified. (default: None)
998
998
  -p, --password PASSWORD
999
- The password to use to connect to the hosts via ssh, (default: none)
999
+ The password to use to connect to the hosts via ssh, "-p -" to be asked for it, or set MSSH_PASSWORD. (default: none)
1000
1000
  -k, --identity_file, --key, --identity [IDENTITY_FILE]
1001
1001
  The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
1002
1002
  -uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
@@ -1370,6 +1370,8 @@ __DEBUG_MODE = False
1370
1370
 
1371
1371
  > `_DEFAULT_NO_START` is somewhat analogous to "demo mode" where mssh will return Host objects without actually allocating and starting the execution threads. Although they still can be started using `start_run_on_hosts()` but you should really be looking for `return_unfinished` instead for asynchronicity.
1372
1372
 
1373
+ > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1374
+
1373
1375
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1374
1376
 
1375
1377
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.
@@ -156,7 +156,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
156
156
  >
157
157
  > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link.
158
158
  >
159
- > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null`: no default hosts). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
159
+ > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null` is `all`). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
160
160
 
161
161
  ### Generating / Storing Config File
162
162
  To store / generate a config file with the current command line options, you can use
@@ -464,7 +464,7 @@ mssh --completion uninstall # remove it again
464
464
  mssh --completion bash # or print the script (bash, zsh or fish)
465
465
  ```
466
466
 
467
- Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
467
+ Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. A zsh completion dump (`.zcompdump`) written while the script was loaded still maps `mssh` to the removed `_mssh`; uninstall names such a file, for you to delete, and zsh makes a new one. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
468
468
 
469
469
  A new block is appended to your rc file; changing or removing it replaces the file in one step, so a full disk or quota leaves it as it was. A link stays a link (the file it points to is the one changed), and the file keeps its permissions and owner (not ACLs or other extended attributes). If the block's `# >>> mssh completion >>>` or `# <<< mssh completion <<<` line is missing or repeated, install and uninstall change nothing and print the line numbers to fix by hand. When mssh may not change the file (read-only, or linked into a read-only store as home-manager does, or not a regular file, such as a link to `/dev/null`), or cannot replace it safely to change a block (bind-mounted, hard-linked, or another user's), it leaves the file as it is and prints the lines to add, replace or delete by hand.
470
470
 
@@ -525,7 +525,7 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
525
525
  | Environment variable | Effect |
526
526
  | --- | --- |
527
527
  | `MSSH_COMPLETE_TIMEOUT` | Seconds the shell waits for an answer (default 0.3) |
528
- | `MSSH_COMPLETE_START_TIMEOUT` | bash only: seconds the shell waits for the Ctrl-R menu or the line editor to open (default 2); past that, Ctrl-R does what it did before and space is a space |
528
+ | `MSSH_COMPLETE_START_TIMEOUT` | seconds the shell waits for the Ctrl-R menu (and bash's line editor) to open (default 2); past that, Ctrl-R does what it did before and space is a space. Ctrl-C while it opens keeps the line. A menu that opens later leaves the terminal alone |
529
529
  | `MSSH_COMPLETE_GHOST_TIMEOUT` | ble.sh only: milliseconds a grey suggestion may take (default 300) |
530
530
  | `MSSH_COMPLETE_SHELL_HISTORY` | Lines of shell history sent along (default 1000) |
531
531
  | `MSSH_COMPLETE_EDITOR` | `1` turns on the plain-bash mssh line editor (`install bash editor` sets it) |
@@ -558,8 +558,8 @@ mssh -q -- web1 grep -r TODO /srv
558
558
  - You can specify the password for all hosts using this option. Although it is recommended to use SSH keys / store password in config file for authentication.
559
559
  - `-p` needs `sshpass`, which types the password when ssh asks for it and answers nothing else. When ssh asks again or asks about a host key, sshpass gives up without a word, and mssh says why next to the return code: 5 the password was not accepted, 6 ssh asked whether to trust a host key that is not in `known_hosts` (the host's own with `SSH_STRICT_HOST_KEY_CHECKING` true, else a ProxyJump host's), 7 `known_hosts` has a different key for the host's IP address. A command of yours that prints nothing can exit with the same codes, so the line says `unless the command itself exited with N`; with `SSH_STRICT_HOST_KEY_CHECKING` false and no ProxyJump (or ProxyCommand) in the ssh config, 6 and 7 can only be the command's and mssh adds nothing.
560
560
  - Warning: by default mssh does not check host keys, so the password goes to whichever machine answers at the host's address. See [Host Key Checking](#host-key-checking).
561
- - Note: a password given on the command line is plain text. It can be seen by other local users (e.g. in `ps` and your shell history), is echoed in the `> mssh ...` line mssh prints at start, and is recorded in the history file (`-hf`, default `~/.mssh_history`) so the command can be replayed (an IPMI password, `-ip`, is not). mssh creates a new history file readable by its owner only (mode 600); an existing history file keeps its permissions. Use `-I, --no_history` to keep a command out of the history, or set `DEFAULT_PASSWORD` in a config file (also plain text, so keep that file private) to avoid typing it on the command line.
562
- - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. Like a password typed on the command line, they reach `sshpass` and `ipmitool` as arguments, where other local users can briefly see them in `ps`.
561
+ - mssh never shows or stores the password: the `> mssh ...` line and the history file (`-hf`, default `~/.mssh_history`) list it as `-p -`, so running either again asks for it. Typed on mssh's own command line, though, it can be seen by other local users (in `ps`) and lands in your shell history. To keep it off the command line, `-p -` asks for it on the terminal (nothing is echoed; without a terminal, or without an answer, mssh stops before running anything), or set `MSSH_PASSWORD` in the environment, or `DEFAULT_PASSWORD` in a config file (plain text, so keep that file private). `-p` on the command line wins over `MSSH_PASSWORD`, which wins over `DEFAULT_PASSWORD`; neither of those two is listed in the `> mssh ...` line or the history, as a replay gets them again, and `--store_config_file` does not write `MSSH_PASSWORD` into the config.
562
+ - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. They reach `sshpass` and `ipmitool`, as a password typed on the command line does, through their environment (`sshpass -e`, `ipmitool -E` with `IPMI_PASSWORD` and `IPMITOOL_PASSWORD`), which other local users cannot read, not as arguments `ps` shows them. `redfishtool` has no such option: it gets its password as an argument, visible in `ps` while it runs.
563
563
 
564
564
  ### `-k, --key, --identity [KEY]` | _`DEFAULT_IDENTITY_FILE`_
565
565
  - You can specify the identity file or folder to use for public key authentication. If a folder is specified, it will search for a key file inside the folder.
@@ -685,15 +685,15 @@ mssh -f ./data/ allServers /tmp/data/
685
685
  - The password ipmitool (or redfishtool) logs in to the hosts' BMCs with, in `--ipmi` mode.
686
686
  - Defaults to `admin`. It does not fall back to `-p` / `DEFAULT_PASSWORD`: see the note under `-M, --ipmi`.
687
687
  - With `-im ssh_ipmitool` it is the ssh password instead (sent with sshpass).
688
- - Unlike `-p`, it is neither echoed in the `> mssh ...` line nor recorded in the history file, and when it is given, neither are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
688
+ - It is left out of the `> mssh ...` line and the history file (not even as `-p -` is for `-p`), and when it is given, so are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
689
689
 
690
690
  ### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
691
691
  - Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
692
- - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -P <password> -I lanplus power status`. Use `=`, as the value starts with `-`.
692
+ - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. Use `=`, as the value starts with `-`.
693
693
 
694
694
  ### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
695
695
  - How `--ipmi` reaches a host:
696
- - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -P <password> [IPMI_ARGS] <command>` on this machine.
696
+ - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -E [IPMI_ARGS] <command>` on this machine; `-E` reads the password from the environment mssh gives it.
697
697
  - `redfish`: runs `redfishtool -S Always -a -r <address> -u <user> -p <password> [IPMI_ARGS] <command>` on this machine (`Systems` without a command).
698
698
  - `ssh_ipmitool`: logs in to the host over ssh with the IPMI username and password (`-iu` / `-ip`, so `admin` / `admin` unless set) and runs `ipmitool <command>` there.
699
699
  - Any other value fails with `IPMI method ... not supported!`.
@@ -850,7 +850,7 @@ mssh -f ./data/ allServers /tmp/data/
850
850
 
851
851
  ### `-hf, --history_file HISTORY_FILE` | _`DEFAULT_HISTORY_FILE`_
852
852
  - The file to store command history.
853
- - By default, mssh store command history in `~/.mssh_history`.
853
+ - By default, mssh store command history in `~/.mssh_history`, readable by its owner only. Passwords are not stored (see `-p`).
854
854
  - The history file is a TSV ( tab separated values ) file with each line containing: timestamp, mssh_path, options, hosts, commands.
855
855
  - A line records the command so that replaying it (the Ctrl-R picker, `mssh-complete`) runs it the same way: the hosts as typed and every option that changes what runs, where, or what is shown; a flag when it is on, an option with a value when the value is not the default (the config files' `DEFAULT_*`). `-T`, `-uk` (or `-k` with a folder), `--script` and `-s PATH` are recorded as the timeout, key file and options they chose, `--config_file` and `--host_file` with their absolute paths. Not recorded: `-ip` (and with it the other IPMI options, see `-ip`), and `--generate_config_file`, `--store_config_file` and `--add_control_master_config`, which do something besides the run. The `> mssh ...` line mssh prints at start lists the same options.
856
856
 
@@ -944,7 +944,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
944
944
 
945
945
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
946
946
 
947
- Below is a sample help message output from multiSSH3 6.32 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
947
+ Below is a sample help message output from multiSSH3 6.33 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
948
948
 
949
949
  ```bash
950
950
  $ mssh -h
@@ -970,7 +970,7 @@ options:
970
970
  -u, --username USERNAME
971
971
  The general username to use to connect to the hosts via ssh. Will get overwrote by individual username@host if specified. (default: None)
972
972
  -p, --password PASSWORD
973
- The password to use to connect to the hosts via ssh, (default: none)
973
+ The password to use to connect to the hosts via ssh, "-p -" to be asked for it, or set MSSH_PASSWORD. (default: none)
974
974
  -k, --identity_file, --key, --identity [IDENTITY_FILE]
975
975
  The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
976
976
  -uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
@@ -1344,6 +1344,8 @@ __DEBUG_MODE = False
1344
1344
 
1345
1345
  > `_DEFAULT_NO_START` is somewhat analogous to "demo mode" where mssh will return Host objects without actually allocating and starting the execution threads. Although they still can be started using `start_run_on_hosts()` but you should really be looking for `return_unfinished` instead for asynchronicity.
1346
1346
 
1347
+ > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1348
+
1347
1349
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1348
1350
 
1349
1351
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.
@@ -16,7 +16,7 @@ import sys
16
16
  import time
17
17
  from bisect import bisect_left
18
18
 
19
- version = '0.5'
19
+ version = '0.6'
20
20
  __version__ = version
21
21
  PROTOCOL = 'MSSHC1'
22
22
  MSSH_NAMES = ('mssh', 'mssh3', 'multissh', 'multissh3', 'multiSSH3')
@@ -5769,6 +5769,42 @@ class LineEditor(object):
5769
5769
  self.draw(term, prompt, style)
5770
5770
 
5771
5771
  #%% ------------ Shell glue ----------------
5772
+ # zsh and fish run the picker through this POSIX sh: sh -c START_SCRIPT sh SECONDS PICKER-COMMAND...
5773
+ # The picker creates MSSH_COMPLETE_CLAIM just before it takes the terminal (_claim_terminal). It is
5774
+ # given SECONDS to do so: then, or on Ctrl-C while it starts, the script creates it first and stops the
5775
+ # picker, which has not touched the terminal (a hung file system can keep it from starting, deaf to
5776
+ # Ctrl-C). mkdir succeeds for one of the two only. Exit status: the picker's, 124 when it was too slow,
5777
+ # 130 on Ctrl-C. The picker runs in the background of this sh, in the shell's process group: it can
5778
+ # read the terminal. Its answer goes through a file: a process it leaves behind when stopped cannot
5779
+ # keep the shell's command substitution open. bash waits for its "ready" line instead (_mssh_complete_start).
5780
+ START_SCRIPT = r"""limit=$1
5781
+ shift
5782
+ dir=$(mktemp -d 2>/dev/null) || exec "$@"
5783
+ exec 3<&0
5784
+ MSSH_COMPLETE_CLAIM=$dir/claim "$@" <&3 3<&- >"$dir/answer" &
5785
+ pid=$!
5786
+ exec 3<&-
5787
+ stop() {
5788
+ mkdir "$dir/claim" 2>/dev/null && : > "$dir/$1" && kill -9 "$pid"
5789
+ }
5790
+ # the sleeper: nothing of it may keep the shell's command substitution open, and Ctrl-C is the
5791
+ # trap's (bash does not make a background subshell ignore it, as dash and busybox do)
5792
+ { trap '' INT; sleep "$limit"; stop late; } </dev/null >/dev/null 2>&1 &
5793
+ sleeper=$!
5794
+ trap 'stop stopped' INT
5795
+ while :; do
5796
+ wait "$pid"
5797
+ status=$?
5798
+ kill -0 "$pid" 2>/dev/null || break
5799
+ done
5800
+ kill "$sleeper" 2>/dev/null
5801
+ [ -e "$dir/stopped" ] && status=130
5802
+ [ -e "$dir/late" ] && status=124
5803
+ cat "$dir/answer" 2>/dev/null
5804
+ rm -rf "$dir"
5805
+ exit "$status"
5806
+ """
5807
+
5772
5808
  GLUE_MARK = 'MSSH_COMPLETE_GLUE'
5773
5809
 
5774
5810
  BASH_GLUE = r'''# MSSH_COMPLETE_GLUE @VERSION@ bash: autocomplete for mssh (multiSSH3), protocol MSSHC1
@@ -6489,6 +6525,18 @@ _mssh_complete_engine() {
6489
6525
  @ENGINE@ "$@"
6490
6526
  }
6491
6527
 
6528
+ typeset -g _mssh_complete_start_script=@START@
6529
+
6530
+ _mssh_complete_start_pick() {
6531
+ # the picker, reading the request on stdin: MSSH_COMPLETE_START_TIMEOUT seconds (2) at most to take
6532
+ # the terminal, then as long as the operator takes. 124 when too slow, 130 on Ctrl-C while it
6533
+ # starts: see START_SCRIPT in mssh_complete.py
6534
+ emulate -L zsh
6535
+ local limit=${MSSH_COMPLETE_START_TIMEOUT:-2}
6536
+ [[ $limit == (<->|<->.<->|.<->) ]] || limit=2
6537
+ command sh -c "$_mssh_complete_start_script" sh "$limit" @ENGINE@ pick
6538
+ }
6539
+
6492
6540
  _mssh_complete_keep_terminal() {
6493
6541
  # after each `exec {fd}< <(...)`: before zsh 5.8 the process substitution takes the terminal from the
6494
6542
  # shell (Ctrl-C then goes to it, and the shell's next reads can fail with "error on TTY read"); a
@@ -6517,7 +6565,10 @@ _mssh_complete_ctrl_r() {
6517
6565
  emulate -L zsh
6518
6566
  local resp line text= cursor= st
6519
6567
  if _mssh_complete_is_mssh "$BUFFER"; then
6520
- resp=$(_mssh_complete_request 'pick=1 nul=0' "$LBUFFER" "$RBUFFER" | _mssh_complete_engine pick 2>/dev/null)
6568
+ # Ctrl-C while the picker starts stops it (130), and keeps the line: without a trap of its own
6569
+ # (local: emulate -L), zsh would end the widget and drop the line
6570
+ trap : INT
6571
+ resp=$(_mssh_complete_request 'pick=1 nul=0' "$LBUFFER" "$RBUFFER" | _mssh_complete_start_pick 2>/dev/null)
6521
6572
  st=$?
6522
6573
  if (( st == 0 )) && [[ $resp == MSSHC1$'\n'*$'\n'E ]]; then
6523
6574
  for line in "${(@f)resp}"; do
@@ -6538,6 +6589,8 @@ _mssh_complete_ctrl_r() {
6538
6589
  [[ -n ${_mssh_complete_warn_reason-} ]] && zle -M -- "$_mssh_complete_warn_reason"
6539
6590
  return 0
6540
6591
  fi
6592
+ # Ctrl-C while it started: the line stays as it is. Too slow (124): what Ctrl-R did before
6593
+ (( st == 130 )) && return 0
6541
6594
  fi
6542
6595
  [[ -n $_mssh_complete_previous ]] && zle $_mssh_complete_previous
6543
6596
  return 0
@@ -7064,6 +7117,7 @@ switch "$FISH_VERSION"
7064
7117
  if not set -q __mssh_complete_keys
7065
7118
  set -g __mssh_complete_keys 1
7066
7119
  ''' + _fish_nested(_FISH_QUERY, 3) + r'''
7120
+ set -g __mssh_complete_start_script @START@
7067
7121
  function __mssh_complete_is_mssh
7068
7122
  string match -qr '^\s*(\S*/)?(mssh|mssh3|multissh|multissh3|multiSSH3)\s' -- "$argv[1]"
7069
7123
  end
@@ -7076,10 +7130,14 @@ switch "$FISH_VERSION"
7076
7130
  set -l keep 1000
7077
7131
  set -q MSSH_COMPLETE_SHELL_HISTORY; and set keep $MSSH_COMPLETE_SHELL_HISTORY
7078
7132
  # the history builtin: the operator's history function or alias (one showing times) would change the lines
7133
+ # MSSH_COMPLETE_START_TIMEOUT seconds (2) at most for the picker to take the terminal, then as
7134
+ # long as the operator takes: 124 when too slow, 130 on Ctrl-C while it starts (START_SCRIPT)
7135
+ set -l limit 2
7136
+ string match -qr '^(\d+|\d+\.\d*|\.\d+)$' -- "$MSSH_COMPLETE_START_TIMEOUT"; and set limit $MSSH_COMPLETE_START_TIMEOUT
7079
7137
  set -l lines (begin
7080
7138
  command printf '%s\0' MSSHC1 fish $FISH_VERSION "pick=1 nul=0 term=$TERM" "$before" "$after" ''
7081
7139
  builtin history search -n $keep 2>/dev/null
7082
- end | @ENGINE@ pick 2>/dev/null)
7140
+ end | command sh -c $__mssh_complete_start_script sh $limit @ENGINE@ pick 2>/dev/null)
7083
7141
  set -l st $status
7084
7142
  if test $st -eq 0; and test (count $lines) -ge 3; and test "$lines[1]" = MSSHC1; and test "$lines[-1]" = E
7085
7143
  for line in $lines[2..-2]
@@ -7090,7 +7148,8 @@ switch "$FISH_VERSION"
7090
7148
  end
7091
7149
  end
7092
7150
  commandline -f repaint
7093
- test $st -le 1; and return
7151
+ # Ctrl-C while it started: the line stays as it is. Too slow (124): what Ctrl-R did before
7152
+ test $st -le 1; or test $st -eq 130; and return
7094
7153
  end
7095
7154
  set -l previous __mssh_complete_previous_$mode
7096
7155
  for command in $$previous
@@ -7213,6 +7272,7 @@ def render_glue(shell, command=None):
7213
7272
  command = engine_command() if command is None else command
7214
7273
  quote = _fish_quote if shell.startswith('fish') else shlex.quote
7215
7274
  text = _GLUES[shell].replace('@VERSION@', version).replace('@ENGINE@', ' '.join(quote(word) for word in command))
7275
+ text = text.replace('@START@', quote(START_SCRIPT))
7216
7276
  return _zsh_guard(text) if shell == 'zsh' else text
7217
7277
 
7218
7278
  #%% ------------ Printing to a reader that may go ----------------
@@ -7826,8 +7886,38 @@ def uninstall(shell, home=None, environ=None):
7826
7886
  return 1, messages + list(error.args)
7827
7887
  if len(messages) == (1 if paths.get('note') else 0):
7828
7888
  messages.append('nothing to remove')
7889
+ if shell == 'zsh':
7890
+ for dump in _zsh_dumps_naming_mssh(home, environ):
7891
+ messages.append("zsh's completion dump {0} still maps mssh to _mssh, which is gone: "
7892
+ "remove it (rm {0}) and zsh makes a new one".format(dump))
7829
7893
  return 0, messages
7830
7894
 
7895
+ def _zsh_dumps_naming_mssh(home, environ):
7896
+ '''
7897
+ The zsh completion dumps (compinit's .zcompdump, oh-my-zsh's .zcompdump-HOST-VERSION) that map mssh
7898
+ to _mssh. A dump written while the glue was loaded keeps that after it is removed, and zsh then
7899
+ answers TAB with "command not found: _mssh". They are zsh's: they are named, not changed.
7900
+ '''
7901
+ candidates = [environ.get('ZSH_COMPDUMP')]
7902
+ for directory in (environ.get('ZDOTDIR'), home):
7903
+ try:
7904
+ names = sorted(os.listdir(directory)) if directory else []
7905
+ except OSError:
7906
+ names = []
7907
+ candidates.extend(os.path.join(directory, name) for name in names
7908
+ if name.startswith('.zcompdump') and not name.endswith('.zwc'))
7909
+ found = []
7910
+ for path in candidates:
7911
+ if not path or path in found:
7912
+ continue
7913
+ try:
7914
+ with open(path, encoding='utf-8', errors='replace') as dump:
7915
+ if "'_mssh'" in dump.read(4 << 20):
7916
+ found.append(path)
7917
+ except OSError:
7918
+ pass
7919
+ return found
7920
+
7831
7921
  def prepare_index():
7832
7922
  '''
7833
7923
  Build the index now, for `mssh --completion install`: the first TAB in a new shell then answers
@@ -8235,6 +8325,25 @@ def _ready():
8235
8325
  except OSError:
8236
8326
  return False
8237
8327
 
8328
+ def _claim_terminal():
8329
+ '''
8330
+ Create the directory MSSH_COMPLETE_CLAIM names (zsh and fish set it), just before taking the terminal.
8331
+
8332
+ The shell waits only so long (MSSH_COMPLETE_START_TIMEOUT) for the picker to take it, and creates
8333
+ that directory itself when it stops waiting (also on Ctrl-C): mkdir succeeds for one of the two
8334
+ only, so a picker slow to start never draws after the shell went on. True when the terminal is
8335
+ ours (also without MSSH_COMPLETE_CLAIM, as bash runs it).
8336
+ '''
8337
+ path = os.environ.get('MSSH_COMPLETE_CLAIM')
8338
+ if not path:
8339
+ return True
8340
+ try:
8341
+ os.mkdir(path, 0o700)
8342
+ return True
8343
+ except OSError as e:
8344
+ _debug('pick: the shell went on ({!r}): leaving the terminal alone'.format(e))
8345
+ return False
8346
+
8238
8347
  def _cmd_pick():
8239
8348
  '''
8240
8349
  mssh-complete pick: the Ctrl-R picker. Reads a request on stdin, draws on /dev/tty, prints the new
@@ -8261,7 +8370,7 @@ def _cmd_pick():
8261
8370
  line = before = None
8262
8371
  if prompt is not None:
8263
8372
  line, before = prompt + request.before + request.after, prompt + request.before
8264
- if request.flags.get('ready') == '1' and not _ready():
8373
+ if not _claim_terminal() or (request.flags.get('ready') == '1' and not _ready()):
8265
8374
  return 2
8266
8375
  with Terminal() as term:
8267
8376
  outcome = picker.run(term, line, before)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 6.32
3
+ Version: 6.33
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -182,7 +182,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
182
182
  >
183
183
  > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link.
184
184
  >
185
- > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null`: no default hosts). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
185
+ > Each value is checked against the type of the setting's default. Values whose meaning is clear are converted (`"true"` / `"false"`, `"yes"` / `"no"` for true / false settings, numbers written as strings, a list of host names for `DEFAULT_HOSTS`); anything else, such as `"DEFAULT_TIMEOUT": "fifty"` or an encoding Python does not know, is ignored with a warning naming the file and the key, and the setting keeps its value from the rest of the chain (no warning when a more specific file sets that key anyway). `null` for a string setting means its default (`DEFAULT_HOSTS: null` is `all`). A file that is not a JSON object is skipped the same way. `--config_file` applies a file exactly as the chain does, `_sshPath`, `DEFAULT_ENV_FILE` and `ERROR_MESSAGES_TO_IGNORE` included.
186
186
 
187
187
  ### Generating / Storing Config File
188
188
  To store / generate a config file with the current command line options, you can use
@@ -490,7 +490,7 @@ mssh --completion uninstall # remove it again
490
490
  mssh --completion bash # or print the script (bash, zsh or fish)
491
491
  ```
492
492
 
493
- Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
493
+ Install writes the script to `~/.local/share/multiSSH3/` and adds a marked block that loads it to `~/.bashrc` (bash) or `$ZDOTDIR/.zshrc` (zsh, which install asks, so a `ZDOTDIR` set in `~/.zshenv` counts; `~/.zshrc` when `ZDOTDIR` is not set); for fish it writes files under `~/.config/fish/` (`$XDG_CONFIG_HOME/fish/` when that is set). It prints every file it changes, and leaves alone a script file in its way that mssh did not write. Uninstall removes exactly what install added. A zsh completion dump (`.zcompdump`) written while the script was loaded still maps `mssh` to the removed `_mssh`; uninstall names such a file, for you to delete, and zsh makes a new one. bash login shells (ssh logins, macOS terminals) read `~/.bash_profile`, `~/.bash_login` or `~/.profile` instead of `~/.bashrc`; those usually load `~/.bashrc`, and install prints the line to add when yours does not.
494
494
 
495
495
  A new block is appended to your rc file; changing or removing it replaces the file in one step, so a full disk or quota leaves it as it was. A link stays a link (the file it points to is the one changed), and the file keeps its permissions and owner (not ACLs or other extended attributes). If the block's `# >>> mssh completion >>>` or `# <<< mssh completion <<<` line is missing or repeated, install and uninstall change nothing and print the line numbers to fix by hand. When mssh may not change the file (read-only, or linked into a read-only store as home-manager does, or not a regular file, such as a link to `/dev/null`), or cannot replace it safely to change a block (bind-mounted, hard-linked, or another user's), it leaves the file as it is and prints the lines to add, replace or delete by hand.
496
496
 
@@ -551,7 +551,7 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
551
551
  | Environment variable | Effect |
552
552
  | --- | --- |
553
553
  | `MSSH_COMPLETE_TIMEOUT` | Seconds the shell waits for an answer (default 0.3) |
554
- | `MSSH_COMPLETE_START_TIMEOUT` | bash only: seconds the shell waits for the Ctrl-R menu or the line editor to open (default 2); past that, Ctrl-R does what it did before and space is a space |
554
+ | `MSSH_COMPLETE_START_TIMEOUT` | seconds the shell waits for the Ctrl-R menu (and bash's line editor) to open (default 2); past that, Ctrl-R does what it did before and space is a space. Ctrl-C while it opens keeps the line. A menu that opens later leaves the terminal alone |
555
555
  | `MSSH_COMPLETE_GHOST_TIMEOUT` | ble.sh only: milliseconds a grey suggestion may take (default 300) |
556
556
  | `MSSH_COMPLETE_SHELL_HISTORY` | Lines of shell history sent along (default 1000) |
557
557
  | `MSSH_COMPLETE_EDITOR` | `1` turns on the plain-bash mssh line editor (`install bash editor` sets it) |
@@ -584,8 +584,8 @@ mssh -q -- web1 grep -r TODO /srv
584
584
  - You can specify the password for all hosts using this option. Although it is recommended to use SSH keys / store password in config file for authentication.
585
585
  - `-p` needs `sshpass`, which types the password when ssh asks for it and answers nothing else. When ssh asks again or asks about a host key, sshpass gives up without a word, and mssh says why next to the return code: 5 the password was not accepted, 6 ssh asked whether to trust a host key that is not in `known_hosts` (the host's own with `SSH_STRICT_HOST_KEY_CHECKING` true, else a ProxyJump host's), 7 `known_hosts` has a different key for the host's IP address. A command of yours that prints nothing can exit with the same codes, so the line says `unless the command itself exited with N`; with `SSH_STRICT_HOST_KEY_CHECKING` false and no ProxyJump (or ProxyCommand) in the ssh config, 6 and 7 can only be the command's and mssh adds nothing.
586
586
  - Warning: by default mssh does not check host keys, so the password goes to whichever machine answers at the host's address. See [Host Key Checking](#host-key-checking).
587
- - Note: a password given on the command line is plain text. It can be seen by other local users (e.g. in `ps` and your shell history), is echoed in the `> mssh ...` line mssh prints at start, and is recorded in the history file (`-hf`, default `~/.mssh_history`) so the command can be replayed (an IPMI password, `-ip`, is not). mssh creates a new history file readable by its owner only (mode 600); an existing history file keeps its permissions. Use `-I, --no_history` to keep a command out of the history, or set `DEFAULT_PASSWORD` in a config file (also plain text, so keep that file private) to avoid typing it on the command line.
588
- - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. Like a password typed on the command line, they reach `sshpass` and `ipmitool` as arguments, where other local users can briefly see them in `ps`.
587
+ - mssh never shows or stores the password: the `> mssh ...` line and the history file (`-hf`, default `~/.mssh_history`) list it as `-p -`, so running either again asks for it. Typed on mssh's own command line, though, it can be seen by other local users (in `ps`) and lands in your shell history. To keep it off the command line, `-p -` asks for it on the terminal (nothing is echoed; without a terminal, or without an answer, mssh stops before running anything), or set `MSSH_PASSWORD` in the environment, or `DEFAULT_PASSWORD` in a config file (plain text, so keep that file private). `-p` on the command line wins over `MSSH_PASSWORD`, which wins over `DEFAULT_PASSWORD`; neither of those two is listed in the `> mssh ...` line or the history, as a replay gets them again, and `--store_config_file` does not write `MSSH_PASSWORD` into the config.
588
+ - mssh does not show the passwords from your config file: `-h` and the autocomplete only say whether one is set, the `> mssh ...` line and the history leave them out, and the commands the TUI, `--debug` and `--copy_id` show have `***` in their place. Only `--generate_config_file` (and `--store_config_file` when it cannot write the file) print the config itself, passwords included. They reach `sshpass` and `ipmitool`, as a password typed on the command line does, through their environment (`sshpass -e`, `ipmitool -E` with `IPMI_PASSWORD` and `IPMITOOL_PASSWORD`), which other local users cannot read, not as arguments `ps` shows them. `redfishtool` has no such option: it gets its password as an argument, visible in `ps` while it runs.
589
589
 
590
590
  ### `-k, --key, --identity [KEY]` | _`DEFAULT_IDENTITY_FILE`_
591
591
  - You can specify the identity file or folder to use for public key authentication. If a folder is specified, it will search for a key file inside the folder.
@@ -711,15 +711,15 @@ mssh -f ./data/ allServers /tmp/data/
711
711
  - The password ipmitool (or redfishtool) logs in to the hosts' BMCs with, in `--ipmi` mode.
712
712
  - Defaults to `admin`. It does not fall back to `-p` / `DEFAULT_PASSWORD`: see the note under `-M, --ipmi`.
713
713
  - With `-im ssh_ipmitool` it is the ssh password instead (sent with sshpass).
714
- - Unlike `-p`, it is neither echoed in the `> mssh ...` line nor recorded in the history file, and when it is given, neither are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
714
+ - It is left out of the `> mssh ...` line and the history file (not even as `-p -` is for `-p`), and when it is given, so are `-iu`, `-ia`, `-im`, `-mpre` and `-irlt`: with the configured password they would make a login you did not give, tried on every BMC (each failed login counts towards the BMC locking the account). A replayed history line tries the configured logins, as `--ipmi` alone does; give the IPMI options again to use yours.
715
715
 
716
716
  ### `-ia, --ipmi_args IPMI_ARGS` | _`DEFAULT_IPMI_ARGS`_
717
717
  - Extra arguments for ipmitool (or redfishtool) in `--ipmi` mode, in one string, placed as they are before the command. `-ea` arguments only go to ssh / scp / rsync.
718
- - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -P <password> -I lanplus power status`. Use `=`, as the value starts with `-`.
718
+ - Ex: `mssh -M -ia='-I lanplus' bmc[1-4] 'power status'` runs `ipmitool -H <address> -U <user> -E -I lanplus power status`, with the password in its environment. Use `=`, as the value starts with `-`.
719
719
 
720
720
  ### `-im, --ipmi_method IPMI_METHOD` | _`DEFAULT_IPMI_METHOD`_
721
721
  - How `--ipmi` reaches a host:
722
- - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -P <password> [IPMI_ARGS] <command>` on this machine.
722
+ - `ipmitool` (default): runs `ipmitool -H <address> -U <user> -E [IPMI_ARGS] <command>` on this machine; `-E` reads the password from the environment mssh gives it.
723
723
  - `redfish`: runs `redfishtool -S Always -a -r <address> -u <user> -p <password> [IPMI_ARGS] <command>` on this machine (`Systems` without a command).
724
724
  - `ssh_ipmitool`: logs in to the host over ssh with the IPMI username and password (`-iu` / `-ip`, so `admin` / `admin` unless set) and runs `ipmitool <command>` there.
725
725
  - Any other value fails with `IPMI method ... not supported!`.
@@ -876,7 +876,7 @@ mssh -f ./data/ allServers /tmp/data/
876
876
 
877
877
  ### `-hf, --history_file HISTORY_FILE` | _`DEFAULT_HISTORY_FILE`_
878
878
  - The file to store command history.
879
- - By default, mssh store command history in `~/.mssh_history`.
879
+ - By default, mssh store command history in `~/.mssh_history`, readable by its owner only. Passwords are not stored (see `-p`).
880
880
  - The history file is a TSV ( tab separated values ) file with each line containing: timestamp, mssh_path, options, hosts, commands.
881
881
  - A line records the command so that replaying it (the Ctrl-R picker, `mssh-complete`) runs it the same way: the hosts as typed and every option that changes what runs, where, or what is shown; a flag when it is on, an option with a value when the value is not the default (the config files' `DEFAULT_*`). `-T`, `-uk` (or `-k` with a folder), `--script` and `-s PATH` are recorded as the timeout, key file and options they chose, `--config_file` and `--host_file` with their absolute paths. Not recorded: `-ip` (and with it the other IPMI options, see `-ip`), and `--generate_config_file`, `--store_config_file` and `--add_control_master_config`, which do something besides the run. The `> mssh ...` line mssh prints at start lists the same options.
882
882
 
@@ -970,7 +970,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
970
970
 
971
971
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
972
972
 
973
- Below is a sample help message output from multiSSH3 6.32 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
973
+ Below is a sample help message output from multiSSH3 6.33 @ 2026-10-14 (no config file, 120 columns; the lines after the options list the programs found on that machine)
974
974
 
975
975
  ```bash
976
976
  $ mssh -h
@@ -996,7 +996,7 @@ options:
996
996
  -u, --username USERNAME
997
997
  The general username to use to connect to the hosts via ssh. Will get overwrote by individual username@host if specified. (default: None)
998
998
  -p, --password PASSWORD
999
- The password to use to connect to the hosts via ssh, (default: none)
999
+ The password to use to connect to the hosts via ssh, "-p -" to be asked for it, or set MSSH_PASSWORD. (default: none)
1000
1000
  -k, --identity_file, --key, --identity [IDENTITY_FILE]
1001
1001
  The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use ~/.ssh/ (default: None)
1002
1002
  -uk, --use_key Attempt to use public key file to connect to the hosts. (default: False)
@@ -1370,6 +1370,8 @@ __DEBUG_MODE = False
1370
1370
 
1371
1371
  > `_DEFAULT_NO_START` is somewhat analogous to "demo mode" where mssh will return Host objects without actually allocating and starting the execution threads. Although they still can be started using `start_run_on_hosts()` but you should really be looking for `return_unfinished` instead for asynchronicity.
1372
1372
 
1373
+ > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1374
+
1373
1375
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1374
1376
 
1375
1377
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.
@@ -16,6 +16,7 @@ tests/conftest.py
16
16
  tests/test_cli_branches.py
17
17
  tests/test_cli_option_words.py
18
18
  tests/test_cli_processing.py
19
+ tests/test_closed_output.py
19
20
  tests/test_color_helpers.py
20
21
  tests/test_command_terminal.py
21
22
  tests/test_compact_hostnames.py
@@ -36,6 +37,7 @@ tests/test_complete_password_spellings.py
36
37
  tests/test_complete_picker.py
37
38
  tests/test_complete_probe.py
38
39
  tests/test_complete_shells.py
40
+ tests/test_complete_start.py
39
41
  tests/test_complete_store.py
40
42
  tests/test_complete_suggest.py
41
43
  tests/test_complete_terminal.py
@@ -65,11 +67,14 @@ tests/test_key_path_home.py
65
67
  tests/test_login_drop_retry.py
66
68
  tests/test_main_lifecycle.py
67
69
  tests/test_narrow_terminal.py
70
+ tests/test_no_hosts.py
68
71
  tests/test_option_no_effect.py
69
72
  tests/test_options_after_command.py
70
73
  tests/test_output_merge.py
71
74
  tests/test_output_robustness.py
72
75
  tests/test_packaging.py
76
+ tests/test_password_handling.py
77
+ tests/test_passwords_out_of_ps.py
73
78
  tests/test_run_command.py
74
79
  tests/test_run_command_branches.py
75
80
  tests/test_run_command_characterization.py
@@ -87,6 +92,8 @@ tests/test_stopped_commands.py
87
92
  tests/test_stream_reader.py
88
93
  tests/test_tui_ssh_notices.py
89
94
  tests/test_unavailable_hosts.py
95
+ tests/test_underscore_settings.py
96
+ tests/test_wide_wrap.py
90
97
  tests/vt_grid.py
91
98
  tests/data/run_command_golden.json
92
99
  tests/shells/Dockerfile.bash5.1