diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 886aff3e..bb01370e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -38,7 +38,7 @@ jobs: pytest --cov=comfy_cli --cov-report=xml . - name: Upload coverage to Codecov - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6.0.2 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/comfy_cli/command/generate/client.py b/comfy_cli/command/generate/client.py index 44e6739c..93f01356 100644 --- a/comfy_cli/command/generate/client.py +++ b/comfy_cli/command/generate/client.py @@ -85,7 +85,7 @@ def _auth_headers(api_key: str, extra: dict[str, str] | None = None) -> dict[str # - "comfyui-..." API keys → X-API-Key (validated by sha256 lookup) # - Firebase ID tokens → Authorization: Bearer (validated as a JWT) # See comfy-api server/middleware/authentication/comfy_firebase_auth.go. - headers = {"User-Agent": "comfy-cli/api", "Comfy-Env": "comfy-cli"} + headers = {"User-Agent": "comfy-cli/api", "Comfy-Env": "comfy-cli", "Comfy-Usage-Source": "comfy-cli"} if api_key.startswith("comfyui-"): headers["X-API-Key"] = api_key else: diff --git a/comfy_cli/command/run.py b/comfy_cli/command/run.py index f74810e3..9ccf1b84 100644 --- a/comfy_cli/command/run.py +++ b/comfy_cli/command/run.py @@ -579,12 +579,14 @@ def connect(self): def queue(self): data: dict = {"prompt": self.workflow, "client_id": self.client_id} + data["extra_data"] = {"comfy_usage_source": "comfy-cli"} if self.api_key: - data["extra_data"] = {"api_key_comfy_org": self.api_key} + data["extra_data"]["api_key_comfy_org"] = self.api_key req = request.Request( f"http://{self.host}:{self.port}/prompt", json.dumps(data).encode("utf-8"), ) + req.add_header("Comfy-Usage-Source", "comfy-cli") try: resp = request.urlopen(req, timeout=self.timeout) raw_body = resp.read() diff --git a/tests/comfy_cli/command/test_run.py b/tests/comfy_cli/command/test_run.py index a09327ad..e74563f5 100644 --- a/tests/comfy_cli/command/test_run.py +++ b/tests/comfy_cli/command/test_run.py @@ -175,7 +175,7 @@ def test_queue_embeds_api_key_in_extra_data(self, workflow): ex.queue() req = mock_open.call_args[0][0] body = json.loads(req.data) - assert body["extra_data"] == {"api_key_comfy_org": "sk-secret"} + assert body["extra_data"] == {"comfy_usage_source": "comfy-cli", "api_key_comfy_org": "sk-secret"} def test_queue_does_not_send_x_api_key_header(self, workflow): ex = self._make_exec(workflow, api_key="sk-secret") @@ -185,15 +185,26 @@ def test_queue_does_not_send_x_api_key_header(self, workflow): req = mock_open.call_args[0][0] assert req.get_header("X-api-key") is None - def test_queue_omits_extra_data_when_no_api_key(self, workflow): + def test_queue_omits_api_key_when_not_set(self, workflow): ex = self._make_exec(workflow) with patch("comfy_cli.command.run.request.urlopen") as mock_open: mock_open.return_value.read.return_value = json.dumps({"prompt_id": "abc"}).encode() ex.queue() req = mock_open.call_args[0][0] body = json.loads(req.data) - assert "extra_data" not in body - assert body == {"prompt": workflow, "client_id": ex.client_id} + assert body == { + "prompt": workflow, + "client_id": ex.client_id, + "extra_data": {"comfy_usage_source": "comfy-cli"}, + } + + def test_queue_sends_usage_source_header(self, workflow): + ex = self._make_exec(workflow) + with patch("comfy_cli.command.run.request.urlopen") as mock_open: + mock_open.return_value.read.return_value = json.dumps({"prompt_id": "abc"}).encode() + ex.queue() + req = mock_open.call_args[0][0] + assert req.get_header("Comfy-usage-source") == "comfy-cli" class TestWatchExecution: diff --git a/uv.lock b/uv.lock index 836ac0ae..1f442ba9 100644 --- a/uv.lock +++ b/uv.lock @@ -30,6 +30,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, ] +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + [[package]] name = "binaryornot" version = "0.4.4" @@ -163,6 +172,7 @@ dependencies = [ { name = "mixpanel" }, { name = "packaging" }, { name = "pathspec" }, + { name = "posthog" }, { name = "psutil" }, { name = "pyyaml" }, { name = "questionary" }, @@ -194,6 +204,7 @@ requires-dist = [ { name = "mixpanel" }, { name = "packaging" }, { name = "pathspec" }, + { name = "posthog", specifier = ">=6,<8" }, { name = "pre-commit", marker = "extra == 'dev'" }, { name = "psutil" }, { name = "pytest", marker = "extra == 'dev'" }, @@ -331,6 +342,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -590,6 +610,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "posthog" +version = "7.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "distro" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/56/49d06b64baf270f8ec7fdb9352eaf2469167fcf0a1cbd2facc9335ab52fb/posthog-7.18.1.tar.gz", hash = "sha256:a4a3496448aa2bc4e13880daab205d11c13f8a537570662dcf9e5ecef3696ca1", size = 231652, upload-time = "2026-06-10T14:22:28.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/c5/0db39bdf91ae2e473ba139568ed24d631e87caac6c175f466d06eedc8747/posthog-7.18.1-py3-none-any.whl", hash = "sha256:54797ae8767911dfd83541f69a9e4fda65e100cb77dc0cf093fd98a3b84916a6", size = 270818, upload-time = "2026-06-10T14:22:26.849Z" }, +] + [[package]] name = "pre-commit" version = "4.2.0"