Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
hetpatel-11 avatar

Zap

  • 6 installs
  • 10 repo stars
  • Updated May 3, 2026
  • hetpatel-11/zap

Customizes a running Electron app's look and behavior live via the Chrome DevTools Protocol, without modifying app files or restarting.

About

A skill that injects CSS/JS into any running Electron app (Slack, VS Code, Discord, etc.) over CDP to restyle UI, move panels, or add features. A developer uses it to patch an app live after dumping its real DOM to find selectors.

  • Mandatory workflow: connect, dump live DOM, then inject against real selectors
  • Uses --remote-debugging-port CDP with no file changes or restarts

Zap by the numbers

  • 6 all-time installs (skills.sh)
  • +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #1,691 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hetpatel-11/zap --skill zap

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs6
repo stars10
Last updatedMay 3, 2026
Repositoryhetpatel-11/zap

What it does

Customizes a running Electron app's look and behavior live via the Chrome DevTools Protocol, without modifying app files or restarting.

Files

SKILL.mdMarkdownGitHub ↗

Electron Live Patcher

Customize any Electron desktop app live using the Chrome DevTools Protocol (CDP). No file modification, no restarts, no sudo. Changes appear instantly in the running app.

IMPORTANT: Default workflow for ANY app

Never guess CSS class names. Every Electron app has a different structure. Always follow this order:

1. Connect to the app via CDP 2. Dump the live DOM to discover real selectors 3. Only then write and inject CSS/JS

Use this snippet to inspect any unknown app first:

// Step 1 — find what elements exist related to the user's request
expression: `
  Array.from(document.querySelectorAll('*'))
    .filter(el => el.className && typeof el.className === 'string')
    .filter(el => el.className.match(/sidebar|panel|nav|left|right|drawer|rail/i))
    .map(el => el.tagName + ' classes: ' + el.className.substring(0, 100))
    .slice(0, 20)
    .join('\\n')
`

// Step 2 — inspect the layout container
expression: `document.body.innerHTML.substring(0, 4000)`

Then use what you find to write precise CSS targeting real class names from the live app.

How It Works

Every Electron app is built on Chromium. When launched with --remote-debugging-port=9222, it exposes a WebSocket API that lets you run JavaScript inside its renderer process — the same way browser DevTools work, but programmatically from outside the app.

Step-by-Step Process

1. Check if the app is running with CDP enabled

curl -s http://localhost:9222/json 2>/dev/null

If empty or connection refused, the app needs to be relaunched with the debug port.

2. Relaunch with CDP enabled (if needed)

pkill -x "AppName"
sleep 1
open -a "AppName" --args --remote-debugging-port=9222
sleep 3

3. Find the main page target

curl -s http://localhost:9222/json

Look for the target where "type": "page" and "title" matches the app name. Note its webSocketDebuggerUrl.

4. Inject CSS or JS via Node.js WebSocket

Create and run a temporary inject script:

const WebSocket = require('ws');
const http = require('http');

const CSS = `/* your CSS here */`;

function getTargets() {
  return new Promise((resolve, reject) => {
    http.get('http://localhost:9222/json', (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => resolve(JSON.parse(data)));
    }).on('error', reject);
  });
}

async function inject() {
  const targets = await getTargets();
  const page = targets.find(t => t.type === 'page');
  if (!page) return console.error('No page target found');

  const ws = new WebSocket(page.webSocketDebuggerUrl);
  let id = 1;

  ws.on('open', () => {
    ws.send(JSON.stringify({
      id: id++,
      method: 'Runtime.evaluate',
      params: {
        expression: `
          (function() {
            const existing = document.getElementById('__zap__');
            if (existing) existing.remove();
            const style = document.createElement('style');
            style.id = '__zap__';
            style.textContent = ${JSON.stringify(CSS)};
            document.head.appendChild(style);
            return 'injected';
          })()
        `
      }
    }));
  });

  ws.on('message', (data) => {
    const msg = JSON.parse(data);
    if (msg.result?.result?.value === 'injected') {
      console.log('Zap applied!');
      ws.close();
    }
  });
}

inject();

Run: node /tmp/zap.js (install ws first if needed: npm install -g ws)

Discovering the DOM Structure

Before writing CSS, inspect the app's live DOM:

// Dump top-level structure
expression: `document.body.innerHTML.substring(0, 3000)`

// Find sidebar/panel elements
expression: `
  Array.from(document.querySelectorAll('[class*="sidebar"],[class*="panel"],[class*="nav"]'))
    .map(el => el.tagName + ' ' + el.className.substring(0, 80))
    .slice(0, 20).join('\\n')
`

Common Customizations

Move left sidebar to the right

.max-h-full.min-h-0.w-full.flex-1:has(> aside) {
  flex-direction: row-reverse;
}

Hide an element

[data-testid="element-name"] { display: none !important; }

Change fonts globally

* { font-family: "Your Font", sans-serif !important; }

Custom theme colors

:root {
  --background-color: #1a1a2e;
  --text-color: #eee;
}

Inject a floating widget

const div = document.createElement('div');
div.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:9999;background:#000;color:#fff;padding:10px;border-radius:8px;';
div.textContent = 'Hello from Zap!';
document.body.appendChild(div);

Persisting Patches

CDP injection is in-memory only — it disappears on app restart or update. If the user asks to keep a change saved, follow these instructions based on their scenario:

Scenario 1: User wants changes to survive restarts

Save the patch as a named script file and create a shell alias that auto-relaunches the app with the debug port open and re-injects the patch every time.

Step 1 — Save the patch as a script (e.g. ~/patches/appname.js):

const WebSocket = require('ws');
const http = require('http');

const CSS = `/* the user's CSS here */`;

function getTargets() {
  return new Promise((resolve, reject) => {
    http.get('http://localhost:9222/json', (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => resolve(JSON.parse(data)));
    }).on('error', reject);
  });
}

async function inject() {
  const targets = await getTargets();
  const page = targets.find(t => t.type === 'page');
  if (!page) return console.error('App not found on debug port');
  const ws = new WebSocket(page.webSocketDebuggerUrl);
  ws.on('open', () => {
    ws.send(JSON.stringify({
      id: 1,
      method: 'Runtime.evaluate',
      params: {
        expression: `
          (function() {
            const el = document.getElementById('__zap__');
            if (el) el.remove();
            const s = document.createElement('style');
            s.id = '__zap__';
            s.textContent = ${JSON.stringify(CSS)};
            document.head.appendChild(s);
            return 'injected';
          })()
        `
      }
    }));
  });
  ws.on('message', (data) => {
    const msg = JSON.parse(data);
    if (msg.result?.result?.value === 'injected') { console.log('Patch applied!'); ws.close(); }
  });
}

inject();

Step 2 — Create a shell alias so every time the user opens the app it auto-patches:

# Add to ~/.zshrc (replace AppName and patch path)
alias appname='pkill -x "AppName" 2>/dev/null; sleep 0.5; open -a "AppName" --args --remote-debugging-port=9222; sleep 3; node ~/patches/appname.js'

Then run source ~/.zshrc to activate. From now on the user just types appname in their terminal and it opens already patched.

Scenario 2: User wants changes to survive app updates

App updates don't affect the patch script — the CSS/JS lives in the patch file on disk, not inside the app. As long as:

  • The patch script is saved (Scenario 1 above)
  • The alias is set up

...updates are transparent. The app updates, the user reopens via alias, patches re-inject automatically.

The only time an update breaks a patch is if the app changes its DOM structure or class names — in that case, re-inspect the DOM and update the CSS selectors in the patch file.

App-Specific Notes

AppProcess NameNotes
SlackSlackLegacy CSS class names, easy to target
CodexCodexTailwind utility classes, use :has() selectors
VS CodeElectronHas built-in DevTools; debug port may conflict on 9222 — try 9223
DiscordDiscordMay need --ignore-gpu-blacklist flag too
NotionNotionReact-based, inspect DOM first
CursorCursorVS Code fork, same notes as VS Code

Guidelines

  • Always inspect the DOM before writing CSS — class names vary per app
  • Use __zap__ as the style tag ID so re-injections replace cleanly
  • Prefer CSS over JS for visual changes — safer and easier to revert
  • Test with a tiny change first (e.g., body { outline: 2px solid red }) to confirm injection works
  • The debug port (9222) is local-only — no security exposure

Examples

  • "Move Codex sidebar to the right" → flex-direction row-reverse on the layout container
  • "Hide Slack's sidebar" → aside { display: none }
  • "Make VS Code font 16px" → * { font-size: 16px !important }
  • "Add dark overlay to Notion" → inject a fixed div with rgba background
  • "Move Discord member list to the left" → reorder flex children with order property

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.