Author SHA1 Message Date
luna 2b2f0994e9 mars(kittentts): local STT add-on — faster-whisper in the audio side-venv
Telegram voice-note transcription on mars, no cloud: faster-whisper 1.2.1
(ctranslate2 CPU) installed into the SAME kittentts side-venv via
stt-requirements.txt (appended by the provisioning unit; input stamp
extended). Second auto-patchelf pass RPATHs the newly installed
ctranslate2/av binaries. Container env gains
PYTHONPATH=/opt/data/kittentts-venv/... so hermes's local-stt provider
imports faster-whisper in-process (the image venv is read-only for uid
986, so the built-in lazy installer cannot work there).

Validated live: Telegram voice note transcribed on CPU (i5-4460,
lang=en, text exact) with the same pins.
2026-09-19 13:31:26 +00:00
darmanandClaude Opus 5 22caa06904 mars(kittentts): patch venv .so RPATHs so numpy/onnxruntime load
The venv's python is Nix python313 (also inside the container, via the
/nix/store mount), whose loader never searches /usr/lib, so PyPI manylinux
wheels failed with "libstdc++.so.6: cannot open shared object file".
auto-patchelf the staged venv against gcc-lib + zlib; RPATHs are store
paths, so the delivered copy works in the container too. The lib paths join
the input hash so a toolchain bump rebuilds.

Verified locally with mars's exact uv/python313: construct offline from the
staged hub layout and synthesize with voice "Luna".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:05:01 +02:00
darmanandClaude Opus 5 812c1af1f2 mars(kittentts): install the wheel under its real filename
uv reads the version from the wheel filename, and the fetchurl store path's
hash prefix made it reject the file ("invalid version"). Expose it via a
linkFarm under kittentts-0.8.1-py3-none-any.whl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:59:01 +02:00
darmanandClaude Opus 5 e0f5c6b37b mars(kittentts): fix review round 3 — eval, delivery and stale-venv bugs
- pkgs.setpriv does not exist (mars failed to eval); setpriv is in util-linux.
- `sh` is not on a unit's PATH; call ${pkgs.runtimeShell} directly.
- Drop `env HOME="$hermesHome"`: unset shell var under `set -u`, and unneeded.
- Staging dir 0700 -> 0755: the uid-986 delivery step could not read it.
- Rebuild the staging venv whenever the stamp misses, else an input bump
  delivered the old venv under the new stamp.
- Add diffutils: `cmp` in the fast path was missing, so every boot re-delivered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:51:05 +02:00
luna bef5215bea mars(kittentts): review round 2 — setpriv delivery, staged hub tree, ordering fix
1: stageVenv declared with the other path vars before the early-exit
reads it (set -u no longer kills the self-heal path on later boots).
2: delivery is rm+cp as the container uid via setpriv — no mv -Tf
rename-replace on a non-empty directory (that failed every
re-provision); chown -R step gone with it.
3: network-online.target back in after= — this unit CAN download at
boot, so uv must not run pre-network.
4: root never writes into hermesHome at all. refs/main, snapshots and
venv staging happen under root-owned staging; delivery drops to uid 986
via setpriv, rm -rf + cp -a from the staging sources. Symlink-takeover
of refs/install targets and the rm/cp race on the delivered tree are
structurally gone.
Minor: sanity check now constructs KittenTTS against the staged hub
tree, not just imports; fetchurls pinned to the commit sha matching
refs/main; fast-path comment matches behavior (staging venv skips
rebuild on damaged delivery).
2026-09-19 12:46:14 +00:00
luna b2982c47a1 mars(kittentts): review fixes — root-owned staging, offline cache resolved, full-input stamp
Review round 1 fixes:
1+2 (security): build+stamp+uv-cache moved out of hermesHome into a
0700 root-owned staging dir (/var/lib/hermes-kittentts). Root executes
python only from the staging venv after an offline import check;
finished artifacts are copied into hermesHome and the stamp compares
delivered copy vs staging byte-for-byte. No root code path follows
anything the container can write, so no stamp symlink-takeover and no
planted-wheel-in-cache execution.
3: HF snapshot dir is the real commit sha with refs/main written, so
hf_hub_download resolves the seeded cache offline (was 'kitten', never
found).
4: container env pins HF_HOME=/opt/data/kittentts-hf + HF_HUB_OFFLINE=1.
5: stamp keyed on the full input set (requirements+wheel+stub+models),
not requirements alone; deletion of a delivered file drops out of the
fast path and re-provisions cheaply.
6: orders after/requires hermes-agent-prepare-dirs.
2026-09-19 12:33:55 +00:00
luna 524315f495 mars: add KittenTTS voice provider (CPU-only, offline mini model)
tts.provider: kittentts, voice Luna, mini-0.8 model (80M params) per
darman. CPU-only onnxruntime inference — no GPU on mars, validated ~1x
realtime on a weaker dev box. Upstream's misaki[en]+spacy declaration is
deliberately not honored (pulls torch→CUDA, 5.6 GB); a .pth-registered
stub satisfies the dead misaki import, failing loudly if it is ever
actually used. HF model cache pre-seeded from hash-pinned store paths so
HF_HUB_OFFLINE=1 runs with zero boot-time network. Provisioner shaped by
the Mnemosyne review: uv venv --clear, root never executes python from
the venv, ownership to the container uid before anything imports, stamp
checked against venv+model files.
2026-09-19 12:14:53 +00:00
4 changed files with 375 additions and 0 deletions
+255
View File
@@ -77,6 +77,44 @@ let
# Mnemosyne memory provider: third-party plugin, not in the image. # Mnemosyne memory provider: third-party plugin, not in the image.
mnemosyneEnv = pkgs.callPackage ../../pkgs/mnemosyne-env.nix { }; mnemosyneEnv = pkgs.callPackage ../../pkgs/mnemosyne-env.nix { };
# KittenTTS voice provider inputs (CPU-only; model + wheel hash-pinned).
# Provisioning unit near the bottom of this file; background on why the
# deps deviate from upstream's declaration lives in
# ./kittentts/requirements.txt + ./kittentts/kitten-misaki-stub.py.
kittenttsWheel = pkgs.fetchurl {
url = "https://github.com/KittenML/KittenTTS/releases/download/0.8.1/kittentts-0.8.1-py3-none-any.whl";
sha256 = "sha256-SCpDbE8fMZIVNxA3bkWf82iVF+vNp8KwUeL9QYe0GFE=";
};
# uv parses the version out of the filename; the store hash prefix breaks it.
kittenttsWheelFile = "${pkgs.linkFarm "kittentts-wheel" {
"kittentts-0.8.1-py3-none-any.whl" = kittenttsWheel;
}}/kittentts-0.8.1-py3-none-any.whl";
kittenttsRuntimeLibs = "${pkgs.stdenv.cc.cc.lib}/lib ${pkgs.zlib}/lib";
kittenttsReqs = pkgs.writeText "kittentts-requirements.txt" (
builtins.readFile ./kittentts/requirements.txt
);
# STT add-on: faster-whisper (CPU) for Telegram voice-note transcription,
# same side-venv, appended by the provisioning unit. Keeps one venv + one
# stamp for the whole local-audio stack. See kittentts/stt-requirements.txt.
kittenttsSttReqs = pkgs.writeText "kittentts-stt-requirements.txt" (
builtins.readFile ./kittentts/stt-requirements.txt
);
kittenttsStub = pkgs.writeText "kitten-tts-stub.py" (
builtins.readFile ./kittentts/kitten-misaki-stub.py
);
kittenttsModelOnnx = pkgs.fetchurl {
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/c02725660cea441db4c383af69f1f26f5cd00947/kitten_tts_mini_v0_8.onnx";
sha256 = "sha256-D1u65PxIAMmNvFRKh+z6eVEN4vuCItsw0S5b/pF335E=";
};
kittenttsModelVoices = pkgs.fetchurl {
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/c02725660cea441db4c383af69f1f26f5cd00947/voices.npz";
sha256 = "sha256-QK0mOJUrd7ey8wEn4mCOFp/GndJWtTvYqqNAmjMZPEI=";
};
kittenttsModelConfig = pkgs.fetchurl {
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/c02725660cea441db4c383af69f1f26f5cd00947/config.json";
sha256 = "sha256-axYLybGeJOyyHoS8FPin2iH99H7HLUJFC8XPUUthgEo=";
};
# Wire event names (X-GitHub-Event) each route accepts — NOT the # Wire event names (X-GitHub-Event) each route accepts — NOT the
# subscription names the gitea hooks in services/dev/gitea.nix use. The two # subscription names the gitea hooks in services/dev/gitea.nix use. The two
# namespaces collide; see the long comment on the route unit below. # namespaces collide; see the long comment on the route unit below.
@@ -274,6 +312,18 @@ in
# Authentik rejects. Safe to trust any peer: 9119 is already scoped to # Authentik rejects. Safe to trust any peer: 9119 is already scoped to
# loopback + tailscale0 only. # loopback + tailscale0 only.
FORWARDED_ALLOW_IPS = "*"; FORWARDED_ALLOW_IPS = "*";
# KittenTTS: point huggingface_hub at the pre-seeded offline cache and
# forbid network — no model drift, no boot-time fetch (review #4).
HF_HOME = "/opt/data/kittentts-hf";
HF_HUB_OFFLINE = "1";
# STT (local whisper): expose the side-venv's site-packages to Hermes's
# own interpreter so faster-whisper imports in-process. PYTHONPATH dirs
# don't run .pth auto-imports, but faster-whisper has no dead imports —
# it imports cleanly from a plain path injection (unlike kittentts,
# which is driven through the command wrapper for exactly that reason).
PYTHONPATH = "/opt/data/kittentts-venv/lib/python3.13/site-packages";
}; };
environmentFiles = [ config.sops.templates."hermes-agent.env".path ]; environmentFiles = [ config.sops.templates."hermes-agent.env".path ];
cmd = [ "gateway" "run" ]; cmd = [ "gateway" "run" ];
@@ -450,4 +500,209 @@ in
chown -h ${hermesUid}:${hermesGid} "$pluginDir" chown -h ${hermesUid}:${hermesGid} "$pluginDir"
''; '';
}; };
# ---- KittenTTS voice provider ------------------------------------------
# CPU-only onnxruntime TTS (no GPU on mars), mini model per darman. The
# upstream `misaki[en]` declaration is deliberately not honored — it pulls
# torch→CUDA (5.6 GB verified); the runtime phonemizes with espeak-ng only,
# so the dead `from misaki import en, espeak` import is satisfied by a
# .pth-registered stub (kitten-misaki-stub.py) that fails loudly if misaki
# is ever actually used.
#
# Provisioner invariants (shaped by the Mnemosyne + KittenTTS review rounds):
# - Build in a ROOT-OWNED staging dir (/var/lib/hermes-kittentts, 0755 so
# the uid-986 delivery step can read it; nothing in it is secret):
# stamp, staging venv, uv cache and staging model copies all live there.
# Root never reads or executes anything the container can write — the
# container cannot symlink-takeover the stamp (review #1) or plant a
# wheel in the uv cache (review #2); only FINISHED artifacts are copied
# into hermesHome, and the stamp compares the delivered copy against
# staging byte-for-byte.
# - Root runs python ONLY from the staging venv (never from the delivered
# uid-986-owned tree in hermesHome) — after a root-side offline import
# check; a failing build aborts before anything is delivered.
# - HF model cache delivered to hermesHome from staging (refs/main ->
# snapshots/<real commit sha>), and the container env pins
# HF_HOME=/opt/data/kittentts-hf + HF_HUB_OFFLINE=1: zero boot-time
# network, no drift.
# - Idempotency stamp keyed on the FULL input set (requirements + wheel +
# stub + model files), not just requirements (review #5); a deleted
# delivered file falls out of the fast path and re-provisions cheaply.
# - Order after prepare-dirs (review #6) — mirrors the mnemosyne unit.
#
# Trust boundary: the DELIVERED venv lives inside hermesHome
# (HERMES_WRITE_SAFE_ROOT), so luna can alter her own TTS engine — and a
# deleted/modified copy just triggers a fresh delivery from the root-owned
# staging area on next boot (self-heals instead of wedging). Deliberate: it's
# her voice, not her jail — the webhook filter scripts remain the only
# write-protected-but-load-bearing items.
systemd.services.hermes-agent-kittentts-provision = {
description = "Provision KittenTTS voice provider (side venv + offline HF cache)";
before = [ "podman-hermes-agent.service" ];
wantedBy = [ "podman-hermes-agent.service" ];
wants = [ "network-online.target" ];
# after prepare-dirs (review #6): on a fresh state dir this unit must not
# create hermesHome root-owned before prepare-dirs sets the ownership
# layout — same ordering contract the mnemosyne unit has. PLUS
# network-online ordering (review2 #3): this unit CAN download at boot
# (unlike mnemosyne's store-path build), so uv must not run before the
# network is actually up.
after = [
"network-online.target"
"hermes-agent-prepare-dirs.service"
];
requires = [ "hermes-agent-prepare-dirs.service" ];
# diffutils: `cmp` in the fast path; missing, it silently re-delivers every boot.
path = [ pkgs.uv pkgs.coreutils pkgs.diffutils pkgs.util-linux pkgs.auto-patchelf pkgs.patchelf ];
serviceConfig = {
Type = "oneshot";
TimeoutStartSec = 600;
};
script = ''
set -euo pipefail
venv=${hermesHome}/kittentts-venv
hubDir=${hermesHome}/kittentts-hf/hub/models--KittenML--kitten-tts-mini-0.8
# Real upstream commit SHA as snapshot dir: hf_hub_download resolves
# refs/main -> snapshots/<sha>; "kitten" (review #3) is never found
# offline and silently triggers a re-download.
modelSha=c02725660cea441db4c383af69f1f26f5cd00947
snap=$hubDir/snapshots/$modelSha
# REVIEW #1/#2: nothing root touches lives in hermesHome. Stamp, staging
# venv and uv cache live root-owned under /var/lib/hermes-kittentts (a
# path the container can not pathwrite or symlinks into its own tree);
# the FINISHED staging venv and model files are the only things copied
# into hermesHome, and only after being validated. Root doesn't follow
# any uid-986-writable path while running as root.
stageDir=/var/lib/hermes-kittentts
# Root-side staging of the DELIVERED TREE (venv + full HF hub layout
# including refs/main) everything root writes lives here.
# review2 #4: root never writes into hermesHome; delivery happens as
# the container uid via setpriv, copying from these root-owned sources.
stageVenv=$stageDir/venv
stageHub=$stageDir/kittentts-hf
stageSnap=$stageDir/hf-model
# Input key: requirements + wheel + stub + model files + resolved script.
# Review #5 a miss on the old requirements-only stamp let a changed
# wheel/stub/model URL keep running the stale install forever.
inputHash=$({ cat ${kittenttsReqs} ${kittenttsSttReqs} ${kittenttsWheel} ${kittenttsStub} \
${kittenttsModelOnnx} ${kittenttsModelVoices} ${kittenttsModelConfig}
echo ${kittenttsRuntimeLibs}; } | sha256sum | cut -d' ' -f1)
stampFile=$stageDir/provision.stamp
# Idempotent early exit: stamp matches the full input hash, staging venv
# validated, delivered copy intact check runs below.
if [ -f "$stampFile" ] && [ "$(cat "$stampFile")" = "$inputHash" ] \
&& [ -x "$stageVenv/bin/python" ] \
&& [ -f "$stageSnap/config.json" ]; then
# Delivered artifacts in hermesHome must ALSO match the staging copy:
# luna can rewrite her copy, that's fine but then the missing file
# forces a re-provision (cheap copy, not a rebuild) so deletions
# cannot wedge the gateway without a voice.
if [ -x "$venv/bin/python" ] \
&& cmp -s "$stageSnap/config.json" "$snap/config.json" 2>/dev/null \
&& cmp -s "$stageSnap/kitten_tts_mini_v0_8.onnx" "$snap/kitten_tts_mini_v0_8.onnx" 2>/dev/null \
&& cmp -s "$stageSnap/voices.npz" "$snap/voices.npz" 2>/dev/null; then
exit 0
fi
fi
# ---- staging venv + staged hub tree: root-owned path, uv cache
# included. Root runs python from HERE (container can write nothing in
# this tree); the FINISHED result is copied into hermesHome AS THE
# CONTAINER USER via setpriv (review2 #4) root never writes into
# hermesHome, so no uid-986-controlled path is ever followed while
# running as root; symlink-takeover of stamps/refs/install targets is
# structurally impossible. ----
mkdir -p "$stageDir" "$stageSnap" "$stageVenv" "$stageHub"
# 0755: the setpriv'd uid-986 delivery below must be able to read it.
chmod 0755 "$stageDir"
install -m 0444 ${kittenttsModelConfig} "$stageSnap/config.json"
install -m 0444 ${kittenttsModelOnnx} "$stageSnap/kitten_tts_mini_v0_8.onnx"
install -m 0444 ${kittenttsModelVoices} "$stageSnap/voices.npz"
# Skip the venv REBUILD when staging is still valid (review2 minor:
# damaged delivery should be a copy, not a rebuild) but only for the
# CURRENT inputs, else a bump delivers the old venv under a new stamp.
if [ "$(cat "$stampFile" 2>/dev/null)" != "$inputHash" ] \
|| ! [ -x "$stageVenv/bin/python" ] \
|| ! [ -f "$stageVenv/lib/python3.13/site-packages/kitten_tts_stub.py" ]; then
rm -rf "$stageVenv"
UV_CACHE_DIR=$stageDir/uv-cache \
uv venv "$stageVenv" --python ${pkgs.python313}/bin/python3 --quiet
UV_CACHE_DIR=$stageDir/uv-cache \
uv pip install --python "$stageVenv/bin/python" --quiet \
--requirement ${kittenttsReqs}
# kittentts --no-deps: its overlay of spacy/misaki[en] is what drags in
# the CUDA tree; the requirements freeze already covers its real needs.
UV_CACHE_DIR=$stageDir/uv-cache \
uv pip install --python "$stageVenv/bin/python" --quiet --no-deps \
${kittenttsWheelFile}
# Dead-import shim: .pth auto-loads kitten_tts_stub at interpreter
# start so `from misaki import en, espeak` resolves without the real
# misaki.en.
siteDir=$("$stageVenv/bin/python" -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
cp ${kittenttsStub} "$siteDir/kitten_tts_stub.py"
printf 'import kitten_tts_stub\n' > "$siteDir/zz-kitten-stub.pth"
# PyPI manylinux .so's (numpy, onnxruntime) need libstdc++, which the
# Nix loader never finds, on the host or in the container.
auto-patchelf --paths "$stageVenv" --libs ${kittenttsRuntimeLibs}
# STT add-on: faster-whisper into the SAME venv (shares the
# numpy/onnxruntime pins; see kittentts/stt-requirements.txt).
UV_CACHE_DIR=$stageDir/uv-cache \
uv pip install --python "$stageVenv/bin/python" --quiet \
--requirement ${kittenttsSttReqs}
# av's bundled ffmpeg libs also need the stdc++/zlib link (already
# covered by the auto-patchelf pass above rerun it so the newly
# installed ctranslate2/av binaries get RPATHs too).
auto-patchelf --paths "$stageVenv" --libs ${kittenttsRuntimeLibs}
fi
# Stage the full delivered hub tree (exact hf_hub_download layout:
# refs/main -> snapshots/<sha>; review1 #3) under root-owned staging.
stageModelDir=$stageHub/hub/models--KittenML--kitten-tts-mini-0.8
mkdir -p "$stageModelDir/refs" "$stageModelDir/snapshots/$modelSha"
printf '%s' "$modelSha" > "$stageModelDir/refs/main"
install -m 0444 ${kittenttsModelOnnx} "$stageModelDir/snapshots/$modelSha/kitten_tts_mini_v0_8.onnx"
install -m 0444 ${kittenttsModelVoices} "$stageModelDir/snapshots/$modelSha/voices.npz"
install -m 0444 ${kittenttsModelConfig} "$stageModelDir/snapshots/$modelSha/config.json"
# No blobs/ indirection: kittentts reads paths RETURNED by
# hf_hub_download, which serves the resolved snapshot file directly
# (prefer-dir layout works offline for fully-materialized files).
# Root-side sanity: the staged interpreter must construct the model
# END-TO-END offline (review2 minor import alone doesn't exercise
# hf_hub_download; a broken cache layout must fail HERE, not in the
# gateway). Points HF_HOME at the staged hub tree itself.
HF_HOME=$stageHub \
HF_HUB_OFFLINE=1 \
PHONEMIZER_ESPEAK_LIBRARY="$("$stageVenv/bin/python" -c 'import espeakng_loader,pathlib;print(pathlib.Path(espeakng_loader.get_library_path()))')" \
PHONEMIZER_ESPEAK_DATA_PATH="$("$stageVenv/bin/python" -c 'import espeakng_loader,pathlib;print(pathlib.Path(espeakng_loader.get_data_path()))')" \
"$stageVenv/bin/python" -c 'from kittentts import KittenTTS; KittenTTS("KittenML/kitten-tts-mini-0.8"); print("kittentts offline build ok")' >/dev/null
# ---- deliver AS THE CONTAINER USER (review2 #4): root never writes
# into hermesHome, so no symlink race and no `chown` step. setpriv
# drops to uid 986, rm -rf's the old delivered copies and copies the
# fresh staging tree in. cp-as-986 also fixes review2 #2: rm+cp in one
# step, no mv -Tf rename-replace on a non-empty directory.
setpriv --reuid=${hermesUid} --regid=${hermesGid} --clear-groups \
${pkgs.runtimeShell} -c '
set -eu
rm -rf "$1" "$2"
cp -a "$3" "$1"
cp -a "$4" "$2"
' _ \
"${hermesHome}/kittentts-venv" \
"${hermesHome}/kittentts-hf" \
"$stageVenv" \
"$stageHub"
# Stamp LAST, root-owned outside hermesHome luna can delete it (which
# forces a cheap re-delivery on next boot), not tamper via symlink.
printf '%s' "$inputHash" > "$stampFile"
'';
};
} }
@@ -0,0 +1,34 @@
# Runtime shim for KittenTTS on a CPU-only, disk-constrained host.
#
# kittentts/onnx_model.py opens with `from misaki import en, espeak` — a dead import:
# every code path that produces audio from text goes through phonemizer-fork's
# EspeakBackend (espeak-ng via espeakng-loader); misaki.en/espeak are never called.
# If misaki[en] were installed for real, those two imports would be a hook for the
# whole spacy → thinc → torch → CUDA chain (~5.6 GB verified). This stub registers
# `misaki.en` and `misaki.espeak` as import-time-only placeholders instead.
#
# Written into site-packages by the derivation (see pkgs/kittentts-env.nix) under
# `sitecustomize.py`-style auto-load — actually via a `kitten_tts_stub.py` + a `.pth`
# pointing at it, so any Python process in this env gets the stub registered before
# any kittentts import. If KittenTTS upstream ever starts USING misaki, this shim
# will fail loudly at import of the missing attributes (better than silent distortion),
# and the fix becomes "install the real misaki[en]" — a deliberate, reviewed change.
import sys
import types
_misaki = sys.modules.get("misaki")
if _misaki is None:
# Avoid registering a fake parent before the real misaki loads — the base misaki
# (addict/regex only) is installed normally, so usually already here.
import misaki # noqa: F401 (raises if base misaki is missing — loud, not silent)
_misaki = sys.modules["misaki"]
if getattr(_misaki, "en", None) is None:
_stub = types.ModuleType("misaki.en")
sys.modules.setdefault("misaki.en", _stub)
_misaki.en = _stub
if getattr(_misaki, "espeak", None) is None:
_stub2 = types.ModuleType("misaki.espeak")
sys.modules.setdefault("misaki.espeak", _stub2)
_misaki.espeak = _stub2
+69
View File
@@ -0,0 +1,69 @@
# Pinned requirements for the KittenTTS side-venv on mars (validated freeze, CPU-only).
#
# DEPENDENCY DISCIPLINE (important — this is NOT the upstream dependency set):
# `kittentts` declares `misaki[en]` + spacy, which transitively pull torch → the whole
# CUDA runtime (~5.6 GB with nvidia-* wheels, verified in a clean install on this box).
# NONE of that is reachable at runtime: onnx_model.py imports `from misaki import en, espeak`
# at module load but generate() phonemizes exclusively through `phonemizer-fork` +
# `espeakng-loader` (espeak-ng via EspeakBackend); misaki.en/espeak glyphs are parsed but
# never called in the ONNX path. mars has no GPU and a modest disk budget, so:
#
# - install kittentts with --no-deps and enumerate its deps manually,
# - install base `misaki` (NOT misaki[en]) — only addict/regex,
# - the misaki.en import is satisfied by a stub registered in sitecustomize
# (kitten-misaki-stub.py, written into site-packages by the derivation).
#
# Freeze captured 2026-09-19 from a verified CPU-only venv (python 3.13, i5-4460,
# generation ~= 1x realtime with the mini model, HF_HUB_OFFLINE=1).
#
# kittentts itself comes from a GitHub release wheel (0.8.1), not PyPI (PyPI's 0.1.3 is
# the old v0.1-lineage); see pkgs/kittentts-env.nix.
addict==2.4.0
anyio==4.15.1
attrs==26.1.0
babel==2.18.0
certifi==2026.7.22
cffi==2.1.1
click==8.5.0
cloudpickle==3.1.2
csvw==4.1.0
dlinfo==2.0.0
docopt==0.6.2
espeakng-loader==0.2.4
filelock==4.0.1
flatbuffers==25.12.19
fsspec==2026.9.0
h11==0.16.0
hf-xet==1.6.0
httpcore==1.0.9
httpx==0.28.1
huggingface-hub==1.32.0
idna==3.20
isodate==0.7.2
joblib==1.6.0
jsonschema-specifications==2025.9.1
jsonschema==4.26.0
language-tags==1.3.1
misaki==0.9.4
num2words==0.5.14
numpy==2.5.3
onnxruntime==1.30.0
packaging==26.3
phonemizer-fork==3.3.2
protobuf==7.36.2
pycparser==3.0
pyparsing==3.3.2
python-dateutil==2.9.0.post0
pyyaml==6.0.3
rdflib==7.6.0
referencing==0.37.0
regex==2026.9.10
rfc3986==1.5.0
rpds-py==2026.6.3
segments==2.4.0
six==1.17.0
soundfile==0.14.0
termcolor==3.3.0
tqdm==4.70.1
typing-extensions==4.16.0
uritemplate==4.2.0
+17
View File
@@ -0,0 +1,17 @@
# STT add-on pins for the KittenTTS side-venv on mars (faster-whisper CPU).
#
# APPENDED to hosts/mars/kittentts/requirements.txt by the provisioning unit
# (see hermes-agent.nix, KittenTTS section) so ONE venv covers the entire
# local-audio stack: TTS (KittenTTS) + STT (faster-whisper). Shares the
# numpy/onnxruntime pins already frozen there — no version conflicts.
#
# faster-whisper runs CTranslate2 on CPU; the tiny/base model (~150 MB)
# downloads once from the HF hub then works fully offline. Validated
# 2026-09-19 with a live Telegram voice note transcribed on i5-4460 CPU.
#
# NOTE on av: PyP AV 18.x needs libz/libstdc++ from the host. On NixOS those
# come via the nix store — the provisioning unit exports LD_LIBRARY_PATH with
# them at runtime (see hermes-agent.nix's kittenttsLDLibraryPath).
faster-whisper==1.2.1
ctranslate2==4.8.2
av==18.1.0