pipecatcloud 1.3.0__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.github/workflows/publish-pypi.yml +39 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/CHANGELOG.md +50 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/CLAUDE.md +2 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/PKG-INFO +2 -2
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/pyproject.toml +1 -1
- pipecatcloud-1.4.0/scripts/release-changelog.py +113 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/api.py +29 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/github.py +142 -39
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/config.py +1 -2
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud.egg-info/PKG-INFO +2 -2
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud.egg-info/SOURCES.txt +1 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud.egg-info/requires.txt +1 -1
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud.egg-info/scm_file_list.json +1 -0
- pipecatcloud-1.4.0/src/pipecatcloud.egg-info/scm_version.json +8 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_api_client.py +68 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_github_integration.py +346 -6
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/uv.lock +460 -340
- pipecatcloud-1.3.0/src/pipecatcloud.egg-info/scm_version.json +0 -8
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.claude/settings.json +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.github/workflows/format.yml +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.github/workflows/python-compatibility.yml +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.github/workflows/tests.yml +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.github/workflows/update-docs.yml +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.gitignore +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/.pre-commit-config.yaml +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/LICENSE +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/README.md +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/pipecat-cloud.png +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/pytest.ini +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/scripts/fix-ruff.sh +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/setup.cfg +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/__init__.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/__version__.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/build_utils.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/console_utils.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/github_utils.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/_utils/regions.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/agent.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/agent.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/agent_profiles.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/build.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/deploy.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/docker.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/regions.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/registry_keys.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/secrets.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/commands/spend_limit.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/config.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/cli/entry_point.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/constants.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/exception.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/py.typed +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/session.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/src/pipecatcloud.egg-info/top_level.txt +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/__init__.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/conftest.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_agent_args.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_agent_profiles_and_resources.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_agent_sessions.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_agent_status_output.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_agent_stop.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_auth.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_build_id_display.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_build_utils.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_config_file.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_deploy_architecture.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_deploy_image_pull_secret.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_deploy_poll.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_deploy_referenced_secret_set.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_deployment_status.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_docker_commands.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_exit_codes.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_json_output.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_krisp_viva.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_max_session_duration.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_organizations.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_output_modes.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_pat_auth.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_regions.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_regions_lifecycle.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_secrets_reference.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_secrets_set_referenced.py +0 -0
- {pipecatcloud-1.3.0 → pipecatcloud-1.4.0}/tests/test_spend_limit.py +0 -0
|
@@ -78,3 +78,42 @@ jobs:
|
|
|
78
78
|
verbose: true
|
|
79
79
|
print-hash: true
|
|
80
80
|
repository-url: https://test.pypi.org/legacy/
|
|
81
|
+
|
|
82
|
+
# Runs once the package is installable from PyPI, so the release notification
|
|
83
|
+
# never points at a version nobody can install yet. Notes come from the
|
|
84
|
+
# CHANGELOG.md section at the tag: the changelog is the single source of
|
|
85
|
+
# release notes, reformatted for GitHub by scripts/release-changelog.py. A tag
|
|
86
|
+
# whose version has no changelog section fails the run.
|
|
87
|
+
github-release:
|
|
88
|
+
name: 'Create GitHub Release'
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
needs: [publish-to-pypi]
|
|
91
|
+
permissions:
|
|
92
|
+
contents: write
|
|
93
|
+
steps:
|
|
94
|
+
- name: Checkout repo
|
|
95
|
+
uses: actions/checkout@v4
|
|
96
|
+
with:
|
|
97
|
+
ref: ${{ github.event.inputs.gitref }}
|
|
98
|
+
|
|
99
|
+
- name: Set up Python
|
|
100
|
+
uses: actions/setup-python@v5
|
|
101
|
+
with:
|
|
102
|
+
python-version: '3.12'
|
|
103
|
+
|
|
104
|
+
- name: Generate release notes
|
|
105
|
+
env:
|
|
106
|
+
GITREF: ${{ github.event.inputs.gitref }}
|
|
107
|
+
run: |
|
|
108
|
+
python scripts/release-changelog.py --no-heading "${GITREF#v}" > release-notes.md
|
|
109
|
+
|
|
110
|
+
- name: Publish release
|
|
111
|
+
env:
|
|
112
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
113
|
+
GITREF: ${{ github.event.inputs.gitref }}
|
|
114
|
+
run: |
|
|
115
|
+
if gh release view "$GITREF" >/dev/null 2>&1; then
|
|
116
|
+
gh release edit "$GITREF" --notes-file release-notes.md
|
|
117
|
+
else
|
|
118
|
+
gh release create "$GITREF" --title "$GITREF" --notes-file release-notes.md
|
|
119
|
+
fi
|
|
@@ -5,6 +5,56 @@ All notable changes to **Pipecat Cloud** will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.0] - 2026-10-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `pipecat cloud github connect --existing` links a GitHub App installation
|
|
13
|
+
that is already on GitHub to your Pipecat Cloud organization. Use it for
|
|
14
|
+
an installation you installed or had an owner approve, while still a
|
|
15
|
+
member of the GitHub organization it is on, or one on your personal
|
|
16
|
+
account or a GitHub organization you own. That covers an install request
|
|
17
|
+
an owner approved and an install made from GitHub, neither of which linked
|
|
18
|
+
before. You authorize the App on GitHub, then confirm the installation to
|
|
19
|
+
link on the Pipecat Cloud dashboard, and the CLI waits up to 14 minutes
|
|
20
|
+
for it. `github status` and the `github connect` timeout message point to
|
|
21
|
+
it.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Connecting an organization to GitHub now finishes on the Pipecat Cloud
|
|
26
|
+
dashboard, where you may need to sign in as the same Pipecat Cloud user the
|
|
27
|
+
CLI is logged in as. GitHub also asks you to authorize the App after you
|
|
28
|
+
install it. This happens on the server side, so it applies to every CLI
|
|
29
|
+
version. `pipecat cloud github connect` now says so, also with `--json`
|
|
30
|
+
(on stderr), and waits up to 26 minutes for the connection instead of 15,
|
|
31
|
+
to cover both steps. Older versions stop waiting after 15 minutes; the
|
|
32
|
+
connection still completes, and `pipecat cloud github status` shows it.
|
|
33
|
+
- `pipecat cloud github connect` stops waiting as soon as Pipecat Cloud
|
|
34
|
+
refuses the CLI's sign-in or its access to the organization, instead of
|
|
35
|
+
waiting until it times out.
|
|
36
|
+
- The `aiohttp` minimum version is now `3.14.4`, the first release with fixes
|
|
37
|
+
for several security advisories. The SDK makes its API requests with
|
|
38
|
+
`aiohttp`, and `pipecat cloud auth login` uses it for the local server that
|
|
39
|
+
receives the sign-in callback.
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- The `login_path` and `login_status_path` settings and their
|
|
44
|
+
`PIPECAT_LOGIN_PATH` and `PIPECAT_LOGIN_STATUS_PATH` overrides. They
|
|
45
|
+
pointed at the device-code sign-in that 0.4.1 replaced with OAuth and
|
|
46
|
+
PKCE, and nothing has read them since, so setting them had no effect.
|
|
47
|
+
Removing them is not a breaking change: the only difference is that they
|
|
48
|
+
no longer appear in `--show-cli-config` output. Pipecat Cloud is retiring
|
|
49
|
+
that sign-in, so a CLI older than 0.4.1 has to be upgraded to log in.
|
|
50
|
+
`pipecat cloud auth login` is unchanged.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- `pipecat cloud github connect` says it could not reach Pipecat Cloud when
|
|
55
|
+
no usable response comes back, such as on a dropped connection, instead
|
|
56
|
+
of saying the API returned no URL.
|
|
57
|
+
|
|
8
58
|
## [1.3.0] - 2026-10-01
|
|
9
59
|
|
|
10
60
|
### Added
|
|
@@ -110,6 +110,8 @@ pcc-deploy.toml Example deployment config consumed by `pipecat cloud de
|
|
|
110
110
|
2. Tag the commit with `v<MAJOR>.<MINOR>.<PATCH>`.
|
|
111
111
|
3. Trigger `publish-pypi.yml` manually with that tag. `setuptools_scm` picks up the version from the tag.
|
|
112
112
|
|
|
113
|
+
After publishing to PyPI, the workflow creates (or updates) the GitHub Release for the tag. Its notes are the tag's `CHANGELOG.md` section, reformatted by `scripts/release-changelog.py`, so fix release-note wording in the changelog rather than on GitHub: a re-run overwrites edits made there. Preview the notes locally with `python scripts/release-changelog.py --no-heading 1.3.0`.
|
|
114
|
+
|
|
113
115
|
## Writing style for generated content in this repo
|
|
114
116
|
|
|
115
117
|
- No emdashes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Cloud hosting for Pipecat AI applications
|
|
5
5
|
License: BSD 2-Clause License
|
|
6
6
|
Project-URL: Website, https://docs.pipecat.ai/pipecat-cloud
|
|
@@ -8,7 +8,7 @@ Keywords: pipecat,pipecat-ai,voice,ai,llm
|
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: aiohttp<4.0.0,>=3.
|
|
11
|
+
Requires-Dist: aiohttp<4.0.0,>=3.14.4
|
|
12
12
|
Requires-Dist: fastapi<1.0.0,>=0.115.6
|
|
13
13
|
Requires-Dist: loguru~=0.7.3
|
|
14
14
|
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
|
|
@@ -11,7 +11,7 @@ license = { text = "BSD 2-Clause License" }
|
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
keywords = ["pipecat", "pipecat-ai", "voice", "ai", "llm"]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"aiohttp>=3.
|
|
14
|
+
"aiohttp>=3.14.4,<4.0.0",
|
|
15
15
|
"fastapi>=0.115.6,<1.0.0",
|
|
16
16
|
"loguru~=0.7.3",
|
|
17
17
|
"python-dotenv>=1.0.1,<2.0.0",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate release notes from CHANGELOG.md for use in GitHub Releases (or anywhere).
|
|
3
|
+
|
|
4
|
+
Usage::
|
|
5
|
+
|
|
6
|
+
python scripts/release-changelog.py VERSION
|
|
7
|
+
python scripts/release-changelog.py 1.1.0
|
|
8
|
+
python scripts/release-changelog.py --file path/to/CHANGELOG.md 1.1.0
|
|
9
|
+
python scripts/release-changelog.py --no-heading 1.1.0
|
|
10
|
+
|
|
11
|
+
Extracts the requested ``## [VERSION]`` section (heading and ``### …``
|
|
12
|
+
subheadings included) and prints it to stdout. The only transformation
|
|
13
|
+
applied is collapsing each entry paragraph onto a single line, so it is
|
|
14
|
+
suitable for pasting into release notes that don't need 80-column wrapping.
|
|
15
|
+
``(PR [...])`` references stay on their own two-space-indented continuation
|
|
16
|
+
line. The input file is never modified.
|
|
17
|
+
|
|
18
|
+
``--no-heading`` drops the ``## [VERSION]`` line, leaving the body to start
|
|
19
|
+
at the first ``### …`` subheading. GitHub Releases carry the version in the
|
|
20
|
+
release title, so the heading would be redundant there.
|
|
21
|
+
|
|
22
|
+
Every paragraph is unfilled. Indentation is preserved — each logical line
|
|
23
|
+
(bullets, sub-bullets, and the ``(PR [...])`` continuation) keeps its
|
|
24
|
+
leading whitespace; only the wrapped continuation lines that follow them
|
|
25
|
+
get joined back. Code-block paragraphs (triple-backtick fences, or deeply
|
|
26
|
+
indented blocks with no list markers around) are passed through untouched.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
DEFAULT_CHANGELOG = Path(__file__).resolve().parent.parent / "CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
PARAGRAPH_SPLIT_RE = re.compile(r"(\n[ \t]*\n+)")
|
|
39
|
+
CONTINUATION_RE = re.compile(r"\n(?![ \t]*[-*+] )[ \t]+")
|
|
40
|
+
PR_RE = re.compile(r" \(PR \[")
|
|
41
|
+
CODE_FENCE_RE = re.compile(r"^\s*```")
|
|
42
|
+
DEEP_INDENT_RE = re.compile(r"^ {4,}")
|
|
43
|
+
LIST_MARKER_RE = re.compile(r"^[ \t]*[-*+] ")
|
|
44
|
+
SECTION_HEADING_RE = re.compile(r"^## \[", re.MULTILINE)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_code_block_paragraph(paragraph: str) -> bool:
|
|
48
|
+
lines = paragraph.splitlines()
|
|
49
|
+
if any(CODE_FENCE_RE.match(line) for line in lines):
|
|
50
|
+
return True
|
|
51
|
+
# In a list paragraph, 4-space indent is a sub-bullet continuation, not
|
|
52
|
+
# code. Only treat deep-indented lines as code when no bullets are around
|
|
53
|
+
# to claim them.
|
|
54
|
+
if any(LIST_MARKER_RE.match(line) for line in lines):
|
|
55
|
+
return False
|
|
56
|
+
return any(DEEP_INDENT_RE.match(line) for line in lines)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def unfill_entry(paragraph: str) -> str:
|
|
60
|
+
joined = CONTINUATION_RE.sub(" ", paragraph)
|
|
61
|
+
return PR_RE.sub("\n (PR [", joined, count=1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def process(text: str) -> str:
|
|
65
|
+
parts = PARAGRAPH_SPLIT_RE.split(text)
|
|
66
|
+
for i in range(0, len(parts), 2):
|
|
67
|
+
para = parts[i]
|
|
68
|
+
if para and not is_code_block_paragraph(para):
|
|
69
|
+
parts[i] = unfill_entry(para)
|
|
70
|
+
return "".join(parts)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def extract_section(text: str, version: str) -> str:
|
|
74
|
+
head_re = re.compile(rf"^## \[{re.escape(version)}\]", re.MULTILINE)
|
|
75
|
+
m = head_re.search(text)
|
|
76
|
+
if not m:
|
|
77
|
+
sys.exit(f"error: version {version!r} not found")
|
|
78
|
+
nxt = SECTION_HEADING_RE.search(text, m.end())
|
|
79
|
+
end = nxt.start() if nxt else len(text)
|
|
80
|
+
return text[m.start() : end]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def strip_heading(section: str) -> str:
|
|
84
|
+
_, _, body = section.partition("\n")
|
|
85
|
+
return body.lstrip("\n")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> None:
|
|
89
|
+
summary = (__doc__ or "").splitlines()[0]
|
|
90
|
+
parser = argparse.ArgumentParser(description=summary)
|
|
91
|
+
parser.add_argument("version", help="Version to extract (e.g. 1.1.0).")
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
"--file",
|
|
94
|
+
type=Path,
|
|
95
|
+
default=DEFAULT_CHANGELOG,
|
|
96
|
+
help=f"Path to the changelog (default: {DEFAULT_CHANGELOG}).",
|
|
97
|
+
)
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"--no-heading",
|
|
100
|
+
action="store_true",
|
|
101
|
+
help="Omit the '## [VERSION]' heading line.",
|
|
102
|
+
)
|
|
103
|
+
args = parser.parse_args()
|
|
104
|
+
|
|
105
|
+
text = args.file.read_text()
|
|
106
|
+
section = extract_section(text, args.version)
|
|
107
|
+
if args.no_heading:
|
|
108
|
+
section = strip_heading(section)
|
|
109
|
+
sys.stdout.write(process(section))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
main()
|
|
@@ -37,6 +37,12 @@ class _API:
|
|
|
37
37
|
def __init__(self, token: str | None = None, is_cli: bool = False):
|
|
38
38
|
self.token = token
|
|
39
39
|
self.error = None
|
|
40
|
+
# The HTTP status of the last failed response, beside `error`, which
|
|
41
|
+
# is its body. A body's `code` is the API's own error code, which is
|
|
42
|
+
# not always the status (a 403 for someone outside the organization
|
|
43
|
+
# carries a code of its own), so a caller that decides on the status
|
|
44
|
+
# reads it here. None when the request got no response at all.
|
|
45
|
+
self.error_status: int | None = None
|
|
40
46
|
self.bubble_next = False
|
|
41
47
|
self.is_cli = is_cli
|
|
42
48
|
|
|
@@ -149,6 +155,7 @@ class _API:
|
|
|
149
155
|
if self.is_cli and not_found_is_empty and response.status == 404:
|
|
150
156
|
return None
|
|
151
157
|
|
|
158
|
+
self.error_status = response.status
|
|
152
159
|
# Extract PCC error code, where applicable
|
|
153
160
|
try:
|
|
154
161
|
# Try to parse the error as JSON
|
|
@@ -180,6 +187,7 @@ class _API:
|
|
|
180
187
|
@wraps(method_func)
|
|
181
188
|
async def wrapper(*args, live=None, **kwargs):
|
|
182
189
|
self.error = None
|
|
190
|
+
self.error_status = None
|
|
183
191
|
try:
|
|
184
192
|
result = await method_func(*args, **kwargs)
|
|
185
193
|
self.bubble_next = False
|
|
@@ -1120,6 +1128,27 @@ class _API:
|
|
|
1120
1128
|
"""
|
|
1121
1129
|
return self.create_api_method(self._github_install_url)
|
|
1122
1130
|
|
|
1131
|
+
async def _github_link_url(self, org: str) -> dict | None:
|
|
1132
|
+
"""Mint the GitHub URL that links an installation already on GitHub."""
|
|
1133
|
+
url = self.construct_api_url("github_link_url_path").format(org=org)
|
|
1134
|
+
return await self._base_request("GET", url)
|
|
1135
|
+
|
|
1136
|
+
@property
|
|
1137
|
+
def github_link_url(self):
|
|
1138
|
+
"""Get the GitHub URL for linking an existing installation.
|
|
1139
|
+
|
|
1140
|
+
The URL asks the user to authorize the App on GitHub, with no install
|
|
1141
|
+
step. The dashboard then offers the installations GitHub confirms they
|
|
1142
|
+
installed or asked for, to link to this org. Like the install URL, it
|
|
1143
|
+
carries a single-use flow, so it must be fetched fresh every time.
|
|
1144
|
+
|
|
1145
|
+
Args:
|
|
1146
|
+
org: Organization ID
|
|
1147
|
+
Returns:
|
|
1148
|
+
Dict with `url`
|
|
1149
|
+
"""
|
|
1150
|
+
return self.create_api_method(self._github_link_url)
|
|
1151
|
+
|
|
1123
1152
|
async def _github_installation(self, org: str) -> dict | None:
|
|
1124
1153
|
"""The GitHub installation linked to this org, or None when unlinked."""
|
|
1125
1154
|
url = self.construct_api_url("github_installation_path").format(org=org)
|
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
"""Org-scoped GitHub App commands (PCC-933).
|
|
8
8
|
|
|
9
|
-
The connect flow deliberately has no local callback server
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
The connect flow deliberately has no local callback server. GitHub sends the
|
|
10
|
+
browser on to the Pipecat Cloud dashboard, which links the installation once
|
|
11
|
+
GitHub confirms that the signed-in user may: they installed the App or asked
|
|
12
|
+
for it, while a member of the GitHub organization it is on, or they own the
|
|
13
|
+
GitHub account it is on. The terminal only has to open a URL and poll until
|
|
14
|
+
the link shows up. That is the same flow the dashboard's own Connect button
|
|
15
|
+
runs.
|
|
13
16
|
"""
|
|
14
17
|
|
|
15
18
|
import asyncio
|
|
@@ -34,11 +37,24 @@ github_cli = typer.Typer(
|
|
|
34
37
|
name="github", help="GitHub App connection management", no_args_is_help=True
|
|
35
38
|
)
|
|
36
39
|
|
|
37
|
-
# Poll cadence while waiting for the
|
|
38
|
-
# server
|
|
39
|
-
# that can still complete, and later
|
|
40
|
+
# Poll cadence while waiting for the link to land. Each timeout is how long the
|
|
41
|
+
# server keeps that flow alive, plus a minute for the link itself to commit:
|
|
42
|
+
# giving up sooner would abandon a connect that can still complete, and later
|
|
43
|
+
# would wait on a flow that has expired. An install's signed state lasts 15
|
|
44
|
+
# minutes, and redeeming it starts GitHub's authorization step, which lasts 10
|
|
45
|
+
# more. Linking an existing installation is the authorization step (10
|
|
46
|
+
# minutes), then a confirmation on the dashboard (3).
|
|
40
47
|
_POLL_INTERVAL_SECONDS = 2.5
|
|
41
|
-
|
|
48
|
+
# Answers no later poll can change, by status, and how many in a row it takes
|
|
49
|
+
# to believe one: this CLI's sign-in is no longer valid (401), or its user may
|
|
50
|
+
# no longer see the organization (403). A 401 can also follow a token refresh
|
|
51
|
+
# that failed on a network blip, which the next poll tries again, so it takes
|
|
52
|
+
# two. Anything else, a dropped connection or a 5xx, is polled through. "Not
|
|
53
|
+
# linked yet" is no error at all: the API's 404 comes back as no installation.
|
|
54
|
+
_POLL_REFUSALS_TO_GIVE_UP = {401: 2, 403: 1}
|
|
55
|
+
_COMMIT_GRACE_SECONDS = 60
|
|
56
|
+
_INSTALL_TIMEOUT_SECONDS = (15 + 10) * 60 + _COMMIT_GRACE_SECONDS
|
|
57
|
+
_LINK_TIMEOUT_SECONDS = (10 + 3) * 60 + _COMMIT_GRACE_SECONDS
|
|
42
58
|
|
|
43
59
|
|
|
44
60
|
def _installation_rows(installation: dict) -> list[tuple[str, str]]:
|
|
@@ -65,42 +81,90 @@ def _print_installation(installation: dict) -> None:
|
|
|
65
81
|
console.print(table)
|
|
66
82
|
|
|
67
83
|
|
|
84
|
+
def _report_already_connected(org: str | None, installation: dict) -> None:
|
|
85
|
+
if console.json_output:
|
|
86
|
+
console.output_json({"installation": installation, "connected": True})
|
|
87
|
+
return
|
|
88
|
+
console.print(
|
|
89
|
+
f"[yellow]Organization '{org}' is already connected to GitHub "
|
|
90
|
+
f"({installation.get('githubAccountLogin')}).[/yellow]\n"
|
|
91
|
+
"[dim]Change which repositories the App can see from GitHub, or run "
|
|
92
|
+
f"[bold]{PIPECAT_CLI_NAME} github disconnect[/bold] first to connect a "
|
|
93
|
+
"different account.[/dim]"
|
|
94
|
+
)
|
|
95
|
+
_print_installation(installation)
|
|
96
|
+
|
|
97
|
+
|
|
68
98
|
@github_cli.command(
|
|
69
99
|
name="connect",
|
|
70
|
-
|
|
100
|
+
short_help="Connect this organization to GitHub",
|
|
101
|
+
help=(
|
|
102
|
+
"Connect this organization to GitHub by installing the Pipecat Cloud App, "
|
|
103
|
+
"or with --existing, by linking an installation already on GitHub"
|
|
104
|
+
),
|
|
71
105
|
)
|
|
72
106
|
@synchronizer.create_blocking
|
|
73
107
|
@requires_login
|
|
74
108
|
async def connect(
|
|
75
109
|
organization: str = typer.Option(None, "--organization", "-o"),
|
|
110
|
+
existing: bool = typer.Option(
|
|
111
|
+
False,
|
|
112
|
+
"--existing",
|
|
113
|
+
help=(
|
|
114
|
+
"Link an installation already on GitHub: one you installed or had "
|
|
115
|
+
"an owner approve, while still a member of the GitHub organization "
|
|
116
|
+
"it is on, or one on your personal account or a GitHub "
|
|
117
|
+
"organization you own"
|
|
118
|
+
),
|
|
119
|
+
),
|
|
76
120
|
):
|
|
77
121
|
org = organization or config.get("org")
|
|
78
122
|
|
|
79
|
-
|
|
123
|
+
linked, error = await API.github_installation(org=org)
|
|
80
124
|
if error:
|
|
81
125
|
raise typer.Exit(1)
|
|
82
|
-
if
|
|
83
|
-
|
|
84
|
-
console.output_json({"installation": existing, "connected": True})
|
|
85
|
-
return
|
|
86
|
-
console.print(
|
|
87
|
-
f"[yellow]Organization '{org}' is already connected to GitHub "
|
|
88
|
-
f"({existing.get('githubAccountLogin')}).[/yellow]\n"
|
|
89
|
-
"[dim]Change which repositories the App can see from GitHub, or run "
|
|
90
|
-
f"[bold]{PIPECAT_CLI_NAME} github disconnect[/bold] first to connect a "
|
|
91
|
-
"different account.[/dim]"
|
|
92
|
-
)
|
|
93
|
-
_print_installation(existing)
|
|
126
|
+
if linked:
|
|
127
|
+
_report_already_connected(org, linked)
|
|
94
128
|
return
|
|
95
129
|
|
|
96
|
-
# Fetched per attempt, never cached: the URL carries a single-use
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
130
|
+
# Fetched per attempt, never cached: the URL carries a single-use flow that
|
|
131
|
+
# the dashboard redeems.
|
|
132
|
+
if existing:
|
|
133
|
+
# bubble_error: another member can link the org between the check above
|
|
134
|
+
# and this call, and that is the same answer the check gives.
|
|
135
|
+
data, error = await API.bubble_error().github_link_url(org=org)
|
|
136
|
+
if isinstance(error, dict) and error.get("reason") == "org_has_one":
|
|
137
|
+
linked, error = await API.github_installation(org=org)
|
|
138
|
+
if linked:
|
|
139
|
+
_report_already_connected(org, linked)
|
|
140
|
+
return
|
|
141
|
+
if not error:
|
|
142
|
+
console.error(
|
|
143
|
+
f"The GitHub connection for organization '{org}' changed while this "
|
|
144
|
+
f"ran. Run [bold]{PIPECAT_CLI_NAME} github connect --existing[/bold] again."
|
|
145
|
+
)
|
|
146
|
+
raise typer.Exit(1)
|
|
147
|
+
if error:
|
|
148
|
+
API.print_error()
|
|
149
|
+
raise typer.Exit(1)
|
|
150
|
+
verb, timeout = "authorize", _LINK_TIMEOUT_SECONDS
|
|
151
|
+
else:
|
|
152
|
+
data, error = await API.github_install_url(org=org)
|
|
153
|
+
if error:
|
|
154
|
+
raise typer.Exit(1)
|
|
155
|
+
verb, timeout = "install", _INSTALL_TIMEOUT_SECONDS
|
|
156
|
+
if data is None:
|
|
157
|
+
# No error and no body: no usable response came back (a dropped
|
|
158
|
+
# connection, a DNS or TLS failure, or a non-JSON page from a proxy),
|
|
159
|
+
# which the API client reports as nothing.
|
|
160
|
+
console.error(
|
|
161
|
+
f"Could not reach Pipecat Cloud to {verb} the GitHub App. Check your "
|
|
162
|
+
"connection and try again."
|
|
163
|
+
)
|
|
100
164
|
raise typer.Exit(1)
|
|
101
|
-
|
|
102
|
-
if not
|
|
103
|
-
console.error("The API did not return a GitHub
|
|
165
|
+
flow_url = data.get("url")
|
|
166
|
+
if not flow_url:
|
|
167
|
+
console.error(f"The API did not return a GitHub {verb} URL")
|
|
104
168
|
raise typer.Exit(1)
|
|
105
169
|
|
|
106
170
|
# Imported at call time: this is the only place github.py needs auth.py,
|
|
@@ -109,21 +173,34 @@ async def connect(
|
|
|
109
173
|
from pipecatcloud.cli.commands.auth import _open_url
|
|
110
174
|
|
|
111
175
|
if not console.json_output:
|
|
112
|
-
console.print("[dim]Opening browser to
|
|
113
|
-
opened = _open_url(
|
|
176
|
+
console.print(f"[dim]Opening browser to {verb} the Pipecat Cloud GitHub App...[/dim]")
|
|
177
|
+
opened = _open_url(flow_url)
|
|
114
178
|
if not opened and not console.json_output:
|
|
115
179
|
console.print(
|
|
116
|
-
f"\nOpen this URL in your browser to
|
|
180
|
+
f"\nOpen this URL in your browser to {verb} the App:\n[blue]{flow_url}[/blue]\n"
|
|
117
181
|
)
|
|
118
182
|
elif not opened:
|
|
119
183
|
# In json mode stdout is reserved for the final payload, so the URL a
|
|
120
184
|
# headless caller needs goes to the console's stream (stderr).
|
|
121
|
-
console.print(f"Open this URL to
|
|
185
|
+
console.print(f"Open this URL to {verb} the App: {flow_url}")
|
|
186
|
+
# The browser half runs on the dashboard, under its own sign-in, and only
|
|
187
|
+
# for the user this terminal is logged in as. This terminal can't see the
|
|
188
|
+
# dashboard refuse, so it says where to look. In json mode too, where it
|
|
189
|
+
# goes to stderr with the URL above: a headless caller needs it more, not
|
|
190
|
+
# less.
|
|
191
|
+
finish = ", where you confirm the installation to link" if existing else " to finish"
|
|
192
|
+
console.print(
|
|
193
|
+
f"[dim]GitHub returns you to the Pipecat Cloud dashboard{finish}. "
|
|
194
|
+
"Sign in there as the same Pipecat Cloud user as this CLI if it asks. If the "
|
|
195
|
+
"dashboard shows an error, press Ctrl+C here and follow what it says.[/dim]"
|
|
196
|
+
)
|
|
122
197
|
|
|
123
|
-
deadline = time.monotonic() +
|
|
198
|
+
deadline = time.monotonic() + timeout
|
|
124
199
|
installation = None
|
|
200
|
+
refused = False
|
|
201
|
+
refusals_in_a_row = 0
|
|
125
202
|
with console.status(
|
|
126
|
-
"[dim]Waiting for GitHub.
|
|
203
|
+
"[dim]Waiting for GitHub. Finish in your browser...[/dim]",
|
|
127
204
|
spinner="dots",
|
|
128
205
|
):
|
|
129
206
|
while time.monotonic() < deadline:
|
|
@@ -134,12 +211,36 @@ async def connect(
|
|
|
134
211
|
if found:
|
|
135
212
|
installation = found
|
|
136
213
|
break
|
|
214
|
+
status = API.error_status if error else None
|
|
215
|
+
needed = None if status is None else _POLL_REFUSALS_TO_GIVE_UP.get(status)
|
|
216
|
+
if needed is None:
|
|
217
|
+
refusals_in_a_row = 0
|
|
218
|
+
continue
|
|
219
|
+
refusals_in_a_row += 1
|
|
220
|
+
if refusals_in_a_row >= needed:
|
|
221
|
+
refused = True
|
|
222
|
+
break
|
|
137
223
|
|
|
224
|
+
if refused:
|
|
225
|
+
# Printed once the spinner is gone.
|
|
226
|
+
API.print_error()
|
|
227
|
+
raise typer.Exit(1)
|
|
138
228
|
if not installation:
|
|
229
|
+
link_existing = f"[bold]{PIPECAT_CLI_NAME} github connect --existing[/bold]"
|
|
230
|
+
if existing:
|
|
231
|
+
retry = f"Otherwise run {link_existing} again."
|
|
232
|
+
else:
|
|
233
|
+
# A member who cannot install on the GitHub account sends an owner a
|
|
234
|
+
# request instead, and nothing links until they come back to it.
|
|
235
|
+
retry = (
|
|
236
|
+
f"If GitHub sent an owner a request to approve, run {link_existing} once "
|
|
237
|
+
f"they have. Otherwise run [bold]{PIPECAT_CLI_NAME} github connect[/bold] "
|
|
238
|
+
"again."
|
|
239
|
+
)
|
|
139
240
|
console.error(
|
|
140
|
-
"Timed out waiting for the GitHub
|
|
141
|
-
f"[dim]If you
|
|
142
|
-
"to check.
|
|
241
|
+
"Timed out waiting for the GitHub connection.\n"
|
|
242
|
+
f"[dim]If you finished in the browser, run [bold]{PIPECAT_CLI_NAME} github status"
|
|
243
|
+
f"[/bold] to check. {retry}[/dim]"
|
|
143
244
|
)
|
|
144
245
|
raise typer.Exit(1)
|
|
145
246
|
|
|
@@ -170,7 +271,9 @@ async def status(
|
|
|
170
271
|
if not installation:
|
|
171
272
|
console.print(
|
|
172
273
|
f"[yellow]Organization '{org}' is not connected to GitHub.[/yellow]\n"
|
|
173
|
-
f"[dim]Run [bold]{PIPECAT_CLI_NAME} github connect[/bold] to connect it.
|
|
274
|
+
f"[dim]Run [bold]{PIPECAT_CLI_NAME} github connect[/bold] to connect it. If you "
|
|
275
|
+
"installed the App on GitHub yourself, or an owner approved your request, run "
|
|
276
|
+
f"[bold]{PIPECAT_CLI_NAME} github connect --existing[/bold] instead.[/dim]"
|
|
174
277
|
)
|
|
175
278
|
return
|
|
176
279
|
|
|
@@ -23,8 +23,6 @@ _SETTINGS = {
|
|
|
23
23
|
),
|
|
24
24
|
"onboarding_path": _Setting("/v1/onboarding"),
|
|
25
25
|
"auth_config_path": _Setting("/auth/config"),
|
|
26
|
-
"login_path": _Setting("/auth/login"),
|
|
27
|
-
"login_status_path": _Setting("/auth/status"),
|
|
28
26
|
"logout_path": _Setting("/auth/logout"),
|
|
29
27
|
"whoami_path": _Setting("/v1/users"),
|
|
30
28
|
"organization_path": _Setting("/v1/organizations"),
|
|
@@ -47,6 +45,7 @@ _SETTINGS = {
|
|
|
47
45
|
# per service, so the binding path hardcodes "production" the same way the
|
|
48
46
|
# dashboard does; it becomes a parameter when multi-environment lands.
|
|
49
47
|
"github_install_url_path": _Setting("/v1/organizations/{org}/github/install-url"),
|
|
48
|
+
"github_link_url_path": _Setting("/v1/organizations/{org}/github/link-url"),
|
|
50
49
|
"github_installation_path": _Setting("/v1/organizations/{org}/github/installation"),
|
|
51
50
|
"github_repositories_path": _Setting("/v1/organizations/{org}/github/repositories"),
|
|
52
51
|
"service_git_path": _Setting(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Cloud hosting for Pipecat AI applications
|
|
5
5
|
License: BSD 2-Clause License
|
|
6
6
|
Project-URL: Website, https://docs.pipecat.ai/pipecat-cloud
|
|
@@ -8,7 +8,7 @@ Keywords: pipecat,pipecat-ai,voice,ai,llm
|
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: aiohttp<4.0.0,>=3.
|
|
11
|
+
Requires-Dist: aiohttp<4.0.0,>=3.14.4
|
|
12
12
|
Requires-Dist: fastapi<1.0.0,>=0.115.6
|
|
13
13
|
Requires-Dist: loguru~=0.7.3
|
|
14
14
|
Requires-Dist: python-dotenv<2.0.0,>=1.0.1
|