<?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: Abubakr Alsheikh</title>
    <description>The latest articles on DEV Community by Abubakr Alsheikh (@abubakr_alsheikh).</description>
    <link>https://dev.to/abubakr_alsheikh</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%2F1917386%2F1732e3ce-9e6b-457b-88b6-5cc54a300f91.jpeg</url>
      <title>DEV Community: Abubakr Alsheikh</title>
      <link>https://dev.to/abubakr_alsheikh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abubakr_alsheikh"/>
    <language>en</language>
    <item>
      <title>Why my saved Claude Code logins kept dying, and the switcher I built around it</title>
      <dc:creator>Abubakr Alsheikh</dc:creator>
      <pubDate>Mon, 02 Nov 2026 19:35:33 +0000</pubDate>
      <link>https://dev.to/abubakr_alsheikh/why-my-saved-claude-code-logins-kept-dying-and-the-switcher-i-built-around-it-1im2</link>
      <guid>https://dev.to/abubakr_alsheikh/why-my-saved-claude-code-logins-kept-dying-and-the-switcher-i-built-around-it-1im2</guid>
      <description>&lt;p&gt;I pay for more than one Claude subscription, and I wanted plain &lt;code&gt;claude&lt;/code&gt; to run on whichever one has room left, without losing my sessions or splitting my history in two.&lt;/p&gt;

&lt;p&gt;The obvious fix looked easy: keep a copy of each account's login file and copy the right one back when I want to switch. It worked for a few hours. Then Claude Code started asking me to log in again.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Abubakr-Alsheikh/claude-account-switch" rel="noopener noreferrer"&gt;https://github.com/Abubakr-Alsheikh/claude-account-switch&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a saved login dies
&lt;/h2&gt;

&lt;p&gt;Claude Code signs in with OAuth. It holds a short-lived access token, about 8 hours, and a refresh token. When the access token runs out, Claude Code trades the refresh token for a new pair and writes both back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The server accepts each refresh token once.&lt;/strong&gt; After a renewal, the old one is dead.&lt;/p&gt;

&lt;p&gt;So a copy you saved on Monday dies the moment any running &lt;code&gt;claude&lt;/code&gt; renews that login on Monday night. Copy it back on Tuesday and you are logged out. Every "save the file, swap the file" approach hits this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tool does about it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;claude-switch&lt;/code&gt; keeps one Claude Code profile and swaps only the login. To stay correct with single-use tokens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most commands save the active account's newest login first,&lt;/strong&gt; so a saved copy is never older than your last command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It takes Claude Code's own locks during the swap,&lt;/strong&gt; in the same order Claude Code takes them, so no running session renews or rewrites the login halfway through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It refuses to file one account's login under another name.&lt;/strong&gt; It compares logins by a SHA-256 hash of the refresh token and by the email Claude Code records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It keeps the last 3 copies of each account,&lt;/strong&gt; and parks logins it doesn't recognize instead of overwriting them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It tells you when a saved login is dead,&lt;/strong&gt; and how to repair it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ claude-switch list
CLAUDE ACCOUNTS   active: work (me@work.example)

   name          email                     plan     weekly use (resets)         5-hour use (resets)    renewed      ends
&amp;gt;&amp;gt; work          me@work.example           Ultra    #######...  71%  Sun 09:00  ###...  48%     17:00  10-30 12:10  11-24
   personal      me@home.example           Max      ##........  18%  Tue 21:00  ......   0%     19:40  10-30 09:02  11-19

Most weekly room: personal (18% used)  -&amp;gt;  claude-switch use personal  or  claude-switch auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;claude-switch use personal&lt;/code&gt; swaps the login. In an open session, &lt;code&gt;/exit&lt;/code&gt; and &lt;code&gt;claude -c&lt;/code&gt; continue the same conversation on the other account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I learned matching Claude Code
&lt;/h2&gt;

&lt;p&gt;Getting this right meant reading how Claude Code itself behaves, version by version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It guards renewals with lock folders (&lt;code&gt;~/.claude/.oauth_refresh.lock&lt;/code&gt;, then an older &lt;code&gt;~/.claude.lock&lt;/code&gt;) and refreshes their timestamps every few seconds. A lock that stops moving counts as dead.&lt;/li&gt;
&lt;li&gt;Every write of the login goes through a separate lock, &lt;code&gt;~/.claude/.storage-write.lock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On macOS, current versions file the Keychain item under the account &lt;code&gt;claude-code-user&lt;/code&gt;, not your user name.&lt;/li&gt;
&lt;li&gt;Newer versions can keep the Windows login in Windows Credential Manager. claude-switch doesn't handle that store yet, so it notices and stops before touching anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it is not
&lt;/h2&gt;

&lt;p&gt;It is &lt;strong&gt;unofficial&lt;/strong&gt;: not made, endorsed or supported by Anthropic. It reads and writes the same files Claude Code uses, so a Claude Code update can break it.&lt;/p&gt;

&lt;p&gt;It is for one person moving between &lt;strong&gt;their own&lt;/strong&gt; subscriptions. Don't share logins or pool access, and follow Anthropic's terms for every account. Renewing logins you are not using (&lt;code&gt;refresh&lt;/code&gt;, &lt;code&gt;keepalive&lt;/code&gt;) is opt-in, and the README lists exactly what leaves your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Python 3.9 or newer, no dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/Abubakr-Alsheikh/claude-account-switch@v0.2.1
claude-switch save work
claude-switch add personal     &lt;span class="c"&gt;# then sign the browser into the other account&lt;/span&gt;
claude auth login
claude-switch save personal
claude-switch list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use it daily on Windows. Linux runs in CI. The macOS code passes its tests against a fake Keychain, but I haven't run it on a real Mac yet, so reports from Mac users are welcome.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>python</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>qtask: a task tracker my AI coding agents share, in one Python file</title>
      <dc:creator>Abubakr Alsheikh</dc:creator>
      <pubDate>Mon, 02 Nov 2026 16:50:00 +0000</pubDate>
      <link>https://dev.to/abubakr_alsheikh/qtask-a-task-tracker-my-ai-coding-agents-share-in-one-python-file-3b3b</link>
      <guid>https://dev.to/abubakr_alsheikh/qtask-a-task-tracker-my-ai-coding-agents-share-in-one-python-file-3b3b</guid>
      <description>&lt;p&gt;I run several AI coding agents at once on the same project. One fixes a backend bug, one checks a screen in the browser, one writes tests. For a while I tracked their work in chat and in notes, and the same problems kept coming back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two agents picked up the same task and changed the same files.&lt;/li&gt;
&lt;li&gt;An agent asked me something in the middle of a long session, I missed it, and it went ahead on a guess.&lt;/li&gt;
&lt;li&gt;An agent said "done" with nothing to show for it.&lt;/li&gt;
&lt;li&gt;When a session ended, what it learned ended with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;strong&gt;qtask&lt;/strong&gt;, a task tracker for agents and the person working with them. It is one Python file that uses only the standard library, and it keeps everything in one SQLite database on your machine.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Abubakr-Alsheikh/qtask" rel="noopener noreferrer"&gt;https://github.com/Abubakr-Alsheikh/qtask&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a session looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ qtask add "Fix the login redirect" --kind bug --pri P1
Created APP-14: Fix the login redirect
$ qtask claim APP-14 --agent fe-fix-1
Claimed APP-14 as fe-fix-1 until 2026-11-02T17:56:49Z. Status: in_progress.
$ qtask ask APP-14 "Keep the old URL working?" --to developer --agent fe-fix-1
Question #1 on APP-14 sent to developer. Status: needs_confirm.
$ qtask inbox --for developer
#1 on APP-14 [question, open] fe-fix-1 to developer: Keep the old URL working?
$ qtask answer 1 "Yes, redirect it" --agent developer
Answer #2 added. Question #1 resolved. APP-14 status: in_progress.
$ qtask done APP-14 --evidence "npm test: 412 passed" --agent fe-fix-1
Done APP-14.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Four rules that fixed my problems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Claim before you work.&lt;/strong&gt; &lt;code&gt;claim&lt;/code&gt; gives the agent a lease, two hours by default. If another agent tries to claim the same task, it gets exit code 3 and picks something else. When the lease runs out, &lt;code&gt;ready&lt;/code&gt; offers the task again, so a crashed session doesn't hold work forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Questions wait for the human.&lt;/strong&gt; &lt;code&gt;ask&lt;/code&gt; sends a question to &lt;code&gt;developer&lt;/code&gt; and parks the task in &lt;code&gt;needs_confirm&lt;/code&gt;. I answer when I get to it, with &lt;code&gt;qtask inbox&lt;/code&gt; and &lt;code&gt;qtask answer&lt;/code&gt;. The agent finds the answer in its own inbox, even in a new session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. No evidence, no done.&lt;/strong&gt; &lt;code&gt;done&lt;/code&gt; refuses without &lt;code&gt;--evidence&lt;/code&gt;, which holds the commands the agent ran and what they printed. It also refuses while a question or a subtask is still open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Findings live on the task.&lt;/strong&gt; Agents write what they learn with &lt;code&gt;qtask comment&lt;/code&gt;. A subagent gets a task ID instead of a long prompt, works on it, and hands back the ID. I read the whole story with &lt;code&gt;qtask show&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why another tracker
&lt;/h2&gt;

&lt;p&gt;I looked at the existing ones first. Most needed a package install, a server or an account, and I wanted something I could read top to bottom before handing it to agents that run commands on my machine. qtask is about 2,000 lines in one file, with no dependencies.&lt;/p&gt;

&lt;p&gt;Two more choices came from how I work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tasks stay out of the repo.&lt;/strong&gt; The database lives in &lt;code&gt;~/.qtask&lt;/code&gt;, so half-ideas and private notes never land in a commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It knows your project.&lt;/strong&gt; The project comes from the git remote of the folder you are in, so every clone and worktree of a repo shares one list, and IDs get a short prefix from the repo name, like &lt;code&gt;APP-14&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using it with your agents
&lt;/h2&gt;

&lt;p&gt;For Claude Code there is a skill in the repo that tells the agent when to load qtask and how to use it. For Codex, Cursor, Aider or Gemini CLI, the README has a short block to paste into your &lt;code&gt;AGENTS.md&lt;/code&gt; or rules file. Any agent that can run a shell command can use qtask, and every command takes &lt;code&gt;--json&lt;/code&gt; for output a program can read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SQLite in WAL mode. Every write runs in a &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; transaction, and a claim is one conditional &lt;code&gt;UPDATE&lt;/code&gt;, so two agents can't both win.&lt;/li&gt;
&lt;li&gt;The tests start 20 processes at the same moment to race for one claim, and only one may get it.&lt;/li&gt;
&lt;li&gt;Full-text search with SQLite FTS5, with a plain &lt;code&gt;LIKE&lt;/code&gt; fallback for builds without it.&lt;/li&gt;
&lt;li&gt;A backup of the database and a JSONL export on the first write of each day.&lt;/li&gt;
&lt;li&gt;It runs on Windows, Linux and macOS. I built it on Windows, so Git Bash path conversion and PowerShell quoting have their own tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Python 3.11 or newer and git 2.31 or newer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/Abubakr-Alsheikh/qtask
qtask &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is version 0.1, so commands may still change before 1.0. Next on the roadmap: turning a local task into a GitHub issue, keeping the two in sync, and a read-only HTML board.&lt;/p&gt;

&lt;p&gt;If you run more than one agent at a time, I'd like to hear what breaks for you. Issues and pull requests are open.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I gave my CLI a cyberpunk dashboard, and taught its AI agent to ask first</title>
      <dc:creator>Abubakr Alsheikh</dc:creator>
      <pubDate>Fri, 30 Oct 2026 11:55:07 +0000</pubDate>
      <link>https://dev.to/abubakr_alsheikh/i-gave-my-cli-a-cyberpunk-dashboard-and-taught-its-ai-agent-to-ask-first-4men</link>
      <guid>https://dev.to/abubakr_alsheikh/i-gave-my-cli-a-cyberpunk-dashboard-and-taught-its-ai-agent-to-ask-first-4men</guid>
      <description>&lt;p&gt;In my last post, the roadmap promised three things: conversation memory, scoped permissions so the AI can't wander through your system, and transcription. All three shipped. Along the way Max picked up something I never planned: a face.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/max-cli" rel="noopener noreferrer"&gt;github.com/Abubakr-Alsheikh/max-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since version 0.4.0 I've made 303 commits and four releases (1.0, 1.1, 1.2, 1.2.1). This post covers the two parts I spent the most time on: the dashboard and the agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with a "lazy" CLI
&lt;/h2&gt;

&lt;p&gt;Max had dozens of commands. &lt;code&gt;max video compress&lt;/code&gt;, &lt;code&gt;max pdf merge&lt;/code&gt;, &lt;code&gt;max grab&lt;/code&gt;. To me they felt lazy: short names, smart defaults.&lt;/p&gt;

&lt;p&gt;But a CLI saves you flags only once you know the flags exist. You still need to know which group a command lives in, what &lt;code&gt;--queue&lt;/code&gt; does, and whether &lt;code&gt;-q&lt;/code&gt; means quality or quiet. I wanted people who never open a terminal to use Max too.&lt;/p&gt;

&lt;p&gt;So I gave Max two front doors for people who don't want to learn commands:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A dashboard.&lt;/strong&gt; Type &lt;code&gt;max&lt;/code&gt; with nothing after it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agent.&lt;/strong&gt; Type what you want: &lt;code&gt;max "shrink the videos in this folder"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The dashboard: &lt;code&gt;max&lt;/code&gt; 🖥️
&lt;/h2&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%2F5c9rgf4p4ds3hdk6lyz5.png" 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%2F5c9rgf4p4ds3hdk6lyz5.png" alt="Max's Home page: launchpad, weekly tiles in block digits, a stacked activity chart and a by-type breakdown" width="799" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs in the terminal, built with &lt;a href="https://textual.textualize.io/" rel="noopener noreferrer"&gt;Textual&lt;/a&gt;. Eleven pages: Home, Download, Video, Audio, Images, PDF, Files, AI, Activity, Extras and Settings. Number keys jump between them, &lt;code&gt;Ctrl+P&lt;/code&gt; finds any action by name, and &lt;code&gt;J&lt;/code&gt; opens a jobs window over whatever page you're on.&lt;/p&gt;

&lt;h3&gt;
  
  
  The look
&lt;/h3&gt;

&lt;p&gt;I wanted it to feel like a command center in a cyberpunk film, while staying readable at 9 in the morning.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One theme, named &lt;code&gt;max-cyber&lt;/code&gt;.&lt;/strong&gt; Cyan marks live values, magenta marks peaks and "now", violet marks where your focus is. Deep navy for pages, a lighter navy for cards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every page shares one layout.&lt;/strong&gt; A &lt;code&gt;◢◤ PAGE // WHAT IT DOES&lt;/code&gt; header, cards with uppercase titles, rows of cards on a grid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each page has a code and a colour.&lt;/strong&gt; &lt;code&gt;02 DOWNLOAD&lt;/code&gt; in green, &lt;code&gt;03 VIDEO&lt;/code&gt; in cyan, &lt;code&gt;04 AUDIO&lt;/code&gt; in magenta. The same colour shows up in the sidebar, on the Home launchpad and in the "by type" chart, so you learn the colours without trying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big numbers use block digits&lt;/strong&gt;, and the charts are drawn with block characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rule I set early: pages use theme variables (&lt;code&gt;$primary&lt;/code&gt;, &lt;code&gt;$secondary&lt;/code&gt;, &lt;code&gt;$surface&lt;/code&gt;) and never a hex colour. You can switch themes with &lt;code&gt;Ctrl+P&lt;/code&gt; and nothing breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The part nobody sees: flicker
&lt;/h3&gt;

&lt;p&gt;On Windows, the terminal can't draw a frame all at once. My first Home page refreshed on a timer and flickered like a broken neon sign. The fixes became rules for every page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A timed refresh runs inside one &lt;code&gt;batch_update()&lt;/code&gt;, so the screen repaints once.&lt;/li&gt;
&lt;li&gt;A widget updates only when its value changed. Each chart keeps its last data and compares.&lt;/li&gt;
&lt;li&gt;Never rebuild widgets on a timer. Mount once, then change values.&lt;/li&gt;
&lt;li&gt;Progress updates at most twice a second per job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pages that know your file
&lt;/h3&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%2Fda7dxw3obkroiitjjbqj.png" 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%2Fda7dxw3obkroiitjjbqj.png" alt="The Download page: a checked YouTube link, its facts, quality choices with sizes, and the Part card set to 1:30 to 6:30" width="799" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste a link on the Download page and Max checks it: channel, length, views, every quality with its size, subtitles. The &lt;strong&gt;Part&lt;/strong&gt; card downloads only a stretch of the video. A link with &lt;code&gt;t=1m30s&lt;/code&gt; fills in the start for you, Max checks the times against the video's length as you type, and the button tells you what you'll get: &lt;code&gt;⬇ 1:30-6:30 · 95 MB&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Video, Audio, Images, PDF and Files pages work the same way. Pick a file, and the page shows what it holds (ffprobe for video, EXIF for photos, page count and "scanned or not" for PDFs). Pick an action, and you get its form.&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%2Fdjdlka60l73gjw44rjeq.png" 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%2Fdjdlka60l73gjw44rjeq.png" alt="The Video page: actions grouped as Shrink &amp;amp; Convert, Cut &amp;amp; Join and Sound, with the compress form open" width="799" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The trick: I wrote none of those forms by hand
&lt;/h3&gt;

&lt;p&gt;This change mattered most, for the dashboard and the agent alike. Every action in Max is described &lt;strong&gt;once&lt;/strong&gt;, in a catalog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cut-silence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cut the pauses out of a recording: jump cuts for talks.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_cli.core.operations.video:cut_silence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;_each_target&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;VIDEO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AUDIO&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Video or audio file.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ParamKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quieter than this many dB counts as silence.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nc"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;padding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ParamKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FLOAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Seconds of each pause to keep at both ends.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--padding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;advanced&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{stem}_nosilence{suffix}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;queueable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;Three things read that description:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The CLI&lt;/strong&gt; checks its options against it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The dashboard&lt;/strong&gt; builds the form from it: fields, defaults, a Browse button for paths, advanced options folded away, and a confirmation for anything that moves, overwrites or deletes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent&lt;/strong&gt; gets it as a tool schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A test fails when a CLI command, its catalog entry and its operation disagree on an option or a default. Fifty-eight actions, three front doors, one source of truth. When I added "compress to a size" in 1.2, I wrote the operation and one &lt;code&gt;Param&lt;/code&gt;, and the dashboard form and the agent got it the same day.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The agent: from "suggest a command" to "do the job" 🤖
&lt;/h2&gt;

&lt;p&gt;In version 0.1, Max's AI translated your sentence into one command and asked &lt;code&gt;Run this command? [y/N]&lt;/code&gt;. Useful, but anything with two steps broke it.&lt;/p&gt;

&lt;p&gt;Now it's an agent loop. The model calls tools, Max runs them, the results go back, repeat until it answers in words. It has 19 tools. Six only look: list a folder, inspect a file, find files, check a link, read recent activity, read the job queue. The rest act: &lt;code&gt;load_group&lt;/code&gt; to read a group's actions, &lt;code&gt;run_action&lt;/code&gt; to run one, plus &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;ask_user&lt;/code&gt;, &lt;code&gt;remember&lt;/code&gt;, &lt;code&gt;undo_request&lt;/code&gt; and a few more.&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%2Fbs9qi2wes5wnk2fqnudl.png" 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%2Fbs9qi2wes5wnk2fqnudl.png" alt="The AI page: a request to convert m4a files and sort them, the lookups, a three-step plan, a batch card with 18 conversions done, and the reply with Open folder and Undo buttons" width="799" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what changed between 1.0 and 1.2, mostly from watching it get things wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  It plans, and it asks
&lt;/h3&gt;

&lt;p&gt;Before multi-step work it shows a plan and waits: go, stop, or tell it what to change. When a request is unclear, it asks one question with choices instead of guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  It asks before it touches your files, whatever your settings say
&lt;/h3&gt;

&lt;p&gt;Every catalog action has a danger level. Anything that &lt;strong&gt;moves, overwrites or deletes&lt;/strong&gt; stops for a yes, even if you turned confirmations off for the CLI. The agent also stays inside a scope: the folder you started in, the folders you name, and your download folder. It never gets a drive root. Shell commands are off by default; turn them on and it still shows you each command and waits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Whole folders, without redoing finished work
&lt;/h3&gt;

&lt;p&gt;"Convert every m4a under Music to mp3" used to mean one tool call and one question per file. Now one &lt;code&gt;run_action&lt;/code&gt; takes a list of files, folders or patterns (up to 500 files), and you get &lt;strong&gt;one&lt;/strong&gt; question: how many files, how big, and which ones it'll skip because an mp3 already sits beside them. Over 20 files of a long job, it hands the batch to the background queue. The AI page shows one card per batch with a live count, so a 200-file run stays readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  It picks the right action
&lt;/h3&gt;

&lt;p&gt;My favourite bug: "fix the numbering of my songs" made the agent &lt;strong&gt;rename the files&lt;/strong&gt; with &lt;code&gt;files order&lt;/code&gt;, when I meant the track numbers in the tags.&lt;/p&gt;

&lt;p&gt;The fix was a guide on every action, in one format:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use for:&lt;/strong&gt; removing the pauses from a talk, lecture or podcast. &lt;strong&gt;Not for:&lt;/strong&gt; removing one part by time (&lt;code&gt;video.cut&lt;/code&gt;) or background noise (&lt;code&gt;video.denoise&lt;/code&gt;). &lt;strong&gt;How:&lt;/strong&gt; threshold -30 dB; -40 for a quiet room.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The "Not for" line does most of the work: it points the model at the neighbour it confuses this action with. I also wrote &lt;code&gt;scripts/agent_eval.py&lt;/code&gt;, which runs the agent on fixed scenarios with a real model in dry-run mode and scores its choices. The track-numbers scenario fails if it reaches for &lt;code&gt;files order&lt;/code&gt; again.&lt;/p&gt;

&lt;h3&gt;
  
  
  It checks its work, and you can take it back
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;After each action, Max checks that the files it reported exist and aren't empty. A missing or empty result counts as a failure, and the agent says so.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Ctrl+C&lt;/code&gt; once (or &lt;strong&gt;Stop&lt;/strong&gt; on the AI page) ends the request after the current step. A second &lt;code&gt;Ctrl+C&lt;/code&gt; quits.&lt;/li&gt;
&lt;li&gt;"Undo what you just did" or &lt;code&gt;max ai undo&lt;/code&gt;: files the request made move to a backup folder (only if you haven't changed them since), and the moves, renames and deletes it recorded go back.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  It remembers, within limits
&lt;/h3&gt;

&lt;p&gt;Tell it "my music lives in D:/Music" and later sessions start with that note. Fifty notes of 300 characters at most; &lt;code&gt;max ai memory&lt;/code&gt; lists and deletes them, and the AI page has a Notes button. Long chats get their oldest turns summarised, so they stay within the token limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guardrails in numbers
&lt;/h3&gt;

&lt;p&gt;Per request: 12 model turns, 40 tool calls, 100,000 tokens. Up to 4 independent actions run side by side, but two actions that touch the same path run one after the other.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A Gemini gotcha, if you build agents:&lt;/strong&gt; Gemini 3 attaches a &lt;code&gt;thought_signature&lt;/code&gt; to its tool calls and rejects the next request if you drop it. Max keeps it, and when the fallback provider answered a turn, it adds Google's stand-in value so the history still passes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Everything else that shipped ⚡
&lt;/h2&gt;

&lt;p&gt;Shorter, but each of these came from a real annoyance:&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;# Fit a video under a chat app's upload limit (two-pass, bitrate from the length)&lt;/span&gt;
max video compress clip.mp4 &lt;span class="nt"&gt;--size&lt;/span&gt; 25MB

&lt;span class="c"&gt;# Jump cuts: remove the dead air from a lecture&lt;/span&gt;
max video cut-silence lecture.mp4

&lt;span class="c"&gt;# Transcript or subtitles through Gemini, with your own instructions&lt;/span&gt;
max audio transcribe meeting.m4a &lt;span class="nt"&gt;-f&lt;/span&gt; srt &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"translate to Arabic"&lt;/span&gt;

&lt;span class="c"&gt;# Only minutes 1 to 5 of a video&lt;/span&gt;
max grab download URL &lt;span class="nt"&gt;--start&lt;/span&gt; 1:00 &lt;span class="nt"&gt;--end&lt;/span&gt; 5:00

&lt;span class="c"&gt;# Long jobs keep going after you close the terminal&lt;/span&gt;
max video compress &lt;span class="k"&gt;*&lt;/span&gt;.mp4 &lt;span class="nt"&gt;--queue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Downloads come out tagged.&lt;/strong&gt; Artist, album, track numbers from the playlist position, and &lt;code&gt;--sort-into artist/album&lt;/code&gt; folders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo for file operations.&lt;/strong&gt; &lt;code&gt;max files undo&lt;/code&gt; reverses the last group of moves and renames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;max doctor&lt;/code&gt;&lt;/strong&gt; checks FFmpeg, your AI keys, the YouTube fix and your folders, and tells you the fix for each problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;max update&lt;/code&gt;&lt;/strong&gt; updates Max the way you installed it, and Max tells you once a day when a new version is out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Three lessons from making it installable
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Windows Defender blocks &lt;code&gt;irm ... | iex&lt;/code&gt;.&lt;/strong&gt; My one-line installer downloaded uv's install script and ran it, a pattern malware also uses. The installer now gets uv from winget, or from uv's official zip, checked against its published SHA-256.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can't update a running program on Windows.&lt;/strong&gt; My first &lt;code&gt;max update&lt;/code&gt; replaced the files while &lt;code&gt;max.exe&lt;/code&gt; was running and left an install that couldn't start. Now it opens a small window that waits for Max to close, then updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download links die.&lt;/strong&gt; By October, the FFmpeg sources Max used on all three systems had broken: a 404 on Windows, a closed API on macOS, a server that stopped answering on Linux. Max now tries winget or Homebrew first, then several mirrors in turn, and checks each download against its checksum.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Windows&lt;/strong&gt; (winget installs uv; uv brings its own Python):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;astral-sh.uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--exact&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# open a new terminal, then:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LsSf&lt;/span&gt; https://raw.githubusercontent.com/Abubakr-Alsheikh/max-cli/main/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then type &lt;code&gt;max&lt;/code&gt;. If you've never opened a terminal, the &lt;a href="https://abubakr-alsheikh.github.io/max-cli/start-here/" rel="noopener noreferrer"&gt;Start Here guide&lt;/a&gt; walks you through it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Watch folders: drop a file into a folder and Max compresses, converts or sorts it on its own. After that, search by meaning across your documents, the RAG idea from my last post that I haven't built yet.&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;Star the repo:&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/max-cli" rel="noopener noreferrer"&gt;github.com/Abubakr-Alsheikh/max-cli&lt;/a&gt;. Issues and PRs welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you've built a TUI: did you fight flicker too, and what fixed it for you?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>tui</category>
      <category>ai</category>
    </item>
    <item>
      <title>From "Lazy Script" to "Local AI Agent": Max CLI is now a Media &amp; Document Powerhouse</title>
      <dc:creator>Abubakr Alsheikh</dc:creator>
      <pubDate>Tue, 10 Feb 2026 15:04:12 +0000</pubDate>
      <link>https://dev.to/abubakr_alsheikh/from-lazy-script-to-local-ai-agent-max-cli-is-now-a-media-document-powerhouse-44ad</link>
      <guid>https://dev.to/abubakr_alsheikh/from-lazy-script-to-local-ai-agent-max-cli-is-now-a-media-document-powerhouse-44ad</guid>
      <description>&lt;p&gt;A few months ago, I shared the first version of &lt;strong&gt;Max CLI&lt;/strong&gt;—a tool I built because I was tired of Googling &lt;code&gt;ffmpeg&lt;/code&gt; flags and uploading private PDFs to sketchy websites. &lt;/p&gt;

&lt;p&gt;The response was great, but the feedback was clear: &lt;em&gt;"Make it do more. Make it smarter."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today, I’m excited to share the major evolution of &lt;strong&gt;Max&lt;/strong&gt;. It’s no longer just a "flag-saver"—it’s a local-first, context-aware AI agent for media, documents, and web automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/max-cli" rel="noopener noreferrer"&gt;github.com/Abubakr-Alsheikh/max-cli&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s New in Max? 🚀
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Local Media Engine (FFmpeg on Steroids)
&lt;/h3&gt;

&lt;p&gt;I finally integrated a full Video/Audio suite. You get the power of FFmpeg with "lazy" human-readable commands.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Smart Compression:&lt;/strong&gt; &lt;code&gt;max video compress&lt;/code&gt; with quality levels (&lt;code&gt;balanced&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Quick Fixes:&lt;/strong&gt; &lt;code&gt;max video louder&lt;/code&gt; (boost audio), &lt;code&gt;max video cut&lt;/code&gt; (frame-perfect trimming), and &lt;code&gt;max video snap&lt;/code&gt; (high-res thumbnails).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;High-Quality GIFs:&lt;/strong&gt; Uses a 2-pass palette generator to avoid that grainy 1990s GIF look.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The PDF "Bundle" Pipeline
&lt;/h3&gt;

&lt;p&gt;Document management is now professional-grade. The standout feature is the &lt;strong&gt;Bundle&lt;/strong&gt;:&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;# Merges a folder of PDFs, compresses the result, and cleans up temp files in one go.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;max pdf bundle ./Invoices &lt;span class="nt"&gt;-o&lt;/span&gt; 2024_Tax_Report.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also &lt;strong&gt;Lock&lt;/strong&gt; PDFs with AES-256, &lt;strong&gt;Stamp&lt;/strong&gt; watermarks, and &lt;strong&gt;Rip&lt;/strong&gt; embedded images out of documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Universal "Smart" Downloader (&lt;code&gt;max grab&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;I wrapped &lt;code&gt;yt-dlp&lt;/code&gt; to create a downloader that understands your intent. Instead of 20-character strings, use &lt;strong&gt;Quality Presets&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;s&lt;/code&gt; (Small): 480p / 64k audio (Data saver)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;x&lt;/code&gt; (Xtreme): 4K / 320k audio (Archival)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Smart Playlists:&lt;/strong&gt; Max now peeks at URLs, counts items, and asks for confirmation before filling your drive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. AI Vision &amp;amp; "Nano Banana" (Image Gen)
&lt;/h3&gt;

&lt;p&gt;Max now has eyes. Since integrating with &lt;strong&gt;Google Gemini (Nano Banana)&lt;/strong&gt; and OpenAI, Max can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Analyze:&lt;/strong&gt; &lt;code&gt;max ai analyze error.png&lt;/code&gt; — troubleshoot terminal errors from a screenshot.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Create/Edit:&lt;/strong&gt; &lt;code&gt;max ai create "A pixel art cabin"&lt;/code&gt; or &lt;code&gt;max ai edit photo.jpg "Make the sky purple"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The "Killer" Upgrade: Context Awareness 🧠
&lt;/h2&gt;

&lt;p&gt;In the previous version, the AI was just a translator. In the new Max, the AI is &lt;strong&gt;Context-Aware&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;max ai ask&lt;/code&gt;, Max scans your current directory and injects the file list into the prompt. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Old Way:&lt;/strong&gt; &lt;code&gt;max ai ask "compress the video named final_v2_edit.mp4"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Max Way:&lt;/strong&gt; &lt;code&gt;max ai ask "compress the video"&lt;/code&gt; 

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Max sees &lt;code&gt;final_v2_edit.mp4&lt;/code&gt; in your folder and resolves the intent automatically.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  New Developer Utilities 🛠️
&lt;/h2&gt;

&lt;p&gt;I added features specifically for my own dev workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;max share&lt;/code&gt;&lt;/strong&gt;: Generates an ASCII QR code in your terminal. I use this daily to send &lt;code&gt;localhost&lt;/code&gt; URLs to my phone for testing.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;max paste&lt;/code&gt;&lt;/strong&gt;: Take an image from your system clipboard and save it directly to a file.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;max smart-sort&lt;/code&gt;&lt;/strong&gt;: AI-powered semantic organization. It reads your filenames and groups them into folders like &lt;code&gt;/Invoices&lt;/code&gt;, &lt;code&gt;/Personal&lt;/code&gt;, or &lt;code&gt;/Code&lt;/code&gt; based on meaning, not just extension.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Refined Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Provider Agnostic:&lt;/strong&gt; Full support for Google Gemini via the OpenAI Compatibility layer.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Speed:&lt;/strong&gt; Added Node.js/Deno detection for faster YouTube signature decoding.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;UI:&lt;/strong&gt; Switched to a "Transient" progress bar system—it shows beautiful progress during the task and disappears when done to keep your terminal clean.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I’m currently exploring &lt;strong&gt;Local Transcription (Whisper)&lt;/strong&gt; and &lt;strong&gt;Semantic Search&lt;/strong&gt; (RAG) so you can ask questions about your local documents directly.&lt;/p&gt;

&lt;p&gt;Max is Open Source and built for the community. If you hate browser-based converters and love the terminal, I’d love for you to try it out.&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;Check out the code:&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/max-cli" rel="noopener noreferrer"&gt;github.com/Abubakr-Alsheikh/max-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the one browser-based tool you still use that you wish lived in your terminal? Let's build a command for it!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built a "Lazy" AI Terminal Assistant because I hate memorizing flags</title>
      <dc:creator>Abubakr Alsheikh</dc:creator>
      <pubDate>Wed, 26 Nov 2025 23:40:11 +0000</pubDate>
      <link>https://dev.to/abubakr_alsheikh/i-built-a-lazy-ai-terminal-assistant-because-i-hate-memorizing-flags-262l</link>
      <guid>https://dev.to/abubakr_alsheikh/i-built-a-lazy-ai-terminal-assistant-because-i-hate-memorizing-flags-262l</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: I love the terminal, but I hate the friction.
&lt;/h2&gt;

&lt;p&gt;I live in the terminal. But let's be honest: doing simple things often feels like solving a puzzle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Want to compress a folder of images? Time to look up ImageMagick flags.&lt;/li&gt;
&lt;li&gt;  Want to merge PDFs? Time to upload your private docs to some random "Free PDF Merger" website.&lt;/li&gt;
&lt;li&gt;  Want to rename 100 files? Time to write a throwaway Python script that you’ll delete 5 minutes later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a tool that was &lt;strong&gt;Fast&lt;/strong&gt;, &lt;strong&gt;Local&lt;/strong&gt;, and unapologetically &lt;strong&gt;Lazy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, I built &lt;strong&gt;Max&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet Max CLI ⚡
&lt;/h2&gt;

&lt;p&gt;Max is an open-source, modular command-line tool built with Python. It automates the mundane, local tasks we deal with daily, and it includes an &lt;strong&gt;AI Copilot&lt;/strong&gt; to handle the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/max-cli" rel="noopener noreferrer"&gt;github.com/Abubakr-Alsheikh/max-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is why it's different: &lt;strong&gt;It prioritizes local execution.&lt;/strong&gt; When you compress images or order files, it happens on your machine. No cloud uploads. Speed is the priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can it do? (The "Lazy" Features)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Smart Image Compression
&lt;/h3&gt;

&lt;p&gt;Stop Googling compression algorithms. Max recursively scans folders, detects images, and optimizes them.&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;# Compress an entire folder of photos to JPEGs (Quality 80)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;max images compress ./VacationPhotos &lt;span class="nt"&gt;--quality&lt;/span&gt; 80 &lt;span class="nt"&gt;--jpeg&lt;/span&gt;

&lt;span class="c"&gt;# Resize a huge banner to 50% scale&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;max img compress banner.png &lt;span class="nt"&gt;--scale&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Sanity-Saving File Ordering
&lt;/h3&gt;

&lt;p&gt;I constantly have folders full of &lt;code&gt;scan.pdf&lt;/code&gt;, &lt;code&gt;scan(1).pdf&lt;/code&gt;, &lt;code&gt;document.docx&lt;/code&gt;. Max cleans this up by adding numerical ordering, with a safety-first "Dry Run" mode.&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;# Preview what will happen&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;max files order ./Downloads &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;DRY RUN] Would rename &lt;span class="s1"&gt;'report.pdf'&lt;/span&gt; -&amp;gt; &lt;span class="s1"&gt;'1_report.pdf'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;DRY RUN] Would rename &lt;span class="s1"&gt;'thesis.docx'&lt;/span&gt; -&amp;gt; &lt;span class="s1"&gt;'2_thesis.docx'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The AI Copilot 🤖
&lt;/h3&gt;

&lt;p&gt;This is the killer feature. Max uses the OpenAI SDK to translate &lt;em&gt;Natural Language&lt;/em&gt; into &lt;em&gt;CLI Arguments&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I didn't want a chatbot that just talks. I wanted a chatbot that &lt;strong&gt;does&lt;/strong&gt;. Max reads its own documentation, understands the available tools, and constructs the command for you.&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="nv"&gt;$ &lt;/span&gt;max ai ask &lt;span class="s2"&gt;"Combine all PDFs in the 'Contracts' folder"&lt;/span&gt;

&lt;span class="c"&gt;# Max thinks... and suggests:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; max pdf merge ./Contracts
&lt;span class="c"&gt;# Run this command? [y/N]: &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Architecture (For the Nerds 🤓)
&lt;/h2&gt;

&lt;p&gt;I didn't want a messy script. Max is built as a &lt;strong&gt;Modular Monolith&lt;/strong&gt; using modern Python standards.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Framework:&lt;/strong&gt; &lt;a href="https://typer.tiangolo.com/" rel="noopener noreferrer"&gt;Typer&lt;/a&gt; (for that sweet CLI developer experience).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;UI:&lt;/strong&gt; &lt;a href="https://github.com/Textualize/rich" rel="noopener noreferrer"&gt;Rich&lt;/a&gt; (for beautiful progress bars, tables, and error handling).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Engine:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Pillow&lt;/code&gt; for image logic.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;PyMuPDF&lt;/code&gt; for PDF manipulation.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;OpenAI&lt;/code&gt; for the intent classification layer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project structure separates &lt;strong&gt;Core Logic&lt;/strong&gt; (pure Python) from the &lt;strong&gt;Interface&lt;/strong&gt; (CLI commands), making it easy to test and scalable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Roadmap 🔮
&lt;/h2&gt;

&lt;p&gt;Max is just getting started. I am actively working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;🎥 Local Media Engine:&lt;/strong&gt; Wrapping &lt;code&gt;ffmpeg&lt;/code&gt; to allow natural language video/audio conversion (&lt;code&gt;max ai ask "Convert this MKV to MP4"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;🧠 Conversation Memory:&lt;/strong&gt; A persistent chat mode so Max remembers context between commands.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;🎨 Generative AI:&lt;/strong&gt; Creating assets locally (&lt;code&gt;max create image "Cyberpunk city"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;🛡 Sandboxing:&lt;/strong&gt; Scoped permissions so the AI can't touch system files without approval.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contribute!
&lt;/h2&gt;

&lt;p&gt;This is an open-source project, and I want it to be the ultimate developer companion.&lt;/p&gt;

&lt;p&gt;If you hate memorizing flags or just want to see how to build a production-grade CLI in Python, check out the code.&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;Star the repo:&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/max-cli" rel="noopener noreferrer"&gt;github.com/Abubakr-Alsheikh/max-cli&lt;/a&gt;&lt;br&gt;
🙌 &lt;strong&gt;Pull Requests are welcome!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me know in the comments: &lt;strong&gt;What is the one terminal command you always forget and wish an AI could handle for you?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>cli</category>
    </item>
    <item>
      <title>I Built an AI Manga Creator with Next.js and Gemini's "Visual Memory"</title>
      <dc:creator>Abubakr Alsheikh</dc:creator>
      <pubDate>Sun, 14 Sep 2025 12:27:17 +0000</pubDate>
      <link>https://dev.to/abubakr_alsheikh/i-built-an-ai-manga-creator-with-nextjs-and-geminis-visual-memory-14md</link>
      <guid>https://dev.to/abubakr_alsheikh/i-built-an-ai-manga-creator-with-nextjs-and-geminis-visual-memory-14md</guid>
      <description>&lt;p&gt;I just wrapped up my submission for the Google Nano Banana Hackathon, and I'm incredibly excited to share what I built: &lt;strong&gt;NanoManga Studio&lt;/strong&gt;. It's an AI-powered web app that lets you generate entire, visually-consistent manga stories from a simple idea.&lt;/p&gt;

&lt;p&gt;The biggest problem with AI image generation for storytelling is consistency. How do you make sure your hero has the same hairstyle on page 3 as they did on page 1? I decided to tackle this head-on.&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://nanomanga-studio.vercel.app/" rel="noopener noreferrer"&gt;nanomanga-studio.vercel.app&lt;/a&gt;&lt;br&gt;
💻 &lt;strong&gt;GitHub Repo (Stars are appreciated! ⭐):&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/nanomanga-studio" rel="noopener noreferrer"&gt;github.com/Abubakr-Alsheikh/nanomanga-studio&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;I wanted a modern, fast, and type-safe stack that would let me iterate quickly for the hackathon.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Framework:&lt;/strong&gt; Next.js 15 (App Router)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;UI:&lt;/strong&gt; shadcn/ui &amp;amp; Tailwind CSS&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;State Management:&lt;/strong&gt; Simple React &lt;code&gt;useState&lt;/code&gt; lifted to the root component.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI:&lt;/strong&gt; Google AI JavaScript SDK (&lt;code&gt;@google/generative-ai&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Deployment:&lt;/strong&gt; Vercel&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Core Innovation: Giving the AI a "Visual Memory"
&lt;/h2&gt;

&lt;p&gt;The magic of this project is in the multi-modal prompting. Instead of just sending text, I created a rich context package for the &lt;code&gt;gemini-2.5-flash-image-preview&lt;/code&gt; (or "Nano Banana") model for every new page generation.&lt;/p&gt;

&lt;p&gt;Here's the breakdown of the &lt;code&gt;fetch&lt;/code&gt; call from the &lt;code&gt;PageGenerator&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: app/components/page-generator.tsx&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleGeneratePage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... state checks and loading indicators&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Get previous pages and selected assets for this scene&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="nx"&gt;currentPageNumber&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectedAssets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allAssets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;selectedAssetIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Craft a highly specific text prompt&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    **Manga Page Generation**
    **Page Number:** &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentPageNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    **Page Description:** &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pagePrompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; // e.g., "Panel 1: Close-up on Kenji..."

    **INSTRUCTIONS FOR IMAGE REFERENCES:**
    - The FIRST &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;previousPages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; images are previous pages for continuity.
    - The REMAINING &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;selectedAssets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; images are specific assets for THIS page.
  `&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Assemble the visual context array (THE KEY PART!)&lt;/span&gt;
  &lt;span class="c1"&gt;// We extract the base64 data from our data URLs&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageImages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;previousPages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetImages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selectedAssets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Previous pages go FIRST to establish context&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseImages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;pageImages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;assetImages&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Make the API call&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/generate&lt;/span&gt;&lt;span class="dl"&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fullPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseImages&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ... handle response&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By explicitly telling the AI how to interpret the sequence of images, it can maintain character appearance, clothing, and even damage across multiple pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI as an Art Director and Story Editor
&lt;/h2&gt;

&lt;p&gt;Before even generating images, I use &lt;code&gt;gemini-2.5-flash&lt;/code&gt; with persona-based prompting to structure the entire project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Story Planning:&lt;/strong&gt; I ask the AI to act as a "master manga editor" and return a complete story plan in a strict JSON format. This plan includes character descriptions, environments, and a page-by-page plot that follows a classic narrative arc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Asset Design:&lt;/strong&gt; When inspiring asset ideas, the AI takes on two roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A &lt;strong&gt;"character concept artist"&lt;/strong&gt; that designs a full-body character sheet on a neutral background.&lt;/li&gt;
&lt;li&gt;  A &lt;strong&gt;"background artist"&lt;/strong&gt; that designs an atmospheric, character-free environment shot.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures the generated assets are clean and perfect for use as references later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;This project was a blast. It hammered home that the future of generative AI isn't just about single, powerful prompts. It's about building systems that maintain context, create feedback loops, and allow for true human-AI collaboration. The multi-modal capabilities of models like Gemini are the key to unlocking this.&lt;/p&gt;

&lt;p&gt;I'd love for you to try it out and see what you can create! Let me know what you think in the comments. What would you build with this kind of "visual memory"?&lt;/p&gt;

&lt;p&gt;If you want to read my full technical write-up on Kaggle:&lt;br&gt;
&lt;a href="https://www.kaggle.com/competitions/banana/writeups/nanomanga-studio" rel="noopener noreferrer"&gt;https://www.kaggle.com/competitions/banana/writeups/nanomanga-studio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;h1&gt;
  
  
  react #nextjs #ai #google #webdev #typescript #hackathon #generativeai
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>nextjs</category>
      <category>gemini</category>
    </item>
    <item>
      <title>I Built the Markdown Editor I Always Wanted with Next.js and AI</title>
      <dc:creator>Abubakr Alsheikh</dc:creator>
      <pubDate>Wed, 13 Aug 2025 13:49:39 +0000</pubDate>
      <link>https://dev.to/abubakr_alsheikh/i-built-the-markdown-editor-i-always-wanted-with-nextjs-and-ai-20bd</link>
      <guid>https://dev.to/abubakr_alsheikh/i-built-the-markdown-editor-i-always-wanted-with-nextjs-and-ai-20bd</guid>
      <description>&lt;p&gt;We've all been there. You have an idea, you start a side project with excitement, and then... it ends up in the GitHub graveyard, untouched for months.&lt;/p&gt;

&lt;p&gt;This time, I was determined to break the cycle. I decided to build a tool that solved a set of real, frustrating problems I faced almost daily as a developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: A Lack of Control
&lt;/h3&gt;

&lt;p&gt;My workflow often involves generating text with LLMs, which usually comes out as an unstructured mess. But the core frustration wasn't just the formatting; it was the &lt;strong&gt;lack of control&lt;/strong&gt; in every step that followed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; ** inflexible Previews:** When working with Arabic text, I couldn't find a live markdown preview that properly supported RTL.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;"One-Size-Fits-All" Styling:&lt;/strong&gt; I wanted to change the preview's font size for readability or switch to a different font for a presentation, but most tools are static.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Manual Formatting:&lt;/strong&gt; The tedious process of manually adding headers, lists, and code blocks was a constant time sink.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I realized I didn't just want a markdown editor; I wanted a fully customizable, intelligent workstation. So, I decided to build it myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Smart Markdown Pro
&lt;/h3&gt;

&lt;p&gt;I'm excited to share &lt;strong&gt;&lt;a href="https://smart-markdown-pro.vercel.app/" rel="noopener noreferrer"&gt;Smart Markdown Pro&lt;/a&gt;&lt;/strong&gt;, a local-first, AI-powered markdown editor built on a modern stack: Next.js (App Router), Tailwind CSS, and TypeScript.&lt;/p&gt;

&lt;p&gt;It's open source, and I built it to be the tool I wish I had from the start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://smart-markdown-pro.vercel.app/" 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.amazonaws.com%2Fuploads%2Farticles%2Fc03a6acq5woq7i7huvil.png" alt="Smart Markdown Pro Screenshot" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Features That Mattered to Me
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Total Preview Customization
&lt;/h4&gt;

&lt;p&gt;This was the core idea. I wanted a preview pane that adapted to my needs. In the settings, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Toggle RTL/LTR direction instantly.&lt;/li&gt;
&lt;li&gt;  Adjust the base font size with a slider.&lt;/li&gt;
&lt;li&gt;  Use &lt;strong&gt;any&lt;/strong&gt; Google Font by pasting its link and CSS value. The app saves your previously used fonts for quick access.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. AI-Powered Formatting
&lt;/h4&gt;

&lt;p&gt;To solve the messy text problem, I integrated the OpenAI API. There's a single "Format with AI" button that takes your unstructured text and turns it into clean, well-formatted GFM. Because let's be honest, you can't ship an app in 2025 without a little AI, right? 😉&lt;/p&gt;

&lt;p&gt;The backend is a simple Next.js API route that securely handles the API key and prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /src/app/api/format-with-ai/route.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
You are an expert markdown formatter... 
Your response must ONLY be the formatted markdown content.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Local-First File Management
&lt;/h4&gt;

&lt;p&gt;I hate creating accounts for simple tools. So, I used &lt;code&gt;Zustand&lt;/code&gt; with its &lt;code&gt;persist&lt;/code&gt; middleware to save everything directly to the user's &lt;code&gt;localStorage&lt;/code&gt;. It supports creating, opening, searching, and deleting multiple documents, and it's incredibly fast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /src/lib/store/useDocumentStore.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;persist&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand/middleware&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useDocumentStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DocumentState&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;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// ... all the state and actions ...&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smart-markdown-pro-storage-v2&lt;/span&gt;&lt;span class="dl"&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;h4&gt;
  
  
  4. High-Fidelity PDF Exports
&lt;/h4&gt;

&lt;p&gt;This was the hardest technical challenge. Client-side libraries like &lt;code&gt;html2canvas&lt;/code&gt; kept failing because of modern CSS color formats (&lt;code&gt;lab()&lt;/code&gt;, &lt;code&gt;oklch()&lt;/code&gt;) used by Tailwind/Shadcn.&lt;/p&gt;

&lt;p&gt;The solution? A serverless function using &lt;strong&gt;Puppeteer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The frontend sends the preview's HTML to an API route. The backend launches a headless Chrome instance, injects the HTML and a minimal set of CSS, and uses Chrome's own print-to-PDF engine to generate a perfect PDF.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /src/app/api/export/pdf/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;puppeteer-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sparticuz/chromium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// For serverless environments&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finalHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdfBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;printBackground&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="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building What You Need is Rewarding
&lt;/h3&gt;

&lt;p&gt;This project was a fantastic learning experience and a powerful reminder that the best projects often come from solving your own problems. It forces you to think through every detail and build something you genuinely want to use.&lt;/p&gt;

&lt;p&gt;I'd love for you to check it out!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Live App:&lt;/strong&gt; &lt;a href="https://smart-markdown-pro.vercel.app/" rel="noopener noreferrer"&gt;https://smart-markdown-pro.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;GitHub Repo (Stars are super appreciated!):&lt;/strong&gt; &lt;a href="https://github.com/Abubakr-Alsheikh/smart-markdown-pro" rel="noopener noreferrer"&gt;https://github.com/Abubakr-Alsheikh/smart-markdown-pro&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What are some daily annoyances you've been tempted to turn into a side project? I'd love to hear about them in the comments&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>nextjs</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
