<?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: Mustafa Bahaa</title>
    <description>The latest articles on DEV Community by Mustafa Bahaa (@mustafa_bahaa).</description>
    <link>https://dev.to/mustafa_bahaa</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%2F3971917%2F1f015572-d979-4817-961c-fd17697ab6d9.jpg</url>
      <title>DEV Community: Mustafa Bahaa</title>
      <link>https://dev.to/mustafa_bahaa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mustafa_bahaa"/>
    <language>en</language>
    <item>
      <title>Capturing exact DOM elements in a Chrome MV3 extension: DPR, sticky elements, and redaction before pixels exist</title>
      <dc:creator>Mustafa Bahaa</dc:creator>
      <pubDate>Sun, 06 Sep 2026 01:51:10 +0000</pubDate>
      <link>https://dev.to/mustafa_bahaa/capturing-exact-dom-elements-in-a-chrome-mv3-extension-dpr-sticky-elements-and-redaction-before-1hgd</link>
      <guid>https://dev.to/mustafa_bahaa/capturing-exact-dom-elements-in-a-chrome-mv3-extension-dpr-sticky-elements-and-redaction-before-1hgd</guid>
      <description>&lt;p&gt;Every screenshot extension workflow I've used shares the same flaw: you take&lt;br&gt;
a picture of the viewport, then crop. The capture is never &lt;em&gt;of&lt;/em&gt; the thing you&lt;br&gt;
wanted — it's of a rectangle that happens to contain it.&lt;/p&gt;

&lt;p&gt;I spent the last months building &lt;a href="https://snaptura-31ff6.web.app" rel="noopener noreferrer"&gt;Snaptura&lt;/a&gt;,&lt;br&gt;
a Chrome MV3 extension that captures exact DOM elements and turns them into&lt;br&gt;
polished exports. This post walks through the four problems that actually&lt;br&gt;
took engineering — skip the marketing, steal the techniques.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Capture at the device's resolution, not the CSS fantasy
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;t.captureVisibleTab()&lt;/code&gt; gives you a bitmap of the viewport. The trap: a&lt;br&gt;
&lt;code&gt;400×300px&lt;/code&gt; card on a laptop with &lt;code&gt;devicePixelRatio: 2&lt;/code&gt; is not 400×300 in&lt;br&gt;
that bitmap — it's 800×600, and the browser helpfully encodes the DPR into&lt;br&gt;
the PNG so naive consumers render it double-sized or blurry.&lt;/p&gt;

&lt;p&gt;The fix is boring and essential: every capture carries its DPR, and every&lt;br&gt;
crop rectangle is multiplied before slicing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&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;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Slice the viewport bitmap in *device* pixels:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dpr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// …and remember `dpr` so exports stay 1:1 sharp.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rounding matters. A &lt;code&gt;Math.floor&lt;/code&gt; on the wrong edge gives you a 1-device-pixel&lt;br&gt;
seam of the neighboring element — invisible in code, very visible in a&lt;br&gt;
marketing screenshot.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Hover-select is a hit-testing problem
&lt;/h2&gt;

&lt;p&gt;"Select the element under the cursor" sounds like &lt;code&gt;document.elementFromPoint&lt;/code&gt;&lt;br&gt;
— until you try it on real pages with overlays, shadow DOM, and iframes. What&lt;br&gt;
worked in the end is closer to what DevTools does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Walk up from the hovered node and offer the &lt;em&gt;meaningful&lt;/em&gt; ancestors, not
just the immediate one (users almost never want the inner &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;; they
want the card).&lt;/li&gt;
&lt;li&gt;Score candidates by visible area and semantic weight — a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; inside
a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; should be grabbable as either, with a keyboard shortcut to walk
up the chain.&lt;/li&gt;
&lt;li&gt;Paint the selection overlay in a page-level layer that ignores
pointer events, so highlighting never changes the page you're capturing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The heuristic I landed on: the element people &lt;em&gt;mean&lt;/em&gt; to select is usually the&lt;br&gt;
nearest ancestor whose background differs from its parent's. Cheap, wrong&lt;br&gt;
occasionally, but predictable in a way users learn in ten seconds.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Full-page captures and sticky-element ghosts
&lt;/h2&gt;

&lt;p&gt;Scroll-and-stitch full-page capture has an obvious implementation: scroll by&lt;br&gt;
a viewport height, capture, repeat, then blend the overlap. Two real-world&lt;br&gt;
ruinizers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sticky elements stamp themselves over every fold.&lt;/strong&gt; The fixed header looks&lt;br&gt;
fine in fold one and photobombs folds two through nine. The fix is to detect&lt;br&gt;
&lt;code&gt;position: sticky/fixed&lt;/code&gt; elements &lt;em&gt;before&lt;/em&gt; scrolling and, per fold, either&lt;br&gt;
hide them or pin their correct-at-this-scroll-offset version. Detection is a&lt;br&gt;
&lt;code&gt;getComputedStyle&lt;/code&gt; walk; the subtle part is compositing them back at their&lt;br&gt;
natural position in the &lt;em&gt;final&lt;/em&gt; fold, where users expect to see the header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scrolling isn't pixel-exact.&lt;/strong&gt; Page zoom, fractional scroll positions, and&lt;br&gt;
&lt;code&gt;scroll-behavior: smooth&lt;/code&gt; all break naive stitching. Forcing&lt;br&gt;
&lt;code&gt;scrollTo({ top: y, behavior: 'instant' })&lt;/code&gt; and waiting for both the scroll&lt;br&gt;
event &lt;em&gt;and&lt;/em&gt; a rAF after it eliminated 95% of the seam artifacts.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Redact before the pixels exist
&lt;/h2&gt;

&lt;p&gt;Pixel-level redaction (draw a black box over the region) leaves the secret&lt;br&gt;
in the file — metadata strips help, but screenshot tools that blur and undo&lt;br&gt;
are a meme for a reason.&lt;/p&gt;

&lt;p&gt;The alternative: redact the DOM itself. Replace the text node with a&lt;br&gt;
placeholder element of identical box metrics, capture, then restore the&lt;br&gt;
original node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;redact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&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;veil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;veil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fixed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currentColor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.85&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="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;veil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &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="nx"&gt;veil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the sensitive string is never rasterized, it can't leak — not in the&lt;br&gt;
PNG, not in an undo stack, nowhere. The cost is that redaction must be&lt;br&gt;
declared &lt;em&gt;before&lt;/em&gt; capture, which turns out to be a fine UX: you mark regions&lt;br&gt;
once, and every future capture of that page respects them.&lt;/p&gt;

&lt;h2&gt;
  
  
  MV3 logistics worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The service worker dies constantly.&lt;/strong&gt; Any capture pipeline that assumes a
long-lived background page breaks. We keep state in &lt;code&gt;chrome.storage&lt;/code&gt;, treat
the worker as a router, and do the heavy lifting elsewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offscreen documents are the escape hatch.&lt;/strong&gt; GIF encoding and video
compositing run in an offscreen document with &lt;code&gt;OffscreenCanvas&lt;/code&gt; + Workers —
keeping the service worker free and alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tabCapture&lt;/code&gt; for viewport recording&lt;/strong&gt; requires the user gesture and the
permission choreography to line up exactly; get that right once and
recording "just works."&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That's the core of it. If you want to see the end result of these&lt;br&gt;
techniques, the extension is&lt;br&gt;
&lt;a href="https://snaptura-31ff6.web.app" rel="noopener noreferrer"&gt;Snaptura&lt;/a&gt; — free, watermark-free, with a&lt;br&gt;
Pro tier and a lifetime option. But the four patterns above stand on their&lt;br&gt;
own for anything you're building with &lt;code&gt;captureVisibleTab&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What did I miss? If you've shipped capture tooling, I'd love to hear which&lt;br&gt;
part hurt most.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>sideprojects</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Syncing AI agents across Claude, Gemini, and Cursor</title>
      <dc:creator>Mustafa Bahaa</dc:creator>
      <pubDate>Sat, 06 Jun 2026 23:40:48 +0000</pubDate>
      <link>https://dev.to/mustafa_bahaa/syncing-ai-agents-across-claude-gemini-and-cursor-3d0j</link>
      <guid>https://dev.to/mustafa_bahaa/syncing-ai-agents-across-claude-gemini-and-cursor-3d0j</guid>
      <description>&lt;p&gt;If you are using AI coding assistants today, chances are you aren't just using one. You might be using Claude Code in the terminal for rapid CLI actions, Gemini CLI for long-context search, and Cursor or Copilot as your main IDE.&lt;/p&gt;

&lt;p&gt;But there is a major friction point: none of these tools talk to each other.&lt;/p&gt;

&lt;p&gt;If you configure a Model Context Protocol (MCP) server in Claude, Cursor knows nothing about it.&lt;br&gt;
If you write a detailed custom system prompt (agent instructions) for Gemini CLI, Claude starts as a blank slate.&lt;br&gt;
You end up manually copy-pasting files and duplicating JSON configs across five different directories.&lt;br&gt;
To solve this, I built Wasla (Arabic for "connection") — a universal synchronization layer for AI coding tools.&lt;/p&gt;

&lt;p&gt;What is Wasla?&lt;/p&gt;

&lt;p&gt;Wasla is an open-source CLI and daemon that monitors and syncs your agent instructions and MCP servers across Claude Code, Gemini CLI, Cursor, and more.&lt;/p&gt;

&lt;p&gt;It runs entirely locally, takes seconds to set up, and ensures your assets are shared across every tool you use.&lt;/p&gt;

&lt;p&gt;You can set up Gemini in your workspace with: npx @untitled-devs/wasla setup gemini --scope workspace&lt;/p&gt;

&lt;p&gt;How it Works Under the Hood&lt;/p&gt;

&lt;p&gt;Instead of copying entire folders (which leads to file duplication and conflicts), Wasla uses a "Latest is Greatest" strategy and lightweight reference stubs:&lt;/p&gt;

&lt;p&gt;Asset Discovery: Wasla scans your configured tool directories (like ~/.claude/ and ~/.gemini/).&lt;br&gt;
Reference Stubs: Instead of copying whole files, it writes a lightweight reference pointer. When Claude loads the stub, it points directly to the original file created in Gemini.&lt;br&gt;
Automatic Mirroring: Whichever tool you edited most recently becomes the source of truth for the next sync.&lt;br&gt;
Here is a quick look at the directory mapping:&lt;/p&gt;

&lt;p&gt;~/.gemini/agents/planner.md (Original file) ~/.claude/agents/planner.md (Stub pointing to Gemini original) ~/.openclaw/agents/planner.md (Stub pointing to Gemini original)&lt;/p&gt;

&lt;p&gt;Key Features&lt;/p&gt;

&lt;p&gt;Dual-Scope Syncing You can choose how you sync your configurations:&lt;br&gt;
--scope workspace: Keep prompts and configs local to your active project or git repository.&lt;br&gt;
--scope user: Share your custom agents globally across your entire machine.&lt;br&gt;
Daemon Watch Mode Run "wasla watch". It runs quietly in the background, listening for file saves, and updates every tool's directory instantly.&lt;/p&gt;

&lt;p&gt;Interactive Web Dashboard Run "wasla visualizer". It spins up a local React dashboard showing exactly which agents, instructions, and MCP servers are connected to which tools.&lt;/p&gt;

&lt;p&gt;Getting Started&lt;/p&gt;

&lt;p&gt;You can install it globally or run it directly using npx:&lt;/p&gt;

&lt;p&gt;npx @untitled-devs/wasla watch --scope user&lt;/p&gt;

&lt;p&gt;The project is fully open-source (MIT). I would love to hear your thoughts, feedback on the architecture, or ideas for new adapters!&lt;/p&gt;

&lt;p&gt;Check out the repo here: &lt;a href="https://github.com/The-Untitled-Org/wasla" rel="noopener noreferrer"&gt;https://github.com/The-Untitled-Org/wasla&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>typescript</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
