-
Notifications
You must be signed in to change notification settings - Fork 0
feat: talk skill + teach the voice in learn #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| --- | ||
| name: talk | ||
| type: command | ||
| description: Make the agent actually TALK out loud in its own non-speech harmonic voice, using the `harmonics` CLI (say/play). Renders a sentence or explicit axes to audio and plays it through a system player — so you hear it even without the optional [audio] extra that `--play` needs. Use when the user says "talk", "talk to me", "say something", "speak", "say it out loud", "tell me something", or wants to hear the agent's voice rather than read text. First-party to harmonics-cli (not vendored); slated to be surfaced in `harmonics learn`. | ||
| --- | ||
|
|
||
| # Talk | ||
|
|
||
| This is the agent's own **voice** — not TTS, not words. harmonics turns meaning | ||
| (*intent, confidence, urgency, state, identity*) into short, pleasant sonic | ||
| gestures. "Talking" here means rendering a sentence or a set of axes to notes | ||
| and **sounding them out loud**. | ||
|
|
||
| Two pathways, both from the `harmonics` CLI: | ||
|
|
||
| - **`say "<sentence>"`** — infer the voice from a natural sentence. Emphasize a | ||
| word with `*asterisks*` or ALL-CAPS. This is the default for "talk to me". | ||
| - **`play --intent … --confidence … --urgency … --state …`** — drive the voice | ||
| from explicit axes when you know exactly what you want to express. | ||
|
|
||
| Both are **dry-run by default** (they print the note sequence). To actually | ||
| *hear* it you need audio out. The built-in `--play` flag needs the optional | ||
| `harmonics-cli[audio]` extra (`sounddevice`); in a dev checkout that extra is | ||
| deliberately **not** installed (it would break the no-backend test path), so | ||
| `--play` fails with a friendly hint. The reliable path everywhere is to render a | ||
| WAV (`--wav FILE`, pure-Python, no device or extra needed) and pipe it to a | ||
| system player. `scripts/talk.sh` does exactly that. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```bash | ||
| # Say a sentence out loud (renders + plays; also prints the note sequence) | ||
| bash .claude/skills/talk/scripts/talk.sh "done, tests all green" | ||
|
|
||
| # Emphasis shapes the voice | ||
| bash .claude/skills/talk/scripts/talk.sh "that is *wonderful*, Ori" | ||
|
|
||
| # Shape the voice: identity, articulation, deterministic variation | ||
| bash .claude/skills/talk/scripts/talk.sh "handing off now" --as harmonics-cli --articulation smooth --seq 7 | ||
|
|
||
| # Explicit axes instead of a sentence | ||
| bash .claude/skills/talk/scripts/talk.sh --axes --intent success --confidence high --urgency calm --state done | ||
|
|
||
| # Keep the rendered WAV instead of a throwaway temp file | ||
| bash .claude/skills/talk/scripts/talk.sh "welcome back" --keep /tmp/hello.wav | ||
| ``` | ||
|
|
||
| The script prints the dry-run note sequence to stderr (so you can see what was | ||
| "said"), renders a WAV, and plays it through the first available of `pw-play`, | ||
| `paplay`, `aplay`, `ffplay`, or `afplay`. If none exists, it prints the WAV path | ||
| so you can play it yourself. | ||
|
|
||
| ## The axes (the design spine) | ||
|
|
||
| | Axis | Values | What it shades | | ||
| |------|--------|----------------| | ||
| | **intent** | ack, question, success, failure, thinking, handoff | timbre / motif family | | ||
| | **confidence** | low → high | consonance, resolved vs. suspended cadence | | ||
| | **urgency** | calm → urgent | tempo, attack sharpness, repetition | | ||
| | **state** | idle, working, blocked, done | sustained pad vs. discrete events | | ||
| | **identity** (`--as`) | which agent | signature motif / key / instrument | | ||
|
|
||
| `say` infers these from the sentence; `play` takes them as flags. Run | ||
| `harmonics play --help` / `harmonics say --help` for the exact accepted values. | ||
|
|
||
| ## Direct CLI (what the script wraps) | ||
|
|
||
| ```bash | ||
| harmonics say "done, tests all green" # dry-run: print notes | ||
| harmonics say "done, tests all green" --wav out.wav # render audio (no extra) | ||
| harmonics say "done, tests all green" --play # live (needs [audio] extra) | ||
| harmonics play --intent success --confidence high # explicit axes | ||
| ``` | ||
|
|
||
| Use `uv run harmonics …` from inside the checkout if `harmonics` is not on PATH. | ||
|
|
||
| ## Notes | ||
|
|
||
| - **No sound stack required to render.** `--wav` uses the stdlib `wave` module; | ||
| it does not pull in `numpy`/`sounddevice`. Only live `--play` needs the extra. | ||
| - **Keep it pleasant.** This voice plays repeatedly next to a human — prefer | ||
| `calm`/`low`-urgency renders for routine chatter; save sharp/urgent for real | ||
| alerts. | ||
| - **Roadmap:** this capability is slated to be taught directly by | ||
| `harmonics learn` (its command map already grows toward `say`/`play`). Until | ||
| then, this skill is the front door for "talk to me". |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| #!/usr/bin/env bash | ||
| # talk.sh — make the agent actually TALK in its own non-speech harmonic voice. | ||
| # | ||
| # Renders a sentence (via `harmonics say`) or explicit axes (via `harmonics | ||
| # play`, with --axes) to a WAV and plays it through a system audio player. This | ||
| # is the reliable "hear it" path: it needs no live-audio backend, because it | ||
| # renders to a file (`--wav`, pure-Python) and hands that file to the OS player, | ||
| # rather than relying on `harmonics … --play` (which needs the optional | ||
| # harmonics-cli[audio] extra, absent in a dev checkout). | ||
| # | ||
| # Usage: | ||
| # talk.sh "<sentence>" [harmonics say flags...] # default: `say` | ||
| # talk.sh --axes [harmonics play flags...] # explicit axes: `play` | ||
| # talk.sh "..." --keep FILE # keep the WAV | ||
| # talk.sh "..." --dry-run # notes only, no render/play | ||
| # | ||
| # Our own flags: --axes (use `play` instead of `say`), --keep FILE (save WAV), | ||
| # --dry-run/--notes-only (print the note sequence only; skip render and | ||
| # playback entirely). | ||
| # --play, --wav, --out, and --midi are NOT accepted here — this wrapper owns | ||
| # audio output end-to-end; use --keep FILE to save the WAV instead. | ||
| # Everything else is passed straight through to the harmonics verb, so you can | ||
| # use --as, --seq, --articulation, --intent, --confidence, --urgency, --state, | ||
| # etc. The dry-run note sequence is always printed to stderr; on success the | ||
| # WAV path is echoed to stdout ONLY when the file persists (i.e. --keep, or | ||
| # the no-player fallback below). | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| MODE="say" | ||
| KEEP="" | ||
| DRY_RUN="" | ||
| ARGS=() | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --axes) MODE="play"; shift ;; | ||
| --keep) KEEP="${2:?--keep needs a FILE}"; shift 2 ;; | ||
| --dry-run|--notes-only) | ||
| DRY_RUN=1; shift ;; | ||
| --play|--wav|--out|--midi) | ||
| echo "talk: '$1' is not supported here — this wrapper owns audio output." >&2 | ||
| echo "talk: use --keep FILE to save the WAV, or --dry-run for notes only." >&2 | ||
| exit 1 ;; | ||
| --help|-h) | ||
| sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//' | ||
| exit 0 ;; | ||
| *) ARGS+=("$1"); shift ;; | ||
| esac | ||
| done | ||
|
|
||
| if [[ "$MODE" == "say" && ${#ARGS[@]} -eq 0 ]]; then | ||
| echo "talk: nothing to say — give a sentence, or use --axes for explicit axes" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ "$MODE" == "play" ]]; then | ||
| has_intent="" | ||
| if [[ ${#ARGS[@]} -gt 0 ]]; then | ||
| for a in "${ARGS[@]}"; do | ||
| if [[ "$a" == "--intent" ]]; then | ||
| has_intent=1 | ||
| break | ||
| fi | ||
| done | ||
| fi | ||
| if [[ -z "$has_intent" ]]; then | ||
| echo "talk: --axes needs --intent (harmonics play requires it)." >&2 | ||
| echo "talk: e.g. talk.sh --axes --intent success --confidence high" >&2 | ||
| exit 1 | ||
| fi | ||
| fi | ||
|
|
||
| # Locate the harmonics command: prefer an installed console script (may carry | ||
| # the [audio] extra); fall back to `uv run` inside a checkout. | ||
| if command -v harmonics >/dev/null 2>&1; then | ||
| HARMONICS=(harmonics) | ||
| elif command -v uv >/dev/null 2>&1; then | ||
| HARMONICS=(uv run harmonics) | ||
| else | ||
| echo "talk: need either 'harmonics' or 'uv' on PATH" >&2 | ||
| exit 2 | ||
| fi | ||
|
|
||
| # 1) Show what is being "said" (dry-run note sequence -> stderr). | ||
| "${HARMONICS[@]}" "$MODE" "${ARGS[@]}" >&2 || { | ||
| echo "talk: harmonics $MODE failed (see above)" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| if [[ -n "$DRY_RUN" ]]; then | ||
| exit 0 | ||
| fi | ||
|
|
||
| # 2) Render to a WAV (no live-audio backend needed). | ||
| if [[ -n "$KEEP" ]]; then | ||
| WAV="$KEEP" | ||
| CLEANUP="" | ||
| else | ||
| WAV="$(mktemp --suffix=.wav 2>/dev/null || mktemp -t talk.XXXXXX.wav)" | ||
| CLEANUP="$WAV" | ||
| fi | ||
| trap '[[ -n "$CLEANUP" ]] && rm -f "$CLEANUP"' EXIT | ||
|
|
||
| "${HARMONICS[@]}" "$MODE" "${ARGS[@]}" --wav "$WAV" >/dev/null | ||
|
|
||
|
OriNachum marked this conversation as resolved.
|
||
| # 3) Play it through the first available system player. | ||
| play_wav() { | ||
| local f="$1" | ||
| if command -v pw-play >/dev/null 2>&1; then pw-play "$f" | ||
| elif command -v paplay >/dev/null 2>&1; then paplay "$f" | ||
| elif command -v aplay >/dev/null 2>&1; then aplay -q "$f" | ||
| elif command -v ffplay >/dev/null 2>&1; then ffplay -nodisp -autoexit -loglevel quiet "$f" | ||
| elif command -v afplay >/dev/null 2>&1; then afplay "$f" | ||
| else return 127; fi | ||
| } | ||
|
|
||
| if play_wav "$WAV"; then | ||
| echo "talk: played ${WAV}" >&2 | ||
| [[ -n "$KEEP" ]] && echo "$WAV" | ||
| else | ||
|
OriNachum marked this conversation as resolved.
|
||
| rc=$? | ||
| if [[ $rc -eq 127 ]]; then | ||
| # No player found — keep the file and tell the user where it is. | ||
| CLEANUP="" | ||
| echo "talk: no audio player found (tried pw-play/paplay/aplay/ffplay/afplay)." >&2 | ||
| echo "talk: WAV written to ${WAV} — play it yourself." >&2 | ||
| echo "$WAV" | ||
| exit 0 | ||
| fi | ||
| echo "talk: playback failed (player exit ${rc}); WAV at ${WAV}" >&2 | ||
| CLEANUP="" | ||
| exit "$rc" | ||
| fi | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.