feat: add generic --feature-flag CLI arg and --list-feature-flags registry

Add --feature-flag KEY=VALUE CLI argument that allows setting arbitrary
server feature flags at startup. Values are auto-converted to appropriate
Python types (bool, int, float, string). CLI flags are merged into
SERVER_FEATURE_FLAGS but cannot overwrite core flags.

Add --list-feature-flags which prints the registry of known CLI-settable
feature flags as JSON and exits, enabling launchers to discover valid
flags for a specific ComfyUI version.

Part of Comfy-Org/ComfyUI-Desktop-2.0-Beta#415

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019d9386-54d3-74d9-a661-97e0a8d37b6b
This commit is contained in:
Jedrzej Kosinski 2026-04-15 17:06:59 -07:00
parent 1de83f91c3
commit 8390ffccfe
4 changed files with 123 additions and 3 deletions

View File

@ -238,6 +238,8 @@ database_default_path = os.path.abspath(
)
parser.add_argument("--database-url", type=str, default=f"sqlite:///{database_default_path}", help="Specify the database URL, e.g. for an in-memory database you can use 'sqlite:///:memory:'.")
parser.add_argument("--enable-assets", action="store_true", help="Enable the assets system (API routes, database synchronization, and background scanning).")
parser.add_argument("--feature-flag", type=str, action='append', default=[], metavar="KEY=VALUE", help="Set a server feature flag as a key=value pair. Can be specified multiple times. Boolean values (true/false) and numbers are auto-converted. Example: --feature-flag show_signin_button=true")
parser.add_argument("--list-feature-flags", action="store_true", help="Print the registry of known CLI-settable feature flags as JSON and exit.")
if comfy.options.args_parsing:
args = parser.parse_args()

View File

@ -5,12 +5,66 @@ This module handles capability negotiation between frontend and backend,
allowing graceful protocol evolution while maintaining backward compatibility.
"""
from typing import Any
from typing import Any, TypedDict
from comfy.cli_args import args
class FeatureFlagInfo(TypedDict):
type: str
default: Any
description: str
# Registry of known CLI-settable feature flags.
# Launchers can query this via --list-feature-flags to discover valid flags.
CLI_FEATURE_FLAG_REGISTRY: dict[str, FeatureFlagInfo] = {
"show_signin_button": {
"type": "bool",
"default": False,
"description": "Show the sign-in button in the frontend even when not signed in",
},
}
def get_cli_feature_flag_registry() -> dict[str, FeatureFlagInfo]:
"""Return the registry of known CLI-settable feature flags."""
return {k: dict(v) for k, v in CLI_FEATURE_FLAG_REGISTRY.items()}
def _parse_feature_flag_value(value: str) -> Any:
"""Convert a string value to its appropriate Python type."""
lower = value.lower()
if lower == "true":
return True
if lower == "false":
return False
try:
return int(value)
except ValueError:
pass
try:
return float(value)
except ValueError:
pass
return value
def _parse_cli_feature_flags() -> dict[str, Any]:
"""Parse --feature-flag key=value pairs from CLI args into a dict."""
result: dict[str, Any] = {}
for item in getattr(args, "feature_flag", []):
if "=" not in item:
continue
key, _, raw_value = item.partition("=")
key = key.strip()
if key:
result[key] = _parse_feature_flag_value(raw_value.strip())
return result
# Default server capabilities
SERVER_FEATURE_FLAGS: dict[str, Any] = {
_CORE_FEATURE_FLAGS: dict[str, Any] = {
"supports_preview_metadata": True,
"max_upload_size": args.max_upload_size * 1024 * 1024, # Convert MB to bytes
"extension": {"manager": {"supports_v4": True}},
@ -18,6 +72,11 @@ SERVER_FEATURE_FLAGS: dict[str, Any] = {
"assets": args.enable_assets,
}
# CLI-provided flags cannot overwrite core flags
_cli_flags = {k: v for k, v in _parse_cli_feature_flags().items() if k not in _CORE_FEATURE_FLAGS}
SERVER_FEATURE_FLAGS: dict[str, Any] = {**_CORE_FEATURE_FLAGS, **_cli_flags}
def get_connection_feature(
sockets_metadata: dict[str, dict[str, Any]],

10
main.py
View File

@ -1,13 +1,21 @@
import comfy.options
comfy.options.enable_args_parsing()
from comfy.cli_args import args
if args.list_feature_flags:
import json
from comfy_api.feature_flags import get_cli_feature_flag_registry
print(json.dumps(get_cli_feature_flag_registry(), indent=2))
raise SystemExit(0)
import os
import importlib.util
import shutil
import importlib.metadata
import folder_paths
import time
from comfy.cli_args import args, enables_dynamic_vram
from comfy.cli_args import enables_dynamic_vram
from app.logger import setup_logger
from app.assets.seeder import asset_seeder
from app.assets.services import register_output_files

View File

@ -4,7 +4,10 @@ from comfy_api.feature_flags import (
get_connection_feature,
supports_feature,
get_server_features,
get_cli_feature_flag_registry,
SERVER_FEATURE_FLAGS,
_parse_feature_flag_value,
_parse_cli_feature_flags,
)
@ -96,3 +99,51 @@ class TestFeatureFlags:
result = get_connection_feature(sockets_metadata, "sid1", "any_feature")
assert result is False
assert supports_feature(sockets_metadata, "sid1", "any_feature") is False
class TestParseFeatureFlagValue:
"""Test suite for _parse_feature_flag_value."""
def test_true_string(self):
assert _parse_feature_flag_value("true") is True
assert _parse_feature_flag_value("True") is True
assert _parse_feature_flag_value("TRUE") is True
def test_false_string(self):
assert _parse_feature_flag_value("false") is False
assert _parse_feature_flag_value("False") is False
def test_integer_string(self):
assert _parse_feature_flag_value("42") == 42
assert _parse_feature_flag_value("0") == 0
def test_float_string(self):
assert _parse_feature_flag_value("3.14") == 3.14
def test_plain_string(self):
assert _parse_feature_flag_value("hello") == "hello"
assert _parse_feature_flag_value("") == ""
class TestParseCliFeatureFlags:
"""Test suite for _parse_cli_feature_flags."""
def test_single_flag(self, monkeypatch):
monkeypatch.setattr("comfy_api.feature_flags.args", type("Args", (), {"feature_flag": ["key=true"]})())
result = _parse_cli_feature_flags()
assert result == {"key": True}
def test_missing_equals_skipped(self, monkeypatch):
monkeypatch.setattr("comfy_api.feature_flags.args", type("Args", (), {"feature_flag": ["noequals", "valid=1"]})())
result = _parse_cli_feature_flags()
assert result == {"valid": 1}
class TestCliFeatureFlagRegistry:
"""Test suite for the CLI feature flag registry."""
def test_registry_entries_have_required_fields(self):
for key, info in get_cli_feature_flag_registry().items():
assert "type" in info, f"{key} missing 'type'"
assert "default" in info, f"{key} missing 'default'"
assert "description" in info, f"{key} missing 'description'"