Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
git clone --depth 1 https://github.com/chendrizzy/claude-ttsWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/commands/chendrizzy/claude-tts/voices)<a href="https://agentmods.dev/commands/chendrizzy/claude-tts/voices"><img src="https://agentmods.dev/badge/commands/chendrizzy/claude-tts/voices.svg" alt="Measured on agentmods" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.00023 | $0.01425 |
| Opus 5 | $0.00012 | $0.00713 |
| Sonnet 5 | $0.00005 | $0.00285 |
| Haiku 4.5 | $0.00002 | $0.00143 |
Grade A, and why
tts:voices scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 7d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 160 lines — stays where its author put it; the contents beside it link to each section on GitHub.
/tts:voices — project → voice index
Per-project voices are audibly distinct but otherwise invisible. This prints the
daemon's deterministic project → voice map so you can see, at a glance, which
repo speaks in which voice. The current repo is highlighted with ▶.
The mapping is the daemon's own: a project is its REPO ROOT (nearest .git
ancestor), and the voice is pool[ sha256(basename(root)) % len(pool) ] — the
exact logic in daemon/content_router.py
(_project_root → _project_label → _resolve_project_voice). The pool and
enable flags are read from your config — $CLAUDE_TTS_CONFIG, else
~/.config/claude-tts/config.json (honoring $XDG_CONFIG_HOME). Restart the
daemon to apply config changes.
"Known/active repos" = repos the daemon has actually spoken in (distinct cwds in
~/.claude/logs/tts/spoken/*.jsonl), plus the current repo, plus any repo paths
you pass as arguments.
Run:
ARGS="$ARGUMENTS"
python3 - "$PWD" $ARGS <<'PY'
# Reproduces daemon/content_router.py's project->voice mapping EXACTLY. Kept
# inline so the command stays self-contained as an installed plugin. Pinned to
# the daemon by tests/test_project_voice_parity.py (imports the real functions).
import sys, os, json, hashlib, glob
CWD = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
EXTRA = [a for a in sys.argv[2:] if a.strip()]
# --- config: pool + enable flags ----------------------------------------------
# Mirrors daemon/paths.py config_path() precedence: $CLAUDE_TTS_CONFIG, then
# $XDG_CONFIG_HOME/claude-tts/config.json (else ~/.config/claude-tts/config.json).
# The trailing entries are legacy source-tree layouts, kept last as fallbacks.
def load_config():
cands = []
if os.environ.get("CLAUDE_TTS_CONFIG"):
cands.append(os.environ["CLAUDE_TTS_CONFIG"])
xdg = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
cands.append(os.path.join(xdg, "claude-tts", "config.json"))
cands.append(os.path.expanduser("~/.claude/tts/config/tts_user_config.json"))
cands.append(os.path.join(CWD, "config", "tts_user_config.json"))
for c in cands:
try:
with open(c, encoding="utf-8") as fh:
return json.load(fh) or {}
except Exception:
continue
return {}
cfg = load_config()
pv = cfg.get("project_voices", {}) if isinstance(cfg, dict) else {}
# Daemon defaults: enabled False, pool [] (content_router.py ctor).
pv_enabled = bool(pv.get("enabled", False))
pool = list(pv.get("pool", []) or [])
default_voice = ((cfg.get("voice", {}) or {}).get("name")) or "(engine default)"
# Per-project voice assignment is independent of output_context; output_context
# only controls verbal project/context phrasing.
active = pv_enabled and bool(pool)
# --- the daemon's mapping (root -> label -> voice) ----------------------------
def project_root(cwd):
if not isinstance(cwd, str) or not cwd.strip():
return ""
cwd = cwd.strip()
root = cwd
try:
d = os.path.abspath(cwd)
while True:
if os.path.exists(os.path.join(d, ".git")):
root = d
break
parent = os.path.dirname(d)
if parent == d:
break
d = parent
except Exception:
root = cwd
return root
def project_label(cwd):
if not isinstance(cwd, str):
return ""
c = cwd.strip()
if not c:
return ""
proj = c.rstrip("/").rsplit("/", 1)[-1]
if not proj or proj in ("", "/", ".", "tmp"):
return ""
return proj
def voice_for(label):
if not pool or not label:
return None
idx = int(hashlib.sha256(label.encode("utf-8")).hexdigest(), 16) % len(pool)
return pool[idx]
# --- gather known/active repo roots -------------------------------------------
def spoken_cwds():
out = set()
for f in glob.glob(os.path.expanduser("~/.claude/logs/tts/spoken/*.jsonl")):
try:
with open(f, encoding="utf-8", errors="ignore") as fh:
for ln in fh:
ln = ln.strip()
if not ln:
continue
try:
r = json.loads(ln)
except Exception:
continue
c = r.get("cwd")
if c:
out.add(c)
except OSError:
pass
return out
cur_root = project_root(CWD)
roots = {} # root -> label
for c in list(spoken_cwds()) + EXTRA + [CWD]:
r = project_root(c)
lbl = project_label(r)
if lbl:
roots[r] = lbl
# --- render -------------------------------------------------------------------
print("# /tts:voices — project → voice")
if not active:
why = []
if not pv_enabled:
why.append("project_voices.enabled=false")
if not pool:
why.append("empty pool")
print(f"per-project voices are OFF ({', '.join(why) or 'disabled'}); "
f"the daemon speaks everything in the default voice: {default_voice}")
print("(showing the mapping that WOULD apply if enabled)")
print(f"pool ({len(pool)}): {', '.join(pool) if pool else '(none)'}")
print(f"default voice: {default_voice}")
print()
if not roots:
print("(no known repos yet — pass a repo path, or speak something first)")
raise SystemExit
width = max((len(l) for l in roots.values()), default=7)
for root in sorted(roots, key=lambda r: roots[r].lower()):
lbl = roots[root]
voice = voice_for(lbl) or "-"
mark = "▶" if root == cur_root else " "
print(f"{mark} {lbl:<{width}} · {voice:<10} {root}")
PY
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 7d ago First seen · 160 lines · 23 tokens per session scan A c1b2c39886b7
tts:voices is a command published in the GitHub repository chendrizzy/claude-tts (1 stars, last pushed 21d ago), licensed MIT. It adds 23 tokens to every session and 1,425 once invoked, about $0.0001 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.
Other commands, from other repositories
wp-cinematic-scene
Author, replace, or regenerate a single cinematic scene. Updates the matching cinematicscenes repeater row (eyebrow, headline, body, CTA, videos, poster) and — if the scene needs a non-default layout — emits a per-scene template fragment override at template-parts/cinematic/scene- .php. Cinematic equivalent of…
wp-cinematic-seed
Seed (or re-seed) all cinematic scenes from a manifest. Idempotent — skips scenes whose sceneid already has content unless --force. Sideloads sample videos from the kit if no Media Library attachments are pinned.
wp-cinematic-encode
Encode source videos for cinematic scroll-scrub (all-keyframe MP4) and mobile autoplay-loop (9:16 portrait crop). Thin wrapper over the kit's scripts/encode-keyframe.sh and scripts/encode-mobile-portrait.sh. Drops encoded files into /assets/videos/ and updates the matching ACF row if --scene=N is provided.
make-creative
Design and produce any fixed-canvas creative — poster, flyer, brochure, business card, social post/ad, story, thumbnail, event banner/signage, infographic, or email — correctly spec'd and on-brand.
make-carousel
Design and produce a social media carousel (Instagram / LinkedIn / TikTok) — hook, narrative slides, CTA — with a consistent template and rendered slides.
make-deck
Design and produce a presentation/deck (pitch, sales, conference, internal) using the Design Pro knowledge base — structure, slide design, and a producible output.