Skip to content
Development
Skill

/muxy-extension

Best-practice guide for authoring a Muxy extension — how it should look and behave so it reads as a native part of the app. Covers theming (follow the theme, never hardcode colors), the sizing scale, and which surface to use. Mechanics (manifest fields, permissions, the

From plugin
muxy
2.2k2 skills
Install
$ npx -y skills add muxy-app/muxy --skill muxy-extension --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/muxy-extension

Context preview

The summary Claude sees to decide when to auto-load this skill.

Best-practice guide for authoring a Muxy extension — how it should look and behave so it reads as a native part of the app. Covers theming (follow the theme, never hardcode colors), the sizing scale, and which surface to use. Mechanics (manifest fields, permissions, the

SKILL.md

muxy-extension.SKILL.md
name: muxy-extension
description: Best-practice guide for authoring a Muxy extension — how it should look and behave so it reads as a native part of the app. Covers theming (follow the theme, never hardcode colors), the sizing scale, and which surface to use. Mechanics (manifest fields, permissions, the window.muxy API) live in the linked docs.

Muxy Extension Guide

A Muxy extension is an npm + [Vite](https://vitejs.dev) project: source under `src/`, an entry HTML that `vite build` emits into `dist/`, and Muxy reads `dist/` when present (otherwise the project folder). The manifest is the `"muxy"` object in `package.json`. There is no fixed folder layout — every `entry`/`background`/icon path is an arbitrary relative path inside the build output (the vanilla starter kit emits its panel to `panel/index.html`); `package.json` and `dist/` are the only names Muxy fixes. During development you don't copy into the config folder — **Load Unpacked** in the Extensions modal points Muxy at any folder (your git checkout *is* the install).

**The `build` script must copy `package.json` into `dist/`.** The publish pipeline ships **only** `dist/`, and the app reads the manifest from the install root — so the manifest has to be inside the build output. `vite build` alone emits your entry/asset paths but **not** the manifest, so use `"build": "vite build && node scripts/copy-manifest.mjs"` where `copy-manifest.mjs` copies `package.json` into `dist/`. Easy to miss because **Load Unpacked** falls back to the root `package.json` in dev, so it loads locally but fails validation/install when published. The vanilla starter kit already wires this up.

**This skill is the guidance layer — how an extension should look and behave.** For the API and manifest mechanics (every field, the permission strings, the full `window.muxy` surface, events, scripts), read the reference docs. Start from the LLM-friendly index, which lists every page and links to its raw Markdown source:

> **<https://muxy.app/llms.txt>**

Append `/plain` to any docs URL for the raw Markdown of that page (e.g. <https://muxy.app/docs/extensions/manifest/plain>).

The goal of everything below: an extension should be indistinguishable from a native Muxy surface. Match the theme and match the scale, and it will be.

Pick the right surface

  • **Showing something to the user** → a **UI page** (tab, panel, or popover). Page scripts get the full `window.muxy` API.
  • **A persistent, full-height navigation or control surface that replaces the built-in left sidebar** → a **`sidebar`** (one per extension; the user selects it in Settings → Sidebar). It fills the entire region — the project list *and* the footer — so own your own navigation. Same `window.muxy` API and theme variables as a panel.
  • **Reacting durably to events, coordinating multiple webviews, or running shell commands headlessly** → a **`background.js`** script. It can also call `muxy.tabs.open` to show a result in the active workspace. Most extensions don't need one.
  • **One-shot logic from the palette** → a **`runScript`** command, not a hidden tab. Its `muxy.*` calls are **synchronous** except for the Promise-based `muxy.execAsync`. It has `tabs`/`panes`/`projects`/`worktrees`/`browser`/`agents`/`files`/`git`/`exec`/`execAsync`/`dialog`/`modal`/`topbar`/`statusbar`/`notifications`, but **not** `http`, `events`, `remote`, `panels`, or `popover`. It can open a modal and act on the choice inline — no page or background listener needed.
  • **Your own HTML in a modal on a keypress** (a form, info, a list, or mixed — not just forms) → a **webview modal** (`muxy.modal.openWebview`). A top-centered omnibox-style overlay rendering your HTML. Reach for it over the native `muxy.modal.open` picker when a list won't express what you need. **Prefer opening it from `background.js`** — it's always running, so a shortcut works with nothing else on screen, and you can pass dynamic `data` and `await` the result. Use the declarative `openModal` command action only for a static, self-contained modal that needs no result and no `background.js`. The modal returns a value via `muxy.modal.submitWebview(value)` only if the opener wants one; an informational modal just calls `muxy.lifecycle.close()`.

Don't open a hidden tab to run logic, and don't put durable event-driven work in tab JS where closing the tab loses it. Use `muxy.events.emit('extension.<name>', payload)` plus a background listener when a webview needs to ask background.js for shared or long-lived work.

Theme — follow it, never hardcode

Muxy ships paired light/dark themes and a user-chosen accent. Every extension webview inherits CSS custom properties on `document.documentElement` that track the live theme and update automatically when the user switches it.

**Rules:**

1. **No hex literals for chrome.** Use `var(--muxy-…)` for every color. The only exception is decorative art meant to be theme-independent. 2. **The variables already invert** for light/dark — never sniff the color scheme to pick a color. Only branch on `muxy.theme.colorScheme` for things a variable can't express (e.g. swapping a logo image). 3. **`--muxy-accent` is the only saturated color.** Use it sparingly — primary action, focus ring, one key number — so it stays distinctive. Text *on* an accent fill must use `--muxy-accent-foreground`, which is resolved for contrast against the active theme's accent. 4. **Use `--muxy-surface-solid` for component backgrounds.** It is the native surface overlay precomposited over the active theme background, so cards, inputs, code blocks, and buttons match Muxy without letting content show through. `--muxy-surface`, `--muxy-border`, `--muxy-hover`, and `--muxy-accent-soft` are translucent overlays for material-relative effects. Do not apply additional opacity to these tokens or to an entire control. 5. **Re-read the theme for JS-drawn color.** Canvas/SVG that doesn't pick up CSS variables must redraw in `muxy.onThemeChange(theme =>

Read more
Ships withmuxy

Lightweight and Memory efficient terminal for Mac built with SwiftUI and libghostty

Get the whole plugin
Stats
2,163
Stars
186
Forks
Active
Maintenance
Swift
Language
MIT
License
6h ago
Last commit
4mo ago
Created

Repo: muxy-app/muxy