pipecatcloud 1.4.0__tar.gz → 1.6.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 (110) hide show
  1. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/CHANGELOG.md +152 -0
  2. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/CLAUDE.md +1 -0
  3. {pipecatcloud-1.4.0/src/pipecatcloud.egg-info → pipecatcloud-1.6.0}/PKG-INFO +5 -1
  4. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/README.md +4 -0
  5. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/pyproject.toml +1 -1
  6. pipecatcloud-1.6.0/src/pipecatcloud/_utils/base_image.py +89 -0
  7. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/_utils/build_utils.py +11 -4
  8. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/_utils/deploy_utils.py +3 -2
  9. pipecatcloud-1.6.0/src/pipecatcloud/_utils/environment_utils.py +136 -0
  10. pipecatcloud-1.6.0/src/pipecatcloud/_utils/log_utils.py +42 -0
  11. pipecatcloud-1.6.0/src/pipecatcloud/_utils/regions.py +99 -0
  12. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/api.py +411 -16
  13. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/agent.py +200 -90
  14. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/agent_profiles.py +3 -3
  15. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/auth.py +4 -3
  16. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/deploy.py +29 -25
  17. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/docker.py +5 -0
  18. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/organizations.py +2 -1
  19. pipecatcloud-1.6.0/src/pipecatcloud/cli/commands/project.py +465 -0
  20. pipecatcloud-1.6.0/src/pipecatcloud/cli/commands/project_group.py +209 -0
  21. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/secrets.py +257 -51
  22. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/entry_point.py +25 -7
  23. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/config.py +10 -3
  24. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/constants.py +1 -1
  25. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0/src/pipecatcloud.egg-info}/PKG-INFO +5 -1
  26. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud.egg-info/SOURCES.txt +13 -1
  27. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud.egg-info/scm_file_list.json +12 -0
  28. pipecatcloud-1.6.0/src/pipecatcloud.egg-info/scm_version.json +8 -0
  29. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_agent_sessions.py +27 -3
  30. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_api_client.py +187 -1
  31. pipecatcloud-1.6.0/tests/test_base_image.py +98 -0
  32. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_deploy_poll.py +15 -2
  33. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_exit_codes.py +16 -0
  34. pipecatcloud-1.6.0/tests/test_log_redaction.py +472 -0
  35. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_organizations.py +28 -0
  36. pipecatcloud-1.6.0/tests/test_projects.py +929 -0
  37. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_regions.py +1 -57
  38. pipecatcloud-1.6.0/tests/test_secrets_bulk_write.py +455 -0
  39. pipecatcloud-1.6.0/tests/test_secrets_keys.py +244 -0
  40. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_secrets_reference.py +4 -8
  41. pipecatcloud-1.6.0/tests/test_secrets_retry.py +551 -0
  42. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_secrets_set_referenced.py +4 -0
  43. pipecatcloud-1.6.0/tests/test_unread_answers.py +153 -0
  44. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/uv.lock +147 -142
  45. pipecatcloud-1.4.0/src/pipecatcloud/_utils/regions.py +0 -89
  46. pipecatcloud-1.4.0/src/pipecatcloud.egg-info/scm_version.json +0 -8
  47. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.claude/settings.json +0 -0
  48. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md +0 -0
  49. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.github/workflows/format.yml +0 -0
  50. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.github/workflows/publish-pypi.yml +0 -0
  51. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.github/workflows/publish-test.yml +0 -0
  52. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.github/workflows/python-compatibility.yml +0 -0
  53. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.github/workflows/tests.yml +0 -0
  54. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.github/workflows/update-docs.yml +0 -0
  55. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.gitignore +0 -0
  56. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/.pre-commit-config.yaml +0 -0
  57. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/LICENSE +0 -0
  58. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/pipecat-cloud.png +0 -0
  59. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/pytest.ini +0 -0
  60. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/scripts/fix-ruff.sh +0 -0
  61. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/scripts/release-changelog.py +0 -0
  62. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/setup.cfg +0 -0
  63. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/__init__.py +0 -0
  64. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/__version__.py +0 -0
  65. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/_utils/__init__.py +0 -0
  66. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/_utils/async_utils.py +0 -0
  67. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  68. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/_utils/console_utils.py +0 -0
  69. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/_utils/github_utils.py +0 -0
  70. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/agent.py +0 -0
  71. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/__init__.py +0 -0
  72. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/api.py +0 -0
  73. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  74. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/build.py +0 -0
  75. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/github.py +0 -0
  76. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/regions.py +0 -0
  77. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/registry_keys.py +0 -0
  78. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/commands/spend_limit.py +0 -0
  79. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/cli/config.py +0 -0
  80. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/exception.py +0 -0
  81. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/py.typed +0 -0
  82. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/session.py +0 -0
  83. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
  84. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  85. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  86. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud.egg-info/requires.txt +0 -0
  87. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  88. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/__init__.py +0 -0
  89. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/conftest.py +0 -0
  90. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_agent_args.py +0 -0
  91. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_agent_profiles_and_resources.py +0 -0
  92. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_agent_status_output.py +0 -0
  93. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_agent_stop.py +0 -0
  94. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_auth.py +0 -0
  95. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_build_id_display.py +0 -0
  96. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_build_utils.py +0 -0
  97. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_config_file.py +0 -0
  98. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_deploy_architecture.py +0 -0
  99. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_deploy_image_pull_secret.py +0 -0
  100. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_deploy_referenced_secret_set.py +0 -0
  101. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_deployment_status.py +0 -0
  102. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_docker_commands.py +0 -0
  103. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_github_integration.py +0 -0
  104. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_json_output.py +0 -0
  105. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_krisp_viva.py +0 -0
  106. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_max_session_duration.py +0 -0
  107. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_output_modes.py +0 -0
  108. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_pat_auth.py +0 -0
  109. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_regions_lifecycle.py +0 -0
  110. {pipecatcloud-1.4.0 → pipecatcloud-1.6.0}/tests/test_spend_limit.py +0 -0
@@ -5,6 +5,158 @@ 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.6.0] - 2026-11-12
9
+
10
+ ### Added
11
+
12
+ - `project` is the command group for everything you deploy and run. A project
13
+ is an agent with environments; each environment is an agent of its own, and
14
+ `--env` addresses one through its project.
15
+
16
+ ```
17
+ pipecat cloud project list
18
+ pipecat cloud project status my-bot --env staging
19
+ pipecat cloud project logs my-bot --env pr-7
20
+ ```
21
+
22
+ Every command that took an agent name takes `--env` as well: `status`,
23
+ `logs`, `sessions`, `deployments`, `delete`, `start`, `stop`, `link`,
24
+ `unlink` and `deploy`. A positional agent name still works, so
25
+ `project status my-bot-pr-7` resolves the same preview.
26
+
27
+ - `project env` manages a project's environments.
28
+
29
+ - `project env add <project> <name> --agent <agent>` links an existing agent
30
+ as an environment. `project env remove` unlinks it, leaving it running as
31
+ a project of its own. `project env list` lists them: `production`, which
32
+ is the project's own agent, then the named environments and then the PR
33
+ previews.
34
+ - `--env production` is the project itself, which is the environment it
35
+ doubles as, so it means the same as naming the project with no `--env`.
36
+ `env promote` and `env remove` name an environment to act on, so they
37
+ refuse `production` and say why.
38
+ - `project env promote <project> <name>` deploys an environment's running
39
+ build, or the image it was deployed from, onto the project. Code only: the
40
+ project keeps its own secrets, scaling and profile. It confirms first, and
41
+ `--force` skips the prompt.
42
+
43
+ - `project previews enable <project> --secret-set <set>` turns PR previews on,
44
+ with `--bot-pull-requests` to include pull requests opened by bots.
45
+ `project previews disable` turns them off and deletes the previews it has.
46
+ `project previews status` shows the setting and the open previews.
47
+
48
+ - `project watch-paths set <project> <pattern>...` sets the glob patterns a
49
+ change has to match to deploy, `clear` removes them, and `show` prints them. They are a
50
+ project setting: its environments and previews follow the same list.
51
+
52
+ - `project list --all` includes environments and PR previews. Without it the
53
+ listing is projects only, as the dashboard shows them. Both carry columns for
54
+ a project's environment count and preview setting, and `--all` names each
55
+ environment's project.
56
+
57
+ - `deploy`, when it runs a cloud build, and `docker build-push` warn when the
58
+ Dockerfile builds `FROM dailyco/pipecat-base:latest`, or from
59
+ `dailyco/pipecat-base` with no tag. That tag names no Python, runs Python
60
+ 3.12, and stops building on 2027-04-05. The warning names the line, and the
61
+ tag that keeps the same Python: `dailyco/pipecat-base:latest-py3.12`.
62
+
63
+ ### Changed
64
+
65
+ - `agent` is **deprecated**. It is the same group under its old name: every
66
+ command goes on working and `agent list` goes on listing every agent, with a
67
+ notice on stderr pointing at `project`. Use `project`, and `--env` to address
68
+ an environment.
69
+ - `status` reports the project and environment of an agent that is one, its
70
+ watch paths, and, for a project, whether PR previews are on and whether bot
71
+ pull requests get them. For a PR preview it names the pull request.
72
+
73
+ ### Removed
74
+
75
+ - `agent scale` is gone. It was never implemented: it printed "Not implemented"
76
+ and exited. Set an agent's instance counts with `pipecat cloud deploy
77
+ --min-agents/--max-agents`, or the `[scaling]` section of `pcc-deploy.toml`.
78
+
79
+ ### Fixed
80
+
81
+ - A command no longer reports an operation as done when the CLI never managed
82
+ to make the request. A mistake inside the client, such as a call made with a
83
+ name the API method does not take, was handed back as an empty result, and
84
+ every command reads an empty error as success: a build promotion printed
85
+ "Promoted build ..." having sent no request at all. Such a failure now ends
86
+ the command with a non-zero exit code.
87
+ - A command that cannot reach the API, or cannot read its answer, now says so
88
+ and exits 1. A dropped connection, a host that does not resolve, a request
89
+ that timed out and an answer that stops part way through were all read as an
90
+ empty answer, which most commands reported as success. `secrets set`
91
+ already said so, and still says whether a write it got no answer for may have
92
+ been stored. `deploy` and `github connect` still treat it as a blip while
93
+ they are waiting, and keep polling.
94
+
95
+ ## [1.5.0] - 2026-11-05
96
+
97
+ ### Added
98
+
99
+ - `secrets keys --region <region>` shows your organization's secret keys in a
100
+ region: each key's fingerprint, state, created and retired times, and how
101
+ many secrets it holds, as text or with `--output json`. The fingerprint is
102
+ the one a security notice names and, in a self-hosted region, the one your
103
+ cluster shows for the key. It never shows key material. Against a server
104
+ that does not offer this yet, it says so and exits 1.
105
+
106
+ ### Changed
107
+
108
+ - `secrets set` with several values now writes them in one request, which the
109
+ server applies whole or not at all, so a large set takes one write in place
110
+ of one per key. Against a server that does not take this yet, including one
111
+ part way through an update, it writes one key at a time as before, and still
112
+ says how far it got if one fails.
113
+ - `secrets set`, `secrets unset` and `secrets image-pull-secret` now wait and
114
+ retry when the API answers a write with a 503 and a `Retry-After` of up to
115
+ 30 seconds, as a region can on its first secrets write. They retry up to 3 times, say they
116
+ are waiting, and fail as before if the 503 persists. Other errors, and a 503
117
+ without a usable `Retry-After`, are not retried. A `Retry-After` above 30
118
+ seconds is not waited out; the error says how long the region asked for.
119
+ A write that ends in a 503 may still have reached the region, unless the
120
+ error says nothing was stored. Running the same command again settles it.
121
+
122
+ ### Fixed
123
+
124
+ - `--region` is now checked against the regions of the organization given
125
+ with `--organization`, not only the organization in your config file. This
126
+ affects `secrets set`, `secrets list`, `secrets image-pull-secret`,
127
+ `secrets reference`, `agent list` and `deploy`. A region that belongs only
128
+ to the other organization, such as a self-hosted region, was refused as
129
+ invalid.
130
+ - `secrets set` no longer reports success when a write gets no answer from the
131
+ server, as when the connection drops. It says so and exits 1.
132
+
133
+ ### Security
134
+
135
+ - Logs no longer contain secret values or other request data. With debug
136
+ logging on (`PIPECAT_CLI_LOG_LEVEL=DEBUG` for the CLI, `PCC_LOG_LEVEL=DEBUG`
137
+ for the Python package), `secrets set` wrote the values it was given to
138
+ stderr, and every API request wrote its body and query there, including
139
+ image pull credentials and session data. Requests now log their method,
140
+ path and how much they carry. Tokens were not in these lines. If you ran
141
+ with debug logging on and the output was kept, for example in CI logs,
142
+ change the values it contains.
143
+ - An unexpected error in the CLI could print its local variables in the
144
+ traceback, including secret values and your token. Tracebacks now show code
145
+ only. To see them as before, set `TYPER_STANDARD_TRACEBACK` to an empty value.
146
+ - A failed build context upload now logs its status and S3 error code, not
147
+ S3's error document, which can repeat signed upload details. This line is
148
+ logged at the default level.
149
+ - `secrets set` no longer shows the first characters of each value when it asks
150
+ you to confirm. It shows a fixed mask, the same for any value. Without
151
+ `--skip` and without a terminal, as in CI, it printed those characters and
152
+ then failed; it now fails first. If such output was kept, change the values
153
+ it contains.
154
+ - The locked versions of `virtualenv`, `packaging`, `distlib`, `Pillow` and
155
+ `setuptools` are upgraded for security fixes. These are the versions in
156
+ `uv.lock`, used to develop and test this package. What it requires when you
157
+ install it has not changed, so upgrade your own environment if it holds an
158
+ older one.
159
+
8
160
  ## [1.4.0] - 2026-10-22
9
161
 
10
162
  ### 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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 1.4.0
3
+ Version: 1.6.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
@@ -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.
@@ -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,89 @@
1
+ #
2
+ # Copyright (c) 2026, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ """Warnings about the base image an agent's Dockerfile builds from.
8
+
9
+ ``dailyco/pipecat-base:latest`` names no Python, so it carries Python 3.12, and
10
+ it stops building on ``LATEST_RETIRES``. A Dockerfile that takes it is warned
11
+ before its build. Version tags without ``-py3.X``, such as ``0.3.0``, are
12
+ deprecated too, but each is fixed to one image and stays published, so a
13
+ Dockerfile pinned to one keeps building and is not warned.
14
+ """
15
+
16
+ import re
17
+ from pathlib import Path
18
+
19
+ from rich.markup import escape
20
+
21
+ from pipecatcloud._utils.console_utils import console
22
+
23
+ LATEST_RETIRES = "2027-04-05"
24
+ LATEST_PYTHON = "3.12"
25
+
26
+ # Docker Hub's own names for the repository, which Docker resolves alike.
27
+ _BASE_IMAGE = re.compile(
28
+ r"(?:(?:docker\.io|index\.docker\.io|registry-1\.docker\.io)/)?dailyco/pipecat-base"
29
+ r"(?::(?P<tag>[\w][\w.-]*))?(?P<digest>@\S+)?"
30
+ )
31
+ _FROM = re.compile(r"\s*FROM\s+(?:--\S+\s+)*(?P<image>\S+)", re.IGNORECASE)
32
+
33
+
34
+ def _instructions(text: str):
35
+ """Each instruction with the line it starts on, continuation lines joined.
36
+
37
+ Comment lines are dropped, inside a continuation too, as Docker drops them.
38
+ """
39
+ start, parts = None, []
40
+ for number, line in enumerate(text.splitlines(), start=1):
41
+ if line.lstrip().startswith("#"):
42
+ continue
43
+ if start is None:
44
+ start = number
45
+ if line.rstrip().endswith("\\"):
46
+ parts.append(line.rstrip()[:-1])
47
+ continue
48
+ parts.append(line)
49
+ yield start, " ".join(parts)
50
+ start, parts = None, []
51
+ if parts:
52
+ yield start, " ".join(parts)
53
+
54
+
55
+ def latest_base_images(text: str) -> list[tuple[int, str]]:
56
+ """The FROM instructions that take ``dailyco/pipecat-base:latest``.
57
+
58
+ Returns each one's line number and image, as written. A FROM with no tag
59
+ takes ``latest`` too. One pinned by digest is fixed, and one built from a
60
+ variable cannot be read here, so neither is returned.
61
+ """
62
+ found = []
63
+ for number, instruction in _instructions(text):
64
+ match = _FROM.match(instruction)
65
+ if not match:
66
+ continue
67
+ image = match["image"]
68
+ base = _BASE_IMAGE.fullmatch(image)
69
+ if base and not base["digest"] and base["tag"] in (None, "latest"):
70
+ found.append((number, image))
71
+ return found
72
+
73
+
74
+ def warn_latest_base_image(dockerfile: Path) -> None:
75
+ """Warn for each FROM in ``dockerfile`` that takes the base image's ``latest``.
76
+
77
+ An unreadable Dockerfile is left to the build to report.
78
+ """
79
+ try:
80
+ text = dockerfile.read_text(errors="replace")
81
+ except OSError:
82
+ return
83
+ for number, image in latest_base_images(text):
84
+ console.print(
85
+ f"[yellow]Warning: {escape(dockerfile.name)} line {number} builds FROM "
86
+ f"{escape(image)}, which stops building on {LATEST_RETIRES}. It names no "
87
+ f"Python, and runs Python {LATEST_PYTHON}: to keep it, use "
88
+ f"dailyco/pipecat-base:latest-py{LATEST_PYTHON}.[/yellow]"
89
+ )
@@ -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
 
@@ -762,7 +763,7 @@ def report_git_deploy_result(
762
763
  from pipecatcloud.cli import PIPECAT_CLI_NAME
763
764
 
764
765
  check_hint = (
765
- f"[dim]Check it with [bold]{PIPECAT_CLI_NAME} agent status {agent_name}[/bold].[/dim]"
766
+ f"[dim]Check it with [bold]{PIPECAT_CLI_NAME} project status {agent_name}[/bold].[/dim]"
766
767
  )
767
768
  label = "First deploy" if first_deploy else "Deploy"
768
769
 
@@ -0,0 +1,136 @@
1
+ #
2
+ # Copyright (c) 2026, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ """Shared helpers for projects and environments (PCC-1255).
8
+
9
+ An environment is an agent of its own, linked to the project it belongs to.
10
+ The project doubles as its own `production` environment, so a project is
11
+ simply an agent with no parent. These helpers read that shape out of the
12
+ service payloads the API already returns, and mirror the name rules it
13
+ enforces so a typo fails before the round trip.
14
+ """
15
+
16
+ import re
17
+
18
+ #: What the project itself is called among its environments.
19
+ PRODUCTION_ENVIRONMENT_NAME = "production"
20
+
21
+ #: PR previews are named `pr-<n>`, so the API reserves the prefix.
22
+ PREVIEW_ENVIRONMENT_PREFIX = "pr-"
23
+
24
+ #: The width of the environments.name column.
25
+ MAX_ENVIRONMENT_NAME_LENGTH = 50
26
+
27
+ # The agent-name rule, which environment names follow too.
28
+ _ENVIRONMENT_NAME_SHAPE = re.compile(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])$")
29
+
30
+ _PREVIEW_NAME = re.compile(r"^pr-(\d+)$")
31
+
32
+
33
+ def environment_name_problem(name: str) -> str | None:
34
+ """Why the API would refuse this environment name, or None when it's fine."""
35
+ if not name:
36
+ return "Enter a name for the environment."
37
+ if name == PRODUCTION_ENVIRONMENT_NAME:
38
+ return f"'{PRODUCTION_ENVIRONMENT_NAME}' is the project itself, so it can't be an environment name."
39
+ if name.startswith(PREVIEW_ENVIRONMENT_PREFIX):
40
+ return f"Names starting with '{PREVIEW_ENVIRONMENT_PREFIX}' are reserved for PR previews."
41
+ if len(name) > MAX_ENVIRONMENT_NAME_LENGTH:
42
+ return f"Use at most {MAX_ENVIRONMENT_NAME_LENGTH} characters."
43
+ if not _ENVIRONMENT_NAME_SHAPE.match(name):
44
+ return (
45
+ "Use two or more lowercase letters, digits and dashes, starting and "
46
+ "ending with a letter or digit."
47
+ )
48
+ return None
49
+
50
+
51
+ def is_preview_name(name: str | None) -> bool:
52
+ """Whether an environment's name is a PR preview's."""
53
+ return bool(name and _PREVIEW_NAME.match(name))
54
+
55
+
56
+ def environment_sort_key(name: str) -> tuple[int, int, str]:
57
+ """Named environments by name, then PR previews, newest pull request first."""
58
+ preview = _PREVIEW_NAME.match(name or "")
59
+ if preview:
60
+ return (1, -int(preview.group(1)), "")
61
+ return (0, 0, name or "")
62
+
63
+
64
+ def is_environment(service: dict) -> bool:
65
+ """Whether this agent is an environment of another agent."""
66
+ return bool(service.get("parentServiceId"))
67
+
68
+
69
+ def projects_in(services: list[dict]) -> list[dict]:
70
+ """The agents that are projects: the ones that are not environments."""
71
+ return [service for service in services if not is_environment(service)]
72
+
73
+
74
+ def environments_of(services: list[dict], project: dict) -> list[dict]:
75
+ """A project's environments, named ones first and then its PR previews."""
76
+ children = [
77
+ service for service in services if service.get("parentServiceId") == project.get("id")
78
+ ]
79
+ return sorted(children, key=lambda s: environment_sort_key(s.get("environmentName") or ""))
80
+
81
+
82
+ def environments_with_production(services: list[dict], project: dict) -> list[dict]:
83
+ """A project's environments, the project itself first as `production`.
84
+
85
+ The project doubles as that environment, so a listing that left it out
86
+ would contradict the model the rest of the group works to. Callers that
87
+ mean only the agents linked to the project use `environments_of`.
88
+ """
89
+ return [
90
+ {**project, "environmentName": PRODUCTION_ENVIRONMENT_NAME},
91
+ *environments_of(services, project),
92
+ ]
93
+
94
+
95
+ def promotion_source(environment: dict) -> dict | None:
96
+ """What promoting this environment deploys: its build, or the image it runs.
97
+
98
+ None when it has nothing deployed. Promotion moves code only, so this is
99
+ the whole payload the project is updated with.
100
+ """
101
+ deployment = environment.get("deployment") or {}
102
+ build_id = deployment.get("buildId")
103
+ if build_id:
104
+ return {"buildId": build_id}
105
+ image = ((deployment.get("manifest") or {}).get("spec") or {}).get("image")
106
+ if image:
107
+ return {"image": image}
108
+ return None
109
+
110
+
111
+ def describe_promotion_source(source: dict) -> str:
112
+ """A promotion source as a short phrase, for confirmations and messages."""
113
+ build_id = source.get("buildId")
114
+ return f"build {build_id}" if build_id else str(source.get("image"))
115
+
116
+
117
+ def previews_on(service: dict) -> bool:
118
+ """Whether this project has PR previews on.
119
+
120
+ Choosing the secret set previews mount is what turns them on, so the set
121
+ being present is the setting.
122
+ """
123
+ git = service.get("git") or {}
124
+ return bool(git.get("previewSecretSet"))
125
+
126
+
127
+ def project_label(service: dict, project_names: dict[str, str]) -> str:
128
+ """`project > environment` for an environment, or a dash for a project.
129
+
130
+ `project_names` maps agent id to name, which is how a parent's name is
131
+ resolved: the service payload carries only `parentServiceId`.
132
+ """
133
+ if not is_environment(service):
134
+ return "—"
135
+ parent = project_names.get(service.get("parentServiceId") or "", "?")
136
+ return f"{parent} > {service.get('environmentName') or '?'}"
@@ -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
+ )