helper-cli 0.1.11__py3-none-any.whl → 0.1.16__py3-none-any.whl

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.
helper/commands/docker.py CHANGED
@@ -147,15 +147,15 @@ def get_verbosity(ctx: click.Context) -> Verbosity:
147
147
  def docker(ctx, verbose):
148
148
  """Docker management commands."""
149
149
  ctx.ensure_object(dict)
150
-
150
+
151
151
  # Get verbosity from parent context if it exists, otherwise use the flag value
152
152
  parent_verbosity = ctx.obj.get('verbosity', 0) if hasattr(ctx, 'obj') else 0
153
153
  verbosity_level = max(verbose, parent_verbosity)
154
-
154
+
155
155
  # Initialize verbosity
156
156
  verbosity = Verbosity(verbosity=verbosity_level)
157
157
  ctx.obj['verbosity'] = verbosity
158
-
158
+
159
159
  # Set up logging
160
160
  logger = logging.getLogger('docker-helper')
161
161
  if verbosity_level >= 3:
@@ -166,7 +166,7 @@ def docker(ctx, verbose):
166
166
  logger.setLevel(logging.WARNING)
167
167
  else:
168
168
  logger.setLevel(logging.ERROR)
169
-
169
+
170
170
  logger.debug(f"Docker command group initialized with verbosity level: {verbosity_level}")
171
171
 
172
172
  verbosity.debug("Initializing Docker command group")
@@ -176,8 +176,10 @@ def docker(ctx, verbose):
176
176
 
177
177
  @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
178
178
  @click.option('--all', '-a', is_flag=True, help='Show all containers (default shows just running)')
179
- @click.option('--format', type=click.Choice(['table', 'json'], case_sensitive=False),
179
+ @click.option('--all-containers', is_flag=True, help='Show all containers (default shows just running)')
180
+ @click.option('--format', '-f', type=click.Choice(['table', 'json'], case_sensitive=False),
180
181
  default='table', help='Output format')
182
+ @click.help_option('--help', '-h')
181
183
  @click.pass_context
182
184
  def ps(ctx, all, format):
183
185
  """List containers."""
@@ -189,7 +191,7 @@ def ps(ctx, all, format):
189
191
  try:
190
192
  verbosity.debug(f"Running command: {' '.join(cmd)}")
191
193
  result = subprocess.run(cmd, capture_output=True, text=True)
192
-
194
+
193
195
  if result.returncode == 0:
194
196
  if format == 'json':
195
197
  # Try to parse and pretty-print JSON output
@@ -277,7 +279,7 @@ def run(ctx, image, name, port, detach, env, volume):
277
279
  if image:
278
280
  cmd.append(image)
279
281
  verbosity.debug(f"Using image: {image}")
280
-
282
+
281
283
  # Add any remaining arguments
282
284
  if hasattr(ctx, 'args') and ctx.args:
283
285
  cmd.extend(ctx.args)
@@ -286,7 +288,7 @@ def run(ctx, image, name, port, detach, env, volume):
286
288
  try:
287
289
  verbosity.debug(f"Running command: {' '.join(cmd)}")
288
290
  result = subprocess.run(cmd, capture_output=True, text=True)
289
-
291
+
290
292
  if result.returncode == 0:
291
293
  if result.stdout:
292
294
  click.echo(result.stdout.strip())
@@ -296,12 +298,12 @@ def run(ctx, image, name, port, detach, env, volume):
296
298
  verbosity.error(error_msg)
297
299
  click.echo(error_msg, err=True)
298
300
  ctx.exit(1)
299
-
301
+
300
302
  except Exception as e:
301
303
  error_msg = f"Failed to run container: {str(e)}"
302
304
  verbosity.error(error_msg, exc_info=verbosity.verbosity >= 3)
303
305
  click.echo(error_msg, err=True)
304
- ctx.exit(1)
306
+ ctx.exit(1)
305
307
 
306
308
  @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
307
309
  @click.argument('containers', nargs=-1, required=False)
@@ -324,7 +326,7 @@ def rm(ctx, containers, force, volumes):
324
326
  all_containers = list(containers)
325
327
  if hasattr(ctx, 'args') and ctx.args:
326
328
  all_containers.extend(ctx.args)
327
-
329
+
328
330
  if not all_containers:
329
331
  error_msg = "Error: You must specify at least one container"
330
332
  verbosity.error(error_msg)
@@ -337,7 +339,7 @@ def rm(ctx, containers, force, volumes):
337
339
  try:
338
340
  verbosity.debug(f"Running command: {' '.join(cmd)}")
339
341
  result = subprocess.run(cmd, capture_output=True, text=True)
340
-
342
+
341
343
  if result.returncode == 0:
342
344
  if result.stdout.strip():
343
345
  click.echo(result.stdout.strip())
@@ -347,16 +349,16 @@ def rm(ctx, containers, force, volumes):
347
349
  verbosity.error(error_msg)
348
350
  click.echo(error_msg, err=True)
349
351
  ctx.exit(1)
350
-
352
+
351
353
  except Exception as e:
352
354
  error_msg = f"Failed to remove containers: {str(e)}"
353
355
  verbosity.error(error_msg, exc_info=verbosity.verbosity >= 3)
354
356
  click.echo(error_msg, err=True)
355
- ctx.exit(1)
357
+ ctx.exit(1)
356
358
 
357
359
  @docker.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
358
360
  @click.option('--show-all', '-a', is_flag=True, help='Show all containers (default shows just running)')
359
- @click.option('--http-only', is_flag=True, help='Show only containers with HTTP/HTTPS ports')
361
+ @click.option('--http-only', '-h', is_flag=True, help='Show only containers with HTTP/HTTPS ports')
360
362
  @click.pass_context
361
363
  def url(ctx, show_all, http_only):
362
364
  """Show containers with their HTTP/HTTPS URLs."""
@@ -413,17 +415,17 @@ def url(ctx, show_all, http_only):
413
415
  if not port.get('host_port') or not port.get('container_port'):
414
416
  verbosity.debug(f"Skipping incomplete port mapping: {port}")
415
417
  continue
416
-
418
+
417
419
  verbosity.debug(f"Checking port mapping: {port}")
418
420
  verbosity.debug(f"Container name: {name}, Port: {port['container_port']}")
419
-
421
+
420
422
  # Handle different port string formats (e.g., '8069/tcp', '0.0.0.0:8080->80/tcp')
421
423
  port_str = port['container_port']
422
-
424
+
423
425
  # Extract port number and protocol
424
426
  port_num = None
425
427
  protocol = 'tcp' # default protocol
426
-
428
+
427
429
  # Handle format like '8069/tcp' or '80/http'
428
430
  if '/' in port_str:
429
431
  port_num, protocol = port_str.split('/', 1)
@@ -436,21 +438,23 @@ def url(ctx, show_all, http_only):
436
438
  port_num = port_mapping
437
439
  else:
438
440
  port_num = port_str
439
-
441
+
440
442
  # Clean up port number (remove any non-numeric characters)
441
443
  port_num = ''.join(c for c in port_num if c.isdigit())
442
-
444
+
443
445
  # Map all ports to HTTP URLs
444
446
  if port_num: # Process all ports regardless of protocol
445
447
  scheme = 'http'
446
-
448
+
447
449
  # Handle IPv6 addresses (add brackets if needed)
448
450
  host = port['host_ip']
449
451
  if ':' in host and not host.startswith('['):
450
452
  host = f'[{host}]'
451
-
453
+ verbosity.info(f"Skipping non-HTTP port: {port['container_port']}")
454
+ continue
455
+
452
456
  url = f"{scheme}://{host}:{port['host_port']}"
453
-
457
+
454
458
  container_info['urls'].append({
455
459
  'url': url,
456
460
  'port': port_num,
@@ -538,7 +542,7 @@ def rmi(ctx, image, all_tags, force, no_prune):
538
542
  images.append(image)
539
543
  if hasattr(ctx, 'args') and ctx.args:
540
544
  images.extend(ctx.args)
541
-
545
+
542
546
  if not images and not all_tags:
543
547
  error_msg = "Error: You must specify at least one image"
544
548
  verbosity.error(error_msg)
@@ -551,7 +555,7 @@ def rmi(ctx, image, all_tags, force, no_prune):
551
555
  verbosity.error(error_msg)
552
556
  click.echo(error_msg, err=True)
553
557
  ctx.exit(1)
554
-
558
+
555
559
  # Get all tags for the specified images
556
560
  all_tags_to_remove = []
557
561
  for img in images:
@@ -559,30 +563,30 @@ def rmi(ctx, image, all_tags, force, no_prune):
559
563
  try:
560
564
  result = subprocess.run(
561
565
  ['docker', 'images', '--format', '{{.Repository}}:{{.Tag}}', img],
562
- capture_output=True,
566
+ capture_output=True,
563
567
  text=True
564
568
  )
565
-
569
+
566
570
  if result.returncode == 0 and result.stdout.strip():
567
571
  tags = [line for line in result.stdout.split('\n') if line]
568
572
  verbosity.debug(f"Found {len(tags)} tags for {img}")
569
573
  all_tags_to_remove.extend(tags)
570
574
  else:
571
575
  verbosity.warning(f"No images found matching '{img}'")
572
-
576
+
573
577
  except Exception as e:
574
578
  verbosity.error(f"Error finding tags for {img}: {str(e)}", exc_info=verbosity.verbosity >= 3)
575
579
  continue
576
-
580
+
577
581
  if not all_tags_to_remove:
578
582
  error_msg = "No matching images found to remove"
579
583
  verbosity.error(error_msg)
580
584
  click.echo(error_msg, err=True)
581
585
  ctx.exit(1)
582
-
586
+
583
587
  cmd.extend(all_tags_to_remove)
584
588
  verbosity.info(f"Removing {len(all_tags_to_remove)} image(s) with all tags")
585
-
589
+
586
590
  else:
587
591
  cmd.extend(images)
588
592
  verbosity.info(f"Removing {len(images)} image(s)")
@@ -590,7 +594,7 @@ def rmi(ctx, image, all_tags, force, no_prune):
590
594
  try:
591
595
  verbosity.debug(f"Running command: {' '.join(cmd)}")
592
596
  result = subprocess.run(cmd, capture_output=True, text=True)
593
-
597
+
594
598
  if result.returncode == 0:
595
599
  if result.stdout.strip():
596
600
  click.echo(result.stdout.strip())
@@ -601,9 +605,9 @@ def rmi(ctx, image, all_tags, force, no_prune):
601
605
  verbosity.error(error_msg)
602
606
  click.echo(error_msg, err=True)
603
607
  ctx.exit(1)
604
-
608
+
605
609
  except Exception as e:
606
610
  error_msg = f"Failed to remove images: {str(e)}"
607
611
  verbosity.error(error_msg, exc_info=verbosity.verbosity >= 3)
608
612
  click.echo(error_msg, err=True)
609
- ctx.exit(1)
613
+ ctx.exit(1)
@@ -0,0 +1,39 @@
1
+ import click
2
+ import subprocess
3
+ from ..utils import run_cmd
4
+
5
+ def check_speedtest_installed():
6
+ """Check if speedtest-cli is installed."""
7
+ try:
8
+ subprocess.run(
9
+ ["which", "speedtest-cli"],
10
+ check=True,
11
+ stdout=subprocess.PIPE,
12
+ stderr=subprocess.PIPE,
13
+ )
14
+ return True
15
+ except (subprocess.CalledProcessError, FileNotFoundError):
16
+ return False
17
+
18
+ @click.command()
19
+ @click.option(
20
+ "--simple", "-s",
21
+ is_flag=True,
22
+ help="Only show basic speed information"
23
+ )
24
+ def speed(simple):
25
+ """Test internet speed using speedtest-cli"""
26
+ if not check_speedtest_installed():
27
+ click.echo("Error: speedtest-cli is not installed. Please install it first.")
28
+ click.echo("You can install it with: pip install speedtest-cli")
29
+ return
30
+
31
+ cmd = "speedtest-cli"
32
+ if simple:
33
+ cmd += " --simple"
34
+
35
+ run_cmd(cmd)
36
+
37
+ # Add aliases for the command
38
+ speed_test = speed
39
+ sp = speed
@@ -0,0 +1,276 @@
1
+ import click
2
+ import platform
3
+ import subprocess
4
+ import sys
5
+ from datetime import datetime
6
+
7
+ def run_command(cmd):
8
+ """Run a shell command and return its output"""
9
+ try:
10
+ result = subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.STDOUT).strip()
11
+ return result if result else "N/A"
12
+ except subprocess.CalledProcessError as e:
13
+ return f"Error: {e.output}" if e.output else "Command failed"
14
+
15
+ def format_bytes(size_bytes):
16
+ """Convert bytes to human readable format"""
17
+ if not isinstance(size_bytes, (int, float)):
18
+ try:
19
+ size_bytes = float(size_bytes)
20
+ except (ValueError, TypeError):
21
+ return str(size_bytes)
22
+
23
+ for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']:
24
+ if size_bytes < 1024.0 or unit == 'PB':
25
+ return f"{size_bytes:.1f} {unit}"
26
+ size_bytes /= 1024.0
27
+ return f"{size_bytes:.1f} PB"
28
+
29
+ def parse_vm_stat(output):
30
+ """Parse macOS vm_stat output and return human readable memory info"""
31
+ if not output or 'Mach Virtual Memory Statistics' not in output:
32
+ return "Memory information not available"
33
+
34
+ # Get physical memory using sysctl
35
+ try:
36
+ total_memory = int(run_command('sysctl -n hw.memsize'))
37
+ except (ValueError, subprocess.CalledProcessError):
38
+ return "Error: Could not determine total physical memory"
39
+
40
+ lines = output.split('\n')
41
+ mem_info = {}
42
+
43
+ for line in lines[1:]: # Skip header
44
+ if ':' in line:
45
+ key, value = line.split(':', 1)
46
+ key = key.strip().strip('.')
47
+ try:
48
+ # Convert pages to bytes (1 page = 4KB on macOS)
49
+ pages = int(value.strip().strip('.'))
50
+ mem_info[key] = pages * 4096 # 4KB per page
51
+ except (ValueError, AttributeError):
52
+ mem_info[key] = value.strip()
53
+
54
+ # Calculate memory usage
55
+ wired_memory = mem_info.get('Pages wired down', 0)
56
+ active_memory = mem_info.get('Pages active', 0)
57
+ inactive_memory = mem_info.get('Pages inactive', 0)
58
+ free_memory = mem_info.get('Pages free', 0)
59
+
60
+ # Calculate used memory (wired + active)
61
+ used_memory = wired_memory + active_memory
62
+
63
+ # Calculate app memory (active + inactive)
64
+ app_memory = active_memory + inactive_memory
65
+
66
+ # Calculate cached files (inactive memory can be purged by the OS)
67
+ cached_files = inactive_memory
68
+
69
+ return (
70
+ f"Total: {format_bytes(total_memory)}\n"
71
+ f"Used: {format_bytes(used_memory)} (Apps: {format_bytes(app_memory)}, Wired: {format_bytes(wired_memory)})\n"
72
+ f"Free: {format_bytes(free_memory)}\n"
73
+ f"Cached: {format_bytes(cached_files)}\n"
74
+ f"Usage: {used_memory/total_memory*100:.1f}%"
75
+ )
76
+
77
+ def parse_df_output(output):
78
+ """Parse df output and format sizes"""
79
+ if not output:
80
+ return "Disk information not available"
81
+
82
+ lines = output.split('\n')
83
+ if not lines:
84
+ return output
85
+
86
+ # Keep the header
87
+ result = [lines[0]]
88
+
89
+ # Process each line
90
+ for line in lines[1:]:
91
+ if not line.strip():
92
+ continue
93
+
94
+ parts = line.split()
95
+ if len(parts) >= 5:
96
+ # Format size columns (assuming standard df -h output)
97
+ parts[1] = format_bytes(parts[1].upper().replace('G', 'GB').replace('M', 'MB')
98
+ .replace('K', 'KB').replace('B', '').replace('I', '').strip())
99
+ parts[2] = format_bytes(parts[2].upper().replace('G', 'GB').replace('M', 'MB')
100
+ .replace('K', 'KB').replace('B', '').replace('I', '').strip())
101
+ parts[3] = format_bytes(parts[3].upper().replace('G', 'GB').replace('M', 'MB')
102
+ .replace('K', 'KB').replace('B', '').replace('I', '').strip())
103
+
104
+ # Reconstruct the line with formatted sizes
105
+ result.append(' '.join(parts))
106
+ else:
107
+ result.append(line)
108
+
109
+ return '\n'.join(result)
110
+
111
+ def get_os_specific_info():
112
+ """Get OS-specific system information"""
113
+ system = platform.system().lower()
114
+
115
+ if system == 'darwin': # macOS
116
+ return {
117
+ 'cpu': 'sysctl -n machdep.cpu.brand_string',
118
+ 'cpu_cores': 'sysctl -n hw.ncpu',
119
+ 'memory': 'vm_stat',
120
+ 'disks': 'df -h',
121
+ 'os_version': 'sw_vers',
122
+ 'hostname': 'hostname',
123
+ 'uptime': 'uptime'
124
+ }
125
+ elif system == 'linux':
126
+ return {
127
+ 'cpu': 'cat /proc/cpuinfo | grep "model name" | head -n 1 | cut -d":" -f2',
128
+ 'cpu_cores': 'nproc',
129
+ 'memory': 'free -h',
130
+ 'disks': 'df -h',
131
+ 'os_version': 'cat /etc/os-release',
132
+ 'hostname': 'hostname',
133
+ 'uptime': 'uptime'
134
+ }
135
+ elif system == 'windows':
136
+ return {
137
+ 'cpu': 'wmic cpu get name',
138
+ 'cpu_cores': 'wmic cpu get NumberOfCores',
139
+ 'memory': 'wmic OS get TotalVisibleMemorySize,FreePhysicalMemory /Value',
140
+ 'disks': 'wmic logicaldisk get size,freespace,caption',
141
+ 'os_version': 'systeminfo | findstr /B /C:"OS Name" /C:"OS Version"',
142
+ 'hostname': 'hostname',
143
+ 'uptime': 'wmic os get lastbootuptime'
144
+ }
145
+ else:
146
+ return None
147
+
148
+ def format_uptime(uptime_str, system):
149
+ """Format uptime string based on OS"""
150
+ if system == 'darwin' or system == 'linux':
151
+ # Example: ' 8:53 up 1 day, 3:45, 2 users, load averages: 2.22 2.41 2.35'
152
+ parts = uptime_str.split(',')
153
+ if 'up' in parts[0]:
154
+ return 'Uptime: ' + parts[0].split('up', 1)[1].strip()
155
+ return uptime_str
156
+
157
+ @click.command()
158
+ def system_info():
159
+ """Display system information including CPU, RAM, and disk usage"""
160
+ system = platform.system().lower()
161
+ commands = get_os_specific_info()
162
+
163
+ if not commands:
164
+ click.echo("Unsupported operating system")
165
+ return
166
+
167
+ # System Information
168
+ click.echo("="*40 + " System Information " + "="*40)
169
+ click.echo(f"System: {platform.system()} {platform.release()}")
170
+ click.echo(f"Node Name: {run_command(commands['hostname'])}")
171
+ click.echo(f"Machine: {platform.machine()}")
172
+ click.echo(f"Processor: {platform.processor() or run_command(commands['cpu']).strip()}")
173
+
174
+ # OS Version
175
+ click.echo("\n" + "="*40 + " OS Version " + "="*40)
176
+ click.echo(run_command(commands['os_version']))
177
+
178
+ # Uptime
179
+ click.echo("\n" + "="*40 + " Uptime " + "="*40)
180
+ uptime = run_command(commands['uptime'])
181
+ click.echo(format_uptime(uptime, system))
182
+
183
+ # CPU Information
184
+ click.echo("\n" + "="*40 + " CPU Info " + "="*40)
185
+ cpu_cores = run_command(commands['cpu_cores']).strip()
186
+ click.echo(f"CPU Cores: {cpu_cores}")
187
+
188
+ if system == 'darwin' or system == 'linux':
189
+ cpu_info = run_command(commands['cpu']).strip()
190
+ click.echo(f"CPU: {cpu_info}")
191
+
192
+ # CPU Usage (simplified for cross-platform)
193
+ if system == 'darwin':
194
+ load_avg = run_command('sysctl -n vm.loadavg').strip()
195
+ click.echo(f"Load Average: {load_avg}")
196
+ elif system == 'linux':
197
+ load_avg = run_command('cat /proc/loadavg').strip()
198
+ click.echo(f"Load Average: {load_avg}")
199
+
200
+ # Memory Information
201
+ click.echo("\n" + "="*40 + " Memory Information " + "="*40)
202
+ if system == 'darwin':
203
+ mem_info = run_command('vm_stat')
204
+ click.echo(parse_vm_stat(mem_info))
205
+ elif system == 'linux':
206
+ mem_info = run_command('free -b') # Get bytes for consistent formatting
207
+ lines = mem_info.split('\n')
208
+ if len(lines) > 1:
209
+ headers = lines[0].split()
210
+ values = lines[1].split()
211
+ if len(values) >= 7: # For Mem: line
212
+ total = int(values[1])
213
+ used = int(values[2])
214
+ free = int(values[3])
215
+ click.echo(
216
+ f"Total: {format_bytes(total)}\n"
217
+ f"Used: {format_bytes(used)}\n"
218
+ f"Free: {format_bytes(free)}\n"
219
+ f"Usage: {used/total*100:.1f}%"
220
+ )
221
+ elif system == 'windows':
222
+ mem_info = run_command('wmic OS get TotalVisibleMemorySize,FreePhysicalMemory /Value')
223
+ if 'TotalVisibleMemorySize' in mem_info and 'FreePhysicalMemory' in mem_info:
224
+ try:
225
+ total = int(mem_info.split('TotalVisibleMemorySize=')[1].split('\n')[0]) * 1024 # KB to bytes
226
+ free = int(mem_info.split('FreePhysicalMemory=')[1].split('\n')[0]) * 1024 # KB to bytes
227
+ used = total - free
228
+ click.echo(
229
+ f"Total: {format_bytes(total)}\n"
230
+ f"Used: {format_bytes(used)}\n"
231
+ f"Free: {format_bytes(free)}\n"
232
+ f"Usage: {used/total*100:.1f}%"
233
+ )
234
+ except (IndexError, ValueError):
235
+ click.echo(mem_info)
236
+ else:
237
+ click.echo(mem_info)
238
+
239
+ # Disk Information
240
+ click.echo("\n" + "="*40 + " Disk Information " + "="*40)
241
+ if system == 'windows':
242
+ disks = run_command('wmic logicaldisk get size,freespace,caption')
243
+ if 'Caption' in disks:
244
+ lines = disks.split('\n')
245
+ result = ["{:<5} {:<15} {:<15} {:<15} {:<10}".format(
246
+ "Drive", "Total Space", "Free Space", "Used Space", "% Used")]
247
+ for line in lines[1:]:
248
+ parts = line.strip().split()
249
+ if len(parts) >= 3:
250
+ drive = parts[0]
251
+ try:
252
+ size = int(parts[1])
253
+ free = int(parts[2])
254
+ used = size - free
255
+ pct_used = (used / size) * 100 if size > 0 else 0
256
+ result.append(
257
+ "{:<5} {:<15} {:<15} {:<15} {:.1f}%".format(
258
+ drive,
259
+ format_bytes(size),
260
+ format_bytes(free),
261
+ format_bytes(used),
262
+ pct_used
263
+ )
264
+ )
265
+ except (ValueError, IndexError):
266
+ continue
267
+ click.echo('\n'.join(result))
268
+ else:
269
+ click.echo(disks)
270
+ else:
271
+ disks = run_command('df -h' if system != 'windows' else 'wmic logicaldisk get size,freespace,caption')
272
+ click.echo(parse_df_output(disks))
273
+
274
+ # Add aliases for the command
275
+ sysinfo = system_info
276
+ si = system_info
helper/main.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import click
2
2
  import logging
3
3
  import sys
4
- from .commands import internal_ip, public_ip, arch, nixos, docker
4
+ import subprocess
5
+ from .commands import internal_ip, public_ip, arch, nixos, docker, speed, system_info
5
6
 
6
7
  class VerbosityCommand(click.Command):
7
8
  def parse_args(self, ctx, args):
@@ -76,9 +77,16 @@ class VerbosityGroup(click.Group):
76
77
 
77
78
  return ctx
78
79
 
79
- @click.group(cls=VerbosityGroup)
80
+ @click.group(cls=VerbosityGroup, context_settings={
81
+ 'help_option_names': ['-h', '--help'],
82
+ 'token_normalize_func': lambda x: 'helper' if x == 'h' else x
83
+ })
80
84
  def cli():
81
- """Helper CLI - quick system info"""
85
+ """Helper CLI - quick system info
86
+
87
+ You can use 'h' as a shortcut for 'helper' command.
88
+ Example: h docker ps
89
+ """
82
90
  # Set up basic logging
83
91
  logging.basicConfig(
84
92
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
@@ -91,6 +99,11 @@ cli.add_command(public_ip.public_ip)
91
99
  cli.add_command(arch.arch)
92
100
  cli.add_command(nixos.nixos, name="nixos")
93
101
  cli.add_command(docker.docker, name="docker")
102
+ cli.add_command(speed.speed, name="speed")
103
+ cli.add_command(speed.speed, name="sp")
104
+ cli.add_command(system_info.system_info, name="system-info")
105
+ cli.add_command(system_info.system_info, name="sysinfo")
106
+ cli.add_command(system_info.system_info, name="si")
94
107
 
95
108
  @cli.command()
96
109
  @click.pass_context
@@ -104,6 +117,8 @@ def all(ctx):
104
117
  ctx.invoke(arch.arch)
105
118
  click.echo("\n=== NixOS ===")
106
119
  ctx.invoke(nixos.nixos, 'version')
120
+ click.echo("\n=== System Info ===")
121
+ ctx.invoke(system_info.system_info)
107
122
 
108
123
  if __name__ == "__main__":
109
124
  cli()
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.4
2
+ Name: helper-cli
3
+ Version: 0.1.16
4
+ Summary: Simple system info CLI
5
+ Author-email: Huy Nguyen <huy.ntq02@gmail.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: click>=8.0.0
18
+
19
+ # 📦 Helper CLI
20
+
21
+ A simple command-line tool to show system info (IP, CPU arch, etc.) and the commands used to get them.
22
+
23
+ ---
24
+
25
+ ### 🚀 Install
26
+
27
+ ```bash
28
+ pip install helper
29
+ ```
30
+
31
+ ---
32
+
33
+ ### 🧠 Usage
34
+
35
+ ```bash
36
+ helper [command]
37
+ ```
38
+
39
+ #### Available commands:
40
+
41
+ | Command | Description | Example |
42
+ | ------------- | --------------------------------- | -------------------- |
43
+ | `internal-ip` | Show internal IP | `helper internal-ip` |
44
+ | `public-ip` | Show public IP | `helper public-ip` |
45
+ | `cpu-arch` | Show CPU architecture (arm / amd) | `helper cpu-arch` |
46
+ | `all` | Show all info | `helper all` |
47
+
48
+ Each command also prints the shell command it runs to get the result.
49
+
50
+ ### 🔄 Upgrade
51
+
52
+ ```bash
53
+ pip install --upgrade helper
54
+ ```
55
+
56
+ ---
57
+
58
+ ### 🧑‍💻 Contribute
59
+
60
+ Pull requests are welcome!
61
+ Repo: [https://github.com/nguyenhuy158/helper](https://github.com/nguyenhuy158/helper)
@@ -0,0 +1,15 @@
1
+ helper/main.py,sha256=iLoQRR_nTgb9Uv_GqebSQPJluvo7PRwBuXGCD3T5JIs,3840
2
+ helper/utils.py,sha256=JynYacwFA9kgkDuWpGPCj3VxYl_yFJL-FRCKlW1g6Mo,337
3
+ helper/commands/__init__.py,sha256=HFk0MU1U4VqGJUkb9SvCoslqpYSah8KePP9tP8Xzzs4,30
4
+ helper/commands/arch.py,sha256=AREsblUki99P_wVpi44maQd7KA8IlUCJ6ilzJAQODDU,141
5
+ helper/commands/docker.py,sha256=ktO6v5vEb8glhEZQl5ijMsQ4Kl6UL_FONNK4iCOvwgg,25250
6
+ helper/commands/internal_ip.py,sha256=ZN7c1HRgB5f-wRk9IwfGQeI3YuZqmPW-UflIpGEImt0,786
7
+ helper/commands/nixos.py,sha256=--Uz2lA-xw6-spp1WBjzzfu4-imFtcziyZuUHZk3Pxs,3113
8
+ helper/commands/public_ip.py,sha256=HS99RDYCaKDZ-AxMQhUwazgR-tT6IGlBb5Qn0f5ITPg,150
9
+ helper/commands/speed.py,sha256=AZ6RoPSi3Al4Z1RsMDIm9pjxYxXXs01qn6Fr7TEW8SM,974
10
+ helper/commands/system_info.py,sha256=QknKrFlknpvAWjHkOoKJkIMQp0S6FT_wecxHgx7Unqs,10716
11
+ helper_cli-0.1.16.dist-info/METADATA,sha256=oY4-b2_Z8Jmgzu0TgUEVFeIt0DsusNdCnCbWYc9LAZg,1619
12
+ helper_cli-0.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ helper_cli-0.1.16.dist-info/entry_points.txt,sha256=SeXS_UhbMNq2biiMGgbcJcJfqkhTIDmQ4UID3ZZgj0c,63
14
+ helper_cli-0.1.16.dist-info/top_level.txt,sha256=VM8lkErPJijbKhnfEGA_hE_YDXde4iizgqWKloZIxW8,7
15
+ helper_cli-0.1.16.dist-info/RECORD,,
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
+ h = helper.main:cli
2
3
  helper = helper.main:cli
@@ -1,6 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: helper-cli
3
- Version: 0.1.11
4
- Summary: Simple system info CLI
5
- Requires-Python: >=3.8
6
- Requires-Dist: click
@@ -1,13 +0,0 @@
1
- helper/main.py,sha256=39UV2OxIOAgak7ALwGRdK2oeCaaMXQqvXgnrWU77kB4,3247
2
- helper/utils.py,sha256=JynYacwFA9kgkDuWpGPCj3VxYl_yFJL-FRCKlW1g6Mo,337
3
- helper/commands/__init__.py,sha256=HFk0MU1U4VqGJUkb9SvCoslqpYSah8KePP9tP8Xzzs4,30
4
- helper/commands/arch.py,sha256=AREsblUki99P_wVpi44maQd7KA8IlUCJ6ilzJAQODDU,141
5
- helper/commands/docker.py,sha256=ZmEk3B2xor0p3AQJPkVQLz0biFa1MSsoF-dRxOHDvsI,25349
6
- helper/commands/internal_ip.py,sha256=ZN7c1HRgB5f-wRk9IwfGQeI3YuZqmPW-UflIpGEImt0,786
7
- helper/commands/nixos.py,sha256=--Uz2lA-xw6-spp1WBjzzfu4-imFtcziyZuUHZk3Pxs,3113
8
- helper/commands/public_ip.py,sha256=HS99RDYCaKDZ-AxMQhUwazgR-tT6IGlBb5Qn0f5ITPg,150
9
- helper_cli-0.1.11.dist-info/METADATA,sha256=6AR44RTeCCTlJvglidOgz754evOnjEhUeWb7yUqvocY,131
10
- helper_cli-0.1.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- helper_cli-0.1.11.dist-info/entry_points.txt,sha256=4EoFQ0yRogLTvStcbjlpkTqayWmiRqbMwkcDBcgQbK8,43
12
- helper_cli-0.1.11.dist-info/top_level.txt,sha256=VM8lkErPJijbKhnfEGA_hE_YDXde4iizgqWKloZIxW8,7
13
- helper_cli-0.1.11.dist-info/RECORD,,