comfygit 0.3.1__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.
- comfygit-0.3.1.dist-info/METADATA +654 -0
- comfygit-0.3.1.dist-info/RECORD +30 -0
- comfygit-0.3.1.dist-info/WHEEL +4 -0
- comfygit-0.3.1.dist-info/entry_points.txt +3 -0
- comfygit-0.3.1.dist-info/licenses/LICENSE.txt +661 -0
- comfygit_cli/__init__.py +12 -0
- comfygit_cli/__main__.py +6 -0
- comfygit_cli/cli.py +704 -0
- comfygit_cli/cli_utils.py +32 -0
- comfygit_cli/completers.py +239 -0
- comfygit_cli/completion_commands.py +246 -0
- comfygit_cli/env_commands.py +2701 -0
- comfygit_cli/formatters/__init__.py +5 -0
- comfygit_cli/formatters/error_formatter.py +141 -0
- comfygit_cli/global_commands.py +1806 -0
- comfygit_cli/interactive/__init__.py +1 -0
- comfygit_cli/logging/compressed_handler.py +150 -0
- comfygit_cli/logging/environment_logger.py +554 -0
- comfygit_cli/logging/log_compressor.py +101 -0
- comfygit_cli/logging/logging_config.py +97 -0
- comfygit_cli/resolution_strategies.py +89 -0
- comfygit_cli/strategies/__init__.py +1 -0
- comfygit_cli/strategies/conflict_resolver.py +113 -0
- comfygit_cli/strategies/interactive.py +843 -0
- comfygit_cli/strategies/rollback.py +40 -0
- comfygit_cli/utils/__init__.py +12 -0
- comfygit_cli/utils/civitai_errors.py +9 -0
- comfygit_cli/utils/orchestrator.py +252 -0
- comfygit_cli/utils/pagination.py +82 -0
- comfygit_cli/utils/progress.py +128 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Utility functions for ComfyGit CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from comfygit_core.factories.workspace_factory import WorkspaceFactory
|
|
7
|
+
from comfygit_core.models.exceptions import CDWorkspaceNotFoundError
|
|
8
|
+
from .logging.environment_logger import WorkspaceLogger
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from comfygit_core.core.workspace import Workspace
|
|
12
|
+
|
|
13
|
+
def get_workspace_or_exit() -> "Workspace":
|
|
14
|
+
"""Get workspace or exit with error message."""
|
|
15
|
+
try:
|
|
16
|
+
workspace = WorkspaceFactory.find()
|
|
17
|
+
# Initialize workspace logging
|
|
18
|
+
WorkspaceLogger.set_workspace_path(workspace.path)
|
|
19
|
+
return workspace
|
|
20
|
+
except CDWorkspaceNotFoundError:
|
|
21
|
+
print("✗ No workspace initialized. Run 'cg init' first.")
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
def get_workspace_optional() -> "Workspace | None":
|
|
25
|
+
"""Get workspace if it exists."""
|
|
26
|
+
try:
|
|
27
|
+
workspace = WorkspaceFactory.find()
|
|
28
|
+
# Initialize workspace logging
|
|
29
|
+
WorkspaceLogger.set_workspace_path(workspace.path)
|
|
30
|
+
return workspace
|
|
31
|
+
except CDWorkspaceNotFoundError:
|
|
32
|
+
return None
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Custom argcomplete completers for ComfyGit CLI."""
|
|
2
|
+
import argparse
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from argcomplete.io import warn
|
|
6
|
+
|
|
7
|
+
from comfygit_core.core.environment import Environment
|
|
8
|
+
from comfygit_core.core.workspace import Workspace
|
|
9
|
+
from comfygit_core.factories.workspace_factory import WorkspaceFactory
|
|
10
|
+
from comfygit_core.models.exceptions import CDWorkspaceNotFoundError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ============================================================================
|
|
14
|
+
# Shared Utilities
|
|
15
|
+
# ============================================================================
|
|
16
|
+
|
|
17
|
+
def get_workspace_safe() -> Workspace | None:
|
|
18
|
+
"""Get workspace or return None if not initialized."""
|
|
19
|
+
try:
|
|
20
|
+
return WorkspaceFactory.find()
|
|
21
|
+
except CDWorkspaceNotFoundError:
|
|
22
|
+
return None
|
|
23
|
+
except Exception as e:
|
|
24
|
+
warn(f"Error loading workspace: {e}")
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_env_from_args(parsed_args: argparse.Namespace, workspace: Workspace) -> Environment | None:
|
|
29
|
+
"""Get environment from -e flag or active environment.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
parsed_args: Parsed arguments from argparse
|
|
33
|
+
workspace: Workspace instance
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Environment instance or None
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
# Check for -e/--env flag
|
|
40
|
+
env_name = getattr(parsed_args, 'target_env', None)
|
|
41
|
+
if env_name:
|
|
42
|
+
return workspace.get_environment(env_name, auto_sync=False)
|
|
43
|
+
|
|
44
|
+
# Fall back to active environment
|
|
45
|
+
env = workspace.get_active_environment()
|
|
46
|
+
if not env:
|
|
47
|
+
warn("No active environment. Use -e or run 'cg use <env>'")
|
|
48
|
+
return env
|
|
49
|
+
except Exception as e:
|
|
50
|
+
warn(f"Error loading environment: {e}")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def filter_by_prefix(items: list[str], prefix: str) -> list[str]:
|
|
55
|
+
"""Filter items that start with the given prefix."""
|
|
56
|
+
return [item for item in items if item.startswith(prefix)]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ============================================================================
|
|
60
|
+
# Completers
|
|
61
|
+
# ============================================================================
|
|
62
|
+
|
|
63
|
+
def environment_completer(prefix: str, parsed_args: argparse.Namespace, **kwargs: Any) -> list[str]:
|
|
64
|
+
"""Complete environment names from workspace.
|
|
65
|
+
|
|
66
|
+
Used for:
|
|
67
|
+
- comfygit use <TAB>
|
|
68
|
+
- comfygit delete <TAB>
|
|
69
|
+
- comfygit -e <TAB>
|
|
70
|
+
"""
|
|
71
|
+
workspace = get_workspace_safe()
|
|
72
|
+
if not workspace:
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
envs = workspace.list_environments()
|
|
77
|
+
names = [env.name for env in envs]
|
|
78
|
+
return filter_by_prefix(names, prefix)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
warn(f"Error listing environments: {e}")
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def workflow_completer(prefix: str, parsed_args: argparse.Namespace, **kwargs: Any) -> list[str]:
|
|
85
|
+
"""Complete workflow names, prioritizing unresolved workflows.
|
|
86
|
+
|
|
87
|
+
Smart ordering:
|
|
88
|
+
1. New/modified workflows (likely need resolution)
|
|
89
|
+
2. Synced workflows
|
|
90
|
+
|
|
91
|
+
Used for:
|
|
92
|
+
- comfygit workflow resolve <TAB>
|
|
93
|
+
"""
|
|
94
|
+
workspace = get_workspace_safe()
|
|
95
|
+
if not workspace:
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
env = get_env_from_args(parsed_args, workspace)
|
|
99
|
+
if not env:
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
workflows = env.list_workflows()
|
|
104
|
+
|
|
105
|
+
# Build candidates with smart ordering
|
|
106
|
+
candidates = []
|
|
107
|
+
|
|
108
|
+
# Priority 1: Unresolved workflows (new/modified)
|
|
109
|
+
candidates.extend(workflows.new)
|
|
110
|
+
candidates.extend(workflows.modified)
|
|
111
|
+
|
|
112
|
+
# Priority 2: Synced workflows
|
|
113
|
+
candidates.extend(workflows.synced)
|
|
114
|
+
|
|
115
|
+
# Remove .json extension and filter by prefix
|
|
116
|
+
names = [name.replace('.json', '') for name in candidates]
|
|
117
|
+
return filter_by_prefix(names, prefix)
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
warn(f"Error listing workflows: {e}")
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def installed_node_completer(prefix: str, parsed_args: argparse.Namespace, **kwargs: Any) -> list[str]:
|
|
125
|
+
"""Complete installed node names.
|
|
126
|
+
|
|
127
|
+
Used for:
|
|
128
|
+
- comfygit node remove <TAB>
|
|
129
|
+
- comfygit node update <TAB>
|
|
130
|
+
"""
|
|
131
|
+
workspace = get_workspace_safe()
|
|
132
|
+
if not workspace:
|
|
133
|
+
return []
|
|
134
|
+
|
|
135
|
+
env = get_env_from_args(parsed_args, workspace)
|
|
136
|
+
if not env:
|
|
137
|
+
return []
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
nodes = env.list_nodes()
|
|
141
|
+
# Use registry_id if available, otherwise fall back to name
|
|
142
|
+
names = [node.registry_id or node.name for node in nodes]
|
|
143
|
+
return filter_by_prefix(names, prefix)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
warn(f"Error listing nodes: {e}")
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def branch_completer(prefix: str, parsed_args: argparse.Namespace, **kwargs: Any) -> list[str]:
|
|
150
|
+
"""Complete branch names from environment.
|
|
151
|
+
|
|
152
|
+
Returns only branch names for commands that accept branches.
|
|
153
|
+
|
|
154
|
+
Used for:
|
|
155
|
+
- cg switch <TAB>
|
|
156
|
+
"""
|
|
157
|
+
workspace = get_workspace_safe()
|
|
158
|
+
if not workspace:
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
env = get_env_from_args(parsed_args, workspace)
|
|
162
|
+
if not env:
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Get branches (returns list of (name, is_current) tuples)
|
|
167
|
+
branches = env.list_branches()
|
|
168
|
+
names = [name for name, _ in branches]
|
|
169
|
+
return filter_by_prefix(names, prefix)
|
|
170
|
+
|
|
171
|
+
except Exception as e:
|
|
172
|
+
warn(f"Error loading branches: {e}")
|
|
173
|
+
return []
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def commit_hash_completer(prefix: str, parsed_args: argparse.Namespace, **kwargs: Any) -> list[str]:
|
|
177
|
+
"""Complete commit hashes from environment history.
|
|
178
|
+
|
|
179
|
+
Returns only short commit hashes for clean tab completion.
|
|
180
|
+
Users can run 'cg log' to see commit messages.
|
|
181
|
+
|
|
182
|
+
Used for:
|
|
183
|
+
- cg reset <TAB>
|
|
184
|
+
"""
|
|
185
|
+
workspace = get_workspace_safe()
|
|
186
|
+
if not workspace:
|
|
187
|
+
return []
|
|
188
|
+
|
|
189
|
+
env = get_env_from_args(parsed_args, workspace)
|
|
190
|
+
if not env:
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
# Get recent commits (50 should cover most use cases)
|
|
195
|
+
history = env.get_commit_history(limit=50)
|
|
196
|
+
|
|
197
|
+
# Return only hashes for clean completion
|
|
198
|
+
hashes = [commit['hash'] for commit in history]
|
|
199
|
+
return filter_by_prefix(hashes, prefix)
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
warn(f"Error loading commits: {e}")
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def ref_completer(prefix: str, parsed_args: argparse.Namespace, **kwargs: Any) -> list[str]:
|
|
207
|
+
"""Complete git refs (branches and commits) from environment.
|
|
208
|
+
|
|
209
|
+
Returns branches first (most common use case), then recent commit hashes.
|
|
210
|
+
This provides comprehensive completion for commands like checkout that
|
|
211
|
+
accept both branches and commits.
|
|
212
|
+
|
|
213
|
+
Used for:
|
|
214
|
+
- cg checkout <TAB>
|
|
215
|
+
"""
|
|
216
|
+
workspace = get_workspace_safe()
|
|
217
|
+
if not workspace:
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
env = get_env_from_args(parsed_args, workspace)
|
|
221
|
+
if not env:
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
candidates = []
|
|
226
|
+
|
|
227
|
+
# Priority 1: Branches (most common for checkout)
|
|
228
|
+
branches = env.list_branches()
|
|
229
|
+
candidates.extend([name for name, _ in branches])
|
|
230
|
+
|
|
231
|
+
# Priority 2: Recent commits
|
|
232
|
+
history = env.get_commit_history(limit=50)
|
|
233
|
+
candidates.extend([commit['hash'] for commit in history])
|
|
234
|
+
|
|
235
|
+
return filter_by_prefix(candidates, prefix)
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
warn(f"Error loading refs: {e}")
|
|
239
|
+
return []
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Commands for managing shell completion setup."""
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CompletionCommands:
|
|
10
|
+
"""Handle shell completion installation and management."""
|
|
11
|
+
|
|
12
|
+
COMMANDS = ['comfygit', 'cg']
|
|
13
|
+
COMPLETION_COMMENT = "# ComfyGit tab completion"
|
|
14
|
+
ZSH_INIT_CHECK = 'if ! command -v compdef &> /dev/null; then'
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def _completion_lines(cls):
|
|
18
|
+
"""Generate completion lines for all command aliases."""
|
|
19
|
+
return [f'eval "$(register-python-argcomplete {cmd})"' for cmd in cls.COMMANDS]
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def _zsh_compinit_block():
|
|
23
|
+
"""Generate zsh compinit initialization block."""
|
|
24
|
+
return [
|
|
25
|
+
"# Initialize zsh completion system if not already loaded",
|
|
26
|
+
"if ! command -v compdef &> /dev/null; then",
|
|
27
|
+
" autoload -Uz compinit",
|
|
28
|
+
" compinit",
|
|
29
|
+
"fi",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _detect_shell():
|
|
34
|
+
"""Detect the user's shell and return shell name and config file path."""
|
|
35
|
+
shell = os.environ.get('SHELL', '')
|
|
36
|
+
|
|
37
|
+
if 'bash' in shell:
|
|
38
|
+
config_file = Path.home() / '.bashrc'
|
|
39
|
+
return 'bash', config_file
|
|
40
|
+
elif 'zsh' in shell:
|
|
41
|
+
config_file = Path.home() / '.zshrc'
|
|
42
|
+
return 'zsh', config_file
|
|
43
|
+
else:
|
|
44
|
+
return None, None
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _check_argcomplete_available():
|
|
48
|
+
"""Check if register-python-argcomplete is available in PATH."""
|
|
49
|
+
return shutil.which('register-python-argcomplete') is not None
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _install_argcomplete():
|
|
53
|
+
"""Install argcomplete globally using uv tool."""
|
|
54
|
+
try:
|
|
55
|
+
print("📦 Installing argcomplete globally...")
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
['uv', 'tool', 'install', 'argcomplete'],
|
|
58
|
+
capture_output=True,
|
|
59
|
+
text=True,
|
|
60
|
+
check=True
|
|
61
|
+
)
|
|
62
|
+
return True
|
|
63
|
+
except subprocess.CalledProcessError as e:
|
|
64
|
+
print(f"✗ Failed to install argcomplete: {e.stderr}")
|
|
65
|
+
return False
|
|
66
|
+
except FileNotFoundError:
|
|
67
|
+
print("✗ 'uv' command not found. Please install uv first.")
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def _is_completion_installed(cls, config_file):
|
|
72
|
+
"""Check if completion is already installed in config file."""
|
|
73
|
+
if not config_file.exists():
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
content = config_file.read_text()
|
|
77
|
+
return cls.COMPLETION_COMMENT in content and all(line in content for line in cls._completion_lines())
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def _add_completion_to_config(cls, shell, config_file):
|
|
81
|
+
"""Add completion lines to shell config file."""
|
|
82
|
+
# Ensure file exists
|
|
83
|
+
config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
config_file.touch(exist_ok=True)
|
|
85
|
+
|
|
86
|
+
# Read current content
|
|
87
|
+
content = config_file.read_text()
|
|
88
|
+
|
|
89
|
+
# Add completion lines at the end
|
|
90
|
+
if content and not content.endswith('\n'):
|
|
91
|
+
content += '\n'
|
|
92
|
+
|
|
93
|
+
content += f'\n{cls.COMPLETION_COMMENT}\n'
|
|
94
|
+
|
|
95
|
+
# Add zsh compinit initialization if needed
|
|
96
|
+
if shell == 'zsh':
|
|
97
|
+
for line in cls._zsh_compinit_block():
|
|
98
|
+
content += f'{line}\n'
|
|
99
|
+
content += '\n'
|
|
100
|
+
|
|
101
|
+
for line in cls._completion_lines():
|
|
102
|
+
content += f'{line}\n'
|
|
103
|
+
|
|
104
|
+
# Write back
|
|
105
|
+
config_file.write_text(content)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def _remove_completion_from_config(cls, config_file):
|
|
109
|
+
"""Remove completion lines from shell config file."""
|
|
110
|
+
if not config_file.exists():
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
lines = config_file.read_text().splitlines(keepends=True)
|
|
114
|
+
new_lines = []
|
|
115
|
+
in_block = False
|
|
116
|
+
|
|
117
|
+
for line in lines:
|
|
118
|
+
# Start of completion block
|
|
119
|
+
if cls.COMPLETION_COMMENT in line:
|
|
120
|
+
in_block = True
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
# Inside block - skip all lines until we find a non-completion line
|
|
124
|
+
if in_block:
|
|
125
|
+
# Check if this is part of our block (init, completion, or empty lines)
|
|
126
|
+
stripped = line.strip()
|
|
127
|
+
is_our_line = (
|
|
128
|
+
not stripped # empty line
|
|
129
|
+
or '# Initialize zsh completion system' in line # our specific comment
|
|
130
|
+
or cls.ZSH_INIT_CHECK in line # zsh init check
|
|
131
|
+
or 'autoload -Uz compinit' in line
|
|
132
|
+
or stripped == 'compinit'
|
|
133
|
+
or stripped == 'fi'
|
|
134
|
+
or any(comp in line for comp in cls._completion_lines())
|
|
135
|
+
)
|
|
136
|
+
if is_our_line:
|
|
137
|
+
continue
|
|
138
|
+
else:
|
|
139
|
+
# Non-completion line found, exit block
|
|
140
|
+
in_block = False
|
|
141
|
+
|
|
142
|
+
new_lines.append(line)
|
|
143
|
+
|
|
144
|
+
config_file.write_text(''.join(new_lines))
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
def install(self, args):
|
|
148
|
+
"""Install shell completion for the current user."""
|
|
149
|
+
shell, config_file = self._detect_shell()
|
|
150
|
+
|
|
151
|
+
if not shell:
|
|
152
|
+
print("✗ Could not detect shell (bash or zsh)")
|
|
153
|
+
print(" Your SHELL environment variable is:", os.environ.get('SHELL', 'not set'))
|
|
154
|
+
print("\nManual setup:")
|
|
155
|
+
print(" Add these lines to your shell config file:")
|
|
156
|
+
for line in self._completion_lines():
|
|
157
|
+
print(f" {line}")
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
|
|
160
|
+
# Check if already installed
|
|
161
|
+
if self._is_completion_installed(config_file):
|
|
162
|
+
print(f"✓ Tab completion is already installed in {config_file}")
|
|
163
|
+
print(f"\nTo activate in current shell, run:")
|
|
164
|
+
print(f" source {config_file}")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Check if argcomplete is available
|
|
168
|
+
if not self._check_argcomplete_available():
|
|
169
|
+
print("⚠️ argcomplete not found in PATH")
|
|
170
|
+
print(" Installing argcomplete as a uv tool...")
|
|
171
|
+
if not self._install_argcomplete():
|
|
172
|
+
print("\n✗ Could not install argcomplete automatically")
|
|
173
|
+
print("\nManual installation:")
|
|
174
|
+
print(" uv tool install argcomplete")
|
|
175
|
+
print("\nThen run:")
|
|
176
|
+
print(" cg completion install")
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
print("✓ argcomplete installed")
|
|
179
|
+
|
|
180
|
+
# Install completion
|
|
181
|
+
try:
|
|
182
|
+
self._add_completion_to_config(shell, config_file)
|
|
183
|
+
print(f"\n✓ Tab completion installed successfully!")
|
|
184
|
+
print(f"\nAdded to: {config_file}")
|
|
185
|
+
print(f"\nTo activate in current shell, run:")
|
|
186
|
+
print(f" source {config_file}")
|
|
187
|
+
print(f"\nOr start a new terminal session.")
|
|
188
|
+
print(f"\nTry it out:")
|
|
189
|
+
print(f" cg stat<TAB>")
|
|
190
|
+
print(f" cg use <TAB>")
|
|
191
|
+
print(f" cg workflow resolve <TAB>")
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(f"✗ Failed to install completion: {e}")
|
|
194
|
+
sys.exit(1)
|
|
195
|
+
|
|
196
|
+
def uninstall(self, args):
|
|
197
|
+
"""Remove shell completion from config."""
|
|
198
|
+
shell, config_file = self._detect_shell()
|
|
199
|
+
|
|
200
|
+
if not shell:
|
|
201
|
+
print("✗ Could not detect shell (bash or zsh)")
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
if not self._is_completion_installed(config_file):
|
|
205
|
+
print(f"✓ Tab completion is not installed")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
self._remove_completion_from_config(config_file)
|
|
210
|
+
print(f"✓ Tab completion uninstalled")
|
|
211
|
+
print(f"\nRemoved from: {config_file}")
|
|
212
|
+
print(f"\nRestart your shell for changes to take effect.")
|
|
213
|
+
except Exception as e:
|
|
214
|
+
print(f"✗ Failed to uninstall completion: {e}")
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
def status(self, args):
|
|
218
|
+
"""Show completion installation status."""
|
|
219
|
+
shell, config_file = self._detect_shell()
|
|
220
|
+
|
|
221
|
+
print("Shell Completion Status")
|
|
222
|
+
print("=" * 40)
|
|
223
|
+
|
|
224
|
+
if not shell:
|
|
225
|
+
print("Shell: Unknown")
|
|
226
|
+
print("Status: ✗ Not supported")
|
|
227
|
+
print(f"\nYour SHELL: {os.environ.get('SHELL', 'not set')}")
|
|
228
|
+
print("Supported shells: bash, zsh")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
print(f"Shell: {shell}")
|
|
232
|
+
print(f"Config: {config_file}")
|
|
233
|
+
|
|
234
|
+
# Check argcomplete availability
|
|
235
|
+
argcomplete_available = self._check_argcomplete_available()
|
|
236
|
+
print(f"Argcomplete: {'✓ Available' if argcomplete_available else '✗ Not found'}")
|
|
237
|
+
|
|
238
|
+
if self._is_completion_installed(config_file):
|
|
239
|
+
print("Status: ✓ Installed")
|
|
240
|
+
if not argcomplete_available:
|
|
241
|
+
print("\n⚠️ Warning: Completion is configured but argcomplete is not in PATH")
|
|
242
|
+
print(" Install with: uv tool install argcomplete")
|
|
243
|
+
print(f"\nTo uninstall: cg completion uninstall")
|
|
244
|
+
else:
|
|
245
|
+
print("Status: ✗ Not installed")
|
|
246
|
+
print(f"\nTo install: cg completion install")
|