media use

Skill

Agent Media OS, the single skill for every media need in a HyperFrames project. Resolve BGM, SFX, image, icon, brand logo, or voice into a frozen local file + ledger record (one verb, `resolve`); generate via TTS / music / image models when the catalog misses; produce voiceover, transcription, captions, and background removal through one shared audio engine; operate on media (cut / reframe / transform); and reuse assets across projects. Keeps search noise on disk, hands the agent a path. Use for any audio, image, icon, voiceover, caption, or media-asset need.

Files103
  • @skills/media-use/.gitignore
  • @skills/media-use/SKILL.md
  • @skills/media-use/audio/assets/sfx/CREDITS.md
  • @skills/media-use/audio/assets/sfx/chime.mp3
  • @skills/media-use/audio/assets/sfx/click-soft.mp3
  • @skills/media-use/audio/assets/sfx/click.mp3
  • @skills/media-use/audio/assets/sfx/error.mp3
  • @skills/media-use/audio/assets/sfx/glitch-1.mp3
  • @skills/media-use/audio/assets/sfx/glitch-2.mp3
  • @skills/media-use/audio/assets/sfx/glitch-3.mp3
  • @skills/media-use/audio/assets/sfx/impact-bass-1.mp3
  • @skills/media-use/audio/assets/sfx/impact-bass-2.mp3
  • @skills/media-use/audio/assets/sfx/key-press.mp3
  • @skills/media-use/audio/assets/sfx/manifest.json
  • @skills/media-use/audio/assets/sfx/notification.mp3
  • @skills/media-use/audio/assets/sfx/ping.mp3
  • @skills/media-use/audio/assets/sfx/pop.mp3
  • @skills/media-use/audio/assets/sfx/riser.mp3
  • @skills/media-use/audio/assets/sfx/sparkle.mp3
  • @skills/media-use/audio/assets/sfx/typing.mp3
  • @skills/media-use/audio/assets/sfx/whoosh-cinematic.mp3
  • @skills/media-use/audio/assets/sfx/whoosh-short.mp3
  • @skills/media-use/audio/assets/sfx/whoosh.mp3
  • @skills/media-use/audio/references/bgm.md
  • @skills/media-use/audio/references/captions/authoring.md
  • @skills/media-use/audio/references/captions/motion.md
  • @skills/media-use/audio/references/captions/transcript-handling.md
  • @skills/media-use/audio/references/remove-background.md
  • @skills/media-use/audio/references/requirements.md
  • @skills/media-use/audio/references/sfx.md
  • @skills/media-use/audio/references/transcribe.md
  • @skills/media-use/audio/references/tts-to-captions.md
  • @skills/media-use/audio/references/tts.md
  • @skills/media-use/audio/scripts/audio.mjs
  • @skills/media-use/audio/scripts/audio.test.mjs
  • @skills/media-use/audio/scripts/heygen-tts.mjs
  • @skills/media-use/audio/scripts/lib/bgm.mjs
  • @skills/media-use/audio/scripts/lib/concurrency.mjs
  • @skills/media-use/audio/scripts/lib/concurrency.test.mjs
  • @skills/media-use/audio/scripts/lib/heygen.mjs
  • @skills/media-use/audio/scripts/lib/python.mjs
  • @skills/media-use/audio/scripts/lib/python.test.mjs
  • @skills/media-use/audio/scripts/lib/sfx.mjs
  • @skills/media-use/audio/scripts/lib/sfx.test.mjs
  • @skills/media-use/audio/scripts/lib/tts.mjs
  • @skills/media-use/audio/scripts/lib/tts.spawn.test.mjs
  • @skills/media-use/audio/scripts/lib/tts.test.mjs
  • @skills/media-use/audio/scripts/lyria-recipe.py
  • @skills/media-use/audio/scripts/wait-bgm.mjs
  • @skills/media-use/references/operations.md
  • @skills/media-use/scripts/audio-duck.mjs
  • @skills/media-use/scripts/eval.mjs
  • @skills/media-use/scripts/lib/adopt.mjs
  • @skills/media-use/scripts/lib/adopt.test.mjs
  • @skills/media-use/scripts/lib/bgm-provider.mjs
  • @skills/media-use/scripts/lib/brand-provider.mjs
  • @skills/media-use/scripts/lib/cache.mjs
  • @skills/media-use/scripts/lib/candidates.mjs
  • @skills/media-use/scripts/lib/candidates.test.mjs
  • @skills/media-use/scripts/lib/codex-provider.mjs
  • @skills/media-use/scripts/lib/coverage.test.mjs
  • @skills/media-use/scripts/lib/cutlist.mjs
  • @skills/media-use/scripts/lib/cutlist.test.mjs
  • @skills/media-use/scripts/lib/duck.mjs
  • @skills/media-use/scripts/lib/duck.test.mjs
  • @skills/media-use/scripts/lib/freeze.mjs
  • @skills/media-use/scripts/lib/freeze.test.mjs
  • @skills/media-use/scripts/lib/heygen-search.mjs
  • @skills/media-use/scripts/lib/image-provider.mjs
  • @skills/media-use/scripts/lib/index-gen.mjs
  • @skills/media-use/scripts/lib/local-models.mjs
  • @skills/media-use/scripts/lib/local-models.test.mjs
  • @skills/media-use/scripts/lib/local-run.mjs
  • @skills/media-use/scripts/lib/local-run.test.mjs
  • @skills/media-use/scripts/lib/logo-provider.mjs
  • @skills/media-use/scripts/lib/logo-provider.test.mjs
  • @skills/media-use/scripts/lib/manifest.mjs
  • @skills/media-use/scripts/lib/manifest.test.mjs
  • @skills/media-use/scripts/lib/match.mjs
  • @skills/media-use/scripts/lib/mflux-provider.mjs
  • @skills/media-use/scripts/lib/parakeet-words.mjs
  • @skills/media-use/scripts/lib/parakeet-words.test.mjs
  • @skills/media-use/scripts/lib/probe.mjs
  • @skills/media-use/scripts/lib/probe.test.mjs
  • @skills/media-use/scripts/lib/providers.mjs
  • @skills/media-use/scripts/lib/registry.mjs
  • @skills/media-use/scripts/lib/registry.test.mjs
  • @skills/media-use/scripts/lib/search.mjs
  • @skills/media-use/scripts/lib/search.test.mjs
  • @skills/media-use/scripts/lib/sfx-provider.mjs
  • @skills/media-use/scripts/lib/specs.mjs
  • @skills/media-use/scripts/lib/specs.test.mjs
  • @skills/media-use/scripts/lib/telemetry.mjs
  • @skills/media-use/scripts/lib/telemetry.test.mjs
  • @skills/media-use/scripts/lib/tts-local-provider.mjs
  • @skills/media-use/scripts/lib/usage.mjs
  • @skills/media-use/scripts/lib/usage.test.mjs
  • @skills/media-use/scripts/lib/voice-provider.mjs
  • @skills/media-use/scripts/lib/words.mjs
  • @skills/media-use/scripts/resolve.mjs
  • @skills/media-use/scripts/resolve.test.mjs
  • @skills/media-use/scripts/transcribe.mjs
  • @skills/media-use/scripts/transcript-cut.mjs

media-use

The media OS for HyperFrames: resolve · generate · operate · remember, every media type, one skill, zero context noise.

What it owns (the gaps HyperFrames leaves)

HyperFrames owns media playback; media-use owns everything else. Each row is enforced by scripts/lib/coverage.test.mjs so the claim can't rot.
HyperFrames gapmedia-use owns it via
Audio-only, no image/iconresolve --type image|icon (heygen asset search)
No third-party brand logosresolve --type logo (svgl → simple-icons → GitHub org avatar → domain favicon)
No voice / audio generationresolve --type voice + the audio engine (audio/scripts/audio.mjs)
Scattered/duplicated audio engineone consolidated engine under audio/ (hyperframes-media retired)
No agent media-ops (cut/reframe/transform)references/operations.md + resolve --from to register outputs
No transcript-driven cuttingscripts/transcript-cut.mjs compiles word-timestamp edits into cut lists
No auto-duck / publish loudnessscripts/audio-duck.mjs + references/operations.md loudnorm/sidechain recipes
No cross-project memoryglobal content-addressed cache + auto-promote (~/.media)
No image generationRAM-graded local mflux (FLUX) via scripts/lib/mflux-provider.mjs, codex image_gen upsell (scripts/lib/codex-provider.mjs)
No video generationspec-gated local LTX (videogen in scripts/lib/local-models.mjs); heygen video create avatar upsell
Weak local-model defaultsfree-usage HeyGen first (TTS, bg-removal) via the heygen CLI; local open-source only as an opt-out fallback (scripts/lib/local-run.mjs)

When to use

Call resolve whenever a composition needs media: background music, sound effects, images, icons, brand logos, or voice. For voiceover / TTS, music, SFX, and caption timing, use the audio engine (below); background removal is delegated to the hyperframes CLI; transcription defaults to Parakeet (better than whisper.cpp: 6.05% vs 7.44% WER, 5-10x faster) via scripts/transcribe.mjs, with whisper.cpp auto-fallback (see references/operations.md). For cutting / reframing / transforming existing media, see references/operations.md. media-use searches the HeyGen catalog first, freezes the best match locally, registers it in a manifest, and hands the agent one line; all search noise stays on disk.

Resolve

bash
node <SKILL_DIR>/scripts/resolve.mjs --type <type> --intent "<description>" --project <dir>
Returns one line: resolved <id> → <path> (<type>, <metadata>)

Types

TypeWhat it findsProvider
bgmBackground musicHeyGen audio catalog (10k+ tracks)
sfxSound effectsBundled 19-file library + HeyGen catalog
imagePhotos, backgroundsHeyGen asset search (75k+ vectors)
iconIcons, symbolsHeyGen asset search (type=icon)
logoOfficial brand markssvgl → simple-icons → GitHub org avatar → domain favicon
voiceTTS voiceoverLocal Kokoro (free); HeyGen TTS upsell

Examples

bash
# Background music
node <SKILL_DIR>/scripts/resolve.mjs --type bgm --intent "upbeat tech launch" --project .
# → resolved bgm_001 → .media/audio/bgm/bgm_001.mp3 (bgm, 25s)

# Sound effect
node <SKILL_DIR>/scripts/resolve.mjs --type sfx --intent "whoosh" --project .
# → resolved sfx_001 → .media/audio/sfx/sfx_001.mp3 (sfx, 0.57s)

# Image
node <SKILL_DIR>/scripts/resolve.mjs --type image --intent "gradient tech background" --project .
# → resolved image_001 → .media/images/image_001.jpg (image)

# Icon
node <SKILL_DIR>/scripts/resolve.mjs --type icon --intent "rocket" --project .
# → resolved icon_001 → .media/images/icon_001.png (icon, transparent)

# Brand logo (official mark — never redrawn by hand)
node <SKILL_DIR>/scripts/resolve.mjs --type logo --entity linkedin --intent "LinkedIn logo" --project .
# → resolved logo_001 → .media/images/logo_001.svg (logo, official mark)

Flags

FlagDescription
--type, -tMedia type: bgm, sfx, image, icon, voice
--intent, -iWhat you need (natural language)
--entity, -eEntity name for cache matching (optional)
--project, -pProject directory (default: .)
--candidatesList reusable assets (project + global cache) for --type; no download, no mutation
--reuse <sha>Import a specific global-cache asset (by content sha/prefix, from --candidates)
--fromFreeze a local file or direct public URL (ingest)
--local-onlyOffline: skip every network provider (cache + local only)
--providerForce one generator (e.g. codex, mflux, kokoro, heygen)
--adoptBulk-import existing assets/ into manifest
--jsonOutput JSON instead of one-line result

Reuse before you resolve

Before resolving bgm/sfx/image/icon, check what already exists and reuse it when it fits. media-use does not semantically match for you — you are the judge. It surfaces candidates; you decide.
bash
node <SKILL_DIR>/scripts/resolve.mjs --type bgm --intent "upbeat tech launch" --candidates --project .
#   [project] upbeat tech launch (25s, heygen.audio.sounds)
#           .media/audio/bgm/bgm_001.wav
#   [global]  energetic tech intro (22s, heygen.audio.sounds)
#           --reuse 06e052c075fd2b80
Read the list and judge semantic fit yourself — "upbeat tech launch" ≈ "energetic tech intro" is a call only you can make from the descriptions. Then:
  • A project candidate fits → just reference its path in your composition. Nothing else to run.
  • A global candidate fitsresolve --type bgm --reuse <sha> copies it into this project (self-contained render) and records it.
  • Nothing fits → resolve fresh (--type ... --intent ...).
Trust guardrail — when unsure, resolve fresh. A redundant download is cheap; shipping the wrong asset is not. Judge fit from description + prompt + type + duration/dims. For brand/entity assets, reuse a global candidate only when the entity matches exactly — the global cache aggregates every project you have worked on, so a --candidates list can surface another client's brand mark and its prompt text. Never reuse a cross-project brand asset on a loose match.
The deterministic floor still runs automatically: an identical (case/whitespace-insensitive) repeat auto-reuses with no --candidates step. --candidates is only for the semantic layer above that floor — and a fuzzy match is never auto-applied; reuse is always your explicit call. On a resolve that misses the floor and is about to fetch, media-use prints a one-line stderr hint when similar cached assets exist, pointing you back here.

Providers

media-use holds no keys; every external tool owns its auth. Generation is local-first with a cloud upsell where one helps. resolve spec-checks AVAILABLE RAM and auto-picks the best local model that fits (a RAM-graded ladder, describeModelLadder); the agent can see the ladder and override.
TypeProvider (in order)
bgm/sfxheygen catalog (free)
imageheygen search, then local mflux (best FLUX for your RAM), then codex image_gen upsell
voicelocal Kokoro (free, on-device), then heygen tts paid upsell
iconheygen asset search
logosvgl, then simple-icons, then GitHub org avatar, then domain favicon (all free)
video (local)local LTX (videogen ladder); heygen video create avatar upsell
Local Kokoro (voice), mflux (image), and LTX (video) run on-device (free, private, offline once cached). Paid/cloud upsells sit behind them: HeyGen TTS for voice, the codex CLI (ChatGPT sub) for a better image, the heygen CLI for avatar video. Cost rule (X4): the agent confirms before an agent-initiated paid call; a user-requested one just runs.
To force a specific generator (e.g. a user says "make this image with codex"), pass --provider codex: it pins resolution to that provider and skips the free-first default. See references/operations.md for the RAM ladders and upsell recipes.
--local-only skips every network provider, including the free HeyGen ones, leaving the project + global cache and any local provider.

How it works

resolve runs an automatic floor, then falls through to fetching:
  1. Check project .media/manifest.jsonl for a prompt match (case- and whitespace-insensitive) — auto-reuse
  2. Scan existing assets/ directory for unregistered files that share a word with the need
  3. Check global cache ~/.media/ for a reusable asset matched on the same normalized prompt — auto-reuse
  4. Search via provider (HeyGen audio catalog, HeyGen asset search), then generate
  5. Freeze file to .media/<type>/, register in manifest, regenerate index.md, auto-promote to ~/.media/
Steps 1 and 3 are the deterministic floor: they only auto-reuse an exact-normalized match, never a fuzzy one. Semantic reuse ("close enough") is the agent's explicit call via Reuse before you resolve — it never happens automatically. The agent gets back one line; candidates, scores, provenance stay on disk.

Adopt existing projects

Most HyperFrames projects already have assets in assets/. media-use adopts them:
bash
node <SKILL_DIR>/scripts/resolve.mjs --adopt --project .
# → adopted 9 assets from assets/
#   bgm_001 → assets/bgm/mango-fizz.mp3 (bgm, 146.6s)
#   image_001 → assets/images/avatar.jpg (image, 400×400)
ffprobe extracts real duration and dimensions. During resolve, unregistered files in assets/ matching the intent are adopted on the fly.

Reading the inventory

After resolve or adopt, read .media/index.md for the full inventory:
text
# .media · 4 assets

id         type   dur   dims       path                          description
bgm_001    bgm    25s   -          .media/audio/bgm/bgm_001.mp3  upbeat tech launch
sfx_001    sfx    0.6s  -          .media/audio/sfx/sfx_001.mp3  whoosh
image_001  image  -     1920×1080  .media/images/image_001.jpg   gradient tech background
icon_001   icon   -     200×200    .media/images/icon_001.png    rocket

Cross-project reuse

Assets are cached automatically on resolve. Every resolved/ingested asset is auto-promoted to the global cache at ~/.media/, so subsequent resolves for the same (or near-identical) prompt, in any project, hit the cache with no re-download and no provider call.
For a semantically similar (not identical) need in another project, the exact-match floor won't fire — use Reuse before you resolve: --candidates lists the global assets, and --reuse <sha> imports the one you pick. This is how a track resolved in one project gets reused in the next when the wording differs.

Files

  • .media/manifest.jsonl: machine SSOT, one JSON record per line
  • .media/index.md: agent-readable table (id, type, dur, dims, path, description)
  • ~/.media/: global cross-project reuse cache (content-addressed, SHA-256)

Audio engine: voiceover, music, SFX, captions, transcription

For a full audio pass (TTS voiceover + background music + sound effects in one shot), use the shared engine at audio/scripts/audio.mjs. It takes a neutral audio_request.json and writes audio_meta.json plus assets under .media/audio/{voice,bgm,sfx}:
bash
node <SKILL_DIR>/audio/scripts/audio.mjs --request ./audio_request.json --out ./audio_meta.json
  • Request { provider?, lang?, speed?, lines: [{ id, text, sfx?: [names] }], bgm: { mode?, query?, prompt? } }: id joins each line back to your model; bgm.mode = retrieve | generate | none (omit for auto). --only tts,bgm,sfx runs a subset and merges into an existing --out.
  • Output audio_meta.json (id-keyed): voices[].{path,duration_s,words[]} (word timestamps for captions), sfx[], bgm, total_duration_s.
  • Auto-degrades on one switch: HeyGen credential present → HeyGen TTS + music/SFX retrieval; absent → ElevenLabs/Kokoro TTS, Lyria/MusicGen BGM generation, and the bundled SFX library (no credential needed).
  • If BGM took the generate path (bgm_pending: true), run audio/scripts/wait-bgm.mjs before final render.
Single-shot helpers: audio/scripts/heygen-tts.mjs (one voice file). Transcription / background removal / captions use the hyperframes CLI (transcribe, remove-background), see the per-topic guides in audio/references/ (tts.md, bgm.md, sfx.md, transcribe.md, remove-background.md, captions/).

Operating on media (cut, reframe, transform)

media-use resolves + remembers; for operating on assets see references/operations.md: local-tool recipes (ffmpeg trim/reframe/montage, auto-editor, scenedetect) and the local-vs-HeyGen transform table (background removal, upscale, lipsync, translate). Run the tool, then register the output with resolve --from <output> --type <type> so it joins the ledger + global cache.

CLI tools used (what to run, and how to enable each)

resolve auto-cascades; each provider shells one CLI. Local tools are OPT-IN: if a local tool is absent, resolve degrades gracefully to the free/cloud path, so nothing here is strictly required except ffmpeg/ffprobe. Install a local tool to unlock its free, private, on-device path. media-use holds no keys.
ToolServesInstall
ffmpeg/ffprobeadopt probing, cut, duck bake, loudnormsystem package (brew install ffmpeg)
heygencatalog (bgm/sfx/image/icon), TTS + avatar upsellcurl -fsSL https://static.heygen.ai/cli/install.sh | bash then heygen auth login --key <key> (needs >= v0.1.6)
mflux-generatelocal image gen (FLUX), best-for-RAMuv venv ~/.venvs/mflux && VIRTUAL_ENV=~/.venvs/mflux uv pip install mflux==0.9.6
codeximage gen upsell (ChatGPT sub)Codex CLI, logged in via ChatGPT (owns its own auth)
parakeet-mlxlocal transcription (default ASR, best)uv venv ~/.venvs/parakeet && VIRTUAL_ENV=~/.venvs/parakeet uv pip install parakeet-mlx
ltx-2-mlxlocal video gengit clone https://github.com/dgrauet/ltx-2-mlx && cd ltx-2-mlx && uv sync --all-extras
npx hyperframesKokoro TTS (voice), whisper.cpp (transcribe fallback), remove-backgroundbundled with the hyperframes CLI
The RAM-graded local-model shortlist + exact per-tier install/invoke lives in scripts/lib/local-models.mjs (the agent can read describeModelLadder(cap, specs) to see which model fits this machine). Without a tool on PATH, its provider prints a one-line diagnostic to stderr and resolve falls through to the next provider (e.g. no mflux -> codex image upsell; no parakeet-mlx -> whisper.cpp).
heygen asset search is a pre-launch command hidden from heygen --help, but it runs; providers tag requests with the allowlisted X-HeyGen-Client-Source header (v0.1.6+).

Telemetry

resolve and the edit tools (transcribe / transcript-cut / audio-duck) send an anonymous usage event to PostHog (scripts/lib/telemetry.mjs), so we can see which capabilities are actually used. It records only the media TYPE, the resolution SOURCE, and the winning PROVIDER: never the intent text, file names, or paths, and $ip:null so no IP is stored. Best-effort and non-blocking (a resolve never waits on or fails from telemetry).
Opt out with DO_NOT_TRACK=1 or HYPERFRAMES_NO_TELEMETRY=1 (also off in CI and dev). Same public PostHog project key and opt-outs as the hyperframes CLI.
media-use — Kortix Marketplace | Kortix