pipecatcloud 0.2.3__tar.gz → 0.2.4__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 (53) hide show
  1. pipecatcloud-0.2.4/.github/workflows/publish-pypi.yml +84 -0
  2. pipecatcloud-0.2.4/.github/workflows/publish-test.yml +73 -0
  3. pipecatcloud-0.2.4/.github/workflows/tests.yml +41 -0
  4. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/CHANGELOG.md +13 -0
  5. {pipecatcloud-0.2.3/src/pipecatcloud.egg-info → pipecatcloud-0.2.4}/PKG-INFO +2 -2
  6. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/README.md +1 -1
  7. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/pyproject.toml +14 -1
  8. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/_utils/deploy_utils.py +12 -3
  9. pipecatcloud-0.2.4/src/pipecatcloud/cli/commands/docker.py +344 -0
  10. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/init.py +8 -8
  11. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/entry_point.py +2 -0
  12. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/config.py +1 -1
  13. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4/src/pipecatcloud.egg-info}/PKG-INFO +2 -2
  14. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud.egg-info/SOURCES.txt +4 -2
  15. pipecatcloud-0.2.4/tests/test_docker_commands.py +169 -0
  16. pipecatcloud-0.2.3/.github/workflows/publish-pypi.yml +0 -66
  17. pipecatcloud-0.2.3/.github/workflows/publish-test.yml +0 -67
  18. pipecatcloud-0.2.3/dev-requirements.txt +0 -8
  19. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/.github/workflows/python-publish.yml +0 -0
  20. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/.gitignore +0 -0
  21. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/LICENSE +0 -0
  22. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/pipecat-cloud.png +0 -0
  23. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/pytest.ini +0 -0
  24. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/setup.cfg +0 -0
  25. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/__init__.py +0 -0
  26. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/__main__.py +0 -0
  27. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/__version__.py +0 -0
  28. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/_utils/__init__.py +0 -0
  29. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/_utils/async_utils.py +0 -0
  30. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  31. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/_utils/console_utils.py +0 -0
  32. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/_utils/local_runner.py +0 -0
  33. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/agent.py +0 -0
  34. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/api.py +0 -0
  35. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/__init__.py +0 -0
  36. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/api.py +0 -0
  37. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  38. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/agent.py +0 -0
  39. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/auth.py +0 -0
  40. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/deploy.py +0 -0
  41. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  42. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/run.py +0 -0
  43. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  44. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/cli/config.py +0 -0
  45. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/exception.py +0 -0
  46. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/py.typed +0 -0
  47. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud/session.py +0 -0
  48. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  49. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  50. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud.egg-info/requires.txt +0 -0
  51. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  52. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/tests/__init__.py +0 -0
  53. {pipecatcloud-0.2.3 → pipecatcloud-0.2.4}/tests/test_agent_sessions.py +0 -0
@@ -0,0 +1,84 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: 'Version to publish (must match pyproject.toml, e.g. 0.0.4)'
8
+ required: true
9
+ type: string
10
+
11
+ jobs:
12
+ build:
13
+ name: 'Build and upload wheels'
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+ with:
19
+ fetch-tags: true
20
+ fetch-depth: 100
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v3
24
+ with:
25
+ version: 'latest'
26
+
27
+ - name: Set up Python
28
+ run: uv python install 3.12
29
+
30
+ - name: Verify version matches
31
+ run: |
32
+ PROJECT_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
33
+ if [ "$PROJECT_VERSION" != "${{ github.event.inputs.version }}" ]; then
34
+ echo "Error: Input version (${{ github.event.inputs.version }}) does not match pyproject.toml version ($PROJECT_VERSION)"
35
+ exit 1
36
+ fi
37
+
38
+ - name: Install development dependencies
39
+ run: uv sync --group dev
40
+
41
+ - name: Build project
42
+ run: uv build
43
+
44
+ - name: Upload wheels
45
+ uses: actions/upload-artifact@v4
46
+ with:
47
+ name: wheels
48
+ path: ./dist
49
+
50
+ publish-to-pypi:
51
+ name: 'Publish to PyPI'
52
+ runs-on: ubuntu-latest
53
+ needs: [build]
54
+ environment:
55
+ name: pypi
56
+ url: https://pypi.org/p/pipecat-cloud
57
+ permissions:
58
+ id-token: write
59
+ contents: write # Needed for tagging
60
+ steps:
61
+ - name: Checkout code
62
+ uses: actions/checkout@v4
63
+ with:
64
+ fetch-tags: true
65
+ fetch-depth: 100
66
+
67
+ - name: Create release tag
68
+ run: |
69
+ git config --local user.email "action@github.com"
70
+ git config --local user.name "GitHub Action"
71
+ git tag -a "v${{ github.event.inputs.version }}" -m "Version ${{ github.event.inputs.version }}"
72
+ git push origin "v${{ github.event.inputs.version }}"
73
+
74
+ - name: Download wheels
75
+ uses: actions/download-artifact@v4
76
+ with:
77
+ name: wheels
78
+ path: ./dist
79
+
80
+ - name: Publish to PyPI
81
+ uses: pypa/gh-action-pypi-publish@release/v1
82
+ with:
83
+ verbose: true
84
+ print-hash: true
@@ -0,0 +1,73 @@
1
+ name: Publish to TEST PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: 'Version to publish (must match pyproject.toml, e.g. 0.0.4)'
8
+ required: true
9
+ type: string
10
+
11
+ jobs:
12
+ build:
13
+ name: 'Build and upload wheels'
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-tags: true
22
+ fetch-depth: 100
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v3
26
+ with:
27
+ version: 'latest'
28
+
29
+ - name: Set up Python
30
+ run: uv python install 3.12
31
+
32
+ - name: Verify version matches
33
+ run: |
34
+ PROJECT_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
35
+ if [ "$PROJECT_VERSION" != "${{ github.event.inputs.version }}" ]; then
36
+ echo "Error: Input version (${{ github.event.inputs.version }}) does not match pyproject.toml version ($PROJECT_VERSION)"
37
+ exit 1
38
+ fi
39
+
40
+ - name: Install development dependencies
41
+ run: uv sync --group dev
42
+
43
+ - name: Build project
44
+ run: uv build
45
+
46
+ - name: Upload wheels
47
+ uses: actions/upload-artifact@v4
48
+ with:
49
+ name: wheels
50
+ path: ./dist
51
+
52
+ publish-to-test-pypi:
53
+ name: 'Publish to Test PyPI'
54
+ runs-on: ubuntu-latest
55
+ needs: [build]
56
+ environment:
57
+ name: testpypi
58
+ url: https://test.pypi.org/project/pipecat-cloud
59
+ permissions:
60
+ id-token: write
61
+ steps:
62
+ - name: Download wheels
63
+ uses: actions/download-artifact@v4
64
+ with:
65
+ name: wheels
66
+ path: ./dist
67
+
68
+ - name: Publish to TestPyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+ with:
71
+ repository-url: https://test.pypi.org/legacy/
72
+ verbose: true
73
+ print-hash: true
@@ -0,0 +1,41 @@
1
+ name: tests
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - '**'
11
+
12
+ concurrency:
13
+ group: build-test-${{ github.event.pull_request.number || github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ name: 'Unit Tests'
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout repo
22
+ uses: actions/checkout@v4
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v3
26
+ with:
27
+ version: 'latest'
28
+
29
+ - name: Set up Python
30
+ run: uv python install 3.12
31
+
32
+ - name: Install dependencies
33
+ run: |
34
+ uv sync --group dev
35
+
36
+ - name: Test Docker commands
37
+ run: |
38
+ uv run pytest tests/test_docker_commands.py -v
39
+
40
+ # Note: test_agent_sessions.py is excluded because it requires complex
41
+ # API mocking for CI. These tests can be run locally with proper auth.
@@ -5,6 +5,19 @@ 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.4] - 2025-08-26
9
+
10
+ ### Added
11
+
12
+ - Added `docker build-push` command for building, tagging, and pushing Docker
13
+ images. Automatically parses registry information from the `image` field in
14
+ `pcc-deploy.toml` and supports both Docker Hub and custom registries. Includes
15
+ real-time build output and helpful authentication error hints.
16
+
17
+ ### Changed
18
+
19
+ - `pcc init` now points to https://github.com/pipecat-ai/pipecat-quickstart.
20
+
8
21
  ## [0.2.3] - 2025-08-19
9
22
 
10
23
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.3
3
+ Version: 0.2.4
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/
@@ -63,7 +63,7 @@ pcc auth login
63
63
 
64
64
  2. Login to your account `pcc auth login`
65
65
 
66
- 3. (Optional): Clone the starter agent [here](https://github.com/daily-co/pipecat-cloud-starter)
66
+ 3. (Optional): Clone the quickstart repo [here](https://github.com/pipecat-ai/pipecat-quickstart)
67
67
 
68
68
  4. Build your agent `docker build --platform linux/arm64 -t your-agent-name .`
69
69
 
@@ -40,7 +40,7 @@ pcc auth login
40
40
 
41
41
  2. Login to your account `pcc auth login`
42
42
 
43
- 3. (Optional): Clone the starter agent [here](https://github.com/daily-co/pipecat-cloud-starter)
43
+ 3. (Optional): Clone the quickstart repo [here](https://github.com/pipecat-ai/pipecat-quickstart)
44
44
 
45
45
  4. Build your agent `docker build --platform linux/arm64 -t your-agent-name .`
46
46
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pipecatcloud"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  requires-python = ">=3.10"
9
9
  description = "Cloud hosting for Pipecat AI applications"
10
10
  license = { text = "BSD 2-Clause License" }
@@ -25,6 +25,19 @@ dependencies = [
25
25
  [project.optional-dependencies]
26
26
  pipecat = ["pipecat-ai>=0.0.79"]
27
27
 
28
+ [dependency-groups]
29
+ dev = [
30
+ "build~=1.2.2",
31
+ "grpcio-tools~=1.67.1",
32
+ "pre-commit~=4.2.0",
33
+ "pyright~=1.1.402",
34
+ "pytest~=8.4.1",
35
+ "pytest-asyncio~=1.0.0",
36
+ "ruff~=0.12.1",
37
+ "setuptools~=78.1.1",
38
+ "setuptools_scm~=8.3.1",
39
+ ]
40
+
28
41
  [project.scripts]
29
42
  pcc = "pipecatcloud.__main__:main"
30
43
  pipecatcloud = "pipecatcloud.__main__:main"
@@ -4,9 +4,9 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
- import os
8
7
  import functools
9
- from typing import Optional, Callable, Any
8
+ import os
9
+ from typing import Callable, Optional
10
10
 
11
11
  import toml
12
12
  from attr import dataclass, field
@@ -64,6 +64,7 @@ class DeployConfigParams:
64
64
  secret_set: Optional[str] = None
65
65
  scaling: ScalingParams = ScalingParams()
66
66
  enable_krisp: bool = False
67
+ docker_config: dict = field(factory=dict)
67
68
 
68
69
  def __attrs_post_init__(self):
69
70
  if self.image is not None and ":" not in self.image:
@@ -77,6 +78,7 @@ class DeployConfigParams:
77
78
  "secret_set": self.secret_set,
78
79
  "scaling": self.scaling.to_dict() if self.scaling else None,
79
80
  "enable_krisp": self.enable_krisp,
81
+ "docker_config": self.docker_config,
80
82
  }
81
83
 
82
84
 
@@ -97,10 +99,14 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
97
99
  scaling_data = config_data.pop("scaling", {})
98
100
  scaling_params = ScalingParams(**scaling_data)
99
101
 
102
+ # Extract docker configuration if present
103
+ docker_data = config_data.pop("docker", {})
104
+
100
105
  # Create DeployConfigParams with validated data
101
106
  validated_config = DeployConfigParams(
102
107
  **config_data,
103
108
  scaling=scaling_params,
109
+ docker_config=docker_data,
104
110
  )
105
111
 
106
112
  # Check for unexpected keys
@@ -111,6 +117,7 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
111
117
  "secret_set",
112
118
  "scaling",
113
119
  "enable_krisp",
120
+ "docker",
114
121
  }
115
122
  unexpected_keys = set(config_data.keys()) - expected_keys
116
123
  if unexpected_keys:
@@ -128,13 +135,15 @@ def with_deploy_config(func: Callable) -> Callable:
128
135
  Decorator that loads the deploy config file and injects it into the function.
129
136
  If the config file exists, it will be loaded and passed to the function as `deploy_config`.
130
137
  """
138
+
131
139
  @functools.wraps(func)
132
140
  def wrapper(*args, **kwargs):
133
141
  try:
134
142
  deploy_config = load_deploy_config_file()
135
- kwargs['deploy_config'] = deploy_config
143
+ kwargs["deploy_config"] = deploy_config
136
144
  except Exception as e:
137
145
  logger.error(f"Error loading deploy config: {e}")
138
146
  raise ConfigFileError(str(e))
139
147
  return func(*args, **kwargs)
148
+
140
149
  return wrapper
@@ -0,0 +1,344 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ import subprocess
8
+ from enum import Enum
9
+ from typing import Optional
10
+
11
+ import typer
12
+ from rich.panel import Panel
13
+
14
+ from pipecatcloud._utils.async_utils import synchronizer
15
+ from pipecatcloud._utils.console_utils import console
16
+ from pipecatcloud._utils.deploy_utils import DeployConfigParams, with_deploy_config
17
+
18
+ docker_cli = typer.Typer(
19
+ name="docker", help="Docker build and push utilities", no_args_is_help=True
20
+ )
21
+
22
+
23
+ class RegistryType(str, Enum):
24
+ DOCKERHUB = "dockerhub"
25
+ CUSTOM = "custom"
26
+
27
+
28
+ def _provide_error_hints(stderr: str, command: list[str], registry_info: dict = None):
29
+ """Provide helpful error hints only when Docker's message isn't clear enough."""
30
+ stderr_lower = stderr.lower()
31
+
32
+ # Only add hints for authentication errors where the solution isn't obvious
33
+ if _is_auth_error(stderr_lower) and "push" in " ".join(command):
34
+ _suggest_docker_login(registry_info)
35
+
36
+
37
+ def _is_auth_error(stderr_lower: str) -> bool:
38
+ """Check if the error is authentication related."""
39
+ auth_keywords = ["unauthorized", "access denied", "denied"]
40
+ return any(keyword in stderr_lower for keyword in auth_keywords)
41
+
42
+
43
+ def _suggest_docker_login(registry_info: dict = None):
44
+ """Suggest the appropriate docker login command."""
45
+ console.print("\n[yellow]💡 You need to authenticate with the registry[/yellow]")
46
+
47
+ if registry_info and registry_info.get("type") == "custom" and registry_info.get("url"):
48
+ console.print(f"[yellow] docker login {registry_info['url']}[/yellow]")
49
+ else:
50
+ console.print("[yellow] docker login[/yellow]")
51
+
52
+
53
+ def run_docker_command(
54
+ command: list[str], description: str, stream_output: bool = True, registry_info: dict = None
55
+ ) -> bool:
56
+ """Run a docker command and handle output/errors."""
57
+ try:
58
+ console.print(f"[dim]{description}...[/dim]")
59
+
60
+ if stream_output:
61
+ # Stream output in real-time for build commands
62
+ process = subprocess.Popen(
63
+ command,
64
+ stdout=subprocess.PIPE,
65
+ stderr=subprocess.STDOUT,
66
+ text=True,
67
+ bufsize=1,
68
+ universal_newlines=True,
69
+ )
70
+
71
+ # Stream output line by line
72
+ for line in process.stdout:
73
+ console.print(f"[dim]{line.rstrip()}[/dim]")
74
+
75
+ process.wait()
76
+ if process.returncode != 0:
77
+ raise subprocess.CalledProcessError(process.returncode, command)
78
+ else:
79
+ # Capture output for push commands (less verbose)
80
+ result = subprocess.run(command, capture_output=True, text=True, check=True)
81
+ if result.stdout.strip():
82
+ console.print(f"[dim]{result.stdout.strip()}[/dim]")
83
+
84
+ return True
85
+ except subprocess.CalledProcessError as e:
86
+ console.error(f"Docker command failed: {' '.join(command)}")
87
+ if e.stdout:
88
+ console.print(f"[red]stdout: {e.stdout}[/red]")
89
+ if e.stderr:
90
+ console.print(f"[red]stderr: {e.stderr}[/red]")
91
+
92
+ # Provide helpful hints for common errors
93
+ _provide_error_hints(e.stderr, command, registry_info)
94
+ return False
95
+ except FileNotFoundError:
96
+ console.error("Docker not found. Please ensure Docker is installed and in your PATH.")
97
+ return False
98
+
99
+
100
+ def _build_image_name(
101
+ registry_type: RegistryType, username: str, agent_name: str, registry_url: Optional[str] = None
102
+ ) -> str:
103
+ """Build the full image name based on registry type."""
104
+ if registry_type == RegistryType.DOCKERHUB:
105
+ return f"{username}/{agent_name}"
106
+ elif registry_type == RegistryType.CUSTOM:
107
+ if not registry_url:
108
+ raise ValueError("registry_url is required for custom registries")
109
+ return f"{registry_url}/{username}/{agent_name}"
110
+ else:
111
+ raise ValueError(f"Unsupported registry type: {registry_type}")
112
+
113
+
114
+ @docker_cli.command(name="build-push", help="Build, tag, and push Docker image")
115
+ @synchronizer.create_blocking
116
+ @with_deploy_config
117
+ async def build_push(
118
+ deploy_config=typer.Option(None, hidden=True),
119
+ agent_name: str = typer.Argument(
120
+ None, help="Name of the agent to build image for e.g. 'my-agent'", show_default=False
121
+ ),
122
+ registry: RegistryType = typer.Option(
123
+ None,
124
+ "--registry",
125
+ "-r",
126
+ help="Registry type to push to",
127
+ rich_help_panel="Registry Configuration",
128
+ ),
129
+ registry_username: str = typer.Option(
130
+ None,
131
+ "--username",
132
+ "-u",
133
+ help="Registry username",
134
+ rich_help_panel="Registry Configuration",
135
+ ),
136
+ registry_url: str = typer.Option(
137
+ None,
138
+ "--registry-url",
139
+ help="Custom registry URL (required for custom registries)",
140
+ rich_help_panel="Registry Configuration",
141
+ ),
142
+ version: str = typer.Option(
143
+ None,
144
+ "--version",
145
+ "-v",
146
+ help="Version tag for the image",
147
+ rich_help_panel="Build Configuration",
148
+ ),
149
+ no_push: bool = typer.Option(
150
+ False,
151
+ "--no-push",
152
+ help="Build and tag only, do not push to registry",
153
+ rich_help_panel="Build Configuration",
154
+ ),
155
+ no_latest: bool = typer.Option(
156
+ False,
157
+ "--no-latest",
158
+ help="Do not tag as 'latest'",
159
+ rich_help_panel="Build Configuration",
160
+ ),
161
+ ):
162
+ """Build, tag, and optionally push a Docker image for your agent."""
163
+
164
+ # Load values from deployment config file (if one exists)
165
+ partial_config = deploy_config or DeployConfigParams()
166
+ docker_config = getattr(partial_config, "docker_config", {})
167
+
168
+ # Parse image field to extract registry info (if present)
169
+ parsed_registry = None
170
+ parsed_username = None
171
+ parsed_agent_name = None
172
+ parsed_version = None
173
+ parsed_registry_url = None
174
+
175
+ if partial_config.image:
176
+ # Parse image like "markatdaily/voice-starter:0.5" or "registry.com/user/app:v1.0"
177
+ image_parts = partial_config.image.split("/")
178
+
179
+ if len(image_parts) == 2:
180
+ # Format: "username/repo:tag" (Docker Hub)
181
+ parsed_registry = "dockerhub"
182
+ parsed_username = image_parts[0]
183
+ repo_and_tag = image_parts[1]
184
+ elif len(image_parts) == 3:
185
+ # Format: "registry.com/username/repo:tag" (Custom registry)
186
+ parsed_registry = "custom"
187
+ parsed_registry_url = image_parts[0]
188
+ parsed_username = image_parts[1]
189
+ repo_and_tag = image_parts[2]
190
+ else:
191
+ # Just "repo:tag" - use for agent name only
192
+ repo_and_tag = partial_config.image
193
+
194
+ # Extract agent name and version from "repo:tag"
195
+ if ":" in repo_and_tag:
196
+ parsed_agent_name, parsed_version = repo_and_tag.split(":", 1)
197
+ else:
198
+ parsed_agent_name = repo_and_tag
199
+ parsed_version = "latest"
200
+
201
+ # Resolve final values with precedence: CLI args > parsed from image > docker config > defaults
202
+ final_agent_name = agent_name or parsed_agent_name or partial_config.agent_name
203
+ final_registry = registry or parsed_registry or docker_config.get("registry")
204
+ final_username = registry_username or parsed_username or docker_config.get("registry_username")
205
+ final_registry_url = registry_url or parsed_registry_url or docker_config.get("registry_url")
206
+ final_version = version or parsed_version or "latest"
207
+ final_no_latest = no_latest or not docker_config.get("auto_latest", True)
208
+
209
+ # Validate required parameters
210
+ if not final_agent_name:
211
+ console.error("Agent name is required. Provide via argument or pcc-deploy.toml")
212
+ return typer.Exit(1)
213
+
214
+ if not no_push:
215
+ if not final_registry:
216
+ console.error(
217
+ "Registry type is required when pushing. Use --registry or configure in pcc-deploy.toml"
218
+ )
219
+ return typer.Exit(1)
220
+
221
+ if not final_username:
222
+ console.error(
223
+ "Registry username is required when pushing. Use --username or configure in pcc-deploy.toml"
224
+ )
225
+ return typer.Exit(1)
226
+
227
+ if final_registry == RegistryType.CUSTOM and not final_registry_url:
228
+ console.error(
229
+ "Registry URL is required for custom registries. Use --registry-url or configure in pcc-deploy.toml"
230
+ )
231
+ return typer.Exit(1)
232
+
233
+ # Build image name
234
+ if no_push:
235
+ # For local builds, just use agent name
236
+ base_image_name = final_agent_name
237
+ else:
238
+ try:
239
+ base_image_name = _build_image_name(
240
+ RegistryType(final_registry), final_username, final_agent_name, final_registry_url
241
+ )
242
+ except ValueError as e:
243
+ console.error(str(e))
244
+ return typer.Exit(1)
245
+
246
+ # Prepare image tags
247
+ version_tag = f"{base_image_name}:{final_version}"
248
+ latest_tag = f"{base_image_name}:latest"
249
+
250
+ # Build Docker command
251
+ build_tags = ["-t", version_tag]
252
+ if not final_no_latest:
253
+ build_tags.extend(["-t", latest_tag])
254
+
255
+ build_command = [
256
+ "docker",
257
+ "buildx",
258
+ "build",
259
+ "--platform=linux/arm64",
260
+ "--load",
261
+ *build_tags,
262
+ ".",
263
+ ]
264
+
265
+ # Display build configuration
266
+ tags_display = [version_tag]
267
+ if not final_no_latest:
268
+ tags_display.append(latest_tag)
269
+
270
+ console.print(
271
+ Panel(
272
+ f"[bold white]Agent:[/bold white] [green]{final_agent_name}[/green]\n"
273
+ f"[bold white]Platform:[/bold white] [green]linux/arm64[/green]\n"
274
+ f"[bold white]Tags:[/bold white] [green]{', '.join(tags_display)}[/green]\n"
275
+ f"[bold white]Push:[/bold white] {'[red]No[/red]' if no_push else f'[green]Yes ({final_registry})[/green]'}",
276
+ title="Docker Build Configuration",
277
+ title_align="left",
278
+ border_style="yellow",
279
+ )
280
+ )
281
+
282
+ if not typer.confirm("Do you want to proceed with the build?", default=True):
283
+ console.cancel()
284
+ return typer.Exit()
285
+
286
+ # Execute build
287
+ console.print("\n[bold cyan]Building Docker image...[/bold cyan]")
288
+ if not run_docker_command(build_command, f"Building {final_agent_name}", stream_output=True):
289
+ return typer.Exit(1)
290
+
291
+ console.success(f"Successfully built image(s): {', '.join(tags_display)}")
292
+
293
+ # Push if requested
294
+ if not no_push:
295
+ console.print(f"\n[bold cyan]Pushing to {final_registry}...[/bold cyan]")
296
+
297
+ # Prepare registry info for better error messages
298
+ registry_info = {
299
+ "type": final_registry,
300
+ "url": final_registry_url if final_registry == "custom" else None,
301
+ }
302
+
303
+ # Push version tag
304
+ if not run_docker_command(
305
+ ["docker", "push", version_tag],
306
+ f"Pushing {version_tag}",
307
+ stream_output=False,
308
+ registry_info=registry_info,
309
+ ):
310
+ return typer.Exit(1)
311
+
312
+ # Push latest tag if created
313
+ if not final_no_latest:
314
+ if not run_docker_command(
315
+ ["docker", "push", latest_tag],
316
+ f"Pushing {latest_tag}",
317
+ stream_output=False,
318
+ registry_info=registry_info,
319
+ ):
320
+ return typer.Exit(1)
321
+
322
+ pushed_tags = [version_tag]
323
+ if not final_no_latest:
324
+ pushed_tags.append(latest_tag)
325
+
326
+ console.success(f"Successfully pushed: {', '.join(pushed_tags)}")
327
+ else:
328
+ console.print("\n[yellow]Skipping push (--no-push specified)[/yellow]")
329
+
330
+ # Display next steps
331
+ if not no_push:
332
+ console.print("\n[dim]Your image is now available at:[/dim]")
333
+ for tag in tags_display:
334
+ console.print(f" [bold]{tag}[/bold]")
335
+
336
+ console.print("\n[dim]To deploy this image, update your pcc-deploy.toml:[/dim]")
337
+ console.print(f' [bold]image = "{version_tag}"[/bold]')
338
+ console.print("\n[dim]Then run:[/dim]")
339
+ console.print(" [bold]pipecatcloud deploy[/bold]")
340
+
341
+
342
+ def create_docker_command(app: typer.Typer):
343
+ """Add docker command to the main CLI app."""
344
+ app.add_typer(docker_cli, rich_help_panel="Commands")
@@ -19,14 +19,14 @@ from pipecatcloud.config import config
19
19
  # ----- Init
20
20
 
21
21
  FILES_TO_EXTRACT = {
22
- "pipecat-cloud-starter-main/bot.py",
23
- "pipecat-cloud-starter-main/Dockerfile",
24
- "pipecat-cloud-starter-main/requirements.txt",
25
- "pipecat-cloud-starter-main/pcc-deploy.toml",
26
- "pipecat-cloud-starter-main/README.md",
27
- "pipecat-cloud-starter-main/.gitignore",
28
- "pipecat-cloud-starter-main/env.example",
29
- "pipecat-cloud-starter-main/local_runner.py",
22
+ "pipecat-quickstart-main/bot.py",
23
+ "pipecat-quickstart-main/Dockerfile",
24
+ "pipecat-quickstart-main/pcc-deploy.toml",
25
+ "pipecat-quickstart-main/README.md",
26
+ "pipecat-quickstart-main/.gitignore",
27
+ "pipecat-quickstart-main/env.example",
28
+ "pipecat-quickstart-main/pyproject.toml",
29
+ "pipecat-quickstart-main/uv.lock",
30
30
  }
31
31
 
32
32
 
@@ -13,6 +13,7 @@ from pipecatcloud._utils.console_utils import console
13
13
  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
+ from pipecatcloud.cli.commands.docker import create_docker_command
16
17
  from pipecatcloud.cli.commands.init import create_init_command
17
18
  from pipecatcloud.cli.commands.organizations import organization_cli
18
19
  from pipecatcloud.cli.commands.run import create_run_command
@@ -80,6 +81,7 @@ def cli(
80
81
  create_run_command(entrypoint_cli_typer)
81
82
  create_init_command(entrypoint_cli_typer)
82
83
  create_deploy_command(entrypoint_cli_typer)
84
+ create_docker_command(entrypoint_cli_typer)
83
85
  entrypoint_cli_typer.add_typer(auth_cli, rich_help_panel="Commands")
84
86
  entrypoint_cli_typer.add_typer(organization_cli, rich_help_panel="Commands")
85
87
  entrypoint_cli_typer.add_typer(secrets_cli, rich_help_panel="Commands")
@@ -19,7 +19,7 @@ _SETTINGS = {
19
19
  "api_host": _Setting("https://api.pipecat.daily.co"),
20
20
  "dashboard_host": _Setting("https://pipecat.daily.co"),
21
21
  "init_zip_url": _Setting(
22
- "https://github.com/daily-co/pipecat-cloud-starter/archive/refs/heads/main.zip"
22
+ "https://github.com/pipecat-ai/pipecat-quickstart/archive/refs/heads/main.zip"
23
23
  ),
24
24
  "onboarding_path": _Setting("/v1/onboarding"),
25
25
  "login_path": _Setting("/auth/login"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.3
3
+ Version: 0.2.4
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/
@@ -63,7 +63,7 @@ pcc auth login
63
63
 
64
64
  2. Login to your account `pcc auth login`
65
65
 
66
- 3. (Optional): Clone the starter agent [here](https://github.com/daily-co/pipecat-cloud-starter)
66
+ 3. (Optional): Clone the quickstart repo [here](https://github.com/pipecat-ai/pipecat-quickstart)
67
67
 
68
68
  4. Build your agent `docker build --platform linux/arm64 -t your-agent-name .`
69
69
 
@@ -2,13 +2,13 @@
2
2
  CHANGELOG.md
3
3
  LICENSE
4
4
  README.md
5
- dev-requirements.txt
6
5
  pipecat-cloud.png
7
6
  pyproject.toml
8
7
  pytest.ini
9
8
  .github/workflows/publish-pypi.yml
10
9
  .github/workflows/publish-test.yml
11
10
  .github/workflows/python-publish.yml
11
+ .github/workflows/tests.yml
12
12
  src/pipecatcloud/__init__.py
13
13
  src/pipecatcloud/__main__.py
14
14
  src/pipecatcloud/__version__.py
@@ -38,9 +38,11 @@ src/pipecatcloud/cli/commands/__init__.py
38
38
  src/pipecatcloud/cli/commands/agent.py
39
39
  src/pipecatcloud/cli/commands/auth.py
40
40
  src/pipecatcloud/cli/commands/deploy.py
41
+ src/pipecatcloud/cli/commands/docker.py
41
42
  src/pipecatcloud/cli/commands/init.py
42
43
  src/pipecatcloud/cli/commands/organizations.py
43
44
  src/pipecatcloud/cli/commands/run.py
44
45
  src/pipecatcloud/cli/commands/secrets.py
45
46
  tests/__init__.py
46
- tests/test_agent_sessions.py
47
+ tests/test_agent_sessions.py
48
+ tests/test_docker_commands.py
@@ -0,0 +1,169 @@
1
+ """
2
+ Unit tests for Docker build-push command.
3
+
4
+ Tests the core functionality: image parsing, registry detection, and error handling.
5
+ """
6
+
7
+ from unittest.mock import patch
8
+
9
+ import pytest
10
+
11
+ from src.pipecatcloud._utils.deploy_utils import DeployConfigParams
12
+ from src.pipecatcloud.cli.commands.docker import (
13
+ RegistryType,
14
+ _build_image_name,
15
+ _is_auth_error,
16
+ _suggest_docker_login,
17
+ )
18
+
19
+
20
+ class TestImageParsing:
21
+ """Test parsing of image names for different registry formats."""
22
+
23
+ def test_build_image_name_dockerhub(self):
24
+ """Test building Docker Hub image names."""
25
+ result = _build_image_name(RegistryType.DOCKERHUB, "my_username", "test-image")
26
+ assert result == "my_username/test-image"
27
+
28
+ def test_build_image_name_custom_registry(self):
29
+ """Test building custom registry image names."""
30
+ result = _build_image_name(RegistryType.CUSTOM, "myuser", "myapp", "gcr.io")
31
+ assert result == "gcr.io/myuser/myapp"
32
+
33
+ def test_build_image_name_custom_registry_missing_url(self):
34
+ """Test custom registry without URL raises error."""
35
+ with pytest.raises(ValueError, match="registry_url is required"):
36
+ _build_image_name(RegistryType.CUSTOM, "user", "app")
37
+
38
+
39
+ class TestConfigParsing:
40
+ """Test parsing configuration from deploy config."""
41
+
42
+ def test_parse_dockerhub_image(self):
43
+ """Test parsing Docker Hub format image."""
44
+ # Simulate parsing "my_username/test-image:0.5"
45
+ image = "my_username/test-image:0.5"
46
+ parts = image.split("/")
47
+
48
+ assert len(parts) == 2
49
+ assert parts[0] == "my_username" # username
50
+
51
+ repo_and_tag = parts[1]
52
+ repo, tag = repo_and_tag.split(":", 1)
53
+ assert repo == "test-image"
54
+ assert tag == "0.5"
55
+
56
+ def test_parse_custom_registry_image(self):
57
+ """Test parsing custom registry format image."""
58
+ # Simulate parsing "gcr.io/my-project/app:v1.0"
59
+ image = "gcr.io/my-project/app:v1.0"
60
+ parts = image.split("/")
61
+
62
+ assert len(parts) == 3
63
+ assert parts[0] == "gcr.io" # registry URL
64
+ assert parts[1] == "my-project" # username/project
65
+
66
+ repo_and_tag = parts[2]
67
+ repo, tag = repo_and_tag.split(":", 1)
68
+ assert repo == "app"
69
+ assert tag == "v1.0"
70
+
71
+ def test_parse_image_no_tag(self):
72
+ """Test parsing image without explicit tag."""
73
+ image = "my_username/test-image"
74
+ parts = image.split("/")
75
+
76
+ repo_and_tag = parts[1]
77
+ if ":" in repo_and_tag:
78
+ repo, tag = repo_and_tag.split(":", 1)
79
+ else:
80
+ repo = repo_and_tag
81
+ tag = "latest"
82
+
83
+ assert repo == "test-image"
84
+ assert tag == "latest"
85
+
86
+
87
+ class TestErrorHandling:
88
+ """Test error detection and hint generation."""
89
+
90
+ def test_is_auth_error_unauthorized(self):
91
+ """Test detection of unauthorized error."""
92
+ stderr = "Error: unauthorized: authentication required"
93
+ assert _is_auth_error(stderr.lower()) is True
94
+
95
+ def test_is_auth_error_access_denied(self):
96
+ """Test detection of access denied error."""
97
+ stderr = "Error: access denied to repository"
98
+ assert _is_auth_error(stderr.lower()) is True
99
+
100
+ def test_is_auth_error_denied(self):
101
+ """Test detection of denied error."""
102
+ stderr = "denied: requested access to the resource is denied"
103
+ assert _is_auth_error(stderr.lower()) is True
104
+
105
+ def test_is_auth_error_false(self):
106
+ """Test non-auth errors are not detected as auth errors."""
107
+ stderr = "Error: failed to build: syntax error in Dockerfile"
108
+ assert _is_auth_error(stderr.lower()) is False
109
+
110
+ @patch("src.pipecatcloud.cli.commands.docker.console")
111
+ def test_suggest_docker_login_dockerhub(self, mock_console):
112
+ """Test Docker Hub login suggestion."""
113
+ registry_info = {"type": "dockerhub"}
114
+ _suggest_docker_login(registry_info)
115
+
116
+ mock_console.print.assert_any_call(
117
+ "\n[yellow]💡 You need to authenticate with the registry[/yellow]"
118
+ )
119
+ mock_console.print.assert_any_call("[yellow] docker login[/yellow]")
120
+
121
+ @patch("src.pipecatcloud.cli.commands.docker.console")
122
+ def test_suggest_docker_login_custom_registry(self, mock_console):
123
+ """Test custom registry login suggestion."""
124
+ registry_info = {"type": "custom", "url": "gcr.io"}
125
+ _suggest_docker_login(registry_info)
126
+
127
+ mock_console.print.assert_any_call(
128
+ "\n[yellow]💡 You need to authenticate with the registry[/yellow]"
129
+ )
130
+ mock_console.print.assert_any_call("[yellow] docker login gcr.io[/yellow]")
131
+
132
+ @patch("src.pipecatcloud.cli.commands.docker.console")
133
+ def test_suggest_docker_login_no_registry_info(self, mock_console):
134
+ """Test login suggestion with no registry info."""
135
+ _suggest_docker_login(None)
136
+
137
+ mock_console.print.assert_any_call(
138
+ "\n[yellow]💡 You need to authenticate with the registry[/yellow]"
139
+ )
140
+ mock_console.print.assert_any_call("[yellow] docker login[/yellow]")
141
+
142
+
143
+ class TestDeployConfigIntegration:
144
+ """Test integration with deploy config system."""
145
+
146
+ def test_deploy_config_with_docker_section(self):
147
+ """Test deploy config parses docker section correctly."""
148
+ config = DeployConfigParams(
149
+ agent_name="test-agent",
150
+ image="my_username/test:1.0",
151
+ docker_config={
152
+ "registry": "dockerhub",
153
+ "registry_username": "my_username",
154
+ "auto_latest": False,
155
+ },
156
+ )
157
+
158
+ assert config.agent_name == "test-agent"
159
+ assert config.image == "my_username/test:1.0"
160
+ assert config.docker_config["registry"] == "dockerhub"
161
+ assert config.docker_config["auto_latest"] is False
162
+
163
+ def test_deploy_config_minimal(self):
164
+ """Test minimal deploy config works."""
165
+ config = DeployConfigParams(agent_name="test-agent", image="my_username/test:1.0")
166
+
167
+ assert config.agent_name == "test-agent"
168
+ assert config.image == "my_username/test:1.0"
169
+ assert config.docker_config == {} # Empty dict by default
@@ -1,66 +0,0 @@
1
- name: Publish to PyPI
2
-
3
- on:
4
- workflow_dispatch:
5
- inputs:
6
- version:
7
- description: 'Version to publish (must match pyproject.toml, e.g. 0.0.4)'
8
- required: true
9
- type: string
10
-
11
- jobs:
12
- build-and-publish:
13
- runs-on: ubuntu-latest
14
- environment:
15
- name: pypi
16
- url: https://pypi.org/p/pipecat-cloud
17
- permissions:
18
- id-token: write # IMPORTANT: this permission is needed for trusted publishing
19
- contents: write # Needed for tagging
20
-
21
- steps:
22
- - name: Checkout code
23
- uses: actions/checkout@v4
24
-
25
- - name: Set up Python
26
- uses: actions/setup-python@v4
27
- with:
28
- python-version: '3.10'
29
-
30
- - name: Verify version matches
31
- run: |
32
- pip install tomli
33
- PROJECT_VERSION=$(python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])")
34
- if [ "$PROJECT_VERSION" != "${{ github.event.inputs.version }}" ]; then
35
- echo "Error: Input version (${{ github.event.inputs.version }}) does not match pyproject.toml version ($PROJECT_VERSION)"
36
- exit 1
37
- fi
38
-
39
- - name: Install dependencies
40
- run: |
41
- python -m pip install --upgrade pip
42
- pip install -r dev-requirements.txt
43
- pip install build twine
44
-
45
- - name: Build package
46
- run: python -m build
47
-
48
- - name: Check and create release tag if needed
49
- run: |
50
- git config --local user.email "action@github.com"
51
- git config --local user.name "GitHub Action"
52
-
53
- # Check if tag already exists remotely
54
- if git ls-remote --tags origin | grep -q "refs/tags/v${{ github.event.inputs.version }}"; then
55
- echo "Tag v${{ github.event.inputs.version }} already exists, skipping tag creation"
56
- else
57
- echo "Creating new tag v${{ github.event.inputs.version }}"
58
- git tag -a "v${{ github.event.inputs.version }}" -m "Version ${{ github.event.inputs.version }}"
59
- git push origin "v${{ github.event.inputs.version }}"
60
- fi
61
-
62
- - name: Publish to PyPI
63
- uses: pypa/gh-action-pypi-publish@release/v1
64
- with:
65
- verbose: true
66
- print-hash: true
@@ -1,67 +0,0 @@
1
- name: Publish to TEST PyPI
2
-
3
- on:
4
- workflow_dispatch:
5
- inputs:
6
- version:
7
- description: 'Version to publish (must match pyproject.toml, e.g. 0.0.4)'
8
- required: true
9
- type: string
10
-
11
- jobs:
12
- build-and-publish:
13
- runs-on: ubuntu-latest
14
- environment:
15
- name: pypi
16
- url: https://test.pypi.org/project/pipecat-cloud
17
- permissions:
18
- id-token: write
19
- contents: write
20
-
21
- steps:
22
- - name: Checkout code
23
- uses: actions/checkout@v4
24
-
25
- - name: Set up Python
26
- uses: actions/setup-python@v4
27
- with:
28
- python-version: '3.10'
29
-
30
- - name: Verify version matches
31
- run: |
32
- pip install tomli
33
- PROJECT_VERSION=$(python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])")
34
- if [ "$PROJECT_VERSION" != "${{ github.event.inputs.version }}" ]; then
35
- echo "Error: Input version (${{ github.event.inputs.version }}) does not match pyproject.toml version ($PROJECT_VERSION)"
36
- exit 1
37
- fi
38
-
39
- - name: Install dependencies
40
- run: |
41
- python -m pip install --upgrade pip
42
- pip install -r dev-requirements.txt
43
- pip install build twine
44
-
45
- - name: Build package
46
- run: python -m build
47
-
48
- - name: Check and create release tag if needed
49
- run: |
50
- git config --local user.email "action@github.com"
51
- git config --local user.name "GitHub Action"
52
-
53
- # Check if tag already exists remotely
54
- if git ls-remote --tags origin | grep -q "refs/tags/v${{ github.event.inputs.version }}"; then
55
- echo "Tag v${{ github.event.inputs.version }} already exists, skipping tag creation"
56
- else
57
- echo "Creating new tag v${{ github.event.inputs.version }}"
58
- git tag -a "v${{ github.event.inputs.version }}" -m "Version ${{ github.event.inputs.version }}"
59
- git push origin "v${{ github.event.inputs.version }}"
60
- fi
61
-
62
- - name: Publish to PyPI
63
- uses: pypa/gh-action-pypi-publish@release/v1
64
- with:
65
- repository-url: https://test.pypi.org/legacy/
66
- verbose: true
67
- print-hash: true
@@ -1,8 +0,0 @@
1
- build~=1.2.2
2
- grpcio-tools~=1.67.1
3
- pip-tools~=7.4.1
4
- pyright~=1.1.402
5
- pytest~=8.4.1
6
- ruff~=0.12.1
7
- setuptools~=78.1.1
8
- setuptools_scm~=8.3.1
File without changes
File without changes
File without changes
File without changes