<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Vlasovdev]]></title><description><![CDATA[Notes from building AI-powered Chrome extensions: Manifest V3, browser agents, Claude Code skills, Hark AI Agent and shipping indie products.]]></description><link>https://vlasovdev.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6af5956eae0b62c6032c61bc/e30849ae-5b2b-4caa-b4ae-3e3e2700877b.png</url><title>Vlasovdev</title><link>https://vlasovdev.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 13 Nov 2026 19:51:17 GMT</lastBuildDate><atom:link href="https://vlasovdev.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building an AI Browser Agent as a Chrome Side Panel: 7 Lessons from Shipping Hark]]></title><description><![CDATA[Lessons from shipping Hark in Manifest V3: optional permissions, a fixed tool list, approval before risky clicks, smooth streaming, voice and images.]]></description><link>https://vlasovdev.hashnode.dev/building-an-ai-browser-agent-as-a-chrome-side-panel-7-lessons-from-shipping-hark</link><guid isPermaLink="true">https://vlasovdev.hashnode.dev/building-an-ai-browser-agent-as-a-chrome-side-panel-7-lessons-from-shipping-hark</guid><category><![CDATA[chrome extension]]></category><category><![CDATA[hark]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Browsers]]></category><dc:creator><![CDATA[Teodor Vlasov]]></dc:creator><pubDate>Thu, 12 Nov 2026 10:54:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af5956eae0b62c6032c61bc/0ac73a08-1a6a-47e4-855d-733ac5ae106f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most AI browser agents ask you to switch browsers and trust something you can't really watch. With <strong>Hark AI Agent</strong> we went the other way: a personal AI agent that lives in Chrome's <strong>side panel</strong>, next to the page you're already on, and asks before anything final.</p>
<p>This post is a collection of the practical lessons from shipping it as a Manifest V3 extension — permissions, safety, streaming, voice input and a few side-panel quirks that cost us real debugging time.</p>
<h2>What Hark does</h2>
<ul>
<li><strong>Chat</strong> with the page or email you have open: summaries, answers, rewrites.</li>
<li><strong>Gmail and Outlook</strong>: summarize a thread and drop a ready reply into the reply box (you press Send).</li>
<li><strong>Agent mode</strong>: clicks, types, chooses options and navigates on a website, step by step.</li>
<li><strong>Tracker</strong>: orders, bookings, deadlines and bills saved from emails and pages into one local list.</li>
<li><strong>Voice input</strong> and <strong>questions about images</strong>.</li>
</ul>
<h2>1. Keep the permission list boring</h2>
<p>The fastest way to get rejected by the Chrome Web Store — and to scare users — is a long permission prompt. Hark's manifest asks for very little up front:</p>
<pre><code>{
  "manifest_version": 3,
  "side_panel": { "default_path": "sidepanel.html" },
  "permissions": ["sidePanel", "storage", "activeTab", "scripting", "contextMenus"],
  "optional_host_permissions": ["&lt;all_urls&gt;"]
}
</code></pre>
<p><code>activeTab</code> covers the common case: the user clicks the toolbar icon, presses the shortcut or uses the right-click menu, and Hark gets temporary access to <em>that</em> tab. Broad website access is <strong>optional</strong> and requested only the first time a feature truly needs it — "Use this tab", an email action, a Tracker scan or the Agent:</p>
<pre><code>export const ORIGINS = { origins: ["&lt;all_urls&gt;"] };
export const requestAccess = () =&gt; chrome.permissions.request(ORIGINS);

function withAccess(fn) {
  if (S.access) return fn();
  requestAccess().then((ok) =&gt; { if (ok) { S.access = true; fn(); } });
}
</code></pre>
<p>Install-time warnings stay minimal, and users who never use the Agent never see the scary prompt.</p>
<h2>2. No content scripts on page load</h2>
<p>Hark declares <strong>no</strong> <code>content_scripts</code> in the manifest. Nothing runs on the pages you visit until you ask. When you do, the panel injects one packaged file into the active tab:</p>
<pre><code>await chrome.scripting.executeScript({
  target: { tabId },
  files: ["content/tools.js"],
});
</code></pre>
<p>That one decision answers half of the privacy questions in review: Hark cannot read your tabs in the background because there is nothing running there.</p>
<h2>3. The model chooses; the extension acts</h2>
<p>A browser agent should never execute code that comes from a server or a model. In Hark the model only <strong>chooses</strong> the next step from a fixed list of tools implemented inside the extension's own <code>content/tools.js</code>:</p>
<pre><code>export const TOOLS = ["click", "type", "select", "scroll", "press", "navigate", "wait", "ask_user", "done"];
</code></pre>
<p>The model's reply is just JSON like <code>{ "tool": "click", "args": { "ref": 12 } }</code>; anything outside <code>TOOLS</code> is rejected, and <code>ref</code> points to an element from the snapshot the extension itself built.</p>
<p>It's also what Chrome Web Store's "no remote code" rule expects, and it makes every step easy to show to the user in plain words: <em>Opened the booking page → Chose today, 8:00 PM → Set 2 guests</em>.</p>
<h2>4. Approval before anything final</h2>
<p>Clicking "next" in a form is harmless. Clicking <strong>Send</strong>, <strong>Book</strong>, <strong>Pay</strong>, <strong>Subscribe</strong> or <strong>Delete</strong> is not. Hark pauses on any step like that and shows exactly what it is about to do, with Approve and Cancel buttons.</p>
<p>Two more hard rules:</p>
<ul>
<li>The page snapshot the model sees <strong>never includes the values</strong> of password, one-time-code or payment-card fields.</li>
<li>When a login, verification or payment step comes up, Hark hands that step back to you.</li>
</ul>
<p>Sensitive fields are detected like this:</p>
<pre><code>const SENSITIVE_AC = /^(cc-|current-password|new-password|one-time-code)/i;

const isSensitive = (el) =&gt; {
  const type = (el.getAttribute("type") || "").toLowerCase();
  const ac = el.getAttribute("autocomplete") || "";
  const name = ((el.name || "") + " " + (el.id || "")).toLowerCase();
  return type === "password" || SENSITIVE_AC.test(ac) ||
    /(card.?number|cardnum|cc-?num|cvv|cvc|security.?code|iban)/.test(name);
};
</code></pre>
<p>The approval gate itself is three checks: the model can flag a step, the target's label matches a "risky" word list (in several languages), or it's a submit button inside a form:</p>
<pre><code>const RISKY = /\b(send|submit|pay|purchase|buy|order|checkout|book|reserve|confirm|delete|subscribe|transfer|publish)\b/i;

let gate = !!modelConfirm;
if (tool === "click" &amp;&amp; (RISKY.test(target.label) || (target.type === "submit" &amp;&amp; target.inForm))) gate = true;
if (tool === "type" &amp;&amp; target.sensitive) return refuse("the user must fill this in");
if (gate &amp;&amp; !(await askUser(step))) return stopRun("user declined");
</code></pre>
<h2>5. Streaming text that doesn't stutter</h2>
<p>Server-sent events arrive in bursts: nothing for 300 ms, then 40 tokens at once. Rendering each chunk as it lands makes the text jump. We buffer incoming text and reveal it on <code>requestAnimationFrame</code>, speeding up when the buffer grows:</p>
<pre><code>let got = "", raf = 0; // `got` = everything received so far, `ai.text` = what's on screen

const tick = () =&gt; {
  raf = 0;
  const back = got.length - ai.text.length;
  if (back &gt; 0) {
    let end = ai.text.length + Math.max(1, Math.ceil(back / 14)); // faster when far behind
    if (/[\uD800-\uDBFF]/.test(got[end - 1] || "")) end++;        // never split an emoji
    ai.text = got.slice(0, end);
    updateMsg(ai);
  }
  if (got.length &gt; ai.text.length) raf = requestAnimationFrame(tick);
};

function onChunk(text) { got += text; if (!raf) raf = requestAnimationFrame(tick); }
</code></pre>
<p>One gotcha: <code>requestAnimationFrame</code> doesn't fire in a hidden document, so when the panel isn't visible we skip the animation and show the full text at once. Small thing, big difference in how "alive" the answer feels.</p>
<h2>6. Voice input in a side panel needs a detour</h2>
<p>The Web Speech API works in extension pages, but a side panel <strong>can't show Chrome's microphone permission prompt</strong> reliably. The fix: the first time the user presses the mic button, open a tiny extension page in a tab that asks for the microphone once:</p>
<pre><code>// mic.js — runs in mic.html, opened once in a normal tab
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach((t) =&gt; t.stop()); // we only needed the permission
await chrome.storage.session.set({ micGranted: Date.now() });
setTimeout(() =&gt; window.close(), 1600);
</code></pre>
<p>The permission is granted to the extension's origin, so from then on speech recognition works inside the panel. Hark only receives the recognized <strong>text</strong> — no audio is recorded or uploaded.</p>
<h2>7. Images: shrink on the client</h2>
<p>Users paste full-resolution screenshots. Sending 8 MB images to a model is slow and expensive, so the panel resizes them on a canvas before sending (longest side ≤ 1568 px, JPEG) and keeps only a ~240 px thumbnail in local chat history.</p>
<pre><code>async function shrink(file, max, q) {
  const bmp = await createImageBitmap(file);
  const k = Math.min(1, max / Math.max(bmp.width, bmp.height));
  const cv = document.createElement("canvas");
  cv.width = Math.max(1, Math.round(bmp.width * k));
  cv.height = Math.max(1, Math.round(bmp.height * k));
  const g = cv.getContext("2d");
  g.fillStyle = "#fff"; // transparent PNGs get a white background instead of black
  g.fillRect(0, 0, cv.width, cv.height);
  g.drawImage(bmp, 0, 0, cv.width, cv.height);
  return cv.toDataURL("image/jpeg", q);
}
</code></pre>
<h2>Bonus: side-panel quirks worth knowing</h2>
<p><strong>Re-render only when something changed.</strong> We listened to <code>tabs.onUpdated</code> and rebuilt the home screen on every event — about ten rebuilds in two seconds while a page loaded, which looked like a jittery panel. Comparing a small key (URL + title + mode) before re-rendering fixed it.</p>
<p><strong>Opening the panel from your website.</strong> With <code>externally_connectable</code> your site can message the extension, and <code>chrome.sidePanel.open()</code> still counts the click on the page as the user gesture. In the background script:</p>
<pre><code>chrome.runtime.onMessageExternal.addListener((msg, sender, reply) =&gt; {
  if (sender.origin !== "https://harkaiagent.com") return;
  if (msg?.type === "hark:open") {
    chrome.sidePanel.open({ windowId: sender.tab.windowId })
      .then(() =&gt; reply({ ok: true }), () =&gt; reply({ ok: false }));
    return true; // async reply
  }
});
</code></pre>
<p>That's how Hark's welcome page has a working <strong>Open Hark</strong> button.</p>
<h2>Try it</h2>
<p>Hark is free to try, no account needed: <a href="https://chromewebstore.google.com/detail/hark-ai-agent-personal-as/cnncmadbmedhekcanfmnadicbogoofkd">Hark AI Agent in the Chrome Web Store</a> · <a href="https://harkaiagent.com">harkaiagent.com</a></p>
<p>If you're building an agent yourself, I'd love to hear how you handle approvals and sensitive fields — drop a comment.</p>
]]></content:encoded></item><item><title><![CDATA[How to Make Animated Reels with Claude Code Skills (No After Effects)]]></title><description><![CDATA[Install a Claude Code skill, type one command, render a vertical MP4: self-drawing charts, 3D map routes and explainers. No After Effects.]]></description><link>https://vlasovdev.hashnode.dev/how-to-make-animated-reels-with-claude-code-skills-no-after-effects</link><guid isPermaLink="true">https://vlasovdev.hashnode.dev/how-to-make-animated-reels-with-claude-code-skills-no-after-effects</guid><category><![CDATA[claude]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[AI]]></category><category><![CDATA[animation]]></category><category><![CDATA[video]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Teodor Vlasov]]></dc:creator><pubDate>Thu, 12 Nov 2026 10:51:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af5956eae0b62c6032c61bc/044ed1e7-e9fd-4174-b84a-10def87d0793.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You can make scroll-stopping vertical videos — self-drawing charts, 3D map routes, cartoon explainers — <strong>without After Effects, stock footage or a timeline</strong>. The trick is Claude Code <strong>skills</strong>: you install one once, type a single command, and Claude Code writes the animation in code and renders an MP4 on your own computer.</p>
<p>This guide shows how it works end to end, and how to find the right skill without digging through GitHub.</p>
<h2>What is a Claude Code skill?</h2>
<p>A skill is a folder with a <code>SKILL.md</code> file (instructions plus optional scripts) that teaches Claude Code a new job. Claude Code looks for personal skills in:</p>
<pre><code>~/.claude/skills/&lt;skill-name&gt;/SKILL.md
</code></pre>
<p>and for project skills in <code>.claude/skills/&lt;skill-name&gt;/SKILL.md</code> inside a repository. You run a skill by typing <code>/skill-name</code> at the start of a message, and whatever you type after the name is passed to the skill as its arguments. (Details: <a href="https://code.claude.com/docs/en/skills">Claude Code docs on skills</a>.)</p>
<p>For animation, that means a skill can contain everything needed to build a specific style of video — and your arguments are the text, numbers and places that go into it.</p>
<h2>What you need</h2>
<ul>
<li><strong>Claude Code</strong> (it needs a paid Claude plan or an Anthropic API account).</li>
<li>A few minutes to install a skill. Some skills need extra free tools; each skill lists exactly what it needs.</li>
</ul>
<h2>Step 1. Install a skill</h2>
<p>Take <strong>3D Map Route</strong> — a clean 3D city where your route draws itself, a pin drops and an ETA counts down. It's MIT-licensed and lives in the <code>artemnovitckii/claude-animation-skills</code> repository on GitHub.</p>
<p>macOS / Linux:</p>
<pre><code>git clone https://github.com/artemnovitckii/claude-animation-skills
mkdir -p ~/.claude/skills
cp -r claude-animation-skills/skills/map-route ~/.claude/skills/
</code></pre>
<p>Windows (PowerShell):</p>
<pre><code>git clone https://github.com/artemnovitckii/claude-animation-skills
New-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null
Copy-Item -Recurse claude-animation-skills\skills\map-route "$HOME\.claude\skills\"
</code></pre>
<h2>Step 2. Run one command</h2>
<p>Open Claude Code in an empty folder and type:</p>
<pre><code>/map-route Home to the gym, 2.8 km
</code></pre>
<p>Claude Code builds the scene and renders a <strong>1080×1920, 30 fps MP4</strong> — about a minute and a half for this skill. Want something else? Change the arguments:</p>
<pre><code>/map-route Hotel to the Eiffel Tower, 3.4 km, red route
/map-route Office to the airport, 18 km, miles
</code></pre>
<h2>Step 3. Edit it like code, not like footage</h2>
<p>Because the video <em>is</em> code, changing it is a sentence away: "make the route orange", "slow the camera down", "add our logo at the end". Claude Code edits the project and re-renders. That's the big difference from AI video generators: nothing is a black box, every frame is reproducible.</p>
<h2>Six skills worth trying first</h2>
<table>
<thead>
<tr>
<th>Command</th>
<th>What you get</th>
<th>Example</th>
</tr>
</thead>
<tbody><tr>
<td><code>/graph</code></td>
<td>A graph that draws itself with a counter</td>
<td><code>/graph 0 to 100K followers in 9 months</code></td>
</tr>
<tr>
<td><code>/map-route</code></td>
<td>3D map route with a pin drop and ETA</td>
<td><code>/map-route Home to the gym, 2.8 km</code></td>
</tr>
<tr>
<td><code>/stick-figure</code></td>
<td>Hand-drawn stick-figure explainer</td>
<td><code>/stick-figure Why did pirates wear eye patches?</code></td>
</tr>
<tr>
<td><code>/zach-d-films</code></td>
<td>3D cartoon "What if…?" explainer</td>
<td><code>/zach-d-films Could ancient armour stop an arrow?</code></td>
</tr>
<tr>
<td><code>/breakdown</code></td>
<td>3D exploded view with labels</td>
<td><code>/breakdown My phone, every part labelled</code></td>
</tr>
<tr>
<td><code>/minimalistic-graphics</code></td>
<td>Minimal line art with a quote</td>
<td><code>/minimalistic-graphics Every step feels small until you look back</code></td>
</tr>
</tbody></table>
<p>Render times range from about 10 seconds (<code>/graph</code>) to several minutes for a 30-second 3D cartoon.</p>
<h2>Finding the right skill without the GitHub hunt</h2>
<p>Good animation skills are scattered across dozens of repositories with different licenses and install steps. That's why we built <strong>Claude Animation Skills</strong> — a Chrome side panel with 100 hand-picked animation skills:</p>
<ul>
<li><strong>Describe your video</strong> in plain words (or by voice, or with a reference image) and the AI picks the best skill and writes the exact command with your details.</li>
<li>Every skill shows a <strong>live animated preview</strong>, its <strong>author, license and GitHub source</strong>, pinned to the version we reviewed.</li>
<li><strong>Install steps</strong> for macOS, Windows and Linux, or one command to copy.</li>
</ul>
<p>It's free to try (two skills and one free action a day): <a href="https://chromewebstore.google.com/detail/claude-animation-skills-d/oibkhnlmohhajhagdlfklmkdmbffpahc">Claude Animation Skills in the Chrome Web Store</a> · <a href="https://claude-skills.art">claude-skills.art</a></p>
<p>What would you animate first? Tell me in the comments.</p>
<hr />
<p><em>Claude Animation Skills is an independent project, not affiliated with Anthropic. Claude and Claude Code are trademarks of Anthropic, PBC. Skills belong to their authors and are offered under their own licenses.</em></p>
]]></content:encoded></item></channel></rss>