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 +4 -0
- ccmd/cli/__init__.py +1 -0
- ccmd/cli/editor.py +216 -0
- ccmd/cli/install.py +367 -0
- ccmd/cli/interactive.py +863 -0
- ccmd/cli/main.py +1151 -0
- ccmd/cli/ssh_manager.py +285 -0
- ccmd/core/__init__.py +1 -0
- ccmd/core/auth.py +607 -0
- ccmd/core/executor.py +583 -0
- ccmd/core/parser.py +251 -0
- ccmd/core/registry.py +319 -0
- ccmd/core/rollback.py +286 -0
- ccmd/core/security.py +324 -0
- ccmd/core/system_check.py +129 -0
- ccmd-1.1.4.dist-info/METADATA +452 -0
- ccmd-1.1.4.dist-info/RECORD +22 -0
- ccmd-1.1.4.dist-info/WHEEL +5 -0
- ccmd-1.1.4.dist-info/entry_points.txt +2 -0
- ccmd-1.1.4.dist-info/licenses/LICENSE +21 -0
- ccmd-1.1.4.dist-info/top_level.txt +1 -0
- commands.yaml +121 -0
ccmd/__init__.py
ADDED
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
|