pipecatcloud 0.1.2__tar.gz → 0.1.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 (45) hide show
  1. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/CHANGELOG.md +44 -2
  2. {pipecatcloud-0.1.2/src/pipecatcloud.egg-info → pipecatcloud-0.1.4}/PKG-INFO +3 -2
  3. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/pyproject.toml +1 -1
  4. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/__init__.py +13 -7
  5. pipecatcloud-0.1.4/src/pipecatcloud/__version__.py +16 -0
  6. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/_utils/console_utils.py +4 -3
  7. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/api.py +21 -5
  8. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/agent.py +93 -20
  9. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/auth.py +23 -7
  10. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/deploy.py +48 -49
  11. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/session.py +15 -0
  12. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4/src/pipecatcloud.egg-info}/PKG-INFO +3 -2
  13. pipecatcloud-0.1.2/src/pipecatcloud/__version__.py +0 -12
  14. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/.github/workflows/publish-pypi.yml +0 -0
  15. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/.github/workflows/publish-test.yml +0 -0
  16. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/.github/workflows/python-publish.yml +0 -0
  17. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/.gitignore +0 -0
  18. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/LICENSE +0 -0
  19. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/README.md +0 -0
  20. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/dev-requirements.txt +0 -0
  21. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/pipecat-cloud.png +0 -0
  22. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/setup.cfg +0 -0
  23. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/__main__.py +0 -0
  24. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/_utils/__init__.py +0 -0
  25. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/_utils/async_utils.py +0 -0
  26. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  27. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
  28. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/_utils/local_runner.py +0 -0
  29. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/agent.py +0 -0
  30. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/__init__.py +0 -0
  31. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/api.py +0 -0
  32. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  33. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/init.py +0 -0
  34. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  35. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/run.py +0 -0
  36. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  37. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/config.py +0 -0
  38. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/cli/entry_point.py +0 -0
  39. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/config.py +0 -0
  40. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud/exception.py +0 -0
  41. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud.egg-info/SOURCES.txt +0 -0
  42. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  43. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  44. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud.egg-info/requires.txt +0 -0
  45. {pipecatcloud-0.1.2 → pipecatcloud-0.1.4}/src/pipecatcloud.egg-info/top_level.txt +0 -0
@@ -7,15 +7,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.4] - 2025-03-28
11
+
12
+ ### Changed
13
+
14
+ - `deploy` now shows a confirmation when `min-instances` is greater than 0 to assert usage will be billed.
15
+
16
+ ### Fixed
17
+
18
+ - `auth login` now accepts a `--headless` / `-h` flag to skip automatic browser opening during authentication. This is particularly useful for:
19
+ - Systems running in headless environments
20
+ - WSL installations where browser context may not match the terminal
21
+ - CI/CD pipelines
22
+ - Users who prefer to manually copy and paste the authentication URL
23
+
24
+ ## [0.1.3] - 2025-03-13
25
+
26
+ ### Fixed
27
+
28
+ - `deploy` now correctly handles error states returned by the API
29
+
30
+ - `deploy` checks revision status vs. general service ready status (when updating)
31
+
32
+ ### Added
33
+
34
+ - `agent logs` now accepts a `--deployment_id` / `-d` argument for filtering
35
+ by specific deployment ID
36
+
37
+ - `agent start` now accepts `--daily-properties` / `-p` for customizing Daily
38
+ room settings when used with `--use-daily`.
39
+
40
+ - Added `daily_room_properties` to `SessionParams` in SDK for configuring Daily
41
+ rooms when creating sessions.
42
+
43
+ - Added an export for the `PipecatSessionArguments` class.
44
+
45
+ ### Fixed
46
+
47
+ - Fix an issue where custom `data` resulted in an agent not starting.
48
+
49
+ - Fixed an issue where the link returned by `pcc agent start` was not clickable
50
+ in IDEs.
51
+
10
52
  ## [0.1.2] - 2025-03-12
11
53
 
12
54
  ### Added
13
55
 
14
- - `agent.py` data classes for use in base images, providing guidance on params
56
+ - `agent.py` data classes for use in base images, providing guidance on params.
15
57
 
16
58
  ### Fixed
17
59
 
18
- - Lookup issue when passing an image pull secret to the `deploy` command
60
+ - Lookup issue when passing an image pull secret to the `deploy` command.
19
61
 
20
62
  ### Changed
21
63
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.1.2
3
+ Version: 0.1.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/
@@ -17,6 +17,7 @@ Requires-Dist: typer~=0.13.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
20
+ Dynamic: license-file
20
21
 
21
22
  <h1><div align="center">
22
23
  <img alt="pipecat cloud" width="500px" height="auto" src="https://raw.githubusercontent.com/daily-co/pipecat-cloud/main/pipecat-cloud.png">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pipecatcloud"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  requires-python = ">=3.10"
9
9
  description = "Cloud hosting for Pipecat AI applications"
10
10
  license = { text = "BSD 2-Clause License" }
@@ -9,7 +9,12 @@ import sys
9
9
 
10
10
  from loguru import logger
11
11
 
12
- from pipecatcloud.agent import DailySessionArguments, SessionArguments, WebSocketSessionArguments
12
+ from pipecatcloud.agent import (
13
+ DailySessionArguments,
14
+ PipecatSessionArguments,
15
+ SessionArguments,
16
+ WebSocketSessionArguments,
17
+ )
13
18
  from pipecatcloud.exception import (
14
19
  AgentNotHealthyError,
15
20
  AgentStartError,
@@ -27,18 +32,19 @@ logger.add(sys.stderr, level=str(os.getenv("PCC_LOG_LEVEL", "INFO")))
27
32
 
28
33
  __all__ = [
29
34
  # Agent classes
30
- "SessionArguments",
31
35
  "DailySessionArguments",
36
+ "PipecatSessionArguments",
37
+ "SessionArguments",
32
38
  "WebSocketSessionArguments",
33
39
  # Session classes
34
40
  "Session",
35
41
  "SessionParams",
36
42
  # Exception classes
37
- "Error",
38
- "ConfigFileError",
39
- "AuthError",
40
- "InvalidError",
41
- "ConfigError",
42
43
  "AgentNotHealthyError",
43
44
  "AgentStartError",
45
+ "AuthError",
46
+ "ConfigError",
47
+ "ConfigFileError",
48
+ "Error",
49
+ "InvalidError",
44
50
  ]
@@ -0,0 +1,16 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ try:
8
+ from importlib.metadata import version as get_version
9
+ version = get_version("pipecatcloud")
10
+ except ImportError:
11
+ # For Python < 3.8
12
+ try:
13
+ from importlib_metadata import version as get_version
14
+ version = get_version("pipecatcloud")
15
+ except ImportError:
16
+ version = "Unknown"
@@ -67,8 +67,7 @@ class PipecatConsole(Console):
67
67
  title_align="left",
68
68
  subtitle_align="left",
69
69
  border_style="red",
70
- )
71
- )
70
+ ))
72
71
 
73
72
  def api_error(
74
73
  self,
@@ -79,7 +78,9 @@ class PipecatConsole(Console):
79
78
  DEFAULT_ERROR_MESSAGE = "Unknown error. Please contact support."
80
79
 
81
80
  if isinstance(error_code, dict):
82
- error_message = error_code.get("error", DEFAULT_ERROR_MESSAGE)
81
+ error_message = error_code.get(
82
+ "error", None) or error_code.get(
83
+ "message", None) or DEFAULT_ERROR_MESSAGE
83
84
  code = error_code.get("code")
84
85
  else:
85
86
  error_message = str(error_code) if error_code else DEFAULT_ERROR_MESSAGE
@@ -4,8 +4,9 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ import json
7
8
  from functools import wraps
8
- from typing import Callable, List, Optional
9
+ from typing import Callable, List, Optional, Union
9
10
 
10
11
  import aiohttp
11
12
  from loguru import logger
@@ -368,13 +369,24 @@ class _API:
368
369
  return self.create_api_method(self._agents)
369
370
 
370
371
  async def _start_agent(
371
- self, agent_name: str, api_key: str, use_daily: bool, data: Optional[str] = None
372
+ self,
373
+ agent_name: str,
374
+ api_key: str,
375
+ use_daily: bool,
376
+ data: Optional[str] = None,
377
+ daily_properties: Optional[str] = None,
372
378
  ) -> dict | None:
373
379
  url = f"{self.construct_api_url('start_path').format(service=agent_name)}"
374
380
 
375
381
  payload: dict = {"createDailyRoom": use_daily}
382
+
383
+ # Add data to payload if provided
376
384
  if data is not None:
377
- payload["body"] = data
385
+ payload["body"] = json.loads(data)
386
+
387
+ # Add Daily room properties only if use_daily is True
388
+ if use_daily and daily_properties:
389
+ payload["dailyRoomProperties"] = json.loads(daily_properties)
378
390
 
379
391
  return await self._base_request(
380
392
  "POST", url, override_token=api_key, json=payload, not_found_is_empty=True
@@ -392,9 +404,13 @@ class _API:
392
404
  def agent_delete(self):
393
405
  return self.create_api_method(self._agent_delete)
394
406
 
395
- async def _agent_logs(self, agent_name: str, org: str, limit: int = 100) -> dict | None:
407
+ async def _agent_logs(self, agent_name: str, org: str, limit: int = 100,
408
+ deployment_id: Optional[str] = None) -> dict | None:
396
409
  url = f"{self.construct_api_url('services_logs_path').format(org=org, service=agent_name)}"
397
- return await self._base_request("GET", url, params={"limit": limit})
410
+ params: dict[str, Union[int, str]] = {"limit": limit}
411
+ if deployment_id:
412
+ params["deploymentId"] = deployment_id
413
+ return await self._base_request("GET", url, params=params)
398
414
 
399
415
  @property
400
416
  def agent_logs(self):
@@ -42,16 +42,19 @@ async def list(
42
42
  ):
43
43
  org = organization or config.get("org")
44
44
 
45
- with console.status(f"[dim]Fetching agents for organization: [bold]'{org}'[/bold][/dim]", spinner="dots"):
45
+ with console.status(
46
+ f"[dim]Fetching agents for organization: [bold]'{org}'[/bold][/dim]", spinner="dots"
47
+ ):
46
48
  data, error = await API.agents(org=org)
47
49
 
48
50
  if error:
49
- typer.Exit()
51
+ return typer.Exit()
50
52
 
51
53
  if not data or len(data) == 0:
52
54
  console.error(
53
55
  f"[red]No agents found for namespace / organization '{org}'[/red]\n\n"
54
- f"[dim]Please deploy an agent first using[/dim] [bold cyan]{PIPECAT_CLI_NAME} deploy[/bold cyan]")
56
+ f"[dim]Please deploy an agent first using[/dim] [bold cyan]{PIPECAT_CLI_NAME} deploy[/bold cyan]"
57
+ )
55
58
  return typer.Exit(1)
56
59
 
57
60
  else:
@@ -92,6 +95,8 @@ async def status(
92
95
  ) as live:
93
96
  data, error = await API.agent(agent_name=agent_name, org=org, live=live)
94
97
 
98
+ logger.debug(f"Agent status: {data}")
99
+
95
100
  live.stop()
96
101
 
97
102
  if error:
@@ -147,6 +152,25 @@ async def status(
147
152
  border_style="dim",
148
153
  )
149
154
 
155
+ # Error status
156
+ error_panel = None
157
+ errors = data.get("errors", [])
158
+ if errors and len(errors) > 0:
159
+ error_table = Table(show_header=False, show_lines=False, box=box.SIMPLE)
160
+ error_table.add_column("Code")
161
+ error_table.add_column("Message")
162
+ for error in errors:
163
+ error_table.add_row(
164
+ f"[bold red]{error['code']}[/bold red]",
165
+ f"[red]{error.get('message', None) or error.get('error', 'Unknown error')}[/red]",
166
+ )
167
+ error_panel = Panel(
168
+ error_table,
169
+ title="[bold red]Agent errors:[/bold red]",
170
+ title_align="left",
171
+ border_style="red",
172
+ )
173
+
150
174
  color = "bold green" if data["ready"] else "bold yellow"
151
175
  subtitle = (
152
176
  f"[dim]Start a new active session with[/dim] [bold cyan]{PIPECAT_CLI_NAME} agent start {agent_name}[/bold cyan]"
@@ -163,6 +187,7 @@ async def status(
163
187
  border_style="green" if data["ready"] else "yellow",
164
188
  expand=False,
165
189
  ),
190
+ error_panel if error_panel else "",
166
191
  ),
167
192
  title=f"Status for agent [bold]{agent_name}[/bold]",
168
193
  title_align="left",
@@ -246,30 +271,51 @@ class LogLevelColors(str, Enum):
246
271
  @synchronizer.create_blocking
247
272
  @requires_login
248
273
  async def logs(
249
- agent_name: str,
250
- organization: str = typer.Option(
251
- None, "--organization", "-o", help="Organization to get status of agent for"
252
- ),
253
- level: LogLevel = typer.Option(None, "--level", "-l", help="Level of logs to get"),
254
- format: LogFormat = typer.Option(LogFormat.TEXT, "--format", "-f", help="Logs format"),
255
- limit: int = typer.Option(100, "--limit", "-n", help="Number of logs to get"),
274
+ agent_name: str,
275
+ organization: str = typer.Option(
276
+ None,
277
+ "--organization",
278
+ "-o",
279
+ help="Organization to get status of agent for"),
280
+ level: LogLevel = typer.Option(
281
+ None,
282
+ "--level",
283
+ "-l",
284
+ help="Level of logs to get"),
285
+ format: LogFormat = typer.Option(
286
+ LogFormat.TEXT,
287
+ "--format",
288
+ "-f",
289
+ help="Logs format"),
290
+ limit: int = typer.Option(
291
+ 100,
292
+ "--limit",
293
+ "-n",
294
+ help="Number of logs to get"),
295
+ deployment_id: str = typer.Option(
296
+ None,
297
+ "--deployment",
298
+ "-d",
299
+ help="Filter logs by deployment ID"),
256
300
  ):
257
301
  org = organization or config.get("org")
258
302
 
303
+ status_text = "agent" if not deployment_id else f"deployment ({deployment_id})"
304
+
259
305
  with console.status(
260
- f"[dim]Fetching logs for agent: [bold]'{agent_name}'[/bold] with severity: [bold cyan]{level.value if level else 'ALL'}[/bold cyan][/dim]",
306
+ f"[dim]Fetching logs for {status_text}: [bold]'{agent_name}'[/bold] with severity: [bold cyan]{level.value if level else 'ALL'}[/bold cyan][/dim]",
261
307
  spinner="dots",
262
308
  ):
263
- data, error = await API.agent_logs(agent_name=agent_name, org=org, limit=limit)
309
+ data, error = await API.agent_logs(agent_name=agent_name, org=org, limit=limit, deployment_id=deployment_id)
264
310
 
265
311
  if not data or not data.get("logs"):
266
312
  console.print("[dim]No logs found for agent[/dim]")
267
313
  return typer.Exit(1)
268
314
 
269
- for l in data["logs"]:
270
- log_data = l.get("log", "")
315
+ for log in data["logs"]:
316
+ log_data = log.get("log", "")
271
317
  if log_data:
272
- timestamp = format_timestamp(l.get("timestamp", ""))
318
+ timestamp = format_timestamp(log.get("timestamp", ""))
273
319
  severity = LogLevel.INFO
274
320
  for log_severity in LogLevel:
275
321
  if log_severity.value in log_data.upper():
@@ -282,9 +328,9 @@ async def logs(
282
328
  if format == LogFormat.TEXT:
283
329
  color = getattr(LogLevelColors, severity, LogLevelColors.DEBUG).value
284
330
  console.print(Text(timestamp, style="bold dim"), end=" ")
285
- console.print(Text(l.get("log", ""), style=color))
331
+ console.print(Text(log_data, style=color))
286
332
  elif format == LogFormat.JSON:
287
- line = {"timestamp": timestamp, "log": l.get("log", "")}
333
+ line = {"timestamp": timestamp, "log": log_data}
288
334
  console.print(Text(json.dumps(line, ensure_ascii=False), style="gray"))
289
335
 
290
336
 
@@ -427,6 +473,13 @@ async def start(
427
473
  help="Create a Daily WebRTC session for the agent",
428
474
  rich_help_panel="Start Configuration",
429
475
  ),
476
+ daily_properties: str = typer.Option(
477
+ None,
478
+ "--daily-properties",
479
+ "-p",
480
+ help="Daily room properties (stringified JSON)",
481
+ rich_help_panel="Start Configuration",
482
+ ),
430
483
  organization: str = typer.Option(
431
484
  None,
432
485
  "--organization",
@@ -449,22 +502,39 @@ async def start(
449
502
  title="Public API Key Required",
450
503
  title_align="left",
451
504
  border_style="yellow",
452
- ))
505
+ )
506
+ )
453
507
 
454
508
  return typer.Exit(1)
455
509
 
510
+ # Validate daily_properties JSON if provided
511
+ if use_daily and daily_properties:
512
+ try:
513
+ json.loads(daily_properties)
514
+ except json.JSONDecodeError as e:
515
+ console.error(f"Invalid JSON format for Daily room properties: {daily_properties}")
516
+ console.print(f"[dim]JSON error: {str(e)}[/dim]")
517
+ return typer.Exit(1)
518
+
456
519
  # Confirm start request
457
520
  if not force:
521
+ daily_props_display = daily_properties or "None"
522
+ # Truncate display of daily properties if too long
523
+ if daily_properties and len(daily_properties) > 80:
524
+ daily_props_display = daily_properties[:77] + "..."
525
+
458
526
  console.print(
459
527
  Panel(
460
528
  f"Agent Name: {agent_name}\n"
461
529
  f"Public API Key: {default_public_api_key_name} [dim]{default_public_api_key}[/dim]\n"
462
530
  f"Use Daily: {use_daily}\n"
531
+ f"Daily Properties: {daily_props_display}\n"
463
532
  f"Data: {data}",
464
533
  title=f"[bold]Start Request for agent: {agent_name}[/bold]",
465
534
  title_align="left",
466
535
  border_style="yellow",
467
- ))
536
+ )
537
+ )
468
538
  if not await questionary.confirm(
469
539
  "Are you sure you want to start an active session for this agent?"
470
540
  ).ask_async():
@@ -494,10 +564,13 @@ async def start(
494
564
  api_key=default_public_api_key,
495
565
  use_daily=use_daily,
496
566
  data=data,
567
+ daily_properties=daily_properties,
497
568
  live=live,
498
569
  )
499
570
 
500
571
  if error:
572
+ live.stop()
573
+ # Error is displayed from start_agent create_api_method wrapper
501
574
  return typer.Exit(1)
502
575
 
503
576
  live.stop()
@@ -509,4 +582,4 @@ async def start(
509
582
  if daily_room:
510
583
  url = f"{daily_room}?t={daily_token}"
511
584
  console.print("\nJoin your session by visiting the link below:")
512
- console.print(f"[link='{url}']{url}[/link]")
585
+ console.print(f"[link={url}]{url}[/link]")
@@ -7,6 +7,7 @@
7
7
  import asyncio
8
8
  import itertools
9
9
  import webbrowser
10
+ import os
10
11
  from contextlib import asynccontextmanager
11
12
  from typing import AsyncGenerator, Optional, Tuple
12
13
 
@@ -83,12 +84,19 @@ AuthFlow = synchronize_api(_AuthFlow)
83
84
 
84
85
  def _open_url(url: str) -> bool:
85
86
  try:
87
+ is_wsl = 'WSL_DISTRO_NAME' in os.environ or 'WSL_INTEROP' in os.environ
88
+ has_display = bool(os.environ.get('DISPLAY') or os.environ.get('WAYLAND_DISPLAY'))
89
+
90
+ if is_wsl and not has_display:
91
+ return False
92
+
86
93
  browser = webbrowser.get()
87
- if isinstance(browser, webbrowser.GenericBrowser) and browser.name != "open":
94
+ if (isinstance(browser, webbrowser.GenericBrowser) and
95
+ browser.name not in ['open', 'x-www-browser', 'xdg-open']):
88
96
  return False
89
- else:
90
- return browser.open_new_tab(url)
91
- except webbrowser.Error:
97
+
98
+ return browser.open_new_tab(url)
99
+ except (webbrowser.Error, ImportError, AttributeError):
92
100
  return False
93
101
 
94
102
 
@@ -124,7 +132,14 @@ async def _get_account_org(
124
132
 
125
133
  @auth_cli.command(name="login", help="Login to Pipecat Cloud and get a new token")
126
134
  @synchronizer.create_blocking
127
- async def login():
135
+ async def login(
136
+ headless: bool = typer.Option(
137
+ False,
138
+ "--headless",
139
+ "-h",
140
+ help="Skip opening a browser window for authentication and print the URL instead",
141
+ ),
142
+ ):
128
143
  active_org = config.get("org")
129
144
  auth_flow = _AuthFlow()
130
145
 
@@ -146,7 +161,7 @@ async def login():
146
161
  transient=True,
147
162
  ) as live:
148
163
  # Open the web url in the browser
149
- if _open_url(web_url):
164
+ if not headless and _open_url(web_url):
150
165
  live.update(
151
166
  console.status(
152
167
  Panel(
@@ -220,7 +235,8 @@ async def logout():
220
235
  )
221
236
 
222
237
 
223
- @auth_cli.command(name="whoami", help="Display data about the current user. Also show Daily API key.")
238
+ @auth_cli.command(name="whoami",
239
+ help="Display data about the current user. Also show Daily API key.")
224
240
  @synchronizer.create_blocking
225
241
  @requires_login
226
242
  async def whomai():
@@ -7,6 +7,7 @@
7
7
  import asyncio
8
8
 
9
9
  import typer
10
+ from loguru import logger
10
11
  from rich.console import Group
11
12
  from rich.live import Live
12
13
  from rich.panel import Panel
@@ -131,7 +132,6 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
131
132
  # 3. Poll status until healthy
132
133
  """
133
134
  active_deployment_id = None
134
- has_failed = False
135
135
  is_ready = False
136
136
  checks_performed = 0
137
137
 
@@ -145,23 +145,44 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
145
145
  ) as status:
146
146
  try:
147
147
  while checks_performed < MAX_ALIVE_CHECKS:
148
+ logger.debug("Polling for deployment status")
149
+
148
150
  # Get deployment status
149
151
  agent_status, error = await API.agent(
150
152
  agent_name=params.agent_name, org=org, live=None
151
153
  )
152
154
 
155
+ logger.debug(f"Deployment status: {agent_status}")
156
+
157
+ # Look for any error messages in the agent status
158
+ # Exit out of the polling loop if we find an error
159
+ status_errors = agent_status.get("errors", [])
160
+ if status_errors and len(status_errors) > 0:
161
+ status.stop()
162
+ # Pluck the first error message
163
+ error_message = status_errors[0]
164
+ if "code" in error_message and "message" in error_message:
165
+ console.api_error(error_message, "Agent deployment failed")
166
+ else:
167
+ console.error(f"Deployment failed with an unknown error: {status_errors}")
168
+ return typer.Exit()
169
+
153
170
  if error:
154
171
  status.stop()
155
172
  console.error("Error checking deployment status")
156
173
  return typer.Exit()
157
174
 
158
- # Update deployment ID if received (silently)
175
+ # Update deployment ID if received
159
176
  if not active_deployment_id and agent_status.get("activeDeploymentId"):
160
177
  active_deployment_id = agent_status["activeDeploymentId"]
161
178
  deployment_status_message = f"[dim]Waiting for deployment to become ready (deployment ID: {active_deployment_id})...[/dim]"
179
+ status.update(deployment_status_message)
180
+
181
+ # If we have an active deployment ID, start tailing the log output
182
+ # @TODO - Implement this
162
183
 
163
184
  # Check if deployment is ready
164
- if agent_status.get("ready"):
185
+ if agent_status.get("activeDeploymentReady", False):
165
186
  is_ready = True
166
187
  break
167
188
 
@@ -176,43 +197,24 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
176
197
  )
177
198
  return typer.Exit()
178
199
 
179
- # Final result handling
180
- if has_failed:
181
- console.print(has_failed["message"])
182
- console.error("Deployment failed with the above error")
183
- return typer.Exit()
184
-
185
- # check for org secret
186
- default_public_api_key = config.get("default_public_key")
187
-
188
200
  if is_ready:
189
- if not default_public_api_key:
190
- console.print(
191
- Panel(
192
- f"No public API key provided. Please provide a public API key using the --api-key flag or set a default using [bold cyan]{PIPECAT_CLI_NAME} organizations keys use[/bold cyan].\n\n"
193
- f"If you have not yet created a public API key, you can do so by running [bold cyan]{PIPECAT_CLI_NAME} organizations keys create[/bold cyan].\n\n"
194
- f"Then...\n\n"
195
- f"[dim]Start a session with your new agent by running:\n[/dim]"
196
- f"[bold]`{PIPECAT_CLI_NAME} agent start {params.agent_name}`[/bold]",
197
- title="Public API Key Required",
198
- title_align="left",
199
- border_style="yellow",
200
- )
201
- )
202
-
203
- return typer.Exit(1)
204
- else:
205
- console.success(
206
- f"Agent deployment [bold]'{params.agent_name}'[/bold] is ready\n\n"
207
- f"[dim]Start a session with your new agent by running:\n[/dim]"
208
- f"[bold]`{PIPECAT_CLI_NAME} agent start {params.agent_name}`[/bold]",
209
- title_extra=f"{'Update' if existing_agent else 'Deployment'} complete",
210
- )
201
+ public_api_key = config.get("default_public_key")
202
+ extra_message = ""
203
+ if not public_api_key:
204
+ extra_message = "\n\n[yellow]Note: if you have not already created a public API key (required to start a session), you can do so by running:\n[/yellow]"
205
+ extra_message += f"[bold yellow]`{PIPECAT_CLI_NAME} organizations keys create`[/bold yellow]"
206
+
207
+ console.success(
208
+ f"Agent deployment [bold]'{params.agent_name}'[/bold] is ready\n\n"
209
+ f"[white]Start a session with your new agent by running:\n[/white]"
210
+ f"[bold]`{PIPECAT_CLI_NAME} agent start {params.agent_name}`[/bold]"
211
+ f"{extra_message}",
212
+ title_extra=f"{'Update' if existing_agent else 'Deployment'} complete",
213
+ )
211
214
  else:
212
215
  console.error(
213
216
  f"Deployment did not enter ready state within {MAX_ALIVE_CHECKS * ALIVE_CHECK_SLEEP} seconds. "
214
- f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`"
215
- )
217
+ f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`")
216
218
 
217
219
  return typer.Exit()
218
220
 
@@ -325,24 +327,21 @@ def create_deploy_command(app: typer.Typer):
325
327
  (f"[bold white]Agent name:[/bold white] [green]{partial_config.agent_name}[/green]"),
326
328
  (f"[bold white]Image:[/bold white] [green]{partial_config.image}[/green]"),
327
329
  (f"[bold white]Organization:[/bold white] [green]{org}[/green]"),
328
- (
329
- f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"
330
- ),
331
- (
332
- f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"
333
- ),
330
+ (f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"),
331
+ (f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"),
334
332
  "\n[dim]Scaling configuration:[/dim]",
335
333
  table,
336
- *(
337
- []
338
- if partial_config.scaling.min_instances
339
- else [
334
+ *
335
+ (
336
+ [
337
+ Text(
338
+ f"Note: Usage costs will apply for {partial_config.scaling.min_instances} reserved instance(s). Please see: https://www.daily.co/pricing/pipecat-cloud/",
339
+ style="red",
340
+ )] if partial_config.scaling.min_instances else [
340
341
  Text(
341
342
  "Note: Deploying with 0 minimum instances may result in cold starts",
342
343
  style="red",
343
- )
344
- ]
345
- ),
344
+ )]),
346
345
  )
347
346
 
348
347
  console.print(
@@ -21,10 +21,14 @@ class SessionParams:
21
21
  Args:
22
22
  data: Optional dictionary of data to pass to the agent.
23
23
  use_daily: If True, creates a Daily WebRTC room for the session.
24
+ daily_room_properties: Optional dictionary of properties to configure the Daily room.
25
+ Only used when use_daily=True. See Daily.co API documentation for available properties:
26
+ https://docs.daily.co/reference/rest-api/rooms/config
24
27
  """
25
28
 
26
29
  data: Optional[Dict[str, Any]] = None
27
30
  use_daily: Optional[bool] = False
31
+ daily_room_properties: Optional[Dict[str, Any]] = None
28
32
 
29
33
 
30
34
  class Session:
@@ -90,12 +94,23 @@ class Session:
90
94
  # If it's already a string or other type, use as is
91
95
  data_param = self.params.data
92
96
 
97
+ # Only process daily_room_properties if use_daily is True
98
+ daily_properties_param = None
99
+ if self.params.use_daily and self.params.daily_room_properties:
100
+ # Convert dictionary to JSON string
101
+ if isinstance(self.params.daily_room_properties, dict):
102
+ daily_properties_param = json.dumps(self.params.daily_room_properties)
103
+ else:
104
+ # If it's already a string, use as is
105
+ daily_properties_param = self.params.daily_room_properties
106
+
93
107
  # Call the method similar to how the CLI does it
94
108
  result, error = await api.start_agent(
95
109
  agent_name=self.agent_name,
96
110
  api_key=self.api_key,
97
111
  use_daily=bool(self.params.use_daily),
98
112
  data=data_param,
113
+ daily_properties=daily_properties_param,
99
114
  )
100
115
 
101
116
  if error:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.1.2
3
+ Version: 0.1.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/
@@ -17,6 +17,7 @@ Requires-Dist: typer~=0.13.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
20
+ Dynamic: license-file
20
21
 
21
22
  <h1><div align="center">
22
23
  <img alt="pipecat cloud" width="500px" height="auto" src="https://raw.githubusercontent.com/daily-co/pipecat-cloud/main/pipecat-cloud.png">
@@ -1,12 +0,0 @@
1
- #
2
- # Copyright (c) 2025, Daily
3
- #
4
- # SPDX-License-Identifier: BSD 2-Clause License
5
- #
6
-
7
- from pathlib import Path
8
-
9
- import toml
10
-
11
- pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
12
- version = toml.load(pyproject_path)["project"]["version"]
File without changes
File without changes
File without changes
File without changes