pipecatcloud 0.2.8__tar.gz → 0.2.10__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 (54) hide show
  1. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/CHANGELOG.md +19 -0
  2. {pipecatcloud-0.2.8/src/pipecatcloud.egg-info → pipecatcloud-0.2.10}/PKG-INFO +2 -2
  3. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/pyproject.toml +1 -1
  4. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/_utils/deploy_utils.py +26 -0
  5. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/api.py +3 -0
  6. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/agent.py +14 -0
  7. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/deploy.py +18 -2
  8. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/entry_point.py +2 -6
  9. pipecatcloud-0.2.10/src/pipecatcloud/constants.py +22 -0
  10. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10/src/pipecatcloud.egg-info}/PKG-INFO +2 -2
  11. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud.egg-info/SOURCES.txt +2 -1
  12. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud.egg-info/requires.txt +1 -1
  13. pipecatcloud-0.2.10/tests/test_krisp_viva.py +647 -0
  14. pipecatcloud-0.2.8/src/pipecatcloud/cli/commands/init.py +0 -142
  15. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/.github/workflows/publish-pypi.yml +0 -0
  16. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/.github/workflows/publish-test.yml +0 -0
  17. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/.github/workflows/tests.yml +0 -0
  18. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/.gitignore +0 -0
  19. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/LICENSE +0 -0
  20. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/README.md +0 -0
  21. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/pipecat-cloud.png +0 -0
  22. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/pytest.ini +0 -0
  23. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/setup.cfg +0 -0
  24. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/__init__.py +0 -0
  25. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/__main__.py +0 -0
  26. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/__version__.py +0 -0
  27. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/_utils/__init__.py +0 -0
  28. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/_utils/async_utils.py +0 -0
  29. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  30. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/_utils/console_utils.py +0 -0
  31. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/_utils/local_runner.py +0 -0
  32. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/agent.py +0 -0
  33. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/__init__.py +0 -0
  34. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/api.py +0 -0
  35. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  36. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/auth.py +0 -0
  37. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/docker.py +0 -0
  38. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  39. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/run.py +0 -0
  40. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  41. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/cli/config.py +0 -0
  42. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/config.py +0 -0
  43. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/exception.py +0 -0
  44. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/py.typed +0 -0
  45. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/session.py +0 -0
  46. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
  47. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  48. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  49. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  50. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/tests/__init__.py +0 -0
  51. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/tests/test_agent_sessions.py +0 -0
  52. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/tests/test_agent_stop.py +0 -0
  53. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/tests/test_docker_commands.py +0 -0
  54. {pipecatcloud-0.2.8 → pipecatcloud-0.2.10}/tests/test_managed_keys.py +0 -0
@@ -5,6 +5,25 @@ 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.10] - 2025-10-25
9
+
10
+ ### Removed
11
+
12
+ - The `init` command was removed. Use the `pipecat-ai-cli`'s `init` command to
13
+ scaffold your project.
14
+
15
+ ## [0.2.9] - 2025-10-24
16
+
17
+ ### Added
18
+
19
+ - Added Krisp VIVA audio filtering support with the `--krisp-viva-audio-filter`
20
+ option to the `deploy` command. Supports `tel` (telephone) and `pro` (professional)
21
+ audio filter models. Can also be configured via `pcc-deploy.toml` by adding a
22
+ `[krisp_viva]` section with `audio_filter = "tel"` or `audio_filter = "pro"`.
23
+
24
+ - `agent status` now displays the Krisp VIVA configuration state, showing whether
25
+ it's enabled and which audio filter model is active.
26
+
8
27
  ## [0.2.8] - 2025-10-24
9
28
 
10
29
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.8
3
+ Version: 0.2.10
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/
@@ -13,7 +13,7 @@ Requires-Dist: fastapi<0.117.0,>=0.115.6
13
13
  Requires-Dist: loguru~=0.7.3
14
14
  Requires-Dist: python-dotenv<2.0.0,>=1.0.1
15
15
  Requires-Dist: uvicorn<1.0.0,>=0.32.0
16
- Requires-Dist: typer<0.17.0,>=0.15.3
16
+ Requires-Dist: typer<1,>=0.20.0
17
17
  Requires-Dist: synchronicity~=0.7.6
18
18
  Requires-Dist: toml~=0.10.2
19
19
  Requires-Dist: questionary~=2.1.0
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "loguru~=0.7.3",
17
17
  "python-dotenv>=1.0.1,<2.0.0",
18
18
  "uvicorn>=0.32.0,<1.0.0",
19
- "typer>=0.15.3,<0.17.0",
19
+ "typer>=0.20.0,<1",
20
20
  "synchronicity~=0.7.6",
21
21
  "toml~=0.10.2",
22
22
  "questionary~=2.1.0"
@@ -12,6 +12,7 @@ import toml
12
12
  from attr import dataclass, field
13
13
  from loguru import logger
14
14
 
15
+ from pipecatcloud.constants import KRISP_VIVA_MODELS, KrispVivaAudioFilter
15
16
  from pipecatcloud.exception import ConfigFileError
16
17
 
17
18
  DEPLOY_STATUS_MAP = {
@@ -56,6 +57,23 @@ class ScalingParams:
56
57
  return {"min_agents": self.min_agents, "max_agents": self.max_agents}
57
58
 
58
59
 
60
+ @dataclass
61
+ class KrispVivaConfig:
62
+ audio_filter: Optional[KrispVivaAudioFilter] = None
63
+
64
+ def __attrs_post_init__(self):
65
+ # Validation against known models
66
+ # IMPORTANT: KRISP_VIVA_MODELS must be kept in sync with API configuration
67
+ if self.audio_filter is not None:
68
+ if self.audio_filter not in KRISP_VIVA_MODELS:
69
+ raise ValueError(
70
+ f"audio_filter must be one of {KRISP_VIVA_MODELS}, got '{self.audio_filter}'"
71
+ )
72
+
73
+ def to_dict(self):
74
+ return {"audio_filter": self.audio_filter}
75
+
76
+
59
77
  @dataclass
60
78
  class DeployConfigParams:
61
79
  agent_name: Optional[str] = None
@@ -67,6 +85,7 @@ class DeployConfigParams:
67
85
  enable_managed_keys: bool = False
68
86
  docker_config: dict = field(factory=dict)
69
87
  agent_profile: Optional[str] = None
88
+ krisp_viva: KrispVivaConfig = field(factory=KrispVivaConfig)
70
89
 
71
90
  def __attrs_post_init__(self):
72
91
  if self.image is not None and ":" not in self.image:
@@ -83,6 +102,7 @@ class DeployConfigParams:
83
102
  "enable_managed_keys": self.enable_managed_keys,
84
103
  "docker_config": self.docker_config,
85
104
  "agent_profile": self.agent_profile,
105
+ "krisp_viva": self.krisp_viva.to_dict() if self.krisp_viva else None,
86
106
  }
87
107
 
88
108
 
@@ -106,11 +126,16 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
106
126
  # Extract docker configuration if present
107
127
  docker_data = config_data.pop("docker", {})
108
128
 
129
+ # Extract krisp_viva configuration if present
130
+ krisp_viva_data = config_data.pop("krisp_viva", {})
131
+ krisp_viva_config = KrispVivaConfig(**krisp_viva_data)
132
+
109
133
  # Create DeployConfigParams with validated data
110
134
  validated_config = DeployConfigParams(
111
135
  **config_data,
112
136
  scaling=scaling_params,
113
137
  docker_config=docker_data,
138
+ krisp_viva=krisp_viva_config,
114
139
  )
115
140
 
116
141
  # Check for unexpected keys
@@ -124,6 +149,7 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
124
149
  "enable_managed_keys",
125
150
  "docker",
126
151
  "agent_profile",
152
+ "krisp_viva",
127
153
  }
128
154
  unexpected_keys = set(config_data.keys()) - expected_keys
129
155
  if unexpected_keys:
@@ -323,6 +323,9 @@ class _API:
323
323
  "enableKrisp": deploy_config.enable_krisp,
324
324
  "enableIntegratedKeysProxy": deploy_config.enable_managed_keys, # API expects this field name
325
325
  "agentProfile": deploy_config.agent_profile,
326
+ "krispViva": {
327
+ "audioFilter": deploy_config.krisp_viva.audio_filter,
328
+ },
326
329
  }
327
330
 
328
331
  # Remove None values recursively
@@ -165,6 +165,20 @@ async def status(
165
165
  "[green]Enabled[/green]" if integrated_keys_enabled else "[dim]Disabled[/dim]",
166
166
  )
167
167
 
168
+ # Check for Krisp VIVA status (reverse-mapped by API)
169
+ krisp_viva = data.get("krispViva")
170
+ krisp_viva_status = "[dim]Disabled[/dim]"
171
+
172
+ if krisp_viva and isinstance(krisp_viva, dict):
173
+ audio_filter = krisp_viva.get("audioFilter")
174
+ if audio_filter:
175
+ krisp_viva_status = f"[green]Enabled ({audio_filter})[/green]"
176
+
177
+ deployment_table.add_row(
178
+ "[bold]Krisp VIVA:[/bold]",
179
+ krisp_viva_status,
180
+ )
181
+
168
182
  # Autoscaling info
169
183
  autoscaling_data = data.get("autoScaling", None)
170
184
  if autoscaling_data:
@@ -19,9 +19,11 @@ from pipecatcloud._utils.auth_utils import requires_login
19
19
  from pipecatcloud._utils.console_utils import console
20
20
  from pipecatcloud._utils.deploy_utils import (
21
21
  DeployConfigParams,
22
+ KrispVivaConfig,
22
23
  ScalingParams,
23
24
  with_deploy_config,
24
25
  )
26
+ from pipecatcloud.constants import KRISP_VIVA_MODELS
25
27
  from pipecatcloud.cli import PIPECAT_CLI_NAME
26
28
  from pipecatcloud.cli.api import API
27
29
  from pipecatcloud.cli.config import config
@@ -276,7 +278,7 @@ def create_deploy_command(app: typer.Typer):
276
278
  False,
277
279
  "--enable-krisp",
278
280
  "-krisp",
279
- help="Enable Krisp integration for this deployment",
281
+ help="[DEPRECATED] Enable Krisp integration for this deployment. Use --krisp-viva-audio-filter instead",
280
282
  rich_help_panel="Deployment Configuration",
281
283
  ),
282
284
  managed_keys: bool = typer.Option(
@@ -285,6 +287,12 @@ def create_deploy_command(app: typer.Typer):
285
287
  help="Enable Managed Keys for this deployment",
286
288
  rich_help_panel="Deployment Configuration",
287
289
  ),
290
+ krisp_viva_audio_filter: str = typer.Option(
291
+ None,
292
+ "--krisp-viva-audio-filter",
293
+ help=f"Enable Krisp VIVA with audio filter model ({' or '.join(KRISP_VIVA_MODELS)})",
294
+ rich_help_panel="Deployment Configuration",
295
+ ),
288
296
  profile: str = typer.Option(
289
297
  None,
290
298
  "--profile",
@@ -334,6 +342,9 @@ def create_deploy_command(app: typer.Typer):
334
342
  logger.warning("max_instances is deprecated, use max_agents instead")
335
343
  max_agents = max_instances
336
344
 
345
+ if krisp:
346
+ logger.warning("--enable-krisp is deprecated, use --krisp-viva-audio-filter instead for the latest Krisp VIVA models.")
347
+
337
348
  org = organization or config.get("org")
338
349
 
339
350
  # Compose deployment config from CLI options and config file (if provided)
@@ -361,6 +372,10 @@ def create_deploy_command(app: typer.Typer):
361
372
  partial_config.enable_managed_keys = managed_keys or partial_config.enable_managed_keys
362
373
  partial_config.agent_profile = profile or partial_config.agent_profile
363
374
 
375
+ # Handle Krisp VIVA configuration
376
+ if krisp_viva_audio_filter is not None:
377
+ partial_config.krisp_viva = KrispVivaConfig(audio_filter=krisp_viva_audio_filter)
378
+
364
379
  # Assert agent name and image are provided
365
380
  if not partial_config.agent_name:
366
381
  console.error("Agent name is required")
@@ -397,7 +412,8 @@ def create_deploy_command(app: typer.Typer):
397
412
  (f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"),
398
413
  (f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"),
399
414
  (f"[bold white]Agent profile:[/bold white] {'[dim]None[/dim]' if not partial_config.agent_profile else '[green]' + partial_config.agent_profile + '[/green]'}"),
400
- (f"[bold white]Krisp:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"),
415
+ (f"[bold white]Krisp (deprecated):[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"),
416
+ (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]'}"),
401
417
  (f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}"),
402
418
  "\n[dim]Scaling configuration:[/dim]",
403
419
  table,
@@ -14,7 +14,6 @@ from pipecatcloud.cli.commands.agent import agent_cli
14
14
  from pipecatcloud.cli.commands.auth import auth_cli
15
15
  from pipecatcloud.cli.commands.deploy import create_deploy_command
16
16
  from pipecatcloud.cli.commands.docker import create_docker_command
17
- from pipecatcloud.cli.commands.init import create_init_command
18
17
  from pipecatcloud.cli.commands.organizations import organization_cli
19
18
  from pipecatcloud.cli.commands.run import create_run_command
20
19
  from pipecatcloud.cli.commands.secrets import secrets_cli
@@ -62,10 +61,8 @@ entrypoint_cli_typer = typer.Typer(
62
61
  no_args_is_help=True,
63
62
  add_completion=False,
64
63
  rich_markup_mode="markdown",
65
- help="""
66
- ᓚᘏᗢ Pipecat Cloud CLI
67
- See website at https://pipecat.daily.co
68
- """,
64
+ short_help="Deploy and manage bots on Pipecat Cloud",
65
+ help="ᓚᘏᗢ Pipecat Cloud CLI. See website at https://pipecat.daily.co",
69
66
  )
70
67
 
71
68
 
@@ -79,7 +76,6 @@ def cli(
79
76
 
80
77
 
81
78
  create_run_command(entrypoint_cli_typer)
82
- create_init_command(entrypoint_cli_typer)
83
79
  create_deploy_command(entrypoint_cli_typer)
84
80
  create_docker_command(entrypoint_cli_typer)
85
81
  entrypoint_cli_typer.add_typer(auth_cli, rich_help_panel="Commands")
@@ -0,0 +1,22 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ """
8
+ Constants for Pipecat Cloud CLI.
9
+
10
+ IMPORTANT: These values must be kept in sync with API configuration.
11
+ When adding new models or options, update both the API ConfigMap and these constants.
12
+ """
13
+
14
+ from typing import Literal, get_args
15
+
16
+ # Krisp VIVA audio filter models
17
+ # These must match the keys in the API's KRISP_AUDIO_FILTER_MODELS ConfigMap
18
+ # Location: pipecat-cloud-sandbox/api/helm/chart/values.yaml -> krispConfig.audioFilterModels
19
+ KrispVivaAudioFilter = Literal["tel", "pro"]
20
+
21
+ # Derive runtime list from the Literal type for validation
22
+ KRISP_VIVA_MODELS = list(get_args(KrispVivaAudioFilter))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.8
3
+ Version: 0.2.10
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/
@@ -13,7 +13,7 @@ Requires-Dist: fastapi<0.117.0,>=0.115.6
13
13
  Requires-Dist: loguru~=0.7.3
14
14
  Requires-Dist: python-dotenv<2.0.0,>=1.0.1
15
15
  Requires-Dist: uvicorn<1.0.0,>=0.32.0
16
- Requires-Dist: typer<0.17.0,>=0.15.3
16
+ Requires-Dist: typer<1,>=0.20.0
17
17
  Requires-Dist: synchronicity~=0.7.6
18
18
  Requires-Dist: toml~=0.10.2
19
19
  Requires-Dist: questionary~=2.1.0
@@ -14,6 +14,7 @@ src/pipecatcloud/__version__.py
14
14
  src/pipecatcloud/agent.py
15
15
  src/pipecatcloud/api.py
16
16
  src/pipecatcloud/config.py
17
+ src/pipecatcloud/constants.py
17
18
  src/pipecatcloud/exception.py
18
19
  src/pipecatcloud/py.typed
19
20
  src/pipecatcloud/session.py
@@ -38,7 +39,6 @@ src/pipecatcloud/cli/commands/agent.py
38
39
  src/pipecatcloud/cli/commands/auth.py
39
40
  src/pipecatcloud/cli/commands/deploy.py
40
41
  src/pipecatcloud/cli/commands/docker.py
41
- src/pipecatcloud/cli/commands/init.py
42
42
  src/pipecatcloud/cli/commands/organizations.py
43
43
  src/pipecatcloud/cli/commands/run.py
44
44
  src/pipecatcloud/cli/commands/secrets.py
@@ -47,4 +47,5 @@ tests/__init__.py
47
47
  tests/test_agent_sessions.py
48
48
  tests/test_agent_stop.py
49
49
  tests/test_docker_commands.py
50
+ tests/test_krisp_viva.py
50
51
  tests/test_managed_keys.py
@@ -3,7 +3,7 @@ fastapi<0.117.0,>=0.115.6
3
3
  loguru~=0.7.3
4
4
  python-dotenv<2.0.0,>=1.0.1
5
5
  uvicorn<1.0.0,>=0.32.0
6
- typer<0.17.0,>=0.15.3
6
+ typer<1,>=0.20.0
7
7
  synchronicity~=0.7.6
8
8
  toml~=0.10.2
9
9
  questionary~=2.1.0
@@ -0,0 +1,647 @@
1
+ """
2
+ Unit tests for Krisp VIVA feature.
3
+
4
+ Tests follow AAA pattern and focus on behaviors/outcomes rather than implementation details.
5
+ Covers data model, TOML parsing, CLI commands, API integration, and validation.
6
+ """
7
+
8
+ from unittest.mock import AsyncMock, patch
9
+
10
+ import pytest
11
+
12
+ from src.pipecatcloud._utils.deploy_utils import (
13
+ DeployConfigParams,
14
+ KrispVivaConfig,
15
+ load_deploy_config_file,
16
+ )
17
+ from src.pipecatcloud.api import _API
18
+ from src.pipecatcloud.constants import KRISP_VIVA_MODELS
19
+
20
+
21
+ class TestKrispVivaDataModel:
22
+ """Test the KrispVivaConfig data model."""
23
+
24
+ def test_default_is_disabled(self):
25
+ """When not specified, Krisp VIVA should default to disabled."""
26
+ # Arrange & Act
27
+ config = KrispVivaConfig()
28
+
29
+ # Assert
30
+ assert config.audio_filter is None
31
+
32
+ def test_can_set_tel_model(self):
33
+ """Tel audio filter model can be explicitly set."""
34
+ # Arrange & Act
35
+ config = KrispVivaConfig(audio_filter="tel")
36
+
37
+ # Assert
38
+ assert config.audio_filter == "tel"
39
+
40
+ def test_can_set_pro_model(self):
41
+ """Pro audio filter model can be explicitly set."""
42
+ # Arrange & Act
43
+ config = KrispVivaConfig(audio_filter="pro")
44
+
45
+ # Assert
46
+ assert config.audio_filter == "pro"
47
+
48
+ def test_rejects_invalid_model(self):
49
+ """Invalid audio filter models should be rejected."""
50
+ # Arrange & Act & Assert
51
+ with pytest.raises(ValueError, match="audio_filter must be one of"):
52
+ KrispVivaConfig(audio_filter="invalid-model")
53
+
54
+ def test_validation_uses_constants(self):
55
+ """Validation should use KRISP_VIVA_MODELS constant."""
56
+ # Arrange & Act & Assert
57
+ with pytest.raises(ValueError) as exc_info:
58
+ KrispVivaConfig(audio_filter="bad-model")
59
+
60
+ # Assert error message includes the models from constants
61
+ error_message = str(exc_info.value)
62
+ for model in KRISP_VIVA_MODELS:
63
+ assert model in error_message
64
+
65
+ def test_to_dict_when_disabled(self):
66
+ """Dictionary representation when disabled should have null audio_filter."""
67
+ # Arrange
68
+ config = KrispVivaConfig()
69
+
70
+ # Act
71
+ result = config.to_dict()
72
+
73
+ # Assert
74
+ assert result == {"audio_filter": None}
75
+
76
+ def test_to_dict_when_enabled(self):
77
+ """Dictionary representation when enabled should include the model."""
78
+ # Arrange
79
+ config = KrispVivaConfig(audio_filter="tel")
80
+
81
+ # Act
82
+ result = config.to_dict()
83
+
84
+ # Assert
85
+ assert result == {"audio_filter": "tel"}
86
+
87
+
88
+ class TestDeployConfigIntegration:
89
+ """Test DeployConfigParams integration with Krisp VIVA."""
90
+
91
+ def test_default_krisp_viva_is_disabled(self):
92
+ """When not specified, Krisp VIVA should default to disabled."""
93
+ # Arrange & Act
94
+ config = DeployConfigParams()
95
+
96
+ # Assert
97
+ assert config.krisp_viva is not None
98
+ assert config.krisp_viva.audio_filter is None
99
+
100
+ def test_can_enable_krisp_viva(self):
101
+ """Krisp VIVA can be explicitly enabled with a model."""
102
+ # Arrange & Act
103
+ config = DeployConfigParams(krisp_viva=KrispVivaConfig(audio_filter="tel"))
104
+
105
+ # Assert
106
+ assert config.krisp_viva.audio_filter == "tel"
107
+
108
+ def test_krisp_viva_included_in_dict_representation(self):
109
+ """Dictionary representation should include Krisp VIVA configuration."""
110
+ # Arrange
111
+ config = DeployConfigParams(
112
+ agent_name="test-agent", krisp_viva=KrispVivaConfig(audio_filter="pro")
113
+ )
114
+
115
+ # Act
116
+ result = config.to_dict()
117
+
118
+ # Assert
119
+ assert "krisp_viva" in result
120
+ assert result["krisp_viva"]["audio_filter"] == "pro"
121
+
122
+ def test_krisp_viva_preserves_other_settings(self):
123
+ """Adding Krisp VIVA should not affect other configuration settings."""
124
+ # Arrange
125
+ config = DeployConfigParams(
126
+ agent_name="test-agent",
127
+ image="test:latest",
128
+ enable_krisp=True,
129
+ enable_managed_keys=True,
130
+ krisp_viva=KrispVivaConfig(audio_filter="tel"),
131
+ )
132
+
133
+ # Act
134
+ result = config.to_dict()
135
+
136
+ # Assert
137
+ assert result["agent_name"] == "test-agent"
138
+ assert result["image"] == "test:latest"
139
+ assert result["enable_krisp"] is True
140
+ assert result["enable_managed_keys"] is True
141
+ assert result["krisp_viva"]["audio_filter"] == "tel"
142
+
143
+
144
+ class TestTOMLConfiguration:
145
+ """Test TOML configuration file parsing with Krisp VIVA."""
146
+
147
+ @pytest.fixture
148
+ def temp_config_file(self, tmp_path):
149
+ """Create a temporary TOML config file."""
150
+ config_path = tmp_path / "pcc-deploy.toml"
151
+ return config_path
152
+
153
+ def test_loads_krisp_viva_tel_from_toml(self, temp_config_file):
154
+ """TOML file with Krisp VIVA tel model should be parsed correctly."""
155
+ # Arrange
156
+ config_content = """
157
+ agent_name = "test-agent"
158
+ image = "test:latest"
159
+
160
+ [krisp_viva]
161
+ audio_filter = "tel"
162
+ """
163
+ temp_config_file.write_text(config_content)
164
+
165
+ # Act
166
+ with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
167
+ config = load_deploy_config_file()
168
+
169
+ # Assert
170
+ assert config is not None
171
+ assert config.krisp_viva.audio_filter == "tel"
172
+
173
+ def test_loads_krisp_viva_pro_from_toml(self, temp_config_file):
174
+ """TOML file with Krisp VIVA pro model should be parsed correctly."""
175
+ # Arrange
176
+ config_content = """
177
+ agent_name = "test-agent"
178
+ image = "test:latest"
179
+
180
+ [krisp_viva]
181
+ audio_filter = "pro"
182
+ """
183
+ temp_config_file.write_text(config_content)
184
+
185
+ # Act
186
+ with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
187
+ config = load_deploy_config_file()
188
+
189
+ # Assert
190
+ assert config is not None
191
+ assert config.krisp_viva.audio_filter == "pro"
192
+
193
+ def test_rejects_invalid_model_from_toml(self, temp_config_file):
194
+ """TOML file with invalid Krisp VIVA model should raise error."""
195
+ # Arrange
196
+ config_content = """
197
+ agent_name = "test-agent"
198
+ image = "test:latest"
199
+
200
+ [krisp_viva]
201
+ audio_filter = "invalid-model"
202
+ """
203
+ temp_config_file.write_text(config_content)
204
+
205
+ # Act & Assert
206
+ with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
207
+ with pytest.raises(Exception): # ConfigFileError wraps ValueError
208
+ load_deploy_config_file()
209
+
210
+ def test_defaults_when_not_in_toml(self, temp_config_file):
211
+ """TOML file without Krisp VIVA should default to disabled."""
212
+ # Arrange
213
+ config_content = """
214
+ agent_name = "test-agent"
215
+ image = "test:latest"
216
+ """
217
+ temp_config_file.write_text(config_content)
218
+
219
+ # Act
220
+ with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
221
+ config = load_deploy_config_file()
222
+
223
+ # Assert
224
+ assert config is not None
225
+ assert config.krisp_viva.audio_filter is None
226
+
227
+ def test_preserves_other_settings_with_krisp_viva(self, temp_config_file):
228
+ """Krisp VIVA setting should not interfere with other TOML settings."""
229
+ # Arrange
230
+ config_content = """
231
+ agent_name = "test-agent"
232
+ image = "test:latest"
233
+ enable_krisp = true
234
+ enable_managed_keys = true
235
+ secret_set = "my-secrets"
236
+
237
+ [scaling]
238
+ min_agents = 2
239
+ max_agents = 10
240
+
241
+ [krisp_viva]
242
+ audio_filter = "pro"
243
+ """
244
+ temp_config_file.write_text(config_content)
245
+
246
+ # Act
247
+ with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
248
+ config = load_deploy_config_file()
249
+
250
+ # Assert
251
+ assert config.agent_name == "test-agent"
252
+ assert config.image == "test:latest"
253
+ assert config.enable_krisp is True
254
+ assert config.enable_managed_keys is True
255
+ assert config.secret_set == "my-secrets"
256
+ assert config.scaling.min_agents == 2
257
+ assert config.scaling.max_agents == 10
258
+ assert config.krisp_viva.audio_filter == "pro"
259
+
260
+
261
+ class TestAPIIntegration:
262
+ """Test API client integration with Krisp VIVA."""
263
+
264
+ @pytest.fixture
265
+ def api_client(self):
266
+ """Create an API client instance."""
267
+ return _API(token="test-token", is_cli=True)
268
+
269
+ @pytest.mark.asyncio
270
+ async def test_deploy_payload_includes_krisp_viva_tel(self, api_client):
271
+ """Deploy payload should include krispViva with tel model."""
272
+ # Arrange
273
+ config = DeployConfigParams(
274
+ agent_name="test-agent",
275
+ image="test:latest",
276
+ krisp_viva=KrispVivaConfig(audio_filter="tel"),
277
+ )
278
+
279
+ with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
280
+ mock_request.return_value = {"success": True}
281
+
282
+ # Act
283
+ await api_client._deploy(config, "test-org", update=False)
284
+
285
+ # Assert
286
+ mock_request.assert_called_once()
287
+ payload = mock_request.call_args[1]["json"]
288
+ assert "krispViva" in payload
289
+ assert payload["krispViva"]["audioFilter"] == "tel"
290
+
291
+ @pytest.mark.asyncio
292
+ async def test_deploy_payload_includes_krisp_viva_pro(self, api_client):
293
+ """Deploy payload should include krispViva with pro model."""
294
+ # Arrange
295
+ config = DeployConfigParams(
296
+ agent_name="test-agent",
297
+ image="test:latest",
298
+ krisp_viva=KrispVivaConfig(audio_filter="pro"),
299
+ )
300
+
301
+ with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
302
+ mock_request.return_value = {"success": True}
303
+
304
+ # Act
305
+ await api_client._deploy(config, "test-org", update=False)
306
+
307
+ # Assert
308
+ mock_request.assert_called_once()
309
+ payload = mock_request.call_args[1]["json"]
310
+ assert "krispViva" in payload
311
+ assert payload["krispViva"]["audioFilter"] == "pro"
312
+
313
+ @pytest.mark.asyncio
314
+ async def test_update_payload_includes_krisp_viva(self, api_client):
315
+ """Update payload should include krispViva field."""
316
+ # Arrange
317
+ config = DeployConfigParams(
318
+ agent_name="test-agent",
319
+ image="test:latest",
320
+ krisp_viva=KrispVivaConfig(audio_filter="tel"),
321
+ )
322
+
323
+ with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
324
+ mock_request.return_value = {"success": True}
325
+
326
+ # Act
327
+ await api_client._deploy(config, "test-org", update=True)
328
+
329
+ # Assert
330
+ mock_request.assert_called_once()
331
+ payload = mock_request.call_args[1]["json"]
332
+ assert "krispViva" in payload
333
+ assert payload["krispViva"]["audioFilter"] == "tel"
334
+
335
+ @pytest.mark.asyncio
336
+ async def test_disabled_krisp_viva_sends_empty_object(self, api_client):
337
+ """When Krisp VIVA is disabled, API should send empty krispViva object."""
338
+ # Arrange
339
+ config = DeployConfigParams(
340
+ agent_name="test-agent", image="test:latest", krisp_viva=KrispVivaConfig()
341
+ )
342
+
343
+ with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
344
+ mock_request.return_value = {"success": True}
345
+
346
+ # Act
347
+ await api_client._deploy(config, "test-org", update=False)
348
+
349
+ # Assert
350
+ mock_request.assert_called_once()
351
+ payload = mock_request.call_args[1]["json"]
352
+ assert "krispViva" in payload
353
+ # None values are filtered out by remove_none_values, leaving empty dict
354
+ assert payload["krispViva"] == {}
355
+
356
+
357
+ class TestAgentStatusDisplay:
358
+ """Test agent status command displays Krisp VIVA status."""
359
+
360
+ @pytest.fixture
361
+ def mock_api(self):
362
+ """Mock the API agent method."""
363
+ with patch("src.pipecatcloud.cli.commands.agent.API") as mock_api:
364
+ yield mock_api
365
+
366
+ def test_displays_enabled_krisp_viva_tel(self, mock_api, capsys):
367
+ """Agent status should display when Krisp VIVA is enabled with tel model."""
368
+ # Arrange
369
+ from src.pipecatcloud.cli.commands.agent import status
370
+
371
+ mock_api.agent = AsyncMock(
372
+ return_value=(
373
+ {
374
+ "ready": True,
375
+ "deployment": {"manifest": {"spec": {}}},
376
+ "krispViva": {"audioFilter": "tel"},
377
+ "autoScaling": {
378
+ "minReplicas": 0,
379
+ "maxReplicas": 1,
380
+ },
381
+ },
382
+ None,
383
+ )
384
+ )
385
+
386
+ # Act
387
+ status(agent_name="test-agent", organization="test-org")
388
+
389
+ # Assert
390
+ captured = capsys.readouterr()
391
+ assert "Krisp VIVA" in captured.out
392
+ assert "Enabled" in captured.out
393
+ assert "tel" in captured.out
394
+
395
+ def test_displays_enabled_krisp_viva_pro(self, mock_api, capsys):
396
+ """Agent status should display when Krisp VIVA is enabled with pro model."""
397
+ # Arrange
398
+ from src.pipecatcloud.cli.commands.agent import status
399
+
400
+ mock_api.agent = AsyncMock(
401
+ return_value=(
402
+ {
403
+ "ready": True,
404
+ "deployment": {"manifest": {"spec": {}}},
405
+ "krispViva": {"audioFilter": "pro"},
406
+ "autoScaling": {
407
+ "minReplicas": 0,
408
+ "maxReplicas": 1,
409
+ },
410
+ },
411
+ None,
412
+ )
413
+ )
414
+
415
+ # Act
416
+ status(agent_name="test-agent", organization="test-org")
417
+
418
+ # Assert
419
+ captured = capsys.readouterr()
420
+ assert "Krisp VIVA" in captured.out
421
+ assert "Enabled" in captured.out
422
+ assert "pro" in captured.out
423
+
424
+ def test_displays_disabled_krisp_viva(self, mock_api, capsys):
425
+ """Agent status should display when Krisp VIVA is disabled."""
426
+ # Arrange
427
+ from src.pipecatcloud.cli.commands.agent import status
428
+
429
+ mock_api.agent = AsyncMock(
430
+ return_value=(
431
+ {
432
+ "ready": True,
433
+ "deployment": {"manifest": {"spec": {}}},
434
+ "autoScaling": {
435
+ "minReplicas": 0,
436
+ "maxReplicas": 1,
437
+ },
438
+ },
439
+ None,
440
+ )
441
+ )
442
+
443
+ # Act
444
+ status(agent_name="test-agent", organization="test-org")
445
+
446
+ # Assert
447
+ captured = capsys.readouterr()
448
+ assert "Krisp VIVA" in captured.out
449
+ assert "Disabled" in captured.out
450
+
451
+ def test_handles_null_audio_filter(self, mock_api, capsys):
452
+ """Agent status should handle krispViva with null audioFilter."""
453
+ # Arrange
454
+ from src.pipecatcloud.cli.commands.agent import status
455
+
456
+ mock_api.agent = AsyncMock(
457
+ return_value=(
458
+ {
459
+ "ready": True,
460
+ "deployment": {"manifest": {"spec": {}}},
461
+ "krispViva": {"audioFilter": None},
462
+ "autoScaling": {
463
+ "minReplicas": 0,
464
+ "maxReplicas": 1,
465
+ },
466
+ },
467
+ None,
468
+ )
469
+ )
470
+
471
+ # Act
472
+ status(agent_name="test-agent", organization="test-org")
473
+
474
+ # Assert
475
+ captured = capsys.readouterr()
476
+ assert "Krisp VIVA" in captured.out
477
+ assert "Disabled" in captured.out
478
+
479
+ def test_handles_missing_krisp_viva_field(self, mock_api, capsys):
480
+ """Agent status should handle responses without krispViva field."""
481
+ # Arrange
482
+ from src.pipecatcloud.cli.commands.agent import status
483
+
484
+ mock_api.agent = AsyncMock(
485
+ return_value=(
486
+ {
487
+ "ready": True,
488
+ "deployment": {"manifest": {"spec": {}}},
489
+ "autoScaling": {
490
+ "minReplicas": 0,
491
+ "maxReplicas": 1,
492
+ },
493
+ },
494
+ None,
495
+ )
496
+ )
497
+
498
+ # Act
499
+ status(agent_name="test-agent", organization="test-org")
500
+
501
+ # Assert
502
+ captured = capsys.readouterr()
503
+ assert "Krisp VIVA" in captured.out
504
+ assert "Disabled" in captured.out
505
+
506
+
507
+ class TestBackwardCompatibility:
508
+ """Test backward compatibility with existing deployments."""
509
+
510
+ def test_existing_config_without_krisp_viva_works(self):
511
+ """Existing configs without Krisp VIVA field should work unchanged."""
512
+ # Arrange
513
+ config = DeployConfigParams(agent_name="test-agent", image="test:latest", enable_krisp=True)
514
+
515
+ # Act
516
+ result = config.to_dict()
517
+
518
+ # Assert
519
+ assert result["enable_krisp"] is True
520
+ assert result["krisp_viva"]["audio_filter"] is None
521
+
522
+ def test_api_response_without_krisp_viva_doesnt_crash(self):
523
+ """Agent status should handle API responses without Krisp VIVA field."""
524
+ # Arrange
525
+ data = {"ready": True, "deployment": {"manifest": {"spec": {"image": "test:latest"}}}}
526
+
527
+ # Act - simulate the logic from agent.py
528
+ krisp_viva = data.get("krispViva")
529
+ krisp_viva_status = "[dim]Disabled[/dim]"
530
+
531
+ if krisp_viva and isinstance(krisp_viva, dict):
532
+ audio_filter = krisp_viva.get("audioFilter")
533
+ if audio_filter:
534
+ krisp_viva_status = f"[green]Enabled ({audio_filter})[/green]"
535
+
536
+ # Assert
537
+ assert krisp_viva_status == "[dim]Disabled[/dim]"
538
+
539
+ @pytest.fixture
540
+ def temp_legacy_config(self, tmp_path):
541
+ """Create a legacy TOML config without Krisp VIVA."""
542
+ config_path = tmp_path / "pcc-deploy.toml"
543
+ config_content = """
544
+ agent_name = "legacy-agent"
545
+ image = "legacy:latest"
546
+ enable_krisp = true
547
+ enable_managed_keys = true
548
+
549
+ [scaling]
550
+ min_agents = 1
551
+ max_agents = 5
552
+ """
553
+ config_path.write_text(config_content)
554
+ return config_path
555
+
556
+ def test_legacy_toml_loads_with_defaults(self, temp_legacy_config):
557
+ """Legacy TOML files should load with Krisp VIVA defaulting to disabled."""
558
+ # Arrange & Act
559
+ with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_legacy_config)):
560
+ config = load_deploy_config_file()
561
+
562
+ # Assert
563
+ assert config is not None
564
+ assert config.agent_name == "legacy-agent"
565
+ assert config.enable_krisp is True
566
+ assert config.enable_managed_keys is True
567
+ assert config.krisp_viva.audio_filter is None
568
+
569
+
570
+ class TestErrorHandling:
571
+ """Test error handling and edge cases."""
572
+
573
+ @pytest.mark.asyncio
574
+ async def test_api_error_during_deploy_with_krisp_viva(self):
575
+ """API errors during deployment with Krisp VIVA should be handled properly."""
576
+ # Arrange
577
+ api = _API(token="test-token", is_cli=True)
578
+ config = DeployConfigParams(
579
+ agent_name="test-agent",
580
+ image="test:latest",
581
+ krisp_viva=KrispVivaConfig(audio_filter="tel"),
582
+ )
583
+
584
+ with patch.object(api, "_base_request", new_callable=AsyncMock) as mock_request:
585
+ mock_request.side_effect = Exception("Network error")
586
+
587
+ # Act & Assert
588
+ with pytest.raises(Exception, match="Network error"):
589
+ await api._deploy(config, "test-org", update=False)
590
+
591
+ def test_simultaneous_krisp_and_krisp_viva(self):
592
+ """Both Krisp and Krisp VIVA can be enabled simultaneously."""
593
+ # Arrange
594
+ config = DeployConfigParams(
595
+ agent_name="test-agent",
596
+ image="test:latest",
597
+ enable_krisp=True,
598
+ krisp_viva=KrispVivaConfig(audio_filter="tel"),
599
+ )
600
+
601
+ # Act
602
+ result = config.to_dict()
603
+
604
+ # Assert
605
+ assert result["enable_krisp"] is True
606
+ assert result["krisp_viva"]["audio_filter"] == "tel"
607
+
608
+ def test_all_features_enabled_together(self):
609
+ """Krisp, Managed Keys, and Krisp VIVA can all be enabled together."""
610
+ # Arrange
611
+ config = DeployConfigParams(
612
+ agent_name="test-agent",
613
+ image="test:latest",
614
+ enable_krisp=True,
615
+ enable_managed_keys=True,
616
+ krisp_viva=KrispVivaConfig(audio_filter="pro"),
617
+ )
618
+
619
+ # Act
620
+ result = config.to_dict()
621
+
622
+ # Assert
623
+ assert result["enable_krisp"] is True
624
+ assert result["enable_managed_keys"] is True
625
+ assert result["krisp_viva"]["audio_filter"] == "pro"
626
+
627
+
628
+ class TestConstantsSynchronization:
629
+ """Test that constants are properly synchronized and used."""
630
+
631
+ def test_all_models_in_constants_are_valid(self):
632
+ """All models in KRISP_VIVA_MODELS should be accepted."""
633
+ # Arrange & Act & Assert
634
+ for model in KRISP_VIVA_MODELS:
635
+ config = KrispVivaConfig(audio_filter=model)
636
+ assert config.audio_filter == model
637
+
638
+ def test_constants_not_empty(self):
639
+ """KRISP_VIVA_MODELS should not be empty."""
640
+ # Arrange & Act & Assert
641
+ assert len(KRISP_VIVA_MODELS) > 0
642
+
643
+ def test_constants_contain_expected_models(self):
644
+ """KRISP_VIVA_MODELS should contain expected default models."""
645
+ # Arrange & Act & Assert
646
+ assert "tel" in KRISP_VIVA_MODELS
647
+ assert "pro" in KRISP_VIVA_MODELS
@@ -1,142 +0,0 @@
1
- #
2
- # Copyright (c) 2025, Daily
3
- #
4
- # SPDX-License-Identifier: BSD 2-Clause License
5
- #
6
-
7
- import os
8
- import warnings
9
- import zipfile
10
- from typing import Set
11
-
12
- import aiohttp
13
- import questionary
14
- import typer
15
-
16
- from pipecatcloud._utils.async_utils import synchronizer
17
- from pipecatcloud._utils.console_utils import console
18
- from pipecatcloud.config import config
19
-
20
- # ----- Init
21
-
22
- FILES_TO_EXTRACT = {
23
- "pipecat-quickstart-main/bot.py",
24
- "pipecat-quickstart-main/Dockerfile",
25
- "pipecat-quickstart-main/pcc-deploy.toml",
26
- "pipecat-quickstart-main/README.md",
27
- "pipecat-quickstart-main/.gitignore",
28
- "pipecat-quickstart-main/env.example",
29
- "pipecat-quickstart-main/pyproject.toml",
30
- "pipecat-quickstart-main/uv.lock",
31
- }
32
-
33
-
34
- def check_existing_files() -> Set[str]:
35
- """Check which target files already exist in the current directory."""
36
- return {
37
- os.path.basename(file)
38
- for file in FILES_TO_EXTRACT
39
- if os.path.exists(os.path.basename(file))
40
- }
41
-
42
-
43
- def create_init_command(app: typer.Typer):
44
- @app.command(
45
- name="init",
46
- help="[DEPRECATED] Initialize project directory with template files",
47
- deprecated=True,
48
- )
49
- @synchronizer.create_blocking
50
- async def init():
51
- warnings.warn(
52
- "The 'pcc init' command is deprecated and will be removed in a future version. "
53
- "Please use the Pipecat CLI instead: https://github.com/pipecat-ai/pipecat-cli",
54
- DeprecationWarning,
55
- stacklevel=2,
56
- )
57
- console.print(
58
- "[yellow]Warning:[/yellow] The 'pcc init' command is deprecated and will be removed in a future version.\n"
59
- "Please use the Pipecat CLI instead: "
60
- "[link=https://github.com/pipecat-ai/pipecat-cli]https://github.com/pipecat-ai/pipecat-cli[/link]"
61
- )
62
-
63
- if not await questionary.confirm(
64
- "This will download the latest starter project to the current directory. Continue?"
65
- ).ask_async():
66
- console.print("[bold]Aborting init request[/bold]")
67
- return typer.Exit(1)
68
-
69
- # Check existing files more efficiently
70
- existing_files = check_existing_files()
71
- if existing_files:
72
- files_list = "\n".join(f"- {f}" for f in existing_files)
73
- if not await questionary.confirm(
74
- f"The following files already exist and will be overwritten:\n{files_list}\nDo you want to continue?"
75
- ).ask_async():
76
- console.print("[bold]Aborting init request[/bold]")
77
- return typer.Exit(1)
78
-
79
- zip_path = "init.zip"
80
- try:
81
- with console.status("[dim]Downloading starter project...[/dim]", spinner="dots"):
82
- zip_url = config.get("init_zip_url")
83
- if not zip_url:
84
- raise ValueError("No starter project URL found in configuration")
85
-
86
- async with aiohttp.ClientSession() as session:
87
- async with session.get(zip_url) as response:
88
- if response.status != 200:
89
- raise aiohttp.ClientResponseError(
90
- response.request_info,
91
- response.history,
92
- status=response.status,
93
- message=f"Failed to download starter project: {response.reason}",
94
- )
95
-
96
- # Stream the download to a file
97
- with open(zip_path, "wb") as f:
98
- async for chunk in response.content.iter_chunked(8192):
99
- f.write(chunk)
100
-
101
- # Extract files
102
- with zipfile.ZipFile(zip_path, "r") as zip_ref:
103
- for file in FILES_TO_EXTRACT:
104
- try:
105
- source = zip_ref.read(file)
106
- target = os.path.basename(file)
107
- with open(target, "wb") as f:
108
- f.write(source)
109
- except KeyError:
110
- console.print(
111
- f"[yellow]Warning:[/yellow] File {file} not found in starter project"
112
- )
113
- except Exception as e:
114
- raise Exception(f"Failed to extract {file}: {str(e)}")
115
-
116
- console.success(
117
- "You can now start building your agent in [bold]bot.py[/bold]"
118
- "\n\n"
119
- "Follow the steps in the [bold]README.md[/bold] file to get started.",
120
- title="Project files downloaded successfully",
121
- )
122
-
123
- except aiohttp.ClientError as e:
124
- console.error(f"Network error while downloading starter project: {str(e)}")
125
- return typer.Exit(1)
126
- except zipfile.BadZipFile:
127
- console.error("Downloaded file is not a valid zip archive")
128
- return typer.Exit(1)
129
- except Exception as e:
130
- console.error(f"Error during initialization: {str(e)}")
131
- return typer.Exit(1)
132
- finally:
133
- # Cleanup the temporary zip file
134
- if os.path.exists(zip_path):
135
- try:
136
- os.remove(zip_path)
137
- except Exception as e:
138
- console.print(
139
- f"[yellow]Warning:[/yellow] Failed to cleanup temporary file {zip_path}: {str(e)}"
140
- )
141
-
142
- return init
File without changes
File without changes
File without changes
File without changes
File without changes