comfy-cli 1.23.0__tar.gz → 1.25.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.25.0}/PKG-INFO +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/_lazy.py +56 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/builder_api.py +20 -4
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cloud/command.py +44 -16
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cloud/oauth.py +49 -3
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cmdline.py +70 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/comfy_client.py +12 -1
- comfy_cli-1.25.0/comfy_cli/command/assets_library.py +846 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build.py +97 -11
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/cloud_http.py +33 -3
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy.py +17 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_ls.py +14 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_types.py +48 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_up.py +124 -18
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/app.py +20 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/client.py +5 -8
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/emit.py +28 -8
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/schema.py +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/jobs.py +555 -106
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/knowledge.py +14 -5
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/models/models.py +201 -6
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/node_deps.py +71 -16
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/run/__init__.py +248 -41
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/run/execution.py +70 -3
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/run/preflight.py +18 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/templates.py +3 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/transfer.py +63 -7
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/workflow.py +148 -10
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/workflow_edit.py +78 -15
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/engine.py +625 -88
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/loader.py +5 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/promoted.py +942 -139
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/credentials.py +235 -74
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/deploy_api.py +23 -4
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/deploy_api_errors.py +8 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/discovery.py +2 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/error_codes.py +122 -6
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/jobs_state.py +19 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/knowledge.py +51 -3
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/layout.py +27 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/link_integrity.py +2 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/model_variants.py +53 -6
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/assets_library.json +6 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_push_event.json +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_ls.json +4 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/error_codes.md +4 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/jobs.json +8 -3
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/node_deps.json +2 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/run.json +6 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/workflow.json +11 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy/SKILL.md +47 -8
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-debug/SKILL.md +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-deploy/SKILL.md +10 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/target.py +27 -2
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/tracking.py +5 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/workflow_ops.py +1208 -65
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/workflow_print.py +676 -150
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/workflow_to_api.py +923 -111
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli.egg-info/PKG-INFO +1 -1
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/pyproject.toml +1 -1
- comfy_cli-1.23.0/comfy_cli/command/assets_library.py +0 -338
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/LICENSE +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/README.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/__main__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/_safe_exec.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/agent/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/agent/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/auth/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/auth/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/auth/store.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/builder_pagination.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/caller.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cancellation.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cloud/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cloud/billing.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/_cloud_errors.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_diff.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_digest_cache.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_package.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_paths.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_pull.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_push.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_spec.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_targets.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_upload_progress.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/build_validation.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/code_search.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/custom_nodes/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/custom_nodes/bisect_custom_nodes.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/custom_nodes/cm_cli_util.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/custom_nodes/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_cancel.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_compute.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_lifecycle.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_progress.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_promote.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_read.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_refs.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_rename.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_resolve.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_rollback.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_run.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_run_assets.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_run_watch.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_runtime.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_status.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/deploy_workflow.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/adapters.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/output.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/poll.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/spec/openapi.yml +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/spec.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/generate/upload.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/github/pr_info.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/install.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/job_watcher.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/launch.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/models/search.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/nodes.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/outdated.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/pack_scan.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/pr_command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/preview.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/project.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/run/loader.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/run/watcher.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/run_cli.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/setup.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/stop_port.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/system.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/version_validators.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command/workflow_fragments.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/command_mentions.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/config_manager.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/constants.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/_net.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/annotations_source.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/data/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/data/cloud_disable_config.yaml +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/data/default_text2img.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/data/supported_nodes.yaml +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/default_workflow.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/errors.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cql/widget_catalog.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/cuda_detect.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/deploy_assets.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/deploy_download.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/deploy_events.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/deploy_jobs.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/deprecation.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/download_state.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/env_checker.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/execution_errors.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/file_utils.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/fragments.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/git_utils.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/hardware.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/hashing.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/help_json.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/host_port.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/http.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/interaction.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/layout_quality.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/local_address.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/locking.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/logging.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/onboarding.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/branding.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/glyphs.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/panels.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/preview.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/progress.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/renderer.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/rich_compat.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/output/sanitize.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/pr_cache.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/project.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/registry/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/registry/api.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/registry/config_parser.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/registry/types.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/resolve_python.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/agent.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/assets.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/auth.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_blob_ls.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_delete.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_init.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_ls.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_pull.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_push.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_refs_base_images.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_refs_build_targets.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_refs_model_dirs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_refs_resolve.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_release_create.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_release_delete.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_release_logs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_release_ls.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_release_manifest.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_release_show.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_show.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_update.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/build_validate.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/cloud_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_cancel.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_delete.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_events.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_history.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_logs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_progress_event.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_promote.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_refs_compute.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_rename.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_rollback.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_run.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_scale.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_show.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_start.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_stop.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/deploy_up.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/discover.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/download.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/download_status.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/downloads.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/env.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/envelope.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/error.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/free.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/generate_list.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/generate_result.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/generate_schema.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/help.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/jobs_wait.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/knowledge.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/knowledge_block.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/launch.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/logs.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/models.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/nodes.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/outdated.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/preview.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/project.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/run_event.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/set_default.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/skill.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/stop.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/system_stats.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/templates.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/transfer.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/update.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/version.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/which.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/schemas/widget_catalog.json +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/selector.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy/__init__.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-agent-permissions/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-build/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-build-authoring/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-build-failures/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-build-pins/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-api-schema/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-caching/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-dependencies/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-frontend/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-invocation/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-migration/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-packaging/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-search/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-troubleshooting/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-custom-nodes-validation/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-deploy-failures/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-director/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/comfy-relay/SKILL.md +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/skills/command.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/standalone.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/typing.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/ui.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/update.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/utils.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/uv.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/where.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli/workspace_manager.py +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli.egg-info/SOURCES.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli.egg-info/dependency_links.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli.egg-info/entry_points.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli.egg-info/requires.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/comfy_cli.egg-info/top_level.txt +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.0}/setup.cfg +0 -0
- {comfy_cli-1.23.0 → comfy_cli-1.25.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.25.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
|
|
@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
32
32
|
|
|
33
33
|
import typer
|
|
34
34
|
from typer.core import TyperGroup
|
|
35
|
-
from typer.models import CommandInfo, TyperInfo
|
|
35
|
+
from typer.models import CommandInfo, DefaultPlaceholder, TyperInfo
|
|
36
36
|
|
|
37
37
|
if TYPE_CHECKING:
|
|
38
38
|
import click
|
|
@@ -105,6 +105,60 @@ class LazyCommand:
|
|
|
105
105
|
hidden: bool = False
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def _reject_single_callback_app(name: str, spec: LazySubcommand, sub_app: typer.Typer) -> None:
|
|
109
|
+
"""Refuse a ``LazySubcommand`` mount of a Typer app that is only a callback.
|
|
110
|
+
|
|
111
|
+
A Typer app with no commands and no subgroups is one function wearing an
|
|
112
|
+
app, and ``add_typer`` turns it into a click ``Group``. A group disables
|
|
113
|
+
interspersed arguments, so the first non-option token ends option parsing
|
|
114
|
+
and everything after it is read as a nested command name. The callback's
|
|
115
|
+
own positional then swallows the query and the trailing flags become a
|
|
116
|
+
subcommand that does not exist::
|
|
117
|
+
|
|
118
|
+
$ comfy code-search KSampler --repo ComfyUI
|
|
119
|
+
Usage: comfy code-search [OPTIONS] {query} COMMAND [ARGS]...
|
|
120
|
+
Error: Missing argument 'query'.
|
|
121
|
+
|
|
122
|
+
while the same flags *before* the query parse fine — the shape a user
|
|
123
|
+
hits only in the installed CLI, since calling the module's app directly
|
|
124
|
+
never exercises the root group. ``code-search`` shipped that way and was
|
|
125
|
+
moved to :class:`LazyCommand`; this keeps the next one from repeating it.
|
|
126
|
+
|
|
127
|
+
A sub-app that supplies its own ``cls`` (e.g. a nested :class:`LazyTyperGroup`
|
|
128
|
+
with its own ``lazy_subcommands`` table) is exempt: its commands are built on
|
|
129
|
+
lookup rather than registered eagerly, so ``registered_commands`` and
|
|
130
|
+
``registered_groups`` are empty by design, not because it is a bare callback.
|
|
131
|
+
``cls`` can be set on the app itself (``typer.Typer(cls=...)``) or on its
|
|
132
|
+
callback (``@sub_app.callback(cls=...)``); ``typer.main.solve_typer_info_defaults``
|
|
133
|
+
reads the callback's value first, so this checks both in that order.
|
|
134
|
+
"""
|
|
135
|
+
if not isinstance(sub_app, typer.Typer):
|
|
136
|
+
raise TypeError(
|
|
137
|
+
f"lazy_subcommands[{name!r}] names {spec.module}.{spec.attr}, which is "
|
|
138
|
+
f"{sub_app!r}, not a typer.Typer instance."
|
|
139
|
+
)
|
|
140
|
+
if sub_app.registered_commands or sub_app.registered_groups:
|
|
141
|
+
return
|
|
142
|
+
callback = sub_app.registered_callback
|
|
143
|
+
callback_cls = callback.cls if callback is not None else DefaultPlaceholder(None)
|
|
144
|
+
effective_cls = callback_cls if not isinstance(callback_cls, DefaultPlaceholder) else sub_app.info.cls
|
|
145
|
+
if not isinstance(effective_cls, DefaultPlaceholder):
|
|
146
|
+
return
|
|
147
|
+
if sub_app.registered_callback is None:
|
|
148
|
+
raise TypeError(
|
|
149
|
+
f"lazy_subcommands[{name!r}] mounts {spec.module}.{spec.attr} as a command group, but "
|
|
150
|
+
f"that Typer app registers no commands, no subgroups, and no callback — there is nothing "
|
|
151
|
+
f"to mount. Check the module actually registers the commands you expect."
|
|
152
|
+
)
|
|
153
|
+
raise TypeError(
|
|
154
|
+
f"lazy_subcommands[{name!r}] mounts {spec.module}.{spec.attr} as a command group, but that "
|
|
155
|
+
f"Typer app registers no commands and no subgroups — it is a single callback. Mounting it as "
|
|
156
|
+
f"a group makes click demand a nested COMMAND, so options placed after the positional "
|
|
157
|
+
f'argument are rejected. Declare it as LazyCommand({spec.module!r}, attr="<function>") '
|
|
158
|
+
f"instead, naming the callback function rather than the app."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
108
162
|
class LazyTyperGroup(TyperGroup):
|
|
109
163
|
"""Root group with subcommands declared in ``lazy_subcommands`` and built
|
|
110
164
|
on first lookup. Set the table on a subclass::
|
|
@@ -174,6 +228,7 @@ class LazyTyperGroup(TyperGroup):
|
|
|
174
228
|
|
|
175
229
|
def _build_group(self, name: str, spec: LazySubcommand) -> click.Command:
|
|
176
230
|
sub_app = getattr(importlib.import_module(spec.module), spec.attr)
|
|
231
|
+
_reject_single_callback_app(name, spec, sub_app)
|
|
177
232
|
# Same TyperInfo ``add_typer`` would have appended: only the keywords
|
|
178
233
|
# that were given, so the sub-app's own settings fill the rest.
|
|
179
234
|
kwargs: dict[str, Any] = {"name": name}
|
|
@@ -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
|
|
@@ -45,7 +45,7 @@ import urllib.request
|
|
|
45
45
|
import webbrowser
|
|
46
46
|
from dataclasses import dataclass, field
|
|
47
47
|
from http.server import BaseHTTPRequestHandler
|
|
48
|
-
from typing import Any
|
|
48
|
+
from typing import TYPE_CHECKING, Any
|
|
49
49
|
from urllib.error import HTTPError, URLError
|
|
50
50
|
|
|
51
51
|
from comfy_cli.cloud import (
|
|
@@ -56,6 +56,11 @@ from comfy_cli.cloud import (
|
|
|
56
56
|
)
|
|
57
57
|
from comfy_cli.http import NoRedirectHandler, build_http_only_opener
|
|
58
58
|
|
|
59
|
+
if TYPE_CHECKING:
|
|
60
|
+
from collections.abc import Callable
|
|
61
|
+
|
|
62
|
+
from comfy_cli.auth.store import CloudSession
|
|
63
|
+
|
|
59
64
|
# ---------------------------------------------------------------------------
|
|
60
65
|
# Error types — caller maps these to renderer.error(code=...) codes.
|
|
61
66
|
# ---------------------------------------------------------------------------
|
|
@@ -92,6 +97,19 @@ class OAuthTimeout(OAuthError):
|
|
|
92
97
|
code = "oauth_timeout"
|
|
93
98
|
|
|
94
99
|
|
|
100
|
+
class SessionReplaced(Exception):
|
|
101
|
+
"""The stored sign-in is not the one ``ensure_fresh_session(expect=...)`` was asked to refresh.
|
|
102
|
+
|
|
103
|
+
Raised before anything is refreshed, persisted or cleared, so another
|
|
104
|
+
sign-in stored while the caller waited is left exactly as it was.
|
|
105
|
+
``session`` is the sign-in found in its place.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, session: CloudSession) -> None:
|
|
109
|
+
super().__init__("the stored sign-in was replaced by another")
|
|
110
|
+
self.session = session
|
|
111
|
+
|
|
112
|
+
|
|
95
113
|
# ---------------------------------------------------------------------------
|
|
96
114
|
# Constants
|
|
97
115
|
# ---------------------------------------------------------------------------
|
|
@@ -758,7 +776,13 @@ def take_last_fatal_refresh_reason() -> str | None:
|
|
|
758
776
|
return reason
|
|
759
777
|
|
|
760
778
|
|
|
761
|
-
def ensure_fresh_session(
|
|
779
|
+
def ensure_fresh_session(
|
|
780
|
+
*,
|
|
781
|
+
leeway_s: int = 60,
|
|
782
|
+
force: bool = False,
|
|
783
|
+
allow_clear: bool = True,
|
|
784
|
+
expect: Callable[[CloudSession], bool] | None = None,
|
|
785
|
+
):
|
|
762
786
|
"""Return the stored cloud session, refreshing it first when the access
|
|
763
787
|
token is expired (or within ``leeway_s`` of expiring) and a refresh token
|
|
764
788
|
is available.
|
|
@@ -787,6 +811,13 @@ def ensure_fresh_session(*, leeway_s: int = 60, force: bool = False, allow_clear
|
|
|
787
811
|
session lifecycle. The refresh still returns ``None`` so the caller knows it
|
|
788
812
|
could not freshen the token; it just declines to destroy the shared session.
|
|
789
813
|
|
|
814
|
+
``expect`` is for a caller bound to one sign-in (an upload finished as the
|
|
815
|
+
account that started it). It is called with the stored session on the first
|
|
816
|
+
read and again under the lock, before any refresh, persist or clear; when it
|
|
817
|
+
returns ``False`` another sign-in replaced that one (``comfy cloud login`` in
|
|
818
|
+
another shell), so :class:`SessionReplaced` is raised and the store is left
|
|
819
|
+
untouched. Without it every stored session is refreshed, as before.
|
|
820
|
+
|
|
790
821
|
Concurrency: the auth server rotates the refresh token on every refresh and
|
|
791
822
|
invalidates the old one. A parallel ``comfy run`` fan-out (plus background
|
|
792
823
|
``jobs watch`` processes) would otherwise have N processes each load the
|
|
@@ -800,6 +831,8 @@ def ensure_fresh_session(*, leeway_s: int = 60, force: bool = False, allow_clear
|
|
|
800
831
|
from comfy_cli.auth import store as auth_store
|
|
801
832
|
|
|
802
833
|
session = auth_store.get_cloud_session()
|
|
834
|
+
if session is not None and expect is not None and not expect(session):
|
|
835
|
+
raise SessionReplaced(session)
|
|
803
836
|
if session is None or not session.refresh_token:
|
|
804
837
|
return session
|
|
805
838
|
if not force and not session.is_expired(leeway_s=leeway_s):
|
|
@@ -821,6 +854,7 @@ def ensure_fresh_session(*, leeway_s: int = 60, force: bool = False, allow_clear
|
|
|
821
854
|
leeway_s=leeway_s,
|
|
822
855
|
force=force,
|
|
823
856
|
allow_clear=allow_clear,
|
|
857
|
+
expect=expect,
|
|
824
858
|
)
|
|
825
859
|
except TimeoutError:
|
|
826
860
|
# A peer has held the lock longer than a refresh should take. Don't
|
|
@@ -833,7 +867,15 @@ def ensure_fresh_session(*, leeway_s: int = 60, force: bool = False, allow_clear
|
|
|
833
867
|
return session
|
|
834
868
|
|
|
835
869
|
|
|
836
|
-
def _locked_refresh(
|
|
870
|
+
def _locked_refresh(
|
|
871
|
+
auth_store,
|
|
872
|
+
*,
|
|
873
|
+
observed_refresh_token: str,
|
|
874
|
+
leeway_s: int,
|
|
875
|
+
force: bool,
|
|
876
|
+
allow_clear: bool = True,
|
|
877
|
+
expect: Callable[[CloudSession], bool] | None = None,
|
|
878
|
+
):
|
|
837
879
|
"""Decide → POST → persist, executed while holding the secrets lock.
|
|
838
880
|
|
|
839
881
|
Re-reads the persisted session first (double-checked locking): if a peer
|
|
@@ -841,6 +883,10 @@ def _locked_refresh(auth_store, *, observed_refresh_token: str, leeway_s: int, f
|
|
|
841
883
|
one rather than spending the now-consumed token we observed before locking.
|
|
842
884
|
"""
|
|
843
885
|
session = auth_store.get_cloud_session()
|
|
886
|
+
# Checked under the lock: another login stored since the first read is
|
|
887
|
+
# neither refreshed nor cleared on this caller's behalf.
|
|
888
|
+
if session is not None and expect is not None and not expect(session):
|
|
889
|
+
raise SessionReplaced(session)
|
|
844
890
|
if session is None or not session.refresh_token:
|
|
845
891
|
return session
|
|
846
892
|
|
|
@@ -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)
|