Files
ytplayer/.agents/skills/lyrics-agy/SKILL.md

5.0 KiB

name, description
name description
lyrics-agy Find lyrics (with timings when they exist) for worship.hesed.sbs songs using agy, the flat-rate Antigravity CLI, via the agy-bridge MCP server. Use when LRCLIB has no match, when a song still has no lyrics or bad machine-transcribed ones, or when the user says "ask agy for the lyrics". Also how to paste lyrics you already have into a song.

Lyrics from agy

agy searches the open web and, when the song has a caption track or a published sync, returns timed lyrics in LRC form. It is flat-rate, so a run costs nothing per song — but it is the last resort, after LRCLIB (lyrics-lookup, lyrics-regenerate), because LRCLIB's synced lyrics are published data while agy's are derived.

Script: scripts/lyrics/agy_lyrics.py.

Run it

cd ~/development/personal/ytplayer
export YTP_ADMIN_PASSWORD='…'          # or YTP_TOKEN=ytp_…

# every song that still has no lyrics — dry run first, ALWAYS
python3 scripts/lyrics/agy_lyrics.py --missing
python3 scripts/lyrics/agy_lyrics.py --missing --apply

# one song, replacing lyrics that are wrong
python3 scripts/lyrics/agy_lyrics.py --ids ZHl6EwSwjv0 --overwrite --apply

# lyrics you already have (LRC or plain text), no agy call at all
python3 scripts/lyrics/agy_lyrics.py --ids ZHl6EwSwjv0 --from-file words.txt --overwrite --apply

Every run backs the current lyrics up to Documents/ytplayer-lyrics-backup-<stamp>.json before writing, and the server keeps each previous version as a restorable revision.

One song takes ~2 minutes per attempt, and --tries defaults to 3, so a batch is slow. Run it detached rather than in a foreground command that will hit a timeout:

setsid nohup python3 scripts/lyrics/agy_lyrics.py --missing --apply \
  > /tmp/agy-lyrics.log 2>&1 < /dev/null &

How it calls agy

Through MCPJungle, so no agy CLI contract is hard-coded here:

mcpjungle invoke agy-bridge__agy_ask --input '{"dir": "<repo>", "prompt": "…"}'
mcpjungle invoke agy-bridge__fetch_output --input '{"keep_id": "ask-…"}'

Four things about that interface cost real debugging time — do not rediscover them:

  1. dir is required. Without it the call fails schema validation.
  2. The answer arrives on STDERR, not stdout. Read both streams or you get an empty string and conclude, wrongly, that agy found nothing.
  3. Long answers are truncated with fetch_output(keep_id='…') for more. A full set of lyrics is almost always longer than the cap, so always follow the keep_id — otherwise you silently save half a song.
  4. agy can fail and still look like a success. A quota-exhausted instance returns prose like WHY: agy-ask failed (rc=1) inside a STATUS: ok envelope. One such line even carried a [05:37.76] stamp and parsed as a perfectly good synced lyric. FAILED in the script rejects those; keep it.

It is not deterministic — that is the main gotcha

The same question can come back synced, plain, or empty on consecutive calls. Observed in one sitting on "Jesus At The Centre": 39 timed lines, then 43 untimed, then nothing. So the script asks up to --tries times and keeps the best answer (score(): any timed lines beats none, then more lines beats fewer), stopping early once a timed answer arrives.

If a song saves untimed and you believe a timed version exists, just run it again with --overwrite.

What it filters out, and why

agy streams its own progress into the answer. Everything below is dropped before parsing, and every pattern is there because it once ended up saved as a lyric line:

  • STATUS: / SUMMARY: / MODEL: / INSTANCE: / AGY-META: / WHY: envelopes
  • Waiting for task execution…, Background task <uuid> completed with…
  • section labels that are not sung (Verse 1, [Chorus], x2)
  • anything over 200 characters (a paragraph of commentary, not a sung line)

After filtering, an answer shorter than --min-lines (6) is rejected outright.

Reviewing before you trust it

agy derives timings, so check them once per song before relying on them in a service:

  • The last timestamp should land near the song's length (a 384 s song ending at 367 s is right; one ending at 120 s means it only got a verse).
  • Timestamps must increase monotonically.
  • Open /admin?v=<id>, press play and watch the highlighted line track the vocal. Fix drift with Shift all, or retime individual lines with Set / Tap mode.

Untimed results are fine — the app shows them as a plain scrolling list, and Tap mode turns them into synced lyrics in one pass of the song.

Provenance tags

Written into data.tags so a later run can tell where lyrics came from:

Tag Meaning
from the web via agy (synced) agy, with timings
from the web via agy (untimed) — check and Tap-sync agy, words only
from a file (<name>) (synced|untimed) --from-file

Related: lyrics-lookup (LRCLIB first, then this), lyrics-regenerate (replace wrong lyrics from LRCLIB), deploy-prod.