cm-readit-5a0ed2 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cm-readit-5a0ed2 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Philosophy: Reading is passive. Listening is intimate. Voice builds trust faster than any headline. Core Principle: Zero dependencies. Progressive enhancement. Respect user's device and preferences.
| File | Status | When to Read |
|---|---|---|
| tts-engine.md | 🔴 REQUIRED | Adding TTS / read-aloud to any page |
| audio-player.md | ⚪ Optional | Pre-recorded MP3 playback |
| voice-cro.md | ⚪ Optional | Trigger-based voice sales / CRO |
| ui-patterns.md | ⚪ Optional | Player bar & bottom sheet design |
🔴 tts-engine.md = ALWAYS READ when implementing TTS. Others = only if relevant.
"I need audio on my website"
│
├─ Read article content aloud (text-to-speech)
│ └─ Use: TTS Engine → tts-engine.md
│ ├─ Blog / article pages → Content Reader pattern
│ ├─ Documentation → Section Reader pattern
│ └─ E-commerce → Product Description Reader pattern
│
├─ Play pre-recorded audio files (MP3/WAV)
│ └─ Use: Audio Player → audio-player.md
│ ├─ Podcasts / interviews → Playlist pattern
│ ├─ Sales pitch / welcome → Triggered playback
│ └─ Background ambient → Loop pattern
│
├─ Voice-based conversion optimization (CRO)
│ └─ Use: Voice CRO → voice-cro.md
│ ├─ Landing pages → Trigger-based bottom sheet
│ ├─ Service pages → Per-page audio scripts
│ └─ Course pages → Social proof audio
│
└─ Combination (TTS + CRO)
└─ Read tts-engine.md + voice-cro.md
└─ Ensure no conflict (TTS reader vs CRO player)| Engine | API | Source | Best For |
|---|---|---|---|
| TTS Reader | SpeechSynthesis | Page text content | Blogs, articles, docs |
| Audio Player | HTMLAudioElement | Pre-recorded MP3 | Sales, podcasts, guides |
| Voice CRO | Audio + triggers | MP3 + behavior detection | Landing pages, sales |
Feature detection → Graceful degradation → Never break the page
if (!('speechSynthesis' in window)) return; // TTS
if (!window.Audio) return; // AudioRule: Audio features are ENHANCEMENTS. The page must function 100% without them.
Clone → Strip → Clean → Split → Speak
DON'T read the raw DOM.
DO clone, remove noise, extract clean text.Strip list (always remove before speaking):
Browsers have a hard limit on utterance length (~3000-5000 chars depending on browser/OS). Long text must be split into chunks.
Split Strategy:
├─ Split on sentence boundaries (. ! ? \n)
├─ Max chunk: 2500 chars (safe across all browsers)
├─ Preserve sentence integrity (never split mid-sentence)
└─ Chain chunks via onend callbackLanguage voices:
1. Local service voice (faster, works offline)
2. Network voice (higher quality, needs internet)
3. Any voice matching language prefix
4. null (browser default)⚠️ CRITICAL: Chrome silently stops SpeechSynthesis after ~15 seconds of continuous speech. This is the #1 gotcha.
// Workaround: pause/resume every 10s
setInterval(() => {
if (synth.speaking && !synth.paused) {
synth.pause();
synth.resume();
}
}, 10000);⚠️ GOTCHA: Callingsynth.cancel()fires theonerrorevent on any active utterance with error type'canceled'or'interrupted'.
Solution: Use a guard flag or check error type:
u.onerror = function(e) {
if (e.error === 'canceled' || e.error === 'interrupted') return;
stopReading();
};┌─────────────────────────────────────────┐
│ IIFE │
│ │
│ ┌─ Feature Detection ─┐ │
│ │ speechSynthesis? │ │
│ └──────────┬───────────┘ │
│ ▼ │
│ ┌─ Content Extraction ─┐ │
│ │ Clone → Strip → Clean│ │
│ └──────────┬────────────┘ │
│ ▼ │
│ ┌─ Chunking Engine ────┐ │
│ │ Split on sentences │ │
│ │ Max 2500 chars │ │
│ └──────────┬────────────┘ │
│ ▼ │
│ ┌─ Utterance Builder ──┐ │
│ │ Set voice/rate/pitch │ │
│ │ Chain via onend │ │
│ └──────────┬────────────┘ │
│ ▼ │
│ ┌─ Player UI ──────────┐ │
│ │ Bar: play/pause/stop │ │
│ │ Progress indicator │ │
│ │ Trigger button │ │
│ └──────────┬────────────┘ │
│ ▼ │
│ ┌─ Keep-Alive Timer ───┐ │
│ │ pause/resume @ 10s │ │
│ └───────────────────────┘ │
└──────────────────────────────────────────┘Init → Detect → Inject Trigger Button
│
User clicks ▶
│
Extract Text → Chunk → Build Utterances
│
synth.speak(chunk[0])
│
chunk[0].onend → speak(chunk[1]) → ... → speak(chunk[N])
│ │
Keep-Alive Timer running chunk[N].onend
│ │
User clicks ⏸ → synth.pause() stopReading()
User clicks ▶ → synth.resume() cleanup UI
User clicks ✕ → synth.cancel()speechSynthesis in window)onerror guard (handle cancel/interrupted)beforeunload cleanupprefers-reduced-motion respectnone → load on demand)currentTime/duration| Pitfall | Symptom | Fix |
|---|---|---|
| Chrome stops after 15s | Audio cuts mid-sentence | Keep-alive timer (pause/resume) |
synth.cancel() fires onerror | Settings sheet closes immediately | Guard flag or check error type |
| Voices not loaded | No voice available | Listen for voiceschanged event |
| Chunk too large | Utterance fails silently | Max 2500 chars per chunk |
| Reading CTA text | TTS reads "Book Now" button text | Strip non-content elements |
| Autoplay blocked | Audio won't start on mobile | Require user interaction first |
| Multiple audio conflicts | TTS + CRO play simultaneously | Mutual exclusion check |
| No cleanup on nav | Audio keeps playing | beforeunload → synth.cancel() |
Voice selection by language:
├─ Vietnamese: v.lang === 'vi-VN' || v.lang.startsWith('vi')
├─ English: v.lang === 'en-US' || v.lang.startsWith('en')
├─ Japanese: v.lang === 'ja-JP' || v.lang.startsWith('ja')
├─ Korean: v.lang === 'ko-KR' || v.lang.startsWith('ko')
└─ Any: Pass language code as config parameterSet utterance.lang to match the content language for correct pronunciation.
| File | Content |
|---|---|
| tts-engine.md | Complete SpeechSynthesis API reference, chunking strategies, voice selection |
| audio-player.md | HTMLAudioElement patterns, preload strategies, error handling |
| voice-cro.md | Trigger system, bottom sheet patterns, CRO analytics |
| ui-patterns.md | Player bar CSS, bottom sheet CSS, animations, responsive design |
| File | Description |
|---|---|
| examples/blog-reader.js | Complete TTS reader — Substack-style, 350 LOC |
| examples/voice-cro.js | Complete Voice CRO trigger system — 390 LOC |
Remember: Voice is the most personal interface. A well-placed audio feature can increase engagement 3-5x. But unwanted audio is the fastest way to lose a user. Always require user initiation. Never autoplay.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.