<?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: Alex Shev</title>
    <description>The latest articles on DEV Community by Alex Shev (@alexshev).</description>
    <link>https://dev.to/alexshev</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3812101%2Fa5dfb3f9-d006-41a1-a868-995b7f219269.png</url>
      <title>DEV Community: Alex Shev</title>
      <link>https://dev.to/alexshev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexshev"/>
    <language>en</language>
    <item>
      <title>Your AI Agent Does Not Need More Context. It Needs a Smaller Workflow.</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Wed, 13 May 2026 22:38:18 +0000</pubDate>
      <link>https://dev.to/alexshev/your-ai-agent-does-not-need-more-context-it-needs-a-smaller-workflow-41p3</link>
      <guid>https://dev.to/alexshev/your-ai-agent-does-not-need-more-context-it-needs-a-smaller-workflow-41p3</guid>
      <description>&lt;p&gt;A lot of AI agent workflows are becoming expensive for a very boring reason:&lt;/p&gt;

&lt;p&gt;We keep giving the agent too much context and not enough direction.&lt;/p&gt;

&lt;p&gt;The usual pattern looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is my whole repo.
Here are 12 tools.
Here are 9 docs.
Here is the bug.
Please figure it out.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes it works.&lt;/p&gt;

&lt;p&gt;But it is also how you end up with huge token usage, messy tool calls, slow runs, and an agent that spends half the session rediscovering what a human already knows.&lt;/p&gt;

&lt;p&gt;More context feels safer.&lt;/p&gt;

&lt;p&gt;In practice, it often makes the workflow worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem is not context. The problem is unfiltered context.
&lt;/h2&gt;

&lt;p&gt;AI agents do need context.&lt;/p&gt;

&lt;p&gt;They need the right files, the right constraints, the right examples, and the right definition of done.&lt;/p&gt;

&lt;p&gt;What they do not need is every possible thing that might be relevant.&lt;/p&gt;

&lt;p&gt;That is where many workflows go wrong.&lt;/p&gt;

&lt;p&gt;A small task becomes a giant investigation because the agent has no boundary.&lt;/p&gt;

&lt;p&gt;For example, imagine asking an agent to update a pricing component.&lt;/p&gt;

&lt;p&gt;Bad version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read the app and update the pricing page.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update the pricing card component.
Only inspect files under components/pricing and app/pricing.
Do not change billing logic.
Run the component test and TypeScript check.
Return a short summary plus changed files.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second prompt is not just shorter.&lt;/p&gt;

&lt;p&gt;It is a smaller workflow.&lt;/p&gt;

&lt;p&gt;That is the part that matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools make this easier and harder
&lt;/h2&gt;

&lt;p&gt;MCP and other tool protocols are useful because they give agents cleaner access to external systems.&lt;/p&gt;

&lt;p&gt;That is a real improvement.&lt;/p&gt;

&lt;p&gt;But tool access also creates a new problem:&lt;/p&gt;

&lt;p&gt;The agent can now search more, read more, call more APIs, and collect more context than it actually needs.&lt;/p&gt;

&lt;p&gt;A connected agent is powerful.&lt;/p&gt;

&lt;p&gt;A connected agent with no workflow boundary is expensive.&lt;/p&gt;

&lt;p&gt;This is why I think a lot of teams are asking the wrong question.&lt;/p&gt;

&lt;p&gt;The question is not only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What tools can my agent access?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The better question is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is the smallest reliable workflow this task needs?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one question changes how you design the agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  A workflow beats a giant prompt
&lt;/h2&gt;

&lt;p&gt;When I say “workflow,” I mean a repeatable operating pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when to use it&lt;/li&gt;
&lt;li&gt;which files or tools are in scope&lt;/li&gt;
&lt;li&gt;which files or tools are out of scope&lt;/li&gt;
&lt;li&gt;what steps to follow&lt;/li&gt;
&lt;li&gt;what checks to run&lt;/li&gt;
&lt;li&gt;what output format to return&lt;/li&gt;
&lt;li&gt;what should trigger a human handoff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is different from a one-off prompt.&lt;/p&gt;

&lt;p&gt;A prompt is a request.&lt;/p&gt;

&lt;p&gt;A workflow is a habit.&lt;/p&gt;

&lt;p&gt;And agents need good habits more than they need another 2,000 words of instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: debugging without reading the universe
&lt;/h2&gt;

&lt;p&gt;Here is a simple debugging workflow I use mentally all the time.&lt;/p&gt;

&lt;p&gt;Instead of telling the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Debug this issue.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want the agent to follow a narrow loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Reproduce the error.
2. Identify the smallest failing command or test.
3. Inspect only the files directly involved.
4. Make one focused change.
5. Re-run the failing check.
6. Stop if the fix requires product judgment.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That workflow does two useful things.&lt;/p&gt;

&lt;p&gt;First, it keeps the agent from wandering through unrelated code.&lt;/p&gt;

&lt;p&gt;Second, it creates a clear stopping point.&lt;/p&gt;

&lt;p&gt;A lot of agent waste happens because there is no stopping point.&lt;/p&gt;

&lt;p&gt;The agent keeps searching, summarizing, and patching because “done” was never defined.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: code review with a smaller surface area
&lt;/h2&gt;

&lt;p&gt;The same idea applies to code review.&lt;/p&gt;

&lt;p&gt;A vague agent review sounds like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review this PR.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can produce a long list of generic comments.&lt;/p&gt;

&lt;p&gt;A smaller workflow is better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review only for:
- data loss risks
- auth or permission mistakes
- broken edge cases
- missing tests for changed behavior

Ignore style unless it affects correctness.
Return only high-confidence findings.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns the agent from a noisy reviewer into a useful filter.&lt;/p&gt;

&lt;p&gt;It also saves attention.&lt;/p&gt;

&lt;p&gt;The goal is not to make the agent say more.&lt;/p&gt;

&lt;p&gt;The goal is to make the agent say fewer, better things.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is where skills help
&lt;/h2&gt;

&lt;p&gt;This is why I like packaging repeatable workflows as skills.&lt;/p&gt;

&lt;p&gt;A skill can tell the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For this kind of task, use this workflow.
Use these tools.
Avoid these traps.
Verify with these checks.
Return this output.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much more useful than repeatedly writing giant prompts.&lt;/p&gt;

&lt;p&gt;For example, a media-processing skill could teach the agent the standard FFmpeg workflow.&lt;/p&gt;

&lt;p&gt;A deployment skill could teach it the exact deploy and verification steps.&lt;/p&gt;

&lt;p&gt;A code-review skill could teach it what kinds of issues matter and what kinds of comments to ignore.&lt;/p&gt;

&lt;p&gt;A security skill could tell it when to stop and ask for human approval.&lt;/p&gt;

&lt;p&gt;The skill is not magic.&lt;/p&gt;

&lt;p&gt;It is just reusable judgment.&lt;/p&gt;

&lt;p&gt;And reusable judgment is exactly what most agent setups are missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The smallest workflow test
&lt;/h2&gt;

&lt;p&gt;Before giving an agent a task, I like to ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is the smallest workflow that can finish this safely?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I reduce the task until the answer is clear.&lt;/p&gt;

&lt;p&gt;That usually means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer files&lt;/li&gt;
&lt;li&gt;fewer tools&lt;/li&gt;
&lt;li&gt;fewer open-ended instructions&lt;/li&gt;
&lt;li&gt;more explicit checks&lt;/li&gt;
&lt;li&gt;a clearer definition of done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds less impressive than “agent with full repo access and every tool connected.”&lt;/p&gt;

&lt;p&gt;But it works better.&lt;/p&gt;

&lt;p&gt;Small workflows are easier to test.&lt;/p&gt;

&lt;p&gt;Small workflows are easier to repeat.&lt;/p&gt;

&lt;p&gt;Small workflows are easier to trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  My rule of thumb
&lt;/h2&gt;

&lt;p&gt;If an agent keeps burning tokens, I do not immediately add a better model.&lt;/p&gt;

&lt;p&gt;I ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the task too broad?&lt;/li&gt;
&lt;li&gt;Did I give it too many tools?&lt;/li&gt;
&lt;li&gt;Did I define done?&lt;/li&gt;
&lt;li&gt;Did I tell it what not to inspect?&lt;/li&gt;
&lt;li&gt;Can this become a reusable skill?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time, the fix is not more intelligence.&lt;/p&gt;

&lt;p&gt;It is less ambiguity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;AI agents are getting better fast.&lt;/p&gt;

&lt;p&gt;But better models will not remove the need for workflow design.&lt;/p&gt;

&lt;p&gt;If anything, stronger agents make workflow design more important because they can do more damage, spend more tokens, and move faster in the wrong direction.&lt;/p&gt;

&lt;p&gt;The next layer of useful agent work is not just bigger context windows.&lt;/p&gt;

&lt;p&gt;It is smaller, clearer, reusable workflows.&lt;/p&gt;

&lt;p&gt;That is what I am collecting at Terminal Skills: practical examples of skills that give agents narrower, repeatable ways to do real work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;https://terminalskills.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More context is not always the answer.&lt;/p&gt;

&lt;p&gt;Sometimes the best thing you can give an AI agent is a smaller job.&lt;/p&gt;

&lt;p&gt;AI-assisted. Human reviewed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>devtools</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI Receptionist Cost in 2026: How to Calculate ROI for Small Business Automation</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Thu, 07 May 2026 23:43:57 +0000</pubDate>
      <link>https://dev.to/alexshev/ai-receptionist-cost-in-2026-how-to-calculate-roi-for-small-business-automation-8cb</link>
      <guid>https://dev.to/alexshev/ai-receptionist-cost-in-2026-how-to-calculate-roi-for-small-business-automation-8cb</guid>
      <description>&lt;p&gt;This is a practical breakdown for builders, consultants, and operators thinking about AI phone automation for small businesses.&lt;/p&gt;

&lt;p&gt;The goal is not to hype “AI receptionists,” but to show what actually drives cost: call volume, integrations, booking workflows, follow-up, and missed-call recovery.&lt;/p&gt;

&lt;p&gt;Small-business owners are not asking whether AI can answer the phone anymore. They are asking what an AI receptionist costs, what is included, and whether it pays for itself faster than hiring another front-desk person.&lt;/p&gt;

&lt;p&gt;The honest answer is that AI receptionist cost depends on what you expect the system to do. A basic voice bot that takes messages is one category. A real AI employee that answers calls, texts missed callers, books appointments, updates your CRM, and follows up with leads is a different investment.&lt;/p&gt;

&lt;p&gt;This guide breaks down AI receptionist pricing in practical terms so you can compare monthly cost against recovered calls, booked jobs, after-hours leads, and staff time saved.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Affects AI Receptionist Cost?
&lt;/h2&gt;

&lt;p&gt;Most pricing differences come from capability, not from the word “AI.” A cheap system may answer calls but fail when the caller asks a real question. A stronger system understands your business rules and turns conversations into outcomes.&lt;/p&gt;

&lt;p&gt;The main drivers are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call volume and whether pricing is per minute or flat monthly&lt;/li&gt;
&lt;li&gt;voice quality and how natural the conversation feels&lt;/li&gt;
&lt;li&gt;whether it can text, email, and follow up after the call&lt;/li&gt;
&lt;li&gt;calendar, CRM, payment, and dispatch integrations&lt;/li&gt;
&lt;li&gt;setup work: scripts, FAQs, service areas, offers, and escalation rules&lt;/li&gt;
&lt;li&gt;reporting on missed calls, booked appointments, and revenue impact&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Basic AI Answering vs. Full AI Receptionist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic call handling
&lt;/h3&gt;

&lt;p&gt;Entry-level tools are useful if you only need a greeting, simple routing, or voicemail replacement. They are usually cheaper, but they often stop at message-taking. For businesses that depend on booked appointments, that is not enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full AI receptionist
&lt;/h3&gt;

&lt;p&gt;A full AI receptionist behaves more like a trained front-desk employee. It answers common questions, qualifies the lead, checks availability, books the next step, sends confirmations, and escalates edge cases. That is where ROI usually appears.&lt;/p&gt;

&lt;p&gt;If an AI receptionist only saves labor, the math is decent. If it captures leads that were previously lost, the math gets much better.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Calculate ROI
&lt;/h2&gt;

&lt;p&gt;Use a simple model before buying anything. Start with the calls you already receive, not fantasy traffic.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many calls do you miss each week?&lt;/li&gt;
&lt;li&gt;How many after-hours calls go to voicemail?&lt;/li&gt;
&lt;li&gt;What percentage of callers become booked appointments?&lt;/li&gt;
&lt;li&gt;What is one booked job or consultation worth?&lt;/li&gt;
&lt;li&gt;How much staff time goes into answering repeat questions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If AI recovers just 5 to 10 extra leads per month and each booked customer is worth hundreds or thousands of dollars, the system can pay for itself quickly.&lt;/p&gt;

&lt;p&gt;This is why phone-heavy businesses often see ROI faster than companies with low call volume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Small Businesses Usually Overpay
&lt;/h2&gt;

&lt;p&gt;Some businesses overpay for software that looks advanced but does not connect to operations. Others underpay for a cheap bot, then still need staff to clean up every conversation.&lt;/p&gt;

&lt;p&gt;The goal is not the lowest monthly invoice. The goal is the lowest cost per qualified appointment.&lt;/p&gt;

&lt;p&gt;Before choosing a tool, ask whether it can handle your actual calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pricing questions&lt;/li&gt;
&lt;li&gt;service-area checks&lt;/li&gt;
&lt;li&gt;emergency routing&lt;/li&gt;
&lt;li&gt;appointment changes&lt;/li&gt;
&lt;li&gt;lead qualification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it cannot, the “savings” may disappear into manual cleanup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Fit Businesses
&lt;/h2&gt;

&lt;p&gt;AI receptionist systems tend to fit best when the phone is directly connected to revenue or operations.&lt;/p&gt;

&lt;p&gt;Good examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;home service companies with technicians in the field&lt;/li&gt;
&lt;li&gt;med spas, clinics, salons, and appointment-based local businesses&lt;/li&gt;
&lt;li&gt;professional services that miss calls during client work&lt;/li&gt;
&lt;li&gt;restaurants and hospitality businesses with repetitive phone questions&lt;/li&gt;
&lt;li&gt;any company paying for Google Ads, Local Services Ads, or social traffic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much does an AI receptionist cost for a small business?
&lt;/h3&gt;

&lt;p&gt;Pricing varies widely. Simple AI answering tools may be relatively low-cost, while full-service implementations with scheduling, CRM integration, follow-up, and reporting cost more.&lt;/p&gt;

&lt;p&gt;Many full-service AI receptionist implementations can land around $1,000–3,000 per month, depending on scope.&lt;/p&gt;

&lt;p&gt;The real cost depends on call volume, voice quality, scheduling, CRM integrations, and whether the system can book appointments instead of only taking messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is an AI receptionist cheaper than hiring a human receptionist?
&lt;/h3&gt;

&lt;p&gt;Usually yes, especially when the goal is 24/7 coverage. A full-time receptionist includes salary, payroll taxes, benefits, training, management, and coverage gaps.&lt;/p&gt;

&lt;p&gt;An AI receptionist can provide round-the-clock call coverage and lead capture for a predictable monthly cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  What gives an AI receptionist the fastest ROI?
&lt;/h3&gt;

&lt;p&gt;The fastest ROI usually comes from recovering missed calls, booking after-hours leads, reducing voicemail leakage, and following up instantly with people who would otherwise call a competitor.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;An AI receptionist should be judged by booked outcomes, not novelty.&lt;/p&gt;

&lt;p&gt;If it answers calls, responds after hours, follows up instantly, and gets more prospects onto the calendar, it is not just a phone tool. It is a revenue-protection layer.&lt;/p&gt;

&lt;p&gt;Originally published on AIEmployees:&lt;br&gt;
&lt;a href="https://aiemployees.us/blog/ai-receptionist-cost-small-business-2026" rel="noopener noreferrer"&gt;https://aiemployees.us/blog/ai-receptionist-cost-small-business-2026&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>business</category>
    </item>
    <item>
      <title>How I Built a CLI Skill to Batch-Process YouTube Shorts</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Thu, 07 May 2026 23:38:39 +0000</pubDate>
      <link>https://dev.to/alexshev/how-i-built-a-cli-skill-to-batch-process-youtube-shorts-476k</link>
      <guid>https://dev.to/alexshev/how-i-built-a-cli-skill-to-batch-process-youtube-shorts-476k</guid>
      <description>&lt;p&gt;Last month, I had to process 16 YouTube Shorts.&lt;/p&gt;

&lt;p&gt;Trim intros. Normalize audio. Add watermarks. Export multiple formats. Generate thumbnails.&lt;/p&gt;

&lt;p&gt;Doing that manually in Premiere would have taken me most of an afternoon.&lt;/p&gt;

&lt;p&gt;So I built a CLI skill instead.&lt;/p&gt;

&lt;p&gt;It took about 2 hours to put together. On my machine, the batch itself finished in under 3 minutes once everything was set up.&lt;/p&gt;

&lt;p&gt;This is the kind of repeatable workflow I think of as a Terminal Skill: small, documented, reusable automation that turns a messy manual task into one command.&lt;/p&gt;

&lt;p&gt;Here’s the exact structure I used.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I mean by a CLI skill
&lt;/h2&gt;

&lt;p&gt;For me, a CLI skill is a reusable shell workflow with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input validation&lt;/li&gt;
&lt;li&gt;sensible defaults&lt;/li&gt;
&lt;li&gt;predictable output&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;li&gt;lightweight docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of retyping a long FFmpeg command every time, I run one script and get the same result every time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Instead of this:&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:02 &lt;span class="nt"&gt;-to&lt;/span&gt; 00:00:35 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1080:1920"&lt;/span&gt; &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-preset&lt;/span&gt; fast output.mp4

&lt;span class="c"&gt;# I run this:&lt;/span&gt;
./process-short.sh input.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That difference sounds small, but it removes the part that always breaks in real work: remembering the flags, the order, and the output steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: List the manual steps first
&lt;/h2&gt;

&lt;p&gt;Before I wrote anything, I wrote down the full workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;trim the first 2 seconds&lt;/li&gt;
&lt;li&gt;cut after 35 seconds&lt;/li&gt;
&lt;li&gt;scale to 1080×1920&lt;/li&gt;
&lt;li&gt;normalize audio to -14 LUFS&lt;/li&gt;
&lt;li&gt;add watermark&lt;/li&gt;
&lt;li&gt;export MP4&lt;/li&gt;
&lt;li&gt;export WebM&lt;/li&gt;
&lt;li&gt;generate a thumbnail&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gave me a real pipeline instead of a vague automation idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Build one script that does the boring work
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;TRIM_START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:02"&lt;/span&gt;
&lt;span class="nv"&gt;TRIM_END&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:35"&lt;/span&gt;
&lt;span class="nv"&gt;RESOLUTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1080:1920"&lt;/span&gt;
&lt;span class="nv"&gt;AUDIO_TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-14"&lt;/span&gt;
&lt;span class="nv"&gt;WATERMARK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./assets/watermark.png"&lt;/span&gt;
&lt;span class="nv"&gt;THUMB_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:05"&lt;/span&gt;

&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?Usage:&lt;span class="p"&gt; process-short.sh &amp;lt;input.mp4&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: File '&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;' not found."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; .mp4&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./output/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-ss&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRIM_START&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-to&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRIM_END&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESOLUTION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUDIO_TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-preset&lt;/span&gt; fast &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WATERMARK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WATERMARK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"overlay=W-w-20:H-h-20"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:v libvpx-vp9 &lt;span class="nt"&gt;-crf&lt;/span&gt; 30 &lt;span class="nt"&gt;-b&lt;/span&gt;:v 0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.webm"&lt;/span&gt;

ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-ss&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$THUMB_TIME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_thumb.jpg"&lt;/span&gt;

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few choices mattered a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;set -euo pipefail&lt;/code&gt; so failures don’t get ignored&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-y&lt;/code&gt; because this is a pipeline, not an interactive tool&lt;/li&gt;
&lt;li&gt;temp file cleanup so output folders stay usable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Make it batch-capable
&lt;/h2&gt;

&lt;p&gt;One file is a demo. A directory is the real use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;INPUT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?Usage:&lt;span class="p"&gt; batch-process.sh &amp;lt;directory&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PROCESSED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    if&lt;/span&gt; ./process-short.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="o"&gt;((&lt;/span&gt;PROCESSED++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAILED: &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="o"&gt;((&lt;/span&gt;FAILED++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Batch complete: &lt;/span&gt;&lt;span class="nv"&gt;$PROCESSED&lt;/span&gt;&lt;span class="s2"&gt; processed, &lt;/span&gt;&lt;span class="nv"&gt;$FAILED&lt;/span&gt;&lt;span class="s2"&gt; failed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where it actually became useful. I didn’t want a cool script. I wanted to stop babysitting repetitive exports.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Add docs, even if it’s just for yourself
&lt;/h2&gt;

&lt;p&gt;I also added a tiny &lt;code&gt;SKILL.md&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the script does&lt;/li&gt;
&lt;li&gt;requirements&lt;/li&gt;
&lt;li&gt;usage&lt;/li&gt;
&lt;li&gt;config variables&lt;/li&gt;
&lt;li&gt;output files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds boring, but it matters. A script without docs becomes archaeology in two weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Test annoying edge cases
&lt;/h2&gt;

&lt;p&gt;This was the part that caught real bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;empty files&lt;/li&gt;
&lt;li&gt;files with missing or unexpected audio tracks&lt;/li&gt;
&lt;li&gt;short clips&lt;/li&gt;
&lt;li&gt;weird filenames with spaces and brackets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That testing found multiple issues immediately. If I had skipped it, the batch version would have failed silently later.&lt;/p&gt;

&lt;p&gt;One of the first things I ran into was how quickly “works on one file” falls apart on real footage. A weird filename, a silent clip, or a shorter-than-expected video is enough to break the whole flow if you never test for it.&lt;/p&gt;

&lt;p&gt;The simplified script above assumes the input has an audio stream. In my real version, I added a small &lt;code&gt;ffprobe&lt;/code&gt; check before applying &lt;code&gt;loudnorm&lt;/code&gt;, because silent clips need a different path.&lt;/p&gt;




&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; most of an afternoon for 16 videos&lt;br&gt;&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; one command, then the machine does the repetitive part&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./batch-process.sh ./raw-videos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s still less flexible than Premiere. If I want one-off polish, I’ll still do it manually.&lt;/p&gt;

&lt;p&gt;But for repeatable batch work, the tradeoff is absolutely worth it.&lt;/p&gt;

&lt;p&gt;That’s why I like building CLI skills.&lt;/p&gt;

&lt;p&gt;Not because they’re clever. Because they turn something fragile and repetitive into something boring and reliable.&lt;/p&gt;

&lt;p&gt;If you build little terminal workflows or CLI skills like this too, I’d genuinely love to hear what you’ve automated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would you automate first?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>cli</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>MCP Is Not the Product — Reusable Skills Are</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Tue, 24 Mar 2026 22:20:07 +0000</pubDate>
      <link>https://dev.to/alexshev/mcp-is-not-the-product-reusable-skills-are-27fp</link>
      <guid>https://dev.to/alexshev/mcp-is-not-the-product-reusable-skills-are-27fp</guid>
      <description>&lt;p&gt;Right now, a lot of people are talking about MCP.&lt;/p&gt;

&lt;p&gt;And I get why.&lt;/p&gt;

&lt;p&gt;It’s a clean idea: connect an AI agent to tools, data, and actions through a standard interface, and suddenly the model can actually do things.&lt;/p&gt;

&lt;p&gt;That matters.&lt;/p&gt;

&lt;p&gt;But I think a lot of people are still confusing the plumbing with the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP is useful. MCP is not the product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The product is what happens after the connection exists.&lt;/p&gt;

&lt;p&gt;The product is a reusable skill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools are not enough
&lt;/h2&gt;

&lt;p&gt;Giving an agent access to tools sounds powerful.&lt;/p&gt;

&lt;p&gt;But in practice, raw tool access is messy.&lt;/p&gt;

&lt;p&gt;If you just hand an agent ten tools, you usually get one of these outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it uses the wrong one&lt;/li&gt;
&lt;li&gt;it uses the right one in the wrong order&lt;/li&gt;
&lt;li&gt;it calls the same thing three times with weak assumptions&lt;/li&gt;
&lt;li&gt;it produces output that technically worked, but isn’t reusable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s because tools are still too low-level.&lt;/p&gt;

&lt;p&gt;A tool is a capability.&lt;br&gt;
A skill is a workflow.&lt;/p&gt;

&lt;p&gt;That difference is everything.&lt;/p&gt;


&lt;h2&gt;
  
  
  A simple example
&lt;/h2&gt;

&lt;p&gt;I’ve seen this in boring, practical work more than once.&lt;/p&gt;

&lt;p&gt;An agent with raw shell access and FFmpeg access can absolutely process a video.&lt;/p&gt;

&lt;p&gt;But “can process a video” is not the same as “can reliably produce the same short-form output every time.”&lt;/p&gt;

&lt;p&gt;The raw-tool version tends to drift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrong trim point&lt;/li&gt;
&lt;li&gt;audio forgotten&lt;/li&gt;
&lt;li&gt;watermark missing&lt;/li&gt;
&lt;li&gt;output naming inconsistent&lt;/li&gt;
&lt;li&gt;thumbnail skipped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The skill version is much narrower, but much more useful.&lt;/p&gt;

&lt;p&gt;It knows the sequence.&lt;br&gt;
It knows the defaults.&lt;br&gt;
It knows what “done” looks like.&lt;/p&gt;

&lt;p&gt;That’s the part people actually want in production.&lt;/p&gt;

&lt;p&gt;Not possibility.&lt;br&gt;
Reliability.&lt;/p&gt;


&lt;h2&gt;
  
  
  What a skill actually is
&lt;/h2&gt;

&lt;p&gt;A skill is not just “the agent can run a command.”&lt;/p&gt;

&lt;p&gt;A useful skill packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the goal&lt;/li&gt;
&lt;li&gt;the right sequence of actions&lt;/li&gt;
&lt;li&gt;defaults&lt;/li&gt;
&lt;li&gt;constraints&lt;/li&gt;
&lt;li&gt;output expectations&lt;/li&gt;
&lt;li&gt;failure handling&lt;/li&gt;
&lt;li&gt;context about when to use it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare these two ideas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool: FFmpeg is available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Skill: Turn a raw clip into a 1080x1920 short with trimmed intro, normalized audio, watermark, and thumbnail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one is what people actually want.&lt;/p&gt;

&lt;p&gt;No one wakes up thinking:&lt;br&gt;
“I hope my agent has access to a media binary.”&lt;/p&gt;

&lt;p&gt;They want the result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters for AI agents
&lt;/h2&gt;

&lt;p&gt;The current wave of agent demos still over-indexes on tool access.&lt;/p&gt;

&lt;p&gt;You see a lot of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database access&lt;/li&gt;
&lt;li&gt;filesystem access&lt;/li&gt;
&lt;li&gt;browser access&lt;/li&gt;
&lt;li&gt;shell access&lt;/li&gt;
&lt;li&gt;API access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all necessary.&lt;br&gt;
But it’s still not enough.&lt;/p&gt;

&lt;p&gt;Because once the novelty wears off, teams ask a more practical question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can this do the same useful task reliably more than once?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s where most agents still fall apart.&lt;/p&gt;

&lt;p&gt;They can improvise.&lt;br&gt;
They can explore.&lt;br&gt;
They can sometimes solve a task.&lt;/p&gt;

&lt;p&gt;But reliable repeated execution comes from skills.&lt;/p&gt;

&lt;p&gt;Skills are what turn “the agent figured it out once” into “the agent can do this whenever I need it.”&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP helps. Skills deliver.
&lt;/h2&gt;

&lt;p&gt;This is why I think the framing matters.&lt;/p&gt;

&lt;p&gt;MCP is important because it standardizes access.&lt;/p&gt;

&lt;p&gt;That’s good.&lt;/p&gt;

&lt;p&gt;But once access is standardized, the real differentiation shifts somewhere else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which workflows are packaged well&lt;/li&gt;
&lt;li&gt;which tasks are reusable&lt;/li&gt;
&lt;li&gt;which skills are trustworthy&lt;/li&gt;
&lt;li&gt;which outputs are predictable&lt;/li&gt;
&lt;li&gt;which agent behaviors are actually worth repeating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP gives agents hands. Skills give them habits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And habits are what make a system valuable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The more useful pattern
&lt;/h2&gt;

&lt;p&gt;The better pattern is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use MCP to expose capabilities&lt;/li&gt;
&lt;li&gt;use skills to package outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That way the agent is not just connected.&lt;br&gt;
It is directed.&lt;/p&gt;

&lt;p&gt;And that is a much more practical way to build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I care about this
&lt;/h2&gt;

&lt;p&gt;This is exactly why I’m bullish on skill-based systems.&lt;/p&gt;

&lt;p&gt;Because once you start packaging repeatable terminal workflows into reusable skills, a lot of noisy AI hype suddenly becomes much simpler.&lt;/p&gt;

&lt;p&gt;You stop asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what tools can this model access?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And start asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what useful work can this system repeat reliably?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a better question.&lt;/p&gt;

&lt;p&gt;And in my experience, it leads to much better products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools unlock possibility. Skills unlock reliability.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And reliability is what people actually pay for.&lt;/p&gt;

&lt;p&gt;If you want to see how we think about packaging repeatable terminal workflows into reusable skills, that’s exactly what we’re building at &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;Terminal Skills&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>product</category>
    </item>
    <item>
      <title>How I Built a CLI Skill to Batch-Process YouTube Shorts</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Wed, 18 Mar 2026 04:16:33 +0000</pubDate>
      <link>https://dev.to/alexshev/how-i-built-a-cli-skill-to-batch-process-youtube-shorts-1i68</link>
      <guid>https://dev.to/alexshev/how-i-built-a-cli-skill-to-batch-process-youtube-shorts-1i68</guid>
      <description>&lt;p&gt;Last month, I had to process 16 YouTube Shorts.&lt;/p&gt;

&lt;p&gt;Trim intros. Normalize audio. Add watermarks. Export multiple formats. Generate thumbnails.&lt;/p&gt;

&lt;p&gt;Doing that manually in Premiere would have taken me most of an afternoon.&lt;/p&gt;

&lt;p&gt;So I built a CLI skill instead.&lt;/p&gt;

&lt;p&gt;It took about 2 hours to put together. On my machine, the batch itself finished in under 3 minutes once everything was set up.&lt;/p&gt;

&lt;p&gt;Here’s the exact structure I used.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I mean by a CLI skill
&lt;/h2&gt;

&lt;p&gt;For me, a CLI skill is a reusable shell workflow with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input validation&lt;/li&gt;
&lt;li&gt;sensible defaults&lt;/li&gt;
&lt;li&gt;predictable output&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;li&gt;lightweight docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of retyping a long FFmpeg command every time, I run one script and get the same result every time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Instead of this:&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:02 &lt;span class="nt"&gt;-to&lt;/span&gt; 00:00:35 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1080:1920"&lt;/span&gt; &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-preset&lt;/span&gt; fast output.mp4

&lt;span class="c"&gt;# I run this:&lt;/span&gt;
./process-short.sh input.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That difference sounds small, but it removes the part that always breaks in real work: remembering the flags, the order, and the output steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: List the manual steps first
&lt;/h2&gt;

&lt;p&gt;Before I wrote anything, I wrote down the full workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;trim the first 2 seconds&lt;/li&gt;
&lt;li&gt;cut after 35 seconds&lt;/li&gt;
&lt;li&gt;scale to 1080×1920&lt;/li&gt;
&lt;li&gt;normalize audio to -14 LUFS&lt;/li&gt;
&lt;li&gt;add watermark&lt;/li&gt;
&lt;li&gt;export MP4&lt;/li&gt;
&lt;li&gt;export WebM&lt;/li&gt;
&lt;li&gt;generate a thumbnail&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gave me a real pipeline instead of a vague automation idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Build one script that does the boring work
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;TRIM_START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:02"&lt;/span&gt;
&lt;span class="nv"&gt;TRIM_END&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:35"&lt;/span&gt;
&lt;span class="nv"&gt;RESOLUTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1080:1920"&lt;/span&gt;
&lt;span class="nv"&gt;AUDIO_TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-14"&lt;/span&gt;
&lt;span class="nv"&gt;WATERMARK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./assets/watermark.png"&lt;/span&gt;
&lt;span class="nv"&gt;THUMB_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:05"&lt;/span&gt;

&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?Usage:&lt;span class="p"&gt; process-short.sh &amp;lt;input.mp4&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: File '&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;' not found."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; .mp4&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./output/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-ss&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRIM_START&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-to&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRIM_END&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESOLUTION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUDIO_TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-preset&lt;/span&gt; fast &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WATERMARK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WATERMARK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"overlay=W-w-20:H-h-20"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:v libvpx-vp9 &lt;span class="nt"&gt;-crf&lt;/span&gt; 30 &lt;span class="nt"&gt;-b&lt;/span&gt;:v 0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.webm"&lt;/span&gt;

ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-ss&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$THUMB_TIME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_thumb.jpg"&lt;/span&gt;

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few choices mattered a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;set -euo pipefail&lt;/code&gt; so failures don’t get ignored&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-y&lt;/code&gt; because this is a pipeline, not an interactive tool&lt;/li&gt;
&lt;li&gt;temp file cleanup so output folders stay usable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Make it batch-capable
&lt;/h2&gt;

&lt;p&gt;One file is a demo. A directory is the real use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;INPUT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?Usage:&lt;span class="p"&gt; batch-process.sh &amp;lt;directory&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PROCESSED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    if&lt;/span&gt; ./process-short.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="o"&gt;((&lt;/span&gt;PROCESSED++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAILED: &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="o"&gt;((&lt;/span&gt;FAILED++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Batch complete: &lt;/span&gt;&lt;span class="nv"&gt;$PROCESSED&lt;/span&gt;&lt;span class="s2"&gt; processed, &lt;/span&gt;&lt;span class="nv"&gt;$FAILED&lt;/span&gt;&lt;span class="s2"&gt; failed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where it actually became useful. I didn’t want a cool script. I wanted to stop babysitting repetitive exports.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Add docs, even if it’s just for yourself
&lt;/h2&gt;

&lt;p&gt;I also added a tiny &lt;code&gt;SKILL.md&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the script does&lt;/li&gt;
&lt;li&gt;requirements&lt;/li&gt;
&lt;li&gt;usage&lt;/li&gt;
&lt;li&gt;config variables&lt;/li&gt;
&lt;li&gt;output files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds boring, but it matters. A script without docs becomes archaeology in two weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Test annoying edge cases
&lt;/h2&gt;

&lt;p&gt;This was the part that caught real bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;empty files&lt;/li&gt;
&lt;li&gt;videos without audio&lt;/li&gt;
&lt;li&gt;short clips&lt;/li&gt;
&lt;li&gt;weird filenames with spaces and brackets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That testing found multiple issues immediately. If I had skipped it, the batch version would have failed silently later.&lt;/p&gt;

&lt;p&gt;One of the first things I ran into was how quickly “works on one file” falls apart on real footage. A weird filename, a silent clip, or a shorter-than-expected video is enough to break the whole flow if you never test for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; most of an afternoon for 16 videos&lt;br&gt;&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; one command, then the machine does the repetitive part&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./batch-process.sh ./raw-videos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s still less flexible than Premiere. If I want one-off polish, I’ll still do it manually.&lt;/p&gt;

&lt;p&gt;But for repeatable batch work, the tradeoff is absolutely worth it.&lt;/p&gt;

&lt;p&gt;That’s why I like building CLI skills.&lt;/p&gt;

&lt;p&gt;Not because they’re clever. Because they turn something fragile and repetitive into something boring and reliable.&lt;/p&gt;

&lt;p&gt;If you build little terminal workflows like this too, I’d genuinely love to hear what you’ve automated. I keep more reusable terminal workflow patterns on &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;Terminal Skills&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would you automate first?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cli</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>8 FFmpeg Recipes I Use Every Week (That Most Developers Don't Know Exist)</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Wed, 11 Mar 2026 15:21:39 +0000</pubDate>
      <link>https://dev.to/alexshev/8-ffmpeg-recipes-i-use-every-week-that-most-developers-dont-know-exist-5458</link>
      <guid>https://dev.to/alexshev/8-ffmpeg-recipes-i-use-every-week-that-most-developers-dont-know-exist-5458</guid>
      <description>&lt;p&gt;I've been using FFmpeg almost every day for the past year.&lt;/p&gt;

&lt;p&gt;Mostly for boring real work: cutting Shorts, cleaning voice tracks, exporting web versions, generating thumbnails, and fixing weird media issues at the last minute when something breaks five minutes before publish.&lt;/p&gt;

&lt;p&gt;Most FFmpeg tutorials cover the basics and stop there. These are the commands I actually come back to in production.&lt;/p&gt;

&lt;p&gt;Here are 8 recipes I use constantly. Copy-paste ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Extract Audio from Video (and Clean It Up)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract audio only&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vn&lt;/span&gt; &lt;span class="nt"&gt;-acodec&lt;/span&gt; libmp3lame &lt;span class="nt"&gt;-q&lt;/span&gt;:a 2 audio.mp3

&lt;span class="c"&gt;# Extract + normalize loudness to broadcast standard&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vn&lt;/span&gt; &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-16:TP=-1.5:LRA=11"&lt;/span&gt; &lt;span class="nt"&gt;-ar&lt;/span&gt; 44100 clean_audio.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Podcast edits, voiceover cleanup, and those annoying cases where a track sounds fine in headphones but way too quiet after upload. The &lt;code&gt;loudnorm&lt;/code&gt; filter saved me from re-exporting more than once.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Create a GIF from a Video Clip (That Doesn't Look Terrible)
&lt;/h2&gt;

&lt;p&gt;Most GIF conversions look like they were made in 2004. The trick is a two-pass approach with a custom palette.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate optimized palette first&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:05 &lt;span class="nt"&gt;-t&lt;/span&gt; 3 &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"fps=15,scale=480:-1:flags=lanczos,palettegen"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    palette.png

&lt;span class="c"&gt;# Then use that palette for the GIF&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:05 &lt;span class="nt"&gt;-t&lt;/span&gt; 3 &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; palette.png &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"fps=15,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    output.gif

&lt;span class="c"&gt;# Cleanup&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;palette.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Single-pass GIFs use a generic 256-color palette. The two-pass method generates a palette optimized for your specific clip.&lt;/p&gt;

&lt;p&gt;I learned this the hard way after making a product GIF that looked fine in preview and terrible after export. Washed-out gradients, ugly banding, weird skin tones. Two-pass fixed it immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Batch Convert an Entire Folder
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Convert all MKV files to MP4 (preserving quality)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.mkv&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="p"&gt;%.mkv&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp4"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Convert all WAV to MP3 at 192kbps&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.wav&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-codec&lt;/span&gt;:a libmp3lame &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="p"&gt;%.wav&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp3"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; &lt;code&gt;${f%.ext}&lt;/code&gt; strips the original extension. This is bash string manipulation, not FFmpeg — but it's the glue that makes FFmpeg scriptable.&lt;/p&gt;

&lt;p&gt;This one saved me the most time in practice. Converting one file is nothing. Converting 40 files before lunch is where FFmpeg starts earning its keep.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Split a Video into Equal Chunks
&lt;/h2&gt;

&lt;p&gt;Perfect for breaking long recordings into social-media-sized pieces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Split into 60-second chunks&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; long_video.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-map&lt;/span&gt; 0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-segment_time&lt;/span&gt; 60 &lt;span class="nt"&gt;-f&lt;/span&gt; segment &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-reset_timestamps&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
    chunk_%03d.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt; &lt;code&gt;chunk_000.mp4&lt;/code&gt;, &lt;code&gt;chunk_001.mp4&lt;/code&gt;, &lt;code&gt;chunk_002.mp4&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-c copy&lt;/code&gt; flag means no re-encoding — it's almost instant regardless of file size. The split happens at keyframes, so chunks might be slightly longer or shorter than 60 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Add Subtitles (Burned In)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From an SRT file&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"subtitles=captions.srt:force_style='FontSize=24,FontName=Arial,PrimaryColour=&amp;amp;HFFFFFF,OutlineColour=&amp;amp;H000000,Outline=2'"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Quick one-liner subtitle (no SRT file needed)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"drawtext=text='Hello World':fontsize=36:fontcolor=white:x=(w-text_w)/2:y=h-th-40:box=1:boxcolor=black@0.6:boxborderw=8"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; The &lt;code&gt;force_style&lt;/code&gt; parameter in the SRT method lets you override subtitle styling without editing the SRT file. Useful when you get captions from auto-transcription services and need them to look consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Picture-in-Picture (Two Videos Overlaid)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Main video with small webcam overlay in bottom-right&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; main.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; webcam.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[1:v]scale=320:240[pip];[0:v][pip]overlay=W-w-20:H-h-20"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
    pip_output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Variations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top-left: &lt;code&gt;overlay=20:20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Center: &lt;code&gt;overlay=(W-w)/2:(H-h)/2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Animated (slide in): &lt;code&gt;overlay='if(lt(t,1),W,W-w-20)':H-h-20&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use this for tutorial videos — screen recording as the main video, webcam as the PiP.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Speed Up / Slow Down Video
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 2x speed (video + audio)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; fast.mp4

&lt;span class="c"&gt;# 0.5x speed (slow motion)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=2.0*PTS[v];[0:a]atempo=0.5[a]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; slow.mp4

&lt;span class="c"&gt;# 4x speed (atempo max is 2.0, so chain them)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=0.25*PTS[v];[0:a]atempo=2.0,atempo=2.0[a]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; 4x.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The gotcha:&lt;/strong&gt; &lt;code&gt;setpts&lt;/code&gt; changes video speed, &lt;code&gt;atempo&lt;/code&gt; changes audio speed. They're separate filters. If you only use &lt;code&gt;setpts&lt;/code&gt;, you get a fast video with normal-speed audio — which is funny exactly once.&lt;/p&gt;

&lt;p&gt;Also: &lt;code&gt;atempo&lt;/code&gt; only accepts values between 0.5 and 2.0. For 4x, you chain two &lt;code&gt;atempo=2.0&lt;/code&gt; filters. I still have to look that up sometimes because it’s one of those FFmpeg details that never stays in my head.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Generate a Video from Images (Slideshow / Timelapse)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From numbered images (img001.png, img002.png, etc.)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 24 &lt;span class="nt"&gt;-i&lt;/span&gt; img%03d.png &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p slideshow.mp4

&lt;span class="c"&gt;# From a folder of images (simple slideshow)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 1/3 &lt;span class="nt"&gt;-pattern_type&lt;/span&gt; glob &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'*.jpg'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"fps=25,format=yuv420p"&lt;/span&gt; &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="se"&gt;\&lt;/span&gt;
    slideshow.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Generating timelapse videos from screenshot sequences. Also useful for turning design mockups into a quick presentation video.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-framerate 1/3&lt;/code&gt; means each image shows for 3 seconds. Change the denominator to control display time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Key flags&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extract audio&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-vn&lt;/code&gt; (no video)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No re-encode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-c copy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality control&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-crf 18&lt;/code&gt; (lower = better, 0-51)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normalize audio&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-af "loudnorm=I=-16"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale video&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-vf "scale=1920:1080"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trim&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-ss START -to END&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overwrite&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-y&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want more of these, I keep a small collection of terminal-first FFmpeg patterns on &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;terminalskills.io&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your go-to FFmpeg recipe?&lt;/strong&gt; Mine is still the two-pass GIF trick — not glamorous, but I use it constantly because bad GIF exports are way more common than they should be. If you have a better one, drop it in the comments 👇&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Ditched GUI Tools and My Productivity Doubled — Here Are the 5 Terminal Tools That Did It</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Sat, 07 Mar 2026 22:03:59 +0000</pubDate>
      <link>https://dev.to/alexshev/i-ditched-gui-tools-and-my-productivity-doubled-here-are-the-5-terminal-tools-that-did-it-4ja2</link>
      <guid>https://dev.to/alexshev/i-ditched-gui-tools-and-my-productivity-doubled-here-are-the-5-terminal-tools-that-did-it-4ja2</guid>
      <description>&lt;p&gt;I used to be a GUI person. Premiere for video. Tower for git. Finder for files.&lt;/p&gt;

&lt;p&gt;Then last year, I started building automation pipelines — video production, data processing, server deploys — and I kept hitting the same wall: GUI tools don't scale. You can't loop a drag-and-drop. You can't pipe a button click.&lt;/p&gt;

&lt;p&gt;So I went terminal-first. And I'm not going back.&lt;/p&gt;

&lt;p&gt;Here are the 5 tools that made the difference.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;strong&gt;ripgrep (rg)&lt;/strong&gt; — Search That Actually Respects Your Time
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; works. &lt;code&gt;ripgrep&lt;/code&gt; works &lt;em&gt;fast&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It respects &lt;code&gt;.gitignore&lt;/code&gt; by default, searches recursively, and handles Unicode. On a 50GB monorepo, the difference isn't seconds — it's minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Search for "TODO" across your entire project, skip node_modules automatically&lt;/span&gt;
rg &lt;span class="s2"&gt;"TODO"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; ts

&lt;span class="c"&gt;# Case-insensitive search with context lines&lt;/span&gt;
rg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"deprecated"&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters in 2026:&lt;/strong&gt; Codebases are bigger than ever. AI-generated code means more files, more boilerplate, more things to search through. You need a search tool that scales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install ripgrep&lt;/code&gt; / &lt;code&gt;cargo install ripgrep&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;strong&gt;jq&lt;/strong&gt; — JSON Surgery from the Command Line
&lt;/h2&gt;

&lt;p&gt;Every API returns JSON. Every config file is JSON. Every log pipeline outputs JSON.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; lets you slice, filter, and transform it without opening a browser tab or writing a Python script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract all error messages from an API response&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; api.example.com/logs | jq &lt;span class="s1"&gt;'.entries[] | select(.level == "error") | .message'&lt;/span&gt;

&lt;span class="c"&gt;# Reshape data — pull specific fields into a new structure&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;data.json | jq &lt;span class="s1"&gt;'[.users[] | {name: .name, email: .contact.email}]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The real power:&lt;/strong&gt; Piping. Chain &lt;code&gt;curl | jq | xargs&lt;/code&gt; and you've built an API automation pipeline in one line. No dependencies. No virtual environments. No "let me spin up a quick script."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install jq&lt;/code&gt; / &lt;code&gt;apt install jq&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;strong&gt;FFmpeg&lt;/strong&gt; — The Swiss Army Knife Nobody Teaches You
&lt;/h2&gt;

&lt;p&gt;Most developers know FFmpeg exists. Few know it can replace an entire video editing suite.&lt;/p&gt;

&lt;p&gt;I recently produced a 49-second educational short — compositing multiple video layers, syncing audio, adding animated subtitles — entirely from the terminal. No Premiere. No DaVinci Resolve.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Trim a video with frame-accurate precision&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:30 &lt;span class="nt"&gt;-to&lt;/span&gt; 00:02:45 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy trimmed.mp4

&lt;span class="c"&gt;# Overlay a watermark with position control&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; logo.png &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"overlay=W-w-10:H-h-10"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Generate a thumbnail from a specific timestamp&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:15 &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 thumbnail.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The unlock:&lt;/strong&gt; Once you can script video processing, you can automate content pipelines. Batch process 100 videos. Auto-generate thumbnails. Convert formats at scale. I've been collecting FFmpeg patterns on &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;terminalskills.io&lt;/a&gt; because the documentation alone could fill a textbook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install ffmpeg&lt;/code&gt; / &lt;code&gt;apt install ffmpeg&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. &lt;strong&gt;lazygit&lt;/strong&gt; — Git, But You Can Actually See What's Happening
&lt;/h2&gt;

&lt;p&gt;I love git. I hate remembering whether it's &lt;code&gt;git rebase -i HEAD~3&lt;/code&gt; or &lt;code&gt;git rebase -i HEAD~4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;lazygit gives you a terminal UI for git — staging hunks, interactive rebasing, cherry-picking — without leaving the terminal or touching a mouse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Just run it in any git repo&lt;/span&gt;
lazygit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What changed for me:&lt;/strong&gt; I stopped being afraid of interactive rebases. The visual diff panel means you SEE what you're about to do before you do it. Conflict resolution went from "let me open VS Code" to "done in 10 seconds."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Hit &lt;code&gt;?&lt;/code&gt; inside lazygit for the full keybinding list. &lt;code&gt;space&lt;/code&gt; to stage, &lt;code&gt;c&lt;/code&gt; to commit, &lt;code&gt;p&lt;/code&gt; to push. That's 90% of your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install lazygit&lt;/code&gt; / &lt;code&gt;go install github.com/jesseduffield/lazygit@latest&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. &lt;strong&gt;zoxide&lt;/strong&gt; — cd, But It Remembers Where You've Been
&lt;/h2&gt;

&lt;p&gt;This is the smallest tool on the list and the one I'd miss the most.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zoxide&lt;/code&gt; replaces &lt;code&gt;cd&lt;/code&gt; with a smart directory jumper. It learns which directories you visit most and lets you jump to them with partial names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Instead of: cd ~/projects/company/backend/services/auth&lt;/span&gt;
&lt;span class="c"&gt;# Just type:&lt;/span&gt;
z auth

&lt;span class="c"&gt;# It knows you mean that specific auth directory&lt;/span&gt;
&lt;span class="c"&gt;# because you've been there 50 times this week&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it's not trivial:&lt;/strong&gt; I timed it. On an average workday, I &lt;code&gt;cd&lt;/code&gt; about 80 times. Each full path takes 3-5 seconds to type. zoxide cuts that to under 1 second. That's 5-6 minutes saved daily. Over a year, that's nearly a full workday of just... not typing paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install zoxide&lt;/code&gt; / &lt;code&gt;curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;None of these tools are new. But together, they changed how I think about work.&lt;/p&gt;

&lt;p&gt;GUI tools are great for one-off tasks. But the moment you need to do something twice — the terminal wins. Every time.&lt;/p&gt;

&lt;p&gt;I went from "let me open the app" to "let me write the command" and my throughput doubled. Not because I'm faster at typing. Because scripts don't forget steps, don't get tired, and don't need a mouse.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your terminal setup look like?&lt;/strong&gt; I'm always looking for tools I've missed — I bet someone here has a workflow that would blow my mind. Drop it in the comments 👇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Some parts of this article were refined with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>devtools</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
