comfygit 0.3.8__tar.gz → 0.3.8.dev1__tar.gz
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.8 → comfygit-0.3.8.dev1}/PKG-INFO +2 -2
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/cli.py +74 -4
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/env_commands.py +238 -3
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/global_commands.py +20 -17
- comfygit-0.3.8.dev1/docs/architecture.md +145 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/pyproject.toml +2 -2
- comfygit-0.3.8.dev1/tests/test_status_uninstalled_reporting.py +78 -0
- comfygit-0.3.8.dev1/tests/test_torch_backend_cli.py +292 -0
- comfygit-0.3.8/docs/codebase-map.md +0 -131
- comfygit-0.3.8/tests/test_status_uninstalled_reporting.py +0 -151
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/.github/workflows/publish_pypi.yml +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/.gitignore +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/.python-version +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/CLAUDE.md +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/LICENSE.txt +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/README.md +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/__init__.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/__main__.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/cli_utils.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/completers.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/completion_commands.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/formatters/__init__.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/formatters/error_formatter.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/interactive/__init__.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/logging/compressed_handler.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/logging/environment_logger.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/logging/log_compressor.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/logging/logging_config.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/resolution_strategies.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/strategies/__init__.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/strategies/conflict_resolver.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/strategies/interactive.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/strategies/rollback.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/utils/__init__.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/utils/civitai_errors.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/utils/orchestrator.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/utils/pagination.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/comfygit_cli/utils/progress.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/README_REGISTRY.md +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/augment_mappings.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/build_global_mappings.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/build_registry_cache.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/extract_builtin_nodes.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/extract_node_modules.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/get_hash.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/global-node-mappings.md +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/registry.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/registry_client.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/scripts/test_concurrent_api.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/conftest.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_batch_node_add.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_batch_node_remove.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_branch_commands.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_completers.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_completion_commands.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_conflict_resolver.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_detached_head_display.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_error_formatter.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_init_system_nodes.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_log_command.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_logging_structure.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_manifest_command.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_pagination.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_preview_display.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_py_commands.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_py_remove_group_commands.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_status_disabled_nodes_display.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_status_displays_uninstalled_nodes.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_status_path_sync_display.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_status_real_bug_scenario.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_status_suggestions.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_update_workflow_model_paths.py +0 -0
- {comfygit-0.3.8 → comfygit-0.3.8.dev1}/tests/test_workflow_model_importance.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comfygit
|
|
3
|
-
Version: 0.3.8
|
|
3
|
+
Version: 0.3.8.dev1
|
|
4
4
|
Summary: ComfyGit - Git-based environment management for ComfyUI
|
|
5
5
|
License-File: LICENSE.txt
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Requires-Dist: argcomplete>=3.5.0
|
|
8
|
-
Requires-Dist: comfygit-core==0.3.8
|
|
8
|
+
Requires-Dist: comfygit-core==0.3.8.dev1
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
|
|
11
11
|
# ComfyGit CLI
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Callable
|
|
9
9
|
|
|
10
10
|
import argcomplete
|
|
11
11
|
|
|
12
|
-
from .completion_commands import CompletionCommands
|
|
13
12
|
from .completers import (
|
|
14
13
|
branch_completer,
|
|
15
14
|
commit_hash_completer,
|
|
@@ -18,6 +17,7 @@ from .completers import (
|
|
|
18
17
|
ref_completer,
|
|
19
18
|
workflow_completer,
|
|
20
19
|
)
|
|
20
|
+
from .completion_commands import CompletionCommands
|
|
21
21
|
from .env_commands import EnvironmentCommands
|
|
22
22
|
from .global_commands import GlobalCommands
|
|
23
23
|
from .logging.logging_config import setup_logging
|
|
@@ -274,8 +274,11 @@ def _add_global_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
|
274
274
|
registry_update_parser = registry_subparsers.add_parser("update", help="Update registry data from GitHub")
|
|
275
275
|
registry_update_parser.set_defaults(func=global_cmds.registry_update)
|
|
276
276
|
|
|
277
|
-
# Config management
|
|
277
|
+
# Config management - now with subcommands
|
|
278
278
|
config_parser = subparsers.add_parser("config", help="Manage configuration settings")
|
|
279
|
+
config_subparsers = config_parser.add_subparsers(dest="config_command", help="Configuration commands")
|
|
280
|
+
|
|
281
|
+
# Legacy flags - still supported at root level for backward compatibility
|
|
279
282
|
config_parser.add_argument("--civitai-key", type=str, help="Set Civitai API key (use empty string to clear)")
|
|
280
283
|
config_parser.add_argument("--show", action="store_true", help="Show current configuration")
|
|
281
284
|
config_parser.set_defaults(func=global_cmds.config)
|
|
@@ -386,9 +389,42 @@ def _add_env_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
|
386
389
|
|
|
387
390
|
# Environment Operation Commands (operate IN environments, require -e or active)
|
|
388
391
|
|
|
392
|
+
# env-config - Environment-scoped configuration (requires -e or active env)
|
|
393
|
+
env_config_parser = subparsers.add_parser("env-config", help="Manage environment-specific configuration")
|
|
394
|
+
env_config_subparsers = env_config_parser.add_subparsers(dest="env_config_command", help="Environment config commands")
|
|
395
|
+
env_config_parser.set_defaults(func=_make_help_func(env_config_parser))
|
|
396
|
+
|
|
397
|
+
# env-config torch-backend - Manage PyTorch backend for this environment
|
|
398
|
+
env_config_torch_parser = env_config_subparsers.add_parser("torch-backend", help="Manage PyTorch backend settings")
|
|
399
|
+
env_config_torch_subparsers = env_config_torch_parser.add_subparsers(dest="torch_command", help="PyTorch backend commands")
|
|
400
|
+
env_config_torch_parser.set_defaults(func=_make_help_func(env_config_torch_parser))
|
|
401
|
+
|
|
402
|
+
# env-config torch-backend show
|
|
403
|
+
env_config_torch_show_parser = env_config_torch_subparsers.add_parser("show", help="Show current PyTorch backend")
|
|
404
|
+
env_config_torch_show_parser.set_defaults(func=env_cmds.env_config_torch_show)
|
|
405
|
+
|
|
406
|
+
# env-config torch-backend set <backend>
|
|
407
|
+
env_config_torch_set_parser = env_config_torch_subparsers.add_parser("set", help="Set PyTorch backend")
|
|
408
|
+
env_config_torch_set_parser.add_argument("backend", help="Backend to set (e.g., cu128, cpu, rocm6.3, xpu)")
|
|
409
|
+
env_config_torch_set_parser.set_defaults(func=env_cmds.env_config_torch_set)
|
|
410
|
+
|
|
411
|
+
# env-config torch-backend detect
|
|
412
|
+
env_config_torch_detect_parser = env_config_torch_subparsers.add_parser("detect", help="Auto-detect and show recommended backend")
|
|
413
|
+
env_config_torch_detect_parser.set_defaults(func=env_cmds.env_config_torch_detect)
|
|
414
|
+
|
|
389
415
|
# run - Run ComfyUI (special handling for ComfyUI args)
|
|
390
416
|
run_parser = subparsers.add_parser("run", help="Run ComfyUI")
|
|
391
417
|
run_parser.add_argument("--no-sync", action="store_true", help="Skip environment sync before running")
|
|
418
|
+
run_parser.add_argument(
|
|
419
|
+
"--torch-backend",
|
|
420
|
+
default=None,
|
|
421
|
+
metavar="BACKEND",
|
|
422
|
+
help=(
|
|
423
|
+
"PyTorch backend override (one-time, not saved). Examples: cpu, "
|
|
424
|
+
"cu128 (CUDA 12.8), cu126, cu124, rocm6.3 (AMD), xpu (Intel). "
|
|
425
|
+
"Reads from .pytorch-backend file if not specified."
|
|
426
|
+
),
|
|
427
|
+
)
|
|
392
428
|
run_parser.set_defaults(func=env_cmds.run, args=[])
|
|
393
429
|
|
|
394
430
|
# status - Show environment status
|
|
@@ -414,6 +450,25 @@ def _add_env_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
|
414
450
|
)
|
|
415
451
|
repair_parser.set_defaults(func=env_cmds.repair)
|
|
416
452
|
|
|
453
|
+
# sync - Sync environment (packages, nodes, models)
|
|
454
|
+
sync_parser = subparsers.add_parser("sync", help="Sync environment packages and dependencies")
|
|
455
|
+
sync_parser.add_argument(
|
|
456
|
+
"--torch-backend",
|
|
457
|
+
default=None,
|
|
458
|
+
metavar="BACKEND",
|
|
459
|
+
help=(
|
|
460
|
+
"PyTorch backend override (one-time, not saved). Examples: cpu, "
|
|
461
|
+
"cu128 (CUDA 12.8), cu126, cu124, rocm6.3 (AMD), xpu (Intel). "
|
|
462
|
+
"Reads from .pytorch-backend file if not specified."
|
|
463
|
+
),
|
|
464
|
+
)
|
|
465
|
+
sync_parser.add_argument(
|
|
466
|
+
"-v", "--verbose",
|
|
467
|
+
action="store_true",
|
|
468
|
+
help="Show full UV output during sync"
|
|
469
|
+
)
|
|
470
|
+
sync_parser.set_defaults(func=env_cmds.sync)
|
|
471
|
+
|
|
417
472
|
# log - Show commit history
|
|
418
473
|
log_parser = subparsers.add_parser("log", help="Show commit history")
|
|
419
474
|
log_parser.add_argument("-n", "--limit", type=int, default=20, metavar="N", help="Number of commits to show (default: 20)")
|
|
@@ -489,6 +544,11 @@ def _add_env_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
|
489
544
|
default="origin",
|
|
490
545
|
help="Git remote name (default: origin)"
|
|
491
546
|
)
|
|
547
|
+
pull_parser.add_argument(
|
|
548
|
+
"-b", "--branch",
|
|
549
|
+
default=None,
|
|
550
|
+
help="Remote branch to pull (default: current local branch). Use when remote has different default branch (e.g., master vs main)"
|
|
551
|
+
)
|
|
492
552
|
pull_parser.add_argument(
|
|
493
553
|
"--models",
|
|
494
554
|
choices=["all", "required", "skip"],
|
|
@@ -510,6 +570,16 @@ def _add_env_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
|
510
570
|
choices=["mine", "theirs"],
|
|
511
571
|
help="Auto-resolve conflicts: 'mine' keeps local, 'theirs' takes incoming"
|
|
512
572
|
)
|
|
573
|
+
pull_parser.add_argument(
|
|
574
|
+
"--torch-backend",
|
|
575
|
+
default=None,
|
|
576
|
+
metavar="BACKEND",
|
|
577
|
+
help=(
|
|
578
|
+
"PyTorch backend override (one-time, not saved). Examples: cpu, "
|
|
579
|
+
"cu128 (CUDA 12.8), cu126, cu124, rocm6.3 (AMD), xpu (Intel). "
|
|
580
|
+
"Reads from .pytorch-backend file if not specified."
|
|
581
|
+
),
|
|
582
|
+
)
|
|
513
583
|
pull_parser.set_defaults(func=env_cmds.pull)
|
|
514
584
|
|
|
515
585
|
# push - Push commits to remote
|
|
@@ -88,6 +88,45 @@ class EnvironmentCommands:
|
|
|
88
88
|
sys.exit(1)
|
|
89
89
|
return active
|
|
90
90
|
|
|
91
|
+
def _get_or_probe_backend(
|
|
92
|
+
self, env: Environment, override: str | None = None
|
|
93
|
+
) -> tuple[str, bool]:
|
|
94
|
+
"""Get torch backend from file or probe if missing.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
env: Environment to get backend for
|
|
98
|
+
override: Optional explicit backend override
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Tuple of (backend_string, was_probed) where was_probed is True
|
|
102
|
+
if we had to auto-probe because no backend was configured.
|
|
103
|
+
"""
|
|
104
|
+
if override:
|
|
105
|
+
return override, False
|
|
106
|
+
|
|
107
|
+
if env.pytorch_manager.has_backend():
|
|
108
|
+
return env.pytorch_manager.get_backend(), False
|
|
109
|
+
|
|
110
|
+
# No backend configured - probe and set it
|
|
111
|
+
print("⚠️ No PyTorch backend configured. Auto-detecting...")
|
|
112
|
+
try:
|
|
113
|
+
# Read Python version from .python-version file
|
|
114
|
+
python_version_file = env.cec_path / ".python-version"
|
|
115
|
+
python_version = (
|
|
116
|
+
python_version_file.read_text(encoding="utf-8").strip()
|
|
117
|
+
if python_version_file.exists()
|
|
118
|
+
else "3.12"
|
|
119
|
+
)
|
|
120
|
+
backend = env.pytorch_manager.probe_and_set_backend(
|
|
121
|
+
python_version,
|
|
122
|
+
backend="auto",
|
|
123
|
+
)
|
|
124
|
+
return backend, True
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"✗ Error probing PyTorch backend: {e}")
|
|
127
|
+
print(" Try setting it explicitly: cg env-config torch-backend set <backend>")
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
|
|
91
130
|
def _format_size(self, size_bytes: int) -> str:
|
|
92
131
|
"""Format bytes as human-readable size."""
|
|
93
132
|
for unit in ("B", "KB", "MB", "GB"):
|
|
@@ -270,6 +309,121 @@ class EnvironmentCommands:
|
|
|
270
309
|
|
|
271
310
|
# === Commands that operate IN environments ===
|
|
272
311
|
|
|
312
|
+
# === Environment Configuration ===
|
|
313
|
+
|
|
314
|
+
@with_env_logging("env-config torch-backend show")
|
|
315
|
+
def env_config_torch_show(self, args: argparse.Namespace, logger=None) -> None:
|
|
316
|
+
"""Show current PyTorch backend setting for this environment."""
|
|
317
|
+
env = self._get_env(args)
|
|
318
|
+
|
|
319
|
+
backend = env.pytorch_manager.get_backend()
|
|
320
|
+
backend_file = env.pytorch_manager.backend_file
|
|
321
|
+
versions = env.pytorch_manager.get_versions()
|
|
322
|
+
|
|
323
|
+
print(f"PyTorch Backend: {backend}")
|
|
324
|
+
if versions:
|
|
325
|
+
for pkg, ver in versions.items():
|
|
326
|
+
print(f" {pkg}={ver}")
|
|
327
|
+
|
|
328
|
+
if backend_file.exists():
|
|
329
|
+
print(f" Source: {backend_file}")
|
|
330
|
+
else:
|
|
331
|
+
print(" Source: auto-detected (no .pytorch-backend file)")
|
|
332
|
+
print()
|
|
333
|
+
print(f"💡 To save this setting: cg env-config torch-backend set {backend}")
|
|
334
|
+
|
|
335
|
+
@with_env_logging("env-config torch-backend set")
|
|
336
|
+
def env_config_torch_set(self, args: argparse.Namespace, logger=None) -> None:
|
|
337
|
+
"""Set PyTorch backend for this environment.
|
|
338
|
+
|
|
339
|
+
Probes for exact versions and stores both backend and version pins.
|
|
340
|
+
"""
|
|
341
|
+
from comfygit_core.utils.pytorch_prober import PyTorchProbeError
|
|
342
|
+
|
|
343
|
+
env = self._get_env(args)
|
|
344
|
+
backend = args.backend
|
|
345
|
+
|
|
346
|
+
# Validate backend format
|
|
347
|
+
if not env.pytorch_manager.is_valid_backend(backend):
|
|
348
|
+
print(f"✗ Invalid backend: {backend}")
|
|
349
|
+
print()
|
|
350
|
+
print("Valid formats:")
|
|
351
|
+
print(" • cu118, cu121, cu124, cu126, cu128 (CUDA)")
|
|
352
|
+
print(" • cpu")
|
|
353
|
+
print(" • rocm6.2, rocm6.3 (AMD)")
|
|
354
|
+
print(" • xpu (Intel)")
|
|
355
|
+
sys.exit(1)
|
|
356
|
+
|
|
357
|
+
# Read python version
|
|
358
|
+
python_version_file = env.cec_path / ".python-version"
|
|
359
|
+
python_version = (
|
|
360
|
+
python_version_file.read_text(encoding="utf-8").strip()
|
|
361
|
+
if python_version_file.exists()
|
|
362
|
+
else "3.12"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Probe and set backend with versions
|
|
366
|
+
print(f"🔍 Probing PyTorch versions for {backend} (Python {python_version})...")
|
|
367
|
+
try:
|
|
368
|
+
resolved = env.pytorch_manager.probe_and_set_backend(python_version, backend)
|
|
369
|
+
except PyTorchProbeError as e:
|
|
370
|
+
print(f"✗ Error probing PyTorch: {e}")
|
|
371
|
+
sys.exit(1)
|
|
372
|
+
|
|
373
|
+
# Show what was stored
|
|
374
|
+
versions = env.pytorch_manager.get_versions()
|
|
375
|
+
print(f"✓ PyTorch backend set to: {resolved}")
|
|
376
|
+
if versions:
|
|
377
|
+
for pkg, ver in versions.items():
|
|
378
|
+
print(f" {pkg}={ver}")
|
|
379
|
+
print()
|
|
380
|
+
print("Run 'cg sync' to apply the new backend configuration.")
|
|
381
|
+
|
|
382
|
+
@with_env_logging("env-config torch-backend detect")
|
|
383
|
+
def env_config_torch_detect(self, args: argparse.Namespace, logger=None) -> None:
|
|
384
|
+
"""Auto-detect recommended PyTorch backend using uv probe."""
|
|
385
|
+
from comfygit_core.utils.pytorch_prober import PyTorchProbeError, probe_pytorch_versions
|
|
386
|
+
|
|
387
|
+
env = self._get_env(args)
|
|
388
|
+
backend_file = env.pytorch_manager.backend_file
|
|
389
|
+
|
|
390
|
+
# Read python version from file
|
|
391
|
+
python_version_file = env.cec_path / ".python-version"
|
|
392
|
+
python_version = (
|
|
393
|
+
python_version_file.read_text(encoding="utf-8").strip()
|
|
394
|
+
if python_version_file.exists()
|
|
395
|
+
else "3.12"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Probe for recommended backend
|
|
399
|
+
print(f"🔍 Probing PyTorch compatibility for Python {python_version}...")
|
|
400
|
+
try:
|
|
401
|
+
_, detected = probe_pytorch_versions(python_version, "auto")
|
|
402
|
+
except PyTorchProbeError as e:
|
|
403
|
+
print(f"✗ Error probing PyTorch: {e}")
|
|
404
|
+
sys.exit(1)
|
|
405
|
+
|
|
406
|
+
# Get current backend (if any)
|
|
407
|
+
if env.pytorch_manager.has_backend():
|
|
408
|
+
current = env.pytorch_manager.get_backend()
|
|
409
|
+
else:
|
|
410
|
+
current = "(not configured)"
|
|
411
|
+
|
|
412
|
+
print(f"Detected backend: {detected}")
|
|
413
|
+
print(f"Current backend: {current}")
|
|
414
|
+
|
|
415
|
+
if backend_file.exists():
|
|
416
|
+
print(f" Source: {backend_file}")
|
|
417
|
+
else:
|
|
418
|
+
print(" Source: not configured")
|
|
419
|
+
|
|
420
|
+
if current != detected and current != "(not configured)":
|
|
421
|
+
print()
|
|
422
|
+
print(f"💡 Consider updating: cg env-config torch-backend set {detected}")
|
|
423
|
+
elif current == "(not configured)":
|
|
424
|
+
print()
|
|
425
|
+
print(f"💡 Set the backend: cg env-config torch-backend set {detected}")
|
|
426
|
+
|
|
273
427
|
@with_env_logging("run")
|
|
274
428
|
def run(self, args: argparse.Namespace) -> None:
|
|
275
429
|
"""Run ComfyUI in the specified environment."""
|
|
@@ -278,14 +432,32 @@ class EnvironmentCommands:
|
|
|
278
432
|
comfyui_args = args.args if hasattr(args, 'args') else []
|
|
279
433
|
no_sync = getattr(args, 'no_sync', False)
|
|
280
434
|
|
|
435
|
+
# Handle torch-backend: use override, read from file, or probe if missing
|
|
436
|
+
torch_backend_override = getattr(args, 'torch_backend', None)
|
|
437
|
+
torch_backend, was_probed = self._get_or_probe_backend(env, torch_backend_override)
|
|
438
|
+
|
|
439
|
+
if torch_backend_override:
|
|
440
|
+
print(f"🔧 Using PyTorch backend override: {torch_backend}")
|
|
441
|
+
elif was_probed:
|
|
442
|
+
print(f"✓ Backend detected and saved: {torch_backend}")
|
|
443
|
+
print(f" To change: cg env-config torch-backend set <backend>")
|
|
444
|
+
else:
|
|
445
|
+
print(f"🔧 Using PyTorch backend: {torch_backend}")
|
|
446
|
+
|
|
281
447
|
current_branch = env.get_current_branch()
|
|
282
448
|
branch_display = f" (on {current_branch})" if current_branch else " (detached HEAD)"
|
|
283
449
|
|
|
284
450
|
while True:
|
|
285
451
|
# Sync before running (unless --no-sync)
|
|
452
|
+
# Use explicit override if provided, otherwise None (backend is now in file)
|
|
286
453
|
if not no_sync:
|
|
287
454
|
print(f"🔄 Syncing environment: {env.name}")
|
|
288
|
-
env.sync(
|
|
455
|
+
env.sync(
|
|
456
|
+
preserve_workflows=True,
|
|
457
|
+
remove_extra_nodes=False,
|
|
458
|
+
backend_override=torch_backend_override if torch_backend_override else None,
|
|
459
|
+
verbose=True,
|
|
460
|
+
)
|
|
289
461
|
|
|
290
462
|
print(f"🎮 Starting ComfyUI in environment: {env.name}{branch_display}")
|
|
291
463
|
if comfyui_args:
|
|
@@ -300,6 +472,54 @@ class EnvironmentCommands:
|
|
|
300
472
|
|
|
301
473
|
sys.exit(result.returncode)
|
|
302
474
|
|
|
475
|
+
@with_env_logging("sync")
|
|
476
|
+
def sync(self, args: argparse.Namespace, logger=None) -> None:
|
|
477
|
+
"""Sync environment packages and dependencies."""
|
|
478
|
+
env = self._get_env(args)
|
|
479
|
+
|
|
480
|
+
# Handle torch-backend: use override, read from file, or probe if missing
|
|
481
|
+
torch_backend_override = getattr(args, 'torch_backend', None)
|
|
482
|
+
torch_backend, was_probed = self._get_or_probe_backend(env, torch_backend_override)
|
|
483
|
+
|
|
484
|
+
if torch_backend_override:
|
|
485
|
+
print(f"🔧 Using PyTorch backend override: {torch_backend}")
|
|
486
|
+
elif was_probed:
|
|
487
|
+
print(f"✓ Backend detected and saved: {torch_backend}")
|
|
488
|
+
print(f" To change: cg env-config torch-backend set <backend>")
|
|
489
|
+
else:
|
|
490
|
+
print(f"🔧 Using PyTorch backend: {torch_backend}")
|
|
491
|
+
|
|
492
|
+
print(f"\n🔄 Syncing environment: {env.name}")
|
|
493
|
+
|
|
494
|
+
verbose = getattr(args, 'verbose', False)
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
# Use explicit override if provided, otherwise None (backend is now in file)
|
|
498
|
+
result = env.sync(
|
|
499
|
+
dry_run=False,
|
|
500
|
+
model_strategy="skip", # Sync command focuses on packages
|
|
501
|
+
remove_extra_nodes=False, # Don't remove nodes, just sync
|
|
502
|
+
verbose=verbose,
|
|
503
|
+
backend_override=torch_backend_override if torch_backend_override else None,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
if result.success:
|
|
507
|
+
print("\n✓ Sync complete")
|
|
508
|
+
if result.packages_synced:
|
|
509
|
+
print(f" Packages synced: {result.packages_synced}")
|
|
510
|
+
if result.dependency_groups_installed:
|
|
511
|
+
print(f" Dependency groups: {', '.join(result.dependency_groups_installed)}")
|
|
512
|
+
else:
|
|
513
|
+
print("\n⚠️ Sync completed with warnings")
|
|
514
|
+
for error in result.errors:
|
|
515
|
+
print(f" • {error}")
|
|
516
|
+
|
|
517
|
+
except Exception as e:
|
|
518
|
+
if logger:
|
|
519
|
+
logger.error(f"Sync failed: {e}", exc_info=True)
|
|
520
|
+
print(f"\n✗ Sync failed: {e}", file=sys.stderr)
|
|
521
|
+
sys.exit(1)
|
|
522
|
+
|
|
303
523
|
def manifest(self, args: argparse.Namespace) -> None:
|
|
304
524
|
"""Show environment manifest (pyproject.toml configuration)."""
|
|
305
525
|
env = self._get_env(args)
|
|
@@ -1997,10 +2217,11 @@ class EnvironmentCommands:
|
|
|
1997
2217
|
sys.exit(1)
|
|
1998
2218
|
|
|
1999
2219
|
# Preview mode - read-only, just show what would change
|
|
2220
|
+
branch = getattr(args, 'branch', None)
|
|
2000
2221
|
if getattr(args, "preview", False):
|
|
2001
2222
|
try:
|
|
2002
2223
|
print(f"Fetching from {args.remote}...")
|
|
2003
|
-
diff = env.preview_pull(remote=args.remote)
|
|
2224
|
+
diff = env.preview_pull(remote=args.remote, branch=branch)
|
|
2004
2225
|
|
|
2005
2226
|
if not diff.has_changes:
|
|
2006
2227
|
if diff.is_already_merged:
|
|
@@ -2044,7 +2265,7 @@ class EnvironmentCommands:
|
|
|
2044
2265
|
else:
|
|
2045
2266
|
# Check for conflicts before pull
|
|
2046
2267
|
print(f"Checking for conflicts with {args.remote}...")
|
|
2047
|
-
diff = env.preview_pull(remote=args.remote)
|
|
2268
|
+
diff = env.preview_pull(remote=args.remote, branch=branch)
|
|
2048
2269
|
if diff.has_conflicts:
|
|
2049
2270
|
# Interactive conflict resolution
|
|
2050
2271
|
from .strategies.conflict_resolver import InteractiveConflictResolver
|
|
@@ -2073,6 +2294,18 @@ class EnvironmentCommands:
|
|
|
2073
2294
|
|
|
2074
2295
|
print(f"📥 Pulling from {args.remote}...")
|
|
2075
2296
|
|
|
2297
|
+
# Handle torch-backend: use override, read from file, or probe if missing
|
|
2298
|
+
torch_backend_override = getattr(args, 'torch_backend', None)
|
|
2299
|
+
torch_backend, was_probed = self._get_or_probe_backend(env, torch_backend_override)
|
|
2300
|
+
|
|
2301
|
+
if torch_backend_override:
|
|
2302
|
+
print(f"🔧 Using PyTorch backend override: {torch_backend}")
|
|
2303
|
+
elif was_probed:
|
|
2304
|
+
print(f"🔧 Auto-detected PyTorch backend: {torch_backend}")
|
|
2305
|
+
print(f" To save: cg env-config torch-backend set {torch_backend}")
|
|
2306
|
+
else:
|
|
2307
|
+
print(f"🔧 Using PyTorch backend: {torch_backend}")
|
|
2308
|
+
|
|
2076
2309
|
# Create callbacks for node and model progress (reuse repair command patterns)
|
|
2077
2310
|
from comfygit_core.models.workflow import BatchDownloadCallbacks, NodeInstallCallbacks
|
|
2078
2311
|
from .utils.progress import create_progress_callback
|
|
@@ -2113,11 +2346,13 @@ class EnvironmentCommands:
|
|
|
2113
2346
|
force = getattr(args, 'force', False)
|
|
2114
2347
|
result = env.pull_and_repair(
|
|
2115
2348
|
remote=args.remote,
|
|
2349
|
+
branch=branch,
|
|
2116
2350
|
model_strategy=getattr(args, 'models', 'all'),
|
|
2117
2351
|
model_callbacks=model_callbacks,
|
|
2118
2352
|
node_callbacks=node_callbacks,
|
|
2119
2353
|
strategy_option=strategy_option,
|
|
2120
2354
|
force=force,
|
|
2355
|
+
backend_override=torch_backend,
|
|
2121
2356
|
)
|
|
2122
2357
|
|
|
2123
2358
|
# Extract sync result for summary
|
|
@@ -94,7 +94,6 @@ class GlobalCommands:
|
|
|
94
94
|
- uv_cache/ for package management
|
|
95
95
|
- environments/ for ComfyUI environments
|
|
96
96
|
"""
|
|
97
|
-
from pathlib import Path
|
|
98
97
|
|
|
99
98
|
# Validate models directory if provided (before creating workspace)
|
|
100
99
|
explicit_models_dir = getattr(args, 'models_dir', None)
|
|
@@ -193,7 +192,7 @@ class GlobalCommands:
|
|
|
193
192
|
logger.info(f"Installed system node: {node_name}")
|
|
194
193
|
except Exception as e:
|
|
195
194
|
print(f" ⚠️ Failed to install {node_name}: {e}")
|
|
196
|
-
print(
|
|
195
|
+
print(" You can install it manually later")
|
|
197
196
|
logger.warning(f"Failed to install system node {node_name}: {e}")
|
|
198
197
|
|
|
199
198
|
def _show_workspace_env_setup(self, workspace_path: Path) -> None:
|
|
@@ -231,8 +230,7 @@ class GlobalCommands:
|
|
|
231
230
|
args: CLI arguments containing models_dir and yes flags
|
|
232
231
|
"""
|
|
233
232
|
from pathlib import Path
|
|
234
|
-
|
|
235
|
-
from comfygit_core.utils.common import format_size
|
|
233
|
+
|
|
236
234
|
|
|
237
235
|
# Check for explicit flags
|
|
238
236
|
use_interactive = not getattr(args, 'yes', False)
|
|
@@ -256,7 +254,7 @@ class GlobalCommands:
|
|
|
256
254
|
print("\nOptions:")
|
|
257
255
|
print(" 1. Point to an existing ComfyUI models directory (recommended)")
|
|
258
256
|
print(" → Access all your existing models immediately")
|
|
259
|
-
print(
|
|
257
|
+
print(" → Example: ~/ComfyUI/models")
|
|
260
258
|
print("\n 2. Use the default empty directory")
|
|
261
259
|
print(f" → ComfyGit created: {workspace.paths.models}")
|
|
262
260
|
print(" → Download models as needed later")
|
|
@@ -296,7 +294,7 @@ class GlobalCommands:
|
|
|
296
294
|
# Auto-detect if they entered ComfyUI root instead of models subdir
|
|
297
295
|
if (models_path / "models").exists() and models_path.name != "models":
|
|
298
296
|
print(f"\n⚠️ Detected ComfyUI installation at: {models_path}")
|
|
299
|
-
use_subdir = input(
|
|
297
|
+
use_subdir = input("Use models/ subdirectory instead? (Y/n): ").strip().lower()
|
|
300
298
|
if use_subdir != 'n':
|
|
301
299
|
models_path = models_path / "models"
|
|
302
300
|
print(f"Using: {models_path}")
|
|
@@ -322,9 +320,10 @@ class GlobalCommands:
|
|
|
322
320
|
workspace: The workspace instance
|
|
323
321
|
models_path: Path to the models directory to scan
|
|
324
322
|
"""
|
|
325
|
-
from comfygit_cli.utils.progress import create_model_sync_progress
|
|
326
323
|
from comfygit_core.utils.common import format_size
|
|
327
324
|
|
|
325
|
+
from comfygit_cli.utils.progress import create_model_sync_progress
|
|
326
|
+
|
|
328
327
|
try:
|
|
329
328
|
progress = create_model_sync_progress()
|
|
330
329
|
workspace.set_models_directory(models_path, progress=progress)
|
|
@@ -404,7 +403,7 @@ class GlobalCommands:
|
|
|
404
403
|
|
|
405
404
|
# Read log lines
|
|
406
405
|
try:
|
|
407
|
-
with open(log_file,
|
|
406
|
+
with open(log_file, encoding='utf-8') as f:
|
|
408
407
|
lines = f.readlines()
|
|
409
408
|
except Exception as e:
|
|
410
409
|
print(f"✗ Failed to read log file: {e}", file=sys.stderr)
|
|
@@ -453,9 +452,9 @@ class GlobalCommands:
|
|
|
453
452
|
for line in record:
|
|
454
453
|
print(line.rstrip())
|
|
455
454
|
|
|
456
|
-
print(
|
|
455
|
+
print("\n=== End of logs ===")
|
|
457
456
|
if not args.full and len(records) == args.lines:
|
|
458
|
-
print(
|
|
457
|
+
print("Tip: Use --full to see all logs, or increase --lines to see more")
|
|
459
458
|
|
|
460
459
|
@with_workspace_logging("migrate")
|
|
461
460
|
def migrate(self, args: argparse.Namespace) -> None:
|
|
@@ -1519,10 +1518,10 @@ class GlobalCommands:
|
|
|
1519
1518
|
def orch_status(self, args: argparse.Namespace) -> None:
|
|
1520
1519
|
"""Show orchestrator status."""
|
|
1521
1520
|
from .utils.orchestrator import (
|
|
1521
|
+
format_uptime,
|
|
1522
|
+
get_orchestrator_uptime,
|
|
1522
1523
|
is_orchestrator_running,
|
|
1523
1524
|
read_switch_status,
|
|
1524
|
-
get_orchestrator_uptime,
|
|
1525
|
-
format_uptime
|
|
1526
1525
|
)
|
|
1527
1526
|
|
|
1528
1527
|
metadata_dir = self.workspace.path / ".metadata"
|
|
@@ -1578,7 +1577,7 @@ class GlobalCommands:
|
|
|
1578
1577
|
try:
|
|
1579
1578
|
port = control_port_file.read_text().strip()
|
|
1580
1579
|
print(f"Control Port: {port}")
|
|
1581
|
-
except
|
|
1580
|
+
except OSError:
|
|
1582
1581
|
pass
|
|
1583
1582
|
|
|
1584
1583
|
# Check switch status
|
|
@@ -1605,6 +1604,7 @@ class GlobalCommands:
|
|
|
1605
1604
|
def orch_restart(self, args: argparse.Namespace) -> None:
|
|
1606
1605
|
"""Request orchestrator to restart ComfyUI."""
|
|
1607
1606
|
import time
|
|
1607
|
+
|
|
1608
1608
|
from .utils.orchestrator import is_orchestrator_running, safe_write_command
|
|
1609
1609
|
|
|
1610
1610
|
metadata_dir = self.workspace.path / ".metadata"
|
|
@@ -1643,11 +1643,12 @@ class GlobalCommands:
|
|
|
1643
1643
|
def orch_kill(self, args: argparse.Namespace) -> None:
|
|
1644
1644
|
"""Shutdown orchestrator."""
|
|
1645
1645
|
import time
|
|
1646
|
+
|
|
1646
1647
|
from .utils.orchestrator import (
|
|
1647
1648
|
is_orchestrator_running,
|
|
1648
|
-
safe_write_command,
|
|
1649
1649
|
kill_orchestrator_process,
|
|
1650
|
-
read_switch_status
|
|
1650
|
+
read_switch_status,
|
|
1651
|
+
safe_write_command,
|
|
1651
1652
|
)
|
|
1652
1653
|
|
|
1653
1654
|
metadata_dir = self.workspace.path / ".metadata"
|
|
@@ -1701,9 +1702,9 @@ class GlobalCommands:
|
|
|
1701
1702
|
def orch_clean(self, args: argparse.Namespace) -> None:
|
|
1702
1703
|
"""Clean orchestrator state files."""
|
|
1703
1704
|
from .utils.orchestrator import (
|
|
1705
|
+
cleanup_orchestrator_state,
|
|
1704
1706
|
is_orchestrator_running,
|
|
1705
1707
|
kill_orchestrator_process,
|
|
1706
|
-
cleanup_orchestrator_state
|
|
1707
1708
|
)
|
|
1708
1709
|
|
|
1709
1710
|
metadata_dir = self.workspace.path / ".metadata"
|
|
@@ -1742,7 +1743,7 @@ class GlobalCommands:
|
|
|
1742
1743
|
print("\nNote: workspace_config.json will be preserved")
|
|
1743
1744
|
|
|
1744
1745
|
if args.kill:
|
|
1745
|
-
print(
|
|
1746
|
+
print("\n⚠️ --kill flag: Will also terminate orchestrator process")
|
|
1746
1747
|
|
|
1747
1748
|
response = input("\nContinue? [y/N]: ").strip().lower()
|
|
1748
1749
|
if response not in ['y', 'yes']:
|
|
@@ -1784,6 +1785,7 @@ class GlobalCommands:
|
|
|
1784
1785
|
def orch_logs(self, args: argparse.Namespace) -> None:
|
|
1785
1786
|
"""Show orchestrator logs."""
|
|
1786
1787
|
import subprocess
|
|
1788
|
+
|
|
1787
1789
|
from .utils.orchestrator import tail_log_file
|
|
1788
1790
|
|
|
1789
1791
|
metadata_dir = self.workspace.path / ".metadata"
|
|
@@ -1808,3 +1810,4 @@ class GlobalCommands:
|
|
|
1808
1810
|
print("".join(lines))
|
|
1809
1811
|
else:
|
|
1810
1812
|
print("(empty log file)")
|
|
1813
|
+
|