pipecatcloud 1.3.0__tar.gz → 1.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.
Files changed (103) hide show
  1. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.github/workflows/publish-pypi.yml +39 -0
  2. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/CHANGELOG.md +115 -0
  3. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/CLAUDE.md +3 -0
  4. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/PKG-INFO +6 -2
  5. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/README.md +4 -0
  6. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/pyproject.toml +2 -2
  7. pipecatcloud-1.5.0/scripts/release-changelog.py +113 -0
  8. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/_utils/build_utils.py +11 -4
  9. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/_utils/deploy_utils.py +2 -1
  10. pipecatcloud-1.5.0/src/pipecatcloud/_utils/log_utils.py +42 -0
  11. pipecatcloud-1.5.0/src/pipecatcloud/_utils/regions.py +99 -0
  12. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/api.py +212 -4
  13. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/agent.py +6 -8
  14. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/auth.py +4 -3
  15. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/deploy.py +7 -10
  16. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/github.py +142 -39
  17. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/organizations.py +2 -1
  18. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/secrets.py +244 -51
  19. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/entry_point.py +7 -0
  20. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/config.py +1 -2
  21. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/constants.py +1 -1
  22. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud.egg-info/PKG-INFO +6 -2
  23. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud.egg-info/SOURCES.txt +6 -0
  24. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud.egg-info/requires.txt +1 -1
  25. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud.egg-info/scm_file_list.json +6 -0
  26. pipecatcloud-1.5.0/src/pipecatcloud.egg-info/scm_version.json +8 -0
  27. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_api_client.py +70 -0
  28. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_github_integration.py +346 -6
  29. pipecatcloud-1.5.0/tests/test_log_redaction.py +472 -0
  30. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_regions.py +0 -57
  31. pipecatcloud-1.5.0/tests/test_secrets_bulk_write.py +450 -0
  32. pipecatcloud-1.5.0/tests/test_secrets_keys.py +244 -0
  33. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_secrets_reference.py +4 -8
  34. pipecatcloud-1.5.0/tests/test_secrets_retry.py +550 -0
  35. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/uv.lock +604 -479
  36. pipecatcloud-1.3.0/src/pipecatcloud/_utils/regions.py +0 -89
  37. pipecatcloud-1.3.0/src/pipecatcloud.egg-info/scm_version.json +0 -8
  38. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.claude/settings.json +0 -0
  39. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md +0 -0
  40. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.github/workflows/format.yml +0 -0
  41. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.github/workflows/publish-test.yml +0 -0
  42. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.github/workflows/python-compatibility.yml +0 -0
  43. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.github/workflows/tests.yml +0 -0
  44. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.github/workflows/update-docs.yml +0 -0
  45. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.gitignore +0 -0
  46. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/.pre-commit-config.yaml +0 -0
  47. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/LICENSE +0 -0
  48. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/pipecat-cloud.png +0 -0
  49. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/pytest.ini +0 -0
  50. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/scripts/fix-ruff.sh +0 -0
  51. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/setup.cfg +0 -0
  52. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/__init__.py +0 -0
  53. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/__version__.py +0 -0
  54. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/_utils/__init__.py +0 -0
  55. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/_utils/async_utils.py +0 -0
  56. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  57. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/_utils/console_utils.py +0 -0
  58. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/_utils/github_utils.py +0 -0
  59. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/agent.py +0 -0
  60. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/__init__.py +0 -0
  61. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/api.py +0 -0
  62. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  63. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/agent_profiles.py +0 -0
  64. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/build.py +0 -0
  65. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/docker.py +0 -0
  66. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/regions.py +0 -0
  67. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/registry_keys.py +0 -0
  68. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/commands/spend_limit.py +0 -0
  69. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/cli/config.py +0 -0
  70. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/exception.py +0 -0
  71. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/py.typed +0 -0
  72. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/session.py +0 -0
  73. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
  74. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  75. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  76. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  77. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/__init__.py +0 -0
  78. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/conftest.py +0 -0
  79. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_agent_args.py +0 -0
  80. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_agent_profiles_and_resources.py +0 -0
  81. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_agent_sessions.py +0 -0
  82. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_agent_status_output.py +0 -0
  83. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_agent_stop.py +0 -0
  84. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_auth.py +0 -0
  85. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_build_id_display.py +0 -0
  86. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_build_utils.py +0 -0
  87. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_config_file.py +0 -0
  88. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_deploy_architecture.py +0 -0
  89. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_deploy_image_pull_secret.py +0 -0
  90. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_deploy_poll.py +0 -0
  91. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_deploy_referenced_secret_set.py +0 -0
  92. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_deployment_status.py +0 -0
  93. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_docker_commands.py +0 -0
  94. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_exit_codes.py +0 -0
  95. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_json_output.py +0 -0
  96. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_krisp_viva.py +0 -0
  97. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_max_session_duration.py +0 -0
  98. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_organizations.py +0 -0
  99. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_output_modes.py +0 -0
  100. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_pat_auth.py +0 -0
  101. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_regions_lifecycle.py +0 -0
  102. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_secrets_set_referenced.py +0 -0
  103. {pipecatcloud-1.3.0 → pipecatcloud-1.5.0}/tests/test_spend_limit.py +0 -0
@@ -78,3 +78,42 @@ jobs:
78
78
  verbose: true
79
79
  print-hash: true
80
80
  repository-url: https://test.pypi.org/legacy/
81
+
82
+ # Runs once the package is installable from PyPI, so the release notification
83
+ # never points at a version nobody can install yet. Notes come from the
84
+ # CHANGELOG.md section at the tag: the changelog is the single source of
85
+ # release notes, reformatted for GitHub by scripts/release-changelog.py. A tag
86
+ # whose version has no changelog section fails the run.
87
+ github-release:
88
+ name: 'Create GitHub Release'
89
+ runs-on: ubuntu-latest
90
+ needs: [publish-to-pypi]
91
+ permissions:
92
+ contents: write
93
+ steps:
94
+ - name: Checkout repo
95
+ uses: actions/checkout@v4
96
+ with:
97
+ ref: ${{ github.event.inputs.gitref }}
98
+
99
+ - name: Set up Python
100
+ uses: actions/setup-python@v5
101
+ with:
102
+ python-version: '3.12'
103
+
104
+ - name: Generate release notes
105
+ env:
106
+ GITREF: ${{ github.event.inputs.gitref }}
107
+ run: |
108
+ python scripts/release-changelog.py --no-heading "${GITREF#v}" > release-notes.md
109
+
110
+ - name: Publish release
111
+ env:
112
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
113
+ GITREF: ${{ github.event.inputs.gitref }}
114
+ run: |
115
+ if gh release view "$GITREF" >/dev/null 2>&1; then
116
+ gh release edit "$GITREF" --notes-file release-notes.md
117
+ else
118
+ gh release create "$GITREF" --title "$GITREF" --notes-file release-notes.md
119
+ fi
@@ -5,6 +5,121 @@ 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
+ ## [1.5.0] - 2026-11-05
9
+
10
+ ### Added
11
+
12
+ - `secrets keys --region <region>` shows your organization's secret keys in a
13
+ region: each key's fingerprint, state, created and retired times, and how
14
+ many secrets it holds, as text or with `--output json`. The fingerprint is
15
+ the one a security notice names and, in a self-hosted region, the one your
16
+ cluster shows for the key. It never shows key material. Against a server
17
+ that does not offer this yet, it says so and exits 1.
18
+
19
+ ### Changed
20
+
21
+ - `secrets set` with several values now writes them in one request, which the
22
+ server applies whole or not at all, so a large set takes one write in place
23
+ of one per key. Against a server that does not take this yet, including one
24
+ part way through an update, it writes one key at a time as before, and still
25
+ says how far it got if one fails.
26
+ - `secrets set`, `secrets unset` and `secrets image-pull-secret` now wait and
27
+ retry when the API answers a write with a 503 and a `Retry-After` of up to
28
+ 30 seconds, as a region can on its first secrets write. They retry up to 3 times, say they
29
+ are waiting, and fail as before if the 503 persists. Other errors, and a 503
30
+ without a usable `Retry-After`, are not retried. A `Retry-After` above 30
31
+ seconds is not waited out; the error says how long the region asked for.
32
+ A write that ends in a 503 may still have reached the region, unless the
33
+ error says nothing was stored. Running the same command again settles it.
34
+
35
+ ### Fixed
36
+
37
+ - `--region` is now checked against the regions of the organization given
38
+ with `--organization`, not only the organization in your config file. This
39
+ affects `secrets set`, `secrets list`, `secrets image-pull-secret`,
40
+ `secrets reference`, `agent list` and `deploy`. A region that belongs only
41
+ to the other organization, such as a self-hosted region, was refused as
42
+ invalid.
43
+ - `secrets set` no longer reports success when a write gets no answer from the
44
+ server, as when the connection drops. It says so and exits 1.
45
+
46
+ ### Security
47
+
48
+ - Logs no longer contain secret values or other request data. With debug
49
+ logging on (`PIPECAT_CLI_LOG_LEVEL=DEBUG` for the CLI, `PCC_LOG_LEVEL=DEBUG`
50
+ for the Python package), `secrets set` wrote the values it was given to
51
+ stderr, and every API request wrote its body and query there, including
52
+ image pull credentials and session data. Requests now log their method,
53
+ path and how much they carry. Tokens were not in these lines. If you ran
54
+ with debug logging on and the output was kept, for example in CI logs,
55
+ change the values it contains.
56
+ - An unexpected error in the CLI could print its local variables in the
57
+ traceback, including secret values and your token. Tracebacks now show code
58
+ only. To see them as before, set `TYPER_STANDARD_TRACEBACK` to an empty value.
59
+ - A failed build context upload now logs its status and S3 error code, not
60
+ S3's error document, which can repeat signed upload details. This line is
61
+ logged at the default level.
62
+ - `secrets set` no longer shows the first characters of each value when it asks
63
+ you to confirm. It shows a fixed mask, the same for any value. Without
64
+ `--skip` and without a terminal, as in CI, it printed those characters and
65
+ then failed; it now fails first. If such output was kept, change the values
66
+ it contains.
67
+ - The locked versions of `virtualenv`, `packaging`, `distlib`, `Pillow` and
68
+ `setuptools` are upgraded for security fixes. These are the versions in
69
+ `uv.lock`, used to develop and test this package. What it requires when you
70
+ install it has not changed, so upgrade your own environment if it holds an
71
+ older one.
72
+
73
+ ## [1.4.0] - 2026-10-22
74
+
75
+ ### Added
76
+
77
+ - `pipecat cloud github connect --existing` links a GitHub App installation
78
+ that is already on GitHub to your Pipecat Cloud organization. Use it for
79
+ an installation you installed or had an owner approve, while still a
80
+ member of the GitHub organization it is on, or one on your personal
81
+ account or a GitHub organization you own. That covers an install request
82
+ an owner approved and an install made from GitHub, neither of which linked
83
+ before. You authorize the App on GitHub, then confirm the installation to
84
+ link on the Pipecat Cloud dashboard, and the CLI waits up to 14 minutes
85
+ for it. `github status` and the `github connect` timeout message point to
86
+ it.
87
+
88
+ ### Changed
89
+
90
+ - Connecting an organization to GitHub now finishes on the Pipecat Cloud
91
+ dashboard, where you may need to sign in as the same Pipecat Cloud user the
92
+ CLI is logged in as. GitHub also asks you to authorize the App after you
93
+ install it. This happens on the server side, so it applies to every CLI
94
+ version. `pipecat cloud github connect` now says so, also with `--json`
95
+ (on stderr), and waits up to 26 minutes for the connection instead of 15,
96
+ to cover both steps. Older versions stop waiting after 15 minutes; the
97
+ connection still completes, and `pipecat cloud github status` shows it.
98
+ - `pipecat cloud github connect` stops waiting as soon as Pipecat Cloud
99
+ refuses the CLI's sign-in or its access to the organization, instead of
100
+ waiting until it times out.
101
+ - The `aiohttp` minimum version is now `3.14.4`, the first release with fixes
102
+ for several security advisories. The SDK makes its API requests with
103
+ `aiohttp`, and `pipecat cloud auth login` uses it for the local server that
104
+ receives the sign-in callback.
105
+
106
+ ### Removed
107
+
108
+ - The `login_path` and `login_status_path` settings and their
109
+ `PIPECAT_LOGIN_PATH` and `PIPECAT_LOGIN_STATUS_PATH` overrides. They
110
+ pointed at the device-code sign-in that 0.4.1 replaced with OAuth and
111
+ PKCE, and nothing has read them since, so setting them had no effect.
112
+ Removing them is not a breaking change: the only difference is that they
113
+ no longer appear in `--show-cli-config` output. Pipecat Cloud is retiring
114
+ that sign-in, so a CLI older than 0.4.1 has to be upgraded to log in.
115
+ `pipecat cloud auth login` is unchanged.
116
+
117
+ ### Fixed
118
+
119
+ - `pipecat cloud github connect` says it could not reach Pipecat Cloud when
120
+ no usable response comes back, such as on a dropped connection, instead
121
+ of saying the API returned no URL.
122
+
8
123
  ## [1.3.0] - 2026-10-01
9
124
 
10
125
  ### Added
@@ -103,6 +103,7 @@ pcc-deploy.toml Example deployment config consumed by `pipecat cloud de
103
103
  - Auth uses OAuth2 with PKCE (RFC 7636). OAuth endpoints are discovered from the API server, not hardcoded. The callback server tries ports 8400-8404.
104
104
  - The session argument types in `agent.py` subclass pipecat-ai's runner types and need pipecat-ai (>= 1.0.0); there are no fallback definitions. `MOQSessionArguments` needs 1.12.0 (`MOQRunnerArguments.relay_url`), so it is imported and defined under its own guard: on older pipecat-ai the module still imports and only that name is missing. It stays out of `__all__` for the same reason.
105
105
  - Krisp VIVA audio filter names are `Literal["tel", "pro"]` in `constants.py`. These must match the server-side ConfigMap, so coordinate changes with the backend.
106
+ - Never log a request or response body, a header, or an exception's text: they can hold secret values and tokens. Use `_utils/log_utils.py`. `tests/test_log_redaction.py` fails on a log call that reads a name or attribute it knows can hold one, however it is formatted, or that uses `logger.opt`, `logger.exception` or `logger.catch`. Only `describe_*` and `len` calls are exempt.
106
107
 
107
108
  ## Release flow
108
109
 
@@ -110,6 +111,8 @@ pcc-deploy.toml Example deployment config consumed by `pipecat cloud de
110
111
  2. Tag the commit with `v<MAJOR>.<MINOR>.<PATCH>`.
111
112
  3. Trigger `publish-pypi.yml` manually with that tag. `setuptools_scm` picks up the version from the tag.
112
113
 
114
+ After publishing to PyPI, the workflow creates (or updates) the GitHub Release for the tag. Its notes are the tag's `CHANGELOG.md` section, reformatted by `scripts/release-changelog.py`, so fix release-note wording in the changelog rather than on GitHub: a re-run overwrites edits made there. Preview the notes locally with `python scripts/release-changelog.py --no-heading 1.3.0`.
115
+
113
116
  ## Writing style for generated content in this repo
114
117
 
115
118
  - No emdashes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 1.3.0
3
+ Version: 1.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.ai/pipecat-cloud
@@ -8,7 +8,7 @@ Keywords: pipecat,pipecat-ai,voice,ai,llm
8
8
  Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
- Requires-Dist: aiohttp<4.0.0,>=3.11.12
11
+ Requires-Dist: aiohttp<4.0.0,>=3.14.4
12
12
  Requires-Dist: fastapi<1.0.0,>=0.115.6
13
13
  Requires-Dist: loguru~=0.7.3
14
14
  Requires-Dist: python-dotenv<2.0.0,>=1.0.1
@@ -70,6 +70,10 @@ To use Pipecat Cloud programmatically (without the CLI), install the package dir
70
70
 
71
71
  4. Build and deploy your agent `pipecat cloud deploy`
72
72
 
73
+ ### Seeing your secret keys
74
+
75
+ `pipecat cloud secrets keys --region <region>` shows your organization's secret keys in a region: each key's fingerprint, state, created and retired times, and how many secrets it holds. The fingerprint is the one a security notice names and, in a self-hosted region, the one your cluster shows for the key, so you can compare them. Add `--output json` before `secrets` for a single JSON document. Key material is never shown.
76
+
73
77
  ### Usage in Python scripts
74
78
 
75
79
  If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.session` module.
@@ -47,6 +47,10 @@ To use Pipecat Cloud programmatically (without the CLI), install the package dir
47
47
 
48
48
  4. Build and deploy your agent `pipecat cloud deploy`
49
49
 
50
+ ### Seeing your secret keys
51
+
52
+ `pipecat cloud secrets keys --region <region>` shows your organization's secret keys in a region: each key's fingerprint, state, created and retired times, and how many secrets it holds. The fingerprint is the one a security notice names and, in a self-hosted region, the one your cluster shows for the key, so you can compare them. Add `--output json` before `secrets` for a single JSON document. Key material is never shown.
53
+
50
54
  ### Usage in Python scripts
51
55
 
52
56
  If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.session` module.
@@ -11,7 +11,7 @@ license = { text = "BSD 2-Clause License" }
11
11
  readme = "README.md"
12
12
  keywords = ["pipecat", "pipecat-ai", "voice", "ai", "llm"]
13
13
  dependencies = [
14
- "aiohttp>=3.11.12,<4.0.0",
14
+ "aiohttp>=3.14.4,<4.0.0",
15
15
  "fastapi>=0.115.6,<1.0.0",
16
16
  "loguru~=0.7.3",
17
17
  "python-dotenv>=1.0.1,<2.0.0",
@@ -41,7 +41,7 @@ dev = [
41
41
  "pytest>=9.0.0,<10",
42
42
  "pytest-asyncio>=1.0.0,<2",
43
43
  "ruff>=0.12.11,<1",
44
- "setuptools>=83.0.0,<84",
44
+ "setuptools>=84.1.0,<85",
45
45
  "setuptools_scm~=8.3.1",
46
46
  # Imported by the tests to compare pipecat-ai versions.
47
47
  "packaging>=25.0",
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ """Generate release notes from CHANGELOG.md for use in GitHub Releases (or anywhere).
3
+
4
+ Usage::
5
+
6
+ python scripts/release-changelog.py VERSION
7
+ python scripts/release-changelog.py 1.1.0
8
+ python scripts/release-changelog.py --file path/to/CHANGELOG.md 1.1.0
9
+ python scripts/release-changelog.py --no-heading 1.1.0
10
+
11
+ Extracts the requested ``## [VERSION]`` section (heading and ``### …``
12
+ subheadings included) and prints it to stdout. The only transformation
13
+ applied is collapsing each entry paragraph onto a single line, so it is
14
+ suitable for pasting into release notes that don't need 80-column wrapping.
15
+ ``(PR [...])`` references stay on their own two-space-indented continuation
16
+ line. The input file is never modified.
17
+
18
+ ``--no-heading`` drops the ``## [VERSION]`` line, leaving the body to start
19
+ at the first ``### …`` subheading. GitHub Releases carry the version in the
20
+ release title, so the heading would be redundant there.
21
+
22
+ Every paragraph is unfilled. Indentation is preserved — each logical line
23
+ (bullets, sub-bullets, and the ``(PR [...])`` continuation) keeps its
24
+ leading whitespace; only the wrapped continuation lines that follow them
25
+ get joined back. Code-block paragraphs (triple-backtick fences, or deeply
26
+ indented blocks with no list markers around) are passed through untouched.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import argparse
32
+ import re
33
+ import sys
34
+ from pathlib import Path
35
+
36
+ DEFAULT_CHANGELOG = Path(__file__).resolve().parent.parent / "CHANGELOG.md"
37
+
38
+ PARAGRAPH_SPLIT_RE = re.compile(r"(\n[ \t]*\n+)")
39
+ CONTINUATION_RE = re.compile(r"\n(?![ \t]*[-*+] )[ \t]+")
40
+ PR_RE = re.compile(r" \(PR \[")
41
+ CODE_FENCE_RE = re.compile(r"^\s*```")
42
+ DEEP_INDENT_RE = re.compile(r"^ {4,}")
43
+ LIST_MARKER_RE = re.compile(r"^[ \t]*[-*+] ")
44
+ SECTION_HEADING_RE = re.compile(r"^## \[", re.MULTILINE)
45
+
46
+
47
+ def is_code_block_paragraph(paragraph: str) -> bool:
48
+ lines = paragraph.splitlines()
49
+ if any(CODE_FENCE_RE.match(line) for line in lines):
50
+ return True
51
+ # In a list paragraph, 4-space indent is a sub-bullet continuation, not
52
+ # code. Only treat deep-indented lines as code when no bullets are around
53
+ # to claim them.
54
+ if any(LIST_MARKER_RE.match(line) for line in lines):
55
+ return False
56
+ return any(DEEP_INDENT_RE.match(line) for line in lines)
57
+
58
+
59
+ def unfill_entry(paragraph: str) -> str:
60
+ joined = CONTINUATION_RE.sub(" ", paragraph)
61
+ return PR_RE.sub("\n (PR [", joined, count=1)
62
+
63
+
64
+ def process(text: str) -> str:
65
+ parts = PARAGRAPH_SPLIT_RE.split(text)
66
+ for i in range(0, len(parts), 2):
67
+ para = parts[i]
68
+ if para and not is_code_block_paragraph(para):
69
+ parts[i] = unfill_entry(para)
70
+ return "".join(parts)
71
+
72
+
73
+ def extract_section(text: str, version: str) -> str:
74
+ head_re = re.compile(rf"^## \[{re.escape(version)}\]", re.MULTILINE)
75
+ m = head_re.search(text)
76
+ if not m:
77
+ sys.exit(f"error: version {version!r} not found")
78
+ nxt = SECTION_HEADING_RE.search(text, m.end())
79
+ end = nxt.start() if nxt else len(text)
80
+ return text[m.start() : end]
81
+
82
+
83
+ def strip_heading(section: str) -> str:
84
+ _, _, body = section.partition("\n")
85
+ return body.lstrip("\n")
86
+
87
+
88
+ def main() -> None:
89
+ summary = (__doc__ or "").splitlines()[0]
90
+ parser = argparse.ArgumentParser(description=summary)
91
+ parser.add_argument("version", help="Version to extract (e.g. 1.1.0).")
92
+ parser.add_argument(
93
+ "--file",
94
+ type=Path,
95
+ default=DEFAULT_CHANGELOG,
96
+ help=f"Path to the changelog (default: {DEFAULT_CHANGELOG}).",
97
+ )
98
+ parser.add_argument(
99
+ "--no-heading",
100
+ action="store_true",
101
+ help="Omit the '## [VERSION]' heading line.",
102
+ )
103
+ args = parser.parse_args()
104
+
105
+ text = args.file.read_text()
106
+ section = extract_section(text, args.version)
107
+ if args.no_heading:
108
+ section = strip_heading(section)
109
+ sys.stdout.write(process(section))
110
+
111
+
112
+ if __name__ == "__main__":
113
+ main()
@@ -17,6 +17,7 @@ import gzip
17
17
  import hashlib
18
18
  import io
19
19
  import os
20
+ import re
20
21
  import tarfile
21
22
  import time
22
23
  from collections.abc import Callable
@@ -26,6 +27,8 @@ from pathlib import Path
26
27
  import aiohttp
27
28
  from loguru import logger
28
29
 
30
+ from pipecatcloud._utils.log_utils import describe_exception
31
+
29
32
  # Default patterns to exclude from build context
30
33
  DEFAULT_EXCLUSIONS: set[str] = {
31
34
  # Version control
@@ -189,7 +192,7 @@ def load_dockerignore(context_dir: Path) -> set[str] | None:
189
192
  if normalized:
190
193
  patterns.add(normalized)
191
194
  except Exception as e:
192
- logger.warning(f"Failed to read .dockerignore: {e}")
195
+ logger.warning(f"Failed to read .dockerignore: {describe_exception(e)}")
193
196
  return None
194
197
 
195
198
  return patterns
@@ -375,11 +378,15 @@ async def upload_to_s3(
375
378
  logger.debug(f"Upload successful: {response.status}")
376
379
  return True
377
380
  else:
378
- body = await response.text()
379
- logger.error(f"Upload failed: {response.status} - {body}")
381
+ # S3's error document can echo the signed policy and the
382
+ # credential it was given, so only its code is logged.
383
+ code = re.search(r"<Code>(\w+)</Code>", await response.text())
384
+ logger.error(
385
+ f"Upload failed: {response.status}{f' ({code[1]})' if code else ''}"
386
+ )
380
387
  return False
381
388
  except Exception as e:
382
- logger.error(f"Upload error: {e}")
389
+ logger.error(f"Upload error: {describe_exception(e)}")
383
390
  return False
384
391
 
385
392
 
@@ -17,6 +17,7 @@ import typer
17
17
  from attr import dataclass, field
18
18
  from loguru import logger
19
19
 
20
+ from pipecatcloud._utils.log_utils import describe_exception
20
21
  from pipecatcloud.cli import PIPECAT_DEPLOY_CONFIG_PATH
21
22
  from pipecatcloud.constants import KRISP_VIVA_MODELS, KrispVivaAudioFilter
22
23
  from pipecatcloud.exception import ConfigFileError
@@ -607,7 +608,7 @@ def load_deploy_config_file() -> DeployConfigParams | None:
607
608
  return validated_config
608
609
 
609
610
  except Exception as e:
610
- logger.debug(e)
611
+ logger.debug(describe_exception(e))
611
612
  raise ConfigFileError(str(e))
612
613
 
613
614
 
@@ -0,0 +1,42 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ """
8
+ What a log line may say about a request, a response or a failure.
9
+
10
+ Request and response bodies can hold secret values and credentials, under any name
11
+ and at any depth, and an exception's text can quote them. So a log line says how
12
+ much is there and what kind of thing it is, and never quotes any of it. Headers are
13
+ not logged at all: they carry the token.
14
+ """
15
+
16
+
17
+ def describe_url(url: str) -> str:
18
+ """A URL without its query string, whose values are not for a log."""
19
+ return url.split("?", 1)[0]
20
+
21
+
22
+ def describe_body(body: object) -> str:
23
+ """How much a request or response body holds, and never what."""
24
+ if body is None:
25
+ return "none"
26
+ if isinstance(body, dict | list | tuple):
27
+ return f"{type(body).__name__} of {len(body)}"
28
+ return type(body).__name__
29
+
30
+
31
+ def describe_exception(exc: BaseException) -> str:
32
+ """An exception's type, and its HTTP status if it has one, without its text."""
33
+ status = getattr(exc, "status", None)
34
+ return (
35
+ f"{type(exc).__name__} (status {status})" if isinstance(status, int) else type(exc).__name__
36
+ )
37
+
38
+
39
+ def describe_api_error(error: object) -> str:
40
+ """The code of an error body from the API, without its text."""
41
+ code = error.get("code") if isinstance(error, dict) else None
42
+ return f"code {code}" if code else "no code"
@@ -0,0 +1,99 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ """
8
+ Region utilities for Pipecat Cloud CLI.
9
+
10
+ Provides functions to fetch and cache available regions from the API.
11
+ """
12
+
13
+ from pipecatcloud.cli.api import API
14
+ from pipecatcloud.cli.config import config
15
+
16
+ # Module-level cache of region lists, keyed by organization. Each organization
17
+ # has its own list (a self-hosted region is visible only to its organization),
18
+ # so one list must never answer for another.
19
+ _regions_cache: dict[str, list[dict[str, str]]] | None = None
20
+
21
+
22
+ async def get_regions(org: str | None = None) -> list[dict[str, str]]:
23
+ """
24
+ Fetch available regions for an organization from the API with caching.
25
+
26
+ The list is cached per organization, so the API is called once per
27
+ organization per CLI session.
28
+
29
+ Args:
30
+ org: Organization to list regions for. Defaults to the organization in
31
+ the config file.
32
+
33
+ Returns:
34
+ List of region objects with 'code' and 'display_name' fields, or an
35
+ empty list when the list cannot be fetched.
36
+ Example: [
37
+ {"code": "us-west", "display_name": "US West"},
38
+ {"code": "eu-central", "display_name": "EU Central"}
39
+ ]
40
+ """
41
+ global _regions_cache
42
+
43
+ org = org or config.get("org")
44
+ if _regions_cache is None:
45
+ _regions_cache = {}
46
+
47
+ if org not in _regions_cache:
48
+ # API returns (data, error) tuple
49
+ data, error = await API.regions(org=org)
50
+ if error or not data:
51
+ return []
52
+ _regions_cache[org] = data
53
+
54
+ return _regions_cache[org]
55
+
56
+
57
+ async def region_error(region: str, org: str | None = None) -> str | None:
58
+ """
59
+ Check a region code against the regions of the organization in use.
60
+
61
+ Fetches the list once. Returns None when the region is valid, otherwise a
62
+ message for the user. When the list cannot be fetched, the message says so
63
+ rather than calling the region invalid. The API client prints its own error
64
+ for the failed call before this message is returned (in json mode that error
65
+ goes to stdout as the error object), so the caller shows both: why the call
66
+ failed, then that the command stopped.
67
+ """
68
+ org = org or config.get("org")
69
+ regions = await get_regions(org)
70
+ if not regions:
71
+ return (
72
+ f"Could not list the regions of organization '{org}', so region "
73
+ f"'{region}' could not be checked. Not continuing."
74
+ )
75
+ codes = [r["code"] for r in regions]
76
+ if region in codes:
77
+ return None
78
+ return f"Invalid region '{region}'. Valid regions are: {', '.join(codes)}"
79
+
80
+
81
+ def validate_architecture_for_region(
82
+ architecture: str, region: str, regions: list[dict]
83
+ ) -> str | None:
84
+ """Pre-validate an architecture choice against a region's capability
85
+ (PCC-1105). Returns an error message, or None when the choice is valid
86
+ or cannot be checked locally.
87
+
88
+ The regions payload carries ``supported_architectures`` only on newer
89
+ APIs — when absent, defer to the server's own validation (its 400 names
90
+ the supported list).
91
+ """
92
+ entry = next((r for r in regions if r.get("code") == region), None)
93
+ supported = (entry or {}).get("supported_architectures")
94
+ if not supported or architecture in supported:
95
+ return None
96
+ return (
97
+ f"Architecture '{architecture}' is not supported in region '{region}' "
98
+ f"(supported: {', '.join(supported)})"
99
+ )