helper-cli 0.1.11__py3-none-any.whl → 0.1.21__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.
@@ -0,0 +1,326 @@
1
+ import click
2
+ import platform
3
+ import subprocess
4
+ import sys
5
+ from datetime import datetime
6
+
7
+
8
+ def run_command(cmd):
9
+ """Run a shell command and return its output"""
10
+ try:
11
+ result = subprocess.check_output(
12
+ cmd, shell=True, text=True, stderr=subprocess.STDOUT
13
+ ).strip()
14
+ return result if result else "N/A"
15
+ except subprocess.CalledProcessError as e:
16
+ return f"Error: {e.output}" if e.output else "Command failed"
17
+
18
+
19
+ def format_bytes(size_bytes):
20
+ """Convert bytes to human readable format"""
21
+ if not isinstance(size_bytes, (int, float)):
22
+ try:
23
+ size_bytes = float(size_bytes)
24
+ except (ValueError, TypeError):
25
+ return str(size_bytes)
26
+
27
+ for unit in ["B", "KB", "MB", "GB", "TB", "PB"]:
28
+ if size_bytes < 1024.0 or unit == "PB":
29
+ return f"{size_bytes:.1f} {unit}"
30
+ size_bytes /= 1024.0
31
+ return f"{size_bytes:.1f} PB"
32
+
33
+
34
+ def parse_vm_stat(output):
35
+ """Parse macOS vm_stat output and return human readable memory info"""
36
+ if not output or "Mach Virtual Memory Statistics" not in output:
37
+ return "Memory information not available"
38
+
39
+ # Get physical memory using sysctl
40
+ try:
41
+ total_memory = int(run_command("sysctl -n hw.memsize"))
42
+ except (ValueError, subprocess.CalledProcessError):
43
+ return "Error: Could not determine total physical memory"
44
+
45
+ lines = output.split("\n")
46
+ mem_info = {}
47
+
48
+ for line in lines[1:]: # Skip header
49
+ if ":" in line:
50
+ key, value = line.split(":", 1)
51
+ key = key.strip().strip(".")
52
+ try:
53
+ # Convert pages to bytes (1 page = 4KB on macOS)
54
+ pages = int(value.strip().strip("."))
55
+ mem_info[key] = pages * 4096 # 4KB per page
56
+ except (ValueError, AttributeError):
57
+ mem_info[key] = value.strip()
58
+
59
+ # Calculate memory usage
60
+ wired_memory = mem_info.get("Pages wired down", 0)
61
+ active_memory = mem_info.get("Pages active", 0)
62
+ inactive_memory = mem_info.get("Pages inactive", 0)
63
+ free_memory = mem_info.get("Pages free", 0)
64
+
65
+ # Calculate used memory (wired + active)
66
+ used_memory = wired_memory + active_memory
67
+
68
+ # Calculate app memory (active + inactive)
69
+ app_memory = active_memory + inactive_memory
70
+
71
+ # Calculate cached files (inactive memory can be purged by the OS)
72
+ cached_files = inactive_memory
73
+
74
+ return (
75
+ f"Total: {format_bytes(total_memory)}\n"
76
+ f"Used: {format_bytes(used_memory)} (Apps: {format_bytes(app_memory)}, Wired: {format_bytes(wired_memory)})\n"
77
+ f"Free: {format_bytes(free_memory)}\n"
78
+ f"Cached: {format_bytes(cached_files)}\n"
79
+ f"Usage: {used_memory/total_memory*100:.1f}%"
80
+ )
81
+
82
+
83
+ def parse_df_output(output):
84
+ """Parse df output and format sizes"""
85
+ if not output:
86
+ return "Disk information not available"
87
+
88
+ lines = output.split("\n")
89
+ if not lines:
90
+ return output
91
+
92
+ # Keep the header
93
+ result = [lines[0]]
94
+
95
+ # Process each line
96
+ for line in lines[1:]:
97
+ if not line.strip():
98
+ continue
99
+
100
+ parts = line.split()
101
+ if len(parts) >= 5:
102
+ # Format size columns (assuming standard df -h output)
103
+ parts[1] = format_bytes(
104
+ parts[1]
105
+ .upper()
106
+ .replace("G", "GB")
107
+ .replace("M", "MB")
108
+ .replace("K", "KB")
109
+ .replace("B", "")
110
+ .replace("I", "")
111
+ .strip()
112
+ )
113
+ parts[2] = format_bytes(
114
+ parts[2]
115
+ .upper()
116
+ .replace("G", "GB")
117
+ .replace("M", "MB")
118
+ .replace("K", "KB")
119
+ .replace("B", "")
120
+ .replace("I", "")
121
+ .strip()
122
+ )
123
+ parts[3] = format_bytes(
124
+ parts[3]
125
+ .upper()
126
+ .replace("G", "GB")
127
+ .replace("M", "MB")
128
+ .replace("K", "KB")
129
+ .replace("B", "")
130
+ .replace("I", "")
131
+ .strip()
132
+ )
133
+
134
+ # Reconstruct the line with formatted sizes
135
+ result.append(" ".join(parts))
136
+ else:
137
+ result.append(line)
138
+
139
+ return "\n".join(result)
140
+
141
+
142
+ def get_os_specific_info():
143
+ """Get OS-specific system information"""
144
+ system = platform.system().lower()
145
+
146
+ if system == "darwin": # macOS
147
+ return {
148
+ "cpu": "sysctl -n machdep.cpu.brand_string",
149
+ "cpu_cores": "sysctl -n hw.ncpu",
150
+ "memory": "vm_stat",
151
+ "disks": "df -h",
152
+ "os_version": "sw_vers",
153
+ "hostname": "hostname",
154
+ "uptime": "uptime",
155
+ }
156
+ elif system == "linux":
157
+ return {
158
+ "cpu": 'cat /proc/cpuinfo | grep "model name" | head -n 1 | cut -d":" -f2',
159
+ "cpu_cores": "nproc",
160
+ "memory": "free -h",
161
+ "disks": "df -h",
162
+ "os_version": "cat /etc/os-release",
163
+ "hostname": "hostname",
164
+ "uptime": "uptime",
165
+ }
166
+ elif system == "windows":
167
+ return {
168
+ "cpu": "wmic cpu get name",
169
+ "cpu_cores": "wmic cpu get NumberOfCores",
170
+ "memory": "wmic OS get TotalVisibleMemorySize,FreePhysicalMemory /Value",
171
+ "disks": "wmic logicaldisk get size,freespace,caption",
172
+ "os_version": 'systeminfo | findstr /B /C:"OS Name" /C:"OS Version"',
173
+ "hostname": "hostname",
174
+ "uptime": "wmic os get lastbootuptime",
175
+ }
176
+ else:
177
+ return None
178
+
179
+
180
+ def format_uptime(uptime_str, system):
181
+ """Format uptime string based on OS"""
182
+ if system == "darwin" or system == "linux":
183
+ # Example: ' 8:53 up 1 day, 3:45, 2 users, load averages: 2.22 2.41 2.35'
184
+ parts = uptime_str.split(",")
185
+ if "up" in parts[0]:
186
+ return "Uptime: " + parts[0].split("up", 1)[1].strip()
187
+ return uptime_str
188
+
189
+
190
+ @click.command()
191
+ def system_info():
192
+ """Display system information including CPU, RAM, and disk usage"""
193
+ system = platform.system().lower()
194
+ commands = get_os_specific_info()
195
+
196
+ if not commands:
197
+ click.echo("Unsupported operating system")
198
+ return
199
+
200
+ # System Information
201
+ click.echo("=" * 40 + " System Information " + "=" * 40)
202
+ click.echo(f"System: {platform.system()} {platform.release()}")
203
+ click.echo(f"Node Name: {run_command(commands['hostname'])}")
204
+ click.echo(f"Machine: {platform.machine()}")
205
+ click.echo(
206
+ f"Processor: {platform.processor() or run_command(commands['cpu']).strip()}"
207
+ )
208
+
209
+ # OS Version
210
+ click.echo("\n" + "=" * 40 + " OS Version " + "=" * 40)
211
+ click.echo(run_command(commands["os_version"]))
212
+
213
+ # Uptime
214
+ click.echo("\n" + "=" * 40 + " Uptime " + "=" * 40)
215
+ uptime = run_command(commands["uptime"])
216
+ click.echo(format_uptime(uptime, system))
217
+
218
+ # CPU Information
219
+ click.echo("\n" + "=" * 40 + " CPU Info " + "=" * 40)
220
+ cpu_cores = run_command(commands["cpu_cores"]).strip()
221
+ click.echo(f"CPU Cores: {cpu_cores}")
222
+
223
+ if system == "darwin" or system == "linux":
224
+ cpu_info = run_command(commands["cpu"]).strip()
225
+ click.echo(f"CPU: {cpu_info}")
226
+
227
+ # CPU Usage (simplified for cross-platform)
228
+ if system == "darwin":
229
+ load_avg = run_command("sysctl -n vm.loadavg").strip()
230
+ click.echo(f"Load Average: {load_avg}")
231
+ elif system == "linux":
232
+ load_avg = run_command("cat /proc/loadavg").strip()
233
+ click.echo(f"Load Average: {load_avg}")
234
+
235
+ # Memory Information
236
+ click.echo("\n" + "=" * 40 + " Memory Information " + "=" * 40)
237
+ if system == "darwin":
238
+ mem_info = run_command("vm_stat")
239
+ click.echo(parse_vm_stat(mem_info))
240
+ elif system == "linux":
241
+ mem_info = run_command("free -b") # Get bytes for consistent formatting
242
+ lines = mem_info.split("\n")
243
+ if len(lines) > 1:
244
+ headers = lines[0].split()
245
+ values = lines[1].split()
246
+ if len(values) >= 7: # For Mem: line
247
+ total = int(values[1])
248
+ used = int(values[2])
249
+ free = int(values[3])
250
+ click.echo(
251
+ f"Total: {format_bytes(total)}\n"
252
+ f"Used: {format_bytes(used)}\n"
253
+ f"Free: {format_bytes(free)}\n"
254
+ f"Usage: {used/total*100:.1f}%"
255
+ )
256
+ elif system == "windows":
257
+ mem_info = run_command(
258
+ "wmic OS get TotalVisibleMemorySize,FreePhysicalMemory /Value"
259
+ )
260
+ if "TotalVisibleMemorySize" in mem_info and "FreePhysicalMemory" in mem_info:
261
+ try:
262
+ total = (
263
+ int(mem_info.split("TotalVisibleMemorySize=")[1].split("\n")[0])
264
+ * 1024
265
+ ) # KB to bytes
266
+ free = (
267
+ int(mem_info.split("FreePhysicalMemory=")[1].split("\n")[0]) * 1024
268
+ ) # KB to bytes
269
+ used = total - free
270
+ click.echo(
271
+ f"Total: {format_bytes(total)}\n"
272
+ f"Used: {format_bytes(used)}\n"
273
+ f"Free: {format_bytes(free)}\n"
274
+ f"Usage: {used/total*100:.1f}%"
275
+ )
276
+ except (IndexError, ValueError):
277
+ click.echo(mem_info)
278
+ else:
279
+ click.echo(mem_info)
280
+
281
+ # Disk Information
282
+ click.echo("\n" + "=" * 40 + " Disk Information " + "=" * 40)
283
+ if system == "windows":
284
+ disks = run_command("wmic logicaldisk get size,freespace,caption")
285
+ if "Caption" in disks:
286
+ lines = disks.split("\n")
287
+ result = [
288
+ "{:<5} {:<15} {:<15} {:<15} {:<10}".format(
289
+ "Drive", "Total Space", "Free Space", "Used Space", "% Used"
290
+ )
291
+ ]
292
+ for line in lines[1:]:
293
+ parts = line.strip().split()
294
+ if len(parts) >= 3:
295
+ drive = parts[0]
296
+ try:
297
+ size = int(parts[1])
298
+ free = int(parts[2])
299
+ used = size - free
300
+ pct_used = (used / size) * 100 if size > 0 else 0
301
+ result.append(
302
+ "{:<5} {:<15} {:<15} {:<15} {:.1f}%".format(
303
+ drive,
304
+ format_bytes(size),
305
+ format_bytes(free),
306
+ format_bytes(used),
307
+ pct_used,
308
+ )
309
+ )
310
+ except (ValueError, IndexError):
311
+ continue
312
+ click.echo("\n".join(result))
313
+ else:
314
+ click.echo(disks)
315
+ else:
316
+ disks = run_command(
317
+ "df -h"
318
+ if system != "windows"
319
+ else "wmic logicaldisk get size,freespace,caption"
320
+ )
321
+ click.echo(parse_df_output(disks))
322
+
323
+
324
+ # Add aliases for the command
325
+ sysinfo = system_info
326
+ si = system_info
@@ -0,0 +1,130 @@
1
+ """
2
+ Virtual environment management commands for the helper CLI.
3
+
4
+ This module provides commands to manage Python virtual environments,
5
+ including activating and deactivating them from the command line.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+
11
+ import click
12
+
13
+
14
+ def find_virtualenv(path=None):
15
+ """Find virtual environment in the given path or current directory.
16
+
17
+ Args:
18
+ path (str, optional): Path to search for virtual environment.
19
+ Defaults to current directory.
20
+
21
+ Returns:
22
+ str: Path to the activate script if found, None otherwise.
23
+ """
24
+ if path is None:
25
+ path = os.getcwd()
26
+
27
+ # Check common virtual environment directories
28
+ venv_dirs = ["venv", ".venv"]
29
+ for venv_dir in venv_dirs:
30
+ activate_script = os.path.join(path, venv_dir, "bin", "activate")
31
+ if os.path.exists(activate_script):
32
+ return activate_script
33
+
34
+ # If not found in current directory, try parent directories
35
+ parent = os.path.dirname(path)
36
+ if parent != path: # Prevent infinite recursion
37
+ return find_virtualenv(parent)
38
+
39
+ return None
40
+
41
+
42
+ def source_virtualenv(venv_path=None):
43
+ """Source a virtual environment.
44
+
45
+ Args:
46
+ venv_path (str, optional): Path to the virtual environment.
47
+ If None, search in current and parent directories.
48
+ """
49
+ if venv_path is None:
50
+ activate_script = find_virtualenv()
51
+ if not activate_script:
52
+ click.echo(
53
+ "Error: No virtual environment found in current or parent directories.",
54
+ err=True,
55
+ )
56
+ click.echo(
57
+ "Please specify the path to the virtual environment or "
58
+ "create one with 'python -m venv venv'"
59
+ )
60
+ sys.exit(1)
61
+ else:
62
+ # If path is provided, check if it's a directory or activate script
63
+ if os.path.isdir(venv_path):
64
+ # If it's a directory, look for bin/activate
65
+ activate_script = os.path.join(venv_path, "bin", "activate")
66
+ if not os.path.exists(activate_script):
67
+ click.echo(f"Error: No activate script found in {venv_path}", err=True)
68
+ sys.exit(1)
69
+ elif os.path.isfile(venv_path):
70
+ # If it's a file, use it directly
71
+ activate_script = venv_path
72
+ else:
73
+ click.echo(f"Error: {venv_path} is not a valid file or directory", err=True)
74
+ sys.exit(1)
75
+
76
+ # Get the absolute path to the activate script
77
+ activate_script = os.path.abspath(activate_script)
78
+
79
+ # Print the command to source the virtual environment
80
+ # The user needs to run this with 'eval $(h venv source [path])' or
81
+ # 'source <(h venv source [path])'
82
+ click.echo(f'source "{activate_script}"')
83
+ venv_dir = os.path.dirname(os.path.dirname(activate_script))
84
+ click.echo(f"# Virtual environment activated: {venv_dir}", err=True)
85
+
86
+
87
+ def deactivate_virtualenv():
88
+ """Print the command to deactivate the current virtual environment."""
89
+ click.echo("deactivate")
90
+ click.echo("# Virtual environment deactivated", err=True)
91
+
92
+
93
+ @click.group()
94
+ def venv():
95
+ """Manage Python virtual environments (v0.1.19)."""
96
+
97
+
98
+ @venv.command()
99
+ @click.argument("path", required=False)
100
+ def source(path):
101
+ """Source a Python virtual environment.
102
+
103
+ If no path is provided, searches for a virtual environment in the current or parent directories.
104
+ Looks for 'venv' first, then '.venv'.
105
+
106
+ Usage:
107
+ h venv source [PATH] # Source the specified virtual environment or auto-detect
108
+ eval $(h venv source) # In bash/zsh to activate the virtual environment
109
+ source <(h venv source) # Alternative syntax for bash/zsh
110
+ """
111
+ source_virtualenv(path)
112
+
113
+
114
+ @venv.command()
115
+ def deactivate():
116
+ """Deactivate the current virtual environment.
117
+
118
+ Usage:
119
+ h venv deactivate # Show the deactivate command
120
+ eval $(h venv deactivate) # In bash/zsh to deactivate the virtual environment
121
+ """
122
+ deactivate_virtualenv()
123
+
124
+
125
+ # Add short aliases
126
+ s = source
127
+ d = deactivate
128
+
129
+ # Add the commands to the module
130
+ __all__ = ["venv", "source", "deactivate", "s", "d"]
helper/main.py CHANGED
@@ -1,38 +1,51 @@
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 . import __version__
6
+ from .commands import (
7
+ internal_ip,
8
+ public_ip,
9
+ arch,
10
+ nixos,
11
+ docker,
12
+ speed,
13
+ system_info,
14
+ venv,
15
+ file,
16
+ )
17
+
5
18
 
6
19
  class VerbosityCommand(click.Command):
7
20
  def parse_args(self, ctx, args):
8
21
  # Initialize verbosity from context if it exists
9
22
  ctx.ensure_object(dict)
10
- verbose = ctx.obj.get('verbosity', 0)
11
-
23
+ verbose = ctx.obj.get("verbosity", 0)
24
+
12
25
  # Process args for verbosity flags
13
26
  new_args = []
14
27
  i = 0
15
28
  while i < len(args):
16
29
  arg = args[i]
17
- if arg == '--verbose':
30
+ if arg == "--verbose":
18
31
  verbose += 1
19
- elif arg.startswith('-v'):
20
- verbose += arg.count('v')
32
+ elif arg.startswith("-v"):
33
+ verbose += arg.count("v")
21
34
  else:
22
35
  new_args.append(arg)
23
36
  i += 1
24
-
37
+
25
38
  # Update verbosity in context
26
- ctx.obj['verbosity'] = verbose
27
-
39
+ ctx.obj["verbosity"] = verbose
40
+
28
41
  # Set up logging
29
42
  self._setup_logging(verbose)
30
-
43
+
31
44
  # Continue with normal argument parsing
32
45
  return super().parse_args(ctx, new_args)
33
-
46
+
34
47
  def _setup_logging(self, verbose):
35
- logger = logging.getLogger('docker-helper')
48
+ logger = logging.getLogger("docker-helper")
36
49
  if verbose >= 3:
37
50
  logger.setLevel(logging.DEBUG)
38
51
  elif verbose == 2:
@@ -42,29 +55,30 @@ class VerbosityCommand(click.Command):
42
55
  else:
43
56
  logger.setLevel(logging.ERROR)
44
57
 
58
+
45
59
  class VerbosityGroup(click.Group):
46
60
  def make_context(self, info_name, args, parent=None, **extra):
47
61
  # Pre-process args to find verbosity flags
48
62
  verbose = 0
49
63
  processed_args = []
50
-
64
+
51
65
  for arg in args:
52
- if arg == '--verbose':
66
+ if arg == "--verbose":
53
67
  verbose += 1
54
- elif arg.startswith('-v'):
55
- verbose += arg.count('v')
68
+ elif arg.startswith("-v"):
69
+ verbose += arg.count("v")
56
70
  else:
57
71
  processed_args.append(arg)
58
-
72
+
59
73
  # Create context with processed args
60
74
  ctx = super().make_context(info_name, processed_args, parent=parent, **extra)
61
-
75
+
62
76
  # Set verbosity in context
63
77
  ctx.ensure_object(dict)
64
- ctx.obj['verbosity'] = verbose
65
-
78
+ ctx.obj["verbosity"] = verbose
79
+
66
80
  # Set up logging
67
- logger = logging.getLogger('docker-helper')
81
+ logger = logging.getLogger("docker-helper")
68
82
  if verbose >= 3:
69
83
  logger.setLevel(logging.DEBUG)
70
84
  elif verbose == 2:
@@ -73,24 +87,48 @@ class VerbosityGroup(click.Group):
73
87
  logger.setLevel(logging.WARNING)
74
88
  else:
75
89
  logger.setLevel(logging.ERROR)
76
-
90
+
77
91
  return ctx
78
92
 
79
- @click.group(cls=VerbosityGroup)
93
+
94
+ @click.group(
95
+ cls=VerbosityGroup,
96
+ context_settings={
97
+ "help_option_names": ["-h", "--help"],
98
+ "token_normalize_func": lambda x: "helper" if x == "h" else x,
99
+ },
100
+ )
101
+ @click.version_option(__version__, "-V", "--version", message="%(prog)s version %(version)s")
80
102
  def cli():
81
- """Helper CLI - quick system info"""
103
+ """Helper CLI - quick system info (v{})
104
+
105
+ You can use 'h' as a shortcut for 'helper' command.
106
+ Example: h docker ps
107
+
108
+ For detailed help on a specific command, use: helper <command> --help
109
+ """.format(__version__)
82
110
  # Set up basic logging
83
111
  logging.basicConfig(
84
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
85
- level=logging.ERROR
112
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
113
+ level=logging.ERROR,
86
114
  )
87
115
 
116
+
88
117
  # Register all commands
89
118
  cli.add_command(internal_ip.internal_ip)
90
119
  cli.add_command(public_ip.public_ip)
91
120
  cli.add_command(arch.arch)
92
121
  cli.add_command(nixos.nixos, name="nixos")
93
122
  cli.add_command(docker.docker, name="docker")
123
+ cli.add_command(speed.speed, name="speed")
124
+ cli.add_command(speed.speed, name="sp")
125
+ cli.add_command(system_info.system_info, name="system-info")
126
+ cli.add_command(system_info.system_info, name="sysinfo")
127
+ cli.add_command(system_info.system_info, name="si")
128
+ cli.add_command(venv.venv, name="v")
129
+ cli.add_command(file.file(), name="file")
130
+ cli.add_command(file.file(), name="f")
131
+
94
132
 
95
133
  @cli.command()
96
134
  @click.pass_context
@@ -103,7 +141,10 @@ def all(ctx):
103
141
  click.echo("\n=== Architecture ===")
104
142
  ctx.invoke(arch.arch)
105
143
  click.echo("\n=== NixOS ===")
106
- ctx.invoke(nixos.nixos, 'version')
144
+ ctx.invoke(nixos.nixos, "version")
145
+ click.echo("\n=== System Info ===")
146
+ ctx.invoke(system_info.system_info)
147
+
107
148
 
108
149
  if __name__ == "__main__":
109
150
  cli()
helper/table.py ADDED
@@ -0,0 +1,29 @@
1
+ import click
2
+ import json
3
+ import yaml
4
+ from tabulate import tabulate
5
+
6
+ data = [{"name": "Huy", "age": 23}, {"name": "An", "age": 25}]
7
+
8
+
9
+ @click.command()
10
+ @click.option(
11
+ "--format",
12
+ "-f",
13
+ type=click.Choice(["json", "yaml", "table", "text"]),
14
+ default="table",
15
+ )
16
+ def show(format):
17
+ if format == "json":
18
+ click.echo(json.dumps(data, indent=2))
19
+ elif format == "yaml":
20
+ click.echo(yaml.dump(data))
21
+ elif format == "table":
22
+ click.echo(tabulate(data, headers="keys"))
23
+ else:
24
+ for item in data:
25
+ click.echo(f"{item['name']} - {item['age']}")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ show()