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
comfygit_cli/cli.py
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
"""ComfyGit MVP CLI - Workspace and Environment Management."""
|
|
2
|
+
# PYTHON_ARGCOMPLETE_OK
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import argcomplete
|
|
10
|
+
|
|
11
|
+
from .completion_commands import CompletionCommands
|
|
12
|
+
from .completers import (
|
|
13
|
+
branch_completer,
|
|
14
|
+
commit_hash_completer,
|
|
15
|
+
environment_completer,
|
|
16
|
+
installed_node_completer,
|
|
17
|
+
ref_completer,
|
|
18
|
+
workflow_completer,
|
|
19
|
+
)
|
|
20
|
+
from .env_commands import EnvironmentCommands
|
|
21
|
+
from .global_commands import GlobalCommands
|
|
22
|
+
from .logging.logging_config import setup_logging
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
__version__ = version("comfygit")
|
|
26
|
+
except PackageNotFoundError:
|
|
27
|
+
__version__ = "unknown"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_comfygit_config_dir() -> Path:
|
|
31
|
+
"""Get ComfyGit config directory (creates if needed)."""
|
|
32
|
+
config_dir = Path.home() / ".config" / "comfygit"
|
|
33
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
return config_dir
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _check_for_old_docker_installation() -> None:
|
|
38
|
+
"""Warn once about old Docker-based ComfyDock installation."""
|
|
39
|
+
old_config = Path.home() / ".comfydock" / "environments.json"
|
|
40
|
+
if not old_config.exists():
|
|
41
|
+
return # No old Docker installation detected
|
|
42
|
+
|
|
43
|
+
# Check if we've already shown this warning
|
|
44
|
+
warning_flag = _get_comfygit_config_dir() / ".docker_warning_shown"
|
|
45
|
+
if warning_flag.exists():
|
|
46
|
+
return # Already warned user
|
|
47
|
+
|
|
48
|
+
# Show warning (compact, informative)
|
|
49
|
+
print("\n" + "="*70)
|
|
50
|
+
print("ℹ️ OLD DOCKER-BASED COMFYDOCK DETECTED")
|
|
51
|
+
print("="*70)
|
|
52
|
+
print("\nYou have an old Docker-based ComfyDock (v0.3.x) at ~/.comfydock")
|
|
53
|
+
print("This is the NEW ComfyGit v1.0+ (UV-based).")
|
|
54
|
+
print("\nKey differences:")
|
|
55
|
+
print(" • Old version: Docker containers, 'comfydock' command")
|
|
56
|
+
print(" • New version: UV packages, 'comfygit' command")
|
|
57
|
+
print("\nBoth versions can coexist. Your old environments are unchanged.")
|
|
58
|
+
print("\nTo use old version: pip install comfydock==0.1.6")
|
|
59
|
+
print("To use new version: comfygit init")
|
|
60
|
+
print("\nMigration guide: https://github.com/comfyhub-org/comfygit/blob/main/MIGRATION.md")
|
|
61
|
+
print("="*70 + "\n")
|
|
62
|
+
|
|
63
|
+
# Mark warning as shown
|
|
64
|
+
warning_flag.touch()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main() -> None:
|
|
68
|
+
"""Main entry point for ComfyGit CLI."""
|
|
69
|
+
# Enable readline for input() line editing (arrow keys, history)
|
|
70
|
+
# Unix/Linux/macOS: provides full editing capability
|
|
71
|
+
# Windows: gracefully falls back to native console editing
|
|
72
|
+
try:
|
|
73
|
+
import readline # noqa: F401
|
|
74
|
+
except ImportError:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
# Check for old Docker installation (show warning once)
|
|
78
|
+
_check_for_old_docker_installation()
|
|
79
|
+
|
|
80
|
+
# Initialize logging system with minimal console output
|
|
81
|
+
# Environment commands will add file handlers as needed
|
|
82
|
+
setup_logging(level="INFO", simple_format=True, console_level="CRITICAL")
|
|
83
|
+
|
|
84
|
+
# Special handling for 'run' command to pass through ComfyUI args
|
|
85
|
+
parser = create_parser()
|
|
86
|
+
if 'run' in sys.argv:
|
|
87
|
+
# Parse known args, pass unknown to ComfyUI
|
|
88
|
+
args, unknown = parser.parse_known_args()
|
|
89
|
+
if getattr(args, 'command', None) == 'run':
|
|
90
|
+
args.args = unknown
|
|
91
|
+
else:
|
|
92
|
+
# Not actually the run command, do normal parsing
|
|
93
|
+
args = parser.parse_args()
|
|
94
|
+
else:
|
|
95
|
+
# Normal parsing for all other commands
|
|
96
|
+
args = parser.parse_args()
|
|
97
|
+
|
|
98
|
+
if not hasattr(args, 'func'):
|
|
99
|
+
parser.print_help()
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Execute the command
|
|
104
|
+
args.func(args)
|
|
105
|
+
except KeyboardInterrupt:
|
|
106
|
+
print("\n✗ Interrupted")
|
|
107
|
+
sys.exit(130)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"✗ Error: {e}", file=sys.stderr)
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
114
|
+
"""Create the argument parser with hierarchical command structure."""
|
|
115
|
+
parser = argparse.ArgumentParser(
|
|
116
|
+
description="ComfyGit - Manage ComfyUI workspaces and environments",
|
|
117
|
+
prog="cg"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Global options
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
'--version',
|
|
123
|
+
action='version',
|
|
124
|
+
version=f'ComfyGit CLI v{__version__}',
|
|
125
|
+
help='Show version and exit'
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
'-e', '--env',
|
|
129
|
+
help='Target environment (uses active if not specified)',
|
|
130
|
+
dest='target_env'
|
|
131
|
+
).completer = environment_completer # type: ignore[attr-defined]
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
'-v', '--verbose',
|
|
134
|
+
action='store_true',
|
|
135
|
+
help='Verbose output'
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
139
|
+
|
|
140
|
+
# Add all commands (workspace and environment)
|
|
141
|
+
_add_global_commands(subparsers)
|
|
142
|
+
_add_env_commands(subparsers)
|
|
143
|
+
|
|
144
|
+
# Enable argcomplete for tab completion
|
|
145
|
+
argcomplete.autocomplete(parser)
|
|
146
|
+
|
|
147
|
+
return parser
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _add_global_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
151
|
+
"""Add global workspace-level commands."""
|
|
152
|
+
global_cmds = GlobalCommands()
|
|
153
|
+
|
|
154
|
+
# init - Initialize workspace
|
|
155
|
+
init_parser = subparsers.add_parser("init", help="Initialize ComfyGit workspace")
|
|
156
|
+
init_parser.add_argument("path", type=Path, nargs="?", help="Workspace directory (default: ~/comfygit)")
|
|
157
|
+
init_parser.add_argument("--models-dir", type=Path, help="Path to existing models directory to index")
|
|
158
|
+
init_parser.add_argument("--yes", "-y", action="store_true", help="Use all defaults, no interactive prompts")
|
|
159
|
+
init_parser.add_argument("--bare", action="store_true", help="Create workspace without system nodes (comfygit-manager)")
|
|
160
|
+
init_parser.set_defaults(func=global_cmds.init)
|
|
161
|
+
|
|
162
|
+
# list - List all environments
|
|
163
|
+
list_parser = subparsers.add_parser("list", help="List all environments")
|
|
164
|
+
list_parser.set_defaults(func=global_cmds.list_envs)
|
|
165
|
+
|
|
166
|
+
# migrate - Import existing ComfyUI
|
|
167
|
+
# migrate_parser = subparsers.add_parser("migrate", help="Scan and import existing ComfyUI instance")
|
|
168
|
+
# migrate_parser.add_argument("source_path", type=Path, help="Path to existing ComfyUI")
|
|
169
|
+
# migrate_parser.add_argument("env_name", help="New environment name")
|
|
170
|
+
# migrate_parser.add_argument("--scan-only", action="store_true", help="Only scan, don't import")
|
|
171
|
+
# migrate_parser.set_defaults(func=global_cmds.migrate)
|
|
172
|
+
|
|
173
|
+
# import - Import ComfyGit environment
|
|
174
|
+
import_parser = subparsers.add_parser("import", help="Import ComfyGit environment from tarball or git repository")
|
|
175
|
+
import_parser.add_argument("path", type=str, nargs="?", help="Path to .tar.gz file or git repository URL (use #subdirectory for subdirectory imports)")
|
|
176
|
+
import_parser.add_argument("--name", type=str, help="Name for imported environment (skip prompt)")
|
|
177
|
+
import_parser.add_argument("--branch", "-b", type=str, help="Git branch, tag, or commit to import (git imports only)")
|
|
178
|
+
import_parser.add_argument(
|
|
179
|
+
"--torch-backend",
|
|
180
|
+
default="auto",
|
|
181
|
+
metavar="BACKEND",
|
|
182
|
+
help=(
|
|
183
|
+
"PyTorch backend. Examples: auto (detect GPU), cpu, "
|
|
184
|
+
"cu128 (CUDA 12.8), cu126, cu124, rocm6.3 (AMD), xpu (Intel). "
|
|
185
|
+
"Default: auto"
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
import_parser.add_argument("--use", action="store_true", help="Set imported environment as active")
|
|
189
|
+
import_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts, use defaults for workspace initialization")
|
|
190
|
+
import_parser.set_defaults(func=global_cmds.import_env)
|
|
191
|
+
|
|
192
|
+
# export - Export ComfyGit environment
|
|
193
|
+
export_parser = subparsers.add_parser("export", help="Export ComfyGit environment (include relevant files from .cec)")
|
|
194
|
+
export_parser.add_argument("path", type=Path, nargs="?", help="Path to output file")
|
|
195
|
+
export_parser.add_argument("--allow-issues", action="store_true", help="Skip confirmation if models are missing source URLs")
|
|
196
|
+
export_parser.set_defaults(func=global_cmds.export_env)
|
|
197
|
+
|
|
198
|
+
# Model management subcommands
|
|
199
|
+
model_parser = subparsers.add_parser("model", help="Manage model index")
|
|
200
|
+
model_subparsers = model_parser.add_subparsers(dest="model_command", help="Model commands")
|
|
201
|
+
|
|
202
|
+
# model index subcommands
|
|
203
|
+
model_index_parser = model_subparsers.add_parser("index", help="Model index operations")
|
|
204
|
+
model_index_subparsers = model_index_parser.add_subparsers(dest="model_index_command", help="Model index commands")
|
|
205
|
+
|
|
206
|
+
# model index find
|
|
207
|
+
model_index_find_parser = model_index_subparsers.add_parser("find", help="Find models by hash or filename")
|
|
208
|
+
model_index_find_parser.add_argument("query", help="Search query (hash prefix or filename)")
|
|
209
|
+
model_index_find_parser.set_defaults(func=global_cmds.model_index_find)
|
|
210
|
+
|
|
211
|
+
# model index list
|
|
212
|
+
model_index_list_parser = model_index_subparsers.add_parser("list", help="List all indexed models")
|
|
213
|
+
model_index_list_parser.add_argument("--duplicates", action="store_true", help="Show only models with multiple locations")
|
|
214
|
+
model_index_list_parser.set_defaults(func=global_cmds.model_index_list)
|
|
215
|
+
|
|
216
|
+
# model index show
|
|
217
|
+
model_index_show_parser = model_index_subparsers.add_parser("show", help="Show detailed model information")
|
|
218
|
+
model_index_show_parser.add_argument("identifier", help="Model hash, hash prefix, filename, or path")
|
|
219
|
+
model_index_show_parser.set_defaults(func=global_cmds.model_index_show)
|
|
220
|
+
|
|
221
|
+
# model index status
|
|
222
|
+
model_index_status_parser = model_index_subparsers.add_parser("status", help="Show models directory and index status")
|
|
223
|
+
model_index_status_parser.set_defaults(func=global_cmds.model_index_status)
|
|
224
|
+
|
|
225
|
+
# model index sync
|
|
226
|
+
model_index_sync_parser = model_index_subparsers.add_parser("sync", help="Scan models directory and update index")
|
|
227
|
+
model_index_sync_parser.set_defaults(func=global_cmds.model_index_sync)
|
|
228
|
+
|
|
229
|
+
# model index dir
|
|
230
|
+
model_index_dir_parser = model_index_subparsers.add_parser("dir", help="Set global models directory to index")
|
|
231
|
+
model_index_dir_parser.add_argument("path", type=Path, help="Path to models directory")
|
|
232
|
+
model_index_dir_parser.set_defaults(func=global_cmds.model_dir_add)
|
|
233
|
+
|
|
234
|
+
# model download
|
|
235
|
+
model_download_parser = model_subparsers.add_parser("download", help="Download model from URL")
|
|
236
|
+
model_download_parser.add_argument("url", help="Model download URL (Civitai, HuggingFace, or direct)")
|
|
237
|
+
model_download_parser.add_argument("--path", type=str, help="Target path relative to models directory (e.g., checkpoints/model.safetensors)")
|
|
238
|
+
model_download_parser.add_argument("-c", "--category", type=str, help="Model category for auto-path (e.g., checkpoints, loras, vae)")
|
|
239
|
+
model_download_parser.add_argument("-y", "--yes", action="store_true", help="Skip path confirmation prompt")
|
|
240
|
+
model_download_parser.set_defaults(func=global_cmds.model_download)
|
|
241
|
+
|
|
242
|
+
# model add-source
|
|
243
|
+
model_add_source_parser = model_subparsers.add_parser("add-source", help="Add download source URL to model(s)")
|
|
244
|
+
model_add_source_parser.add_argument("model", nargs="?", help="Model filename or hash (omit for interactive mode)")
|
|
245
|
+
model_add_source_parser.add_argument("url", nargs="?", help="Download URL")
|
|
246
|
+
model_add_source_parser.set_defaults(func=global_cmds.model_add_source)
|
|
247
|
+
|
|
248
|
+
# Registry management subcommands
|
|
249
|
+
registry_parser = subparsers.add_parser("registry", help="Manage node registry cache")
|
|
250
|
+
registry_subparsers = registry_parser.add_subparsers(dest="registry_command", help="Registry commands")
|
|
251
|
+
|
|
252
|
+
# registry status
|
|
253
|
+
registry_status_parser = registry_subparsers.add_parser("status", help="Show registry cache status")
|
|
254
|
+
registry_status_parser.set_defaults(func=global_cmds.registry_status)
|
|
255
|
+
|
|
256
|
+
# registry update
|
|
257
|
+
registry_update_parser = registry_subparsers.add_parser("update", help="Update registry data from GitHub")
|
|
258
|
+
registry_update_parser.set_defaults(func=global_cmds.registry_update)
|
|
259
|
+
|
|
260
|
+
# Config management
|
|
261
|
+
config_parser = subparsers.add_parser("config", help="Manage configuration settings")
|
|
262
|
+
config_parser.add_argument("--civitai-key", type=str, help="Set Civitai API key (use empty string to clear)")
|
|
263
|
+
config_parser.add_argument("--show", action="store_true", help="Show current configuration")
|
|
264
|
+
config_parser.set_defaults(func=global_cmds.config)
|
|
265
|
+
|
|
266
|
+
# debug - Show application logs for debugging
|
|
267
|
+
debug_parser = subparsers.add_parser("debug", help="Show application debug logs")
|
|
268
|
+
debug_parser.add_argument("-n", "--lines", type=int, default=200, help="Number of lines to show (default: 200)")
|
|
269
|
+
debug_parser.add_argument("--level", choices=["DEBUG", "INFO", "WARNING", "ERROR"], help="Filter by log level")
|
|
270
|
+
debug_parser.add_argument("--full", action="store_true", help="Show all logs (no line limit)")
|
|
271
|
+
debug_parser.add_argument("--workspace", action="store_true", help="Show workspace logs instead of environment logs")
|
|
272
|
+
debug_parser.set_defaults(func=global_cmds.debug)
|
|
273
|
+
|
|
274
|
+
# Shell completion management
|
|
275
|
+
completion_cmds = CompletionCommands()
|
|
276
|
+
completion_parser = subparsers.add_parser("completion", help="Manage shell tab completion")
|
|
277
|
+
completion_subparsers = completion_parser.add_subparsers(dest="completion_command", help="Completion commands")
|
|
278
|
+
|
|
279
|
+
# completion install
|
|
280
|
+
completion_install_parser = completion_subparsers.add_parser("install", help="Install tab completion for your shell")
|
|
281
|
+
completion_install_parser.set_defaults(func=completion_cmds.install)
|
|
282
|
+
|
|
283
|
+
# completion uninstall
|
|
284
|
+
completion_uninstall_parser = completion_subparsers.add_parser("uninstall", help="Remove tab completion from your shell")
|
|
285
|
+
completion_uninstall_parser.set_defaults(func=completion_cmds.uninstall)
|
|
286
|
+
|
|
287
|
+
# completion status
|
|
288
|
+
completion_status_parser = completion_subparsers.add_parser("status", help="Show tab completion installation status")
|
|
289
|
+
completion_status_parser.set_defaults(func=completion_cmds.status)
|
|
290
|
+
|
|
291
|
+
# Orchestrator management subcommands
|
|
292
|
+
orch_parser = subparsers.add_parser(
|
|
293
|
+
"orch",
|
|
294
|
+
aliases=["orchestrator"],
|
|
295
|
+
help="Monitor and control orchestrator"
|
|
296
|
+
)
|
|
297
|
+
orch_subparsers = orch_parser.add_subparsers(
|
|
298
|
+
dest="orch_command",
|
|
299
|
+
help="Orchestrator commands"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# orch status
|
|
303
|
+
orch_status_parser = orch_subparsers.add_parser("status", help="Show orchestrator status")
|
|
304
|
+
orch_status_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
305
|
+
orch_status_parser.set_defaults(func=global_cmds.orch_status)
|
|
306
|
+
|
|
307
|
+
# orch restart
|
|
308
|
+
orch_restart_parser = orch_subparsers.add_parser("restart", help="Restart ComfyUI")
|
|
309
|
+
orch_restart_parser.add_argument("--wait", action="store_true", help="Wait for restart to complete")
|
|
310
|
+
orch_restart_parser.set_defaults(func=global_cmds.orch_restart)
|
|
311
|
+
|
|
312
|
+
# orch kill
|
|
313
|
+
orch_kill_parser = orch_subparsers.add_parser("kill", help="Shutdown orchestrator")
|
|
314
|
+
orch_kill_parser.add_argument("--force", action="store_true", help="Force kill (bypass command queue)")
|
|
315
|
+
orch_kill_parser.set_defaults(func=global_cmds.orch_kill)
|
|
316
|
+
|
|
317
|
+
# orch clean
|
|
318
|
+
orch_clean_parser = orch_subparsers.add_parser("clean", help="Clean orchestrator state")
|
|
319
|
+
orch_clean_parser.add_argument("--dry-run", action="store_true", help="Show what would be deleted")
|
|
320
|
+
orch_clean_parser.add_argument("--force", action="store_true", help="Skip confirmation")
|
|
321
|
+
orch_clean_parser.add_argument("--kill", action="store_true", help="Also kill orchestrator process")
|
|
322
|
+
orch_clean_parser.set_defaults(func=global_cmds.orch_clean)
|
|
323
|
+
|
|
324
|
+
# orch logs
|
|
325
|
+
orch_logs_parser = orch_subparsers.add_parser("logs", help="Show orchestrator logs")
|
|
326
|
+
orch_logs_parser.add_argument("-f", "--follow", action="store_true", help="Follow logs in real-time")
|
|
327
|
+
orch_logs_parser.add_argument("-n", "--lines", type=int, default=50, help="Number of lines to show (default: 50)")
|
|
328
|
+
orch_logs_parser.set_defaults(func=global_cmds.orch_logs)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _add_env_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
332
|
+
"""Add environment-specific commands."""
|
|
333
|
+
env_cmds = EnvironmentCommands()
|
|
334
|
+
|
|
335
|
+
# Environment Management Commands (operate ON environments)
|
|
336
|
+
|
|
337
|
+
# create - Create new environment
|
|
338
|
+
create_parser = subparsers.add_parser("create", help="Create new environment")
|
|
339
|
+
create_parser.add_argument("name", help="Environment name")
|
|
340
|
+
create_parser.add_argument("--template", type=Path, help="Template manifest")
|
|
341
|
+
create_parser.add_argument("--python", default="3.11", help="Python version")
|
|
342
|
+
create_parser.add_argument("--comfyui", help="ComfyUI version")
|
|
343
|
+
create_parser.add_argument(
|
|
344
|
+
"--torch-backend",
|
|
345
|
+
default="auto",
|
|
346
|
+
metavar="BACKEND",
|
|
347
|
+
help=(
|
|
348
|
+
"PyTorch backend. Examples: auto (detect GPU), cpu, "
|
|
349
|
+
"cu128 (CUDA 12.8), cu126, cu124, rocm6.3 (AMD), xpu (Intel). "
|
|
350
|
+
"Default: auto"
|
|
351
|
+
),
|
|
352
|
+
)
|
|
353
|
+
create_parser.add_argument("--use", action="store_true", help="Set active environment after creation")
|
|
354
|
+
create_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompts, use defaults for workspace initialization")
|
|
355
|
+
create_parser.set_defaults(func=env_cmds.create)
|
|
356
|
+
|
|
357
|
+
# use - Set active environment
|
|
358
|
+
use_parser = subparsers.add_parser("use", help="Set active environment")
|
|
359
|
+
use_parser.add_argument("name", help="Environment name").completer = environment_completer # type: ignore[attr-defined]
|
|
360
|
+
use_parser.set_defaults(func=env_cmds.use)
|
|
361
|
+
|
|
362
|
+
# delete - Delete environment
|
|
363
|
+
delete_parser = subparsers.add_parser("delete", help="Delete environment")
|
|
364
|
+
delete_parser.add_argument("name", help="Environment name").completer = environment_completer # type: ignore[attr-defined]
|
|
365
|
+
delete_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
|
|
366
|
+
delete_parser.set_defaults(func=env_cmds.delete)
|
|
367
|
+
|
|
368
|
+
# Environment Operation Commands (operate IN environments, require -e or active)
|
|
369
|
+
|
|
370
|
+
# run - Run ComfyUI (special handling for ComfyUI args)
|
|
371
|
+
run_parser = subparsers.add_parser("run", help="Run ComfyUI")
|
|
372
|
+
run_parser.add_argument("--no-sync", action="store_true", help="Skip environment sync before running")
|
|
373
|
+
run_parser.set_defaults(func=env_cmds.run, args=[])
|
|
374
|
+
|
|
375
|
+
# status - Show environment status
|
|
376
|
+
status_parser = subparsers.add_parser("status", help="Show status (both sync and git status)")
|
|
377
|
+
status_parser.add_argument("-v", "--verbose", action="store_true", help="Show full details")
|
|
378
|
+
status_parser.set_defaults(func=env_cmds.status)
|
|
379
|
+
|
|
380
|
+
# manifest - Show environment manifest
|
|
381
|
+
manifest_parser = subparsers.add_parser("manifest", help="Show environment manifest (pyproject.toml)")
|
|
382
|
+
manifest_parser.add_argument("--pretty", action="store_true", help="Output as YAML instead of TOML")
|
|
383
|
+
manifest_parser.add_argument("--section", type=str, help="Show specific section (e.g., tool.comfygit.nodes)")
|
|
384
|
+
manifest_parser.add_argument("--ide", nargs="?", const="auto", metavar="CMD", help="Open in editor (uses $EDITOR if no command given)")
|
|
385
|
+
manifest_parser.set_defaults(func=env_cmds.manifest)
|
|
386
|
+
|
|
387
|
+
# repair - Repair environment drift (manual edits or git operations)
|
|
388
|
+
repair_parser = subparsers.add_parser("repair", help="Repair environment to match pyproject.toml")
|
|
389
|
+
repair_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
|
|
390
|
+
repair_parser.add_argument(
|
|
391
|
+
"--models",
|
|
392
|
+
choices=["all", "required", "skip"],
|
|
393
|
+
default="all",
|
|
394
|
+
help="Model download strategy: all (default), required only, or skip"
|
|
395
|
+
)
|
|
396
|
+
repair_parser.set_defaults(func=env_cmds.repair)
|
|
397
|
+
|
|
398
|
+
# log - Show commit history
|
|
399
|
+
log_parser = subparsers.add_parser("log", help="Show commit history")
|
|
400
|
+
log_parser.add_argument("-n", "--limit", type=int, default=20, metavar="N", help="Number of commits to show (default: 20)")
|
|
401
|
+
log_parser.add_argument("-v", "--verbose", action="store_true", help="Show full details")
|
|
402
|
+
log_parser.set_defaults(func=env_cmds.log)
|
|
403
|
+
|
|
404
|
+
# commit - Save environment changes
|
|
405
|
+
commit_parser = subparsers.add_parser("commit", help="Commit environment changes")
|
|
406
|
+
commit_parser.add_argument("-m", "--message", help="Commit message (auto-generated if not provided)")
|
|
407
|
+
commit_parser.add_argument("--auto", action="store_true", help="Auto-resolve issues without interaction")
|
|
408
|
+
commit_parser.add_argument("--allow-issues", action="store_true", help="Allow committing workflows with unresolved issues")
|
|
409
|
+
commit_parser.add_argument("-y", "--yes", action="store_true", help="Skip detached HEAD warning (allow commit anyway)")
|
|
410
|
+
commit_parser.set_defaults(func=env_cmds.commit)
|
|
411
|
+
|
|
412
|
+
# checkout - Move HEAD without committing
|
|
413
|
+
checkout_parser = subparsers.add_parser("checkout", help="Checkout commits, branches, or files")
|
|
414
|
+
checkout_parser.add_argument("ref", nargs="?", help="Commit, branch, or tag to checkout (defaults to HEAD when using -b)").completer = ref_completer # type: ignore[attr-defined]
|
|
415
|
+
checkout_parser.add_argument("-b", "--branch", help="Create new branch and switch to it")
|
|
416
|
+
checkout_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation for uncommitted changes")
|
|
417
|
+
checkout_parser.add_argument("--force", action="store_true", help="Force checkout, discarding uncommitted changes")
|
|
418
|
+
checkout_parser.set_defaults(func=env_cmds.checkout)
|
|
419
|
+
|
|
420
|
+
# branch - Manage branches
|
|
421
|
+
branch_parser = subparsers.add_parser("branch", help="List, create, or delete branches")
|
|
422
|
+
branch_parser.add_argument("name", nargs="?", help="Branch name (list all if omitted)").completer = branch_completer # type: ignore[attr-defined]
|
|
423
|
+
branch_parser.add_argument("-d", "--delete", action="store_true", help="Delete branch")
|
|
424
|
+
branch_parser.add_argument("-D", "--force-delete", action="store_true", help="Force delete branch (even if unmerged)")
|
|
425
|
+
branch_parser.set_defaults(func=env_cmds.branch)
|
|
426
|
+
|
|
427
|
+
# switch - Switch branches
|
|
428
|
+
switch_parser = subparsers.add_parser("switch", help="Switch to a branch")
|
|
429
|
+
switch_parser.add_argument("branch", help="Branch name to switch to").completer = branch_completer # type: ignore[attr-defined]
|
|
430
|
+
switch_parser.add_argument("-c", "--create", action="store_true", help="Create branch if it doesn't exist")
|
|
431
|
+
switch_parser.set_defaults(func=env_cmds.switch)
|
|
432
|
+
|
|
433
|
+
# reset - Reset current HEAD to ref
|
|
434
|
+
reset_parser = subparsers.add_parser("reset", help="Reset current HEAD to specified state")
|
|
435
|
+
reset_parser.add_argument("ref", nargs="?", default="HEAD", help="Commit to reset to (default: HEAD)").completer = commit_hash_completer # type: ignore[attr-defined]
|
|
436
|
+
reset_parser.add_argument("--hard", action="store_true", help="Discard all changes (hard reset)")
|
|
437
|
+
reset_parser.add_argument("--mixed", action="store_true", help="Keep changes in working tree, unstage (default)")
|
|
438
|
+
reset_parser.add_argument("--soft", action="store_true", help="Keep changes staged")
|
|
439
|
+
reset_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
|
|
440
|
+
reset_parser.set_defaults(func=env_cmds.reset_git)
|
|
441
|
+
|
|
442
|
+
# merge - Merge branches
|
|
443
|
+
merge_parser = subparsers.add_parser("merge", help="Merge branch into current")
|
|
444
|
+
merge_parser.add_argument("branch", help="Branch to merge")
|
|
445
|
+
merge_parser.add_argument("-m", "--message", help="Merge commit message")
|
|
446
|
+
merge_parser.add_argument(
|
|
447
|
+
"--preview",
|
|
448
|
+
action="store_true",
|
|
449
|
+
help="Preview changes without applying (read-only diff with conflict detection)"
|
|
450
|
+
)
|
|
451
|
+
merge_parser.add_argument(
|
|
452
|
+
"--auto-resolve",
|
|
453
|
+
choices=["mine", "theirs"],
|
|
454
|
+
help="Auto-resolve conflicts: 'mine' keeps local, 'theirs' takes incoming"
|
|
455
|
+
)
|
|
456
|
+
merge_parser.set_defaults(func=env_cmds.merge)
|
|
457
|
+
|
|
458
|
+
# revert - Revert commits
|
|
459
|
+
revert_parser = subparsers.add_parser("revert", help="Create new commit that undoes previous commit")
|
|
460
|
+
revert_parser.add_argument("commit", help="Commit to revert")
|
|
461
|
+
revert_parser.set_defaults(func=env_cmds.revert)
|
|
462
|
+
|
|
463
|
+
# pull - Pull from remote and sync
|
|
464
|
+
pull_parser = subparsers.add_parser(
|
|
465
|
+
"pull",
|
|
466
|
+
help="Pull changes from remote and repair environment"
|
|
467
|
+
)
|
|
468
|
+
pull_parser.add_argument(
|
|
469
|
+
"-r", "--remote",
|
|
470
|
+
default="origin",
|
|
471
|
+
help="Git remote name (default: origin)"
|
|
472
|
+
)
|
|
473
|
+
pull_parser.add_argument(
|
|
474
|
+
"--models",
|
|
475
|
+
choices=["all", "required", "skip"],
|
|
476
|
+
default="all",
|
|
477
|
+
help="Model download strategy (default: all)"
|
|
478
|
+
)
|
|
479
|
+
pull_parser.add_argument(
|
|
480
|
+
"--force",
|
|
481
|
+
action="store_true",
|
|
482
|
+
help="Discard uncommitted changes and force pull"
|
|
483
|
+
)
|
|
484
|
+
pull_parser.add_argument(
|
|
485
|
+
"--preview",
|
|
486
|
+
action="store_true",
|
|
487
|
+
help="Preview changes without applying (read-only fetch and diff)"
|
|
488
|
+
)
|
|
489
|
+
pull_parser.add_argument(
|
|
490
|
+
"--auto-resolve",
|
|
491
|
+
choices=["mine", "theirs"],
|
|
492
|
+
help="Auto-resolve conflicts: 'mine' keeps local, 'theirs' takes incoming"
|
|
493
|
+
)
|
|
494
|
+
pull_parser.set_defaults(func=env_cmds.pull)
|
|
495
|
+
|
|
496
|
+
# push - Push commits to remote
|
|
497
|
+
push_parser = subparsers.add_parser(
|
|
498
|
+
"push",
|
|
499
|
+
help="Push committed changes to remote"
|
|
500
|
+
)
|
|
501
|
+
push_parser.add_argument(
|
|
502
|
+
"-r", "--remote",
|
|
503
|
+
default="origin",
|
|
504
|
+
help="Git remote name (default: origin)"
|
|
505
|
+
)
|
|
506
|
+
push_parser.add_argument(
|
|
507
|
+
"--force",
|
|
508
|
+
action="store_true",
|
|
509
|
+
help="Force push using --force-with-lease (overwrite remote)"
|
|
510
|
+
)
|
|
511
|
+
push_parser.set_defaults(func=env_cmds.push)
|
|
512
|
+
|
|
513
|
+
# remote - Manage git remotes
|
|
514
|
+
remote_parser = subparsers.add_parser(
|
|
515
|
+
"remote",
|
|
516
|
+
help="Manage git remotes"
|
|
517
|
+
)
|
|
518
|
+
remote_subparsers = remote_parser.add_subparsers(
|
|
519
|
+
dest="remote_command",
|
|
520
|
+
required=True
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# remote add
|
|
524
|
+
remote_add_parser = remote_subparsers.add_parser(
|
|
525
|
+
"add",
|
|
526
|
+
help="Add a git remote"
|
|
527
|
+
)
|
|
528
|
+
remote_add_parser.add_argument(
|
|
529
|
+
"name",
|
|
530
|
+
help="Remote name (e.g., origin)"
|
|
531
|
+
)
|
|
532
|
+
remote_add_parser.add_argument(
|
|
533
|
+
"url",
|
|
534
|
+
help="Remote URL"
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# remote remove
|
|
538
|
+
remote_remove_parser = remote_subparsers.add_parser(
|
|
539
|
+
"remove",
|
|
540
|
+
help="Remove a git remote"
|
|
541
|
+
)
|
|
542
|
+
remote_remove_parser.add_argument(
|
|
543
|
+
"name",
|
|
544
|
+
help="Remote name to remove"
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# remote list
|
|
548
|
+
remote_list_parser = remote_subparsers.add_parser(
|
|
549
|
+
"list",
|
|
550
|
+
help="List all git remotes"
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
remote_parser.set_defaults(func=env_cmds.remote)
|
|
554
|
+
|
|
555
|
+
# Node management subcommands
|
|
556
|
+
node_parser = subparsers.add_parser("node", help="Manage custom nodes")
|
|
557
|
+
node_subparsers = node_parser.add_subparsers(dest="node_command", help="Node commands")
|
|
558
|
+
|
|
559
|
+
# node add
|
|
560
|
+
node_add_parser = node_subparsers.add_parser("add", help="Add custom node(s)")
|
|
561
|
+
node_add_parser.add_argument("node_names", nargs="+", help="Node identifier(s): registry-id[@version], github-url[@ref], or directory name")
|
|
562
|
+
node_add_parser.add_argument("--dev", action="store_true", help="Track existing local development node")
|
|
563
|
+
node_add_parser.add_argument("--no-test", action="store_true", help="Don't test resolution")
|
|
564
|
+
node_add_parser.add_argument("--force", action="store_true", help="Force overwrite existing directory")
|
|
565
|
+
node_add_parser.add_argument("--verbose", "-v", action="store_true", help="Show full UV error output for dependency conflicts")
|
|
566
|
+
node_add_parser.set_defaults(func=env_cmds.node_add)
|
|
567
|
+
|
|
568
|
+
# node remove
|
|
569
|
+
node_remove_parser = node_subparsers.add_parser("remove", help="Remove custom node(s)")
|
|
570
|
+
node_remove_parser.add_argument("node_names", nargs="+", help="Node registry ID(s) or name(s)").completer = installed_node_completer # type: ignore[attr-defined]
|
|
571
|
+
node_remove_parser.add_argument("--dev", action="store_true", help="Remove development node specifically")
|
|
572
|
+
node_remove_parser.add_argument("--untrack", action="store_true", help="Only remove from tracking, leave filesystem unchanged")
|
|
573
|
+
node_remove_parser.set_defaults(func=env_cmds.node_remove)
|
|
574
|
+
|
|
575
|
+
# node prune
|
|
576
|
+
node_prune_parser = node_subparsers.add_parser("prune", help="Remove unused custom nodes")
|
|
577
|
+
node_prune_parser.add_argument("--exclude", nargs="+", metavar="PACKAGE", help="Package IDs to keep even if unused")
|
|
578
|
+
node_prune_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompt")
|
|
579
|
+
node_prune_parser.set_defaults(func=env_cmds.node_prune)
|
|
580
|
+
|
|
581
|
+
# node list
|
|
582
|
+
node_list_parser = node_subparsers.add_parser("list", help="List custom nodes")
|
|
583
|
+
node_list_parser.set_defaults(func=env_cmds.node_list)
|
|
584
|
+
|
|
585
|
+
# node update
|
|
586
|
+
node_update_parser = node_subparsers.add_parser("update", help="Update custom node")
|
|
587
|
+
node_update_parser.add_argument("node_name", help="Node identifier or name to update").completer = installed_node_completer # type: ignore[attr-defined]
|
|
588
|
+
node_update_parser.add_argument("-y", "--yes", action="store_true", help="Auto-confirm updates (skip prompts)")
|
|
589
|
+
node_update_parser.add_argument("--no-test", action="store_true", help="Don't test resolution")
|
|
590
|
+
node_update_parser.set_defaults(func=env_cmds.node_update)
|
|
591
|
+
|
|
592
|
+
# Workflow management subcommands
|
|
593
|
+
workflow_parser = subparsers.add_parser("workflow", help="Manage workflows")
|
|
594
|
+
workflow_subparsers = workflow_parser.add_subparsers(dest="workflow_command", help="Workflow commands")
|
|
595
|
+
|
|
596
|
+
# workflow list
|
|
597
|
+
workflow_list_parser = workflow_subparsers.add_parser("list", help="List all workflows with sync status")
|
|
598
|
+
workflow_list_parser.set_defaults(func=env_cmds.workflow_list)
|
|
599
|
+
|
|
600
|
+
# workflow resolve
|
|
601
|
+
workflow_resolve_parser = workflow_subparsers.add_parser("resolve", help="Resolve workflow dependencies (nodes & models)")
|
|
602
|
+
workflow_resolve_parser.add_argument("name", help="Workflow name to resolve").completer = workflow_completer # type: ignore[attr-defined]
|
|
603
|
+
workflow_resolve_parser.add_argument("--auto", action="store_true", help="Auto-resolve without interaction")
|
|
604
|
+
workflow_resolve_parser.add_argument("--install", action="store_true", help="Auto-install missing nodes without prompting")
|
|
605
|
+
workflow_resolve_parser.add_argument("--no-install", action="store_true", help="Skip node installation prompt")
|
|
606
|
+
workflow_resolve_parser.set_defaults(func=env_cmds.workflow_resolve)
|
|
607
|
+
|
|
608
|
+
# workflow model importance
|
|
609
|
+
workflow_importance_parser = workflow_subparsers.add_parser(
|
|
610
|
+
"model",
|
|
611
|
+
help="Manage workflow models"
|
|
612
|
+
)
|
|
613
|
+
workflow_model_subparsers = workflow_importance_parser.add_subparsers(
|
|
614
|
+
dest="model_command",
|
|
615
|
+
help="Model management commands"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
importance_parser = workflow_model_subparsers.add_parser(
|
|
619
|
+
"importance",
|
|
620
|
+
help="Set model importance (required/flexible/optional)"
|
|
621
|
+
)
|
|
622
|
+
importance_parser.add_argument(
|
|
623
|
+
"workflow_name",
|
|
624
|
+
nargs="?",
|
|
625
|
+
help="Workflow name (interactive if omitted)"
|
|
626
|
+
).completer = workflow_completer # type: ignore[attr-defined]
|
|
627
|
+
importance_parser.add_argument(
|
|
628
|
+
"model_identifier",
|
|
629
|
+
nargs="?",
|
|
630
|
+
help="Model filename or hash (interactive if omitted)"
|
|
631
|
+
)
|
|
632
|
+
importance_parser.add_argument(
|
|
633
|
+
"importance",
|
|
634
|
+
nargs="?",
|
|
635
|
+
choices=["required", "flexible", "optional"],
|
|
636
|
+
help="Importance level"
|
|
637
|
+
)
|
|
638
|
+
importance_parser.set_defaults(func=env_cmds.workflow_model_importance)
|
|
639
|
+
|
|
640
|
+
# Constraint management subcommands
|
|
641
|
+
constraint_parser = subparsers.add_parser("constraint", help="Manage UV constraint dependencies")
|
|
642
|
+
constraint_subparsers = constraint_parser.add_subparsers(dest="constraint_command", help="Constraint commands")
|
|
643
|
+
|
|
644
|
+
# constraint add
|
|
645
|
+
constraint_add_parser = constraint_subparsers.add_parser("add", help="Add constraint dependencies")
|
|
646
|
+
constraint_add_parser.add_argument("packages", nargs="+", help="Package specifications (e.g., torch==2.4.1)")
|
|
647
|
+
constraint_add_parser.set_defaults(func=env_cmds.constraint_add)
|
|
648
|
+
|
|
649
|
+
# constraint list
|
|
650
|
+
constraint_list_parser = constraint_subparsers.add_parser("list", help="List constraint dependencies")
|
|
651
|
+
constraint_list_parser.set_defaults(func=env_cmds.constraint_list)
|
|
652
|
+
|
|
653
|
+
# constraint remove
|
|
654
|
+
constraint_remove_parser = constraint_subparsers.add_parser("remove", help="Remove constraint dependencies")
|
|
655
|
+
constraint_remove_parser.add_argument("packages", nargs="+", help="Package names to remove")
|
|
656
|
+
constraint_remove_parser.set_defaults(func=env_cmds.constraint_remove)
|
|
657
|
+
|
|
658
|
+
# Python dependency management subcommands
|
|
659
|
+
py_parser = subparsers.add_parser("py", help="Manage Python dependencies")
|
|
660
|
+
py_subparsers = py_parser.add_subparsers(dest="py_command", help="Python dependency commands")
|
|
661
|
+
|
|
662
|
+
# py add
|
|
663
|
+
py_add_parser = py_subparsers.add_parser("add", help="Add Python dependencies")
|
|
664
|
+
py_add_parser.add_argument("packages", nargs="*", help="Package specifications (e.g., requests>=2.0.0)")
|
|
665
|
+
py_add_parser.add_argument("-r", "--requirements", type=Path, help="Add packages from requirements.txt file")
|
|
666
|
+
py_add_parser.add_argument("--upgrade", action="store_true", help="Upgrade existing packages")
|
|
667
|
+
# Tier 2: Power-user flags
|
|
668
|
+
py_add_parser.add_argument("--group", help="Add to dependency group (e.g., optional-cuda)")
|
|
669
|
+
py_add_parser.add_argument("--dev", action="store_true", help="Add to dev dependencies")
|
|
670
|
+
py_add_parser.add_argument("--editable", action="store_true", help="Install as editable (for local development)")
|
|
671
|
+
py_add_parser.add_argument("--bounds", choices=["lower", "major", "minor", "exact"], help="Version specifier style")
|
|
672
|
+
py_add_parser.set_defaults(func=env_cmds.py_add)
|
|
673
|
+
|
|
674
|
+
# py remove
|
|
675
|
+
py_remove_parser = py_subparsers.add_parser("remove", help="Remove Python dependencies")
|
|
676
|
+
py_remove_parser.add_argument("packages", nargs="+", help="Package names to remove")
|
|
677
|
+
py_remove_parser.add_argument("--group", help="Remove packages from dependency group instead of main dependencies")
|
|
678
|
+
py_remove_parser.set_defaults(func=env_cmds.py_remove)
|
|
679
|
+
|
|
680
|
+
# py remove-group
|
|
681
|
+
py_remove_group_parser = py_subparsers.add_parser("remove-group", help="Remove entire dependency group")
|
|
682
|
+
py_remove_group_parser.add_argument("group", help="Dependency group name to remove")
|
|
683
|
+
py_remove_group_parser.set_defaults(func=env_cmds.py_remove_group)
|
|
684
|
+
|
|
685
|
+
# py list
|
|
686
|
+
py_list_parser = py_subparsers.add_parser("list", help="List project dependencies")
|
|
687
|
+
py_list_parser.add_argument("--all", action="store_true", help="Show all dependencies including dependency groups")
|
|
688
|
+
py_list_parser.set_defaults(func=env_cmds.py_list)
|
|
689
|
+
|
|
690
|
+
# py uv - Direct UV passthrough for advanced users
|
|
691
|
+
py_uv_parser = py_subparsers.add_parser(
|
|
692
|
+
"uv",
|
|
693
|
+
help="Direct UV passthrough (advanced)",
|
|
694
|
+
add_help=False # Don't interfere with UV's --help
|
|
695
|
+
)
|
|
696
|
+
py_uv_parser.add_argument(
|
|
697
|
+
"uv_args",
|
|
698
|
+
nargs=argparse.REMAINDER, # Capture everything after 'uv'
|
|
699
|
+
help="UV command and arguments (e.g., 'add --group optional-cuda sageattention')"
|
|
700
|
+
)
|
|
701
|
+
py_uv_parser.set_defaults(func=env_cmds.py_uv)
|
|
702
|
+
|
|
703
|
+
if __name__ == "__main__":
|
|
704
|
+
main()
|