pipecatcloud 0.4.3__tar.gz → 0.5.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.
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/CHANGELOG.md +58 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/PKG-INFO +29 -3
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/README.md +26 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/pyproject.toml +9 -9
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/_utils/deploy_utils.py +1 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/api.py +5 -1
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/auth.py +61 -7
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/deploy.py +8 -3
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/secrets.py +25 -21
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/config.py +29 -10
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/entry_point.py +13 -3
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud.egg-info/PKG-INFO +29 -3
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud.egg-info/SOURCES.txt +1 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud.egg-info/requires.txt +1 -1
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_api_client.py +48 -0
- pipecatcloud-0.5.0/tests/test_auth.py +303 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/uv.lock +345 -748
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/.github/workflows/format.yml +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/.github/workflows/publish-pypi.yml +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/.github/workflows/tests.yml +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/.gitignore +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/.pre-commit-config.yaml +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/LICENSE +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/pytest.ini +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/scripts/fix-ruff.sh +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/scripts/pre-commit.sh +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/setup.cfg +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/__init__.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/__version__.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/_utils/build_utils.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/_utils/console_utils.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/_utils/regions.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/agent.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/agent.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/build.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/docker.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/cli/commands/regions.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/config.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/constants.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/exception.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/py.typed +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/session.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/src/pipecatcloud.egg-info/top_level.txt +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/__init__.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/conftest.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_agent_sessions.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_agent_stop.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_config_file.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_deployment_status.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_docker_commands.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_krisp_viva.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_pat_auth.py +0 -0
- {pipecatcloud-0.4.3 → pipecatcloud-0.5.0}/tests/test_regions.py +0 -0
|
@@ -5,6 +5,64 @@ All notable changes to **Pipecat Cloud** will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-04-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Minimum Python version bumped from 3.10 to 3.11, matching Pipecat 1.0's
|
|
13
|
+
supported range (3.11-3.14). Users on Python 3.10 will continue to receive
|
|
14
|
+
the previous release via pip/uv version resolution.
|
|
15
|
+
|
|
16
|
+
- The `pipecatcloud[pipecat]` optional dependency now requires `pipecat-ai>=1.0.0`
|
|
17
|
+
(previously `>=0.0.79`).
|
|
18
|
+
|
|
19
|
+
- **`pcc deploy --force` now forces a new deployment** even if your
|
|
20
|
+
configuration hasn't changed. Previously, `--force` only skipped the
|
|
21
|
+
confirmation prompt. Use this when you want to pick up a new image
|
|
22
|
+
behind the same tag (e.g. `:latest`) or restart your agent instances.
|
|
23
|
+
`--yes` remains available for non-interactive/CI usage.
|
|
24
|
+
|
|
25
|
+
## [0.4.4] - 2026-04-10
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `pcc secrets image-pull-secret` now supports updating existing image pull
|
|
30
|
+
secrets. Previously, updating required deleting and recreating the secret.
|
|
31
|
+
The command prompts for confirmation before overwriting; use `--skip` to
|
|
32
|
+
bypass the prompt in scripts.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- `--show-cli-config` now redacts `token` and `refresh_token` values in output.
|
|
37
|
+
A type prefix (e.g. `pcc_pat_****`) is shown for diagnostics. Users who need
|
|
38
|
+
raw values can read the config file directly.
|
|
39
|
+
|
|
40
|
+
- `pcc auth use-pat` now reads the token from a secure hidden prompt instead of
|
|
41
|
+
a positional argument. The positional form still works but emits a deprecation
|
|
42
|
+
warning (tokens passed as arguments are visible in shell history and process
|
|
43
|
+
listings).
|
|
44
|
+
|
|
45
|
+
- OAuth callback server now binds to `127.0.0.1` instead of `localhost` to
|
|
46
|
+
prevent DNS rebinding attacks.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- `pcc auth logout` no longer claims "session revoked" when server-side
|
|
51
|
+
revocation was not confirmed. It now accurately reports what succeeded.
|
|
52
|
+
|
|
53
|
+
- `pcc auth logout` no longer crashes if the credentials file is already absent.
|
|
54
|
+
|
|
55
|
+
- Fixed `organizations_current()` fallback returning the wrong data shape when
|
|
56
|
+
no specific org is requested.
|
|
57
|
+
|
|
58
|
+
- OIDC discovery metadata is now validated before use: issuer must match
|
|
59
|
+
expected value, endpoints must use HTTPS, and PKCE S256 support is verified
|
|
60
|
+
when advertised.
|
|
61
|
+
|
|
62
|
+
- Credentials are now written atomically (write to temp file, fsync, rename)
|
|
63
|
+
to prevent corruption from interrupted writes. File permissions are
|
|
64
|
+
restrictive from creation.
|
|
65
|
+
|
|
8
66
|
## [0.4.3] - 2026-04-02
|
|
9
67
|
|
|
10
68
|
### Fixed
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Cloud hosting for Pipecat AI applications
|
|
5
5
|
License: BSD 2-Clause License
|
|
6
6
|
Project-URL: Website, https://docs.pipecat.daily.co/
|
|
7
7
|
Keywords: pipecat,pipecat-ai,voice,ai,llm
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: aiohttp<4.0.0,>=3.11.12
|
|
@@ -18,7 +18,7 @@ Requires-Dist: synchronicity<1.0.0,>=0.11
|
|
|
18
18
|
Requires-Dist: toml~=0.10.2
|
|
19
19
|
Requires-Dist: questionary~=2.1.0
|
|
20
20
|
Provides-Extra: pipecat
|
|
21
|
-
Requires-Dist: pipecat-ai>=0.0
|
|
21
|
+
Requires-Dist: pipecat-ai>=1.0.0; extra == "pipecat"
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
24
|
<h1><div align="center">
|
|
@@ -97,6 +97,32 @@ if __name__ == "__main__":
|
|
|
97
97
|
asyncio.run(main())
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
## Troubleshooting
|
|
101
|
+
|
|
102
|
+
### SSL certificate errors on macOS
|
|
103
|
+
|
|
104
|
+
If `pcc auth login` fails with an SSL certificate verification error, your Python
|
|
105
|
+
installation may not have access to the macOS system certificate store. This is
|
|
106
|
+
common with Python installed via pyenv, conda, or the python.org installer.
|
|
107
|
+
|
|
108
|
+
To diagnose:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import ssl, sys, os
|
|
112
|
+
print(ssl.get_default_verify_paths())
|
|
113
|
+
print(os.path.realpath(sys.executable))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
To fix, install `certifi` and point Python to its certificates:
|
|
117
|
+
|
|
118
|
+
```shell
|
|
119
|
+
pip install certifi
|
|
120
|
+
export SSL_CERT_FILE=$(python -c "import certifi; print(certifi.where())")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For the python.org installer, you can also run the bundled
|
|
124
|
+
`Install Certificates.command` script found in `/Applications/Python X.Y/`.
|
|
125
|
+
|
|
100
126
|
## 🛠️ Contributing
|
|
101
127
|
|
|
102
128
|
### Setup Steps
|
|
@@ -74,6 +74,32 @@ if __name__ == "__main__":
|
|
|
74
74
|
asyncio.run(main())
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## Troubleshooting
|
|
78
|
+
|
|
79
|
+
### SSL certificate errors on macOS
|
|
80
|
+
|
|
81
|
+
If `pcc auth login` fails with an SSL certificate verification error, your Python
|
|
82
|
+
installation may not have access to the macOS system certificate store. This is
|
|
83
|
+
common with Python installed via pyenv, conda, or the python.org installer.
|
|
84
|
+
|
|
85
|
+
To diagnose:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import ssl, sys, os
|
|
89
|
+
print(ssl.get_default_verify_paths())
|
|
90
|
+
print(os.path.realpath(sys.executable))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To fix, install `certifi` and point Python to its certificates:
|
|
94
|
+
|
|
95
|
+
```shell
|
|
96
|
+
pip install certifi
|
|
97
|
+
export SSL_CERT_FILE=$(python -c "import certifi; print(certifi.where())")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For the python.org installer, you can also run the bundled
|
|
101
|
+
`Install Certificates.command` script found in `/Applications/Python X.Y/`.
|
|
102
|
+
|
|
77
103
|
## 🛠️ Contributing
|
|
78
104
|
|
|
79
105
|
### Setup Steps
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pipecatcloud"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
-
requires-python = ">=3.
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
9
|
description = "Cloud hosting for Pipecat AI applications"
|
|
10
10
|
license = { text = "BSD 2-Clause License" }
|
|
11
11
|
readme = "README.md"
|
|
@@ -23,17 +23,17 @@ dependencies = [
|
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
[project.optional-dependencies]
|
|
26
|
-
pipecat = ["pipecat-ai>=0.0
|
|
26
|
+
pipecat = ["pipecat-ai>=1.0.0"]
|
|
27
27
|
|
|
28
28
|
[dependency-groups]
|
|
29
29
|
dev = [
|
|
30
|
-
"build~=1.
|
|
31
|
-
"grpcio-tools~=1.
|
|
32
|
-
"pre-commit~=4.
|
|
33
|
-
"pyright
|
|
34
|
-
"pytest
|
|
35
|
-
"pytest-asyncio
|
|
36
|
-
"ruff
|
|
30
|
+
"build~=1.4.0",
|
|
31
|
+
"grpcio-tools~=1.78.0",
|
|
32
|
+
"pre-commit~=4.5.1",
|
|
33
|
+
"pyright>=1.1.404,<1.2",
|
|
34
|
+
"pytest>=9.0.0,<10",
|
|
35
|
+
"pytest-asyncio>=1.0.0,<2",
|
|
36
|
+
"ruff>=0.12.11,<1",
|
|
37
37
|
"setuptools~=78.1.1",
|
|
38
38
|
"setuptools_scm~=8.3.1",
|
|
39
39
|
]
|
|
@@ -333,6 +333,7 @@ class DeployConfigParams:
|
|
|
333
333
|
build_config: BuildConfig = field(factory=BuildConfig) # Cloud build configuration
|
|
334
334
|
agent_profile: Optional[str] = None
|
|
335
335
|
krisp_viva: KrispVivaConfig = field(factory=KrispVivaConfig)
|
|
336
|
+
force_redeploy: bool = False
|
|
336
337
|
|
|
337
338
|
def __attrs_post_init__(self):
|
|
338
339
|
if self.image is not None and ":" not in self.image:
|
|
@@ -233,7 +233,10 @@ class _API:
|
|
|
233
233
|
return {"name": o["name"], "verbose_name": o["verboseName"]}
|
|
234
234
|
|
|
235
235
|
# Default to first organization if active_org not found or not specified
|
|
236
|
-
return {
|
|
236
|
+
return {
|
|
237
|
+
"name": results["organizations"][0]["name"],
|
|
238
|
+
"verbose_name": results["organizations"][0]["verboseName"],
|
|
239
|
+
}
|
|
237
240
|
|
|
238
241
|
@property
|
|
239
242
|
def organizations_current(self):
|
|
@@ -411,6 +414,7 @@ class _API:
|
|
|
411
414
|
"krispViva": {
|
|
412
415
|
"audioFilter": deploy_config.krisp_viva.audio_filter,
|
|
413
416
|
},
|
|
417
|
+
"forceRedeploy": deploy_config.force_redeploy or None,
|
|
414
418
|
}
|
|
415
419
|
|
|
416
420
|
# Use either build_id (cloud build) or image (user-provided)
|
|
@@ -40,6 +40,7 @@ USER_AGENT = f"PipecatCloudCLI/{_cli_version}"
|
|
|
40
40
|
# Ports to try for the localhost OAuth callback server.
|
|
41
41
|
# Our audience is developers who may have services running on common ports.
|
|
42
42
|
# We try a range and use the first available.
|
|
43
|
+
CALLBACK_HOST = "127.0.0.1"
|
|
43
44
|
CALLBACK_PORTS = [8400, 8401, 8402, 8403, 8404]
|
|
44
45
|
CALLBACK_PATH = "/oauth_callback"
|
|
45
46
|
|
|
@@ -63,17 +64,49 @@ async def _fetch_oauth_config() -> dict:
|
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
async def _fetch_oidc_discovery(issuer: str) -> dict:
|
|
66
|
-
"""Fetch OIDC discovery document from the OAuth issuer (RFC 8414).
|
|
67
|
+
"""Fetch and validate OIDC discovery document from the OAuth issuer (RFC 8414).
|
|
67
68
|
|
|
68
69
|
This gives us the authorization and token endpoints without
|
|
69
70
|
hardcoding any provider-specific URLs.
|
|
71
|
+
|
|
72
|
+
Validates the discovery metadata per RFC 8414 §3.1 and RFC 9207 §2.4:
|
|
73
|
+
- issuer must exactly match the expected value
|
|
74
|
+
- authorization_endpoint and token_endpoint must use HTTPS
|
|
75
|
+
- code_challenge_methods_supported must include S256 (when present)
|
|
76
|
+
- response_types_supported must include "code" (when present)
|
|
70
77
|
"""
|
|
71
78
|
url = f"{issuer}/.well-known/openid-configuration"
|
|
72
79
|
async with aiohttp.ClientSession() as session:
|
|
73
80
|
async with session.get(url, headers={"User-Agent": USER_AGENT}) as resp:
|
|
74
81
|
if resp.status != 200:
|
|
75
82
|
raise RuntimeError(f"OIDC discovery failed ({resp.status}) at {url}")
|
|
76
|
-
|
|
83
|
+
doc = await resp.json()
|
|
84
|
+
|
|
85
|
+
# RFC 8414 §3.1: issuer in metadata MUST exactly match the expected issuer.
|
|
86
|
+
if doc.get("issuer") != issuer:
|
|
87
|
+
raise RuntimeError(f"OIDC issuer mismatch: expected {issuer!r}, got {doc.get('issuer')!r}")
|
|
88
|
+
|
|
89
|
+
# Endpoints must use HTTPS to prevent credential interception.
|
|
90
|
+
for key in ("authorization_endpoint", "token_endpoint"):
|
|
91
|
+
endpoint = doc.get(key, "")
|
|
92
|
+
if not endpoint.startswith("https://"):
|
|
93
|
+
raise RuntimeError(f"OIDC {key} must use HTTPS, got {endpoint!r}")
|
|
94
|
+
|
|
95
|
+
# If the server advertises supported challenge methods, verify S256 is included.
|
|
96
|
+
challenge_methods = doc.get("code_challenge_methods_supported")
|
|
97
|
+
if challenge_methods is not None and "S256" not in challenge_methods:
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
f"OIDC server does not support S256 PKCE (supported: {challenge_methods})"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# If the server advertises supported response types, verify "code" is included.
|
|
103
|
+
response_types = doc.get("response_types_supported")
|
|
104
|
+
if response_types is not None and "code" not in response_types:
|
|
105
|
+
raise RuntimeError(
|
|
106
|
+
f"OIDC server does not support 'code' response type (supported: {response_types})"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return doc
|
|
77
110
|
|
|
78
111
|
|
|
79
112
|
# ---- Helpers ----
|
|
@@ -241,7 +274,7 @@ async def _start_callback_server() -> Tuple[Any, int, "asyncio.Future"]:
|
|
|
241
274
|
# so we use a less-common range and try multiple.
|
|
242
275
|
for port in CALLBACK_PORTS:
|
|
243
276
|
try:
|
|
244
|
-
site = web.TCPSite(runner,
|
|
277
|
+
site = web.TCPSite(runner, CALLBACK_HOST, port)
|
|
245
278
|
await site.start()
|
|
246
279
|
return runner, port, result_future
|
|
247
280
|
except OSError:
|
|
@@ -343,7 +376,7 @@ async def login():
|
|
|
343
376
|
console.error(str(e))
|
|
344
377
|
return
|
|
345
378
|
|
|
346
|
-
redirect_uri = f"http://
|
|
379
|
+
redirect_uri = f"http://{CALLBACK_HOST}:{port}{CALLBACK_PATH}"
|
|
347
380
|
|
|
348
381
|
try:
|
|
349
382
|
# Generate PKCE verifier + challenge
|
|
@@ -442,6 +475,7 @@ async def login():
|
|
|
442
475
|
@requires_login
|
|
443
476
|
async def logout():
|
|
444
477
|
refresh_token = config.get("refresh_token")
|
|
478
|
+
revocation_succeeded = False
|
|
445
479
|
|
|
446
480
|
# If this is an OAuth session, attempt server-side token revocation (PCC-678).
|
|
447
481
|
# The /auth/logout endpoint proxies revocation to Clerk with the client_secret.
|
|
@@ -464,7 +498,7 @@ async def logout():
|
|
|
464
498
|
"Token will expire within 24 hours.[/yellow]"
|
|
465
499
|
)
|
|
466
500
|
elif resp.ok:
|
|
467
|
-
|
|
501
|
+
revocation_succeeded = True
|
|
468
502
|
else:
|
|
469
503
|
logger.debug(f"Logout revocation returned {resp.status}")
|
|
470
504
|
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
|
@@ -477,8 +511,13 @@ async def logout():
|
|
|
477
511
|
with console.status("[dim]Removing credentials...[/dim]", spinner="dots"):
|
|
478
512
|
remove_user_config()
|
|
479
513
|
|
|
480
|
-
if
|
|
514
|
+
if revocation_succeeded:
|
|
481
515
|
console.success("Logged out and session revoked.")
|
|
516
|
+
elif refresh_token:
|
|
517
|
+
console.success(
|
|
518
|
+
"Local credentials removed. Server-side revocation could not be confirmed.\n"
|
|
519
|
+
"[dim]The token will expire automatically within 24 hours.[/dim]"
|
|
520
|
+
)
|
|
482
521
|
else:
|
|
483
522
|
console.success(
|
|
484
523
|
"User credentials for Pipecat Cloud removed. Please sign out via dashboard to fully revoke session.",
|
|
@@ -528,8 +567,23 @@ async def _use_pat_impl(token: str):
|
|
|
528
567
|
@auth_cli.command(name="use-pat", help="Authenticate with a Personal Access Token")
|
|
529
568
|
@synchronizer.create_blocking
|
|
530
569
|
async def use_pat(
|
|
531
|
-
token: str = typer.Argument(
|
|
570
|
+
token: Optional[str] = typer.Argument(
|
|
571
|
+
None, help="[deprecated] PAT as argument (leaks to shell history)"
|
|
572
|
+
),
|
|
532
573
|
):
|
|
574
|
+
if token is not None:
|
|
575
|
+
console.print(
|
|
576
|
+
"[yellow]Warning: Passing tokens as arguments is deprecated — they are visible in "
|
|
577
|
+
"shell history and process listings. Next time, omit the argument to use the "
|
|
578
|
+
"secure prompt.[/yellow]"
|
|
579
|
+
)
|
|
580
|
+
else:
|
|
581
|
+
import getpass
|
|
582
|
+
|
|
583
|
+
token = getpass.getpass("Personal Access Token: ")
|
|
584
|
+
if not token:
|
|
585
|
+
console.error("No token provided.")
|
|
586
|
+
return
|
|
533
587
|
await _use_pat_impl(token)
|
|
534
588
|
|
|
535
589
|
|
|
@@ -346,6 +346,10 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
346
346
|
# Close the live display before starting the new polling phase
|
|
347
347
|
live.stop()
|
|
348
348
|
|
|
349
|
+
# Surface API warning (e.g. when identical config was deployed without --force)
|
|
350
|
+
if result and result.get("warning"):
|
|
351
|
+
console.print(f"[yellow]Warning: {result['warning']}[/yellow]")
|
|
352
|
+
|
|
349
353
|
"""
|
|
350
354
|
# 3. Poll status until healthy
|
|
351
355
|
"""
|
|
@@ -556,11 +560,11 @@ def create_deploy_command(app: typer.Typer):
|
|
|
556
560
|
help="Region for service deployment",
|
|
557
561
|
rich_help_panel="Deployment Configuration",
|
|
558
562
|
),
|
|
559
|
-
|
|
563
|
+
force: bool = typer.Option(
|
|
560
564
|
False,
|
|
561
565
|
"--force",
|
|
562
566
|
"-f",
|
|
563
|
-
help="Force deployment
|
|
567
|
+
help="Force a new deployment even if config hasn't changed",
|
|
564
568
|
rich_help_panel="Additional Options",
|
|
565
569
|
),
|
|
566
570
|
yes: bool = typer.Option(
|
|
@@ -632,7 +636,7 @@ def create_deploy_command(app: typer.Typer):
|
|
|
632
636
|
org = organization or config.get("org")
|
|
633
637
|
|
|
634
638
|
# Combine automation flags
|
|
635
|
-
auto_yes = yes or
|
|
639
|
+
auto_yes = yes or force
|
|
636
640
|
|
|
637
641
|
# Compose deployment config from CLI options and config file (if provided)
|
|
638
642
|
# Order of precedence:
|
|
@@ -653,6 +657,7 @@ def create_deploy_command(app: typer.Typer):
|
|
|
653
657
|
)
|
|
654
658
|
partial_config.enable_krisp = krisp or partial_config.enable_krisp
|
|
655
659
|
partial_config.agent_profile = profile or partial_config.agent_profile
|
|
660
|
+
partial_config.force_redeploy = force
|
|
656
661
|
|
|
657
662
|
# Override build config from CLI args
|
|
658
663
|
if build_dir:
|
|
@@ -15,7 +15,6 @@ import questionary
|
|
|
15
15
|
import typer
|
|
16
16
|
from loguru import logger
|
|
17
17
|
from rich import box
|
|
18
|
-
from rich.live import Live
|
|
19
18
|
from rich.panel import Panel
|
|
20
19
|
from rich.table import Table
|
|
21
20
|
|
|
@@ -492,7 +491,7 @@ async def delete(
|
|
|
492
491
|
|
|
493
492
|
|
|
494
493
|
@secrets_cli.command(
|
|
495
|
-
name="image-pull-secret", help="Create an image pull secret for active organization."
|
|
494
|
+
name="image-pull-secret", help="Create or update an image pull secret for active organization."
|
|
496
495
|
)
|
|
497
496
|
@synchronizer.create_blocking
|
|
498
497
|
@requires_login
|
|
@@ -509,6 +508,12 @@ async def image_pull_secret(
|
|
|
509
508
|
base64encode: bool = typer.Option(
|
|
510
509
|
True, "--encode", "-e", help="base64 encode credentials for added security"
|
|
511
510
|
),
|
|
511
|
+
skip_confirm: boolean = typer.Option(
|
|
512
|
+
False,
|
|
513
|
+
"--skip",
|
|
514
|
+
"-s",
|
|
515
|
+
help="Skip confirmations / force creation or update",
|
|
516
|
+
),
|
|
512
517
|
organization: str = typer.Option(
|
|
513
518
|
None,
|
|
514
519
|
"--organization",
|
|
@@ -556,13 +561,10 @@ async def image_pull_secret(
|
|
|
556
561
|
return typer.Exit(1)
|
|
557
562
|
|
|
558
563
|
# Check if secret already exists
|
|
559
|
-
|
|
560
|
-
with
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
),
|
|
564
|
-
refresh_per_second=4,
|
|
565
|
-
) as live:
|
|
564
|
+
existing_secret = None
|
|
565
|
+
with console.status(
|
|
566
|
+
f"[dim]Checking if image pull secret '{name}' already exists[/dim]", spinner="dots"
|
|
567
|
+
):
|
|
566
568
|
data, error = await API.secrets_list(org=org)
|
|
567
569
|
|
|
568
570
|
if error:
|
|
@@ -572,19 +574,20 @@ async def image_pull_secret(
|
|
|
572
574
|
existing_secret = next(
|
|
573
575
|
(s for s in data if s["name"] == name and s["type"] == "imagePullSecret"), None
|
|
574
576
|
)
|
|
575
|
-
if existing_secret:
|
|
576
|
-
live.stop()
|
|
577
|
-
console.error(
|
|
578
|
-
f"Image pull secret '[bold]{name}'[/bold] already exists. Please choose a different name or delete the existing one first."
|
|
579
|
-
)
|
|
580
|
-
return typer.Exit(1)
|
|
581
577
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
578
|
+
if existing_secret and not skip_confirm:
|
|
579
|
+
confirm = await questionary.confirm(
|
|
580
|
+
f"Image pull secret '{name}' already exists. Update it?"
|
|
581
|
+
).ask_async()
|
|
582
|
+
if not confirm:
|
|
583
|
+
console.print("[bold red]Update cancelled[/bold red]")
|
|
584
|
+
return typer.Exit(1)
|
|
587
585
|
|
|
586
|
+
used_region = None
|
|
587
|
+
with console.status(
|
|
588
|
+
f"[dim]{'Updating' if existing_secret else 'Creating'} image pull secret [bold]'{name}'[/bold][/dim]",
|
|
589
|
+
spinner="dots",
|
|
590
|
+
):
|
|
588
591
|
data, error = await API.secrets_upsert(
|
|
589
592
|
data={"isImagePullSecret": True, "secretValue": credentials, "host": host},
|
|
590
593
|
set_name=name,
|
|
@@ -600,6 +603,7 @@ async def image_pull_secret(
|
|
|
600
603
|
used_region = data["region"]
|
|
601
604
|
|
|
602
605
|
region_info = f" in [bold cyan]{used_region}[/bold cyan]" if used_region else ""
|
|
606
|
+
action = "updated" if existing_secret else "created"
|
|
603
607
|
console.success(
|
|
604
|
-
f"Image pull secret [bold green]'{name}'[/bold green] for [bold green]{host}[/bold green]
|
|
608
|
+
f"Image pull secret [bold green]'{name}'[/bold green] for [bold green]{host}[/bold green] {action} successfully{region_info}.",
|
|
605
609
|
)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import os
|
|
8
8
|
import stat
|
|
9
9
|
import sys
|
|
10
|
+
import tempfile
|
|
10
11
|
from typing import Optional
|
|
11
12
|
|
|
12
13
|
import toml
|
|
@@ -80,20 +81,38 @@ user_config = _read_user_config()
|
|
|
80
81
|
|
|
81
82
|
def _write_user_config(new_config):
|
|
82
83
|
dir_path = os.path.dirname(user_config_path)
|
|
83
|
-
os.makedirs(dir_path, exist_ok=True)
|
|
84
|
+
os.makedirs(dir_path, mode=0o700, exist_ok=True)
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
# Atomic write: serialize to a temp file in the same directory, fsync,
|
|
87
|
+
# then rename into place. This prevents a crash mid-write from leaving
|
|
88
|
+
# a truncated or empty credentials file (RFC 6749 §10.3).
|
|
89
|
+
fd = None
|
|
90
|
+
tmp_path = None
|
|
91
|
+
try:
|
|
92
|
+
fd, tmp_path = tempfile.mkstemp(dir=dir_path, prefix=".pipecatcloud_", suffix=".tmp")
|
|
93
|
+
# mkstemp opens with 0o600 by default — restrictive from creation.
|
|
94
|
+
with os.fdopen(fd, "w") as f:
|
|
95
|
+
fd = None # os.fdopen takes ownership of the fd
|
|
96
|
+
toml.dump(new_config, f)
|
|
97
|
+
f.flush()
|
|
98
|
+
os.fsync(f.fileno())
|
|
99
|
+
os.replace(tmp_path, user_config_path)
|
|
100
|
+
tmp_path = None # rename succeeded, nothing to clean up
|
|
101
|
+
finally:
|
|
102
|
+
if fd is not None:
|
|
103
|
+
os.close(fd)
|
|
104
|
+
if tmp_path is not None:
|
|
105
|
+
try:
|
|
106
|
+
os.unlink(tmp_path)
|
|
107
|
+
except OSError:
|
|
108
|
+
pass
|
|
93
109
|
|
|
94
110
|
|
|
95
111
|
def remove_user_config():
|
|
96
|
-
|
|
112
|
+
try:
|
|
113
|
+
os.remove(user_config_path)
|
|
114
|
+
except FileNotFoundError:
|
|
115
|
+
pass
|
|
97
116
|
|
|
98
117
|
|
|
99
118
|
def update_user_config(
|
|
@@ -42,8 +42,18 @@ def show_config_callback(value: bool):
|
|
|
42
42
|
from pipecatcloud._utils.deploy_utils import load_deploy_config_file
|
|
43
43
|
from pipecatcloud.cli.config import deploy_config_path
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
config_dict = config.to_dict()
|
|
46
|
+
|
|
47
|
+
# Redact credential values — users who need raw values can read the
|
|
48
|
+
# config file directly (~/.config/pipecatcloud/pipecatcloud.toml).
|
|
49
|
+
for key in ("token", "refresh_token"):
|
|
50
|
+
if config_dict.get(key):
|
|
51
|
+
val = str(config_dict[key])
|
|
52
|
+
# Show token type prefix (e.g. "pcc_pat_****") for diagnostics
|
|
53
|
+
prefix = val[:8] if len(val) > 12 else ""
|
|
54
|
+
config_dict[key] = f"{prefix}****"
|
|
55
|
+
|
|
56
|
+
pprint(config_dict)
|
|
47
57
|
|
|
48
58
|
# Check for deploy config
|
|
49
59
|
try:
|
|
@@ -76,7 +86,7 @@ def cli(
|
|
|
76
86
|
None,
|
|
77
87
|
"--show-cli-config",
|
|
78
88
|
callback=show_config_callback,
|
|
79
|
-
help="Show CLI internal configuration (credentials
|
|
89
|
+
help="Show CLI internal configuration (credentials redacted)",
|
|
80
90
|
),
|
|
81
91
|
):
|
|
82
92
|
pass
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Cloud hosting for Pipecat AI applications
|
|
5
5
|
License: BSD 2-Clause License
|
|
6
6
|
Project-URL: Website, https://docs.pipecat.daily.co/
|
|
7
7
|
Keywords: pipecat,pipecat-ai,voice,ai,llm
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: aiohttp<4.0.0,>=3.11.12
|
|
@@ -18,7 +18,7 @@ Requires-Dist: synchronicity<1.0.0,>=0.11
|
|
|
18
18
|
Requires-Dist: toml~=0.10.2
|
|
19
19
|
Requires-Dist: questionary~=2.1.0
|
|
20
20
|
Provides-Extra: pipecat
|
|
21
|
-
Requires-Dist: pipecat-ai>=0.0
|
|
21
|
+
Requires-Dist: pipecat-ai>=1.0.0; extra == "pipecat"
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
24
|
<h1><div align="center">
|
|
@@ -97,6 +97,32 @@ if __name__ == "__main__":
|
|
|
97
97
|
asyncio.run(main())
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
## Troubleshooting
|
|
101
|
+
|
|
102
|
+
### SSL certificate errors on macOS
|
|
103
|
+
|
|
104
|
+
If `pcc auth login` fails with an SSL certificate verification error, your Python
|
|
105
|
+
installation may not have access to the macOS system certificate store. This is
|
|
106
|
+
common with Python installed via pyenv, conda, or the python.org installer.
|
|
107
|
+
|
|
108
|
+
To diagnose:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import ssl, sys, os
|
|
112
|
+
print(ssl.get_default_verify_paths())
|
|
113
|
+
print(os.path.realpath(sys.executable))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
To fix, install `certifi` and point Python to its certificates:
|
|
117
|
+
|
|
118
|
+
```shell
|
|
119
|
+
pip install certifi
|
|
120
|
+
export SSL_CERT_FILE=$(python -c "import certifi; print(certifi.where())")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For the python.org installer, you can also run the bundled
|
|
124
|
+
`Install Certificates.command` script found in `/Applications/Python X.Y/`.
|
|
125
|
+
|
|
100
126
|
## 🛠️ Contributing
|
|
101
127
|
|
|
102
128
|
### Setup Steps
|