<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Haider Farooq</title>
    <description>The latest articles on DEV Community by Haider Farooq (@haiderfarooq3).</description>
    <link>https://dev.to/haiderfarooq3</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4088012%2F0ef1892f-3846-4dc5-8a3a-21467e7ba729.jpg</url>
      <title>DEV Community: Haider Farooq</title>
      <link>https://dev.to/haiderfarooq3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haiderfarooq3"/>
    <language>en</language>
    <item>
      <title>Claude Code Skills for Content: How I Make Khan Academy Style Videos for Free</title>
      <dc:creator>Haider Farooq</dc:creator>
      <pubDate>Sun, 11 Oct 2026 00:27:42 +0000</pubDate>
      <link>https://dev.to/haiderfarooq3/claude-code-skills-for-content-how-i-make-khan-academy-style-videos-for-free-52e2</link>
      <guid>https://dev.to/haiderfarooq3/claude-code-skills-for-content-how-i-make-khan-academy-style-videos-for-free-52e2</guid>
      <description>&lt;p&gt;I opened a &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7514233403710353408/" rel="noopener noreferrer"&gt;LinkedIn post&lt;/a&gt; last week with "I think Claude Code skills are mostly useless." It got about 30,000 impressions and a lot of disagreement, so let me be precise about what I meant, and about the one skill that changed my mind.&lt;/p&gt;

&lt;p&gt;A skill is a markdown file Claude Code reads when your request matches its description. Most of the ones I have seen are prompt shortcuts: a paragraph of instructions you would otherwise type again. Claude is already good enough to do that work from a plain sentence. Saving the sentence is not worth a repo.&lt;/p&gt;

&lt;p&gt;The exception is a skill that ships something Claude cannot improvise in one shot. A renderer. A capture recipe that took a day to get right. A list of rules learned from thirty failed outputs. That kind of skill is a tool with a manual, and the manual is what the model reads. &lt;a href="https://github.com/haiderfarooq3/khan-explainer" rel="noopener noreferrer"&gt;khan-explainer&lt;/a&gt; is that kind. I built it to make Khan Academy style explainer videos, where a pen draws on a blackboard while a voice explains, and the whole thing runs free on a laptop with no API keys. Two days after I published it, it had 58 stars, 16 forks and a merged pull request from someone I had never met.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/JD9OirRC3YE" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhkxci2f6sz54zma8b1x8.jpg" alt="khan-explainer explains itself" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Nobody is holding this pen. Claude Code drew this video from a prompt. The voice is ElevenLabs; everything else is free. Watch it on YouTube: &lt;a href="https://youtu.be/JD9OirRC3YE" rel="noopener noreferrer"&gt;https://youtu.be/JD9OirRC3YE&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What it makes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blackboard lessons.&lt;/strong&gt; A diagram drawn stroke by stroke while the voice explains it. The original Khan Academy look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Draw-over-UI tutorials.&lt;/strong&gt; Arrows, circles and numbered steps drawn over your own product screenshots. "Press this, then this."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-audio clips.&lt;/strong&gt; A YouTube video or podcast in, vertical shorts out. The speaker's own voice plays while the board is drawn to their words. It can also cut the speaker out of the frame and stand them in front of the board.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every mode renders as chalk on a blackboard or marker on a whiteboard, landscape or vertical. One environment variable switches the theme, and a single beat can flip the board mid-video.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7vba0zoqjqh81par3ehy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7vba0zoqjqh81par3ehy.jpg" alt="The same khan-explainer scene rendered on a blackboard and on a whiteboard" width="800" height="225"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same scene file, rendered dark and light. Colour names map to chalk on one and marker on the other.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The stack, and why it costs nothing
&lt;/h2&gt;

&lt;p&gt;There is no video model anywhere in this. Four boring pieces do the work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An HTML canvas&lt;/strong&gt; draws the handwriting. Every letter and shape is a path, revealed a little more on each frame. A handwriting font plus progressive stroke drawing is enough to read as a hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright&lt;/strong&gt; opens that page in headless Chromium and screenshots it frame by frame. This is the same tool people use for browser tests. It is also a perfectly good frame grabber.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS &lt;code&gt;say&lt;/code&gt;&lt;/strong&gt; speaks each line to an audio file. It is robotic, but it is free, it is on every Mac, and it reports exactly how long each clause takes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ffmpeg&lt;/strong&gt; stitches the frames and the audio into an MP4.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the full bill of materials. The commercial tools that make this style of video charge monthly subscriptions. Manim can do it and is beautiful, but it is a library you learn, not a prompt you type. AI video generators bill per second and cannot draw a diagram that stays consistent for sixty seconds. For a teaching video, the pen is the point, and the pen is just canvas strokes.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one idea that makes it work: the voice drives the clock
&lt;/h2&gt;

&lt;p&gt;Every attempt at this that I have seen fails the same way. Someone writes a script, guesses that a sentence takes three seconds, animates to three seconds, and then the voice lands early or late. By the fourth sentence the pen is drawing the wrong thing.&lt;/p&gt;

&lt;p&gt;khan-explainer never guesses. A scene is a list of &lt;strong&gt;beats&lt;/strong&gt;. A beat is one spoken clause plus the drawing that happens while it is said. The renderer speaks each beat first, measures the audio, and gives that beat's drawing exactly that long. Nobody types a timestamp, so the pen cannot run ahead of the words.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;beats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;say&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What is an AI agent?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What is an AI agent?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yellow&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;say&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;It's software that uses a language model to think,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;oval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;155&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1. THINK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;328&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;p&lt;/code&gt; runs from 0 to 1 while the clause is spoken. &lt;code&gt;seg(p, 0, .4)&lt;/code&gt; spends the first 40% of the beat on the oval and the rest on the label. That is the entire timing model. Claude writes these scene files from a one-line prompt because the rules are short enough to hold in its head.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/haiderfarooq3/khan-explainer ~/.claude/skills/khan-explainer
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.claude/skills/khan-explainer/scripts &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask Claude Code for the video you want. "Make a 30-second Khan-style explainer on how DNS works." "Draw over these screenshots and show a new user how to send their first invoice." "Khanify this YouTube video, light and dark." Claude writes the scene, renders it, reads the contact sheet, fixes what is wrong, and hands you the MP4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tricks that transfer to any content skill
&lt;/h2&gt;

&lt;p&gt;The renderer is maybe a third of the value. The rest is the rules in the skill file, and most of them apply to any skill that produces content, whether that is video, slides, ads or diagrams. These are the ones that earned their place.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Make the skill grade its own output
&lt;/h3&gt;

&lt;p&gt;Every render prints a contact sheet: the board at the end of every beat, in one image, plus every beat full-size as &lt;code&gt;b000.jpg&lt;/code&gt;, &lt;code&gt;b001.jpg&lt;/code&gt; and so on. The renderer also prints &lt;code&gt;WARN&lt;/code&gt; lines for text that runs off the board or one label sitting on another. Claude is told to read the sheet and fix every warning before showing you anything. A render takes seconds, so it iterates three or four times and you only see the last one. Whatever your skill makes, give the model a cheap way to look at the result. Without it, you are the QA department.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lay out with the free voice, switch on the expensive one last
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;say&lt;/code&gt; is free and instant, so all the layout work happens with it. When the board is right, you add an ElevenLabs voice id to the scene. The whole script is read in &lt;strong&gt;one take&lt;/strong&gt; so it sounds like one person talking, then cut back into beats at the character timestamps the API returns. Takes are cached by a hash of the script, so re-rendering the same words is free. Changing one drawing costs nothing. Changing one word buys a new take. Order the work so the paid step runs once.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Keep the units small enough to re-render
&lt;/h3&gt;

&lt;p&gt;The most common question under the LinkedIn post was from a teacher: if one step in a worked solution is wrong, can I fix that step, or does it need a full rerun? The answer is that you edit one beat and re-render, and the new file exists a few seconds later. That is only possible because a beat is a few lines of JavaScript, not a timeline in an editor. Design the unit of content so a human can point at one of them and say "that one is wrong."&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Measure, never eyeball
&lt;/h3&gt;

&lt;p&gt;For draw-over-UI tutorials the skill captures screenshots with Playwright and saves &lt;code&gt;getBoundingClientRect()&lt;/code&gt; for every button and field it will point at. It never reads coordinates off the image. It also measures again for every state, because elements move when they are focused, typed into, or when a menu opens over them. The &lt;code&gt;view()&lt;/code&gt; helper maps capture pixels to board units so the rings land on the right button at any crop. The early tutorials where I let the model guess coordinates from the screenshot all circled the wrong thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Write the script once, theme-neutral, and cut everything from it
&lt;/h3&gt;

&lt;p&gt;The video in this post exists in four cuts: dark and light, landscape and vertical. They share one voice take because the script never says "blackboard" or "left". The theme is a flag, the aspect ratio is a size, and the voice cache does not care about either. If your content needs variants, decide what the variants are before the first expensive step, and keep anything variant-specific out of the shared layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Hit a target length by changing words, not speed
&lt;/h3&gt;

&lt;p&gt;A quick explainer runs about &lt;code&gt;words ÷ 3 + beats × 0.15 + 1.3&lt;/code&gt; seconds, within roughly 5%. A ten-second explainer is about 24 words in 5 beats. A 60-second tutorial is about 145 words in 15 beats. The skill file includes this formula so Claude can plan a 30-second video that is actually 30 seconds, instead of speeding up the voice until it fits. Speeding up the voice is how you get the "too AI" complaint.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Put the failures in the skill file
&lt;/h3&gt;

&lt;p&gt;Every bad render taught the skill something, and the lesson went into &lt;code&gt;SKILL.md&lt;/code&gt; where the model reads it. A few examples. ElevenLabs' multilingual model at speed 1.12 with style 0.35 sounded, in the words of the first person I showed it to, "too AI", so the skill now uses the v3 model and speeds the finished video with ffmpeg's &lt;code&gt;atempo&lt;/code&gt; instead. Screenshots are dimmed towards the board colour so chalk stays readable on a busy screen. Private data is hidden in the page before any still is taken. Demos are never staged in a shared or public channel. One render stalled forever once, so the build script has a watchdog. None of this is clever. It is the difference between a skill that works on the second try and one that works on the tenth.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Real audio is a different product, not a feature
&lt;/h3&gt;

&lt;p&gt;The clip mode downloads a video with yt-dlp, transcribes it locally with faster-whisper, and lets you mark the moments worth a board. Each clip gets its own scene drawn to the speaker's own words, and a macOS Vision cutout can stand the speaker in front of the board for the vertical version. One 20-minute talk became 22 clips in an afternoon. The reason to mention it here: the renderer did not change at all. Once timing comes from measured audio, the audio can come from anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it falls short
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The default voice is robotic. For anything you publish, you will want ElevenLabs or a real recording.&lt;/li&gt;
&lt;li&gt;It is macOS first. &lt;code&gt;say&lt;/code&gt; and the Vision cutout are Apple APIs. The renderer itself is a browser page, and the first outside contribution (right-to-left writing for Arabic and Hebrew) was tested on Windows by driving &lt;code&gt;board.html&lt;/code&gt; directly, but a Windows voice path does not exist yet.&lt;/li&gt;
&lt;li&gt;The handwriting is a font drawn progressively, not a model of a hand. It reads well at normal speed and less well paused.&lt;/li&gt;
&lt;li&gt;It is not affiliated with Khan Academy. The name describes the look.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this is a blog post and not just a LinkedIn post
&lt;/h2&gt;

&lt;p&gt;Two days after the post went up, I ran a sweep of every public surface I could reach: X, Reddit, Hacker News, YouTube, newsletters, skill directories, package registries. Outside GitHub, not one human had mentioned the repo. The only pickups were two crawlers. Bing had not indexed the repository at all. The LinkedIn post itself, with its 30,000 impressions, is invisible to every search engine; the exact hook line returns nothing.&lt;/p&gt;

&lt;p&gt;That is the deal with social: a day of reach, then nothing. A video you post to a feed is gone from the record in a week. A page on a domain you own is a search result for years, gets cited by AI answer engines, and is where the repo can point back to. This post is that page. If you build something and it does well on a feed, write it down somewhere with a URL you control, the same week, while the traffic is still there to link to it.&lt;/p&gt;

&lt;p&gt;If you want to see the other formats I make this way, the &lt;a href="https://haiderfarooq.dev/content" rel="noopener noreferrer"&gt;content catalogue&lt;/a&gt; has one finished example of each. If you want this kind of pipeline built for your product, the usual place to start is the &lt;a href="https://haiderfarooq.dev/contact" rel="noopener noreferrer"&gt;contact page&lt;/a&gt;. And if you just want the skill, &lt;a href="https://github.com/haiderfarooq3/khan-explainer" rel="noopener noreferrer"&gt;it is on GitHub&lt;/a&gt;, MIT licensed. Go draw something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions people asked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is khan-explainer really free?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. It renders with an HTML canvas in headless Chromium (Playwright), the voice built into macOS (say), and ffmpeg. There are no API keys, accounts, or usage limits. The only optional paid part is swapping the Mac voice for an ElevenLabs voice, and even that works on the free tier for short scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it work on Windows or Linux?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The default voice and the talking-head cutout are macOS only. The renderer itself is a browser page driven by Playwright, so the first outside contributor tested their change on Windows by driving board.html directly. Linux works with espeak-ng in place of say. Full Windows support would need a different TTS call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does a render take?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seconds. The 65-second video in this post rendered in under a minute on a laptop. The voice is generated first, then each beat's drawing is fitted to it, then ffmpeg stitches frames and audio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I fix one wrong step without re-rendering everything?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. A scene is a list of beats. Edit the beat, re-render, and you have a new MP4 in seconds. If you are using an ElevenLabs voice, the take is cached, so changing only the drawing costs nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can it use my own voice or a real person's voice?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The real-audio mode takes a YouTube link or an audio file, transcribes it locally with faster-whisper, and draws the board to the speaker's words. One 20-minute video produced 22 clips. For a synthetic human voice, set an ElevenLabs voice id on the scene.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it affiliated with Khan Academy?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. 'Khan Academy style' describes the look: handwriting on a dark board, drawn as it is explained. The project is independent and MIT licensed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://haiderfarooq.dev/blog/claude-code-skills-for-content" rel="noopener noreferrer"&gt;haiderfarooq.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>opensource</category>
      <category>video</category>
    </item>
    <item>
      <title>RAG That Actually Works: A Practical Checklist</title>
      <dc:creator>Haider Farooq</dc:creator>
      <pubDate>Fri, 21 Aug 2026 10:12:38 +0000</pubDate>
      <link>https://dev.to/haiderfarooq3/rag-that-actually-works-a-practical-checklist-4mnm</link>
      <guid>https://dev.to/haiderfarooq3/rag-that-actually-works-a-practical-checklist-4mnm</guid>
      <description>&lt;p&gt;Retrieval-augmented generation is the most requested AI feature and the most commonly botched. The failure is almost never the generation step -- it's retrieval quietly returning the wrong context, and the model confidently summarizing garbage. This checklist comes from building research and analysis pipelines where wrong retrieval means wrong business decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunking: respect the document's own structure
&lt;/h2&gt;

&lt;p&gt;Fixed 512-token windows sliced mid-sentence are the default and the mistake. Chunk on semantic boundaries -- headings, paragraphs, table rows -- and prepend each chunk with its breadcrumb ("Annual Report 2025 &amp;gt; Risk Factors &amp;gt; Currency"). A chunk that can't be understood alone can't be retrieved reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hybrid search beats embeddings alone
&lt;/h2&gt;

&lt;p&gt;Dense vectors miss exact identifiers -- SKUs, names, error codes -- that keyword search catches trivially. Run BM25 and vector search in parallel and fuse results (reciprocal rank fusion is fine). This one change fixes a huge share of "the answer was in the corpus but retrieval missed it" tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rerank before you generate
&lt;/h2&gt;

&lt;p&gt;First-stage retrieval optimizes recall; a cross-encoder reranker optimizes precision on the top 30-50 candidates. Feeding the model 6 highly relevant chunks beats feeding it 20 mediocre ones -- better answers &lt;em&gt;and&lt;/em&gt; lower token spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metadata filters are half the battle
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Store structured fields (date, source, doc type, customer) alongside every chunk.&lt;/li&gt;
&lt;li&gt;Resolve filters &lt;em&gt;before&lt;/em&gt; semantic search -- "latest pricing" should hard-filter on recency, not hope embeddings notice.&lt;/li&gt;
&lt;li&gt;Let the LLM extract the filter from the question with a typed schema, then apply it in the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Answer honesty: refuse to guess
&lt;/h2&gt;

&lt;p&gt;The system prompt must make abstention a first-class outcome: if the retrieved context doesn't contain the answer, say so and show what &lt;em&gt;was&lt;/em&gt; found. Require citations to chunk IDs and render them in the UI. Users forgive "not found"; they don't forgive confident fabrication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluate retrieval separately from generation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build a golden set of ~50 real questions with known source passages.&lt;/li&gt;
&lt;li&gt;Measure retrieval hit-rate (is the right chunk in the top-k?) -- this isolates most failures.&lt;/li&gt;
&lt;li&gt;Then grade end-to-end answers for faithfulness and completeness.&lt;/li&gt;
&lt;li&gt;Re-run on every change to chunking, embeddings, or prompts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;RAG is a search-quality problem wearing an AI costume. Treat it with the same rigor as the rest of your production agent stack, and it stops being the feature users quietly distrust.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://haiderfarooq.dev/blog/rag-that-actually-works" rel="noopener noreferrer"&gt;haiderfarooq.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
    </item>
    <item>
      <title>7 Lessons from Building Agentic AI in Production</title>
      <dc:creator>Haider Farooq</dc:creator>
      <pubDate>Fri, 21 Aug 2026 10:01:55 +0000</pubDate>
      <link>https://dev.to/haiderfarooq3/7-lessons-from-building-agentic-ai-in-production-253d</link>
      <guid>https://dev.to/haiderfarooq3/7-lessons-from-building-agentic-ai-in-production-253d</guid>
      <description>&lt;p&gt;For the past year I've been a core engineer on TryCook.ai, an AI operating system that replaced a $3M/year fulfillment team and powers 348+ founders. That means agents doing real, billable work every day -- not demos. Here are the seven lessons that survived contact with production.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The agent is 20% of the system
&lt;/h2&gt;

&lt;p&gt;The LLM call is the easy part. The other 80% is queues, retries, idempotency keys, state machines, audit logs, and rollback paths. If you architect the agent as a stateless function inside a boring, observable pipeline, failures become recoverable. If the agent &lt;em&gt;is&lt;/em&gt; the pipeline, every hallucination is an outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Constrain outputs structurally, not rhetorically
&lt;/h2&gt;

&lt;p&gt;Prompts that beg the model to "only respond with valid JSON" fail at scale. Schema-enforced outputs (tool calling, JSON mode, grammar constraints) fail approximately never. Every agent boundary in our stack is a typed contract -- if the model can't fill the schema, that's a retry, not a parse error downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Design for the 2% failure case first
&lt;/h2&gt;

&lt;p&gt;An agent that's 98% autonomous still fails dozens of times a day at volume. The difference between a product and a liability is what happens on failure: does the task land in a human review queue with full context, or does it silently vanish? Build the escalation path before you build the happy path.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Small models for routing, big models for judgment
&lt;/h2&gt;

&lt;p&gt;Most agent steps are classification, extraction, or formatting -- cheap-model work. Reserve frontier models for the few steps that need actual judgment. This is the core of cost engineering for LLM products, and it routinely cuts spend 5-10x with no quality loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Memory is a database problem, not a prompt problem
&lt;/h2&gt;

&lt;p&gt;Long-running agents need durable state: what they did, what they learned, what the customer prefers. Stuffing history into the context window doesn't scale and isn't queryable. Model agent memory as normal rows -- events, facts, preferences -- and retrieve selectively. Retrieval discipline is the same skill as building RAG that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Evals are your regression tests
&lt;/h2&gt;

&lt;p&gt;Every prompt change ships against a fixed eval set of real (anonymized) cases with graded outputs. Without evals, prompt engineering is vibes; with them, it's engineering. Start with 30 cases and a pass/fail rubric -- you can grow sophistication later, but you can't retrofit confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Humans are a feature, not a fallback
&lt;/h2&gt;

&lt;p&gt;The systems that win don't remove humans -- they move humans up the stack. In a well-designed agentic system, one operator supervises the throughput that used to take a team, intervening only on flagged exceptions. Frame it that way and adoption stops being a fight.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Agents don't replace engineering discipline. They punish the lack of it, at machine speed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://haiderfarooq.dev/blog/lessons-agentic-ai-production" rel="noopener noreferrer"&gt;haiderfarooq.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Get Cited by AI Search Engines: A Practical GEO Checklist</title>
      <dc:creator>Haider Farooq</dc:creator>
      <pubDate>Fri, 21 Aug 2026 09:59:59 +0000</pubDate>
      <link>https://dev.to/haiderfarooq3/how-to-get-cited-by-ai-search-engines-a-practical-geo-checklist-4ae7</link>
      <guid>https://dev.to/haiderfarooq3/how-to-get-cited-by-ai-search-engines-a-practical-geo-checklist-4ae7</guid>
      <description>&lt;p&gt;Search is splitting in two. Classic Google still matters, but a growing share of discovery now happens inside ChatGPT, Perplexity, Claude, and Google's AI Overviews. Those engines don't rank ten blue links — they synthesize an answer and cite a handful of sources. Getting cited is the new ranking. The practice of engineering for it is called &lt;strong&gt;Generative Engine Optimization (GEO)&lt;/strong&gt;, and this post documents the exact setup running on my site.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Let the AI crawlers in
&lt;/h2&gt;

&lt;p&gt;Every AI engine has its own crawler, and many sites block them by accident with a blanket rule. Your &lt;code&gt;robots.txt&lt;/code&gt; should explicitly allow the bots you want: &lt;code&gt;GPTBot&lt;/code&gt; and &lt;code&gt;OAI-SearchBot&lt;/code&gt; (OpenAI), &lt;code&gt;PerplexityBot&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt; and &lt;code&gt;anthropic-ai&lt;/code&gt; (Anthropic), &lt;code&gt;Google-Extended&lt;/code&gt; (Gemini training), &lt;code&gt;Applebot-Extended&lt;/code&gt; (Apple Intelligence), and &lt;code&gt;CCBot&lt;/code&gt; (Common Crawl, which feeds many models).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;Agent&lt;/span&gt;: &lt;span class="n"&gt;GPTBot&lt;/span&gt;
&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;Agent&lt;/span&gt;: &lt;span class="n"&gt;OAI&lt;/span&gt;-&lt;span class="n"&gt;SearchBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;Agent&lt;/span&gt;: &lt;span class="n"&gt;PerplexityBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;Agent&lt;/span&gt;: &lt;span class="n"&gt;ClaudeBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;Sitemap&lt;/span&gt;: &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;haiderfarooq&lt;/span&gt;.&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;sitemap&lt;/span&gt;.&lt;span class="n"&gt;xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Ship an llms.txt
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is an emerging convention: a plain-markdown file at your site root that gives language models a compressed, structured summary of who you are and what your pages contain. Think of it as a sitemap for meaning rather than URLs. Keep it under a few hundred lines, lead with a one-sentence identity statement, and list your key pages with one-line summaries each. A fuller &lt;code&gt;llms-full.txt&lt;/code&gt; can carry the long version.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Build an entity graph with JSON-LD
&lt;/h2&gt;

&lt;p&gt;AI engines resolve &lt;em&gt;entities&lt;/em&gt;, not keywords. Schema.org JSON-LD is how you declare them unambiguously. The minimum viable graph for a personal site: a &lt;code&gt;Person&lt;/code&gt; node with &lt;code&gt;@id&lt;/code&gt;, &lt;code&gt;sameAs&lt;/code&gt; links to GitHub/LinkedIn/X, &lt;code&gt;knowsAbout&lt;/code&gt;, and &lt;code&gt;hasOccupation&lt;/code&gt;; a &lt;code&gt;WebSite&lt;/code&gt; node; and &lt;code&gt;CreativeWork&lt;/code&gt; or &lt;code&gt;Article&lt;/code&gt; nodes for project pages. Crucially, use stable &lt;code&gt;@id&lt;/code&gt; URIs and reference them across pages so crawlers can stitch the graph together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://haiderfarooq.dev/#person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Haider Farooq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobTitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Engineer &amp;amp; Data Scientist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/haiderfarooq3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.linkedin.com/in/haiderfarooqdev/"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Write outcome-first copy
&lt;/h2&gt;

&lt;p&gt;LLMs extract and repeat concrete numbers. "$12.1M in client revenue", "141 districts", "5,500+ patients" — figures like these survive summarization; adjectives don't. Put at least one hard number in the first 150 words of every page. Write bios in third person ("Haider Farooq is an AI engineer…") because that's the frame AI answers use when citing you.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Freshness and internal linking still matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Update pages on a real cadence — stale &lt;code&gt;lastmod&lt;/code&gt; dates in your sitemap depress recrawl priority.&lt;/li&gt;
&lt;li&gt;Interlink related pages with descriptive anchor text; AI crawlers follow links to build context just like Googlebot does.&lt;/li&gt;
&lt;li&gt;Use IndexNow to ping Bing (which feeds ChatGPT search and Copilot) the moment content changes.&lt;/li&gt;
&lt;li&gt;Keep canonical URLs consistent — one host, one protocol, no trailing-slash ambiguity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;robots.txt&lt;/code&gt; allows GPTBot, OAI-SearchBot, PerplexityBot, ClaudeBot, Google-Extended, CCBot&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llms.txt&lt;/code&gt; + &lt;code&gt;llms-full.txt&lt;/code&gt; at site root&lt;/li&gt;
&lt;li&gt;JSON-LD Person/WebSite/Article graph with stable &lt;code&gt;@id&lt;/code&gt; URIs&lt;/li&gt;
&lt;li&gt;One concrete metric in the first 150 words of every page&lt;/li&gt;
&lt;li&gt;Sitemap with honest &lt;code&gt;lastmod&lt;/code&gt; dates, submitted to Google Search Console and Bing&lt;/li&gt;
&lt;li&gt;IndexNow key installed and pinged on every deploy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this replaces good content — it makes good content legible to machines.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://haiderfarooq.dev/blog/geo-visibility-ai-search" rel="noopener noreferrer"&gt;haiderfarooq.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>ai</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
