Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/scripts/verify_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pathlib import Path

import imageio_ffmpeg
from agent_voice.media import CONTENT_TYPES, generating_audio

SERVICE_URL = "http://127.0.0.1:18765"

Expand Down Expand Up @@ -129,6 +130,9 @@ def main() -> None:
output_dir = Path(os.environ["RUNNER_TEMP"]) / "agent-voice-package-e2e"
output_dir.mkdir(parents=True, exist_ok=True)

for audio_format in CONTENT_TYPES:
assert generating_audio(audio_format)

subprocess.run([str(cli), "setup", "--model", "int8"], check=True)
offline_doctor = run_cli(cli, "doctor", "--service-url", SERVICE_URL, "--json")
check_doctor(offline_doctor, "warn")
Expand Down Expand Up @@ -200,18 +204,27 @@ def main() -> None:
)
check_doctor(online_doctor, "pass")

service_wav = output_dir / "service.wav"
remote = run_cli(
cli,
"speak",
f"{system} localhost service verification.",
"--service-url",
SERVICE_URL,
"--output",
str(service_wav),
"--label",
"Package Service E2E",
"--format",
"wav",
)
service_wav = Path(str(remote["path"]))
assert remote["backend"] == "service"
assert remote["generation"] == {"state": "started"}
assert "stream_url" in remote["delivery"]
assert "playback" not in remote
pending = service_wav.with_name(f".{service_wav.name}.pending")
deadline = time.monotonic() + 120
while pending.is_file() and time.monotonic() < deadline:
time.sleep(0.1)
assert not pending.exists(), "background generation did not finish"
validate_wav(service_wav)
finally:
service.terminate()
Expand Down
29 changes: 12 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,8 @@ agent-voice speak "The build is finished."
# Agent output through stdin
printf '%s' "$TEXT" | agent-voice speak --label build-summary

# Spoken response text and written response Markdown in one command
agent-voice speak "$RESPONSE_AS_TEXT" \
--markdown "$RESPONSE_AS_MARKDOWN" --label response

# Use separate files for a long spoken response and its written Markdown
agent-voice speak --response-file "$RESPONSE_AS_MARKDOWN_FILE" \
< "$RESPONSE_AS_TEXT_FILE"
# Use a file for a long spoken response
agent-voice speak --label response < "$RESPONSE_AS_TEXT_FILE"

# Choose the output and delivery
agent-voice speak "Here is your summary." \
Expand All @@ -180,8 +175,6 @@ agent-voice speak "Here is your summary." \
| --- | --- |
| `-o, --output PATH` | Write to an exact path. |
| `--label TEXT` | Set the managed filename prefix. |
| `--markdown TEXT` | Show an inline Markdown response in the viewer. |
| `--response-file PATH` | Show a Markdown response in the browser viewer. |
| `--output-dir DIR` | Choose the managed output directory. |
| `-f, --format FORMAT` | Use `wav`, `mp3`, `opus`, or `m4a`. |
| `-v, --voice NAME` | Select a voice. |
Expand All @@ -190,12 +183,14 @@ agent-voice speak "Here is your summary." \
| `-p, --play` | Start local playback after creation, without waiting for it to finish. |
| `--play-after SECONDS` | Schedule local playback after creation, without waiting. |
| `--controls` | Include experimental `agent-voice://` playback control links. |
| `--wait` | Wait for the completed recording instead of returning live links. |
| `--no-service` | Run the same Agent Voice model inside this command, then unload it. |
| `--model-id ID`, `--variant NAME` | Select a model and build. |

`speak` prints one JSON receipt with the absolute recording path, file URI,
audio metadata, playback state (`started` or `scheduled`), and available viewer
links. This makes the command reliable for both people and agents.
`speak` prints one JSON receipt with the absolute recording path, generation
state, audio metadata, playback state (`started` or `scheduled`), and available
delivery links. By default it returns live links while generation continues;
pass `--wait` when the completed file is required before the command returns.

### Configure defaults

Expand Down Expand Up @@ -258,11 +253,11 @@ agent-voice viewer stop
The lightweight viewer starts automatically when needed and serves only local
recordings. It prefers `http://127.0.0.1:8779` and selects a free port if that
port is unavailable. Each managed recording keeps an editable `.txt` source
beside it. At startup and every six hours, the viewer removes owned audio older
than four days and 18 hours; files without Agent Voice source, transcript, and
language metadata are left alone. Opening a player or audio URL regenerates
missing audio from its source with the original language and current voice and
speed.
beside it. At startup and every hour, the viewer removes temporary live-stream
audio older than one hour and owned recordings older than four days and 18
hours; files without Agent Voice source, transcript, and language metadata are
left alone. Opening a player or audio URL regenerates missing audio from its
source with the original language and current voice and speed.

Playback commands return as soon as local playback starts, or immediately with
`scheduled` when a delay is requested; they never wait for the recording to end.
Expand Down
3 changes: 0 additions & 3 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,3 @@ This project depends on third-party software and downloads model assets.
- **imageio-ffmpeg** — Copyright imageio-ffmpeg contributors, licensed under the BSD 2-Clause License. Its platform wheels include an FFmpeg executable: <https://github.com/imageio/imageio-ffmpeg>
- **FFmpeg** — The executable bundled by imageio-ffmpeg is licensed under GNU GPL version 2 or later. License and source information: <https://ffmpeg.org/legal.html>
- **miniaudio Python bindings** — Copyright Irmen de Jong and contributors, licensed under the MIT License: <https://github.com/irmen/pyminiaudio>
- **linkify-it-py and uc-micro-py** — Copyright their contributors, licensed under the MIT License: <https://github.com/tsutsu3/linkify-it-py>
- **markdown-it-py and mdurl** — Copyright their contributors, licensed under the MIT License: <https://github.com/executablebooks/markdown-it-py>
- **Pygments** — Copyright Georg Brandl and contributors, licensed under the BSD 2-Clause License: <https://pygments.org>
5 changes: 1 addition & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "agent-voice"
version = "0.8.0"
version = "0.9.0"
description = "Local, free voice artifacts for AI agents"
readme = "README.md"
requires-python = ">=3.11,<3.14"
Expand All @@ -24,11 +24,8 @@ dependencies = [
"filelock>=3.20,<4",
"imageio-ffmpeg==0.6.0",
"kokoro-onnx==0.5.0",
"linkify-it-py>=2,<3",
"markdown-it-py>=4.2,<5",
"miniaudio==1.71",
"numpy>=2,<3",
"pygments>=2.19,<3",
]

[project.urls]
Expand Down
12 changes: 5 additions & 7 deletions skills/create-speech-recording-controls/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ matching voice with `agent-voice voices` and pass `--voice` and `--lang`.

## Prepare

Set `RESPONSE_AS_MARKDOWN` to the original text or Markdown. Set
`RESPONSE_AS_TEXT` to its spoken form. Never use `RESPONSE_AS_MARKDOWN` as the
speech input.
Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text.
Set `RESPONSE_AS_TEXT` to the text's spoken form.

- For supplied text, preserve every word and punctuation mark in order while
translating presentation syntax into speech.
Expand All @@ -24,13 +21,13 @@ Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text.
## Record

```sh
agent-voice speak "$RESPONSE_AS_TEXT" --markdown "$RESPONSE_AS_MARKDOWN" --controls
agent-voice speak "$RESPONSE_AS_TEXT" --controls
```

For long text, use temporary files outside the workspace and remove them afterward:

```sh
agent-voice speak --label "$LABEL" --response-file "$RESPONSE_AS_MARKDOWN_FILE" --controls < "$RESPONSE_AS_TEXT_FILE"
agent-voice speak --label "$LABEL" --controls < "$RESPONSE_AS_TEXT_FILE"
```

Set `LABEL` to a short subject. For an exact requested filename, replace
Expand All @@ -42,7 +39,8 @@ blocking.

## Deliver

Use [default.md](references/delivery/default.md).
Use [default.md](references/delivery/default.md) immediately after `speak`
returns a receipt with generation.state: "started"

## Setup

Expand Down
15 changes: 6 additions & 9 deletions skills/create-speech-recording-desktop/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ matching voice with `agent-voice voices` and pass `--voice` and `--lang`.

## Prepare

Set `RESPONSE_AS_MARKDOWN` to the original text or Markdown. Set
`RESPONSE_AS_TEXT` to its spoken form. Never use `RESPONSE_AS_MARKDOWN` as the
speech input.
Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text.
Set `RESPONSE_AS_TEXT` to the text's spoken form.

- For supplied text, preserve every word and punctuation mark in order while
translating presentation syntax into speech.
Expand All @@ -24,23 +21,23 @@ Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text.
## Record

```sh
agent-voice speak "$RESPONSE_AS_TEXT" --markdown "$RESPONSE_AS_MARKDOWN"
agent-voice speak "$RESPONSE_AS_TEXT" --wait
```

For long text, use temporary files outside the workspace and remove them afterward:

```sh
agent-voice speak --label "$LABEL" --response-file "$RESPONSE_AS_MARKDOWN_FILE" < "$RESPONSE_AS_TEXT_FILE"
agent-voice speak --label "$LABEL" --wait < "$RESPONSE_AS_TEXT_FILE"
```

On Antigravity App, follow the special
On OpenCode Desktop, omit `--wait`. On Antigravity App, follow the special
[recording and delivery instructions](references/delivery/antigravity.md).

Set `LABEL` to a short subject. For an exact requested filename, replace
`--label "$LABEL"` with `--output "$PATH"`.

Use the returned `path` to deliver the recording. For speaker playback, add
`-p`; continue after the result reports `playback.state: "started"`. Add
Use the matching delivery reference. For speaker playback, add `-p`; continue
after the result reports `playback.state: "started"`. Add
`--play-after SECONDS` to schedule it without blocking.

## Deliver
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# OpenCode Desktop

Set `PLAYER_ID` to a unique lowercase ID. Set `RECORDING_NAME` to the basename
of the returned `path`. Set `audio_url` to `delivery.audio_url`. Replace the
placeholders in the template. Render the HTML directly without the code fence.
of `path`. Set `AUDIO_SOURCE` to `delivery.stream_url` when present, otherwise
to `delivery.audio_url`. Replace the placeholders and render the HTML directly
without the code fence.

```html
<div id="$PLAYER_ID" style="box-sizing:border-box;width:min(100%,520px);margin:0 0 1.125rem;color:CanvasText;color-scheme:light dark">
<div style="margin:0 0 .375rem;font:700 16px/1.2 ui-sans-serif,system-ui,-apple-system,sans-serif;letter-spacing:-.01em;color:CanvasText">$RECORDING_NAME</div>
<div style="display:flex;align-items:center;gap:12px;min-width:0">
<audio controls preload="metadata" src="$audio_url" style="display:block;min-width:0;width:100%;height:42px">
<a href="$audio_url">Play recording</a>
<audio controls preload="auto" src="$AUDIO_SOURCE" style="display:block;min-width:0;width:100%;height:42px">
<a href="$AUDIO_SOURCE">Play recording</a>
</audio>
<svg viewBox="0 0 100 100" role="img" aria-label="Agent Voice" style="display:block;flex:0 0 42px;width:42px;height:42px;fill:none;stroke:#ff6037;stroke-width:4;stroke-linecap:round;stroke-linejoin:round">
<path d="M8 52h4c3 0 3-7 6-7s3 13 6 13c4 0 4-25 8-25s5 35 10 35c5 0 5-37 10-37s4 31 8 31c3 0 3-16 6-16s3 6 6 6" style="stroke-width:4.5"/>
Expand Down
12 changes: 5 additions & 7 deletions skills/create-speech-recording/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ matching voice with `agent-voice voices` and pass `--voice` and `--lang`.

## Prepare

Set `RESPONSE_AS_MARKDOWN` to the original text or Markdown. Set
`RESPONSE_AS_TEXT` to its spoken form. Never use `RESPONSE_AS_MARKDOWN` as the
speech input.
Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text.
Set `RESPONSE_AS_TEXT` to the text's spoken form.

- For supplied text, preserve every word and punctuation mark in order while
translating presentation syntax into speech.
Expand All @@ -24,13 +21,13 @@ Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text.
## Record

```sh
agent-voice speak "$RESPONSE_AS_TEXT" --markdown "$RESPONSE_AS_MARKDOWN"
agent-voice speak "$RESPONSE_AS_TEXT"
```

For long text, use temporary files outside the workspace and remove them afterward:

```sh
agent-voice speak --label "$LABEL" --response-file "$RESPONSE_AS_MARKDOWN_FILE" < "$RESPONSE_AS_TEXT_FILE"
agent-voice speak --label "$LABEL" < "$RESPONSE_AS_TEXT_FILE"
```

Set `LABEL` to a short subject. For an exact requested filename, replace
Expand All @@ -42,7 +39,8 @@ Use the returned `path` to deliver the recording. For speaker playback, add

## Deliver

Use [default.md](references/delivery/default.md).
Use [default.md](references/delivery/default.md) immediately after `speak`
returns a receipt with generation.state: "started"

## Setup

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fence exactly as shown. Send the block without the outer four-backtick fence.

Agent Voice recording

Listen: [web player]($browser_url) · [media app]($file_uri) · [web audio]($audio_url)
Listen: [web player]($browser_url) · [media app]($file_uri)

Or run in the terminal

Expand Down
30 changes: 14 additions & 16 deletions skills/spoken-response-controls/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: spoken-response-controls
description: Create the spoken semantic twin of an assistant response with clickable Agent Voice playback controls. Use when the user explicitly requests controlled playback on a compatible desktop renderer.
description: Read an assistant response aloud with clickable Agent Voice playback controls. Use when the user explicitly requests controlled playback on a compatible desktop renderer.
---

# Spoken Response Controls

A spoken response is the audio semantic twin of an assistant response.
A spoken response reads the assistant response aloud without rewriting it.

## Mode

Expand All @@ -22,31 +22,29 @@ Choose the mode from the user's request:
1. Select the response:
- For `Single` and `Thread`, finalize the current response.
- For `Previous`, use the most recent assistant response.
2. Set `RESPONSE_AS_MARKDOWN` to the selected response's Markdown. Use real line
breaks, not escaped `\n` text.
3. Set `RESPONSE_AS_TEXT` to its spoken semantic twin: preserve meaning, detail,
and order while translating formatting into natural speech. Never use
`RESPONSE_AS_MARKDOWN` as the speech input.
- For tables, state the headers once and read each row as labeled values.
- For long code, explain it naturally and refer to the written response for
exact syntax.
4. Set `LABEL` to `SR`. When a thread title is already available, use
2. Set `RESPONSE_AS_TEXT` to a read-aloud copy:
- **Fidelity:** preserve wording and order; do not summarize or rephrase.
- **Non-prose:** translate formatting and read tables naturally. Explain code
or visuals when useful; otherwise briefly introduce them ("Here is the
code" or "See the diagram below") and continue.
3. Set `LABEL` to `SR`. When a thread title is already available, use
`<title> - SR`.
5. Create the recording with the configured voice, speed, and format:
4. Create the recording with the configured voice, speed, and format:

```sh
agent-voice speak "$RESPONSE_AS_TEXT" --markdown "$RESPONSE_AS_MARKDOWN" --label "$LABEL" --controls
agent-voice speak "$RESPONSE_AS_TEXT" --label "$LABEL" --controls
```

For long responses, use temporary files outside the workspace and remove them
afterward:

```sh
agent-voice speak --label "$LABEL" --response-file "$RESPONSE_AS_MARKDOWN_FILE" --controls < "$RESPONSE_AS_TEXT_FILE"
agent-voice speak --label "$LABEL" --controls < "$RESPONSE_AS_TEXT_FILE"
```

6. Place the controls above the written response or `Previous` confirmation using
[default.md](references/delivery/default.md).
5. Place the controls above the written response or `Previous` confirmation using
[default.md](references/delivery/default.md) immediately after `speak`
returns a receipt with generation.state: "started"

For speaker playback, add `-p` and continue after the result reports
`playback.state: "started"`. Add `--play-after SECONDS` to schedule it without
Expand Down
29 changes: 14 additions & 15 deletions skills/spoken-response-desktop/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: spoken-response-desktop
description: Create and embed the spoken semantic twin of a response in Codex Desktop, Antigravity App, or OpenCode Desktop.
description: Read and embed an assistant response aloud in Codex Desktop, Antigravity App, or OpenCode Desktop.
---

# Spoken Response Desktop

A spoken response is the audio semantic twin of an assistant response.
A spoken response reads the assistant response aloud without rewriting it.

## Mode

Expand All @@ -22,32 +22,31 @@ Choose the mode from the user's request:
1. Select the response:
- For `Single` and `Thread`, finalize the current response.
- For `Previous`, use the most recent assistant response.
2. Set `RESPONSE_AS_MARKDOWN` to the selected response's Markdown. Use real line
breaks, not escaped `\n` text.
3. Set `RESPONSE_AS_TEXT` to its spoken semantic twin: preserve meaning, detail,
and order while translating formatting into natural speech. Never use
`RESPONSE_AS_MARKDOWN` as the speech input.
- For tables, state the headers once and read each row as labeled values.
- For long code, explain it naturally and refer to the written response for
exact syntax.
4. Set `LABEL` to `SR`. When a thread title is already available, use
2. Set `RESPONSE_AS_TEXT` to a read-aloud copy:
- **Fidelity:** preserve wording and order; do not summarize or rephrase.
- **Non-prose:** translate formatting and read tables naturally. Explain code
or visuals when useful; otherwise briefly introduce them ("Here is the
code" or "See the diagram below") and continue.
3. Set `LABEL` to `SR`. When a thread title is already available, use
`<title> - SR`.
5. Create the recording with the configured voice, speed, and format. On
4. Create the recording with the configured voice, speed, and format. On
Antigravity App, follow the special
[recording and delivery instructions](references/delivery/antigravity.md).

```sh
agent-voice speak "$RESPONSE_AS_TEXT" --markdown "$RESPONSE_AS_MARKDOWN" --label "$LABEL"
agent-voice speak "$RESPONSE_AS_TEXT" --label "$LABEL" --wait
```

For long responses, use temporary files outside the workspace and remove them
afterward:

```sh
agent-voice speak --label "$LABEL" --response-file "$RESPONSE_AS_MARKDOWN_FILE" < "$RESPONSE_AS_TEXT_FILE"
agent-voice speak --label "$LABEL" --wait < "$RESPONSE_AS_TEXT_FILE"
```

6. Place the audio above the written response or `Previous` confirmation. Use
On OpenCode Desktop, omit `--wait` and go straight to the delivery reference.

5. Place the audio above the written response or `Previous` confirmation. Use
the first matching delivery reference:
- Antigravity App: [antigravity.md](references/delivery/antigravity.md)
- OpenCode Desktop: [opencode-desktop.md](references/delivery/opencode-desktop.md)
Expand Down
Loading