@pyai/sdk 0.4.0 → 0.6.0

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.
package/CLI.md ADDED
@@ -0,0 +1,634 @@
1
+ # PyAI CLI handbook
2
+
3
+ Use `pyai` to render voiceovers, transcribe recordings, dub audio, manage Agent
4
+ profiles, and inspect the PyAI API from a terminal. The same commands work in
5
+ scripts and in coding agents with shell access, including Cursor, Claude Code,
6
+ and Codex. `--json` provides structured results; `--help` and `schema` describe
7
+ the installed command surface.
8
+
9
+ **Version:** the expanded CLI is included in `@pyai/sdk` 0.6.0. Install it from
10
+ npm with `npm install -g @pyai/sdk@0.6.0`, then run `pyai login` for browser
11
+ sign-in or provide `PYAI_API_KEY` for unattended automation.
12
+
13
+ ## Contents
14
+
15
+ - [Install and verify](#install-and-verify)
16
+ - [First five minutes](#first-five-minutes)
17
+ - [Shortcuts and one-liners](#shortcuts-and-one-liners)
18
+ - [Authentication and profiles](#authentication-and-profiles)
19
+ - [Speak and Hear](#speak-and-hear)
20
+ - [Dub in one command](#dub-in-one-command)
21
+ - [Jobs, Cast, and voice design](#jobs-cast-and-voice-design)
22
+ - [Manage resources with JSON](#manage-resources-with-json)
23
+ - [Command reference](#command-reference)
24
+ - [API discovery and generic requests](#api-discovery-and-generic-requests)
25
+ - [Agent and project handoff](#agent-and-project-handoff)
26
+ - [Automation contract](#automation-contract)
27
+ - [Troubleshooting](#troubleshooting)
28
+
29
+ ## Install and verify
30
+
31
+ Install the published package (Node.js 22 or newer recommended):
32
+
33
+ ```bash
34
+ npm install -g @pyai/sdk@0.6.0
35
+ pyai --version
36
+ pyai login
37
+ pyai speak "Hello from PyAI." -o hello.wav
38
+ ```
39
+
40
+ For development, use Node.js 22 or newer to build and test a source checkout:
41
+
42
+ ```bash
43
+ cd sdk/typescript
44
+ npm ci
45
+ npm run build
46
+ node dist/cli.js --help
47
+ npm install -g .
48
+ pyai --help
49
+ ```
50
+
51
+ The compiled executable is part of `@pyai/sdk` and supports Node.js 18 or newer.
52
+ Source tests require a runtime with TypeScript execution support. A build can
53
+ also be packaged and installed on another machine:
54
+
55
+ ```bash
56
+ # In sdk/typescript after building
57
+ npm pack --ignore-scripts
58
+ # On the destination machine, using the actual tarball path
59
+ npm install -g /path/to/pyai-sdk-0.6.0.tgz
60
+ pyai schema --json
61
+ ```
62
+
63
+ Use `node dist/cli.js` instead of `pyai` when avoiding a global install. If a
64
+ Python SDK installation or older npm install also provides a `pyai` executable,
65
+ that explicit path selects this CLI. `pyai --version` identifies the package
66
+ version; `pyai schema --json` describes the commands supported by that
67
+ installed version.
68
+
69
+ ## First five minutes
70
+
71
+ With an existing key provided by your secret manager as `PYAI_API_KEY`:
72
+
73
+ ```bash
74
+ pyai whoami -j
75
+ pyai voices --language en -j
76
+ pyai speak "Your appointment is confirmed." -o confirmation.wav
77
+ pyai hear confirmation.wav --text-only
78
+ ```
79
+
80
+ For a new isolated sandbox:
81
+
82
+ ```bash
83
+ pyai auth sandbox -p sandbox
84
+ pyai whoami -p sandbox -j
85
+ pyai speak "Hello from PyAI." -p sandbox -o hello.wav
86
+ ```
87
+
88
+ Each `auth sandbox` call creates a new organization and saves its key privately.
89
+ Reuse the saved profile for subsequent commands. Check the returned scopes and
90
+ expiry; sandbox keys do not automatically include every product. In particular,
91
+ check for `dub:render` before using Dub. An exported `PYAI_API_KEY` takes
92
+ precedence over a profile's key, so unset it when intending to use a saved key.
93
+
94
+ For browser sign-in, an engineer can instead run:
95
+
96
+ ```bash
97
+ pyai login -p work
98
+ pyai whoami -p work -j
99
+ ```
100
+
101
+ ## Shortcuts and one-liners
102
+
103
+ Short forms use the same validation, API routes, and output behavior as the full
104
+ commands. They do not silently choose a different account or skip a job stage.
105
+
106
+ | Shortcut | Full form |
107
+ | --- | --- |
108
+ | `pyai login` | `pyai auth login` |
109
+ | `pyai logout` | `pyai auth logout` |
110
+ | `pyai whoami` | `pyai auth status` |
111
+ | `pyai use work` | `pyai profiles use work` |
112
+ | `pyai say "Hello"` | `pyai speak --text "Hello"` |
113
+ | `pyai hear call.wav` | `pyai transcribe --file call.wav` |
114
+ | `pyai voices` | `pyai voices list` |
115
+ | `pyai models` | `pyai models list` |
116
+ | `pyai profiles` | `pyai profiles list` |
117
+
118
+ | Short option | Long option | Use |
119
+ | --- | --- | --- |
120
+ | `-o` | `--out` | Save a response or audio file |
121
+ | `-f` | `--file` | Upload a local file |
122
+ | `-t` | `--text` | Supply speech text |
123
+ | `-p` | `--profile` | Select a profile for this command |
124
+ | `-j` | `--json` | Emit machine-readable output |
125
+ | `-h` | `--help` | Show help |
126
+ | `-v` | `--version` | Show package version |
127
+
128
+ Options still need to be valid for the selected command. Write short options
129
+ separately with a space before their value. Quote speech text and paths with
130
+ spaces; `-f` means a file, while overwriting is always the explicit `--force`.
131
+
132
+ ```bash
133
+ # A voiceover from a text file
134
+ pyai speak --text-file script.txt --format mp3 -o narration.mp3
135
+
136
+ # A transcript your shell can redirect to a text file
137
+ pyai hear meeting.wav --text-only > meeting.txt
138
+
139
+ # Read a pipe, with no temporary input file
140
+ printf '%s\n' 'The build is ready.' | pyai speak --text-file - -o build.wav
141
+
142
+ # Pass raw audio to another program (ffplay installed separately)
143
+ pyai speak "Build complete." --format wav -o - | ffplay -autoexit -nodisp -i -
144
+
145
+ # Take a catalog snapshot (jq is optional and installed separately)
146
+ pyai voices --language en -j | jq '.data'
147
+
148
+ # Inspect the planned request before creating an Agent
149
+ pyai agents create --data @agent.json --dry-run -j
150
+
151
+ # Find more runnable patterns without making an API request
152
+ pyai recipes
153
+ pyai recipes speak
154
+ pyai recipes --json
155
+ ```
156
+
157
+ Available recipes are `auth`, `speak`, `transcribe`, `dub`, `agent`, `inspect`,
158
+ and `ci`. Recipe examples can make API calls when you execute them; displaying a recipe
159
+ is offline. Shell redirection such as `> meeting.txt` is controlled by your
160
+ shell and may overwrite that file. The CLI's no-overwrite protection applies
161
+ to files written through `--out`.
162
+
163
+ ## Authentication and profiles
164
+
165
+ ### Browser sign-in for engineers
166
+
167
+ ```bash
168
+ pyai login
169
+ pyai login -p production
170
+ pyai whoami -p production -j
171
+ ```
172
+
173
+ The CLI opens the PyAI console and displays a short code. Sign in using the
174
+ console's email or Google login, match the code, choose an active project, and
175
+ approve access. The CLI saves a 30-day API key in the selected local profile.
176
+ The secret is never placed in a browser URL or printed in the login receipt.
177
+ Approval requires the same owner/admin role as creating a key in the console.
178
+ The consent screen shows scopes and expiry; revoke the key from the console's
179
+ API Keys screen when access is no longer needed.
180
+
181
+ For SSH, containers, or a terminal without a browser:
182
+
183
+ ```bash
184
+ pyai login --no-browser -p remote
185
+ ```
186
+
187
+ Open the printed link on a machine with a browser and approve the matching
188
+ code there. The CLI uses outbound polling, so no callback port or port
189
+ forwarding is needed. Requests expire after ten minutes. `--login-timeout
190
+ SECONDS` can shorten the local wait. A decline, expired code, or lost exchange
191
+ returns an error and preserves the existing profile. Begin a new login after a
192
+ failed exchange; an exchanged grant cannot be replayed.
193
+
194
+ `login` uses the web flow even when `PYAI_API_KEY` is exported. `--web` selects
195
+ that flow explicitly. In CI the link is printed without launching a browser
196
+ unless `--web` is supplied. Unattended jobs should use an environment key.
197
+ An older deployment returns `browser_login_unavailable`; key authentication
198
+ continues to work.
199
+
200
+ ### Existing keys and unattended agents
201
+
202
+ A key in `PYAI_API_KEY` works directly without saving a profile. Supply it from
203
+ your CI secret store or secret manager. To persist an existing key locally,
204
+ use stdin so its value is not part of the command's arguments:
205
+
206
+ ```bash
207
+ printf '%s\n' "$PYAI_API_KEY" | pyai login --key-stdin -p production
208
+ pyai profiles -j
209
+ ```
210
+
211
+ `--api-key KEY` is also accepted when explicitly needed. It may be visible in
212
+ shell history or process arguments; environment variables or stdin avoid that
213
+ argument exposure. Do not combine browser flags with key-input flags.
214
+
215
+ ### Account selection and storage
216
+
217
+ ```bash
218
+ pyai use production
219
+ pyai agents list -p sandbox -j
220
+ pyai logout -p sandbox
221
+ ```
222
+
223
+ Login and sandbox creation select the saved profile as active. `use` changes
224
+ the default. `--profile NAME` selects a profile for one command; `PYAI_PROFILE`
225
+ is its environment equivalent. `profiles list` is local; `whoami` checks the
226
+ selected key against `GET /v1/me` and reports its organization and scopes.
227
+ Logout removes a local profile; it does not revoke the server-side key.
228
+
229
+ | Setting | Precedence, highest first | Default |
230
+ | --- | --- | --- |
231
+ | API key | `--api-key`, `PYAI_API_KEY`, selected profile | None |
232
+ | Base URL | `--base-url`, `PYAI_BASE_URL`, selected profile | `https://api.pyai.com` |
233
+ | Profile | `--profile`, `PYAI_PROFILE`, saved active profile | `default` |
234
+ | Config directory | `PYAI_CONFIG_DIR`, `$XDG_CONFIG_HOME/pyai` | `~/.config/pyai` |
235
+
236
+ `https://api.pyai.com/v1` is accepted as a base URL too. HTTPS is required,
237
+ except HTTP on loopback for local development. Profiles are plaintext in
238
+ `config.json`, with directory mode `0700` and file mode `0600` where POSIX
239
+ permissions are supported. Treat the file as a credential store, exclude it
240
+ from source control, and use environment authentication on shared runners.
241
+
242
+ ## Speak and Hear
243
+
244
+ ### Speech output
245
+
246
+ ```bash
247
+ pyai speak "Hello from PyAI." -o hello.wav -j
248
+ pyai voices --language en -j
249
+ pyai speak -t "Welcome back." --voice VOICE_ID --format mp3 -o welcome.mp3
250
+ pyai speak --text-file script.txt --format g711_ulaw -o prompt.raw
251
+ ```
252
+
253
+ Replace `VOICE_ID` with an identifier returned by the catalog. Supply exactly
254
+ one text source: a positional string, `--text`, or `--text-file`. `--text-file
255
+ -` reads UTF-8 stdin. With no explicit text input, piped stdin is read
256
+ automatically: `echo "Hello" | pyai speak -o hello.wav`. With an interactive
257
+ terminal and no input, the command returns an input error promptly. `--model` defaults to `pyai-speak`; formats include `wav`,
258
+ `mp3`, `opus`, `aac`, `flac`, `pcm`, `g711_ulaw`, and `g711_alaw`. Optional
259
+ `--sample-rate` accepts 8000, 16000, 24000, or 48000; G.711 requires 8000.
260
+
261
+ Speak defaults to `pyai-speak.wav`, or a matching extension for `--format`
262
+ (`.raw` for G.711). Choose a new name for each artifact or intentionally pass
263
+ `--force`. The output directory must exist. File writes use a temporary file
264
+ and complete atomically; a failed transfer does not leave a partial destination.
265
+ `--out -` streams bytes to stdout and cannot be combined with `--json`.
266
+ Binary output directly to an interactive terminal is refused.
267
+
268
+ A file-producing command with `--json` returns a receipt containing `path`,
269
+ `bytes`, and `content_type`. It does not put base64 audio in JSON. The response
270
+ format determines the bytes; changing only a filename extension does not
271
+ transcode audio.
272
+
273
+ ### Local and URL transcription
274
+
275
+ ```bash
276
+ pyai transcribe meeting.wav -j
277
+ pyai hear meeting.wav --text-only > meeting.txt
278
+ cat meeting.wav | pyai hear -f - --filename meeting.wav -j
279
+ pyai transcribe --url https://example.com/meeting.wav --wait -j
280
+ pyai transcribe --url https://example.com/meeting.wav --wait --text-only
281
+ ```
282
+
283
+ A positional input is a local file, or a hosted URL when it begins with
284
+ `http://` or `https://`. Use `--file` or `--url` to select the input type
285
+ explicitly.
286
+ Local transcription calls Hear synchronously. URL transcription creates an
287
+ asynchronous job; `--wait` waits for completion, and `--poll` is a compatibility
288
+ alias. `--diarize` and `--idempotency-key` apply to URL jobs. `--language` is an
289
+ STT hint for a local file, but only a Recap summarization hint for URL jobs.
290
+ Read the live API contract for current limits and language support.
291
+
292
+ `--text-only` emits the transcript text with a trailing newline and requires
293
+ `--wait` for a URL job. It is incompatible with `--json`; use normal JSON when
294
+ you need segments, speaker information, timestamps, or the job envelope.
295
+ Large asynchronous results may be returned through `result_url` rather than
296
+ inline text. Retain the job's JSON when downstream processing needs its full
297
+ result; do not assume every successful job has an inline `text` field.
298
+
299
+ ## Dub in one command
300
+
301
+ Discover the deployment's current input and output language capabilities first:
302
+
303
+ ```bash
304
+ pyai request GET /healthz/dub -j
305
+ ```
306
+
307
+ When the deployment lists the desired language pair and your key includes
308
+ `dub:render`, submit, wait, and download audio in one command:
309
+
310
+ ```bash
311
+ pyai dub interview.wav --from en --to hi -o interview-hi.wav --wait-timeout 600
312
+ ```
313
+
314
+ The shortcut accepts a local source or a positional HTTP(S) audio URL. `--from` optionally sets the source language
315
+ and `--to` sets the required target. Output defaults to `pyai-dub.wav`;
316
+ use `--out` to choose another path. Check language capabilities
317
+ before selecting values; languages are enabled per deployment. The command
318
+ performs one submission, bounded polling, then an audio download. It does not
319
+ play audio or start a realtime session. The final JSON form is a file receipt with `job_id`, `status`, `path`,
320
+ `bytes`, and `content_type`.
321
+
322
+ For independent lifecycle control, video inputs, or extra API fields:
323
+
324
+ ```bash
325
+ pyai dub create -f interview.mp4 --language hi --source-language en -j
326
+ pyai dub create --url https://example.com/interview.wav --language hi -j
327
+ pyai dub create -f interview.mp4 --language hi \
328
+ --data '{"preserve_background":true,"timing":"source"}' -j
329
+ pyai dub get DUB_JOB_ID -j
330
+ pyai dub wait DUB_JOB_ID --wait-timeout 600 -j
331
+ pyai dub audio DUB_JOB_ID -o dubbed.wav -j
332
+ pyai dub video DUB_JOB_ID -o dubbed.mp4 -j
333
+ ```
334
+
335
+ Replace `DUB_JOB_ID` with the returned `job_id`. `dub create` accepts exactly
336
+ one of `--file` or `--url`. `--file -` reads binary stdin and `--filename`
337
+ supplies its upload filename. Additional `--data` must be an object and cannot
338
+ repeat a field set by flags. Binary input and JSON `--data @-` cannot both
339
+ consume stdin. Video download requires a job with a video source.
340
+
341
+ A polling timeout does not cancel the remote job. Use the job path returned
342
+ in the error to inspect or resume waiting, then download with `dub audio`.
343
+ Do not blindly resubmit after a timeout or an ambiguous network failure.
344
+
345
+ ## Jobs, Cast, and voice design
346
+
347
+ Separate submit/wait/download commands are useful for long jobs, CI stages,
348
+ and resuming work after a terminal disconnect:
349
+
350
+ ```bash
351
+ pyai jobs list --limit 20 -j
352
+ pyai jobs get JOB_ID -j
353
+ pyai jobs wait JOB_ID --wait-timeout 300 -j
354
+ pyai jobs cancel JOB_ID -j
355
+ pyai cast capabilities -j
356
+ pyai cast direct --data '{"text":"Welcome. Let us begin."}' -j
357
+ pyai cast render --data @cast-render.json -j
358
+ pyai cast wait RENDER_ID --wait-timeout 300 -j
359
+ pyai cast audio RENDER_ID -o performance.wav -j
360
+ pyai clones create -f reference.wav --name "Support voice" -j
361
+ pyai design create --data '{"prompt":"A warm, calm English narrator"}' -j
362
+ pyai design wait DESIGN_ID --wait-timeout 300 -j
363
+ pyai design save DESIGN_ID --data '{"candidate_id":"c1","name":"Narrator"}' -j
364
+ ```
365
+
366
+ Use IDs from actual API responses, including the candidate ID returned by
367
+ voice design. Cast render bodies contain `voice` and either `script` or
368
+ `lines`. Directed lines contain `text`, `emotion`, and `intensity`; discover
369
+ supported values through `cast capabilities`. `cast speech` produces audio
370
+ from one JSON request and requires `--out`.
371
+
372
+ | Job family | Successful state | Failed terminal states |
373
+ | --- | --- | --- |
374
+ | Transcription | `completed` | `failed`, `cancelled` |
375
+ | Voice design | `completed` | `failed` |
376
+ | Cast | `done` | `error` |
377
+ | Dub | `done` | `error` |
378
+
379
+ Waiting commands use the appropriate status vocabulary and return a nonzero
380
+ exit code for a failed job. `jobs cancel` stops pending work and retains
381
+ already completed results. Explicit submission and wait commands return API
382
+ JSON; only the top-level Dub shortcut also downloads the audio automatically.
383
+
384
+ ## Manage resources with JSON
385
+
386
+ Keep configuration in files that engineers and coding agents can review:
387
+
388
+ ```bash
389
+ pyai agents create --data '{"name":"Support","greeting":"How can I help?"}' -j
390
+ pyai agents update AGENT_ID --data @agent-update.json --dry-run -j
391
+ pyai agents update AGENT_ID --data @agent-update.json -j
392
+ printf '%s\n' '{"greeting":null}' | pyai agents update AGENT_ID --data @- -j
393
+ pyai vocabulary set --data '{"terms":["AcmeCloud"],"enabled_for":["batch"]}' -j
394
+ ```
395
+
396
+ `--data` accepts inline JSON, `@PATH`, or `@-` for stdin. Dedicated mutations
397
+ expect an object. `request` also accepts arrays, scalars, and null where the
398
+ API supports them. Agent updates modify only supplied fields; use `null` to
399
+ clear a field. Consult the API schema before constructing nested tool bindings,
400
+ metadata, extraction schemas, or other product configuration.
401
+
402
+ Commands do not open terminal confirmation prompts, including explicit delete
403
+ and cancel commands. Use `--dry-run` to review intent before a mutation. A dry
404
+ run validates and reads local input, but does not make the planned API call;
405
+ it does not validate remote permissions, available credit, or every server-side
406
+ schema rule. Request data may itself be sensitive, so handle saved previews
407
+ and configuration files appropriately.
408
+
409
+ Paginated list commands support `--limit 1..100` and `--cursor TOKEN`. Copy the
410
+ returned `next_cursor` into the next request; one command returns one page.
411
+ Use `request --query name=value` for API filters without a dedicated option.
412
+
413
+ ## Command reference
414
+
415
+ Each verb after a group retains that group: `agents get ID`, `agents create`,
416
+ and so on. Run `pyai GROUP --help` or `pyai COMMAND --help` for exact flags.
417
+ Use `pyai help all` for the expanded help listing.
418
+ The offline schema is authoritative for the installed build.
419
+
420
+ | Group | Commands |
421
+ | --- | --- |
422
+ | Identity | `auth login`, `auth sandbox`, `auth status`, `auth logout` |
423
+ | Profiles | `profiles list`, `profiles use NAME` |
424
+ | Catalogs | `models list`, `voices list`, `voices get ID` |
425
+ | Speech | `speak [TEXT]`, `transcribe [FILE_OR_URL]` |
426
+ | Shortcuts | `login`, `logout`, `whoami`, `use NAME`, `say [TEXT]`, `hear [FILE]`, `dub INPUT --from LANG --to LANG --out PATH` |
427
+ | Agents | `agents list`, `get ID`, `create`, `update ID`, `delete ID` |
428
+ | Transcription jobs | `jobs list`, `create`, `get ID`, `wait ID`, `cancel ID` |
429
+ | Cloned voices | `clones list`, `create`, `delete ID` |
430
+ | Voice design | `design create`, `get ID`, `wait ID`, `save ID` |
431
+ | Cast | `cast capabilities`, `direct`, `speech`, `render`, `get ID`, `wait ID`, `audio ID` |
432
+ | Dub jobs | `dub create`, `get ID`, `wait ID`, `audio ID`, `video ID` |
433
+ | Recap | `recap list`, `get ID`, `config`, `configure` |
434
+ | Trace | `trace list`, `get ID`, `config`, `configure`, `findings`, `violations`, `exposure` |
435
+ | Tools | `tools list`, `get ID`, `create`, `update ID`, `delete ID` |
436
+ | Hear vocabulary | `vocabulary get`, `set` |
437
+ | AMD | `amd calls`, `get ID`, `config`, `configure` |
438
+ | Discovery | `schema`, `schema --openapi`, `recipes [NAME]`, `help` |
439
+ | Project setup | `init DIRECTORY --template agent\|typescript\|python` |
440
+ | Generic API | `request METHOD /PATH` |
441
+ | Diagnostics | `doctor`, `smoke` |
442
+
443
+ JSON mutations require `--data`; binary downloads and `cast speech` require
444
+ `--out`. Voice enrollment and Dub submission use their file/URL flags.
445
+
446
+ ## API discovery and generic requests
447
+
448
+ ```bash
449
+ pyai schema -j
450
+ pyai schema speak -j
451
+ pyai schema agents create -j
452
+ pyai schema --openapi -j > openapi.json
453
+ pyai request GET /v1/voices --query language=en --query tier=natural -j
454
+ pyai request POST /v1/agents --data @agent.json --dry-run -j
455
+ pyai request GET /v1/cast/render_jobs/RENDER_ID/audio -o render.wav
456
+ ```
457
+
458
+ `schema` is offline and describes CLI syntax. Supply a command or group to
459
+ reduce context: `schema agents` returns the Agent commands, while
460
+ `schema agents create` returns that command. Aliases such as `say` resolve
461
+ to their canonical commands. `schema --openapi` fetches the
462
+ configured deployment's current API contract without requiring authentication.
463
+ The API contract describes bodies, responses, scopes, and endpoint availability;
464
+ the CLI schema does not replace it. `--openapi` does not accept a command
465
+ filter. Use `request` for routes without dedicated
466
+ commands and newly added API features.
467
+
468
+ Generic requests accept `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, and `DELETE`.
469
+ Paths must be relative to the configured API origin, such as `/v1/models`.
470
+ Absolute URLs, cross-origin requests, redirects, and credential query parameters
471
+ are rejected. Repeat `--query` to add parameters. `GET` and `HEAD` do not accept
472
+ `--data`. `request` handles JSON and raw response downloads; specialized upload
473
+ commands handle multipart audio.
474
+
475
+ Terminal JSON redacts credential fields. If an API response includes a token
476
+ your application needs, `request ... --out response.json` saves the original
477
+ response with private file permissions and emits only a path/size receipt.
478
+ That file can contain secrets; it should not be committed or printed to logs.
479
+
480
+ ## Agent and project handoff
481
+
482
+ Create a starter directory without credentials or network calls:
483
+
484
+ ```bash
485
+ pyai init voice-project
486
+ pyai init voice-ts --template typescript
487
+ pyai init voice-python --template python
488
+ pyai init voice-project --template agent --dry-run -j
489
+ ```
490
+
491
+ `agent` is the default template. The target must be a new directory with an
492
+ existing parent; scaffolding
493
+ does not merge into or overwrite an existing project. Every template includes `README.md`, `PYAI.md`, `.env.example`, and
494
+ `.gitignore`. The agent template adds `agent.json`, `speech.json`, and
495
+ `job.json`; TypeScript adds `main.ts` and `package.json`; Python adds `main.py`.
496
+ `PYAI.md` provides an integration handoff for an engineer or coding agent. Follow the
497
+ starter's README for its files, runtime prerequisites, and next commands.
498
+ Scaffolding does not install dependencies, create an account, or make paid API
499
+ calls. JSON output includes `directory`, `template`, `files`, `created`, and
500
+ `next_steps`. Read the generated files before running the starter.
501
+
502
+ For an existing repository, point the agent at these resources:
503
+
504
+ - [CLI agent guide](https://pyai.com/cli-agent-guide.md): compact integration workflow and machine contract.
505
+ - [Raw CLI guide](https://pyai.com/cli.md): readable without rendering the website.
506
+ - [Canonical CLI documentation](https://docs.pyai.com/guides/cli): task-oriented guide.
507
+ - [CLI schema snapshot](https://pyai.com/cli-schema.json): machine-readable CLI syntax for this release; inspect your installed CLI for its supported commands.
508
+ - [Live OpenAPI](https://api.pyai.com/openapi.json): API source of truth.
509
+ - [API agent index](https://api.pyai.com/llms.txt): product and protocol references.
510
+
511
+ A useful task instruction is:
512
+
513
+ > Read PYAI.md, inspect `pyai schema --json`, and fetch `pyai schema --openapi
514
+ > --json`. Use PYAI_API_KEY from the environment without printing it. Discover
515
+ > voices before selecting one. Preview resource mutations with `--dry-run`.
516
+ > Implement the requested workflow with bounded waits, stable error-code
517
+ > handling, and a runnable verification command. Use the SDK for realtime audio.
518
+
519
+ Keep real credentials out of prompts and repository instruction files. The
520
+ CLI can configure Agent resources, but that is separate from opening an Omni
521
+ conversation. Use the [TypeScript/Python SDK guides](https://docs.pyai.com/guides/sdks)
522
+ for realtime transports. The [PyAI MCP server](https://docs.pyai.com/guides/use-pyai-in-cursor) offers a tool
523
+ interface for hosts that prefer MCP over shell commands.
524
+
525
+ ## Automation contract
526
+
527
+ | Global flag | Meaning | Default |
528
+ | --- | --- | --- |
529
+ | `--json`, `-j` | Compact JSON result; structured errors | Off |
530
+ | `--profile`, `-p` | Profile for this invocation | Selected profile |
531
+ | `--base-url` | Deployment origin | Profile/env or production API |
532
+ | `--api-key` | Explicit opaque key | Environment/profile |
533
+ | `--timeout` | Seconds per HTTP request, including body download | `30` |
534
+ | `--retries` | Read retry count, integer `0..5` | `2` |
535
+ | `--dry-run` | Preview the operation without executing it | Off |
536
+
537
+ Waiting commands additionally accept `--wait-timeout` (default 120 seconds)
538
+ and `--poll-interval` (default 2 seconds). The wait deadline bounds polling;
539
+ individual requests also respect `--timeout`. Browser login has its separate
540
+ `--login-timeout`. A local timeout does not revoke credentials or cancel jobs.
541
+
542
+ Automatic retries apply only to reads, with rate-limit delays respected.
543
+ Mutations are not automatically retried to avoid duplicate resources or jobs.
544
+ Commands accepting JSON mutation bodies also accept `--idempotency-key TOKEN`,
545
+ with support determined by the API route. Reuse an idempotency key only for
546
+ retries of the same intended operation and body. Never assume the option makes
547
+ every endpoint idempotent.
548
+
549
+ With `--json`, stdout contains one command result and errors use stderr:
550
+
551
+ ```json
552
+ {"error":{"code":"unauthorized","message":"...","status":401}}
553
+ ```
554
+
555
+ `status`, `request_id`, job `path`, and other details are present when available.
556
+ Branch on `error.code` and the exit code; do not parse human wording. Unknown
557
+ fields may be added, and normal API result shapes follow the live API contract.
558
+ Do not assume all successful commands wrap output under a `data` property.
559
+
560
+ Browser login is the stderr exception to a single final error object: it emits
561
+ newline-delimited public events such as `authorization_required` with a link,
562
+ user code, and expiry, and `browser_unavailable` if launch fails. Its final
563
+ success receipt is still a single stdout JSON object. Progress never mixes
564
+ with stdout. Diagnostics emit one result object with `--json`, including failed
565
+ checks, and set a nonzero status when a check fails.
566
+
567
+ | Exit code | Meaning | Typical next step |
568
+ | --- | --- | --- |
569
+ | `0` | Success | Consume result/artifact |
570
+ | `1` | API, job, or operational failure | Inspect error code and job status |
571
+ | `2` | Invalid arguments or local configuration/input | Fix flags, JSON, paths, or config |
572
+ | `3` | Authentication or permission failure | Check key, profile, and scopes |
573
+ | `4` | Network failure or timeout | Inspect remote state before a mutation retry |
574
+ | `130` | Interrupted | Resume an existing job if appropriate |
575
+
576
+ The CLI bounds stdin at 128 MiB; use a file or hosted URL for larger inputs.
577
+ This bound is a client limit, not a promise about server upload limits.
578
+ Secrets known to the CLI and credential-shaped JSON fields are redacted from
579
+ terminal output. Redaction does not replace reviewing arbitrary application
580
+ payloads, downloaded responses, or shell tracing settings.
581
+
582
+ ## Troubleshooting
583
+
584
+ | Symptom | Action |
585
+ | --- | --- |
586
+ | New command is unknown | Inspect `pyai schema -j`; build this checkout and check which `pyai` executable your shell resolves. The npm release may be older. |
587
+ | Browser login is unavailable | Deploy the matching browser-auth components or use `PYAI_API_KEY` / `login --key-stdin`. |
588
+ | Browser does not open on SSH | Run `login --no-browser` and approve the printed code from another device. |
589
+ | Login expired or was declined | Run login again and approve the new code before expiry. Existing profile credentials remain unchanged on failure. |
590
+ | Wrong account despite `-p` | An exported `PYAI_API_KEY` overrides the profile key; also inspect `PYAI_BASE_URL` and `PYAI_PROFILE`. |
591
+ | `401` / exit `3` | Check expiry and revocation, then login or replace the key. |
592
+ | `403` / exit `3` | Check required scope and organization/project access. Repeating the request will not add permissions. |
593
+ | `402` | Check organization credit, plan limits, and per-key budget in the console. |
594
+ | `429` | Honor retry delays and check concurrency/daily caps. Avoid a tight retry loop. |
595
+ | Output already exists | Pick another name or intentionally pass `--force`; ensure the parent directory exists. |
596
+ | `--out -` rejected | Pipe bytes to a process and remove `--json`, or use a file destination for a JSON receipt. |
597
+ | Job timed out | Inspect the returned job path and use `jobs wait`, `cast wait`, `design wait`, or `dub wait`; the job may still be running. |
598
+ | Text-only job has no inline transcript | Inspect normal job JSON and its `result_url`; preserve the result envelope for larger outputs. |
599
+ | JSON sent to an API is rejected | Fetch the live OpenAPI; CLI `--dry-run` does not prove server schema validity. |
600
+
601
+ `pyai doctor` checks credentials, catalogs, and a Speak-to-Hear round trip.
602
+ `pyai smoke` runs a smaller catalog and synthesis check. These diagnostics make
603
+ real API calls and consume the applicable usage quota; use `--dry-run` to
604
+ inspect the request plan first. For a problem report, retain the CLI schema,
605
+ exit/error code, request ID, and a redacted reproduction. Never include the key.
606
+
607
+ Realtime microphone sessions, live Hear streams, Omni conversations, and live
608
+ AMD streams require a realtime client. Use the SDK and canonical protocol docs
609
+ for those workflows; the CLI covers files, REST operations, and job lifecycles.
610
+
611
+ ## Speech and calling
612
+
613
+ Read the [speech and calling decision tree](https://pyai.com/agents/speech-calling.md),
614
+ or run `pyai recipes calling --json` / `pyai recipes omni --json`.
615
+
616
+ - `numbers search`, `numbers list`, `numbers buy`, `numbers bind ID`: discover,
617
+ purchase and route managed numbers. `numbers bind` takes `--data` with
618
+ `agent_id`; null unassigns.
619
+ - `calls create`, `calls list`, `calls get ID`, `calls wait ID`: real outbound
620
+ calling. Creation requires a request body from the live contract.
621
+ - `omni calls`, `omni get ID`, `omni summary ID`, `omni transcript ID`,
622
+ `omni recording ID --out call.wav`: read session artifacts, including inbound
623
+ calls. Use `--session-label` on `omni calls` to find an Agent's sessions.
624
+
625
+ Purchases and outbound calls require `--confirm` and `--idempotency-key`. Use
626
+ `--dry-run` for inspection before authorized execution. `needs_human` (exit 2)
627
+ means confirmation is missing and no submission was made. Outbound test calls
628
+ are real calls; sandbox keys do not grant dialing or purchase permissions.
629
+ The API enforces deployment availability, scopes, billing and policy gates.
630
+
631
+ `calls wait` accepts `--wait-timeout` and `--poll-interval`; preserve the original
632
+ ID after a deadline. `dispatch_unknown` stops waiting with a failure result for
633
+ inspection, never redial. Follow `calls get`'s `artifacts.omni_call_id` to session
634
+ artifacts. A completed call does not guarantee a recording or summary.