{
  "id": "chrome-mv3-service-worker-guard",
  "type": "loadout",
  "title": "Chrome MV3 service worker guard for MV2 code",
  "summary": "Stop agents writing MV2 background-page habits into MV3 extensions, such as globals as state and long timers, and prove state survives a service worker restart.",
  "description": "A Manifest V3 extension's background script is a service worker. Chrome stops it after about 30 seconds without events and starts it again for the next one. Code written for an MV2 background page assumes it runs forever, so it works while you click around and breaks later: counters reset, timers never fire, and document, localStorage and XMLHttpRequest don't exist. Agents still write chrome.browserAction, tabs.executeScript, getBackgroundPage and blocking webRequest too.\n\nThis loadout pairs a Cursor rule and AGENTS.md (what a service worker isn't, and the API replacements) with a skill that checks the work. A standard-library Python scanner reports MV2 APIs and things a worker can't do as errors, and lifecycle risks (globals, long timers, nested listeners, keep-alive hacks) as warnings. A Playwright test then stops the worker through the DevTools protocol and uses the extension again. The verifier runs it in Playwright's Chromium: storage-backed state survives the restart, and the same extension with a global counter resets to 0. It works alongside the Chrome extension (Manifest V3) loadout, which covers manifests, permissions and store review.\n",
  "highlights": [
    "Scanner flags MV2 APIs and DOM, localStorage or XHR in the worker",
    "Warns on globals as state, long timers and listeners below the top level",
    "Playwright test stops the service worker through CDP, then retries",
    "Shows a global counter resetting to 0 after a real worker restart",
    "API table from browserAction, executeScript and blocking webRequest to MV3"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "SetupBay",
    "url": "https://setupbay.com"
  },
  "credits": [
    {
      "title": "Chrome docs: migrate to a service worker",
      "url": "https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers"
    },
    {
      "title": "Chrome docs: the extension service worker lifecycle",
      "url": "https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle"
    },
    {
      "title": "Chrome docs: replace blocking web request listeners",
      "url": "https://developer.chrome.com/docs/extensions/develop/migrate/blocking-web-requests"
    },
    {
      "title": "Playwright docs: Chrome extensions",
      "url": "https://playwright.dev/docs/chrome-extensions"
    },
    {
      "title": "Chrome DevTools Protocol: ServiceWorker domain",
      "url": "https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker/"
    },
    {
      "title": "@unblckhq: ask the agent for MV3 and declarativeNetRequest, not blocking webRequest",
      "url": "https://x.com/unblckhq/status/2114719662881833243"
    }
  ],
  "categories": [
    "browser-extension"
  ],
  "stacks": [
    "chrome",
    "javascript",
    "playwright"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Service worker facts, MV2 to MV3 replacements and the done checklist.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "antigravity-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/AGENTS.md",
      "bytes": 2294,
      "lang": "markdown",
      "content": "# AGENTS.md: Chrome extension, Manifest V3 service worker guard\n\nThis extension is **Manifest V3**. Its background script is a **service worker**, not a background page. Chrome\nstarts it for an event and stops it after about 30 seconds without events. Manifest V2 no longer runs in\nChrome, but much of the extension code you learnt from is MV2.\n\n## What a service worker is not\n- It has no DOM: no `document`, `window`, `localStorage`, `sessionStorage`, `XMLHttpRequest` or `alert()`. Use\n  `chrome.storage`, `fetch`, and an offscreen document (`chrome.offscreen`) for work that needs a DOM.\n- It doesn't keep globals. A `let count = 0` at the top of the worker goes back to `0` every time Chrome stops and\n  restarts it. Keep state in `chrome.storage.session` (in memory, cleared when the browser closes) or\n  `chrome.storage.local`.\n- It doesn't keep timers. Use `chrome.alarms` for anything that should happen later than a few seconds from now.\n- Chrome wakes it only for listeners it already knows about. Register every `chrome.*.on*.addListener` call\n  synchronously at the top level of the worker, not inside a callback, a function or after an `await`.\n- There is no background page to reach into: `chrome.extension.getBackgroundPage()` and\n  `chrome.runtime.getBackgroundPage()` don't exist in MV3. Send messages.\n\n## API replacements\n- `chrome.browserAction` / `chrome.pageAction` → `chrome.action`\n- `chrome.tabs.executeScript` / `insertCSS` → `chrome.scripting.executeScript` / `insertCSS` (permission `scripting`)\n- `chrome.extension.sendRequest` / `onRequest` / `getURL` → `chrome.runtime.sendMessage` / `onMessage` / `getURL`\n- `chrome.tabs.getSelected` → `chrome.tabs.query({ active: true, currentWindow: true })`\n- blocking `webRequest` → `chrome.declarativeNetRequest` rules\n- No `eval`, `new Function` or remotely hosted scripts: everything runs from the package.\n\n## Before you say \"done\"\n1. Run the `mv3-worker-guard` scanner on the extension source and `manifest.json`. It must report `0 error(s)`; any\n   warning you keep needs a `// guard: ok <reason>` comment.\n2. Load the extension, stop its service worker (chrome://serviceworker-internals, or the CDP command\n   `ServiceWorker.stopAllWorkers` in a test), then use the feature again. It must behave the same.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/CLAUDE.md",
      "bytes": 398,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n\n- Skills from this loadout live in `.claude/skills/`: `mv3-worker-guard`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From SetupBay \"Chrome MV3 service worker guard for MV2 code\" v1.0.0 — https://setupbay.com/loadouts/chrome-mv3-service-worker-guard/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/GEMINI.md",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/GEMINI.md",
      "bytes": 399,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n\n- Skills from this loadout live in `.agents/skills/`: `mv3-worker-guard`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From SetupBay \"Chrome MV3 service worker guard for MV2 code\" v1.0.0 — https://setupbay.com/loadouts/chrome-mv3-service-worker-guard/ -->\n"
    },
    {
      "path": ".cursor/rules/mv3-service-worker.mdc",
      "role": "rules",
      "description": "Never/always table for background scripts and manifest.json.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": ".cursor/rules/mv3-service-worker.mdc",
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/.cursor/rules/mv3-service-worker.mdc",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/.cursor/rules/mv3-service-worker.mdc",
      "bytes": 1281,
      "lang": "markdown",
      "content": "---\ndescription: Manifest V3 service worker rules; no MV2 background-page code\nglobs: \"**/sw.js,**/sw.ts,**/service-worker.*,**/service_worker.*,**/background.*,**/*-worker.*,**/manifest.json\"\nalwaysApply: false\n---\n\nThe background script is an MV3 service worker. Chrome stops it after about 30 seconds idle.\n\n| Never | Always |\n|---|---|\n| `let count = 0` as state | `chrome.storage.session` / `chrome.storage.local` |\n| `setTimeout(fn, 60 * 60 * 1000)` | `chrome.alarms.create(\"name\", { periodInMinutes: 60 })` |\n| `addListener` inside a callback or after `await` | every `addListener` at the top level, synchronously |\n| `document`, `window`, `localStorage`, `XMLHttpRequest` | `fetch`, `chrome.storage`, `chrome.offscreen` for DOM work |\n| `chrome.browserAction`, `chrome.pageAction` | `chrome.action` |\n| `chrome.tabs.executeScript` | `chrome.scripting.executeScript` |\n| `chrome.extension.getBackgroundPage()` | `chrome.runtime.sendMessage` |\n| `webRequest` with `[\"blocking\"]` | `chrome.declarativeNetRequest` |\n| `eval`, `new Function`, remote `<script src>` | code shipped in the package |\n\n- An async `onMessage` reply needs `return true` from the listener.\n- After editing, run the `mv3-worker-guard` skill's scanner, then restart the worker and use the feature again.\n"
    },
    {
      "path": "skills/mv3-worker-guard/SKILL.md",
      "role": "skill",
      "description": "Scan for MV2 APIs and lifecycle bugs, then restart the worker and check the state survived.",
      "skill": "mv3-worker-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/mv3-worker-guard/SKILL.md",
        "codex": ".agents/skills/mv3-worker-guard/SKILL.md",
        "cursor": ".agents/skills/mv3-worker-guard/SKILL.md",
        "gemini-cli": ".agents/skills/mv3-worker-guard/SKILL.md",
        "antigravity-cli": ".agents/skills/mv3-worker-guard/SKILL.md",
        "copilot": ".agents/skills/mv3-worker-guard/SKILL.md"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/skills/mv3-worker-guard/SKILL.md",
      "bytes": 2788,
      "lang": "markdown",
      "content": "---\nname: mv3-worker-guard\ndescription: Check a Chrome extension's Manifest V3 service worker and scripts for MV2 APIs (browserAction, tabs.executeScript, getBackgroundPage, blocking webRequest) and for code that breaks when Chrome stops the worker (globals as state, long timers, nested listeners, DOM and localStorage), then prove state survives a worker restart. Use after writing or editing extension background code, when a feature works at first and then stops, or when porting an MV2 extension.\n---\n\n# MV3 service worker guard\n\nChrome stops an MV3 service worker after about 30 seconds without events and starts it again for the next one.\nCode written for an MV2 background page assumes it runs forever, so it works while you click around and breaks\nlater: counts reset to zero, timers never fire, and `document` or `localStorage` throw. This skill finds those\npatterns and the removed MV2 APIs, then checks the behaviour by restarting the worker.\n\n## 1. Scan\n```bash\npython3 .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py src manifest.json\n```\n(Use the path the skill was installed to: `.claude/skills/...` for Claude Code, `.agents/skills/...` for the others.)\n\n- The worker rules apply to files named like a background script (`sw.js`, `service-worker.js`, `background.js`,\n  `*-worker.js` and the `.ts` versions). Other files get the API rules only.\n- `error` lines are MV2 APIs or things a worker can't do. Fix each with the replacement on the line; see\n  `references/mv2-to-mv3.md`.\n- `warn` lines are lifecycle risks: a mutable global, a long timer, a listener registered below the top level,\n  a keep-alive hack. Fix them, or add `// guard: ok <reason>` (for example, a constant cache that's fine to rebuild).\n\n## 2. Restart the worker and use the feature again\nThe bug only shows after a restart, so test that directly. With Playwright and a persistent context that loads the\nunpacked extension, stop every service worker through CDP and send the next message:\n```js\nconst cdp = await context.newCDPSession(page);   // page: an extension page such as popup.html\nawait cdp.send(\"ServiceWorker.enable\");\nconst stopped = new Promise((resolve) => cdp.on(\"ServiceWorker.workerVersionUpdated\", (e) => {\n  if (e.versions.some((v) => v.runningStatus === \"stopped\")) resolve();\n}));\nawait cdp.send(\"ServiceWorker.stopAllWorkers\");\nawait stopped;\n// now sendMessage again and assert the state is unchanged\n```\n`references/worker-restart.spec.mjs` is a complete test that does this. By hand, use chrome://serviceworker-internals\nand click **Stop**.\n\n## 3. Report\nSay which MV2 APIs you replaced, where state now lives (`storage.session` or `storage.local`), which timers became\nalarms, and that the scanner was clean and the feature still worked after a worker restart.\n"
    },
    {
      "path": "skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
      "role": "reference",
      "description": "Read-only scanner for MV2 APIs, worker-only problems and lifecycle risks.",
      "skill": "mv3-worker-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
        "codex": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
        "cursor": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
        "gemini-cli": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
        "antigravity-cli": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
        "copilot": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
      "bytes": 8550,
      "lang": "markdown",
      "content": "#!/usr/bin/env python3\n\"\"\"Find Manifest V2 extension APIs, and code that breaks in an MV3 service worker, in a Chrome extension's source.\n\nUsage: python3 mv3_worker_guard.py [PATH ...]        (default: the current folder)\n       python3 mv3_worker_guard.py --list            (print every rule)\n\nA \"guard: ok\" comment on a line silences its warnings (never its errors).\nPrints one line per finding:  path:line: level [rule] what is wrong -> what to use\nExit 0 when nothing at \"error\" level is found, 1 when something is, 2 on bad usage.\nStandard library only. Read-only: it never edits a file and makes no network calls.\n\"\"\"\nimport fnmatch\nimport os\nimport re\nimport sys\n\n# (rule id, level, regex, message, file patterns). Patterns run against each line with comments removed.\nRULES = [\n    ('browser-action', 'error', '\\\\bchrome\\\\.(browserAction|pageAction)\\\\b',\n     'chrome.browserAction/pageAction were removed in MV3 -> chrome.action', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('tabs-execute-script', 'error', '\\\\bchrome\\\\.tabs\\\\.(executeScript|insertCSS|removeCSS)\\\\(',\n     'tabs.executeScript/insertCSS/removeCSS were removed in MV3 -> chrome.scripting.executeScript/insertCSS/removeCSS (permission scripting)', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('extension-legacy', 'error', '\\\\bchrome\\\\.extension\\\\.(getBackgroundPage|getViews|getURL|sendRequest|onRequest|connect|sendMessage|onMessage|getExtensionTabs)\\\\b',\n     'chrome.extension.* messaging and page access are gone in MV3 -> chrome.runtime.sendMessage/onMessage/getURL; there is no background page', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('runtime-background-page', 'error', '\\\\bchrome\\\\.runtime\\\\.getBackgroundPage\\\\(',\n     'MV3 has no background page -> message the service worker with chrome.runtime.sendMessage', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('tabs-legacy', 'error', '\\\\bchrome\\\\.tabs\\\\.(getSelected|getAllInWindow|sendRequest|onActiveChanged|onSelectionChanged)\\\\b',\n     'removed tabs API -> chrome.tabs.query({ active: true, currentWindow: true }) / tabs.sendMessage / tabs.onActivated', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('webrequest-blocking', 'error', '[\\\\\"\\']blocking[\\\\\"\\']',\n     'blocking webRequest is not available to MV3 extensions (except force-installed) -> chrome.declarativeNetRequest rules', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('remote-code', 'error', '\\\\beval\\\\(|\\\\bnew Function\\\\(|setTimeout\\\\(\\\\s*[\\\\\"\\'`]',\n     'MV3 forbids running strings as code -> ship the code in the package', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('remote-script-tag', 'error', '\\\\.src\\\\s*=\\\\s*[\\\\\"\\'`]https?://|import\\\\(\\\\s*[\\\\\"\\'`]https?://',\n     'MV3 forbids remotely hosted code -> bundle the script', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),\n    ('sw-dom', 'error', '\\\\b(document|window)\\\\.|\\\\blocalStorage\\\\b|\\\\bsessionStorage\\\\b|\\\\bXMLHttpRequest\\\\b|\\\\balert\\\\(',\n     'service workers have no DOM, window, localStorage or XMLHttpRequest -> chrome.storage, fetch, and an offscreen document for DOM work', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),\n    ('sw-global-state', 'warn', '^(let|var)\\\\s+\\\\w+\\\\s*=',\n     'a mutable global in the service worker is lost when Chrome stops it (about 30 s idle) -> keep state in chrome.storage.session or chrome.storage.local', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),\n    ('sw-long-timer', 'warn', '\\\\bset(Timeout|Interval)\\\\([^;]*,\\\\s*(\\\\d{5,}|\\\\d+\\\\s*\\\\*\\\\s*60\\\\b)|^\\\\s*\\\\}\\\\s*,\\\\s*(\\\\d{5,}|\\\\d+\\\\s*\\\\*\\\\s*60\\\\b)',\n     'timers die with the worker -> chrome.alarms for anything longer than a few seconds', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),\n    ('sw-nested-listener', 'warn', '^\\\\s+chrome\\\\.[\\\\w.]+\\\\.on[A-Z]\\\\w*\\\\.addListener\\\\(',\n     'register event listeners synchronously at the top level of the worker, not inside a function or after an await, so Chrome can wake the worker for them', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),\n    ('sw-keepalive', 'warn', 'keep-?alive|setInterval\\\\([^,]*chrome\\\\.runtime\\\\.getPlatformInfo',\n     'keep-alive hacks fight the worker lifecycle -> store state and let the worker stop', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),\n    ('mf-version-2', 'error', '\\\\\"manifest_version\\\\\"\\\\s*:\\\\s*2\\\\b',\n     'Manifest V2 no longer runs in Chrome -> \"manifest_version\": 3', ['manifest.json']),\n    ('mf-browser-action', 'error', '\\\\\"(browser_action|page_action)\\\\\"\\\\s*:',\n     'browser_action/page_action -> \"action\"', ['manifest.json']),\n    ('mf-background-page', 'error', '\\\\\"(scripts|page|persistent)\\\\\"\\\\s*:',\n     'MV3 background is a service worker -> \"background\": { \"service_worker\": \"sw.js\", \"type\": \"module\" }', ['manifest.json']),\n    ('mf-webrequest-blocking', 'error', '\\\\\"webRequestBlocking\\\\\"',\n     'webRequestBlocking is MV2 only -> declarativeNetRequest', ['manifest.json']),\n]\nFILES = ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx', 'manifest.json']\nCOMMENT = '//'\nSKIP_DIRS = {\".git\", \".venv\", \"venv\", \"node_modules\", \"__pycache__\", \".agents\", \".claude\", \".cursor\", \"dist\", \"build\", \"target\", \".godot\"}\nCOMPILED = [(rid, lvl, re.compile(rx), msg, globs) for rid, lvl, rx, msg, globs in RULES]\n\n\ndef strip_comment(line):\n    \"\"\"Drop a trailing comment, leaving string literals alone.\"\"\"\n    q = None\n    i = 0\n    while i < len(line):\n        c = line[i]\n        if q:\n            if c == \"\\\\\":\n                i += 2\n                continue\n            if c == q:\n                q = None\n        elif c in \"\\\"'`\":\n            q = c\n        elif line.startswith(COMMENT, i):\n            return line[:i]\n        i += 1\n    return line\n\n\ndef wanted(name):\n    return any(fnmatch.fnmatch(name, g) for g in FILES)\n\n\ndef walk(paths):\n    for p in paths:\n        if os.path.isfile(p):\n            yield p\n            continue\n        for root, dirs, files in os.walk(p):\n            dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS)\n            for f in sorted(files):\n                if wanted(f):\n                    yield os.path.join(root, f)\n\n\ndef scan(path):\n    out = []\n    name = os.path.basename(path)\n    try:\n        with open(path, encoding=\"utf-8\", errors=\"replace\") as fh:\n            lines = fh.read().splitlines()\n    except OSError as e:\n        return [(path, 0, \"error\", \"unreadable\", str(e))]\n    for n, raw in enumerate(lines, 1):\n        code = strip_comment(raw)\n        if not code.strip():\n            continue\n        accepted = \"guard: ok\" in raw  # a person accepted the warnings on this line\n        for rid, lvl, rx, msg, globs in COMPILED:\n            if accepted and lvl == \"warn\":\n                continue\n            if globs and not any(fnmatch.fnmatch(name, g) for g in globs):\n                continue\n            if rx.search(code):\n                out.append((path, n, lvl, rid, msg))\n    return out\n\n\ndef main(argv):\n    if \"--list\" in argv:\n        for rid, lvl, _, msg, globs in RULES:\n            print(f\"{lvl:5} [{rid}] {msg}\" + (f\"  (only {', '.join(globs)})\" if globs else \"\"))\n        return 0\n    if any(a.startswith(\"-\") for a in argv):\n        print(__doc__)\n        return 2\n    paths = argv or [\".\"]\n    for p in paths:\n        if not os.path.exists(p):\n            print(f\"no such path: {p}\", file=sys.stderr)\n            return 2\n    files = list(walk(paths))\n    findings = []\n    for f in files:\n        findings.extend(scan(f))\n    for path, n, lvl, rid, msg in findings:\n        print(f\"{os.path.relpath(path)}:{n}: {lvl} [{rid}] {msg}\")\n    errors = sum(1 for x in findings if x[2] == \"error\")\n    warns = sum(1 for x in findings if x[2] == \"warn\")\n    print(f\"mv3_worker_guard: {len(files)} file(s), {errors} error(s), {warns} warning(s)\")\n    return 1 if errors else 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n"
    },
    {
      "path": "skills/mv3-worker-guard/references/mv2-to-mv3.md",
      "role": "reference",
      "description": "MV2 to MV3 table and the service worker lifecycle facts.",
      "skill": "mv3-worker-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/mv3-worker-guard/references/mv2-to-mv3.md",
        "codex": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
        "cursor": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
        "gemini-cli": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
        "antigravity-cli": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
        "copilot": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/skills/mv3-worker-guard/references/mv2-to-mv3.md",
      "bytes": 2189,
      "lang": "markdown",
      "content": "# MV2 background page to MV3 service worker\n\nFrom the Chrome docs: \"Migrate to a service worker\" and \"Replace blocking web request listeners\"\n(https://developer.chrome.com/docs/extensions/develop/migrate). Checked in Playwright's Chromium (@playwright/test 1.64.1).\n\n| MV2 | MV3 |\n|---|---|\n| `\"background\": { \"scripts\": [...], \"persistent\": false }` | `\"background\": { \"service_worker\": \"sw.js\", \"type\": \"module\" }` |\n| `\"browser_action\"` / `\"page_action\"` | `\"action\"` |\n| `chrome.browserAction.*` / `chrome.pageAction.*` | `chrome.action.*` |\n| `chrome.tabs.executeScript(tabId, { code })` | `chrome.scripting.executeScript({ target: { tabId }, func })` |\n| `chrome.tabs.insertCSS` / `removeCSS` | `chrome.scripting.insertCSS` / `removeCSS` |\n| `chrome.extension.getBackgroundPage()` / `getViews()` | message the worker: `chrome.runtime.sendMessage` |\n| `chrome.extension.sendRequest` / `onRequest` | `chrome.runtime.sendMessage` / `onMessage` |\n| `chrome.extension.getURL` | `chrome.runtime.getURL` |\n| `chrome.tabs.getSelected(null, cb)` | `chrome.tabs.query({ active: true, currentWindow: true })` |\n| `webRequest.onBeforeRequest` with `[\"blocking\"]` + `webRequestBlocking` | `declarativeNetRequest` rules (static `rule_resources` or `updateDynamicRules`) |\n| `localStorage` in the background | `chrome.storage.local` (or `storage.session` for in-memory state) |\n| globals as state | `chrome.storage.session` |\n| `setTimeout` / `setInterval` for minutes or hours | `chrome.alarms` |\n| `XMLHttpRequest` | `fetch` |\n| DOM work in the background (`DOMParser`, audio, clipboard) | `chrome.offscreen.createDocument({ url, reasons, justification })` |\n| `eval`, `new Function`, remote scripts | not allowed; ship the code in the package |\n\n## Service worker lifecycle facts\n- Chrome stops the worker after 30 seconds without events; a single request taking longer than 5 minutes is also stopped.\n- Events reset the idle timer, and extension API calls do too.\n- Register listeners at the top level, synchronously, so they exist when Chrome restarts the worker for an event.\n- `chrome.storage.session` keeps data in memory for the browser session (about 10 MB) and survives worker restarts.\n"
    },
    {
      "path": "skills/mv3-worker-guard/references/worker-restart.spec.mjs",
      "role": "reference",
      "description": "Playwright test that stops the service worker through CDP and checks state survives.",
      "skill": "mv3-worker-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
        "codex": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
        "cursor": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
        "gemini-cli": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
        "antigravity-cli": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
        "copilot": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
      "bytes": 3644,
      "lang": "markdown",
      "content": "// Stop the MV3 service worker the way Chrome does when it's idle, then talk to it again.\nimport { test as base, chromium, expect } from \"@playwright/test\";\nimport path from \"node:path\";\n\nconst withExtension = (dir) => {\n  const EXT = path.resolve(dir);\n  return base.extend({\n    context: async ({}, use) => {\n      const context = await chromium.launchPersistentContext(\"\", {\n        channel: \"chromium\",\n        args: [`--disable-extensions-except=${EXT}`, `--load-extension=${EXT}`],\n      });\n      await use(context);\n      await context.close();\n    },\n    serviceWorker: async ({ context }, use) => {\n      let [worker] = context.serviceWorkers();\n      if (!worker) worker = await context.waitForEvent(\"serviceworker\");\n      await use(worker);\n    },\n    extensionId: async ({ serviceWorker }, use) => use(serviceWorker.url().split(\"/\")[2]),\n  });\n};\n\nasync function countAcrossRestart(context, extensionId) {\n  const page = await context.newPage();\n  await page.goto(`chrome-extension://${extensionId}/popup.html`);\n  const send = (type) => page.evaluate((t) => chrome.runtime.sendMessage({ type: t }), type);\n  await send(\"count\");\n  await send(\"count\");\n  const before = (await send(\"get\")).count;\n  const cdp = await context.newCDPSession(page);\n  await cdp.send(\"ServiceWorker.enable\");\n  const stopped = new Promise((resolve) =>\n    cdp.on(\"ServiceWorker.workerVersionUpdated\", (e) => {\n      if (e.versions.some((v) => v.runningStatus === \"stopped\")) resolve();\n    }),\n  );\n  await cdp.send(\"ServiceWorker.stopAllWorkers\");\n  await stopped;\n  const after = (await send(\"get\")).count;\n  return { before, after };\n}\n\nconst good = withExtension(\"extension\");\nconst globals = withExtension(\"globals-extension\");\n\ngood(\"the MV3 worker has no DOM, localStorage, XMLHttpRequest or MV2 APIs\", async ({ serviceWorker }) => {\n  const seen = await serviceWorker.evaluate(() => ({\n    document: typeof document,\n    localStorage: typeof localStorage,\n    XMLHttpRequest: typeof XMLHttpRequest,\n    browserAction: typeof chrome.browserAction,\n    executeScript: typeof chrome.tabs.executeScript,\n    getBackgroundPage: typeof chrome.extension?.getBackgroundPage,\n    action: typeof chrome.action,\n    alarms: typeof chrome.alarms,\n  }));\n  console.log(`worker globals ${JSON.stringify(seen)}`);\n  expect(seen).toEqual({\n    document: \"undefined\", localStorage: \"undefined\", XMLHttpRequest: \"undefined\",\n    browserAction: \"undefined\", executeScript: \"undefined\", getBackgroundPage: \"undefined\",\n    action: \"object\", alarms: \"object\",\n  });\n});\n\ngood(\"state in chrome.storage.session survives a worker restart\", async ({ context, extensionId }) => {\n  const { before, after } = await countAcrossRestart(context, extensionId);\n  console.log(`storage.session before=${before} after=${after}`);\n  expect(before).toBe(2);\n  expect(after).toBe(2);\n});\n\ngood(\"the alarm is registered with chrome.alarms, not a timer\", async ({ serviceWorker }) => {\n  // onInstalled creates the alarm asynchronously, so wait for it rather than racing it.\n  await expect.poll(() => serviceWorker.evaluate(async () => Boolean(await chrome.alarms.get(\"tidy\")))).toBe(true);\n  const alarm = await serviceWorker.evaluate(() => chrome.alarms.get(\"tidy\"));\n  console.log(`alarm ${alarm.name} every ${alarm.periodInMinutes} min`);\n  expect(alarm.periodInMinutes).toBe(30);\n});\n\nglobals(\"state in a global is lost when Chrome stops the worker\", async ({ context, extensionId }) => {\n  const { before, after } = await countAcrossRestart(context, extensionId);\n  console.log(`global before=${before} after=${after}`);\n  expect(before).toBe(2);\n  expect(after).toBe(0);\n});\n"
    },
    {
      "path": "skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
      "role": "reference",
      "description": "MV2 background page for checking the guard.",
      "skill": "mv3-worker-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
        "codex": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
        "cursor": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
        "gemini-cli": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
        "antigravity-cli": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
        "copilot": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
      "bytes": 1018,
      "lang": "markdown",
      "content": "// MV2 background page for a tab counter, used to check the guard.\nvar count = 0;\nvar blocked = [\"ads.example.com\"];\n\nchrome.browserAction.onClicked.addListener(function (tab) {\n  count++;\n  chrome.browserAction.setBadgeText({ text: String(count) });\n  chrome.tabs.executeScript(tab.id, { code: \"document.body.style.border = '4px solid red'\" });\n  localStorage.setItem(\"count\", count);\n});\n\nchrome.webRequest.onBeforeRequest.addListener(\n  function (details) {\n    return { cancel: blocked.some((d) => details.url.includes(d)) };\n  },\n  { urls: [\"<all_urls>\"] },\n  [\"blocking\"]\n);\n\nchrome.storage.local.get(\"settings\", function (s) {\n  chrome.runtime.onMessage.addListener(function (msg, sender, reply) {\n    reply({ count: count, settings: s.settings });\n  });\n});\n\nsetInterval(function () {\n  var xhr = new XMLHttpRequest();\n  xhr.open(\"GET\", \"https://example.com/rules.json\");\n  xhr.send();\n}, 60 * 60 * 1000);\n\nchrome.tabs.getSelected(null, function (tab) {\n  console.log(tab.url, window.navigator.userAgent);\n});\n"
    },
    {
      "path": "skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
      "role": "reference",
      "description": "MV2 popup that reaches into the background page.",
      "skill": "mv3-worker-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
        "codex": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
        "cursor": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
        "gemini-cli": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
        "antigravity-cli": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
        "copilot": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
      "bytes": 148,
      "lang": "markdown",
      "content": "// MV2 popup reaching into the background page.\nvar bg = chrome.extension.getBackgroundPage();\ndocument.getElementById(\"n\").textContent = bg.count;\n"
    },
    {
      "path": "skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
      "role": "reference",
      "description": "MV2 manifest for checking the guard.",
      "skill": "mv3-worker-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
        "codex": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
        "cursor": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
        "gemini-cli": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
        "antigravity-cli": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
        "copilot": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt"
      },
      "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
      "raw_latest_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/latest/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
      "bytes": 289,
      "lang": "markdown",
      "content": "{\n  \"manifest_version\": 2,\n  \"name\": \"Tab counter\",\n  \"version\": \"0.9.0\",\n  \"browser_action\": { \"default_popup\": \"popup.html\" },\n  \"background\": { \"scripts\": [\"background.js\"], \"persistent\": false },\n  \"permissions\": [\"storage\", \"tabs\", \"webRequest\", \"webRequestBlocking\", \"<all_urls>\"]\n}\n"
    }
  ],
  "requires": {
    "tools": [
      "node",
      "python3"
    ],
    "env": [],
    "mcp": []
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "antigravity-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/2",
    "verdict": "clean",
    "network": true,
    "scripts": [
      "mv3_worker_guard.py"
    ],
    "notes": "The scanner only reads source files and manifest.json and prints findings; it never edits files and makes no network calls. Installing Playwright and its Chromium with npm downloads them."
  },
  "featured": false,
  "deprecated": false,
  "last_checked": "2026-10-29",
  "published": "2026-10-29",
  "updated": "2026-10-29",
  "compatibility": {
    "claude-code": "native",
    "codex": "native",
    "cursor": "native",
    "gemini-cli": "native",
    "antigravity-cli": "native",
    "copilot": "native"
  },
  "counts": {
    "rules": 2,
    "skills": 1,
    "mcp": 0
  },
  "url": "https://setupbay.com/loadouts/chrome-mv3-service-worker-guard/",
  "api_url": "https://setupbay.com/api/loadouts/chrome-mv3-service-worker-guard.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-29T12:58:05Z",
    "commit": "cc18d7ef3e9c98d99f00c17e2ef9a0565a3f9597",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 9,
      "fail": 0,
      "skip": 0,
      "not_applicable": 1
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 11 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 11 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 6 agents; layouts re-discovered",
        "details": [
          "Claude Code: 9 files, 1 skill in .claude/skills/",
          "Codex CLI: 8 files, 1 skill in .agents/skills/",
          "Cursor: 9 files, 1 skill in .agents/skills/, 1 rule",
          "Gemini CLI: 9 files, 1 skill in .agents/skills/",
          "Antigravity CLI: 8 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 8 files, 1 skill in .agents/skills/"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "n/a",
        "summary": "No MCP servers in this loadout",
        "details": []
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell, hidden text or covert instructions",
        "details": [
          "Scanner intake-precheck/2: verdict clean; network no (listing says yes)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "2 references between files resolve for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "11 files, 22.5 KB total; largest skills/mv3-worker-guard/scripts/mv3_worker_guard.py 8.3 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by SetupBay; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "mv3_worker_guard.py passes an MV3 counter extension whose state lives in chrome.storage.session, and flags every MV2 line in the skill's sample: 14 removed APIs and things a worker can't do as errors, plus globals, a long timer and a nested listener as warnings. In Playwright's Chromium (@playwright",
        "details": [
          "Applied 18 loadout files at every agent's paths",
          "✓ scanner: the MV3 extension is clean: bash -c python3 .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py extension; echo \"exit=$?\"",
          "✓ scanner: the global-state variant gets a warning: bash -c python3 .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py globals-extension; echo \"exit=$?\"",
          "✓ scanner: the MV2 sample is flagged line by line: bash -c R=.claude/skills/mv3-worker-guard/references/mv2-sample; mkdir -p mv2 && cp $R/background.js.txt mv2/background.js && cp $R/popup.js.txt mv2/popup.js && cp $R/manifest.json.txt mv2/manifest.json && python3 .claude/skills/mv3-worker-guard/scripts/mv3_worker_guard.py mv2; echo \"exit=$?\"",
          "✓ install @playwright/test 1.64.1 (exact pin): npm ci --no-audit --no-fund",
          "✓ download Playwright's Chromium: npx playwright install chromium",
          "✓ place the skill's restart test: bash -c mkdir -p e2e && cp .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs e2e/ && echo placed",
          "✓ load both extensions headless, stop the worker, and use them again: npx playwright test",
          "Covers: The scanner's MV2 API, worker and manifest rules against a real MV2 background page, popup and manifest, and a clean MV3 extension",
          "Covers: A real service worker restart (CDP ServiceWorker.stopAllWorkers): chrome.storage.session state survives and global state is lost",
          "Covers: What the MV3 worker actually lacks (DOM, localStorage, XMLHttpRequest, browserAction, tabs.executeScript, getBackgroundPage), read from the loaded worker",
          "Covers: Not covered: the 30-second idle timeout itself (the test stops the worker directly) and listeners registered after an await, which this Chromium still delivered in our run, so the scanner only warns"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/mv3-worker-guard/SKILL.md",
          "target": ".claude/skills/mv3-worker-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "target": ".claude/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "target": ".claude/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "target": ".claude/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "target": ".claude/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "target": ".claude/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "target": ".claude/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/chrome-mv3-service-worker-guard/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/mv3-worker-guard/SKILL.md",
          "target": ".agents/skills/mv3-worker-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "target": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "target": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/chrome-mv3-service-worker-guard/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": ".cursor/rules/mv3-service-worker.mdc",
          "target": ".cursor/rules/mv3-service-worker.mdc",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/.cursor/rules/mv3-service-worker.mdc",
          "role": "rules"
        },
        {
          "path": "skills/mv3-worker-guard/SKILL.md",
          "target": ".agents/skills/mv3-worker-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "target": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "target": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/chrome-mv3-service-worker-guard/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/mv3-worker-guard/SKILL.md",
          "target": ".agents/skills/mv3-worker-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "target": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "target": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/chrome-mv3-service-worker-guard/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/mv3-worker-guard/SKILL.md",
          "target": ".agents/skills/mv3-worker-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "target": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "target": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/chrome-mv3-service-worker-guard/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/mv3-worker-guard/SKILL.md",
          "target": ".agents/skills/mv3-worker-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "target": ".agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "target": ".agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt",
          "role": "reference"
        },
        {
          "path": "skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "target": ".agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "raw_url": "https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/chrome-mv3-service-worker-guard/copilot.zip"
    }
  },
  "source_zip_url": "https://setupbay.com/download/chrome-mv3-service-worker-guard/source.zip",
  "claude_code_plugin": {
    "name": "chrome-mv3-service-worker-guard",
    "marketplace_url": "https://setupbay.com/marketplace.json",
    "install": [
      "/plugin marketplace add https://setupbay.com/marketplace.json",
      "/plugin install chrome-mv3-service-worker-guard@setupbay"
    ],
    "zip_url": "https://setupbay.com/plugins/chrome-mv3-service-worker-guard.zip",
    "sha256": "44f6dd778603dafa7a8bfa906c0da8a60f081f14dc46df10b6ba1b5a54b61b0a",
    "carries": [
      "skills"
    ],
    "note": "Plugins do not carry project rules (AGENTS.md, CLAUDE.md); install those from install.claude-code."
  }
}