comfy-cli 1.23.0__tar.gz → 1.24.0__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.
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/PKG-INFO +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/builder_api.py +20 -4
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cloud/command.py +44 -16
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cmdline.py +70 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/comfy_client.py +12 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build.py +97 -11
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy.py +17 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_ls.py +14 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_types.py +48 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_up.py +124 -18
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/app.py +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/client.py +5 -8
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/schema.py +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/jobs.py +555 -106
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/node_deps.py +71 -16
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/run/__init__.py +248 -41
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/run/execution.py +70 -3
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/run/preflight.py +18 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/templates.py +3 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/transfer.py +63 -7
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/workflow.py +139 -8
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/workflow_edit.py +8 -5
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/engine.py +230 -27
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/loader.py +5 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/promoted.py +907 -130
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/credentials.py +114 -62
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/deploy_api.py +23 -4
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/deploy_api_errors.py +8 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/error_codes.py +63 -4
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/jobs_state.py +19 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/layout.py +27 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/model_variants.py +48 -5
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_ls.json +4 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/error_codes.md +4 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/jobs.json +8 -3
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/node_deps.json +2 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/run.json +6 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/workflow.json +11 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy/SKILL.md +43 -5
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-debug/SKILL.md +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-deploy/SKILL.md +10 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/target.py +3 -2
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/tracking.py +5 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/workflow_ops.py +560 -20
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/workflow_print.py +647 -146
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/workflow_to_api.py +907 -108
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli.egg-info/PKG-INFO +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/pyproject.toml +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/LICENSE +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/README.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/__main__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/_lazy.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/_safe_exec.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/agent/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/agent/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/auth/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/auth/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/auth/store.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/builder_pagination.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/caller.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cancellation.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cloud/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cloud/billing.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cloud/oauth.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/_cloud_errors.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/assets_library.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_diff.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_digest_cache.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_package.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_paths.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_pull.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_push.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_spec.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_targets.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_upload_progress.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/build_validation.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/cloud_http.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/code_search.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/custom_nodes/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/custom_nodes/bisect_custom_nodes.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/custom_nodes/cm_cli_util.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/custom_nodes/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_cancel.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_compute.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_lifecycle.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_progress.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_promote.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_read.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_refs.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_rename.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_resolve.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_rollback.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_run.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_run_assets.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_run_watch.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_runtime.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_status.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/deploy_workflow.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/adapters.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/emit.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/output.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/poll.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/spec/openapi.yml +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/spec.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/generate/upload.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/github/pr_info.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/install.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/job_watcher.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/knowledge.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/launch.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/models/models.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/models/search.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/nodes.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/outdated.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/pack_scan.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/pr_command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/preview.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/project.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/run/loader.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/run/watcher.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/run_cli.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/setup.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/stop_port.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/system.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/version_validators.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command/workflow_fragments.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/command_mentions.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/config_manager.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/constants.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/_net.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/annotations_source.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/data/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/data/cloud_disable_config.yaml +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/data/default_text2img.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/data/supported_nodes.yaml +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/default_workflow.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/errors.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cql/widget_catalog.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/cuda_detect.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/deploy_assets.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/deploy_download.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/deploy_events.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/deploy_jobs.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/deprecation.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/discovery.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/download_state.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/env_checker.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/execution_errors.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/file_utils.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/fragments.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/git_utils.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/hardware.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/hashing.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/help_json.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/host_port.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/http.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/interaction.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/knowledge.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/layout_quality.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/link_integrity.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/local_address.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/locking.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/logging.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/onboarding.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/branding.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/glyphs.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/panels.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/preview.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/progress.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/renderer.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/rich_compat.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/output/sanitize.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/pr_cache.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/project.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/registry/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/registry/api.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/registry/config_parser.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/registry/types.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/resolve_python.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/agent.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/assets.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/assets_library.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/auth.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_blob_ls.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_delete.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_init.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_ls.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_pull.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_push.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_push_event.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_refs_base_images.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_refs_build_targets.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_refs_model_dirs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_refs_resolve.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_release_create.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_release_delete.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_release_logs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_release_ls.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_release_manifest.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_release_show.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_show.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_update.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/build_validate.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/cloud_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_cancel.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_delete.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_events.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_history.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_logs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_progress_event.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_promote.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_refs_compute.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_rename.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_rollback.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_run.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_scale.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_show.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_start.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_stop.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/deploy_up.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/discover.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/download.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/download_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/downloads.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/env.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/envelope.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/error.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/free.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/generate_list.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/generate_result.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/generate_schema.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/help.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/jobs_wait.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/knowledge.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/knowledge_block.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/launch.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/logs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/models.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/nodes.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/outdated.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/preview.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/project.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/run_event.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/set_default.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/skill.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/stop.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/system_stats.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/templates.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/transfer.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/update.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/version.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/which.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/schemas/widget_catalog.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/selector.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-agent-permissions/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-build/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-build-authoring/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-build-failures/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-build-pins/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-api-schema/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-caching/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-dependencies/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-frontend/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-invocation/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-migration/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-packaging/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-search/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-troubleshooting/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-custom-nodes-validation/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-deploy-failures/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-director/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/comfy-relay/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/skills/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/standalone.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/typing.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/ui.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/update.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/utils.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/uv.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/where.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli/workspace_manager.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli.egg-info/SOURCES.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli.egg-info/dependency_links.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli.egg-info/entry_points.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli.egg-info/requires.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/comfy_cli.egg-info/top_level.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/setup.cfg +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.24.0}/tests/test_file_utils_network.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comfy-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.24.0
|
|
4
4
|
Summary: A CLI tool for installing and using ComfyUI.
|
|
5
5
|
Maintainer-email: Yoland Yan <yoland@drip.art>, James Kwon <hongilkwon316@gmail.com>, Robin Huang <robin@drip.art>, "Dr.Lt.Data" <dr.lt.data@gmail.com>
|
|
6
6
|
License: GPL-3.0-only
|
|
@@ -116,7 +116,14 @@ class BuilderClient:
|
|
|
116
116
|
``X-API-Key``, via the shared authed HTTP path (HTTPS-enforced, no credential
|
|
117
117
|
replay on redirect, response-size capped)."""
|
|
118
118
|
|
|
119
|
-
def __init__(
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
base_url: str,
|
|
122
|
+
token: str | None = None,
|
|
123
|
+
*,
|
|
124
|
+
api_key: Credential | None = None,
|
|
125
|
+
token_env_var: str = "COMFY_BUILDER_TOKEN",
|
|
126
|
+
):
|
|
120
127
|
# kind="cloud" is what makes the shared http layer attach the credential;
|
|
121
128
|
# path_prefix carries the /v1 base so target.url("builds") is right.
|
|
122
129
|
self.target = Target(
|
|
@@ -127,18 +134,27 @@ class BuilderClient:
|
|
|
127
134
|
api_key=api_key.value if api_key is not None else None,
|
|
128
135
|
)
|
|
129
136
|
# Only a client built from the stored sign-in may swap its token; one
|
|
130
|
-
# handed a token (COMFY_BUILDER_TOKEN) or a key keeps
|
|
137
|
+
# handed a token (COMFY_BUILDER_TOKEN, COMFY_CLOUD_AUTH_TOKEN) or a key keeps
|
|
138
|
+
# it and lets a 401 surface.
|
|
131
139
|
self._refreshes_on_401 = False
|
|
132
140
|
# What to replace once the builder refuses this client's credential.
|
|
133
141
|
self._refused_hint = (
|
|
134
142
|
credentials.refused_key_hint(api_key)
|
|
135
143
|
if api_key is not None
|
|
136
|
-
else "replace
|
|
144
|
+
else f"replace {token_env_var} with a fresh Cloud JWT"
|
|
137
145
|
)
|
|
138
146
|
|
|
139
147
|
@classmethod
|
|
140
148
|
def from_credentials(cls, base_url: str) -> BuilderClient:
|
|
141
|
-
"""Build a client from
|
|
149
|
+
"""Build a client from a credential handed in by env when one is set, else from the sign-in.
|
|
150
|
+
|
|
151
|
+
``COMFY_CLOUD_AUTH_TOKEN``, then ``COMFY_CLOUD_API_KEY``, outrank the
|
|
152
|
+
stored sign-in, which is then not refreshed; the saved set-key key comes
|
|
153
|
+
only after the sign-in.
|
|
154
|
+
"""
|
|
155
|
+
token = credentials.platform_env_token()
|
|
156
|
+
if token:
|
|
157
|
+
return cls(base_url, token, token_env_var=credentials.CLOUD_BEARER_ENV_VAR)
|
|
142
158
|
key = credentials.platform_api_key()
|
|
143
159
|
if key is None:
|
|
144
160
|
try:
|
|
@@ -182,12 +182,16 @@ def logout_cmd():
|
|
|
182
182
|
@app.command("whoami", help="Show the current Comfy Cloud sign-in status.")
|
|
183
183
|
@tracking.track_command("cloud")
|
|
184
184
|
def whoami_cmd():
|
|
185
|
-
from comfy_cli.credentials import find_api_key, get_session
|
|
185
|
+
from comfy_cli.credentials import env_credential, find_api_key, get_session
|
|
186
186
|
|
|
187
187
|
renderer = get_renderer()
|
|
188
|
+
# A credential handed in by env outranks the stored sign-in for every cloud
|
|
189
|
+
# call, so whoami reports that one as the identity in effect.
|
|
190
|
+
handed_in = env_credential("cloud")
|
|
188
191
|
# Refresh first so we report (and persist) a live token rather than a
|
|
189
|
-
# token that quietly lapsed since the last command
|
|
190
|
-
|
|
192
|
+
# token that quietly lapsed since the last command — but only when the
|
|
193
|
+
# sign-in is what cloud calls will send. Otherwise it is read as stored.
|
|
194
|
+
session = get_session(refresh=handed_in is None)
|
|
191
195
|
configured_base_url = get_base_url()
|
|
192
196
|
|
|
193
197
|
# Ambient API key (env / store), reported even when an OAuth session
|
|
@@ -198,28 +202,47 @@ def whoami_cmd():
|
|
|
198
202
|
if ambient_key is not None:
|
|
199
203
|
api_key_source = "env" if ambient_key.source.startswith("env:") else "store"
|
|
200
204
|
|
|
201
|
-
# `signed_in` reflects *any* valid auth path
|
|
202
|
-
#
|
|
203
|
-
#
|
|
205
|
+
# `signed_in` reflects *any* valid auth path. Precedence matches what the
|
|
206
|
+
# client sends: a credential handed in by env (COMFY_CLOUD_AUTH_TOKEN, then
|
|
207
|
+
# COMFY_CLOUD_API_KEY), then a live OAuth session, then the stored key.
|
|
208
|
+
# `credential_source` names the one in effect ("env:<VAR>", "session",
|
|
209
|
+
# "stored:<provider>").
|
|
204
210
|
has_api_key = api_key_source is not None
|
|
205
211
|
has_oauth = session is not None
|
|
206
212
|
expired = session.is_expired() if session else None
|
|
207
|
-
|
|
213
|
+
# A session minted for another base URL is never sent to the configured
|
|
214
|
+
# one (the replay-guard in resolve_target), so it is not usable here.
|
|
215
|
+
stale_base_url = (session.base_url != configured_base_url) if session else False
|
|
216
|
+
oauth_usable = has_oauth and not expired and not stale_base_url
|
|
208
217
|
# `signed_in` must reflect whether we can actually authenticate right now:
|
|
209
|
-
# a live (non-expired) OAuth session, or an API
|
|
210
|
-
# unrefreshable OAuth session is NOT signed in on its own.
|
|
211
|
-
signed_in = oauth_usable or has_api_key
|
|
218
|
+
# a handed-in credential, a live (non-expired) OAuth session, or an API
|
|
219
|
+
# key. An expired, unrefreshable OAuth session is NOT signed in on its own.
|
|
220
|
+
signed_in = handed_in is not None or oauth_usable or has_api_key
|
|
212
221
|
auth_method: str | None
|
|
213
|
-
|
|
222
|
+
credential_source: str | None
|
|
223
|
+
if handed_in is not None:
|
|
224
|
+
auth_method = handed_in.kind
|
|
225
|
+
credential_source = handed_in.source
|
|
226
|
+
elif oauth_usable:
|
|
214
227
|
auth_method = "oauth"
|
|
215
|
-
|
|
228
|
+
credential_source = "session"
|
|
229
|
+
elif ambient_key is not None:
|
|
216
230
|
auth_method = "api_key"
|
|
231
|
+
credential_source = ambient_key.source
|
|
217
232
|
else:
|
|
218
233
|
auth_method = None
|
|
219
|
-
|
|
234
|
+
credential_source = None
|
|
220
235
|
|
|
221
236
|
if renderer.is_pretty():
|
|
222
|
-
if
|
|
237
|
+
if handed_in is not None:
|
|
238
|
+
env_var = handed_in.source.removeprefix("env:")
|
|
239
|
+
rprint(f"[bold]Authenticated via {env_var}[/bold] [dim](handed in by the environment)[/dim]")
|
|
240
|
+
if has_oauth:
|
|
241
|
+
rprint(
|
|
242
|
+
f"[dim]A stored `comfy cloud login` session ({session.base_url}) is present "
|
|
243
|
+
f"but unused while {env_var} is set.[/dim]"
|
|
244
|
+
)
|
|
245
|
+
elif has_oauth:
|
|
223
246
|
from comfy_cli.output.branding import whoami_banner
|
|
224
247
|
|
|
225
248
|
renderer.console().print(
|
|
@@ -244,7 +267,8 @@ def whoami_cmd():
|
|
|
244
267
|
f"[dim]OAuth session is preferred; X-API-Key from {api_key_source} present but unused.[/dim]"
|
|
245
268
|
)
|
|
246
269
|
else:
|
|
247
|
-
|
|
270
|
+
reason = "is for another base URL" if stale_base_url else "expired"
|
|
271
|
+
rprint(f"[dim]OAuth session {reason} — falling back to X-API-Key from {api_key_source}.[/dim]")
|
|
248
272
|
else:
|
|
249
273
|
from comfy_cli.output.branding import signed_out_banner
|
|
250
274
|
|
|
@@ -260,9 +284,13 @@ def whoami_cmd():
|
|
|
260
284
|
payload: dict[str, object | None] = {
|
|
261
285
|
"signed_in": signed_in,
|
|
262
286
|
"auth_method": auth_method,
|
|
263
|
-
|
|
287
|
+
# The URL the credential in effect goes to. Cloud calls always go to the
|
|
288
|
+
# configured URL (a session minted for another one is not used), so
|
|
289
|
+
# this is the configured URL; the session's own URL is in `session`.
|
|
290
|
+
"base_url": configured_base_url,
|
|
264
291
|
"configured_base_url": configured_base_url,
|
|
265
292
|
"api_key_source": api_key_source,
|
|
293
|
+
"credential_source": credential_source,
|
|
266
294
|
}
|
|
267
295
|
if session is not None:
|
|
268
296
|
payload["expired"] = expired
|
|
@@ -2002,6 +2002,33 @@ def run(
|
|
|
2002
2002
|
),
|
|
2003
2003
|
),
|
|
2004
2004
|
] = False,
|
|
2005
|
+
output_node_ids: Annotated[
|
|
2006
|
+
list[str] | None,
|
|
2007
|
+
typer.Option(
|
|
2008
|
+
"--output-node-ids",
|
|
2009
|
+
show_default=False,
|
|
2010
|
+
help=(
|
|
2011
|
+
"Comma-separated ids of output nodes to run (e.g. 9,12; repeatable); only they and "
|
|
2012
|
+
"the nodes they depend on execute, the rest of the graph is skipped. Ids are the "
|
|
2013
|
+
"API-format graph's node ids and must be output nodes. Sent to /prompt as "
|
|
2014
|
+
"partial_execution_targets. Omit to run the whole graph."
|
|
2015
|
+
),
|
|
2016
|
+
),
|
|
2017
|
+
] = None,
|
|
2018
|
+
client_id: Annotated[
|
|
2019
|
+
str | None,
|
|
2020
|
+
typer.Option(
|
|
2021
|
+
"--client-id",
|
|
2022
|
+
show_default=False,
|
|
2023
|
+
help=(
|
|
2024
|
+
"Local-only: submit as this already-connected WebSocket clientId instead of a fresh "
|
|
2025
|
+
"one. ComfyUI sends a prompt's execution events only to the socket that submitted it, "
|
|
2026
|
+
"so passing a browser tab's clientId is what makes that tab show node highlights, "
|
|
2027
|
+
"progress and outputs for a run it did not start. This invocation then cannot watch "
|
|
2028
|
+
"the run itself, so it cannot be combined with --wait."
|
|
2029
|
+
),
|
|
2030
|
+
),
|
|
2031
|
+
] = None,
|
|
2005
2032
|
):
|
|
2006
2033
|
# Snapshot kwargs before the body mutates api_key/host/port — analytics should record what user actually supplied.
|
|
2007
2034
|
_track_props = tracking.filter_command_kwargs(dict(locals()))
|
|
@@ -2047,11 +2074,51 @@ def run(
|
|
|
2047
2074
|
# `where` kwarg is None then). Rides on the execution_success/_error events.
|
|
2048
2075
|
_track_props["target"] = "cloud" if decision.target is where_module.WhereTarget.CLOUD else "local"
|
|
2049
2076
|
|
|
2077
|
+
if client_id is not None and not client_id.strip():
|
|
2078
|
+
renderer.error(
|
|
2079
|
+
code="client_id_rejected",
|
|
2080
|
+
message="--client-id must not be empty",
|
|
2081
|
+
hint="pass the connected browser client's id, or omit --client-id",
|
|
2082
|
+
details={"reason": "empty"},
|
|
2083
|
+
)
|
|
2084
|
+
raise typer.Exit(code=1)
|
|
2085
|
+
client_id = client_id.strip() if client_id is not None else None
|
|
2086
|
+
if client_id:
|
|
2087
|
+
rejection = None
|
|
2088
|
+
if decision.target is where_module.WhereTarget.CLOUD:
|
|
2089
|
+
rejection = ("cloud", "--client-id addresses a local ComfyUI socket and has no meaning on cloud")
|
|
2090
|
+
elif wait:
|
|
2091
|
+
rejection = (
|
|
2092
|
+
"wait",
|
|
2093
|
+
"--client-id hands this run's events to another client, so --wait would watch a silent socket",
|
|
2094
|
+
)
|
|
2095
|
+
if rejection is not None:
|
|
2096
|
+
reason, message = rejection
|
|
2097
|
+
renderer.error(
|
|
2098
|
+
code="client_id_rejected",
|
|
2099
|
+
message=message,
|
|
2100
|
+
hint="drop --client-id, or drop the conflicting flag",
|
|
2101
|
+
details={"reason": reason},
|
|
2102
|
+
)
|
|
2103
|
+
raise typer.Exit(code=1)
|
|
2104
|
+
|
|
2050
2105
|
# Default for --notify: on when a human is at the terminal, off for
|
|
2051
2106
|
# agents (they shouldn't get surprise side-channel processes they didn't
|
|
2052
2107
|
# ask for). The user can override either way with --notify/--no-notify.
|
|
2053
2108
|
effective_notify = notify if notify is not None else (renderer.is_pretty() and not wait)
|
|
2054
2109
|
|
|
2110
|
+
target_ids: list[str] | None = None
|
|
2111
|
+
if output_node_ids:
|
|
2112
|
+
target_ids = run_inner.parse_output_node_ids(output_node_ids)
|
|
2113
|
+
if not target_ids:
|
|
2114
|
+
renderer.error(
|
|
2115
|
+
code="invalid_output_node_ids",
|
|
2116
|
+
message="--output-node-ids was given but names no node ids",
|
|
2117
|
+
hint="pass comma-separated output node ids, e.g. --output-node-ids 9,12; omit it to run the whole graph",
|
|
2118
|
+
details={"unknown_node_ids": [], "non_output_node_ids": []},
|
|
2119
|
+
)
|
|
2120
|
+
raise typer.Exit(code=1)
|
|
2121
|
+
|
|
2055
2122
|
# --prompt/--set build an in-memory API-format graph from the bundled
|
|
2056
2123
|
# default text2img workflow (no --workflow file). The aliases resolve
|
|
2057
2124
|
# against OUR pinned node ids, so mixing them with a user --workflow —
|
|
@@ -2106,6 +2173,7 @@ def run(
|
|
|
2106
2173
|
workflow_id=workflow_id,
|
|
2107
2174
|
preloaded=preloaded,
|
|
2108
2175
|
allow_spend=allow_spend,
|
|
2176
|
+
output_node_ids=target_ids,
|
|
2109
2177
|
)
|
|
2110
2178
|
else:
|
|
2111
2179
|
# Both targets must fall off the END of this try suite: Python skips
|
|
@@ -2144,6 +2212,8 @@ def run(
|
|
|
2144
2212
|
print_prompt=print_prompt,
|
|
2145
2213
|
preloaded=preloaded,
|
|
2146
2214
|
allow_spend=allow_spend,
|
|
2215
|
+
output_node_ids=target_ids,
|
|
2216
|
+
client_id=client_id,
|
|
2147
2217
|
)
|
|
2148
2218
|
except typer.Exit as e:
|
|
2149
2219
|
if (e.exit_code or 0) == 0:
|
|
@@ -175,7 +175,12 @@ class Client:
|
|
|
175
175
|
if self.target.api_key:
|
|
176
176
|
return False
|
|
177
177
|
|
|
178
|
-
from comfy_cli.credentials import get_session
|
|
178
|
+
from comfy_cli.credentials import env_credential, get_session
|
|
179
|
+
|
|
180
|
+
# A token handed in by env is what was refused; the stored sign-in was
|
|
181
|
+
# never used, so it is not refreshed (or rewritten) on this token's behalf.
|
|
182
|
+
if env_credential("cloud") is not None:
|
|
183
|
+
return False
|
|
179
184
|
|
|
180
185
|
old_token = self.target.auth_token
|
|
181
186
|
# Was there a stored OAuth session backing this token? If not (e.g. a
|
|
@@ -347,6 +352,7 @@ class Client:
|
|
|
347
352
|
timeout: float | None = None,
|
|
348
353
|
extra_data: dict | None = None,
|
|
349
354
|
workflow_id: str | None = None,
|
|
355
|
+
output_node_ids: list[str] | None = None,
|
|
350
356
|
) -> SubmitResult:
|
|
351
357
|
"""POST {prefix}/prompt — submit a workflow for execution.
|
|
352
358
|
|
|
@@ -355,6 +361,9 @@ class Client:
|
|
|
355
361
|
forwarded as a top-level ``workflow_id`` field so the server can associate
|
|
356
362
|
the job with an existing workflow and auto-promote a draft on run. Omitted
|
|
357
363
|
from the body entirely when unset.
|
|
364
|
+
``output_node_ids`` is sent as top-level ``partial_execution_targets`` so
|
|
365
|
+
the server runs only those output nodes and what they depend on; omitted
|
|
366
|
+
from the body entirely when unset, which queues the whole graph.
|
|
358
367
|
For cloud submissions, the user's OAuth token is injected as
|
|
359
368
|
``auth_token_comfy_org`` so partner-API nodes (BFL Flux Pro, Gemini
|
|
360
369
|
Nano Banana, etc.) can call out to comfy.org — matching what the web
|
|
@@ -385,6 +394,8 @@ class Client:
|
|
|
385
394
|
# workflow (auto-promotes a draft on run). Only sent when provided.
|
|
386
395
|
if workflow_id:
|
|
387
396
|
request_payload["workflow_id"] = workflow_id
|
|
397
|
+
if output_node_ids:
|
|
398
|
+
request_payload["partial_execution_targets"] = list(output_node_ids)
|
|
388
399
|
return request_payload
|
|
389
400
|
|
|
390
401
|
resp = self._request("POST", ("prompt",), body_factory=payload, timeout=timeout)
|
|
@@ -26,6 +26,7 @@ from __future__ import annotations
|
|
|
26
26
|
|
|
27
27
|
import contextlib
|
|
28
28
|
import hashlib
|
|
29
|
+
import http.client
|
|
29
30
|
import io
|
|
30
31
|
import json
|
|
31
32
|
import os
|
|
@@ -2487,13 +2488,14 @@ def _builder_client(renderer, builder_url: str | None):
|
|
|
2487
2488
|
A caller that already holds a Cloud JWT, such as the Developer Platform agent
|
|
2488
2489
|
service forwarding the request's token, injects it via ``COMFY_BUILDER_TOKEN``,
|
|
2489
2490
|
and that explicit token comes first. Otherwise ``from_credentials`` uses a
|
|
2490
|
-
|
|
2491
|
-
stored sign-in.
|
|
2491
|
+
Cloud JWT from ``COMFY_CLOUD_AUTH_TOKEN`` or a workspace API key from
|
|
2492
|
+
``COMFY_CLOUD_API_KEY``, which a CI job sets, then the stored sign-in. Empty
|
|
2493
|
+
or whitespace-only values count as unset.
|
|
2492
2494
|
"""
|
|
2493
2495
|
from comfy_cli.builder_api import BuilderAuthError, BuilderClient
|
|
2494
2496
|
|
|
2495
2497
|
base_url = builder_url or os.environ.get("COMFY_BUILDER_URL") or DEFAULT_BUILDER_URL
|
|
2496
|
-
token = os.environ.get("COMFY_BUILDER_TOKEN")
|
|
2498
|
+
token = (os.environ.get("COMFY_BUILDER_TOKEN") or "").strip()
|
|
2497
2499
|
if token:
|
|
2498
2500
|
return BuilderClient(base_url, token)
|
|
2499
2501
|
try:
|
|
@@ -2978,6 +2980,10 @@ def show_cmd(
|
|
|
2978
2980
|
|
|
2979
2981
|
|
|
2980
2982
|
_RELEASE_POLL_SECONDS = 2.0
|
|
2983
|
+
#: Failed polls in a row a watch rides out before it ends: transport failures and
|
|
2984
|
+
#: answers too damaged to read, counted together. With the backoff in
|
|
2985
|
+
#: ``_poll_call``, about a minute of outage.
|
|
2986
|
+
_POLL_TRANSPORT_MISSES = 5
|
|
2981
2987
|
|
|
2982
2988
|
|
|
2983
2989
|
#: Sorts below every usable timestamp, so a release the builder dated badly loses
|
|
@@ -3133,9 +3139,71 @@ def _selected_release_id(renderer, client, scope: _BuildScope, release: str | No
|
|
|
3133
3139
|
return _newest_release_id(renderer, client, _resolve_build_id(renderer, client, scope))
|
|
3134
3140
|
|
|
3135
3141
|
|
|
3142
|
+
def _poll_call(renderer, fn, usable=None):
|
|
3143
|
+
"""``_builder_call`` for one poll of a watch, which a single timed-out or
|
|
3144
|
+
dropped request must not end: the release keeps building either way. A
|
|
3145
|
+
transport failure sleeps and polls again, up to ``_POLL_TRANSPORT_MISSES`` in
|
|
3146
|
+
a row; the builder's own answer (an ``HTTPError``) and every other failure
|
|
3147
|
+
still end the watch on the first one, through the same envelope.
|
|
3148
|
+
|
|
3149
|
+
The builder client is urllib-only: a connect timeout arrives wrapped in a
|
|
3150
|
+
``URLError``, a timeout or reset while awaiting the response arrives bare, and
|
|
3151
|
+
a connection closed partway through the body arrives as ``IncompleteRead``.
|
|
3152
|
+
A certificate the CA store refuses also arrives as a ``URLError``, but it
|
|
3153
|
+
will be refused again, so it ends the watch at once with its trust hint.
|
|
3154
|
+
A fixed-length body cut short arrives as no error at all: the HTTP layer
|
|
3155
|
+
returns the partial bytes, which parse as ``{}``. So an answer ``usable``
|
|
3156
|
+
rejects counts as a miss in the same run, and the last one ends the watch."""
|
|
3157
|
+
from comfy_cli.http import tls_verification_failed
|
|
3158
|
+
|
|
3159
|
+
def attempt():
|
|
3160
|
+
for miss in range(1, _POLL_TRANSPORT_MISSES + 1):
|
|
3161
|
+
try:
|
|
3162
|
+
answer = fn()
|
|
3163
|
+
except (urllib.error.URLError, TimeoutError, ConnectionError, http.client.IncompleteRead) as e:
|
|
3164
|
+
if isinstance(e, urllib.error.HTTPError) or tls_verification_failed(e):
|
|
3165
|
+
raise
|
|
3166
|
+
if miss == _POLL_TRANSPORT_MISSES:
|
|
3167
|
+
if isinstance(e, urllib.error.URLError):
|
|
3168
|
+
raise
|
|
3169
|
+
# Wrapped, or a bare one would escape ``_builder_call`` as a traceback.
|
|
3170
|
+
raise urllib.error.URLError(e) from e
|
|
3171
|
+
renderer.warn(f"builder unreachable ({e}); polling again ({miss}/{_POLL_TRANSPORT_MISSES - 1})")
|
|
3172
|
+
else:
|
|
3173
|
+
if usable is None or usable(answer):
|
|
3174
|
+
return answer
|
|
3175
|
+
if miss == _POLL_TRANSPORT_MISSES:
|
|
3176
|
+
renderer.error(
|
|
3177
|
+
code="build_builder_error",
|
|
3178
|
+
message=f"the builder sent no readable answer in {miss} polls in a row.",
|
|
3179
|
+
)
|
|
3180
|
+
raise typer.Exit(code=1)
|
|
3181
|
+
renderer.warn(f"builder answer unreadable; polling again ({miss}/{_POLL_TRANSPORT_MISSES - 1})")
|
|
3182
|
+
time.sleep(min(_RELEASE_POLL_SECONDS * 2**miss, 30.0))
|
|
3183
|
+
|
|
3184
|
+
return _builder_call(renderer, attempt)
|
|
3185
|
+
|
|
3186
|
+
|
|
3187
|
+
def _readable_log(answer) -> bool:
|
|
3188
|
+
"""The builder always sends ``log``, empty when none was captured yet, so an
|
|
3189
|
+
answer without one is a body cut short."""
|
|
3190
|
+
return isinstance(answer, dict) and isinstance(answer.get("log"), str)
|
|
3191
|
+
|
|
3192
|
+
|
|
3193
|
+
def _poll_status(renderer, client, release_id: str) -> dict:
|
|
3194
|
+
"""One status read of a watch. An answer with no status string is unreadable
|
|
3195
|
+
and polled again; a whole answer naming an unknown status is the builder's
|
|
3196
|
+
word and ends the watch at the caller."""
|
|
3197
|
+
return _poll_call(
|
|
3198
|
+
renderer,
|
|
3199
|
+
lambda: client.get_release(release_id),
|
|
3200
|
+
usable=lambda release: isinstance(release, dict) and isinstance(release.get("status"), str),
|
|
3201
|
+
)
|
|
3202
|
+
|
|
3203
|
+
|
|
3136
3204
|
def _poll_release(renderer, client, release_id: str) -> dict:
|
|
3137
3205
|
while True:
|
|
3138
|
-
release =
|
|
3206
|
+
release = _poll_status(renderer, client, release_id)
|
|
3139
3207
|
status = release.get("status")
|
|
3140
3208
|
if renderer.is_pretty():
|
|
3141
3209
|
renderer.info(f"release {release_id}: {status}")
|
|
@@ -3323,21 +3391,39 @@ def release_logs(
|
|
|
3323
3391
|
release_id = _selected_release_id(renderer, client, _BuildScope(ctx, path, build_id), release)
|
|
3324
3392
|
previous_log = ""
|
|
3325
3393
|
content: dict = {}
|
|
3394
|
+
complete = False
|
|
3395
|
+
|
|
3396
|
+
def read_log() -> dict:
|
|
3397
|
+
return client.get_release_logs(release_id, os=selected_target.os, gpu=selected_target.gpu)
|
|
3398
|
+
|
|
3326
3399
|
while True:
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3400
|
+
# Only a follow is a watch; a single read fails fast like every other command.
|
|
3401
|
+
# A body cut short inside its Content-Length parses as `{}` rather than
|
|
3402
|
+
# raising. Before any whole log is in hand the follow polls again, as for
|
|
3403
|
+
# a status read; after, publishing it would drop the log already read and
|
|
3404
|
+
# fail `build_release_logs.json`, so the last whole log stands until a
|
|
3405
|
+
# later read replaces it.
|
|
3406
|
+
if follow:
|
|
3407
|
+
had_log = _readable_log(content)
|
|
3408
|
+
read = _poll_call(renderer, read_log, usable=lambda answer: had_log or _readable_log(answer))
|
|
3409
|
+
else:
|
|
3410
|
+
read = _builder_call(renderer, read_log)
|
|
3411
|
+
if _readable_log(read) or not content:
|
|
3412
|
+
content = read
|
|
3413
|
+
elif complete:
|
|
3414
|
+
renderer.warn("the final log read came back incomplete; showing the log read before the release completed")
|
|
3331
3415
|
log = content.get("log", "")
|
|
3332
3416
|
if renderer.is_pretty() and isinstance(log, str) and log:
|
|
3333
3417
|
renderer.print(log[len(previous_log) :] if log.startswith(previous_log) else log)
|
|
3334
3418
|
previous_log = log
|
|
3335
|
-
if not follow:
|
|
3419
|
+
if not follow or complete:
|
|
3336
3420
|
break
|
|
3337
|
-
detail =
|
|
3421
|
+
detail = _poll_status(renderer, client, release_id)
|
|
3338
3422
|
match detail.get("status"):
|
|
3339
3423
|
case "complete":
|
|
3340
|
-
|
|
3424
|
+
# The log above may predate completion (a retried status read
|
|
3425
|
+
# widens that gap), so read it once more for the final lines.
|
|
3426
|
+
complete = True
|
|
3341
3427
|
case "queued" | "building":
|
|
3342
3428
|
time.sleep(_RELEASE_POLL_SECONDS)
|
|
3343
3429
|
case status:
|
|
@@ -52,6 +52,7 @@ from comfy_cli.command.deploy_up import (
|
|
|
52
52
|
landed_result,
|
|
53
53
|
move_line,
|
|
54
54
|
move_text,
|
|
55
|
+
observe_editor,
|
|
55
56
|
reconcile_up,
|
|
56
57
|
release_or_id,
|
|
57
58
|
replaced_message,
|
|
@@ -358,6 +359,15 @@ def up_cmd(
|
|
|
358
359
|
help="Name the deployment this creates. Default: production for a Build's first, else deployment-N.",
|
|
359
360
|
),
|
|
360
361
|
] = None,
|
|
362
|
+
editor: Annotated[
|
|
363
|
+
bool | None,
|
|
364
|
+
typer.Option(
|
|
365
|
+
"--editor/--no-editor",
|
|
366
|
+
help="Keep a CPU server for opening the deployment in the editor. Use --no-editor for one only called "
|
|
367
|
+
"through the API. Omitted, a new deployment gets comfy-deploy's default, on, and an existing one keeps "
|
|
368
|
+
"what it has.",
|
|
369
|
+
),
|
|
370
|
+
] = None,
|
|
361
371
|
# Watching is what someone who just asked for a deployment wants: the command
|
|
362
372
|
# that starts a several-minute wait should say how the wait is going. Ctrl-C
|
|
363
373
|
# and --no-watch both leave the deploy running and print how to re-attach.
|
|
@@ -382,6 +392,7 @@ def up_cmd(
|
|
|
382
392
|
deployment_id=deployment_id,
|
|
383
393
|
create=create,
|
|
384
394
|
name=name,
|
|
395
|
+
editor=editor,
|
|
385
396
|
watch=watch,
|
|
386
397
|
)
|
|
387
398
|
try:
|
|
@@ -418,12 +429,16 @@ def up_cmd(
|
|
|
418
429
|
# process: report where it had got to, and leave it be.
|
|
419
430
|
_warn_unapplied_bounds(renderer, result)
|
|
420
431
|
if last is not None:
|
|
421
|
-
|
|
432
|
+
abandoned_at = replace(result, deployment=last, pending_bounds=None)
|
|
433
|
+
_render_result(renderer, observe_editor(abandoned_at, last), watch=False)
|
|
422
434
|
|
|
423
435
|
move = watched_move(result.release, result.previous_release, result.deployment) if moving else None
|
|
424
436
|
outcomes = MoveOutcomes(client, watched_id, move) if move is not None else None
|
|
425
437
|
watched = _watch(renderer, client, watched_id, outcomes=outcomes, on_abandon=abandoned)
|
|
426
|
-
|
|
438
|
+
if outcomes is not None:
|
|
439
|
+
result = finish_move(client, result, watched, outcomes)
|
|
440
|
+
else:
|
|
441
|
+
result = observe_editor(replace(result, deployment=watched), watched)
|
|
427
442
|
_render_result(renderer, result, watch=watch)
|
|
428
443
|
except (BuildSpecNotFoundError, BuildSpecInvalidError) as error:
|
|
429
444
|
_render_spec_error(renderer, error)
|
|
@@ -54,6 +54,17 @@ def _release_column(deployment: JsonObject) -> str:
|
|
|
54
54
|
return f" {release_id}" if isinstance(release_id, str) and release_id else ""
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def _editor_of(row: JsonObject) -> bool | None:
|
|
58
|
+
# A comfy-deploy older than the editor switch sends no `editor`.
|
|
59
|
+
editor = row.get("editor")
|
|
60
|
+
return editor if isinstance(editor, bool) else None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _editor_column(deployment: JsonObject) -> str:
|
|
64
|
+
editor = deployment.get("editor")
|
|
65
|
+
return "" if editor is None else f" editor {'on' if editor else 'off'}"
|
|
66
|
+
|
|
67
|
+
|
|
57
68
|
def run_ls(request: ListRequest) -> None:
|
|
58
69
|
renderer = get_renderer()
|
|
59
70
|
try:
|
|
@@ -90,7 +101,7 @@ def run_ls(request: ListRequest) -> None:
|
|
|
90
101
|
version = versions[release_id]
|
|
91
102
|
if not request.include_deleted and row.get("deletedAt") is not None:
|
|
92
103
|
continue
|
|
93
|
-
named = {**row, "name": deployment_name(row)}
|
|
104
|
+
named = {**row, "name": deployment_name(row), "editor": _editor_of(row)}
|
|
94
105
|
deployments.append(named if version is None else {**named, "releaseVersion": version})
|
|
95
106
|
if len(deployments) == request.limit:
|
|
96
107
|
break
|
|
@@ -104,7 +115,8 @@ def run_ls(request: ListRequest) -> None:
|
|
|
104
115
|
marker = " (deleted)" if deployment.get("deletedAt") is not None else ""
|
|
105
116
|
renderer.print(
|
|
106
117
|
f" {sanitize_markup(deployment['name'] or '-')} {required_string(deployment, 'id')}"
|
|
107
|
-
f" {required_string(deployment, 'status')}{_release_column(deployment)}
|
|
118
|
+
f" {required_string(deployment, 'status')}{_release_column(deployment)}"
|
|
119
|
+
f"{_editor_column(deployment)}{marker}"
|
|
108
120
|
)
|
|
109
121
|
payload_rows: list[JsonValue] = [*deployments]
|
|
110
122
|
renderer.emit({"deployments": payload_rows}, command="deploy ls", changed=False)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
-
from dataclasses import dataclass
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
7
|
from typing import Final, Protocol
|
|
8
8
|
|
|
9
9
|
from comfy_cli.command.build_spec import JsonObject, JsonValue
|
|
@@ -21,6 +21,7 @@ class DeployUpClient(Protocol):
|
|
|
21
21
|
*,
|
|
22
22
|
idempotency_key: str | None = None,
|
|
23
23
|
name: str | None = None,
|
|
24
|
+
editor: bool | None = None,
|
|
24
25
|
) -> JsonObject: ...
|
|
25
26
|
|
|
26
27
|
def get_deployment(self, deployment_id: str) -> JsonObject: ...
|
|
@@ -29,7 +30,11 @@ class DeployUpClient(Protocol):
|
|
|
29
30
|
|
|
30
31
|
def start_deployment(self, deployment_id: str) -> JsonObject: ...
|
|
31
32
|
|
|
32
|
-
def
|
|
33
|
+
def set_deployment_editor(self, deployment_id: str, editor: bool) -> JsonObject: ...
|
|
34
|
+
|
|
35
|
+
def move_deployment(
|
|
36
|
+
self, deployment_id: str, base_revision: int, release_id: str, *, editor: bool | None = None
|
|
37
|
+
) -> JsonObject: ...
|
|
33
38
|
|
|
34
39
|
def promote_deployment(self, deployment_id: str, base_revision: int, from_deployment_id: str) -> JsonObject: ...
|
|
35
40
|
|
|
@@ -59,12 +64,51 @@ class UpRequest:
|
|
|
59
64
|
# `--name`: the name a created deployment gets, where comfy-deploy would
|
|
60
65
|
# otherwise pick one.
|
|
61
66
|
name: str | None = None
|
|
67
|
+
# `--editor/--no-editor`: whether the deployment keeps an editor CPU server.
|
|
68
|
+
# ``None`` is "flag omitted" and sends nothing: a create gets comfy-deploy's
|
|
69
|
+
# default, and an existing deployment keeps what it has.
|
|
70
|
+
editor: bool | None = None
|
|
62
71
|
# Whether the caller follows the deployment once the request is accepted.
|
|
63
72
|
# A move cannot carry new worker bounds, so they are applied after it lands,
|
|
64
73
|
# and only a watch is there to see it land.
|
|
65
74
|
watch: bool = True
|
|
66
75
|
|
|
67
76
|
|
|
77
|
+
@dataclass(frozen=True, slots=True)
|
|
78
|
+
class EditorCheck:
|
|
79
|
+
"""Where `--editor/--no-editor` stands, judged apart from what else `up` did.
|
|
80
|
+
|
|
81
|
+
comfy-deploy keeps a move it took when the editor write fails, and leaves
|
|
82
|
+
`editor` out of an answer where its lookup of it fails, so neither ever
|
|
83
|
+
undoes the change it is judged beside.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
requested: bool
|
|
87
|
+
# The value the latest answer that had one showed; None while none has.
|
|
88
|
+
seen: bool | None = None
|
|
89
|
+
# Set on an edit of the editor alone, of a deployment that had the other
|
|
90
|
+
# value: the value confirmed is then the change.
|
|
91
|
+
turns: bool = False
|
|
92
|
+
# Why the read made to confirm the value failed, until an answer shows it.
|
|
93
|
+
read_error: str | None = None
|
|
94
|
+
|
|
95
|
+
def observe(self, answer: JsonObject) -> EditorCheck:
|
|
96
|
+
"""The check with the editor value ``answer`` shows, where it shows one."""
|
|
97
|
+
seen = answer.get("editor")
|
|
98
|
+
if not isinstance(seen, bool):
|
|
99
|
+
return self
|
|
100
|
+
return replace(self, seen=seen, read_error=None)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def held(self) -> bool:
|
|
104
|
+
return self.seen is self.requested
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def made(self) -> bool:
|
|
108
|
+
"""Whether the edit of the editor alone changed the deployment."""
|
|
109
|
+
return self.turns and self.held
|
|
110
|
+
|
|
111
|
+
|
|
68
112
|
@dataclass(frozen=True, slots=True)
|
|
69
113
|
class UpResult:
|
|
70
114
|
deployment: JsonObject
|
|
@@ -91,6 +135,8 @@ class UpResult:
|
|
|
91
135
|
pending_bounds: JsonObject | None = None
|
|
92
136
|
# The name `--name` asked a create for, to say so where comfy-deploy took none.
|
|
93
137
|
requested_name: str | None = None
|
|
138
|
+
# `--editor/--no-editor` as asked and as last read; None when neither was passed.
|
|
139
|
+
editor: EditorCheck | None = None
|
|
94
140
|
|
|
95
141
|
def payload(self) -> JsonObject:
|
|
96
142
|
supersedes: list[JsonValue] = [*self.supersedes]
|