This repository was archived by the owner on Jun 17, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathinit.py
More file actions
338 lines (303 loc) · 14.2 KB
/
Copy pathinit.py
File metadata and controls
338 lines (303 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#
# Copyright (c) 2025, Daily
#
# SPDX-License-Identifier: BSD 2-Clause License
#
"""Init command implementation for creating new Pipecat projects."""
import json
from pathlib import Path
import typer
from rich.console import Console
from pipecat_cli.generators import ProjectGenerator
from pipecat_cli.prompts import ProjectConfig, ask_project_questions
from pipecat_cli.registry.service_metadata import ServiceRegistry
console = Console()
def _list_options_callback(value: bool):
"""Print available options as JSON and exit."""
if not value:
return
def values(defs):
return [s.value for s in defs]
options = {
"bot_type": ["web", "telephony"],
"transports": {
"web": values(ServiceRegistry.WEBRTC_TRANSPORTS),
"telephony": values(ServiceRegistry.TELEPHONY_TRANSPORTS),
},
"stt": values(ServiceRegistry.STT_SERVICES),
"llm": values(ServiceRegistry.LLM_SERVICES),
"tts": values(ServiceRegistry.TTS_SERVICES),
"realtime": values(ServiceRegistry.REALTIME_SERVICES),
"video": values(ServiceRegistry.VIDEO_SERVICES),
}
print(json.dumps(options, indent=2))
raise typer.Exit(0)
def init_command(
target: str | None = typer.Argument(
None,
help="Target directory; use '.' to scaffold into the current directory "
"(no <name> subfolder). Omit to create a <name> subfolder.",
),
output_dir: Path | None = typer.Option(
None, "--output", "-o", help="Output directory (defaults to current directory)"
),
list_options: bool = typer.Option(
False,
"--list-options",
help="Print available service options as JSON and exit",
callback=_list_options_callback,
is_eager=True,
),
# --- Non-interactive flags ---
name: str | None = typer.Option(
None, "--name", "-n", help="Project name (triggers non-interactive mode)"
),
bot_type: str | None = typer.Option(
None, "--bot-type", "-b", help="Bot type: 'web' or 'telephony'"
),
transport: list[str] | None = typer.Option(
None, "--transport", "-t", help="Transport (repeatable, e.g. -t daily -t smallwebrtc)"
),
mode: str | None = typer.Option(
None, "--mode", "-m", help="Pipeline mode: 'cascade' or 'realtime'"
),
stt: str | None = typer.Option(None, "--stt", help="STT service (cascade mode)"),
llm: str | None = typer.Option(None, "--llm", help="LLM service (cascade mode)"),
tts: str | None = typer.Option(None, "--tts", help="TTS service (cascade mode)"),
realtime: str | None = typer.Option(
None, "--realtime", help="Realtime service (realtime mode)"
),
video: str | None = typer.Option(None, "--video", help="Video avatar service"),
client_framework: str | None = typer.Option(
None, "--client-framework", help="Client framework: 'react', 'vanilla', or 'none'"
),
client_server: str | None = typer.Option(
None, "--client-server", help="Client dev server: 'vite' or 'nextjs'"
),
daily_pstn_mode: str | None = typer.Option(
None, "--daily-pstn-mode", help="Daily PSTN mode: 'dial-in' or 'dial-out'"
),
twilio_daily_sip_mode: str | None = typer.Option(
None, "--twilio-daily-sip-mode", help="Twilio+Daily SIP mode: 'dial-in' or 'dial-out'"
),
recording: bool = typer.Option(False, "--recording/--no-recording", help="Enable recording"),
transcription: bool = typer.Option(
False, "--transcription/--no-transcription", help="Enable transcription"
),
video_input: bool = typer.Option(
False, "--video-input/--no-video-input", help="Enable video input"
),
video_output: bool = typer.Option(
False, "--video-output/--no-video-output", help="Enable video output"
),
deploy_to_cloud: bool = typer.Option(
True, "--deploy-to-cloud/--no-deploy-to-cloud", help="Generate cloud deployment files"
),
enable_krisp: bool = typer.Option(
False, "--enable-krisp/--no-enable-krisp", help="Enable Krisp noise cancellation"
),
observability: bool = typer.Option(
False, "--observability/--no-observability", help="Enable observability"
),
evals: bool = typer.Option(
False,
"--evals/--no-evals",
help="Include behavioral evals (eval transport + scenario; cascade mode with "
"standard transports only)",
),
config: Path | None = typer.Option(
None, "--config", "-c", help="JSON config file (triggers non-interactive mode)"
),
dry_run: bool = typer.Option(
False, "--dry-run", help="Print resolved config as JSON without generating files"
),
):
"""
Initialize a new Pipecat project.
Creates a complete project structure with bot.py, dependencies, and configuration files.
In interactive mode (default), uses a wizard to guide you through setup.
In non-interactive mode (when --name or --config is provided), all configuration
is taken from flags or a config file.
Examples:
pc init # Interactive wizard
pc init . # Scaffold into the current dir
pc init --name my-bot --bot-type web \\
--transport daily --mode cascade \\
--stt deepgram_stt --llm openai_llm \\
--tts cartesia_tts # Non-interactive
pc init . --bot-type web -t daily -m cascade \\
--stt deepgram_stt --llm openai_llm \\
--tts cartesia_tts # In-place, name from dir
pc init --config project-config.json # From config file
pc init --name my-bot ... --dry-run # Preview config as JSON
"""
# `quickstart` is dispatched here rather than as a subcommand: a positional arg on a
# Typer *group* can't be followed by options (Click stops parsing at the first
# positional), which would break `pc init . --bot-type ...`. Keeping `init` a plain
# command and routing the `quickstart` token preserves `pc init quickstart [-o ...]`.
if target == "quickstart":
return quickstart_command(output_dir=output_dir)
try:
# Resolve the in-place target. A positional path is the exact destination
# (vite/django style); -o keeps its legacy "parent dir + name subfolder" meaning,
# so passing both is ambiguous.
in_place = target is not None
dest: Path | None = None
derived_name: str | None = None
if in_place:
if output_dir is not None:
console.print(
"\n[red]Error: pass either a target directory or --output, not both.[/red]"
)
raise typer.Exit(1)
dest = Path(target).resolve()
derived_name = dest.name or "pipecat-app"
# In-place runs go non-interactive as soon as any non-interactive intent is
# present (the name can be derived from the directory). Scoped to in_place so
# no existing (no-positional) invocation changes behavior.
non_interactive = name is not None or config is not None
if in_place and (name is not None or bot_type is not None or config is not None):
non_interactive = True
if non_interactive:
from pipecat_cli.config_validator import (
ConfigValidationError,
config_to_json,
load_config_from_file,
validate_and_build_config,
)
# Load from config file if provided
if config is not None:
file_data = load_config_from_file(config)
# CLI flags override file values
name = name or file_data.get("name") or file_data.get("project_name")
bot_type = bot_type or file_data.get("bot_type")
transport = transport or file_data.get("transports") or file_data.get("transport")
mode = mode or file_data.get("mode")
stt = stt or file_data.get("stt") or file_data.get("stt_service")
llm = llm or file_data.get("llm") or file_data.get("llm_service")
tts = tts or file_data.get("tts") or file_data.get("tts_service")
realtime = (
realtime or file_data.get("realtime") or file_data.get("realtime_service")
)
video = video or file_data.get("video") or file_data.get("video_service")
client_framework = client_framework or file_data.get("client_framework")
client_server = client_server or file_data.get("client_server")
daily_pstn_mode = daily_pstn_mode or file_data.get("daily_pstn_mode")
twilio_daily_sip_mode = twilio_daily_sip_mode or file_data.get(
"twilio_daily_sip_mode"
)
recording = recording or file_data.get("recording", False)
transcription = transcription or file_data.get("transcription", False)
video_input = video_input or file_data.get("video_input", False)
video_output = video_output or file_data.get("video_output", False)
if "deploy_to_cloud" in file_data:
deploy_to_cloud = file_data["deploy_to_cloud"]
enable_krisp = enable_krisp or file_data.get("enable_krisp", False)
observability = observability or file_data.get(
"observability", file_data.get("enable_observability", False)
)
evals = evals or file_data.get("evals", file_data.get("enable_evals", False))
try:
project_config = validate_and_build_config(
name=name or derived_name,
bot_type=bot_type,
transport=transport,
mode=mode,
stt=stt,
llm=llm,
tts=tts,
realtime=realtime,
video=video,
client_framework=client_framework,
client_server=client_server,
daily_pstn_mode=daily_pstn_mode,
twilio_daily_sip_mode=twilio_daily_sip_mode,
recording=recording,
transcription=transcription,
video_input=video_input,
video_output=video_output,
deploy_to_cloud=deploy_to_cloud,
enable_krisp=enable_krisp,
observability=observability,
evals=evals,
)
except ConfigValidationError as e:
console.print(f"\n[red]{e}[/red]")
raise typer.Exit(1)
if dry_run:
print(config_to_json(project_config))
raise typer.Exit(0)
# Generate project
generator = ProjectGenerator(project_config)
project_path = generator.generate(
dest if in_place else output_dir, non_interactive=True, in_place=in_place
)
# Show next steps
generator.print_next_steps(project_path, in_place=in_place)
else:
# Interactive mode: ask questions
config_result = ask_project_questions(default_name=derived_name)
# Generate project
generator = ProjectGenerator(config_result)
project_path = generator.generate(dest if in_place else output_dir, in_place=in_place)
# Show next steps
generator.print_next_steps(project_path, in_place=in_place)
except KeyboardInterrupt:
console.print("\n[yellow]Project creation cancelled.[/yellow]")
raise typer.Exit(1)
except typer.Exit:
raise
except FileExistsError as e:
console.print(f"\n[red]Error: {e}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"\n[red]Error creating project: {e}[/red]")
raise typer.Exit(1)
def quickstart_command(output_dir: Path | None = None):
"""
Create a new Pipecat project with quickstart defaults.
Sets up a project with SmallWebRTC, Deepgram STT, OpenAI LLM, and Cartesia TTS
— the fastest way to get a voice agent running. Dispatched from ``init_command``
when the target is ``quickstart`` (e.g. ``pc init quickstart [-o DIR]``).
"""
try:
project_name = "pipecat-quickstart"
console.print("[bold cyan]Let's create your Pipecat project![/bold cyan]\n")
# Display all pre-selected defaults
console.print(f"[green]✔[/green] Project name: [cyan]{project_name}[/cyan]")
console.print("[green]✔[/green] Bot type: [cyan]Web/Mobile[/cyan]")
console.print("[green]✔[/green] Transport: [cyan]SmallWebRTC, Daily[/cyan]")
console.print(
"[green]✔[/green] Pipeline architecture: [cyan]Cascade (STT → LLM → TTS)[/cyan]"
)
console.print("[green]✔[/green] Speech-to-Text: [cyan]Deepgram[/cyan]")
console.print("[green]✔[/green] Language model: [cyan]OpenAI[/cyan]")
console.print("[green]✔[/green] Text-to-Speech: [cyan]Cartesia[/cyan]")
console.print("[green]✔[/green] Deploy to Pipecat Cloud: [cyan]Yes[/cyan]")
# Build config with quickstart defaults
project_config = ProjectConfig(
project_name=project_name,
bot_type="web",
transports=["smallwebrtc", "daily"],
mode="cascade",
stt_service="deepgram_stt",
llm_service="openai_responses_llm",
tts_service="cartesia_tts",
deploy_to_cloud=True,
)
# Generate project
generator = ProjectGenerator(project_config)
project_path = generator.generate(output_dir, non_interactive=True)
# Show next steps
generator.print_next_steps(project_path)
except KeyboardInterrupt:
console.print("\n[yellow]Project creation cancelled.[/yellow]")
raise typer.Exit(1)
except typer.Exit:
raise
except FileExistsError as e:
console.print(f"\n[red]Error: {e}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"\n[red]Error creating project: {e}[/red]")
raise typer.Exit(1)