ccmd 1.1.4__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.
ccmd/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """CCMD - Cross-platform Command Manager"""
2
+
3
+ __version__ = "1.1.4"
4
+ __author__ = "De Catalyst"
ccmd/cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """CLI modules for CCMD"""
ccmd/cli/editor.py ADDED
@@ -0,0 +1,216 @@
1
+ """Interactive command editor for CCMD"""
2
+
3
+ import sys
4
+ from typing import Optional
5
+
6
+ from ccmd.core.registry import CommandRegistry
7
+ from ccmd.core.executor import sanitize_input
8
+
9
+
10
+ def launch_editor() -> int:
11
+ """
12
+ Launch interactive command editor
13
+
14
+ Returns:
15
+ Exit code
16
+ """
17
+ print("=" * 60)
18
+ print("CCMD Command Editor")
19
+ print("=" * 60)
20
+
21
+ registry = CommandRegistry()
22
+
23
+ while True:
24
+ print("\nOptions:")
25
+ print(" 1. List all commands")
26
+ print(" 2. Add new command")
27
+ print(" 3. Edit existing command")
28
+ print(" 4. Delete command")
29
+ print(" 5. View command details")
30
+ print(" 6. Save and exit")
31
+ print(" 7. Exit without saving")
32
+
33
+ choice = input("\nEnter choice (1-7): ").strip()
34
+
35
+ if choice == '1':
36
+ list_commands(registry)
37
+ elif choice == '2':
38
+ add_command(registry)
39
+ elif choice == '3':
40
+ edit_command(registry)
41
+ elif choice == '4':
42
+ delete_command(registry)
43
+ elif choice == '5':
44
+ view_command(registry)
45
+ elif choice == '6':
46
+ try:
47
+ registry.save_commands()
48
+ print("\n✓ Commands saved successfully!")
49
+ return 0
50
+ except Exception as e:
51
+ print(f"\n✗ Failed to save commands: {e}")
52
+ return 1
53
+ elif choice == '7':
54
+ print("\nExiting without saving...")
55
+ return 0
56
+ else:
57
+ print("\n✗ Invalid choice. Please enter 1-7.")
58
+
59
+
60
+ def list_commands(registry: CommandRegistry):
61
+ """List all commands"""
62
+ commands = registry.list_commands()
63
+
64
+ if not commands:
65
+ print("\nNo commands registered.")
66
+ return
67
+
68
+ print("\nRegistered Commands:")
69
+ print("-" * 60)
70
+ for cmd in sorted(commands):
71
+ cmd_def = registry.get_command(cmd)
72
+ desc = cmd_def.get('description', 'No description')
73
+ cmd_type = cmd_def.get('type', 'unknown')
74
+ print(f" {cmd:15} [{cmd_type:10}] - {desc}")
75
+
76
+
77
+ def add_command(registry: CommandRegistry):
78
+ """Add a new command"""
79
+ print("\n--- Add New Command ---")
80
+
81
+ name = input("Command name: ").strip()
82
+ if not name:
83
+ print("✗ Command name cannot be empty")
84
+ return
85
+
86
+ if registry.command_exists(name):
87
+ print(f"✗ Command '{name}' already exists")
88
+ return
89
+
90
+ description = input("Description: ").strip()
91
+ action = input("Action (shell command): ").strip()
92
+
93
+ if not action:
94
+ print("✗ Action cannot be empty")
95
+ return
96
+
97
+ cmd_type = input("Type (git/system/navigation/custom) [custom]: ").strip() or "custom"
98
+
99
+ command_def = {
100
+ 'description': description,
101
+ 'action': action,
102
+ 'type': cmd_type
103
+ }
104
+
105
+ # Check if action needs parameters
106
+ if '{' in action and '}' in action:
107
+ prompt = input("Prompt for parameter: ").strip()
108
+ if prompt:
109
+ command_def['prompt'] = prompt
110
+
111
+ registry.add_command(name, command_def)
112
+ print(f"\n✓ Command '{name}' added successfully!")
113
+
114
+
115
+ def edit_command(registry: CommandRegistry):
116
+ """Edit an existing command"""
117
+ print("\n--- Edit Command ---")
118
+
119
+ commands = registry.list_commands()
120
+ if not commands:
121
+ print("No commands to edit.")
122
+ return
123
+
124
+ name = input("Command name to edit: ").strip()
125
+
126
+ if not registry.command_exists(name):
127
+ print(f"✗ Command '{name}' not found")
128
+ return
129
+
130
+ cmd_def = registry.get_command(name)
131
+
132
+ print(f"\nCurrent definition:")
133
+ print(f" Description: {cmd_def.get('description', 'N/A')}")
134
+ print(f" Action: {cmd_def.get('action', 'N/A')}")
135
+ print(f" Type: {cmd_def.get('type', 'N/A')}")
136
+ if 'prompt' in cmd_def:
137
+ print(f" Prompt: {cmd_def.get('prompt')}")
138
+
139
+ print("\nEnter new values (press Enter to keep current value):")
140
+
141
+ description = input(f"Description [{cmd_def.get('description', '')}]: ").strip()
142
+ if description:
143
+ cmd_def['description'] = description
144
+
145
+ action = input(f"Action [{cmd_def.get('action', '')}]: ").strip()
146
+ if action:
147
+ cmd_def['action'] = action
148
+
149
+ cmd_type = input(f"Type [{cmd_def.get('type', 'custom')}]: ").strip()
150
+ if cmd_type:
151
+ cmd_def['type'] = cmd_type
152
+
153
+ # Check if action needs parameters
154
+ if '{' in str(cmd_def.get('action', '')) and '}' in str(cmd_def.get('action', '')):
155
+ prompt = input(f"Prompt [{cmd_def.get('prompt', '')}]: ").strip()
156
+ if prompt:
157
+ cmd_def['prompt'] = prompt
158
+
159
+ registry.add_command(name, cmd_def)
160
+ print(f"\n✓ Command '{name}' updated successfully!")
161
+
162
+
163
+ def delete_command(registry: CommandRegistry):
164
+ """Delete a command"""
165
+ print("\n--- Delete Command ---")
166
+
167
+ commands = registry.list_commands()
168
+ if not commands:
169
+ print("No commands to delete.")
170
+ return
171
+
172
+ name = input("Command name to delete: ").strip()
173
+
174
+ if not registry.command_exists(name):
175
+ print(f"✗ Command '{name}' not found")
176
+ return
177
+
178
+ confirm = input(f"Are you sure you want to delete '{name}'? (yes/no): ").strip().lower()
179
+
180
+ if confirm == 'yes':
181
+ registry.remove_command(name)
182
+ print(f"\n✓ Command '{name}' deleted successfully!")
183
+ else:
184
+ print("Deletion cancelled.")
185
+
186
+
187
+ def view_command(registry: CommandRegistry):
188
+ """View command details"""
189
+ print("\n--- View Command Details ---")
190
+
191
+ commands = registry.list_commands()
192
+ if not commands:
193
+ print("No commands registered.")
194
+ return
195
+
196
+ name = input("Command name: ").strip()
197
+
198
+ if not registry.command_exists(name):
199
+ print(f"✗ Command '{name}' not found")
200
+ return
201
+
202
+ cmd_def = registry.get_command(name)
203
+
204
+ print(f"\nCommand: {name}")
205
+ print("-" * 60)
206
+ for key, value in cmd_def.items():
207
+ if isinstance(value, dict):
208
+ print(f"{key}:")
209
+ for sub_key, sub_value in value.items():
210
+ print(f" {sub_key}: {sub_value}")
211
+ else:
212
+ print(f"{key}: {value}")
213
+
214
+
215
+ if __name__ == "__main__":
216
+ sys.exit(launch_editor())
ccmd/cli/install.py ADDED
@@ -0,0 +1,367 @@
1
+ """Installation module for CCMD"""
2
+
3
+ import sys
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Tuple
7
+
8
+ # Fix Windows console encoding for Unicode support
9
+ if sys.platform == 'win32':
10
+ try:
11
+ # Try to set console to UTF-8 mode (Windows 10+)
12
+ import ctypes
13
+ kernel32 = ctypes.windll.kernel32
14
+ kernel32.SetConsoleOutputCP(65001) # UTF-8
15
+ except:
16
+ pass # Ignore if fails
17
+
18
+ from ccmd.core.system_check import get_system_info
19
+ from ccmd.core.registry import CommandRegistry, create_default_config
20
+ from ccmd.core.rollback import RollbackManager
21
+
22
+
23
+ def install_ccmd() -> Tuple[bool, str]:
24
+ """
25
+ Install CCMD by adding command aliases to shell configuration
26
+
27
+ Returns:
28
+ Tuple of (success, message)
29
+ """
30
+ import subprocess
31
+
32
+ system_info = get_system_info()
33
+ rollback = RollbackManager()
34
+
35
+ # Get installation directory
36
+ install_dir = Path(__file__).parent.parent.parent.resolve()
37
+ run_py = install_dir / "run.py"
38
+
39
+ if not run_py.exists():
40
+ return False, f"run.py not found at {run_py}"
41
+
42
+ # Install Python dependencies from requirements.txt
43
+ requirements_file = install_dir / "requirements.txt"
44
+ if requirements_file.exists():
45
+ print("→ Installing Python dependencies...")
46
+ try:
47
+ # First try regular pip install
48
+ subprocess.run(
49
+ [sys.executable, "-m", "pip", "install", "-q", "-r", str(requirements_file)],
50
+ check=True,
51
+ capture_output=True
52
+ )
53
+ print("✓ Dependencies installed successfully")
54
+ except subprocess.CalledProcessError as e:
55
+ # If regular install fails, try with --break-system-packages for externally-managed systems
56
+ try:
57
+ subprocess.run(
58
+ [sys.executable, "-m", "pip", "install", "--break-system-packages", "-q", "-r", str(requirements_file)],
59
+ check=True,
60
+ capture_output=True
61
+ )
62
+ print("✓ Dependencies installed successfully")
63
+ except subprocess.CalledProcessError:
64
+ # If both attempts fail, show warning (dependencies might already be installed)
65
+ print(f"⚠ Warning: Could not install dependencies automatically")
66
+ print(" Dependencies may already be installed, or you may need to run:")
67
+ print(f" pip3 install -r {requirements_file}")
68
+
69
+ # Create default commands.yaml if it doesn't exist
70
+ commands_yaml = install_dir / "commands.yaml"
71
+ if not commands_yaml.exists():
72
+ try:
73
+ create_default_config(commands_yaml)
74
+ except Exception as e:
75
+ return False, f"Failed to create commands.yaml: {e}"
76
+
77
+ # Load commands
78
+ registry = CommandRegistry(commands_yaml)
79
+ commands = registry.list_commands()
80
+
81
+ if not commands:
82
+ return False, "No commands found in commands.yaml"
83
+
84
+ # Load disabled commands
85
+ disabled_config_path = install_dir / ".disabled_commands"
86
+ disabled_commands = set()
87
+
88
+ if disabled_config_path.exists():
89
+ with open(disabled_config_path, 'r') as f:
90
+ disabled_commands = set(line.strip() for line in f if line.strip())
91
+
92
+ # Filter out disabled commands
93
+ commands = [cmd for cmd in commands if cmd not in disabled_commands]
94
+
95
+ if not commands:
96
+ return False, "All commands are disabled"
97
+
98
+ # Get shell RC file
99
+ rc_file = system_info.shell_rc_file
100
+ if not rc_file:
101
+ return False, f"Could not determine shell RC file for {system_info.shell_type}"
102
+
103
+ # Generate shell integration code
104
+ if system_info.shell_type in ['bash', 'zsh']:
105
+ integration_code = generate_bash_integration(install_dir, run_py, commands, registry)
106
+ elif system_info.shell_type == 'fish':
107
+ integration_code = generate_fish_integration(install_dir, run_py, commands)
108
+ elif system_info.shell_type == 'powershell':
109
+ integration_code = generate_powershell_integration(install_dir, run_py, commands, registry)
110
+ else:
111
+ return False, f"Unsupported shell: {system_info.shell_type}"
112
+
113
+ # Add integration code to RC file
114
+ def edit_rc_file(content: str) -> str:
115
+ # Check if CCMD is already installed
116
+ if "# CCMD Integration" in content:
117
+ # Remove old integration
118
+ lines = content.split('\n')
119
+ new_lines = []
120
+ skip = False
121
+
122
+ for line in lines:
123
+ if "# CCMD Integration - Start" in line:
124
+ skip = True
125
+ elif "# CCMD Integration - End" in line:
126
+ skip = False
127
+ continue
128
+ elif not skip:
129
+ new_lines.append(line)
130
+
131
+ content = '\n'.join(new_lines)
132
+
133
+ # Add new integration
134
+ if not content.endswith('\n'):
135
+ content += '\n'
136
+
137
+ content += '\n' + integration_code + '\n'
138
+ return content
139
+
140
+ # Safely edit RC file with backup
141
+ success, message = rollback.safe_file_edit(
142
+ rc_file,
143
+ edit_rc_file,
144
+ "CCMD installation"
145
+ )
146
+
147
+ if success:
148
+ # Platform-specific reload instructions
149
+ if system_info.shell_type == 'powershell':
150
+ reload_cmd = ". $PROFILE"
151
+ elif system_info.shell_type == 'fish':
152
+ reload_cmd = f"source {rc_file}"
153
+ else: # bash/zsh
154
+ reload_cmd = f"source {rc_file}"
155
+
156
+ return True, f"CCMD installed successfully! Restart your shell or run: {reload_cmd}"
157
+ else:
158
+ return False, message
159
+
160
+
161
+ def generate_bash_integration(install_dir: Path, run_py: Path, commands: list, registry) -> str:
162
+ """Generate Bash/Zsh integration code"""
163
+ python_exec = sys.executable
164
+
165
+ code = "# CCMD Integration - Start\n"
166
+ code += f"export CCMD_HOME=\"{install_dir}\"\n\n"
167
+
168
+ # Add error flag to show message only once
169
+ code += "export _CCMD_ERROR_SHOWN=0\n\n"
170
+
171
+ # Helper function to check CCMD availability
172
+ code += """_ccmd_check() {
173
+ if [ ! -f "$CCMD_HOME/run.py" ]; then
174
+ if [ "$_CCMD_ERROR_SHOWN" -eq 0 ]; then
175
+ echo "⚠ CCMD not found at $CCMD_HOME"
176
+ echo "→ Please reinstall CCMD or run: python3 /path/to/ccmd/run.py --uninstall"
177
+ echo "→ To silence this message, remove CCMD integration from your shell config"
178
+ export _CCMD_ERROR_SHOWN=1
179
+ fi
180
+ return 1
181
+ fi
182
+ return 0
183
+ }
184
+
185
+ """
186
+
187
+ # Create function for each command
188
+ for cmd in commands:
189
+ # Check if command is interactive
190
+ cmd_def = registry.get_command(cmd)
191
+ is_interactive = cmd_def.get('interactive', False)
192
+
193
+ if is_interactive:
194
+ # Interactive commands: run directly without output capture
195
+ code += f"""{cmd}() {{
196
+ _ccmd_check || return 1
197
+ "{python_exec}" "$CCMD_HOME/run.py" {cmd} "$@"
198
+ return $?
199
+ }}
200
+
201
+ """
202
+ else:
203
+ # Non-interactive commands: capture output for cd handling
204
+ code += f"""{cmd}() {{
205
+ _ccmd_check || return 1
206
+
207
+ local output
208
+ output=$("{python_exec}" "$CCMD_HOME/run.py" {cmd} "$@")
209
+ local exit_code=$?
210
+
211
+ # Check if output is a cd command
212
+ if [[ "$output" =~ ^cd[[:space:]] ]]; then
213
+ eval "$output"
214
+ else
215
+ echo "$output"
216
+ fi
217
+
218
+ return $exit_code
219
+ }}
220
+
221
+ """
222
+
223
+ code += "# CCMD Integration - End\n"
224
+ return code
225
+
226
+
227
+ def generate_fish_integration(install_dir: Path, run_py: Path, commands: list) -> str:
228
+ """Generate Fish shell integration code"""
229
+ python_exec = sys.executable
230
+
231
+ code = "# CCMD Integration - Start\n"
232
+ code += f"set -gx CCMD_HOME \"{install_dir}\"\n"
233
+ code += "set -gx _CCMD_ERROR_SHOWN 0\n\n"
234
+
235
+ # Helper function to check CCMD availability
236
+ code += """function _ccmd_check
237
+ if not test -f "$CCMD_HOME/run.py"
238
+ if test "$_CCMD_ERROR_SHOWN" -eq 0
239
+ echo "⚠ CCMD not found at $CCMD_HOME"
240
+ echo "→ Please reinstall CCMD or run: python3 /path/to/ccmd/run.py --uninstall"
241
+ echo "→ To silence this message, remove CCMD integration from your shell config"
242
+ set -gx _CCMD_ERROR_SHOWN 1
243
+ end
244
+ return 1
245
+ end
246
+ return 0
247
+ end
248
+
249
+ """
250
+
251
+ for cmd in commands:
252
+ code += f"""function {cmd}
253
+ _ccmd_check; or return 1
254
+
255
+ set output ({python_exec} "$CCMD_HOME/run.py" {cmd} $argv)
256
+
257
+ if string match -q -r '^cd ' "$output"
258
+ eval "$output"
259
+ else
260
+ echo "$output"
261
+ end
262
+ end
263
+
264
+ """
265
+
266
+ code += "# CCMD Integration - End\n"
267
+ return code
268
+
269
+
270
+ def generate_powershell_integration(install_dir: Path, run_py: Path, commands: list, registry) -> str:
271
+ """Generate PowerShell integration code"""
272
+ python_exec = sys.executable
273
+
274
+ code = "# CCMD Integration - Start\n"
275
+ code += f"$env:CCMD_HOME = \"{install_dir}\"\n"
276
+ code += "$global:_CCMD_ERROR_SHOWN = 0\n\n"
277
+
278
+ # Helper function to check CCMD availability
279
+ code += """function _ccmd_check {
280
+ if (-not (Test-Path "$env:CCMD_HOME/run.py")) {
281
+ if ($global:_CCMD_ERROR_SHOWN -eq 0) {
282
+ Write-Host "⚠ CCMD not found at $env:CCMD_HOME" -ForegroundColor Yellow
283
+ Write-Host "→ Please reinstall CCMD or run: python3 /path/to/ccmd/run.py --uninstall"
284
+ Write-Host "→ To silence this message, remove CCMD integration from your PowerShell profile"
285
+ $global:_CCMD_ERROR_SHOWN = 1
286
+ }
287
+ return $false
288
+ }
289
+ return $true
290
+ }
291
+
292
+ """
293
+
294
+ # Create function for each command
295
+ for cmd in commands:
296
+ # Check if command is interactive
297
+ cmd_def = registry.get_command(cmd)
298
+ is_interactive = cmd_def.get('interactive', False)
299
+
300
+ if is_interactive:
301
+ # Interactive commands: run directly without output capture
302
+ code += f"""function {cmd} {{
303
+ if (-not (_ccmd_check)) {{ return }}
304
+ & "{python_exec}" "$env:CCMD_HOME/run.py" {cmd} $args
305
+ }}
306
+
307
+ """
308
+ else:
309
+ # Non-interactive commands: capture output for cd handling
310
+ code += f"""function {cmd} {{
311
+ if (-not (_ccmd_check)) {{ return }}
312
+
313
+ $output = & "{python_exec}" "$env:CCMD_HOME/run.py" {cmd} $args
314
+
315
+ if ($output -match '^cd ') {{
316
+ Invoke-Expression $output
317
+ }} else {{
318
+ Write-Output $output
319
+ }}
320
+ }}
321
+
322
+ """
323
+
324
+ code += "# CCMD Integration - End\n"
325
+ return code
326
+
327
+
328
+ def uninstall_ccmd() -> Tuple[bool, str]:
329
+ """
330
+ Uninstall CCMD by removing integration code
331
+
332
+ Returns:
333
+ Tuple of (success, message)
334
+ """
335
+ system_info = get_system_info()
336
+ rollback = RollbackManager()
337
+
338
+ rc_file = system_info.shell_rc_file
339
+ if not rc_file or not rc_file.exists():
340
+ return False, "Shell RC file not found"
341
+
342
+ def remove_integration(content: str) -> str:
343
+ lines = content.split('\n')
344
+ new_lines = []
345
+ skip = False
346
+
347
+ for line in lines:
348
+ if "# CCMD Integration - Start" in line:
349
+ skip = True
350
+ elif "# CCMD Integration - End" in line:
351
+ skip = False
352
+ continue
353
+ elif not skip:
354
+ new_lines.append(line)
355
+
356
+ return '\n'.join(new_lines)
357
+
358
+ success, message = rollback.safe_file_edit(
359
+ rc_file,
360
+ remove_integration,
361
+ "CCMD uninstallation"
362
+ )
363
+
364
+ if success:
365
+ return True, "CCMD uninstalled successfully"
366
+ else:
367
+ return False, message