---
title: Mandatory Claude Extensions (now all optional, and no longer installed at setup)
---

# Claude Code extensions — mandatory no longer, and not installed at setup

## What it is

> **Update (2026-07-29): Superpowers is now OPTIONAL too — NO extension is force-installed anymore.** Omniscio ships its own built-in **Dev Pipeline** (plan → build → verify → ready-to-merge), so the Superpowers plugin is no longer needed as a hard dependency. Its registry entry flipped to `required: false` (`src/shared/extension-types.ts`), which — exactly like the 2026-06-22 Playwright change — drops it from onboarding's force-install AND retires the launch-time nag banner (the banner lists **required-missing** extensions only, and there are now none). Existing users who already installed Superpowers get a **one-time, dismissible inbox card** offering to uninstall it and adopt Dev Pipeline instead — raised at most once ever (stamped in `settings.superpowersMigrationNudgedAt`), with a desktop-only **Remove Superpowers** button behind a confirm dialog that calls the new `EXTENSIONS_UNINSTALL` IPC. See "Superpowers migration nudge" below. Net effect: `EXTENSION_DEFINITIONS` still lists both extensions, but BOTH are `required: false`, so onboarding's "Install Missing Tools" and the launch banner act on an empty required set. Treat the "required extension" language in the sections below as historical for BOTH entries.
>
> **Update (2026-06-22): Playwright MCP is now OPTIONAL, not required.** A redesign (commit `1f88b21992`) turned Playwright MCP into a **bundled, composed per-session built-in** that is **off by default** (`settings.playwrightMcpEnabled`) and enabled on demand via **Settings → MCP**, per-project, or per-session — it no longer auto-registers a `mcpServers.playwright` entry in `~/.claude.json`. Its registry entry is now `required: false` (commit `a245b9ae41`), so the launch banner and onboarding **no longer list Playwright**; they act on genuinely-required missing extensions only. The sections below still describe Playwright's original always-required, `~/.claude.json`-registered design — treat Playwright's "required"/registration details as historical. Background: ``mandatory-claude-extensions-postmortem.md``.

Beyond the CLI binaries listed in the [Tools view](tools-view.md) (claude-code, codex, gh, ffmpeg, tailscale, …), Omniscio also depends on two **Claude Code extensions** that aren't CLIs at all — they're features registered inside Claude Code's own configuration.

Because they're not binaries, they don't fit the existing toolchain pipeline; they get a parallel installer, a parallel onboarding row, and a launch-time nudge banner for users who already finished onboarding before the extensions feature shipped.

The two required extensions are:

1. **Playwright MCP** — a Model Context Protocol server (a way for Claude to talk to outside services like a browser) that gives agents a controllable Chromium browser. Registered in Claude Code's `~/.claude.json` under `mcpServers` as `playwright`, with an `npx @playwright/mcp@latest` command. Install also runs `npx playwright install chrome` to fetch the ~170 MB Chromium binary so sessions don't pay that cost on first use. Used by the `agent-browser`, `browser-use`, and other browser-automation skills.
2. **Superpowers** — a Claude Code plugin loaded from the [obra/superpowers-marketplace](https://github.com/obra/superpowers-marketplace) repository. Provides eleven foundational skills — `brainstorming`, `writing-plans`, `executing-plans`, `subagent-driven-development`, `test-driven-development`, `systematic-debugging`, `verification-before-completion`, `using-git-worktrees`, `requesting-code-review`, `receiving-code-review`, `writing-skills`, `using-superpowers`, `dispatching-parallel-agents`, `finishing-a-development-branch` — that Omniscio's recipes and routine prompts assume are present. Registered in Claude Code's plugin storage under a key like `superpowers@superpowers-marketplace`. Where that record lives changed between Claude Code versions: legacy installs (Claude Code v1.x) wrote `enabledPlugins: { "superpowers@…": true }` into `~/.claude.json`; current installs (Claude Code v2.x) write a per-plugin install record into `~/.claude/plugins/installed_plugins.json` instead. Omniscio's probe reads both — see "Why two sources for the Superpowers probe" below.

**Why a separate concept from "tools".** A tool in Omniscio is a CLI binary on `$PATH` (e.g. the `claude` binary, `git.exe`). Probing means running `<tool> --version`; installing means `npm install -g`, `winget`, `brew`, or a direct download. An extension is a record inside Claude Code's JSON config that Claude Code itself reads — installing means `claude mcp add …` or `claude plugin install …`, and "is it installed?" means "does the right key exist in `~/.claude.json`?" Mixing them into one pipeline would force every probe and install function to know about both shapes, so they live in separate `EXTENSION_DEFINITIONS` / `TOOL_DEFINITIONS` tables. This is why the Tools view and Settings → Connected Tools don't list these two.

Tailscale, by contrast, is **a tool, not an extension** — a system binary on `$PATH` — so it lives in `TOOL_DEFINITIONS`, appears in the Tools view, and is installed by the regular toolchain pipeline (winget on Windows, brew on macOS). It is _not_ part of this extensions feature.

## Where to find it

The onboarding step appears during first-run setup. The one-time offer to existing users arrives as a dismissible **inbox card**, and the extensions themselves are listed under **Settings → Extensions**.

## How it behaves

### How to use it

### First-time users (first-run setup)

**The extensions step is RETIRED — first-run no longer installs, probes or mentions either extension.** Setup v2 (the Cinematic Cascade, the one canonical first-run flow) has an **Install the essentials** step, and it installs only the required CLI tools: **Claude Code** and **Git**, both blocking, plus **browser automation**, which is non-blocking because it is a large download. No extension row appears there, and no extensions phase follows it.

What replaced it is the composition described at the top of this page: **Playwright MCP** became a bundled, composed per-session built-in instead of an installed MCP server, and **Superpowers** was dropped in favour of Omniscio's own built-in Dev Pipeline. With `required: false` on both (`src/shared/extension-types.ts`), the launch banner has nothing to report either — it only lists genuinely-required missing extensions, and there are currently none.

You can still see and manage both under **Settings → Extensions**, and install them by hand if you want them.

### Existing users (post-onboarding banner)

Users who finished onboarding before this feature shipped see a **dismissible amber banner** at the top of the app on every launch when any required extension is missing (rendered by `src/renderer/src/features/extensions/ExtensionsBanner.tsx`):

> Omniscio needs Superpowers. Install for full functionality.

(Before the 2026-06-22 fix the banner also listed **Playwright MCP**; now that Playwright is optional, the banner lists only genuinely-required missing extensions, so Playwright never appears. The example above shows the post-fix banner when only Superpowers is missing.)

Two buttons:

- **Install** — runs the install-all flow (`EXTENSIONS_INSTALL_ALL`) for the missing required set. While running, the button shows "Installing…" and is disabled. On success the banner disappears. On failure a toast surfaces the per-extension error ("playwright-mcp: Cleanup failed after partial install: …").
- **Later** — snoozes the banner for 24 hours (`SNOOZE_MS` in `src/renderer/src/hooks/useExtensionsLaunchCheck.ts`). It won't reappear until the snooze expires. Snooze state persists across app restarts.

The banner only mounts after onboarding is complete (`settings.onboardingCompleted === true`). That gate was added as a double-prompt fix while the retired wizard step ran the same install UI; the step is gone now, and the gate is kept so the banner counts as a post-onboarding surface. In practice it is currently quiet: with `required: false` on both extensions, the missing-required set is empty, so the banner has nothing to show. The gate and the surface remain for whenever a genuinely required extension is added.

### Superpowers migration nudge (un-forcing Superpowers)

When Superpowers was un-forced (2026-07-29), existing users who already installed it needed a graceful off-ramp rather than a silent config change. That off-ramp is a **one-time, dismissible inbox card**, deliberately built on the existing inbox-alert primitive + the `AlertInboxViewer` "carve-out button" pattern (the same shape the cache-cap, safe-catch-up, and quick-reply cards use) — **not** a full bloat-style integration, so it needs no new DB table, migration, store, or approval pane.

- **Producer** — `src/main/app/superpowers-migration-nudge.ts`, wired into the `StartupTask` registry (`src/main/startup/registry.ts`) as a leaf task (no `dependsOn`; startup tasks run well after db-init, so the alert's SQLite write always lands). It mirrors `src/main/app/hotkey-conflict-alert.ts`: a pure decision (`shouldRaiseSuperpowersMigrationNudge`), a pure copy builder (`buildSuperpowersMigrationAlertInput`), and a bulletproof orchestrator (`maybeSurfaceSuperpowersMigrationNudge`) that lazy-imports config-store / extensions-installer / alert-service so the SQLite + inbox graph stays off this leaf's static import path.
- **Raise-once-ever** — the orchestrator reads `settings.superpowersMigrationNudgedAt`; if set, it returns immediately (never re-nags). Otherwise it probes `isSuperpowersInstalled()` — a user WITHOUT Superpowers (every fresh install) is never nudged and never stamped. If installed, it raises the card via `raiseAgentAlert` (dedupKey `superpowers-migration-nudge`, so re-fires coalesce into one row) and stamps `superpowersMigrationNudgedAt` — the stamp is written even if the raise was suppressed by the notifications off-switch, because a one-shot nudge must not retry every launch.
- **The card's action** — the card carries a hand-wired, desktop-only **Remove Superpowers** button in `src/renderer/src/features/alerts/AlertInboxViewer.tsx` (recognised via `isSuperpowersMigrationDedupKey`; the shared dedupKey + predicate live in `src/shared/alert-features/superpowers-migration-alert.ts`). It sits behind a `ConfirmDialog`; on confirm it invokes `EXTENSIONS_UNINSTALL`, toasts success, and archives the card. The card ALSO keeps the universal additive Start-session button (inbox-alert-contract I8). Because the predicate is registered in `LEGACY_ACTION_PREDICATES` (`src/shared/alert-primary-actions.ts`), the `alert-directive-needs-action` guard sees the card as actioned.
- **The uninstall** — `uninstallSuperpowers()` in `src/main/services/extensions-installer.ts` runs `claude plugin uninstall superpowers@superpowers-marketplace`. It is **idempotent** (an already-absent plugin resolves success via `isNotInstalledError`) and **reversible** (reinstall from the same marketplace anytime — the confirm copy says so). Raw CLI stderr stays in the log; the returned `error` is plain, humanized copy.

## For agents

### How it works

The implementation lives in two parallel layers, mirroring the existing toolchain pipeline but kept separate because the install paths and probe mechanics differ.

**Definitions and probes.** `src/main/services/extensions-installer.ts` exports `EXTENSION_DEFINITIONS` (currently two entries: `playwright-mcp` and `superpowers`), `EXT_CHECK_FNS` (probe functions returning `ExtensionCheckResult`), and `EXT_INSTALL_FNS` (install functions that emit progress and return `ExtensionInstallResult`). Probing reads JSON files from the user's home directory via `src/main/services/claude-config.ts` — `readClaudeConfig()` parses `~/.claude.json` and `readClaudeInstalledPlugins()` parses `~/.claude/plugins/installed_plugins.json`. The Playwright probe looks for a `playwright` key in `mcpServers` (in `~/.claude.json`) and **validates the config is healthy** — if the entry's `args` array contains `--cdp-endpoint`, the probe returns `misconfigured` instead of `installed`, because a CDP-dependent config silently breaks when the external Chrome isn't running. The installer treats `misconfigured` the same as `not-installed` (the banner shows, the user clicks Install, and the repair flow removes-then-re-adds a clean self-managing config). The Superpowers probe runs both reads in parallel via `Promise.all` and returns "installed" if **either** source confirms: a key containing `superpowers` set to `true` in `~/.claude.json`'s `enabledPlugins`, OR a key containing `superpowers` mapped to a non-empty install array in `~/.claude/plugins/installed_plugins.json`'s `plugins` map. No `claude` binary is invoked for probing, so it's fast and offline-safe.

**Why two sources for the Superpowers probe.** Claude Code v2.x changed where it stores plugin enablement records. v1.x (and prior) wrote a flat boolean map `enabledPlugins: { "superpowers@superpowers-marketplace": true }` into `~/.claude.json`. v2.x writes a richer install record into a separate file `~/.claude/plugins/installed_plugins.json` of shape `{ version: 2, plugins: { "superpowers@superpowers-marketplace": [{ scope, version, installPath, … }] } }`, and may or may not also keep the legacy `enabledPlugins` field populated. A previous version of this probe checked only the legacy field, so every user on Claude Code v2.x was false-flagged as missing Superpowers and shown the install banner — even though their plugin was installed and working. The current dual-source probe reads both files in parallel and treats the plugin as installed if either source confirms it. Reading both is intentional belt-and-suspenders: users mid-upgrade may have only the legacy field; users on a fresh v2.x install only have the new file. I/O errors (`EACCES`, `EBUSY`) propagate from `readWithRetry`; only `JSON.parse` failures retry once after 50ms (concurrent CLI writes can produce a truncated file mid-read).

**Install paths.** Both installs shell out via `cmd.exe /c <claude|npx> …` on Windows (Node's `execFile` doesn't honor `PATHEXT`, so `.cmd` shims like `claude.cmd` and `npx.cmd` won't resolve without going through `cmd.exe`). All arguments are constants — no user input — so no shell-escape is needed. Playwright MCP first does a best-effort `claude mcp remove playwright` (ignoring errors if the key doesn't exist) to clear any stale or misconfigured entry, then runs `claude mcp add playwright -- npx @playwright/mcp@latest` followed by `npx playwright install chrome`; if the Chromium step fails after the add succeeded, a cleanup `claude mcp remove playwright` removes the dangling MCP entry so sessions don't fail at spawn time pointing at a missing Chromium. Superpowers runs `claude plugin marketplace add https://github.com/obra/superpowers-marketplace` (tolerating "already added" exit codes via stderr matching) then `claude plugin install superpowers@superpowers-marketplace`.

**IPC.** `src/main/ipc/extensions-ipc-handlers.ts` registers five channels — `EXTENSIONS_CHECK_ALL`, `EXTENSIONS_GET_DEFINITIONS`, `EXTENSIONS_INSTALL` (single, by id), `EXTENSIONS_INSTALL_ALL`, and `EXTENSIONS_UNINSTALL` (Superpowers only — the migration card's Remove action) — plus the `EXTENSIONS_INSTALL_PROGRESS` push event (definitions in `src/shared/ipc-channels/index.ts`). All handlers are wrapped in `wrapHandler()` for Zod validation; `EXTENSIONS_UNINSTALL` validates against `extensionUninstallInputSchema` (`{ id: z.literal('superpowers') }`), so the channel can never remove any other plugin. Progress events use `emitPush()` from `push-bus.ts`, listened in the renderer via `useIpcListener(IPC.EXTENSIONS_INSTALL_PROGRESS, …)`. `EXTENSIONS_UNINSTALL` is **desktop-only** — it mutates this computer's local `~/.claude` plugin config, so it is in the mobile/web WS bridge's `BLOCKED_CHANNELS` denylist (`src/main/services/web/web-access-ws-channels.ts`) and its inbox button is hidden on mobile; it is cli-parity-classified `dev-diagnostic`.

**Onboarding integration — RETIRED.** This was `src/renderer/src/features/onboarding/steps/ToolchainStep.tsx`, which fetched tools and extensions in parallel, merged them into separate `tools` / `extensions` arrays, and rendered a unified list. Nothing in the live first-run path calls it: the shipped cascade's install step is `src/renderer/src/features/onboarding/setup-v2/frames/InstallFrame.tsx`, and a search of `src/renderer/src/features/onboarding/setup-v2/` for `EXTENSIONS_CHECK_ALL` / `EXTENSIONS_INSTALL_ALL` returns nothing — the cascade installs CLI tools only. ToolchainStep is kept in the tree for reference and still compiles, so this description is of what it did, not of what ships. Extension probe failures are soft-fail — wrapped in `Promise.resolve(...).catch(() => undefined)` so a missing IPC handler doesn't crash the wizard. The install loop has a deliberate two-phase shape: all missing tools first (serial), then all missing extensions (serial). A compile-time guard `_IdNamespacesDisjoint` asserts `ToolId & ExtensionId extends never` so a future id collision (both tables sharing the `installingIds` Set) fails to typecheck.

**Launch-time banner.** `src/renderer/src/hooks/useExtensionsLaunchCheck.ts` drives the post-onboarding banner. On mount, if `settings.extensionsPromptSnoozedUntil` is in the future the effect bails out immediately (no IPC traffic). Otherwise it calls `EXTENSIONS_CHECK_ALL` + `EXTENSIONS_GET_DEFINITIONS` and computes the missing set. The hook returns `{ missing, install, snooze, installing }`. `install()` calls `EXTENSIONS_INSTALL_ALL`, surfaces toasts on full or partial failure, then re-runs the check to clear newly-installed extensions from the banner. `snooze()` writes `new Date(Date.now() + 24h).toISOString()` to `extensionsPromptSnoozedUntil` and clears the missing list locally.

**Banner gating.** The banner is rendered by `ExtensionsBannerMount` inside `src/renderer/src/App.tsx`, conditionally on `settings.onboardingCompleted`. This is the double-prompt fix: onboarding's wizard step already shows the same install UI, so we mount the banner only after onboarding has been completed — otherwise a user mid-wizard would see both. The banner uses `role="status"` (aria-live polite), not `role="alert"`, because a launch-time nudge for missing optional features shouldn't interrupt screen-reader flow on every re-render.

**Soft-fail design.** The continue button stays enabled even when extensions failed in the wizard, the banner can be snoozed indefinitely (re-snoozing each day), and the rest of Omniscio works without these extensions. The only cost is that browser-automation skills and Superpowers-dependent recipes will fail at runtime if they're invoked. This is intentional: we'd rather let the user proceed with a clear failure path than block app startup behind a dependency they may not need today.

**Settings field.** `src/shared/types.ts` adds `extensionsPromptSnoozedUntil: string | null` to `AppSettings` (default `null`). It's also wired into `updateSettingsSchema` so the IPC layer doesn't silently strip it — without that, the snooze write would round-trip cleanly in the store but never persist to `config.json`. The migration nudge adds a second marker of the same shape, `superpowersMigrationNudgedAt: string | null` (default `null`), wired the same way.

## Related

- [tools-view.md](tools-view.md) — the dedicated virtual project for CLI binaries; explains why these two extensions don't appear there
- [toolchain-update-v3.md](toolchain-update-v3.md) — how the parallel **tool** pipeline handles updates, deferred queues, and preflight checks (the model this extensions feature is shaped after)
- [use-skills.md](use-skills.md) — the Skills view; many catalogued skills depend on Playwright MCP or on Superpowers' foundational skills
