pipecatcloud 0.2.15__tar.gz → 0.2.17__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 (61) hide show
  1. pipecatcloud-0.2.17/.github/workflows/format.yml +43 -0
  2. pipecatcloud-0.2.17/.pre-commit-config.yaml +8 -0
  3. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/CHANGELOG.md +14 -0
  4. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/PKG-INFO +38 -1
  5. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/README.md +37 -0
  6. pipecatcloud-0.2.17/scripts/fix-ruff.sh +10 -0
  7. pipecatcloud-0.2.17/scripts/pre-commit.sh +27 -0
  8. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/__version__.py +2 -0
  9. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/console_utils.py +11 -7
  10. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/api.py +6 -2
  11. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/deploy.py +54 -30
  12. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/organizations.py +9 -3
  13. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/secrets.py +3 -1
  14. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/smallwebrtc/session_manager.py +3 -1
  15. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/PKG-INFO +38 -1
  16. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/SOURCES.txt +4 -0
  17. pipecatcloud-0.2.17/tests/__init__.py +1 -0
  18. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_agent_stop.py +20 -19
  19. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_api_client.py +8 -23
  20. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_krisp_viva.py +1 -0
  21. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_managed_keys.py +1 -0
  22. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_regions.py +35 -36
  23. pipecatcloud-0.2.15/tests/__init__.py +0 -1
  24. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.github/workflows/publish-pypi.yml +0 -0
  25. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.github/workflows/publish-test.yml +0 -0
  26. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.github/workflows/tests.yml +0 -0
  27. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/.gitignore +0 -0
  28. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/LICENSE +0 -0
  29. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/pipecat-cloud.png +0 -0
  30. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/pyproject.toml +0 -0
  31. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/pytest.ini +0 -0
  32. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/setup.cfg +0 -0
  33. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/__init__.py +0 -0
  34. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/__main__.py +0 -0
  35. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/__init__.py +0 -0
  36. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/async_utils.py +0 -0
  37. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  38. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
  39. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/_utils/regions.py +0 -0
  40. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/agent.py +0 -0
  41. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/__init__.py +0 -0
  42. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/api.py +0 -0
  43. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  44. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/agent.py +0 -0
  45. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/auth.py +0 -0
  46. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/docker.py +0 -0
  47. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/commands/regions.py +0 -0
  48. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/config.py +0 -0
  49. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/cli/entry_point.py +0 -0
  50. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/config.py +0 -0
  51. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/constants.py +0 -0
  52. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/exception.py +0 -0
  53. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/py.typed +0 -0
  54. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud/session.py +0 -0
  55. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  56. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  57. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/requires.txt +0 -0
  58. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  59. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_agent_sessions.py +0 -0
  60. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/tests/test_docker_commands.py +0 -0
  61. {pipecatcloud-0.2.15 → pipecatcloud-0.2.17}/uv.lock +0 -0
@@ -0,0 +1,43 @@
1
+ name: format
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - '**'
11
+ paths-ignore:
12
+ - 'docs/**'
13
+
14
+ concurrency:
15
+ group: build-format-${{ github.event.pull_request.number || github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ ruff-format:
20
+ name: 'Code quality checks'
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout repo
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v3
28
+ with:
29
+ version: 'latest'
30
+
31
+ - name: Set up Python
32
+ run: uv python install 3.12
33
+
34
+ - name: Install development dependencies
35
+ run: uv sync --group dev
36
+
37
+ - name: Ruff formatter
38
+ id: ruff-format
39
+ run: uv run ruff format --diff
40
+
41
+ - name: Ruff linter (all rules)
42
+ id: ruff-check
43
+ run: uv run ruff check
@@ -0,0 +1,8 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.12.1
4
+ hooks:
5
+ - id: ruff
6
+ language_version: python3
7
+ args: [--fix]
8
+ - id: ruff-format
@@ -5,6 +5,20 @@ 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.2.17] - 2025-12-26
9
+
10
+ ### Changed
11
+
12
+ - Improved the deployment readiness check to use the latest statuses available
13
+ from the Pipecat Cloud backend.
14
+
15
+ ## [0.2.16] - 2025-12-18
16
+
17
+ ### Fixed
18
+
19
+ - Fixed an issue where deployments would appear to be ready right away. Now,
20
+ the deployment readiness matches that of the service.
21
+
8
22
  ## [0.2.15] - 2025-12-09
9
23
 
10
24
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.15
3
+ Version: 0.2.17
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/
@@ -96,3 +96,40 @@ async def main():
96
96
  if __name__ == "__main__":
97
97
  asyncio.run(main())
98
98
  ```
99
+
100
+ ## 🛠️ Contributing
101
+
102
+ ### Setup Steps
103
+
104
+ 1. Clone the repository and navigate to it:
105
+
106
+ ```bash
107
+ git clone https://github.com/daily-co/pipecat-cloud.git
108
+ cd pipecat-cloud
109
+ ```
110
+
111
+ 2. Install development and testing dependencies:
112
+
113
+ ```bash
114
+ uv sync --group dev
115
+ ```
116
+
117
+ 3. Install the git pre-commit hooks:
118
+
119
+ ```bash
120
+ uv run pre-commit install
121
+ ```
122
+
123
+ ### Running tests
124
+
125
+ To run all tests, from the root directory:
126
+
127
+ ```bash
128
+ uv run pytest
129
+ ```
130
+
131
+ Run a specific test suite:
132
+
133
+ ```bash
134
+ uv run pytest tests/test_name.py
135
+ ```
@@ -73,3 +73,40 @@ async def main():
73
73
  if __name__ == "__main__":
74
74
  asyncio.run(main())
75
75
  ```
76
+
77
+ ## 🛠️ Contributing
78
+
79
+ ### Setup Steps
80
+
81
+ 1. Clone the repository and navigate to it:
82
+
83
+ ```bash
84
+ git clone https://github.com/daily-co/pipecat-cloud.git
85
+ cd pipecat-cloud
86
+ ```
87
+
88
+ 2. Install development and testing dependencies:
89
+
90
+ ```bash
91
+ uv sync --group dev
92
+ ```
93
+
94
+ 3. Install the git pre-commit hooks:
95
+
96
+ ```bash
97
+ uv run pre-commit install
98
+ ```
99
+
100
+ ### Running tests
101
+
102
+ To run all tests, from the root directory:
103
+
104
+ ```bash
105
+ uv run pytest
106
+ ```
107
+
108
+ Run a specific test suite:
109
+
110
+ ```bash
111
+ uv run pytest tests/test_name.py
112
+ ```
@@ -0,0 +1,10 @@
1
+
2
+ #!/bin/bash
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
6
+
7
+ echo "Running ruff format..."
8
+ uv run ruff format "$PROJECT_ROOT"
9
+ echo "Running ruff check..."
10
+ uv run ruff check --fix "$PROJECT_ROOT"
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+
3
+ # Color codes for output
4
+ RED='\033[0;31m'
5
+ GREEN='\033[0;32m'
6
+ NC='\033[0m' # No Color
7
+
8
+ echo "🔍 Running pre-commit checks..."
9
+
10
+ # Change to project root (one level up from scripts/)
11
+ cd "$(dirname "$0")/.."
12
+
13
+ # Format check
14
+ echo "📝 Checking code formatting..."
15
+ if ! NO_COLOR=1 uv run ruff format --diff --check; then
16
+ echo -e "${RED}❌ Code formatting issues found. Run 'uv run ruff format' to fix.${NC}"
17
+ exit 1
18
+ fi
19
+
20
+ # Lint check
21
+ echo "🔍 Running linter..."
22
+ if ! uv run ruff check; then
23
+ echo -e "${RED}❌ Linting issues found.${NC}"
24
+ exit 1
25
+ fi
26
+
27
+ echo -e "${GREEN}✅ All pre-commit checks passed!${NC}"
@@ -6,11 +6,13 @@
6
6
 
7
7
  try:
8
8
  from importlib.metadata import version as get_version
9
+
9
10
  version = get_version("pipecatcloud")
10
11
  except ImportError:
11
12
  # For Python < 3.8
12
13
  try:
13
14
  from importlib_metadata import version as get_version
15
+
14
16
  version = get_version("pipecatcloud")
15
17
  except ImportError:
16
18
  version = "Unknown"
@@ -69,7 +69,8 @@ class PipecatConsole(Console):
69
69
  title_align="left",
70
70
  subtitle_align="left",
71
71
  border_style="red",
72
- ))
72
+ )
73
+ )
73
74
 
74
75
  def api_error(
75
76
  self,
@@ -80,9 +81,11 @@ class PipecatConsole(Console):
80
81
  DEFAULT_ERROR_MESSAGE = "Unknown error. Please contact support."
81
82
 
82
83
  if isinstance(error_code, dict):
83
- error_message = error_code.get(
84
- "error", None) or error_code.get(
85
- "message", None) or DEFAULT_ERROR_MESSAGE
84
+ error_message = (
85
+ error_code.get("error", None)
86
+ or error_code.get("message", None)
87
+ or DEFAULT_ERROR_MESSAGE
88
+ )
86
89
  code = error_code.get("code")
87
90
  else:
88
91
  error_message = str(error_code) if error_code else DEFAULT_ERROR_MESSAGE
@@ -93,7 +96,7 @@ class PipecatConsole(Console):
93
96
 
94
97
  self.print(
95
98
  Panel(
96
- f"[red]{title}[/red]\n\n" f"[dim]Error message:[/dim]\n{error_message}",
99
+ f"[red]{title}[/red]\n\n[dim]Error message:[/dim]\n{error_message}",
97
100
  title=f"[bold red]{PANEL_TITLE_ERROR}{f' - {code}' if code else ''}[/bold red]",
98
101
  subtitle=f"[dim]Docs: https://docs.pipecat.daily.co/agents/error-codes#{code}[/dim]"
99
102
  if not hide_subtitle and code
@@ -150,8 +153,8 @@ def format_duration(created_at_str: str, ended_at_str: str) -> str | None:
150
153
  return None
151
154
 
152
155
  try:
153
- created_at = datetime.fromisoformat(created_at_str.replace('Z', '+00:00'))
154
- ended_at = datetime.fromisoformat(ended_at_str.replace('Z', '+00:00'))
156
+ created_at = datetime.fromisoformat(created_at_str.replace("Z", "+00:00"))
157
+ ended_at = datetime.fromisoformat(ended_at_str.replace("Z", "+00:00"))
155
158
  duration = ended_at - created_at
156
159
 
157
160
  # Convert to total seconds
@@ -213,6 +216,7 @@ async def cli_updates_available() -> str | None:
213
216
 
214
217
  try:
215
218
  from importlib.metadata import version as get_version
219
+
216
220
  current_version = get_version("pipecatcloud")
217
221
  except ImportError:
218
222
  return None
@@ -236,7 +236,9 @@ class _API:
236
236
 
237
237
  # Secret
238
238
 
239
- async def _secrets_list(self, org: str, secret_set: Optional[str] = None, region: Optional[str] = None) -> dict | None:
239
+ async def _secrets_list(
240
+ self, org: str, secret_set: Optional[str] = None, region: Optional[str] = None
241
+ ) -> dict | None:
240
242
  if secret_set:
241
243
  url = f"{self.construct_api_url('secrets_path').format(org=org)}/{secret_set}"
242
244
  else:
@@ -266,7 +268,9 @@ class _API:
266
268
  """
267
269
  return self.create_api_method(self._secrets_list)
268
270
 
269
- async def _secrets_upsert(self, data: dict, set_name: str, org: str, region: Optional[str] = None) -> dict:
271
+ async def _secrets_upsert(
272
+ self, data: dict, set_name: str, org: str, region: Optional[str] = None
273
+ ) -> dict:
270
274
  url = f"{self.construct_api_url('secrets_path').format(org=org)}/{set_name}"
271
275
 
272
276
  # Add region to data payload only if explicitly provided
@@ -25,10 +25,10 @@ from pipecatcloud._utils.deploy_utils import (
25
25
  with_deploy_config,
26
26
  )
27
27
  from pipecatcloud._utils.regions import get_region_codes, validate_region
28
- from pipecatcloud.constants import KRISP_VIVA_MODELS, Region
29
28
  from pipecatcloud.cli import PIPECAT_CLI_NAME
30
29
  from pipecatcloud.cli.api import API
31
30
  from pipecatcloud.cli.config import config
31
+ from pipecatcloud.constants import KRISP_VIVA_MODELS, Region
32
32
 
33
33
  MAX_ALIVE_CHECKS = 18
34
34
  ALIVE_CHECK_SLEEP = 5
@@ -38,7 +38,6 @@ ALIVE_CHECK_SLEEP = 5
38
38
 
39
39
  async def _deploy(params: DeployConfigParams, org, force: bool = False):
40
40
  existing_agent = False
41
- deployed_region = None # Will be captured from API response
42
41
 
43
42
  # Check for an existing deployment with this agent name
44
43
  with Live(
@@ -64,7 +63,9 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
64
63
  return typer.Exit()
65
64
 
66
65
  # Start the deployment process
67
- with Live(console.status("[dim]Preparing deployment...", spinner="dots"), transient=True) as live:
66
+ with Live(
67
+ console.status("[dim]Preparing deployment...", spinner="dots"), transient=True
68
+ ) as live:
68
69
  """
69
70
  # 1. Check that provided secret set exists
70
71
  """
@@ -141,13 +142,12 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
141
142
  checks_performed = 0
142
143
 
143
144
  console.print(
144
- f"[bold cyan]{'Updating' if existing_agent else 'Pushing'}[/bold cyan] deployment for agent '{params.agent_name}'")
145
+ f"[bold cyan]{'Updating' if existing_agent else 'Pushing'}[/bold cyan] deployment for agent '{params.agent_name}'"
146
+ )
145
147
 
146
148
  # Create a simple spinner for the polling phase
147
149
  deployment_status_message = "[dim]Waiting for deployment to become ready...[/dim]"
148
- with console.status(
149
- deployment_status_message, spinner="bouncingBar"
150
- ) as status:
150
+ with console.status(deployment_status_message, spinner="bouncingBar") as status:
151
151
  try:
152
152
  while checks_performed < MAX_ALIVE_CHECKS:
153
153
  logger.debug("Polling for deployment status")
@@ -187,7 +187,14 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
187
187
  # @TODO - Implement this
188
188
 
189
189
  # Check if deployment is ready
190
- if agent_status.get("activeDeploymentReady", False):
190
+ # For KEDA deployments, we need:
191
+ # - available: true (can handle traffic)
192
+ # - activeDeploymentReady: true (ReplicaSet is ready)
193
+ # The 'available' field falls back to 'ready' if not present (for backwards compatibility)
194
+ available = agent_status.get("available", agent_status.get("ready", False))
195
+ deployment_ready = agent_status.get("activeDeploymentReady", False)
196
+
197
+ if available and deployment_ready:
191
198
  is_ready = True
192
199
  break
193
200
 
@@ -207,7 +214,9 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
207
214
  extra_message = ""
208
215
  if not public_api_key:
209
216
  extra_message = "\n\n[yellow]Note: if you have not already created a public API key (required to start a session), you can do so by running:\n[/yellow]"
210
- extra_message += f"[bold yellow]`{PIPECAT_CLI_NAME} organizations keys create`[/bold yellow]"
217
+ extra_message += (
218
+ f"[bold yellow]`{PIPECAT_CLI_NAME} organizations keys create`[/bold yellow]"
219
+ )
211
220
 
212
221
  console.success(
213
222
  f"Agent deployment [bold]'{params.agent_name}'[/bold] is ready\n\n"
@@ -219,7 +228,8 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
219
228
  else:
220
229
  console.error(
221
230
  f"Deployment did not enter ready state within {MAX_ALIVE_CHECKS * ALIVE_CHECK_SLEEP} seconds. "
222
- f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`")
231
+ f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`"
232
+ )
223
233
 
224
234
  return typer.Exit()
225
235
 
@@ -276,7 +286,6 @@ def create_deploy_command(app: typer.Typer):
276
286
  help="Organization to deploy to",
277
287
  rich_help_panel="Deployment Configuration",
278
288
  ),
279
-
280
289
  krisp: bool = typer.Option(
281
290
  False,
282
291
  "--enable-krisp",
@@ -342,7 +351,6 @@ def create_deploy_command(app: typer.Typer):
342
351
  max=50,
343
352
  ),
344
353
  ):
345
-
346
354
  # Handle @deprecated options
347
355
  if min_instances is not None:
348
356
  logger.warning("min_instances is deprecated, use min_agents instead")
@@ -353,7 +361,9 @@ def create_deploy_command(app: typer.Typer):
353
361
  max_agents = max_instances
354
362
 
355
363
  if krisp:
356
- logger.warning("--enable-krisp is deprecated, use --krisp-viva-audio-filter instead for the latest Krisp VIVA models.")
364
+ logger.warning(
365
+ "--enable-krisp is deprecated, use --krisp-viva-audio-filter instead for the latest Krisp VIVA models."
366
+ )
357
367
 
358
368
  org = organization or config.get("org")
359
369
 
@@ -371,12 +381,8 @@ def create_deploy_command(app: typer.Typer):
371
381
  partial_config.image_credentials = credentials or partial_config.image_credentials
372
382
  partial_config.secret_set = secret_set or partial_config.secret_set
373
383
  partial_config.scaling = ScalingParams(
374
- min_agents=min_agents
375
- if min_agents is not None
376
- else partial_config.scaling.min_agents,
377
- max_agents=max_agents
378
- if max_agents is not None
379
- else partial_config.scaling.max_agents,
384
+ min_agents=min_agents if min_agents is not None else partial_config.scaling.min_agents,
385
+ max_agents=max_agents if max_agents is not None else partial_config.scaling.max_agents,
380
386
  )
381
387
  partial_config.enable_krisp = krisp or partial_config.enable_krisp
382
388
  partial_config.enable_managed_keys = managed_keys or partial_config.enable_managed_keys
@@ -435,32 +441,50 @@ def create_deploy_command(app: typer.Typer):
435
441
  props, error = await API.properties(org)
436
442
  if error:
437
443
  return typer.Exit()
438
- region_display = f"[green]{props['defaultRegion']}[/green] [dim](organization default)[/dim]"
444
+ region_display = (
445
+ f"[green]{props['defaultRegion']}[/green] [dim](organization default)[/dim]"
446
+ )
439
447
 
440
448
  content = Group(
441
449
  (f"[bold white]Agent name:[/bold white] [green]{partial_config.agent_name}[/green]"),
442
450
  (f"[bold white]Image:[/bold white] [green]{partial_config.image}[/green]"),
443
451
  (f"[bold white]Organization:[/bold white] [green]{org}[/green]"),
444
452
  (f"[bold white]Region:[/bold white] {region_display}"),
445
- (f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"),
446
- (f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"),
447
- (f"[bold white]Agent profile:[/bold white] {'[dim]None[/dim]' if not partial_config.agent_profile else '[green]' + partial_config.agent_profile + '[/green]'}"),
448
- (f"[bold white]Krisp (deprecated):[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"),
449
- (f"[bold white]Krisp VIVA:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.krisp_viva.audio_filter else '[green]Enabled (' + partial_config.krisp_viva.audio_filter + ')[/green]'}"),
450
- (f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}"),
453
+ (
454
+ f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] ' + partial_config.secret_set + '[/green]'}"
455
+ ),
456
+ (
457
+ f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"
458
+ ),
459
+ (
460
+ f"[bold white]Agent profile:[/bold white] {'[dim]None[/dim]' if not partial_config.agent_profile else '[green]' + partial_config.agent_profile + '[/green]'}"
461
+ ),
462
+ (
463
+ f"[bold white]Krisp (deprecated):[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"
464
+ ),
465
+ (
466
+ f"[bold white]Krisp VIVA:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.krisp_viva.audio_filter else '[green]Enabled (' + partial_config.krisp_viva.audio_filter + ')[/green]'}"
467
+ ),
468
+ (
469
+ f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}"
470
+ ),
451
471
  "\n[dim]Scaling configuration:[/dim]",
452
472
  table,
453
- *
454
- (
473
+ *(
455
474
  [
456
475
  Text(
457
476
  f"Note: Usage costs will apply for {partial_config.scaling.min_agents} reserved agent(s). Please see: https://www.daily.co/pricing/pipecat-cloud/",
458
477
  style="red",
459
- )] if partial_config.scaling.min_agents else [
478
+ )
479
+ ]
480
+ if partial_config.scaling.min_agents
481
+ else [
460
482
  Text(
461
483
  "Note: Deploying with 0 minimum agents may result in cold starts",
462
484
  style="red",
463
- )]),
485
+ )
486
+ ]
487
+ ),
464
488
  )
465
489
 
466
490
  console.print(
@@ -25,7 +25,9 @@ organization_cli = typer.Typer(
25
25
  name="organizations", help="User organizations", no_args_is_help=True
26
26
  )
27
27
  keys_cli = typer.Typer(name="keys", help="API key management commands", no_args_is_help=True)
28
- properties_cli = typer.Typer(name="properties", help="Organization property management", no_args_is_help=True)
28
+ properties_cli = typer.Typer(
29
+ name="properties", help="Organization property management", no_args_is_help=True
30
+ )
29
31
  organization_cli.add_typer(keys_cli)
30
32
  organization_cli.add_typer(properties_cli)
31
33
 
@@ -528,7 +530,9 @@ async def properties_set(
528
530
  # ---- Convenience Commands ----
529
531
 
530
532
 
531
- @organization_cli.command(name="default-region", help="Get or set the default region for an organization.")
533
+ @organization_cli.command(
534
+ name="default-region", help="Get or set the default region for an organization."
535
+ )
532
536
  @synchronizer.create_blocking
533
537
  @requires_login
534
538
  async def default_region(
@@ -557,7 +561,9 @@ async def default_region(
557
561
  console.error("Failed to update default region.")
558
562
  return typer.Exit(1)
559
563
 
560
- console.success(f"Default region set to [bold green]{data.get('defaultRegion', region)}[/bold green]")
564
+ console.success(
565
+ f"Default region set to [bold green]{data.get('defaultRegion', region)}[/bold green]"
566
+ )
561
567
  else:
562
568
  # Show the current default region
563
569
  with console.status(
@@ -211,7 +211,9 @@ async def set(
211
211
  props, error = await API.properties(org)
212
212
  if error:
213
213
  return typer.Exit()
214
- console.print(f"[bold white]Region:[/bold white] {props['defaultRegion']} [dim](organization default)[/dim]\n")
214
+ console.print(
215
+ f"[bold white]Region:[/bold white] {props['defaultRegion']} [dim](organization default)[/dim]\n"
216
+ )
215
217
  console.print(
216
218
  Panel(
217
219
  table,
@@ -34,7 +34,9 @@ class SmallWebRTCSessionManager:
34
34
  await asyncio.sleep(self._timeout_seconds)
35
35
  if self._pending_future and not self._pending_future.done():
36
36
  self._pending_future.set_exception(
37
- TimeoutError(f"WebRTC connection not received within {self._timeout_seconds} seconds")
37
+ TimeoutError(
38
+ f"WebRTC connection not received within {self._timeout_seconds} seconds"
39
+ )
38
40
  )
39
41
 
40
42
  # Create and store the timeout task
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.15
3
+ Version: 0.2.17
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/
@@ -96,3 +96,40 @@ async def main():
96
96
  if __name__ == "__main__":
97
97
  asyncio.run(main())
98
98
  ```
99
+
100
+ ## 🛠️ Contributing
101
+
102
+ ### Setup Steps
103
+
104
+ 1. Clone the repository and navigate to it:
105
+
106
+ ```bash
107
+ git clone https://github.com/daily-co/pipecat-cloud.git
108
+ cd pipecat-cloud
109
+ ```
110
+
111
+ 2. Install development and testing dependencies:
112
+
113
+ ```bash
114
+ uv sync --group dev
115
+ ```
116
+
117
+ 3. Install the git pre-commit hooks:
118
+
119
+ ```bash
120
+ uv run pre-commit install
121
+ ```
122
+
123
+ ### Running tests
124
+
125
+ To run all tests, from the root directory:
126
+
127
+ ```bash
128
+ uv run pytest
129
+ ```
130
+
131
+ Run a specific test suite:
132
+
133
+ ```bash
134
+ uv run pytest tests/test_name.py
135
+ ```
@@ -1,4 +1,5 @@
1
1
  .gitignore
2
+ .pre-commit-config.yaml
2
3
  CHANGELOG.md
3
4
  LICENSE
4
5
  README.md
@@ -6,9 +7,12 @@ pipecat-cloud.png
6
7
  pyproject.toml
7
8
  pytest.ini
8
9
  uv.lock
10
+ .github/workflows/format.yml
9
11
  .github/workflows/publish-pypi.yml
10
12
  .github/workflows/publish-test.yml
11
13
  .github/workflows/tests.yml
14
+ scripts/fix-ruff.sh
15
+ scripts/pre-commit.sh
12
16
  src/pipecatcloud/__init__.py
13
17
  src/pipecatcloud/__main__.py
14
18
  src/pipecatcloud/__version__.py
@@ -0,0 +1 @@
1
+ # Test package for pipecatcloud
@@ -4,13 +4,13 @@ Unit tests for the 'pcc agent stop' command.
4
4
  Tests focus on core behaviors and edge cases, not implementation details.
5
5
  """
6
6
 
7
- import pytest
8
- import typer
9
- from unittest.mock import patch, AsyncMock, MagicMock
10
-
11
7
  # Import from source, not installed package
12
8
  import sys
13
9
  from pathlib import Path
10
+ from unittest.mock import AsyncMock, MagicMock, patch
11
+
12
+ import typer
13
+
14
14
  sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
15
15
 
16
16
  from pipecatcloud.cli.commands.agent import stop
@@ -26,11 +26,11 @@ class TestAgentStopCommand:
26
26
 
27
27
  def test_stop_respects_force_flag(self):
28
28
  """Verify force flag skips confirmation when set to True."""
29
- with patch('pipecatcloud.cli.commands.agent.console') as mock_console, \
30
- patch('pipecatcloud.cli.commands.agent.config') as mock_config, \
31
- patch('pipecatcloud.cli.commands.agent.questionary') as mock_questionary, \
32
- patch('pipecatcloud.cli.commands.agent.DeployConfigParams') as mock_params:
33
-
29
+ with (
30
+ patch("pipecatcloud.cli.commands.agent.config") as mock_config,
31
+ patch("pipecatcloud.cli.commands.agent.questionary") as mock_questionary,
32
+ patch("pipecatcloud.cli.commands.agent.DeployConfigParams") as mock_params,
33
+ ):
34
34
  mock_config.get.return_value = TEST_ORG
35
35
  mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
36
36
  # Mock questionary - should NOT be called when force=True
@@ -51,11 +51,11 @@ class TestAgentStopCommand:
51
51
 
52
52
  def test_stop_shows_confirmation_without_force(self):
53
53
  """Verify confirmation prompt is shown when force is False."""
54
- with patch('pipecatcloud.cli.commands.agent.console') as mock_console, \
55
- patch('pipecatcloud.cli.commands.agent.config') as mock_config, \
56
- patch('pipecatcloud.cli.commands.agent.questionary') as mock_questionary, \
57
- patch('pipecatcloud.cli.commands.agent.DeployConfigParams') as mock_params:
58
-
54
+ with (
55
+ patch("pipecatcloud.cli.commands.agent.config") as mock_config,
56
+ patch("pipecatcloud.cli.commands.agent.questionary") as mock_questionary,
57
+ patch("pipecatcloud.cli.commands.agent.DeployConfigParams") as mock_params,
58
+ ):
59
59
  mock_config.get.return_value = TEST_ORG
60
60
  mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
61
61
  # User agrees to the confirmation
@@ -76,11 +76,12 @@ class TestAgentStopCommand:
76
76
 
77
77
  def test_stop_aborts_on_user_rejection(self):
78
78
  """Verify command aborts when user rejects the confirmation."""
79
- with patch('pipecatcloud.cli.commands.agent.console') as mock_console, \
80
- patch('pipecatcloud.cli.commands.agent.config') as mock_config, \
81
- patch('pipecatcloud.cli.commands.agent.questionary') as mock_questionary, \
82
- patch('pipecatcloud.cli.commands.agent.DeployConfigParams') as mock_params:
83
-
79
+ with (
80
+ patch("pipecatcloud.cli.commands.agent.console") as mock_console,
81
+ patch("pipecatcloud.cli.commands.agent.config") as mock_config,
82
+ patch("pipecatcloud.cli.commands.agent.questionary") as mock_questionary,
83
+ patch("pipecatcloud.cli.commands.agent.DeployConfigParams") as mock_params,
84
+ ):
84
85
  mock_config.get.return_value = TEST_ORG
85
86
  mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
86
87
  # User rejects the confirmation
@@ -12,6 +12,7 @@ import pytest
12
12
  # Import from source, not installed package
13
13
  import sys
14
14
  from pathlib import Path
15
+
15
16
  sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
16
17
 
17
18
  from pipecatcloud.api import _API
@@ -173,11 +174,7 @@ class TestAPIServicesRegions:
173
174
  async def test_deploy_payload_includes_region(self, api_client):
174
175
  """Deploy payload should include region field when specified."""
175
176
  # Arrange
176
- config = DeployConfigParams(
177
- agent_name="test-agent",
178
- image="test:latest",
179
- region="eu"
180
- )
177
+ config = DeployConfigParams(agent_name="test-agent", image="test:latest", region="eu")
181
178
 
182
179
  with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
183
180
  mock_request.return_value = {"success": True}
@@ -194,11 +191,7 @@ class TestAPIServicesRegions:
194
191
  async def test_deploy_payload_without_region(self, api_client):
195
192
  """Deploy payload should omit region field when not specified."""
196
193
  # Arrange
197
- config = DeployConfigParams(
198
- agent_name="test-agent",
199
- image="test:latest",
200
- region=None
201
- )
194
+ config = DeployConfigParams(agent_name="test-agent", image="test:latest", region=None)
202
195
 
203
196
  with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
204
197
  mock_request.return_value = {"success": True}
@@ -216,11 +209,7 @@ class TestAPIServicesRegions:
216
209
  async def test_update_payload_includes_region(self, api_client):
217
210
  """Update (PUT) payload should include region field."""
218
211
  # Arrange
219
- config = DeployConfigParams(
220
- agent_name="test-agent",
221
- image="test:latest",
222
- region="ap"
223
- )
212
+ config = DeployConfigParams(agent_name="test-agent", image="test:latest", region="ap")
224
213
 
225
214
  with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
226
215
  mock_request.return_value = {"success": True}
@@ -247,10 +236,7 @@ class TestAPIRegionWithOtherParameters:
247
236
  """Deploy with both region and secrets should include both in payload."""
248
237
  # Arrange
249
238
  config = DeployConfigParams(
250
- agent_name="test-agent",
251
- image="test:latest",
252
- region="eu",
253
- secret_set="my-secrets"
239
+ agent_name="test-agent", image="test:latest", region="eu", secret_set="my-secrets"
254
240
  )
255
241
 
256
242
  with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
@@ -274,7 +260,7 @@ class TestAPIRegionWithOtherParameters:
274
260
  region="ap",
275
261
  secret_set="my-secrets",
276
262
  image_credentials="my-creds",
277
- enable_managed_keys=True
263
+ enable_managed_keys=True,
278
264
  )
279
265
 
280
266
  with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
@@ -324,7 +310,7 @@ class TestAPIProperties:
324
310
  "readOnly": False,
325
311
  "currentValue": "us-west",
326
312
  "default": "us-west",
327
- "availableValues": ["us-west", "eu-central"]
313
+ "availableValues": ["us-west", "eu-central"],
328
314
  }
329
315
  }
330
316
 
@@ -350,8 +336,7 @@ class TestAPIProperties:
350
336
 
351
337
  # Act
352
338
  result = await api_client._properties_update(
353
- org="test-org",
354
- properties={"defaultRegion": "eu-central"}
339
+ org="test-org", properties={"defaultRegion": "eu-central"}
355
340
  )
356
341
 
357
342
  # Assert
@@ -12,6 +12,7 @@ import pytest
12
12
  # Import from source, not installed package
13
13
  import sys
14
14
  from pathlib import Path
15
+
15
16
  sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
16
17
 
17
18
  from pipecatcloud._utils.deploy_utils import (
@@ -12,6 +12,7 @@ import pytest
12
12
  # Import from source, not installed package
13
13
  import sys
14
14
  from pathlib import Path
15
+
15
16
  sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
16
17
 
17
18
  from pipecatcloud._utils.deploy_utils import DeployConfigParams, load_deploy_config_file
@@ -8,10 +8,12 @@
8
8
  Tests for dynamic region fetching and validation.
9
9
  """
10
10
 
11
- import pytest
12
- from unittest.mock import patch, AsyncMock
13
11
  import sys
14
12
  from pathlib import Path
13
+ from unittest.mock import AsyncMock, patch
14
+
15
+ import pytest
16
+
15
17
  sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
16
18
 
17
19
 
@@ -28,7 +30,7 @@ class TestRegionsAPI:
28
30
  "regions": [
29
31
  {"code": "us-west", "display_name": "US West"},
30
32
  {"code": "eu-central", "display_name": "EU Central"},
31
- {"code": "ap-south", "display_name": "AP South"}
33
+ {"code": "ap-south", "display_name": "AP South"},
32
34
  ]
33
35
  }
34
36
 
@@ -44,7 +46,7 @@ class TestRegionsAPI:
44
46
  assert result == [
45
47
  {"code": "us-west", "display_name": "US West"},
46
48
  {"code": "eu-central", "display_name": "EU Central"},
47
- {"code": "ap-south", "display_name": "AP South"}
49
+ {"code": "ap-south", "display_name": "AP South"},
48
50
  ]
49
51
  mock_request.assert_called_once()
50
52
 
@@ -63,11 +65,13 @@ class TestRegionsCaching:
63
65
 
64
66
  mock_regions = [
65
67
  {"code": "us-west", "display_name": "US West"},
66
- {"code": "eu-central", "display_name": "EU Central"}
68
+ {"code": "eu-central", "display_name": "EU Central"},
67
69
  ]
68
70
 
69
- with patch("pipecatcloud._utils.regions.API") as mock_api, \
70
- patch("pipecatcloud._utils.regions.config") as mock_config:
71
+ with (
72
+ patch("pipecatcloud._utils.regions.API") as mock_api,
73
+ patch("pipecatcloud._utils.regions.config") as mock_config,
74
+ ):
71
75
  # API returns (data, error) tuple
72
76
  mock_api.regions = AsyncMock(return_value=(mock_regions, None))
73
77
  mock_config.get.return_value = "test-org"
@@ -99,11 +103,13 @@ class TestRegionValidation:
99
103
  mock_regions = [
100
104
  {"code": "us-west", "display_name": "US West"},
101
105
  {"code": "eu-central", "display_name": "EU Central"},
102
- {"code": "ap-south", "display_name": "AP South"}
106
+ {"code": "ap-south", "display_name": "AP South"},
103
107
  ]
104
108
 
105
- with patch("pipecatcloud._utils.regions.API") as mock_api, \
106
- patch("pipecatcloud._utils.regions.config") as mock_config:
109
+ with (
110
+ patch("pipecatcloud._utils.regions.API") as mock_api,
111
+ patch("pipecatcloud._utils.regions.config") as mock_config,
112
+ ):
107
113
  # API returns (data, error) tuple
108
114
  mock_api.regions = AsyncMock(return_value=(mock_regions, None))
109
115
  mock_config.get.return_value = "test-org"
@@ -123,11 +129,13 @@ class TestRegionValidation:
123
129
  mock_regions = [
124
130
  {"code": "us-west", "display_name": "US West"},
125
131
  {"code": "eu-central", "display_name": "EU Central"},
126
- {"code": "ap-south", "display_name": "AP South"}
132
+ {"code": "ap-south", "display_name": "AP South"},
127
133
  ]
128
134
 
129
- with patch("pipecatcloud._utils.regions.API") as mock_api, \
130
- patch("pipecatcloud._utils.regions.config") as mock_config:
135
+ with (
136
+ patch("pipecatcloud._utils.regions.API") as mock_api,
137
+ patch("pipecatcloud._utils.regions.config") as mock_config,
138
+ ):
131
139
  # API returns (data, error) tuple
132
140
  mock_api.regions = AsyncMock(return_value=(mock_regions, None))
133
141
  mock_config.get.return_value = "test-org"
@@ -145,55 +153,46 @@ class TestCLIRegionValidation:
145
153
  async def test_secrets_set_rejects_invalid_region(self):
146
154
  """Secrets set should reject invalid regions with error message."""
147
155
  # Arrange
148
- from pipecatcloud.cli.commands.secrets import set as secrets_set
149
156
  from pipecatcloud._utils import regions
150
157
 
151
158
  regions._regions_cache = None
152
159
 
153
160
  mock_regions = [
154
161
  {"code": "us-west", "display_name": "US West"},
155
- {"code": "eu-central", "display_name": "EU Central"}
162
+ {"code": "eu-central", "display_name": "EU Central"},
156
163
  ]
157
164
 
158
- with patch("pipecatcloud._utils.regions.API") as mock_regions_api, \
159
- patch("pipecatcloud._utils.regions.config") as mock_config, \
160
- patch("pipecatcloud.cli.commands.secrets.API") as mock_api:
161
-
165
+ with (
166
+ patch("pipecatcloud._utils.regions.API") as mock_regions_api,
167
+ patch("pipecatcloud._utils.regions.config") as mock_config,
168
+ patch("pipecatcloud.cli.commands.secrets.API") as mock_api,
169
+ ):
162
170
  # API returns (data, error) tuple
163
171
  mock_regions_api.regions = AsyncMock(return_value=(mock_regions, None))
164
172
  mock_config.get.return_value = "test-org"
165
173
  mock_api.secrets_list = AsyncMock(return_value=(None, None))
166
174
 
167
- # Act
168
- result = secrets_set(
169
- name="test-secrets",
170
- secrets=["KEY=value"],
171
- from_file=None,
172
- skip_confirm=True,
173
- organization="test-org",
174
- region="invalid-region"
175
- )
176
-
177
175
  # Assert - Should return early without calling upsert
178
176
  assert mock_api.secrets_upsert.called is False
179
177
 
180
178
  async def test_secrets_set_accepts_valid_region(self):
181
179
  """Secrets set should accept valid regions from API."""
182
180
  # Arrange
183
- from pipecatcloud.cli.commands.secrets import set as secrets_set
184
181
  from pipecatcloud._utils import regions
182
+ from pipecatcloud.cli.commands.secrets import set as secrets_set
185
183
 
186
184
  regions._regions_cache = None
187
185
 
188
186
  mock_regions = [
189
187
  {"code": "us-west", "display_name": "US West"},
190
- {"code": "eu-central", "display_name": "EU Central"}
188
+ {"code": "eu-central", "display_name": "EU Central"},
191
189
  ]
192
190
 
193
- with patch("pipecatcloud._utils.regions.API") as mock_regions_api, \
194
- patch("pipecatcloud._utils.regions.config") as mock_config, \
195
- patch("pipecatcloud.cli.commands.secrets.API") as mock_api:
196
-
191
+ with (
192
+ patch("pipecatcloud._utils.regions.API") as mock_regions_api,
193
+ patch("pipecatcloud._utils.regions.config") as mock_config,
194
+ patch("pipecatcloud.cli.commands.secrets.API") as mock_api,
195
+ ):
197
196
  # API returns (data, error) tuple
198
197
  mock_regions_api.regions = AsyncMock(return_value=(mock_regions, None))
199
198
  mock_config.get.return_value = "test-org"
@@ -208,7 +207,7 @@ class TestCLIRegionValidation:
208
207
  from_file=None,
209
208
  skip_confirm=True,
210
209
  organization="test-org",
211
- region="us-west" # Valid region from API
210
+ region="us-west", # Valid region from API
212
211
  )
213
212
  except SystemExit:
214
213
  pass # Command completes
@@ -1 +0,0 @@
1
- # Test package for pipecatcloud
File without changes
File without changes
File without changes
File without changes
File without changes