comfygit 0.4.0__tar.gz → 0.4.2__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.
Files changed (76) hide show
  1. {comfygit-0.4.0 → comfygit-0.4.2}/PKG-INFO +2 -2
  2. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/cli.py +90 -0
  3. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/env_commands.py +149 -6
  4. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/serve_runtime.py +16 -2
  5. comfygit-0.4.2/comfygit_cli/studio_static/assets/index-AxBlFaca.js +17 -0
  6. comfygit-0.4.2/comfygit_cli/studio_static/assets/index-BDmIh9tA.css +1 -0
  7. {comfygit-0.4.0/comfygit_cli/contract_studio_static → comfygit-0.4.2/comfygit_cli/studio_static}/index.html +2 -2
  8. {comfygit-0.4.0 → comfygit-0.4.2}/pyproject.toml +2 -3
  9. comfygit-0.4.0/comfygit_cli/contract_studio_static/assets/index-BrjwrxCF.js +0 -17
  10. comfygit-0.4.0/comfygit_cli/contract_studio_static/assets/index-U1BNuI3X.css +0 -1
  11. comfygit-0.4.0/contract-studio/AGENTS.md +0 -52
  12. comfygit-0.4.0/contract-studio/components.json +0 -21
  13. comfygit-0.4.0/contract-studio/index.html +0 -13
  14. comfygit-0.4.0/contract-studio/package-lock.json +0 -6926
  15. comfygit-0.4.0/contract-studio/package.json +0 -36
  16. comfygit-0.4.0/contract-studio/src/App.tsx +0 -429
  17. comfygit-0.4.0/contract-studio/src/app/components.tsx +0 -371
  18. comfygit-0.4.0/contract-studio/src/components/ContractInputControl.tsx +0 -233
  19. comfygit-0.4.0/contract-studio/src/components/GalleryTile.tsx +0 -142
  20. comfygit-0.4.0/contract-studio/src/components/OutputViewer.tsx +0 -275
  21. comfygit-0.4.0/contract-studio/src/components/ReadoutBlock.tsx +0 -45
  22. comfygit-0.4.0/contract-studio/src/components/ui/button.tsx +0 -164
  23. comfygit-0.4.0/contract-studio/src/components/ui/select-content.tsx +0 -320
  24. comfygit-0.4.0/contract-studio/src/components/ui/select.tsx +0 -449
  25. comfygit-0.4.0/contract-studio/src/components/ui/tooltip.tsx +0 -123
  26. comfygit-0.4.0/contract-studio/src/hooks/use-proximity-hover.ts +0 -185
  27. comfygit-0.4.0/contract-studio/src/lib/api.ts +0 -93
  28. comfygit-0.4.0/contract-studio/src/lib/clipboard.ts +0 -23
  29. comfygit-0.4.0/contract-studio/src/lib/font-weight.ts +0 -6
  30. comfygit-0.4.0/contract-studio/src/lib/format.ts +0 -87
  31. comfygit-0.4.0/contract-studio/src/lib/gallery.ts +0 -169
  32. comfygit-0.4.0/contract-studio/src/lib/icon-context.tsx +0 -7
  33. comfygit-0.4.0/contract-studio/src/lib/inputs.ts +0 -76
  34. comfygit-0.4.0/contract-studio/src/lib/shape-context.tsx +0 -115
  35. comfygit-0.4.0/contract-studio/src/lib/springs.ts +0 -17
  36. comfygit-0.4.0/contract-studio/src/lib/utils.ts +0 -6
  37. comfygit-0.4.0/contract-studio/src/main.tsx +0 -13
  38. comfygit-0.4.0/contract-studio/src/styles.css +0 -1601
  39. comfygit-0.4.0/contract-studio/src/types.ts +0 -183
  40. comfygit-0.4.0/contract-studio/src/vite-env.d.ts +0 -3
  41. comfygit-0.4.0/contract-studio/tsconfig.json +0 -24
  42. comfygit-0.4.0/contract-studio/vite.config.ts +0 -28
  43. {comfygit-0.4.0 → comfygit-0.4.2}/.gitignore +0 -0
  44. {comfygit-0.4.0 → comfygit-0.4.2}/LICENSE.txt +0 -0
  45. {comfygit-0.4.0 → comfygit-0.4.2}/README.md +0 -0
  46. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/__init__.py +0 -0
  47. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/__main__.py +0 -0
  48. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/cli_utils.py +0 -0
  49. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/completers.py +0 -0
  50. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/completion_commands.py +0 -0
  51. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/formatters/__init__.py +0 -0
  52. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/formatters/error_formatter.py +0 -0
  53. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/global_commands.py +0 -0
  54. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/interactive/__init__.py +0 -0
  55. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/logging/compressed_handler.py +0 -0
  56. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/logging/environment_logger.py +0 -0
  57. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/logging/log_compressor.py +0 -0
  58. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/logging/logging_config.py +0 -0
  59. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/resolution_strategies.py +0 -0
  60. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/serve_executor.py +0 -0
  61. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/serve_state.py +0 -0
  62. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/strategies/__init__.py +0 -0
  63. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/strategies/conflict_resolver.py +0 -0
  64. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/strategies/interactive.py +0 -0
  65. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/strategies/rollback.py +0 -0
  66. {comfygit-0.4.0/comfygit_cli/contract_studio_static → comfygit-0.4.2/comfygit_cli/studio_static}/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  67. {comfygit-0.4.0/comfygit_cli/contract_studio_static → comfygit-0.4.2/comfygit_cli/studio_static}/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  68. {comfygit-0.4.0/comfygit_cli/contract_studio_static → comfygit-0.4.2/comfygit_cli/studio_static}/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  69. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/update_commands.py +0 -0
  70. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/utils/__init__.py +0 -0
  71. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/utils/civitai_errors.py +0 -0
  72. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/utils/orchestrator.py +0 -0
  73. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/utils/pagination.py +0 -0
  74. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/utils/progress.py +0 -0
  75. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/utils/update_checker.py +0 -0
  76. {comfygit-0.4.0 → comfygit-0.4.2}/comfygit_cli/utils/update_notice.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfygit
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: ComfyGit - Git-based environment management for ComfyUI
5
5
  Project-URL: Documentation, https://docs.comfygit.org/
6
6
  Project-URL: Repository, https://github.com/comfygit-ai/comfygit
@@ -9,7 +9,7 @@ License-File: LICENSE.txt
9
9
  Requires-Python: >=3.10
10
10
  Requires-Dist: aiohttp>=3.13.4
11
11
  Requires-Dist: argcomplete>=3.5.0
12
- Requires-Dist: comfygit-core==0.4.0
12
+ Requires-Dist: comfygit-core==0.4.2
13
13
  Requires-Dist: requests>=2.33.0
14
14
  Description-Content-Type: text/markdown
15
15
 
@@ -978,6 +978,37 @@ def _add_env_commands(subparsers: argparse._SubParsersAction) -> None:
978
978
  )
979
979
  node_add_parser.set_defaults(func=env_cmds.node_add)
980
980
 
981
+ # node dev-link
982
+ node_dev_link_parser = node_subparsers.add_parser(
983
+ "dev-link",
984
+ help="Link a tracked node to a local development checkout",
985
+ )
986
+ node_dev_link_parser.add_argument(
987
+ "node_name",
988
+ help="Existing node identifier/name, or identifier to use for a new dev node",
989
+ ).completer = installed_node_completer # type: ignore[attr-defined]
990
+ node_dev_link_parser.add_argument(
991
+ "--path",
992
+ required=True,
993
+ type=Path,
994
+ help="Path to the local custom-node development checkout",
995
+ )
996
+ node_dev_link_parser.add_argument(
997
+ "--name",
998
+ help="custom_nodes directory/symlink name to use when no tracked node exists",
999
+ )
1000
+ node_dev_link_parser.add_argument(
1001
+ "--replace-existing",
1002
+ action="store_true",
1003
+ help="Archive an existing materialized node directory and replace it with a symlink",
1004
+ )
1005
+ node_dev_link_parser.add_argument(
1006
+ "--force",
1007
+ action="store_true",
1008
+ help="Allow replacing an existing mismatched symlink or directory",
1009
+ )
1010
+ node_dev_link_parser.set_defaults(func=env_cmds.node_dev_link)
1011
+
981
1012
  # node remove
982
1013
  node_remove_parser = node_subparsers.add_parser("remove", help="Remove custom node(s)")
983
1014
  node_remove_parser.add_argument("node_names", nargs="+", help="Node registry ID(s) or name(s)").completer = installed_node_completer # type: ignore[attr-defined]
@@ -1030,6 +1061,65 @@ def _add_env_commands(subparsers: argparse._SubParsersAction) -> None:
1030
1061
  )
1031
1062
  workflow_importance_parser.set_defaults(func=_make_help_func(workflow_importance_parser))
1032
1063
 
1064
+ model_list_parser = workflow_model_subparsers.add_parser(
1065
+ "list",
1066
+ help="List models declared for a workflow"
1067
+ )
1068
+ model_list_parser.add_argument(
1069
+ "workflow_name",
1070
+ nargs="?",
1071
+ help="Workflow name (interactive if omitted)"
1072
+ ).completer = workflow_completer # type: ignore[attr-defined]
1073
+ model_list_parser.set_defaults(func=env_cmds.workflow_model_list)
1074
+
1075
+ model_add_parser = workflow_model_subparsers.add_parser(
1076
+ "add",
1077
+ help="Declare an indexed local model as required by a workflow"
1078
+ )
1079
+ model_add_parser.add_argument(
1080
+ "workflow_name",
1081
+ help="Workflow name"
1082
+ ).completer = workflow_completer # type: ignore[attr-defined]
1083
+ model_add_parser.add_argument(
1084
+ "--hash",
1085
+ dest="model_hash",
1086
+ help="Indexed model hash to attach"
1087
+ )
1088
+ model_add_parser.add_argument(
1089
+ "--path",
1090
+ dest="relative_path",
1091
+ help="Model path relative to the models directory, e.g. checkpoints/model.safetensors"
1092
+ )
1093
+ model_add_parser.add_argument(
1094
+ "--importance",
1095
+ "--criticality",
1096
+ dest="importance",
1097
+ default="required",
1098
+ choices=["required", "flexible", "optional"],
1099
+ help="Workflow dependency importance (default: required)"
1100
+ )
1101
+ model_add_parser.set_defaults(func=env_cmds.workflow_model_add)
1102
+
1103
+ model_remove_parser = workflow_model_subparsers.add_parser(
1104
+ "remove",
1105
+ help="Remove a manually declared workflow model"
1106
+ )
1107
+ model_remove_parser.add_argument(
1108
+ "workflow_name",
1109
+ help="Workflow name"
1110
+ ).completer = workflow_completer # type: ignore[attr-defined]
1111
+ model_remove_parser.add_argument(
1112
+ "--hash",
1113
+ dest="model_hash",
1114
+ help="Manual model hash to remove"
1115
+ )
1116
+ model_remove_parser.add_argument(
1117
+ "--path",
1118
+ dest="relative_path",
1119
+ help="Manual model path relative to the models directory"
1120
+ )
1121
+ model_remove_parser.set_defaults(func=env_cmds.workflow_model_remove)
1122
+
1033
1123
  importance_parser = workflow_model_subparsers.add_parser(
1034
1124
  "importance",
1035
1125
  help="Set model importance (required/flexible/optional)"
@@ -1799,6 +1799,50 @@ packages = []
1799
1799
 
1800
1800
  print(f"\nRun 'cg -e {env.name} status' to review changes")
1801
1801
 
1802
+ @with_env_logging("node dev-link")
1803
+ def node_dev_link(self, args: argparse.Namespace, logger=None) -> None:
1804
+ """Link a tracked or new custom node to a local development checkout."""
1805
+ env = self._get_env(args)
1806
+
1807
+ print(f"🔗 Linking development node: {args.node_name}")
1808
+
1809
+ try:
1810
+ result = env.link_development_node(
1811
+ args.node_name,
1812
+ args.path,
1813
+ name=getattr(args, "name", None),
1814
+ replace_existing=getattr(args, "replace_existing", False),
1815
+ force=getattr(args, "force", False),
1816
+ )
1817
+ except CDNodeConflictError as e:
1818
+ if logger:
1819
+ logger.error(f"Node dev-link conflict for '{args.node_name}': {e}", exc_info=True)
1820
+ print(f"✗ Cannot link development node '{args.node_name}'", file=sys.stderr)
1821
+ print(f" {e}", file=sys.stderr)
1822
+ sys.exit(1)
1823
+ except Exception as e:
1824
+ if logger:
1825
+ logger.error(f"Node dev-link failed for '{args.node_name}': {e}", exc_info=True)
1826
+ print(f"✗ Failed to link development node '{args.node_name}'", file=sys.stderr)
1827
+ print(f" {e}", file=sys.stderr)
1828
+ sys.exit(1)
1829
+
1830
+ if result.already_linked and not result.requirements_changed:
1831
+ print(f"✓ Development node '{result.name}' is already linked")
1832
+ else:
1833
+ print(f"✓ Development node '{result.name}' linked and tracked")
1834
+
1835
+ print(f" Identifier: {result.identifier}")
1836
+ print(f" Link: {result.link_path} -> {result.source_path}")
1837
+ if result.backup_path:
1838
+ print(f" Archived previous copy: {result.backup_path}")
1839
+ if result.requirements_changed:
1840
+ print(" Python requirements updated")
1841
+ if result.needs_restart:
1842
+ print(" Restart ComfyUI for Python node changes to load")
1843
+
1844
+ print(f"\nRun 'cg -e {env.name} status' to review changes")
1845
+
1802
1846
  @with_env_logging("node remove")
1803
1847
  def node_remove(self, args: argparse.Namespace, logger=None) -> None:
1804
1848
  """Remove custom node(s) - handles filesystem immediately."""
@@ -3424,6 +3468,90 @@ packages = []
3424
3468
 
3425
3469
  print(f"\n✓ Updated {updated_count}/{len(models)} model(s)")
3426
3470
 
3471
+ @with_env_logging("workflow model list", get_env_name=lambda self, args: self._get_env(args).name)
3472
+ def workflow_model_list(self, args: argparse.Namespace, logger=None) -> None:
3473
+ """List models declared for a workflow."""
3474
+ env = self._get_env(args)
3475
+ workflow_name = getattr(args, "workflow_name", None)
3476
+ if not workflow_name:
3477
+ workflow_name = self._select_workflow_interactive(env)
3478
+ if not workflow_name:
3479
+ print("✗ No workflow selected")
3480
+ return
3481
+
3482
+ models = env.pyproject.workflows.get_workflow_models(workflow_name)
3483
+ if not models:
3484
+ print(f"✗ No models found in workflow '{workflow_name}'")
3485
+ return
3486
+
3487
+ print(f"\n📦 Models for workflow: {workflow_name}")
3488
+ for model in models:
3489
+ manual = getattr(model, "declared_by", None) == "manual" or not getattr(model, "nodes", None)
3490
+ print(f"\n • {model.filename}")
3491
+ if model.hash:
3492
+ print(f" hash: {model.hash}")
3493
+ if model.relative_path:
3494
+ print(f" path: {model.relative_path}")
3495
+ print(f" category: {model.category}")
3496
+ print(f" importance: {model.criticality}")
3497
+ print(f" status: {model.status}")
3498
+ print(f" declared: {'manual' if manual else 'workflow graph'}")
3499
+
3500
+ @with_env_logging("workflow model add", get_env_name=lambda self, args: self._get_env(args).name)
3501
+ def workflow_model_add(self, args: argparse.Namespace, logger=None) -> None:
3502
+ """Declare an indexed local model as a manual workflow dependency."""
3503
+ env = self._get_env(args)
3504
+ model_hash = getattr(args, "model_hash", None)
3505
+ relative_path = getattr(args, "relative_path", None)
3506
+ importance = getattr(args, "importance", "required")
3507
+
3508
+ if not model_hash and not relative_path:
3509
+ print("✗ Provide --hash or --path for the indexed model to add")
3510
+ sys.exit(1)
3511
+
3512
+ try:
3513
+ model = env.workflow_manager.add_existing_model_to_workflow(
3514
+ workflow_name=args.workflow_name,
3515
+ model_hash=model_hash,
3516
+ relative_path=relative_path,
3517
+ criticality=importance,
3518
+ )
3519
+ except ValueError as e:
3520
+ print(f"✗ {e}")
3521
+ sys.exit(1)
3522
+
3523
+ identifier = model.relative_path or model.hash or model.filename
3524
+ print(f"✓ Added manual model dependency to '{args.workflow_name}': {identifier}")
3525
+ print(f" importance: {model.criticality}")
3526
+
3527
+ @with_env_logging("workflow model remove", get_env_name=lambda self, args: self._get_env(args).name)
3528
+ def workflow_model_remove(self, args: argparse.Namespace, logger=None) -> None:
3529
+ """Remove a manually declared workflow model dependency."""
3530
+ env = self._get_env(args)
3531
+ model_hash = getattr(args, "model_hash", None)
3532
+ relative_path = getattr(args, "relative_path", None)
3533
+
3534
+ if not model_hash and not relative_path:
3535
+ print("✗ Provide --hash or --path for the manual model to remove")
3536
+ sys.exit(1)
3537
+
3538
+ try:
3539
+ removed = env.workflow_manager.remove_manual_model_from_workflow(
3540
+ workflow_name=args.workflow_name,
3541
+ model_hash=model_hash,
3542
+ relative_path=relative_path,
3543
+ )
3544
+ except ValueError as e:
3545
+ print(f"✗ {e}")
3546
+ sys.exit(1)
3547
+
3548
+ if not removed:
3549
+ print(f"✗ Manual model not found in workflow '{args.workflow_name}'")
3550
+ sys.exit(1)
3551
+
3552
+ identifier = relative_path or model_hash
3553
+ print(f"✓ Removed manual model dependency from '{args.workflow_name}': {identifier}")
3554
+
3427
3555
  def _select_workflow_interactive(self, env) -> str | None:
3428
3556
  """Interactive workflow selection from available workflows.
3429
3557
 
@@ -3782,7 +3910,7 @@ packages = []
3782
3910
  def metadata_refresh(self, args: argparse.Namespace, logger: Any = None) -> None:
3783
3911
  """Refresh extracted metadata from ComfyUI installation.
3784
3912
 
3785
- Re-extracts builtins and folder paths for the current environment.
3913
+ Re-extracts builtins, folder paths, and model loader metadata for the current environment.
3786
3914
  Useful after upgrading ComfyUI or to fix missing metadata files.
3787
3915
  """
3788
3916
  env = self._get_env(args)
@@ -3792,17 +3920,32 @@ packages = []
3792
3920
  try:
3793
3921
  result = env.refresh_metadata()
3794
3922
 
3795
- if result["builtins_refreshed"]:
3796
- print(f" Refreshed comfyui_builtins.json ({result['builtins_count']} nodes)")
3923
+ if result.get("builtins_refreshed"):
3924
+ print(f" Refreshed comfyui_builtins.json ({result.get('builtins_count', 0)} nodes)")
3797
3925
  else:
3798
3926
  print(" Failed to refresh comfyui_builtins.json")
3799
3927
 
3800
- if result["folder_paths_refreshed"]:
3801
- print(f" Refreshed comfyui_folder_paths.json ({result['folder_mappings_count']} folder types)")
3928
+ if result.get("folder_paths_refreshed"):
3929
+ print(
3930
+ f" Refreshed comfyui_folder_paths.json "
3931
+ f"({result.get('folder_mappings_count', 0)} folder types)"
3932
+ )
3802
3933
  else:
3803
3934
  print(" Failed to refresh comfyui_folder_paths.json")
3804
3935
 
3805
- if result["builtins_refreshed"] or result["folder_paths_refreshed"]:
3936
+ if result.get("model_loaders_refreshed"):
3937
+ print(
3938
+ f" Refreshed comfyui_model_loaders.json "
3939
+ f"({result.get('model_loaders_count', 0)} model loaders)"
3940
+ )
3941
+ elif "model_loaders_refreshed" in result:
3942
+ print(" Failed to refresh comfyui_model_loaders.json")
3943
+
3944
+ if (
3945
+ result.get("builtins_refreshed")
3946
+ or result.get("folder_paths_refreshed")
3947
+ or result.get("model_loaders_refreshed")
3948
+ ):
3806
3949
  print("\nMetadata refreshed successfully")
3807
3950
  else:
3808
3951
  print("\nNo metadata was refreshed", file=sys.stderr)
@@ -576,14 +576,28 @@ def _serve_artifact_dir(env: Environment, config: ServeConfig) -> Path:
576
576
 
577
577
 
578
578
  def _studio_static_dir() -> Path:
579
- return Path(str(resources.files("comfygit_cli").joinpath("contract_studio_static")))
579
+ return Path(str(resources.files("comfygit_cli").joinpath("studio_static")))
580
580
 
581
581
 
582
582
  async def studio_index_handler(request: web.Request) -> web.StreamResponse:
583
583
  static_dir = request.app[STUDIO_STATIC_DIR_KEY]
584
584
  index_path = static_dir / "index.html"
585
585
  if index_path.exists():
586
- return web.FileResponse(index_path)
586
+ html = index_path.read_text(encoding="utf-8")
587
+ env_name = getattr(_state(request).env, "name", "Environment")
588
+ config = {
589
+ "apiBasePath": "",
590
+ "authMode": "none",
591
+ "endpointName": env_name if isinstance(env_name, str) else "Environment",
592
+ }
593
+ script = f"<script>window.__COMFYGIT_STUDIO_CONFIG__ = {json.dumps(config)};</script>"
594
+ if "<head>" in html:
595
+ html = html.replace("<head>", f"<head>{script}", 1)
596
+ elif "</head>" in html:
597
+ html = html.replace("</head>", f"{script}</head>", 1)
598
+ else:
599
+ html = f"{script}{html}"
600
+ return web.Response(text=html, content_type="text/html")
587
601
  return web.Response(
588
602
  text=(
589
603
  "<!doctype html><html><head><title>ComfyGit Studio</title></head>"