multiSSH3 5.97__tar.gz → 5.98__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.97
3
+ Version: 5.98
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
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.97
3
+ Version: 5.98
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
@@ -84,10 +84,10 @@ except Exception:
84
84
  print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
85
85
  def cache_decorator(func):
86
86
  return func
87
- version = '5.97'
87
+ version = '5.98'
88
88
  VERSION = version
89
89
  __version__ = version
90
- COMMIT_DATE = '2025-10-21'
90
+ COMMIT_DATE = '2025-10-24'
91
91
 
92
92
  CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
93
93
  '~/multiSSH3.config.json',
@@ -377,6 +377,24 @@ ERROR_MESSAGES_TO_IGNORE = [
377
377
  'Killed by signal',
378
378
  'Connection reset by peer',
379
379
  ]
380
+ __DEFAULT_COLOR_PALETTE = {
381
+ 'cyan': (86, 173, 188),
382
+ 'green': (114, 180, 43),
383
+ 'magenta': (140, 107, 200),
384
+ 'red': (196, 38, 94),
385
+ 'white': (227, 227, 221),
386
+ 'yellow': (179, 180, 43),
387
+ 'blue': (106, 126, 200),
388
+ 'bright_black': (102, 102, 102),
389
+ 'bright_blue': (129, 154, 255),
390
+ 'bright_cyan': (102, 217, 239),
391
+ 'bright_green': (126, 226, 46),
392
+ 'bright_magenta': (174, 129, 255),
393
+ 'bright_red': (249, 38, 114),
394
+ 'bright_white': (248, 248, 242),
395
+ 'bright_yellow': (226, 226, 46),
396
+ }
397
+ COLOR_PALETTE = __DEFAULT_COLOR_PALETTE.copy()
380
398
  _DEFAULT_CALLED = True
381
399
  _DEFAULT_RETURN_UNFINISHED = False
382
400
  _DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
@@ -936,6 +954,8 @@ def get_terminal_color_capability():
936
954
  return '24bit'
937
955
  elif "256" in term:
938
956
  return '256'
957
+ elif "16" in term:
958
+ return '16'
939
959
  try:
940
960
  curses.setupterm()
941
961
  colors = curses.tigetnum("colors")
@@ -954,96 +974,120 @@ def get_terminal_color_capability():
954
974
  return 'None'
955
975
 
956
976
  @cache_decorator
957
- def get_xterm256_palette():
958
- palette = []
959
- # 0–15: system colors (we'll just fill with dummy values;
960
- # you could fill in real RGB if you need to)
961
- system_colors = [
962
- (0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0),
963
- (0, 0, 128), (128, 0, 128), (0, 128, 128), (192, 192, 192),
964
- (128, 128, 128), (255, 0, 0), (0, 255, 0), (255, 255, 0),
965
- (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255),
966
- ]
967
- palette.extend(system_colors)
968
- # 16–231: 6x6x6 color cube
969
- levels = [0, 95, 135, 175, 215, 255]
970
- for r in levels:
971
- for g in levels:
972
- for b in levels:
973
- palette.append((r, g, b))
974
- # 232–255: grayscale ramp, 24 steps from 8 to 238
975
- for i in range(24):
976
- level = 8 + i * 10
977
- palette.append((level, level, level))
978
- return palette
977
+ def rgb_to_ansi_color_string(r, g, b):
978
+ """
979
+ Return an ANSI escape sequence setting the foreground to (r,g,b)
980
+ approximated to the terminal's capability, or '' if none.
981
+ """
982
+ cap = get_terminal_color_capability()
983
+ if cap == 'None':
984
+ return ''
985
+ if cap == '24bit':
986
+ return f'\x1b[38;2;{r};{g};{b}m'
987
+ if cap == '256':
988
+ idx = _rgb_to_256_color(r, g, b)
989
+ return f'\x1b[38;5;{idx}m'
990
+ if cap == '16':
991
+ idx = _rgb_to_16_color(r, g, b)
992
+ # 0–7 = 30–37, 8–15 = 90–97
993
+ if idx < 8:
994
+ return f'\x1b[{30 + idx}m'
995
+ else:
996
+ return f'\x1b[{90 + (idx - 8)}m'
997
+ if cap == '8':
998
+ idx = _rgb_to_8_color(r, g, b)
999
+ return f'\x1b[{30 + idx}m'
1000
+ return ''
979
1001
 
980
- @cache_decorator
981
- def rgb_to_xterm_index(r, g, b):
1002
+ def _rgb_to_256_color(r, g, b):
1003
+ """
1004
+ Map (r,g,b) to the 256-color cube or grayscale ramp.
982
1005
  """
983
- Map 24-bit RGB to nearest xterm-256 color index.
984
- r, g, b should be in 0-255.
985
- Returns an int in 0-255.
1006
+ # if it’s already gray, use the 232–255 grayscale ramp
1007
+ if r == g == b:
1008
+ # 24 shades from 232 to 255
1009
+ return 232 + int(round(r / 255 * 23))
1010
+ # else map each channel to 0–5
1011
+ def to6(v):
1012
+ return int(round(v / 255 * 5))
1013
+ r6, g6, b6 = to6(r), to6(g), to6(b)
1014
+ return 16 + 36 * r6 + 6 * g6 + b6
1015
+
1016
+ def _rgb_to_16_color(r, g, b):
986
1017
  """
987
- best_index = 0
1018
+ Pick the nearest of the 16 ANSI standard colors.
1019
+ Returns an index 0-15.
1020
+ """
1021
+ palette = [
1022
+ (0, 0, 0), # 0 black
1023
+ (128, 0, 0), # 1 red
1024
+ (0, 128, 0), # 2 green
1025
+ (128, 128, 0), # 3 yellow
1026
+ (0, 0, 128), # 4 blue
1027
+ (128, 0, 128), # 5 magenta
1028
+ (0, 128, 128), # 6 cyan
1029
+ (192, 192, 192), # 7 white (light gray)
1030
+ (128, 128, 128), # 8 bright black (dark gray)
1031
+ (255, 0, 0), # 9 bright red
1032
+ (0, 255, 0), # 10 bright green
1033
+ (255, 255, 0), # 11 bright yellow
1034
+ (0, 0, 255), # 12 bright blue
1035
+ (255, 0, 255), # 13 bright magenta
1036
+ (0, 255, 255), # 14 bright cyan
1037
+ (255, 255, 255), # 15 bright white
1038
+ ]
1039
+ best_idx = 0
988
1040
  best_dist = float('inf')
989
- for i, (pr, pg, pb) in enumerate(get_xterm256_palette()):
990
- dr = pr - r
991
- dg = pg - g
992
- db = pb - b
993
- dist = dr*dr + dg*dg + db*db
1041
+ for i, (pr, pg, pb) in enumerate(palette):
1042
+ dist = (r - pr)**2 + (g - pg)**2 + (b - pb)**2
994
1043
  if dist < best_dist:
995
1044
  best_dist = dist
996
- best_index = i
997
- return best_index
1045
+ best_idx = i
1046
+ return best_idx
998
1047
 
999
- @cache_decorator
1000
- def hashable_to_color(n, brightness_threshold=500):
1001
- hash_value = hash(str(n))
1048
+ def _rgb_to_8_color(r, g, b):
1049
+ """
1050
+ Reduce to 8 colors by mapping to the 16-color index then clamping 0-7.
1051
+ """
1052
+ return _rgb_to_16_color(r//2, g//2, b//2)
1053
+
1054
+
1055
+ def int_to_color(hash_value, min_brightness=100,max_brightness=220):
1002
1056
  r = (hash_value >> 16) & 0xFF
1003
1057
  g = (hash_value >> 8) & 0xFF
1004
1058
  b = hash_value & 0xFF
1005
- if (r + g + b) < brightness_threshold:
1006
- return hashable_to_color(hash_value, brightness_threshold)
1059
+ brightness = math.sqrt(0.299 * r**2 + 0.587 * g**2 + 0.114 * b**2)
1060
+ if brightness < min_brightness:
1061
+ return int_to_color(hash(str(hash_value)), min_brightness, max_brightness)
1062
+ if brightness > max_brightness:
1063
+ return int_to_color(hash(str(hash_value)), min_brightness, max_brightness)
1007
1064
  return (r, g, b)
1008
1065
 
1009
- __previous_ansi_color_index = -1
1066
+ __previous_color_rgb = ()
1010
1067
  @cache_decorator
1011
- def string_to_unique_ansi_color(string):
1068
+ def int_to_unique_ansi_color(number):
1012
1069
  '''
1013
- Convert a string to a unique ANSI color code
1070
+ Convert a number to a unique ANSI color code
1014
1071
 
1015
1072
  Args:
1016
- string (str): The string to convert
1017
-
1073
+ number (int): The number to convert
1018
1074
  Returns:
1019
1075
  int: The ANSI color code
1020
1076
  '''
1021
- global __previous_ansi_color_index
1077
+ global __previous_color_rgb
1022
1078
  # Use a hash function to generate a consistent integer from the string
1023
1079
  color_capability = get_terminal_color_capability()
1024
- index = None
1025
1080
  if color_capability == 'None':
1026
1081
  return ''
1027
- elif color_capability == '16':
1028
- # Map to one of the 14 colors (31-37, 90-96), avoiding black and white
1029
- index = (hash(string) % 14) + 31
1030
- if index > 37:
1031
- index += 52 # Bright colors (90-97)
1032
- elif color_capability == '8':
1033
- index = (hash(string) % 6) + 31
1034
- r,g,b = hashable_to_color(string)
1035
- if color_capability == '256':
1036
- index = rgb_to_xterm_index(r,g,b)
1037
- if index:
1038
- if index == __previous_ansi_color_index:
1039
- return string_to_unique_ansi_color(hash(string))
1040
- __previous_ansi_color_index = index
1041
- if color_capability == '256':
1042
- return f'\033[38;5;{index}m'
1043
- else:
1044
- return f'\033[{index}m'
1082
+ if color_capability == '24bit':
1083
+ r, g, b = int_to_color(number)
1045
1084
  else:
1046
- return f'\033[38;2;{r};{g};{b}m'
1085
+ # for 256 colors and below, reduce brightness threshold as we do not have many color to work with
1086
+ r, g, b = int_to_color(number, min_brightness=70, max_brightness=190)
1087
+ if sum(abs(a - b) for a, b in zip((r, g, b), __previous_color_rgb)) <= 256:
1088
+ r, g, b = int_to_color(hash(str(number)))
1089
+ __previous_color_rgb = (r, g, b)
1090
+ return rgb_to_ansi_color_string(r, g, b)
1047
1091
 
1048
1092
  #%% ------------ Compacting Hostnames ----------------
1049
1093
  def __tokenize_hostname(hostname):
@@ -2795,7 +2839,7 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
2795
2839
  indexes = Counter({hostname: 0 for hostname in merging_hostnames})
2796
2840
  working_index_keys = set(merging_hostnames)
2797
2841
  previousBuddies = set()
2798
- hostnameWrapper = textwrap.TextWrapper(width=line_length - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent='├─ ', subsequent_indent='│- ')
2842
+ hostnameWrapper = textwrap.TextWrapper(width=line_length -1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent=' ', subsequent_indent='- ')
2799
2843
  hostnameWrapper.wordsep_simple_re = re.compile(r'([,]+)')
2800
2844
  diff_display_item_count = max(1,int(max(map(len, outputs_by_hostname.values())) * (1 - diff_display_threshold)))
2801
2845
  def get_multiset_index_for_hostname(hostname):
@@ -2867,12 +2911,14 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
2867
2911
  if buddy != previousBuddies:
2868
2912
  hostnameStr = ','.join(compact_hostnames(buddy))
2869
2913
  hostnameLines = hostnameWrapper.wrap(hostnameStr)
2870
- hostnameLines = [line.ljust(line_length - 1) + '│' for line in hostnameLines]
2871
- color = string_to_unique_ansi_color(hostnameStr) if len(buddy) < len(merging_hostnames) else ''
2872
- hostnameLines[0] = f"\033[0m{color}{hostnameLines[0]}"
2914
+ hostnameLines = [line.ljust(line_length) for line in hostnameLines]
2915
+ color = int_to_unique_ansi_color(hash(hostnameStr)) if len(buddy) < len(merging_hostnames) else ''
2916
+ if color:
2917
+ color = f"\033[0m{color}"
2918
+ hostnameLines[0] = f"{color}{hostnameLines[0]}"
2873
2919
  output.extend(hostnameLines)
2874
2920
  previousBuddies = buddy
2875
- output.append(lineToAdd.ljust(line_length - 1) + '│')
2921
+ output.append(lineToAdd.ljust(line_length))
2876
2922
  currentLines[lineToAdd].difference_update(buddy)
2877
2923
  if not currentLines[lineToAdd]:
2878
2924
  del currentLines[lineToAdd]
@@ -2896,17 +2942,24 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
2896
2942
 
2897
2943
  def mergeOutputs(outputs_by_hostname, merge_groups, remaining_hostnames, diff_display_threshold, line_length):
2898
2944
  output = []
2899
- output.append(('┌'+'─'*(line_length-2) + '┐'))
2900
- hostnameWrapper = textwrap.TextWrapper(width=line_length - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent='├─ ', subsequent_indent='│- ')
2945
+ color_cap = get_terminal_color_capability()
2946
+ if color_cap == 'None':
2947
+ color_line = ''
2948
+ color_reset = ''
2949
+ else:
2950
+ color_line = rgb_to_ansi_color_string(*COLOR_PALETTE.get('white', __DEFAULT_COLOR_PALETTE['white']))
2951
+ color_reset = '\033[0m'
2952
+ output.append(color_line+'─'*(line_length)+color_reset)
2953
+ hostnameWrapper = textwrap.TextWrapper(width=line_length - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent='─ ', subsequent_indent='- ')
2901
2954
  hostnameWrapper.wordsep_simple_re = re.compile(r'([,]+)')
2902
2955
  for merging_hostnames in merge_groups:
2903
2956
  mergeOutput(merging_hostnames, outputs_by_hostname, output, diff_display_threshold,line_length)
2904
- output.append('\033[0m├'+'─'*(line_length-2) + '┤')
2957
+ output.append(color_line+'─'*(line_length)+color_reset)
2905
2958
  for hostname in remaining_hostnames:
2906
2959
  hostnameLines = hostnameWrapper.wrap(','.join(compact_hostnames([hostname])))
2907
- output.extend(line.ljust(line_length - 1) + '│' for line in hostnameLines)
2908
- output.extend(line.ljust(line_length - 1) + '│' for line in outputs_by_hostname[hostname])
2909
- output.append('\033[0m├'+'─'*(line_length-2) + '┤')
2960
+ output.extend(line.ljust(line_length ) for line in hostnameLines)
2961
+ output.extend(line.ljust(line_length ) for line in outputs_by_hostname[hostname])
2962
+ output.append(color_line+'─'*(line_length)+color_reset)
2910
2963
  if output:
2911
2964
  output.pop()
2912
2965
  # if output and output[0] and output[0].startswith('├'):
@@ -2931,26 +2984,40 @@ def get_host_raw_output(hosts, terminal_width):
2931
2984
  outputs_by_hostname = {}
2932
2985
  line_bag_by_hostname = {}
2933
2986
  hostnames_by_line_bag_len = {}
2934
- text_wrapper = textwrap.TextWrapper(width=terminal_width - 2, tabsize=4, replace_whitespace=False, drop_whitespace=False,
2935
- initial_indent=' ', subsequent_indent='│-')
2987
+ text_wrapper = textwrap.TextWrapper(width=terminal_width - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
2988
+ initial_indent=' ', subsequent_indent='-')
2936
2989
  max_length = 20
2990
+ color_cap = get_terminal_color_capability()
2991
+ if color_cap == 'None':
2992
+ color_reset_str = ''
2993
+ blue_str = ''
2994
+ cyan_str = ''
2995
+ green_str = ''
2996
+ red_str = ''
2997
+ else:
2998
+ color_reset_str = '\033[0m'
2999
+ blue_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('blue', __DEFAULT_COLOR_PALETTE['blue']))
3000
+ cyan_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_cyan', __DEFAULT_COLOR_PALETTE['bright_cyan']))
3001
+ green_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_green', __DEFAULT_COLOR_PALETTE['bright_green']))
3002
+ red_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_red', __DEFAULT_COLOR_PALETTE['bright_red']))
2937
3003
  hosts = pre_merge_hosts(hosts)
2938
3004
  for host in hosts:
2939
3005
  max_length = max(max_length, len(max(host.name.split(','), key=len)) + 3)
2940
- hostPrintOut = ["│█ EXECUTED COMMAND:"]
3006
+ hostPrintOut = [f"{cyan_str}█{color_reset_str} EXECUTED COMMAND:"]
2941
3007
  for line in host.command.splitlines():
2942
3008
  hostPrintOut.extend(text_wrapper.wrap(line))
2943
3009
  # hostPrintOut.extend(itertools.chain.from_iterable(text_wrapper.wrap(line) for line in host['command'].splitlines()))
2944
3010
  lineBag = {(0,host.command)}
2945
3011
  prevLine = host.command
2946
3012
  if host.stdout:
2947
- hostPrintOut.append('│▓ STDOUT:')
3013
+ hostPrintOut.append(f'{blue_str}▓{color_reset_str} STDOUT:')
2948
3014
  # for line in host.stdout:
2949
3015
  # if len(line) < terminal_width - 2:
2950
- # hostPrintOut.append(f" {line}")
3016
+ # hostPrintOut.append(f" {line}")
2951
3017
  # else:
2952
3018
  # hostPrintOut.extend(text_wrapper.wrap(line))
2953
- hostPrintOut.extend(f" {line}" for line in host.stdout)
3019
+ hostPrintOut.extend(f" {line}" for line in host.stdout)
3020
+ max_length = max(max_length, max(map(len, host.stdout)))
2954
3021
  # hostPrintOut.extend(text_wrapper.wrap(line) for line in host.stdout)
2955
3022
  lineBag.add((prevLine,1))
2956
3023
  lineBag.add((1,host.stdout[0]))
@@ -2966,22 +3033,26 @@ def get_host_raw_output(hosts, terminal_width):
2966
3033
  elif host.stderr[-1].strip().endswith('No route to host'):
2967
3034
  host.stderr[-1] = 'Cannot find host!'
2968
3035
  if host.stderr:
2969
- hostPrintOut.append('│▒ STDERR:')
3036
+ hostPrintOut.append(f'{red_str}▒{color_reset_str} STDERR:')
2970
3037
  # for line in host.stderr:
2971
3038
  # if len(line) < terminal_width - 2:
2972
- # hostPrintOut.append(f" {line}")
3039
+ # hostPrintOut.append(f" {line}")
2973
3040
  # else:
2974
3041
  # hostPrintOut.extend(text_wrapper.wrap(line))
2975
- hostPrintOut.extend(f" {line}" for line in host.stderr)
3042
+ hostPrintOut.extend(f" {line}" for line in host.stderr)
3043
+ max_length = max(max_length, max(map(len, host.stderr)))
2976
3044
  lineBag.add((prevLine,2))
2977
3045
  lineBag.add((2,host.stderr[0]))
2978
3046
  lineBag.update(host.stderr)
2979
3047
  if len(host.stderr) > 1:
2980
3048
  lineBag.update(zip(host.stderr, host.stderr[1:]))
2981
3049
  prevLine = host.stderr[-1]
2982
- hostPrintOut.append(f"│░ RETURN CODE: {host.returncode}")
3050
+ if host.returncode != 0:
3051
+ codeColor = red_str
3052
+ else:
3053
+ codeColor = green_str
3054
+ hostPrintOut.append(f"{codeColor}░{color_reset_str} RETURN CODE: {host.returncode}")
2983
3055
  lineBag.add((prevLine,f"{host.returncode}"))
2984
- max_length = max(max_length, max(map(len, hostPrintOut)))
2985
3056
  outputs_by_hostname[host.name] = hostPrintOut
2986
3057
  line_bag_by_hostname[host.name] = lineBag
2987
3058
  hostnames_by_line_bag_len.setdefault(len(lineBag), set()).add(host.name)
@@ -3022,6 +3093,7 @@ def form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_le
3022
3093
  return merge_groups, remaining_hostnames
3023
3094
 
3024
3095
  def generate_output(hosts, usejson = False, greppable = False,quiet = False,encoding = _encoding,keyPressesIn = [[]]):
3096
+ color_cap = get_terminal_color_capability()
3025
3097
  if quiet:
3026
3098
  # remove hosts with returncode 0
3027
3099
  hosts = [host for host in hosts if host.returncode != 0]
@@ -3029,7 +3101,10 @@ def generate_output(hosts, usejson = False, greppable = False,quiet = False,enco
3029
3101
  if usejson:
3030
3102
  return '{"Success": true}'
3031
3103
  else:
3032
- return 'Success'
3104
+ if color_cap == 'None':
3105
+ return 'Success'
3106
+ else:
3107
+ return '\033[32mSuccess\033[0m'
3033
3108
  if usejson:
3034
3109
  # [print(dict(host)) for host in hosts]
3035
3110
  #print(json.dumps([dict(host) for host in hosts],indent=4))
@@ -3064,23 +3139,32 @@ def generate_output(hosts, usejson = False, greppable = False,quiet = False,enco
3064
3139
  except Exception:
3065
3140
  eprint("Warning: diff_display_threshold should be a float between 0 and 1. Setting to default value of 0.9")
3066
3141
  diff_display_threshold = 0.9
3142
+
3143
+ color_reset_str = '' if color_cap == 'None' else '\033[0m'
3144
+ white_str = '' if color_cap == 'None' else rgb_to_ansi_color_string(*COLOR_PALETTE.get('white', __DEFAULT_COLOR_PALETTE['white']))
3067
3145
  terminal_length = get_terminal_size()[0]
3068
3146
  outputs_by_hostname, line_bag_by_hostname, hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_length = get_host_raw_output(hosts,terminal_length)
3069
3147
  merge_groups ,remaining_hostnames = form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_bag_by_hostname, diff_display_threshold)
3070
3148
  outputs = mergeOutputs(outputs_by_hostname, merge_groups,remaining_hostnames, diff_display_threshold,line_length)
3071
3149
  if keyPressesIn[-1]:
3072
3150
  CMDsOut = [''.join(cmd).encode(encoding=encoding,errors='backslashreplace').decode(encoding=encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in keyPressesIn if cmd]
3073
- outputs.append("├─ User Inputs:".ljust(line_length -1,'─')+'┤')
3151
+ outputs.append(color_reset_str + " User Inputs:".ljust(line_length,'─'))
3074
3152
  cmdOut = []
3075
3153
  for line in CMDsOut:
3076
3154
  cmdOut.extend(textwrap.wrap(line, width=line_length-1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
3077
- initial_indent=' ', subsequent_indent='│-'))
3078
- outputs.extend(cmd.ljust(line_length -1)+'│' for cmd in cmdOut)
3155
+ initial_indent=' ', subsequent_indent='-'))
3156
+ outputs.extend(cmd.ljust(line_length) for cmd in cmdOut)
3079
3157
  keyPressesIn[-1].clear()
3080
3158
  if not outputs:
3081
- rtnStr = 'Success' if quiet else ''
3159
+ if quiet:
3160
+ if color_cap == 'None':
3161
+ return 'Success'
3162
+ else:
3163
+ return '\033[32mSuccess\033[0m'
3164
+ else:
3165
+ rtnStr = ''
3082
3166
  else:
3083
- rtnStr = '\n'.join(outputs + [('\033[0m└'+'─'*(line_length-2)+'┘')])
3167
+ rtnStr = '\n'.join(outputs + [white_str + '─' * (line_length) + color_reset_str])
3084
3168
  return rtnStr
3085
3169
 
3086
3170
  def print_output(hosts,usejson = False,quiet = False,greppable = False):
@@ -3126,7 +3210,15 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
3126
3210
  if total_sleeped > 0.1:
3127
3211
  break
3128
3212
  if any([host.returncode is None for host in hosts]):
3129
- curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
3213
+ try:
3214
+ curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
3215
+ except Exception:
3216
+ try:
3217
+ curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
3218
+ except Exception as e:
3219
+ eprint(f"Curses print error: {e}")
3220
+ import traceback
3221
+ print(traceback.format_exc())
3130
3222
  if not returnUnfinished:
3131
3223
  # wait until all hosts have a return code
3132
3224
  while any([host.returncode is None for host in hosts]):
File without changes
File without changes
File without changes
File without changes
File without changes