{"owner":"nukeop","repo":"nuclear","hasSkills":true,"totalSkillsCount":95,"totalTokensCount":35346,"categories":["anthropic-skill","copilot-instructions","root-instruction","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"nuclear":{"command":"npx","args":["-y","@modelcontextprotocol/server-nuclear"]}}},"found":[".agents/skills/writing-docs/SKILL.md","packages/website/package.json",".agents/skills/writing-plugins/SKILL.md",".github/copilot-instructions.md",".agents/skills/creating-components/SKILL.md",".agents/skills/host-pattern/SKILL.md","AGENTS.md","packages/website/tsconfig.json","packages/ui/package.json","packages/ui/tsconfig.json","packages/tools/package.json","packages/tools/tsconfig.json","packages/themes/package.json","packages/themes/README.md","packages/themes/tsconfig.json","packages/tailwind-config/package.json","packages/tailwind-config/tsconfig.json","packages/storybook/package.json","packages/storybook/tsconfig.json","packages/plugin-sdk/api-extractor.json","packages/plugin-sdk/package.json","packages/plugin-sdk/README.md","packages/plugin-sdk/tsconfig.json","packages/player/changelog.json","packages/player/package.json","packages/player/tsconfig.json","packages/player/tsconfig.node.json","packages/model/package.json","packages/model/tsconfig.json","packages/i18n/package.json","packages/i18n/tsconfig.json","packages/hifi/package.json","packages/hifi/tsconfig.json","packages/eslint-config/package.json","packages/eslint-config/tsconfig.json","packages/docs/.gitbook.yaml","packages/docs/package.json","packages/docs/README.md","packages/docs/SUMMARY.md","packages/player/src-tauri/tauri.conf.json","packages/docs/core-concepts/favorites.md","packages/docs/core-concepts/how-nuclear-works.md","packages/docs/core-concepts/listening-history.md","packages/docs/core-concepts/listening-stats.md","packages/docs/core-concepts/playlists.md","packages/docs/core-concepts/plugins-and-providers.md","packages/docs/core-concepts/the-queue.md","packages/docs/development/contributing.md","packages/docs/development/host-pattern.md","packages/docs/development/logging.md","packages/docs/development/mcp-architecture.md","packages/docs/integrations/http-api.md","packages/docs/integrations/mcp-server.md","packages/docs/integrations/mpd-server.md","packages/docs/misc/discovery.md","packages/docs/misc/keyboard-shortcuts.md","packages/docs/misc/platform-specific.md","packages/docs/plugins/dashboard.md","packages/docs/plugins/discovery.md","packages/docs/plugins/events.md","packages/docs/plugins/favorites.md","packages/docs/plugins/getting-started.md","packages/docs/plugins/http.md","packages/docs/plugins/logger.md","packages/docs/plugins/metadata.md","packages/docs/plugins/playback.md","packages/docs/plugins/playlists.md","packages/docs/plugins/plugin-store.md","packages/docs/plugins/plugin-system.md","packages/docs/plugins/providers.md","packages/docs/plugins/publishing.md","packages/docs/plugins/queue.md","packages/docs/plugins/settings.md","packages/docs/plugins/shell.md","packages/docs/plugins/streaming.md","packages/docs/plugins/ytdlp.md","packages/docs/themes/theme-store.md","packages/docs/themes/themes.md","packages/docs/themes/themes-advanced.md","packages/docs/themes/themes-basic.md","packages/docs/user-manual/getting-started.md","packages/docs/user-manual/installation.md","packages/docs/user-manual/remote-control.md","packages/player/src-tauri/capabilities/default.json","packages/player/src-tauri/capabilities/desktop.json","packages/i18n/src/locales/de_DE.json","packages/i18n/src/locales/en_US.json","packages/i18n/src/locales/es_ES.json","packages/i18n/src/locales/fr_FR.json","packages/i18n/src/locales/it_IT.json","packages/i18n/src/locales/ja_JP.json","packages/i18n/src/locales/pl_PL.json","packages/i18n/src/locales/pt_BR.json","packages/i18n/src/locales/ru_RU.json","packages/i18n/src/locales/zh_CN.json"],"skills":{".agents/skills/writing-docs/SKILL.md":"---\nname: writing-docs\ndescription: Use when writing or editing documentation in packages/docs. Covers Gitbook markdown syntax, special blocks, page structure, and the SUMMARY.md table of contents. Trigger phrases include \"write docs\", \"add documentation\", \"docs page\", \"gitbook\", \"user manual\".\n---\n\n# Writing Documentation\n\nDocs live in `packages/docs/`. Hosted on Gitbook, synced from the repo via `.gitbook.yaml`.\n\n## Page structure\n\nEvery page starts with YAML frontmatter and a top-level heading:\n\n```markdown\n---\ndescription: Short description shown in search and link previews\n---\n\n# Page title\n```\n\n## Table of contents\n\n`packages/docs/SUMMARY.md` defines the sidebar navigation. Group pages under `##` headings:\n\n```markdown\n# Table of contents\n\n* [Nuclear Documentation](README.md)\n\n## User Manual\n\n* [Getting started](user-manual/getting-started.md)\n* [Installation](user-manual/installation.md)\n\n## Plugins\n\n* [Getting started](plugins/getting-started.md)\n```\n\nWhen adding a new page, always add it to SUMMARY.md or it won't appear in the sidebar.\n\n## Gitbook special blocks\n\n### Tabs\n\nUse for platform-specific instructions or language variants:\n\n```markdown\n{% tabs %}\n\n{% tab title=\"Windows\" %}\nContent for Windows.\n{% endtab %}\n\n{% tab title=\"macOS\" %}\nContent for macOS.\n{% endtab %}\n\n{% tab title=\"Linux\" %}\nContent for Linux.\n{% endtab %}\n\n{% endtabs %}\n```\n\nTabs can contain any block type (code, tables, lists, images) but cannot nest other tabs or expandables.\n\n### Hints (callouts)\n\nFour styles: `info`, `success`, `warning`, `danger`.\n\n```markdown\n{% hint style=\"info\" %}\nGeneral information or tips.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nNon-critical warnings or gotchas.\n{% endhint %}\n\n{% hint style=\"danger\" %}\nDestructive actions or critical information.\n{% endhint %}\n\n{% hint style=\"success\" %}\nPositive actions or confirmations.\n{% endhint %}\n```\n\n### Code blocks with options\n\nWrap in `{% code %}` for title, line numbers, or wrapping:\n\n````markdown\n{% code title=\"config.json\" overflow=\"wrap\" lineNumbers=\"true\" %}\n\n```json\n{\n  \"key\": \"value\"\n}\n```\n\n{% endcode %}\n````\n\nWithout the wrapper, standard fenced code blocks work fine.\n\n### Expandable (collapsible sections)\n\nUses standard HTML `<details>` / `<summary>`:\n\n```markdown\n<details>\n\n<summary>Click to expand</summary>\n\nHidden content goes here. Supports lists and code blocks.\n\n</details>\n```\n\nAdd `open` attribute to expand by default: `<details open>`.\n\n### Page links\n\nProminent block-level links to other pages:\n\n```markdown\n{% content-ref url=\"getting-started.md\" %}\n[Getting started](getting-started.md)\n{% endcontent-ref %}\n```\n\n### Cards\n\nGrid layout for landing pages. Uses a table with `data-view=\"cards\"`:\n\n```markdown\n<table data-view=\"cards\">\n  <thead>\n    <tr>\n      <th></th>\n      <th></th>\n      <th data-hidden data-card-target data-type=\"content-ref\"></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><strong>Card title</strong></td>\n      <td>Card description.</td>\n      <td><a href=\"page.md\">page.md</a></td>\n    </tr>\n  </tbody>\n</table>\n```\n\n### Annotations (footnotes)\n\n```markdown\nNuclear uses plugins[^1] to provide functionality.\n\n[^1]: Plugins are installed from the plugin store.\n```\n\n## Content guidelines\n\n- No brand names of third-party services in docs\n- All user-facing strings reference i18n keys, not hardcoded text\n- Use relative links between docs pages (`../themes/themes.md`), not absolute URLs\n- Keep pages focused on one topic. Link to related pages instead of duplicating content.\n- Describe the UI as the user sees it: use actual button labels, menu names, and icon descriptions\n","packages/website/package.json":"{\n  \"name\": \"@nuclearplayer/website\",\n  \"version\": \"2.17.2\",\n  \"description\": \"Nuclear website\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"build\": \"astro build\",\n    \"preview\": \"astro preview\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"type-check\": \"astro check\",\n    \"clean\": \"rm -rf dist .turbo node_modules/.cache .astro\"\n  },\n  \"dependencies\": {\n    \"@astrojs/markdown-remark\": \"^7.3.1\",\n    \"@astrojs/mdx\": \"^8.0.0\",\n    \"@astrojs/react\": \"^6.0.0\",\n    \"@astrojs/rss\": \"^4.0.18\",\n    \"@astrojs/sitemap\": \"^3.7.0\",\n    \"@fontsource-variable/bricolage-grotesque\": \"^5.2.10\",\n    \"@fontsource-variable/dm-sans\": \"^5.2.8\",\n    \"@fontsource/space-mono\": \"^5.2.9\",\n    \"@iconify-json/simple-icons\": \"^1.2.67\",\n    \"@nuclearplayer/ui\": \"workspace:*\",\n    \"astro\": \"^7.0.0\",\n    \"astro-expressive-code\": \"^0.44.0\",\n    \"astro-icon\": \"^1.1.5\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.542.0\",\n    \"medium-zoom\": \"^1.1.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\"\n  },\n  \"devDependencies\": {\n    \"@astrojs/check\": \"^0.9.6\",\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@nuclearplayer/tailwind-config\": \"workspace:*\",\n    \"@tailwindcss/typography\": \"^0.5.19\",\n    \"@tailwindcss/vite\": \"^4.1.11\",\n    \"@types/react\": \"^18.3.23\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"sharp\": \"^0.35.0\",\n    \"tailwindcss\": \"^4.1.11\",\n    \"typescript\": \"^5.9.2\"\n  }\n}\n",".agents/skills/writing-plugins/SKILL.md":"---\nname: writing-plugins\ndescription: Use when writing, scaffolding, or modifying Nuclear plugins. Covers plugin structure, manifest, entry point, provider types, available APIs, and publishing. Trigger phrases include \"create a plugin\", \"write a plugin\", \"plugin scaffold\", \"streaming provider\", \"metadata provider\".\n---\n\n# Writing Nuclear Plugins\n\nPlugins are standalone repos compiled in-browser via esbuild-wasm.\n\n## Structure\n\n```\nmy-plugin/\n  package.json      # Manifest with nuclear metadata\n  src/\n    index.ts        # Entry point, default exports NuclearPlugin\n```\n\n## Manifest (package.json)\n\n```json\n{\n  \"name\": \"nuclear-plugin-example\",\n  \"version\": \"0.1.0\",\n  \"description\": \"What this plugin does\",\n  \"author\": \"Your Name\",\n  \"license\": \"AGPL-3.0-only\",\n  \"main\": \"src/index.ts\",\n  \"type\": \"module\",\n  \"nuclear\": {\n    \"displayName\": \"Example Plugin\",\n    \"category\": \"streaming\",\n    \"icon\": { \"type\": \"link\", \"link\": \"https://example.com/icon.svg\" }\n  },\n  \"dependencies\": {\n    \"@nuclearplayer/plugin-sdk\": \"^1.1.0\"\n  }\n}\n```\n\nCategories: `streaming`, `metadata`, `lyrics`\n\n## Entry Point\n\n```typescript\nimport type { NuclearPlugin, NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';\n\nconst plugin: NuclearPlugin = {\n  onLoad(api: NuclearPluginAPI) {},\n  onEnable(api: NuclearPluginAPI) {\n    // Register providers\n  },\n  onDisable() {\n    // Unregister providers\n  },\n  onUnload() {},\n};\n\nexport default plugin;\n```\n\n## Provider Types\n\n**Streaming** - resolve tracks to playable audio:\n\n```typescript\nconst provider: StreamingProvider = {\n  id: 'my-streaming',\n  kind: 'streaming',\n  name: 'My Streaming',\n  async searchForTrack(artist, title, album?) { /* return StreamCandidate[] */ },\n  async getStreamUrl(candidateId) { /* return Stream */ },\n};\n\napi.Providers.register(provider);\napi.Providers.unregister('my-streaming');\n```\n\n**Metadata** - search artists/albums, fetch details:\n\n```typescript\nconst provider: MetadataProvider = {\n  id: 'my-metadata',\n  kind: 'metadata',\n  name: 'My Metadata',\n  searchCapabilities: ['artists', 'albums'],\n  streamingProviderId: 'my-streaming', // Optional: locks streaming to this provider\n  async searchArtists(params) { /* ... */ },\n  async searchAlbums(params) { /* ... */ },\n  async fetchArtistBio(id) { /* ... */ },\n  async fetchAlbumDetails(id) { /* ... */ },\n};\n```\n\n## Available APIs\n\n- `api.Providers` - register/unregister providers\n- `api.Settings` - plugin settings storage\n- `api.Http` - fetch wrapper\n- `api.Ytdlp` - yt-dlp integration\n- `api.Queue` - playback queue control\n- `api.Metadata` - search music metadata\n- `api.Streaming` - resolve streams\n- `api.Logger` - logging (trace/debug/info/warn/error)\n\n## Publishing\n\n1. Create a GitHub repo with the plugin code\n2. Add `.github/workflows/release.yml`:\n\n```yaml\nname: Release\non:\n  push:\n    tags: ['v*']\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@v4\n      - run: zip -r plugin.zip src package.json README.md\n      - uses: softprops/action-gh-release@v2\n        with:\n          files: plugin.zip\n          generate_release_notes: true\n```\n\n3. Tag release: `git tag v0.1.0 && git push origin v0.1.0`\n4. Submit PR to `NuclearPlayer/plugin-registry` adding your plugin to `plugins.json`\n",".github/copilot-instructions.md":"# Nuclear Music Player - Copilot Instructions\n\n## Project overview\n\nNuclear is a free, open-source music player without ads or tracking. Search for any song or artist, build playlists, and start listening.\n\nIt's a monorepo managed with pnpm and turbo.\n\nThis particular repo is a rewrite project, with the original Nuclear codebase in a different repo. \n\n### Key features\n\n- **Themes**: Support for basic and advanced themes, which work by customizing CSS variables that then control Tailwind classes. Users can load their own themes from JSON, or use built-in ones.\n- **Plugins**: A powerful plugin system that allows you to control any part of the player. No sandboxing.\n\n### Tech stack\n\n- Typescript - primary language.\n- Tauri - creates a native desktop application shell.\n- Tailwind v4 - CSS-first setup. Configured via CSS using @theme and @layer (see `packages/tailwind-config/global.css`). No tailwind.config.js. Don't use built-in colors, prefer the palette defined in `global.css`.\n- Turbo repo - tool for managing the monorepo.\n- pnpm - the preferred package manager. Used for workspaces.\n- eslint + prettier - formatting and linting.\n- Storybook - used for demoing UI components.\n- Vite - build tool\n- Vitest - for all tests\n- Lucide React - icon library\n- motion and tw-animate-css - for animations\n- TanStack Router - chosen solution for routing.\n- TanStack Query v5 - chosen solution for HTTP requests.\n\n### Packages\n- `@nuclearplayer/player` - Main Tauri app (React + Rust).  \n- `@nuclearplayer/plugin-sdk` - Plugin system (TS/React).  \n- `@nuclearplayer/ui` - Shared UI components.  \n- `@nuclearplayer/model` - Data model.\n- `@nuclearplayer/docs` - Gitbook documentation.\n- `@nuclearplayer/tailwind-config` - Shared Tailwind config.  \n- `@nuclearplayer/eslint-config` - Shared linting & formatting rules.  \n- `@nuclearplayer/hifi` - Advanced HTML5 audio component for playback.\n- `@nuclearplayer/themes` - Theming system.\n- `@nuclearplayer/tools` - Build and maintenance utilities.\n- `@nuclearplayer/storybook` - Storybook stories.\n\n### Workflow Commands\n\n```bash\npnpm dev        # run the project in dev mode\npnpm build      # build all packages\npnpm lint       # lint all packages\npnpm test       # test all packages\npnpm test:coverage  # run tests with coverage\npnpm type-check     # run TypeScript checks across packages\npnpm tauri          # run Tauri CLI for the player\npnpm storybook      # run Storybook\n```\n\n## Coding principles\n\n### General\n\n- Prioritize readability.\n- This is a production project meant for long term maintenance and development. Cutting corners, using short term solutions, placeholders, half-baked methods, and messy code is unacceptable.\n- Avoid premature abstractions. Start concrete, extract later.\n- Prefer clarity over cleverness.\n- Do not leave comments in code. Their place is in the chat.\n- Stick to existing conventions. This is a monorepo so standardizing everything is very very important. Look at other packages when in doubt. Use centralized configs for tools like Typescript, Eslint, Prettier, Tailwind.\n- Break work into the smallest reasonable steps. Small commits > big dumps.\n- Always be on the lookout for dead code, copy-pasta, and other opportunities to optimize and trim the codebase in a sensible way.\n\n### TypeScript / React\n- Use `type`, not `interface` (except when TS requires merging). Do not use interfaces for props.\n- Use `const Component: FC<Props> = () => {}` instead of `function Component()`.\n- No magic numbers → extract into named constants.\n- Prefer compound components (`Component.Sub`) when building complex widgets.\n- Keep business logic out of UI components.\n\n### Architecture\n- UI components should stay dumb, presentation-only.\n- State management:\n  - **Zustand** for persistent UI state.  \n  - **React state** for local, temporary state.\n  - **TanStack Query v5 (aka react-query)** for HTTP requests.\n- Routing:\n  - **TanStack Router** for client-side routing.\n- Lift complex or performance-critical logic to Tauri (Rust).\n\n### Testing\n- Use **Vitest + React Testing Library**.\n- Test like a user: minimal mocks, simulate interactions.\n- Only mock external dependencies (HTTP, FS, Tauri).\n- Snapshot tests: **basic rendering only**. Start names with `(Snapshot)`.\n- Extract DOM querying into wrappers; assertions stay in tests.\n- Coverage: enabled across packages with V8-based coverage and CI reporting.\n- Use the test runner tool instead of running tests in the terminal manually.\n\n### Design & UX Philosophy\n\n- Visual style: **neo-brutalist with premium polish**.\n- Animations should enhance UX, not slow it down.\n- Disable animations during high-friction moments (e.g., resize).\n- Use **motion** + **tw-animate-css** for smooth springy physics.\n- Feel: professional yet approachable, Discord-like.\n\n#### Typography\n\n- Fonts are standardized at the design system level. Prefer utilities `font-sans` and `font-heading` when needed.\n- Defaults are applied for common HTML tags (e.g., body, headings), so you rarely need to set fonts manually.\n\n## Final Notes\n\n- Treat this as **production code from day one**. No shortcuts, no placeholders.\n- Prioritize long-term maintainability.\n- Work with me iteratively: pause, summarize, ask.\n- Above all: **be reliable, disciplined, and clear.**\n- Use Serena tools wherever possible for searches, lookups, replacements, and file operations.",".agents/skills/creating-components/SKILL.md":"---\nname: creating-components\ndescription: Use when creating new UI components in packages/ui. Covers component structure, tests, stories, and what to avoid.\n---\n\n# Creating UI Components\n\n## Component Structure\n\n```\npackages/ui/src/components/MyComponent/\n├── MyComponent.tsx      # Implementation\n├── MyComponent.test.tsx # Tests\n├── index.ts             # Re-exports\n└── __snapshots__/       # Vitest snapshots (auto-generated)\n```\n\nAfter creating, export from `packages/ui/src/components/index.ts`.\n\n## Implementation Pattern\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\nimport { cn } from '../../utils';\n\nconst variants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype MyComponentProps = ComponentProps<'div'> & VariantProps<typeof variants>;\n\nexport const MyComponent: FC<MyComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(variants({ variant, className }))} {...props} />\n);\n```\n\n## Tests\n\n**What to test:**\n- Snapshots (1-2 covering key variants)\n- User interactions\n- Behavior (callbacks called with correct args)\n\n**What NOT to test:**\n- CSS classes, attributes (use snapshots instead)\n- Internal state\n- Things TypeScript already enforces\n\n**Consolidate tests.** One test can cover multiple related assertions. An exception to that is snapshot tests - one snapshot per variant/state.\n\n## Stories\n\nCreate `packages/storybook/src/MyComponent.stories.tsx`.\n\n**One story can show multiple related variants.** For example, it's wasteful to create a story for each variant of a button. Put them all together in one place. Don't create separate stories for each variant:\n\n```tsx\nimport { Meta, StoryObj } from '@storybook/react-vite';\nimport { useState } from 'react';\nimport { MyComponent } from '@nuclearplayer/ui';\n\nconst meta = {\n  title: 'Components/MyComponent',\n  component: MyComponent,\n  tags: ['autodocs'],\n} satisfies Meta<typeof MyComponent>;\n\nexport default meta;\ntype Story = StoryObj<typeof MyComponent>;\n\nexport const AllVariants: Story = {\n  render: () => {\n    const [state, setState] = useState(initialState);\n    return (\n      <div className=\"flex flex-col gap-4\">\n        {/* Show all variants, states, interactions */}\n      </div>\n    );\n  },\n};\n```\n\nThis isn't an iron rule, sometimes it will make more sense to have separate stories.\n\nDon't run storybook build checks.\n\n## Avoiding Duplication\n\nBefore creating a new component, check if an existing one can be extended.\n\n**Pattern: Discriminated unions for mode variants**\n\nExample: Instead of creating `SingleSelect` and `MultiSelect` components:\n\n```tsx\ntype Props = \n  | { multiple?: false; selected: string; onChange: (id: string) => void }\n  | { multiple: true; selected: string[]; onChange: (ids: string[]) => void };\n```\n\nTypeScript enforces correct types based on the `multiple` prop.\n\n## Classes for customization\n\nWhere it's likely that a component will need custom styling, expose a `className` prop.\n\nIf there are many parts that may need styling, consider exposing a `classes` prop with specific class names for each part. Define a type for the `classes` prop. Refer to `packages/ui/src/components/TrackTable/types.ts` for an example.\n\n## Strings\n\nAll user-facing strings go through i18n - no hardcoded UI text.\nIf a new component in the ui package needs labels and other kinds of localized text, it should accept a `labels` prop with the relevant strings. The prop should have its own type defined. Refer to `packages/ui/src/components/QueueItem/types.ts` for an example.\n\n## Accessibility\n\nWe don't care about that. If there's an opportunity to handle that easily, do it, but don't go out of your way.\n\n## Checklist\n\n- [ ] Component in `packages/ui/src/components/MyComponent/`\n- [ ] Exported from `packages/ui/src/components/index.ts`\n- [ ] Tests cover behavior, not implementation\n- [ ] Tests consolidated (not one per variant)\n- [ ] One story showing all variants\n- [ ] No CSS class assertions in tests\n- [ ] No duplicate component when extending existing one works\n",".agents/skills/host-pattern/SKILL.md":"---\nname: host-pattern\ndescription: Use when adding a new domain to Nuclear's plugin system, or implementing a host. Covers the host pattern (how player functionality is exposed to plugins), the host interface and API class structure, how hosts are implemented in the player, error handling conventions, and what files to create and modify. Trigger phrases include \"add a domain\", \"new domain\", \"host implementation\", \"host pattern\", \"createPluginAPI\".\n---\n\n# Host pattern\n\nEvery feature area follows the same three-layer structure:\n\n1. Host type - the contract (SDK, no implementation)\n2. API class - what plugins actually call (SDK, wraps the host)\n3. Host implementation - bridges the API to player internals (player)\n\nPlugins call methods on an API class (e.g. `api.Queue.addToQueue()`), which holds a reference to the host and delegates to it. Plugins never touch a host directly.\n\n## Files to create/modify\n\n### SDK (`packages/plugin-sdk/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/types/yourDomain.ts` | `YourDomainHost` interface + related types |\n| Create | `src/api/yourDomain.ts` | `YourDomainAPI` class |\n| Modify | `src/api/index.ts` | Add `yourDomainHost` option + `YourDomain` field to `NuclearAPI` |\n| Modify | `src/index.ts` | Export types and API class |\n\n### Player (`packages/player/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/services/yourDomainHost.ts` | Host implementation + singleton export |\n| Modify | `src/services/plugins/createPluginAPI.ts` | Pass singleton to `NuclearPluginAPI` |\n| Modify | `src/services/logger.ts` | Add domain to `LOG_SCOPES` (needed for `reportError`) |\n\nIf the domain needs shared model types: create `packages/model/src/yourDomain.ts` and re-export from `packages/model/src/index.ts`.\n\n## API class pattern\n\nEvery API class follows this structure.\n\n```typescript\n// packages/plugin-sdk/src/api/yourDomain.ts\nexport class YourDomainAPI {\n  #host?: YourDomainHost;\n\n  constructor(host?: YourDomainHost) {\n    this.#host = host;\n  }\n\n  #withHost<T>(fn: (host: YourDomainHost) => T): T {\n    const host = this.#host;\n    if (!host) {\n      throw new Error('YourDomain host not available');\n    }\n    return fn(host);\n  }\n\n  yourMethod(arg: SomeType) {\n    return this.#withHost((host) => host.yourMethod(arg));\n  }\n}\n```\n\nReference: `packages/plugin-sdk/src/api/queue.ts`, `packages/plugin-sdk/src/api/dashboard.ts`\n\n## Connecting to NuclearAPI\n\n```typescript\n// packages/plugin-sdk/src/api/index.ts\nimport type { YourDomainHost } from '../types/yourDomain';\nimport { YourDomainAPI } from './yourDomain';\n\n// Add to class:\nreadonly YourDomain: YourDomainAPI;\n\n// Add to constructor opts type:\nyourDomainHost?: YourDomainHost;\n\n// Add to constructor body:\nthis.YourDomain = new YourDomainAPI(opts?.yourDomainHost);\n```\n\n```typescript\n// packages/player/src/services/plugins/createPluginAPI.ts\nimport { yourDomainHost } from '../../services/yourDomainHost';\n// Add to NuclearPluginAPI constructor call:\nyourDomainHost,\n```\n\n## Host implementation\n\nHosts bridge the SDK contract to whatever backs the domain. Most commonly a Zustand store:\n\n```typescript\n// packages/player/src/services/yourDomainHost.ts\nimport type { YourDomainHost } from '@nuclearplayer/plugin-sdk';\nimport { useYourDomainStore } from '../stores/yourDomainStore';\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  yourMethod: (arg) => useYourDomainStore.getState().doThing(arg),\n  getState: () => useYourDomainStore.getState().value,\n});\n\nexport const yourDomainHost = createYourDomainHost();\n```\n\nA host can also have access to the **provider registry** (metadata, streaming, dashboard), resolving which registered provider to call. Two patterns:\n\n**Single provider** (user picks one in Sources):\n\n```typescript\nconst getProvider = (providerId?: string) =>\n  providersHost.get<YourProvider>(\n    providerId ?? providersHost.getActive('yourkind'), 'yourkind',\n  );\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetch(query, providerId?) {\n    const provider = getProvider(providerId);\n    if (!provider) throw new Error('No provider available');\n    return provider.fetch(query);\n  },\n});\n```\n\n**Fan-out** (aggregate across all providers - dashboard):\n\n```typescript\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetchAll() {\n    const providers = providersHost.list('yourkind') as YourProvider[];\n    const results = await Promise.allSettled(providers.map(async (provider) => {\n      const items = await provider.fetch();\n      return { providerId: provider.id, providerName: provider.name, items };\n    }));\n    return results.filter(isFulfilled).map((r) => r.value);\n  },\n});\n```\n\nReference: `packages/player/src/services/metadataHost.ts` (single), `packages/player/src/services/dashboardHost.ts` (fan-out)\n\n## Error handling\n\n- **Store-backed hosts**: let errors propagate naturally.\n- **Provider-backed, single provider** (metadata, streaming): throw errors so the user sees failures.\n- **Provider-backed, fan-out** (dashboard): catch per-provider so one failure doesn't block others. Log via `reportError`, return partial results.\n- `MissingCapabilityError`: always a plugin bug. Single-provider domains throw it; fan-out domains log it and skip the offending provider.\n","AGENTS.md":"# AGENTS.md\n\nGuidelines for AI coding agents working on Nuclear.\n\n## Project Overview\n\nNuclear is a free, open-source music player without ads or tracking. Search for any song or artist, build playlists, and start listening. It's a desktop app built with Tauri (Rust + React), organized as a pnpm monorepo managed with Turborepo.\n\n### Packages\n\n- `@nuclearplayer/player` - Main Tauri app (React + Rust)\n- `@nuclearplayer/ui` - Shared UI components\n- `@nuclearplayer/plugin-sdk` - Plugin system (published to npm)\n- `@nuclearplayer/model` - Data model\n- `@nuclearplayer/themes` - Theming system\n- `@nuclearplayer/hifi` - Advanced HTML5 audio component\n- `@nuclearplayer/tailwind-config` - Shared Tailwind config\n- `@nuclearplayer/eslint-config` - Shared linting rules\n- `@nuclearplayer/i18n` - Internationalization\n- `@nuclearplayer/storybook` - Component demos\n- `@nuclearplayer/videos` - Remotion videos rendered from UI components\n- `@nuclearplayer/tools` - Build and maintenance utilities\n- `@nuclearplayer/docs` - Documentation\n- `@nuclearplayer/website` - Project website (Astro)\n\n## Commands\n\n```bash\n# Development\npnpm dev                    # Run player in dev mode\npnpm dev:remote             # Same, but binds Vite to 0.0.0.0 so the remote control UI is reachable from other devices\npnpm storybook              # Run Storybook\npnpm --filter @nuclearplayer/videos dev      # Run the Remotion Studio\npnpm --filter @nuclearplayer/videos render   # Render a video to packages/videos/out\n\n# Build\npnpm build                  # Build all packages\npnpm tauri build            # Build Tauri app\n\n# Quality\npnpm lint                   # Lint all packages\npnpm lint:fix               # Lint and auto-fix\npnpm type-check             # TypeScript checks\npnpm test                   # Run all tests\npnpm test:coverage          # Run tests with coverage\npnpm clean                  # Clean build artifacts\n\n# Package-specific testing\npnpm --filter @nuclearplayer/ui test -- src/components/Badge/Badge.test.tsx\npnpm --filter @nuclearplayer/ui test -- --testNamePattern=\"renders\"\n\n# Update snapshots (run at root for all, or filter to a specific package)\n\n# At root\npnpm test -- -u\n\n# Filtering for a specific package\npnpm --filter @nuclearplayer/ui test -- -u\n\n# After cd'ing into a package\npnpm test -u\n```\n\n## Code Style\n\n### General Principles\n\n- Prioritize readability over cleverness\n- No comments in code - explain reasoning in chat/commits\n- Avoid premature abstractions - start concrete, extract later\n- Small, focused changes over large dumps\n- Never commit unless explicitly asked\n\n### TypeScript\n\n- Use `type` not `interface` (except when merging is required)\n- Strict mode with `noUnusedLocals` and `noUnusedParameters`\n- Do not use one-letter variable names.\nAVOID: `(b) => b.buildIndexEntry()`\nPREFER: `(build) => build.buildIndexEntry()`\n\n### React Components\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\n\nimport { cn } from '../../utils';\n\nconst componentVariants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype ComponentProps = ComponentProps<'div'> &\n  VariantProps<typeof componentVariants>;\n\nexport const Component: FC<ComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(componentVariants({ variant, className }))} {...props} />\n);\n```\n\n- Use `const Component: FC<Props>` not `function Component()`\n- Compound components (`Component.Sub`) for complex widgets\n- Keep business logic out of UI components\n\n### Adding UI Components\n\nWhen adding a new component to `@nuclearplayer/ui`:\n\n1. Create component directory: `packages/ui/src/components/MyComponent/`\n   - `MyComponent.tsx` - implementation\n   - `MyComponent.test.tsx` - tests (aim for 100% coverage)\n   - `index.ts` - re-exports\n2. Export from `packages/ui/src/components/index.ts`\n3. Add Storybook story in `packages/storybook/src/MyComponent.stories.tsx`\n4. Include snapshot test(s) covering all variants\n\n### Styling (Tailwind v4)\n\n- CSS-first config in `packages/tailwind-config/global.css`\n- Theme colors: `bg-background`, `text-foreground`, `bg-primary`\n- Accents: `accent-green`, `accent-yellow`, `accent-purple`, `accent-blue`, `accent-orange`, `accent-cyan`, `accent-red`\n- Use `cn()` for conditional classes, `cva()` for variants\n\n### State Management\n\n- **Zustand** - persistent UI state\n- **React state** - local, temporary state\n- **TanStack Query v5** - HTTP requests/server state\n- **TanStack Router** - client-side routing\n\n### Standardized Libraries\n\n- **Icons**: Lucide React (not heroicons, not font-awesome)\n- **Toasts**: Sonner\n- **Dates**: Luxon\n- **Utilities**: lodash-es (use individual imports: `import isEqual from 'lodash-es/isEqual'`)\n- **HTTP**: Native fetch via ApiClient base class (no axios)\n\n### Adding New Domains\n\nA \"domain\" is a feature area exposed to plugins (e.g., settings, queue, favorites). When adding a new domain:\n\n1. **Types** (`packages/plugin-sdk/src/types/myDomain.ts`)\n   - Define the `MyDomainHost` interface (the contract between player and SDK)\n   - Export any related types plugins will use\n\n2. **API class** (`packages/plugin-sdk/src/api/myDomain.ts`)\n   - Create a class that wraps the host and exposes methods to plugins\n   - Add to `NuclearAPI` constructor in `packages/plugin-sdk/src/api/index.ts`\n\n3. **Store** (`packages/player/src/stores/myDomainStore.ts`)\n   - Zustand store holding the domain state\n   - Persists to disk via `@tauri-apps/plugin-store` if needed\n\n4. **Host** (`packages/player/src/services/myDomainHost.ts`)\n   - Implements the `MyDomainHost` interface\n   - Bridges the SDK API to the Zustand store\n   - Passed to `NuclearAPI` when initializing plugins\n\n### External API Clients\n\nLive in `packages/player/src/apis/`. Use `ApiClient` base class (fetch→json→Zod).\n\n- Validate external data with Zod schemas\n- Export singleton instances\n- One class per external service\n\n### Internationalization\n\nAll user-facing strings go through i18n - no hardcoded UI text.\n\n```tsx\nimport { useTranslation } from '@nuclearplayer/i18n';\n\nconst { t } = useTranslation();\n<span>{t('navigation.settings')}</span>\n```\n\nAdd new strings to `packages/i18n/src/locales/en_US.json` only. Other locales come from Crowdin.\n\n## Testing\n\nTests use Vitest + React Testing Library. Globals enabled (`describe`, `it`, `expect`, `vi`).\n\n- Integration tests over unit tests for user-facing behavior. Render real components and assert on DOM content rather than verifying mock calls.\n- Unit tests for utilities - standalone data structures (RingBuffer, parsers) deserve isolated tests. Use them sparingly.\n- Test user behavior, not implementation details\n- Minimize mocks - only mock external deps (HTTP, FS, Tauri)\n- Snapshot tests: prefix with `(Snapshot)`, basic rendering only\n- Never use `querySelector` in tests. Prefer RTL queries.\n- When semantic queries aren't possible, add `data-testid` attributes. And don't be shy with them\n- Don't use defensive measures like try-catch or conditional checks in tests. The test will fail anyway if our assumptions are wrong.\n\n### Test-first for views\n\nWhen building a new view, write the test wrapper and tests **before** any implementation code. The tests describe what the user sees and does — they define the contract. Then implement to make them pass.\n\nDon't start with unit tests for internal utilities (grouping functions, registries, etc.). Start from the outside: what does the user see on the page? The internal structure is an implementation detail that falls out of making the tests green.\n\n### Test Wrappers for Views\n\nPlayer views and some components use a `*.test-wrapper.tsx` file that creates a domain-specific abstraction layer over the DOM. This lets tests read like user stories, and if the implementation changes, only the wrapper needs updating.\n\n**Wrapper conventions:**\n- Use **getters** for element queries: `get emptyState()`, `get cards()` — not `getEmptyState()`\n- Use **nested objects** for interactive elements: `createButton: { get element(), async click() }`\n- Use **methods** for multi-step user actions: `async openContextMenu(title: string)`\n- Tests should use `Wrapper.emptyState`, `Wrapper.cards`, `Wrapper.createButton.click()` — not bare `screen` queries\n- The wrapper is the only place that knows about test IDs, roles, and DOM structure\n- Don't use queryX methods in the wrapper - always get or find as appropriate.\n- Never use fireEvent. Always use userEvent for interactions.\n\n### Test fixtures\n\nTo populate the app with testing data, use fixtures. See `packages/player/src/test/fixtures` for examples.\n\n### Wrapper fixtures\n\nTest wrappers can expose a `fixtures` object with factory methods that return pre-configured builders for common test scenarios. This keeps test setup readable and co-located with the wrapper, while the raw fixture data itself lives in `packages/player/src/test/fixtures/`.\n\n```tsx\n// Dashboard.test-wrapper.tsx\nimport { TOP_TRACKS_RADIOHEAD } from '../../test/fixtures/dashboard';\n\nexport const DashboardWrapper = {\n  // ... mount, getters, etc.\n\n  fixtures: {\n    topTracksProvider() {\n      return new DashboardProviderBuilder()\n        .withCapabilities('topTracks')\n        .withFetchTopTracks(async () => TOP_TRACKS_RADIOHEAD);\n    },\n  },\n};\n\n// Dashboard.test.tsx\nDashboardWrapper.seedProvider(DashboardWrapper.fixtures.topTracksProvider());\n```\n\n### The builder pattern for tests\n\nWe use builders to create test data and various entities cleanly. You can see them in `packages/player/src/test/builders`.\n\n- A builder is a class that has an instance of the object it's building\n- When the builder is instantiated, it creates a default object with reasonable defaults\n- The builder has methods that mutate the object and return `this` for chaining\n- The `build()` method returns the final object, which can then be used in tests\n\n```tsx\n// Playlists.test-wrapper.tsx\nexport const PlaylistsWrapper = {\n  async mount(): Promise<RenderResult> { /* ... */ },\n\n  get emptyState() {\n    return screen.queryByTestId('empty-state');\n  },\n  get cards() {\n    return screen.queryAllByTestId('card');\n  },\n\n  createButton: {\n    get element() {\n      return screen.getByTestId('create-playlist-button');\n    },\n    async click() {\n      await userEvent.click(this.element);\n    },\n  },\n};\n\n// Playlists.test.tsx — reads like a user story\nit('shows empty state when no playlists', async () => {\n  await PlaylistsWrapper.mount();\n  expect(PlaylistsWrapper.emptyState).toBeInTheDocument();\n});\n```\n\n## File Organization\n\n```\npackages/ui/src/components/Badge/\n  Badge.tsx           # Implementation\n  Badge.test.tsx      # Tests\n  index.ts            # Re-exports\n  __snapshots__/      # Vitest snapshots\n```\n\n## Rust Backend\n\nThe Tauri backend lives in `packages/player/src-tauri/src/`. Modules:\n\n- `bridge/` - bidirectional RPC. Lets Rust servers call into the frontend (`Bridge::call` emits a `bridge:request` event, frontend replies via the `bridge_respond` command).\n- `http_api/` - Axum REST + SSE server for Nuclear Jam remote control\n- `mcp/` - MCP server exposing player functions as tools to LLM clients\n- `mpd/` - MPD-protocol TCP server for clients like ncmpcpp\n- `stream_server.rs` - local audio proxy adding CORS + Range so the browser can play blocked streams\n- `http.rs` - `http_fetch` command, a CORS-bypassing HTTP proxy for the frontend\n- `ytdlp.rs` / `ytdlp_setup.rs` - yt-dlp subprocess wrapper; auto-downloads the binary\n- `discord.rs` - Discord Rich Presence\n- `commands.rs` - filesystem helpers (zip, download, flatpak detection)\n- `net.rs`, `setup.rs`, `logging.rs` - port binding, log plugin config, startup log ring buffer\n- `lib.rs` - declares modules, registers all commands, runs `init_*` in `.setup()`. `main.rs` - binary entry, env fixups.\n\nCommands (run from `packages/player/src-tauri/`):\n\n```bash\ncargo test          # Rust tests (no clippy/rustfmt configured; run manually)\n```\n\n`pnpm dev` runs `tauri dev`; `pnpm build` (in `packages/player`) runs the full `tauri build`.\n\n## Design Philosophy\n\n- Neo-brutalist with premium polish - bold borders, purposeful shadows\n- Premium, designed feel\n- Animations via `motion` and `tw-animate-css`\n- Disable animations during high-friction moments (resize, drag)\n- Avoid generic AI patterns (icon-grid cards, stock heroes, \"Built with love\" badges)\n\n## Tooling Notes\n\n- **pnpm** with workspace protocol for internal deps\n- **Turborepo** for task orchestration\n- **ESLint + Prettier** run together\n- **Husky + lint-staged** for pre-commit hooks\n\nUse centralized configs from eslint-config and tailwind-config packages.\n\nAssume TanStack Router routes regenerate on dev - don't regenerate manually.\n\n## Changelog\n\n`packages/player/changelog.json` is the source of truth for the in-app \"What's New\" tab and auto-generated GitHub release notes.\n\nWhen building a user-facing feature, fix, or improvement, add an entry to the top of the array according to the format you find there.\n\n## Releasing\n\n### Nuclear Player\n\nReleases are triggered by git tags. The workflow builds for macOS (arm64/x64), Linux, and Windows. Release notes are auto-generated from `packages/player/changelog.json`.\n\n```bash\n# 1. Bump versions, update the appstream metainfo, commit, and tag\npnpm release:prepare X.Y.Z\n\n# 2. Push the tag\ngit push origin player@X.Y.Z\n```\n\n`release:prepare` does everything that's needed for a release. Do not bump versions or edit any files by hand when preparing a release.\n\nThe `release-player.yml` workflow creates a GitHub release with platform binaries.\n\n### Plugin SDK\n\nPublished to npm via the `release-plugin-sdk.yml` workflow.\n\n```bash\n# 1. Update version in packages/plugin-sdk/package.json\n# 2. Commit the version bump\ngit add packages/plugin-sdk/package.json && git commit -m \"plugin-sdk@X.Y.Z\"\n\n# 3. Tag and push\ngit tag plugin-sdk@X.Y.Z\ngit push origin plugin-sdk@X.Y.Z\n```\n\nThe workflow builds with `build:npm`, runs tests, and publishes to npm.\n","packages/website/tsconfig.json":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"declaration\": false,\n    \"declarationMap\": false\n  },\n  \"include\": [\"src/**/*\", \"astro.config.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n","packages/ui/package.json":"{\n  \"name\": \"@nuclearplayer/ui\",\n  \"version\": \"0.0.10\",\n  \"description\": \"Shared UI component library for Nuclear\",\n  \"type\": \"module\",\n  \"main\": \"./src/index.ts\",\n  \"module\": \"./src/index.ts\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./src/index.ts\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./assets/*\": \"./src/assets/*\"\n  },\n  \"files\": [\n    \"src\"\n  ],\n  \"sideEffects\": [\n    \"*.css\",\n    \"./src/index.ts\"\n  ],\n  \"scripts\": {\n    \"dev\": \"vite build --watch\",\n    \"build\": \"tsc && vite build\",\n    \"test\": \"vitest --run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest --run --coverage\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"type-check\": \"tsc --noEmit\",\n    \"clean\": \"rm -rf dist build .turbo node_modules/.vite\"\n  },\n  \"dependencies\": {\n    \"@dnd-kit/core\": \"^6.3.1\",\n    \"@dnd-kit/sortable\": \"^10.0.0\",\n    \"@dnd-kit/utilities\": \"^3.2.2\",\n    \"@floating-ui/react-dom\": \"^2.1.7\",\n    \"@fontsource/bricolage-grotesque\": \"^5.2.8\",\n    \"@fontsource/dm-sans\": \"^5.2.6\",\n    \"@fontsource/space-mono\": \"^5.2.9\",\n    \"@headlessui/react\": \"^2.2.7\",\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@nuclearplayer/model\": \"workspace:*\",\n    \"@nuclearplayer/tailwind-config\": \"workspace:*\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.16\",\n    \"@tanstack/react-router\": \"^1.170.35\",\n    \"@tanstack/react-table\": \"^8.21.3\",\n    \"@tanstack/react-virtual\": \"^3.14.11\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"d3-scale\": \"^4.0.2\",\n    \"d3-shape\": \"^3.2.0\",\n    \"lodash-es\": \"^4.17.21\",\n    \"lucide-react\": \"^0.542.0\",\n    \"luxon\": \"^3.7.1\",\n    \"react-activity-calendar\": \"3.2.1\",\n    \"recharts\": \"^3.9.2\",\n    \"tailwind-merge\": \"^3.3.1\",\n    \"tailwindcss\": \"^4.1.11\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/vite\": \"^4.1.11\",\n    \"@testing-library/jest-dom\": \"^7.0.0\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@testing-library/user-event\": \"^14.6.1\",\n    \"@types/d3-scale\": \"^4.0.9\",\n    \"@types/d3-shape\": \"^3.1.8\",\n    \"@types/lodash-es\": \"^4.17.12\",\n    \"@types/luxon\": \"^3.7.1\",\n    \"@types/node\": \"^25.4.0\",\n    \"@types/react\": \"^18.3.23\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"@vitejs/plugin-react\": \"^6.0.0\",\n    \"@vitest/coverage-v8\": \"^5.0.0\",\n    \"jsdom\": \"^26.1.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^8.0.0\",\n    \"vite-plugin-dts\": \"^5.0.0\",\n    \"vite-plugin-svgr\": \"^5.0.0\",\n    \"vitest\": \"^5.0.0\"\n  },\n  \"peerDependencies\": {\n    \"motion\": \"^12.23.12 || ^13.0.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"sonner\": \"^2.0.7\"\n  }\n}\n","packages/ui/tsconfig.json":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"noEmit\": false,\n    \"allowImportingTsExtensions\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"types\": [\n      \"node\",\n      \"vitest/globals\",\n      \"@testing-library/jest-dom\",\n      \"vite-plugin-svgr/client\"\n    ]\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"src/**/*.test.ts\", \"src/**/*.test.tsx\"]\n}\n","packages/tools/package.json":"{\n  \"name\": \"@nuclearplayer/tools\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"test\": \"vitest --run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest --run --coverage\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\"\n  },\n  \"devDependencies\": {\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@vitest/coverage-v8\": \"^5.0.0\",\n    \"typescript\": \"^5.9.2\",\n    \"vitest\": \"^5.0.0\"\n  }\n}\n","packages/tools/tsconfig.json":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"vitest/globals\"]\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n","packages/themes/package.json":"# Themes Skill\n\nPath: `packages/themes/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/package.json)","packages/themes/README.md":"# themes Documentation\n\nPath: `packages/themes/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/README.md)","packages/themes/tsconfig.json":"# Themes Skill\n\nPath: `packages/themes/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/tsconfig.json)","packages/tailwind-config/package.json":"# Tailwind-config Skill\n\nPath: `packages/tailwind-config/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/package.json)","packages/tailwind-config/tsconfig.json":"# Tailwind-config Skill\n\nPath: `packages/tailwind-config/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/tsconfig.json)","packages/storybook/package.json":"# Storybook Skill\n\nPath: `packages/storybook/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/package.json)","packages/storybook/tsconfig.json":"# Storybook Skill\n\nPath: `packages/storybook/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/tsconfig.json)","packages/plugin-sdk/api-extractor.json":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/api-extractor.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/api-extractor.json)","packages/plugin-sdk/package.json":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/package.json)","packages/plugin-sdk/README.md":"# plugin-sdk Documentation\n\nPath: `packages/plugin-sdk/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/README.md)","packages/plugin-sdk/tsconfig.json":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/tsconfig.json)","packages/player/changelog.json":"# Player Skill\n\nPath: `packages/player/changelog.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/changelog.json)","packages/player/package.json":"# Player Skill\n\nPath: `packages/player/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/package.json)","packages/player/tsconfig.json":"# Player Skill\n\nPath: `packages/player/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.json)","packages/player/tsconfig.node.json":"# Player Skill\n\nPath: `packages/player/tsconfig.node.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.node.json)","packages/model/package.json":"# Model Skill\n\nPath: `packages/model/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/package.json)","packages/model/tsconfig.json":"# Model Skill\n\nPath: `packages/model/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/tsconfig.json)","packages/i18n/package.json":"# I18n Skill\n\nPath: `packages/i18n/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/package.json)","packages/i18n/tsconfig.json":"# I18n Skill\n\nPath: `packages/i18n/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/tsconfig.json)","packages/hifi/package.json":"# Hifi Skill\n\nPath: `packages/hifi/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/package.json)","packages/hifi/tsconfig.json":"# Hifi Skill\n\nPath: `packages/hifi/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/tsconfig.json)","packages/eslint-config/package.json":"# Eslint-config Skill\n\nPath: `packages/eslint-config/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/package.json)","packages/eslint-config/tsconfig.json":"# Eslint-config Skill\n\nPath: `packages/eslint-config/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/tsconfig.json)","packages/docs/.gitbook.yaml":"# Docs Skill\n\nPath: `packages/docs/.gitbook.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/.gitbook.yaml)","packages/docs/package.json":"# Docs Skill\n\nPath: `packages/docs/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/package.json)","packages/docs/README.md":"# docs Documentation\n\nPath: `packages/docs/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/README.md)","packages/docs/SUMMARY.md":"# Docs Skill\n\nPath: `packages/docs/SUMMARY.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/SUMMARY.md)","packages/player/src-tauri/tauri.conf.json":"# Src-tauri Skill\n\nPath: `packages/player/src-tauri/tauri.conf.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/tauri.conf.json)","packages/docs/core-concepts/favorites.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/favorites.md)","packages/docs/core-concepts/how-nuclear-works.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/how-nuclear-works.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/how-nuclear-works.md)","packages/docs/core-concepts/listening-history.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-history.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-history.md)","packages/docs/core-concepts/listening-stats.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-stats.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-stats.md)","packages/docs/core-concepts/playlists.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/playlists.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/playlists.md)","packages/docs/core-concepts/plugins-and-providers.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/plugins-and-providers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/plugins-and-providers.md)","packages/docs/core-concepts/the-queue.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/the-queue.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/the-queue.md)","packages/docs/development/contributing.md":"# Development Skill\n\nPath: `packages/docs/development/contributing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/contributing.md)","packages/docs/development/host-pattern.md":"# Development Skill\n\nPath: `packages/docs/development/host-pattern.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/host-pattern.md)","packages/docs/development/logging.md":"# Development Skill\n\nPath: `packages/docs/development/logging.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/logging.md)","packages/docs/development/mcp-architecture.md":"# Development Skill\n\nPath: `packages/docs/development/mcp-architecture.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/mcp-architecture.md)","packages/docs/integrations/http-api.md":"# Integrations Skill\n\nPath: `packages/docs/integrations/http-api.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/http-api.md)","packages/docs/integrations/mcp-server.md":"# Integrations Skill\n\nPath: `packages/docs/integrations/mcp-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mcp-server.md)","packages/docs/integrations/mpd-server.md":"# Integrations Skill\n\nPath: `packages/docs/integrations/mpd-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mpd-server.md)","packages/docs/misc/discovery.md":"# Misc Skill\n\nPath: `packages/docs/misc/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/discovery.md)","packages/docs/misc/keyboard-shortcuts.md":"# Misc Skill\n\nPath: `packages/docs/misc/keyboard-shortcuts.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/keyboard-shortcuts.md)","packages/docs/misc/platform-specific.md":"# Misc Skill\n\nPath: `packages/docs/misc/platform-specific.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/platform-specific.md)","packages/docs/plugins/dashboard.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/dashboard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/dashboard.md)","packages/docs/plugins/discovery.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/discovery.md)","packages/docs/plugins/events.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/events.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/events.md)","packages/docs/plugins/favorites.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/favorites.md)","packages/docs/plugins/getting-started.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/getting-started.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/getting-started.md)","packages/docs/plugins/http.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/http.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/http.md)","packages/docs/plugins/logger.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/logger.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/logger.md)","packages/docs/plugins/metadata.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/metadata.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/metadata.md)","packages/docs/plugins/playback.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/playback.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playback.md)","packages/docs/plugins/playlists.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/playlists.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playlists.md)","packages/docs/plugins/plugin-store.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/plugin-store.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-store.md)","packages/docs/plugins/plugin-system.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/plugin-system.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-system.md)","packages/docs/plugins/providers.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/providers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/providers.md)","packages/docs/plugins/publishing.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/publishing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/publishing.md)","packages/docs/plugins/queue.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/queue.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/queue.md)","packages/docs/plugins/settings.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/settings.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/settings.md)","packages/docs/plugins/shell.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/shell.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/shell.md)","packages/docs/plugins/streaming.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/streaming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/streaming.md)","packages/docs/plugins/ytdlp.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/ytdlp.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/ytdlp.md)","packages/docs/themes/theme-store.md":"# Themes Skill\n\nPath: `packages/docs/themes/theme-store.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/theme-store.md)","packages/docs/themes/themes.md":"# Themes Skill\n\nPath: `packages/docs/themes/themes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes.md)","packages/docs/themes/themes-advanced.md":"# Themes Skill\n\nPath: `packages/docs/themes/themes-advanced.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-advanced.md)","packages/docs/themes/themes-basic.md":"# Themes Skill\n\nPath: `packages/docs/themes/themes-basic.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-basic.md)","packages/docs/user-manual/getting-started.md":"# User-manual Skill\n\nPath: `packages/docs/user-manual/getting-started.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/getting-started.md)","packages/docs/user-manual/installation.md":"# User-manual Skill\n\nPath: `packages/docs/user-manual/installation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/installation.md)","packages/docs/user-manual/remote-control.md":"# User-manual Skill\n\nPath: `packages/docs/user-manual/remote-control.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/remote-control.md)","packages/player/src-tauri/capabilities/default.json":"# Capabilities Skill\n\nPath: `packages/player/src-tauri/capabilities/default.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/default.json)","packages/player/src-tauri/capabilities/desktop.json":"# Capabilities Skill\n\nPath: `packages/player/src-tauri/capabilities/desktop.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/desktop.json)","packages/i18n/src/locales/de_DE.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/de_DE.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/de_DE.json)","packages/i18n/src/locales/en_US.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/en_US.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/en_US.json)","packages/i18n/src/locales/es_ES.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/es_ES.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/es_ES.json)","packages/i18n/src/locales/fr_FR.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/fr_FR.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/fr_FR.json)","packages/i18n/src/locales/it_IT.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/it_IT.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/it_IT.json)","packages/i18n/src/locales/ja_JP.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/ja_JP.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ja_JP.json)","packages/i18n/src/locales/pl_PL.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/pl_PL.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pl_PL.json)","packages/i18n/src/locales/pt_BR.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/pt_BR.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pt_BR.json)","packages/i18n/src/locales/ru_RU.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/ru_RU.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ru_RU.json)","packages/i18n/src/locales/zh_CN.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/zh_CN.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/zh_CN.json)"},"files":{".agents/skills/writing-docs/SKILL.md":"---\nname: writing-docs\ndescription: Use when writing or editing documentation in packages/docs. Covers Gitbook markdown syntax, special blocks, page structure, and the SUMMARY.md table of contents. Trigger phrases include \"write docs\", \"add documentation\", \"docs page\", \"gitbook\", \"user manual\".\n---\n\n# Writing Documentation\n\nDocs live in `packages/docs/`. Hosted on Gitbook, synced from the repo via `.gitbook.yaml`.\n\n## Page structure\n\nEvery page starts with YAML frontmatter and a top-level heading:\n\n```markdown\n---\ndescription: Short description shown in search and link previews\n---\n\n# Page title\n```\n\n## Table of contents\n\n`packages/docs/SUMMARY.md` defines the sidebar navigation. Group pages under `##` headings:\n\n```markdown\n# Table of contents\n\n* [Nuclear Documentation](README.md)\n\n## User Manual\n\n* [Getting started](user-manual/getting-started.md)\n* [Installation](user-manual/installation.md)\n\n## Plugins\n\n* [Getting started](plugins/getting-started.md)\n```\n\nWhen adding a new page, always add it to SUMMARY.md or it won't appear in the sidebar.\n\n## Gitbook special blocks\n\n### Tabs\n\nUse for platform-specific instructions or language variants:\n\n```markdown\n{% tabs %}\n\n{% tab title=\"Windows\" %}\nContent for Windows.\n{% endtab %}\n\n{% tab title=\"macOS\" %}\nContent for macOS.\n{% endtab %}\n\n{% tab title=\"Linux\" %}\nContent for Linux.\n{% endtab %}\n\n{% endtabs %}\n```\n\nTabs can contain any block type (code, tables, lists, images) but cannot nest other tabs or expandables.\n\n### Hints (callouts)\n\nFour styles: `info`, `success`, `warning`, `danger`.\n\n```markdown\n{% hint style=\"info\" %}\nGeneral information or tips.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nNon-critical warnings or gotchas.\n{% endhint %}\n\n{% hint style=\"danger\" %}\nDestructive actions or critical information.\n{% endhint %}\n\n{% hint style=\"success\" %}\nPositive actions or confirmations.\n{% endhint %}\n```\n\n### Code blocks with options\n\nWrap in `{% code %}` for title, line numbers, or wrapping:\n\n````markdown\n{% code title=\"config.json\" overflow=\"wrap\" lineNumbers=\"true\" %}\n\n```json\n{\n  \"key\": \"value\"\n}\n```\n\n{% endcode %}\n````\n\nWithout the wrapper, standard fenced code blocks work fine.\n\n### Expandable (collapsible sections)\n\nUses standard HTML `<details>` / `<summary>`:\n\n```markdown\n<details>\n\n<summary>Click to expand</summary>\n\nHidden content goes here. Supports lists and code blocks.\n\n</details>\n```\n\nAdd `open` attribute to expand by default: `<details open>`.\n\n### Page links\n\nProminent block-level links to other pages:\n\n```markdown\n{% content-ref url=\"getting-started.md\" %}\n[Getting started](getting-started.md)\n{% endcontent-ref %}\n```\n\n### Cards\n\nGrid layout for landing pages. Uses a table with `data-view=\"cards\"`:\n\n```markdown\n<table data-view=\"cards\">\n  <thead>\n    <tr>\n      <th></th>\n      <th></th>\n      <th data-hidden data-card-target data-type=\"content-ref\"></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><strong>Card title</strong></td>\n      <td>Card description.</td>\n      <td><a href=\"page.md\">page.md</a></td>\n    </tr>\n  </tbody>\n</table>\n```\n\n### Annotations (footnotes)\n\n```markdown\nNuclear uses plugins[^1] to provide functionality.\n\n[^1]: Plugins are installed from the plugin store.\n```\n\n## Content guidelines\n\n- No brand names of third-party services in docs\n- All user-facing strings reference i18n keys, not hardcoded text\n- Use relative links between docs pages (`../themes/themes.md`), not absolute URLs\n- Keep pages focused on one topic. Link to related pages instead of duplicating content.\n- Describe the UI as the user sees it: use actual button labels, menu names, and icon descriptions\n","packages/website/package.json":"{\n  \"name\": \"@nuclearplayer/website\",\n  \"version\": \"2.17.2\",\n  \"description\": \"Nuclear website\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"build\": \"astro build\",\n    \"preview\": \"astro preview\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"type-check\": \"astro check\",\n    \"clean\": \"rm -rf dist .turbo node_modules/.cache .astro\"\n  },\n  \"dependencies\": {\n    \"@astrojs/markdown-remark\": \"^7.3.1\",\n    \"@astrojs/mdx\": \"^8.0.0\",\n    \"@astrojs/react\": \"^6.0.0\",\n    \"@astrojs/rss\": \"^4.0.18\",\n    \"@astrojs/sitemap\": \"^3.7.0\",\n    \"@fontsource-variable/bricolage-grotesque\": \"^5.2.10\",\n    \"@fontsource-variable/dm-sans\": \"^5.2.8\",\n    \"@fontsource/space-mono\": \"^5.2.9\",\n    \"@iconify-json/simple-icons\": \"^1.2.67\",\n    \"@nuclearplayer/ui\": \"workspace:*\",\n    \"astro\": \"^7.0.0\",\n    \"astro-expressive-code\": \"^0.44.0\",\n    \"astro-icon\": \"^1.1.5\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.542.0\",\n    \"medium-zoom\": \"^1.1.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\"\n  },\n  \"devDependencies\": {\n    \"@astrojs/check\": \"^0.9.6\",\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@nuclearplayer/tailwind-config\": \"workspace:*\",\n    \"@tailwindcss/typography\": \"^0.5.19\",\n    \"@tailwindcss/vite\": \"^4.1.11\",\n    \"@types/react\": \"^18.3.23\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"sharp\": \"^0.35.0\",\n    \"tailwindcss\": \"^4.1.11\",\n    \"typescript\": \"^5.9.2\"\n  }\n}\n",".agents/skills/writing-plugins/SKILL.md":"---\nname: writing-plugins\ndescription: Use when writing, scaffolding, or modifying Nuclear plugins. Covers plugin structure, manifest, entry point, provider types, available APIs, and publishing. Trigger phrases include \"create a plugin\", \"write a plugin\", \"plugin scaffold\", \"streaming provider\", \"metadata provider\".\n---\n\n# Writing Nuclear Plugins\n\nPlugins are standalone repos compiled in-browser via esbuild-wasm.\n\n## Structure\n\n```\nmy-plugin/\n  package.json      # Manifest with nuclear metadata\n  src/\n    index.ts        # Entry point, default exports NuclearPlugin\n```\n\n## Manifest (package.json)\n\n```json\n{\n  \"name\": \"nuclear-plugin-example\",\n  \"version\": \"0.1.0\",\n  \"description\": \"What this plugin does\",\n  \"author\": \"Your Name\",\n  \"license\": \"AGPL-3.0-only\",\n  \"main\": \"src/index.ts\",\n  \"type\": \"module\",\n  \"nuclear\": {\n    \"displayName\": \"Example Plugin\",\n    \"category\": \"streaming\",\n    \"icon\": { \"type\": \"link\", \"link\": \"https://example.com/icon.svg\" }\n  },\n  \"dependencies\": {\n    \"@nuclearplayer/plugin-sdk\": \"^1.1.0\"\n  }\n}\n```\n\nCategories: `streaming`, `metadata`, `lyrics`\n\n## Entry Point\n\n```typescript\nimport type { NuclearPlugin, NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';\n\nconst plugin: NuclearPlugin = {\n  onLoad(api: NuclearPluginAPI) {},\n  onEnable(api: NuclearPluginAPI) {\n    // Register providers\n  },\n  onDisable() {\n    // Unregister providers\n  },\n  onUnload() {},\n};\n\nexport default plugin;\n```\n\n## Provider Types\n\n**Streaming** - resolve tracks to playable audio:\n\n```typescript\nconst provider: StreamingProvider = {\n  id: 'my-streaming',\n  kind: 'streaming',\n  name: 'My Streaming',\n  async searchForTrack(artist, title, album?) { /* return StreamCandidate[] */ },\n  async getStreamUrl(candidateId) { /* return Stream */ },\n};\n\napi.Providers.register(provider);\napi.Providers.unregister('my-streaming');\n```\n\n**Metadata** - search artists/albums, fetch details:\n\n```typescript\nconst provider: MetadataProvider = {\n  id: 'my-metadata',\n  kind: 'metadata',\n  name: 'My Metadata',\n  searchCapabilities: ['artists', 'albums'],\n  streamingProviderId: 'my-streaming', // Optional: locks streaming to this provider\n  async searchArtists(params) { /* ... */ },\n  async searchAlbums(params) { /* ... */ },\n  async fetchArtistBio(id) { /* ... */ },\n  async fetchAlbumDetails(id) { /* ... */ },\n};\n```\n\n## Available APIs\n\n- `api.Providers` - register/unregister providers\n- `api.Settings` - plugin settings storage\n- `api.Http` - fetch wrapper\n- `api.Ytdlp` - yt-dlp integration\n- `api.Queue` - playback queue control\n- `api.Metadata` - search music metadata\n- `api.Streaming` - resolve streams\n- `api.Logger` - logging (trace/debug/info/warn/error)\n\n## Publishing\n\n1. Create a GitHub repo with the plugin code\n2. Add `.github/workflows/release.yml`:\n\n```yaml\nname: Release\non:\n  push:\n    tags: ['v*']\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@v4\n      - run: zip -r plugin.zip src package.json README.md\n      - uses: softprops/action-gh-release@v2\n        with:\n          files: plugin.zip\n          generate_release_notes: true\n```\n\n3. Tag release: `git tag v0.1.0 && git push origin v0.1.0`\n4. Submit PR to `NuclearPlayer/plugin-registry` adding your plugin to `plugins.json`\n",".github/copilot-instructions.md":"# Nuclear Music Player - Copilot Instructions\n\n## Project overview\n\nNuclear is a free, open-source music player without ads or tracking. Search for any song or artist, build playlists, and start listening.\n\nIt's a monorepo managed with pnpm and turbo.\n\nThis particular repo is a rewrite project, with the original Nuclear codebase in a different repo. \n\n### Key features\n\n- **Themes**: Support for basic and advanced themes, which work by customizing CSS variables that then control Tailwind classes. Users can load their own themes from JSON, or use built-in ones.\n- **Plugins**: A powerful plugin system that allows you to control any part of the player. No sandboxing.\n\n### Tech stack\n\n- Typescript - primary language.\n- Tauri - creates a native desktop application shell.\n- Tailwind v4 - CSS-first setup. Configured via CSS using @theme and @layer (see `packages/tailwind-config/global.css`). No tailwind.config.js. Don't use built-in colors, prefer the palette defined in `global.css`.\n- Turbo repo - tool for managing the monorepo.\n- pnpm - the preferred package manager. Used for workspaces.\n- eslint + prettier - formatting and linting.\n- Storybook - used for demoing UI components.\n- Vite - build tool\n- Vitest - for all tests\n- Lucide React - icon library\n- motion and tw-animate-css - for animations\n- TanStack Router - chosen solution for routing.\n- TanStack Query v5 - chosen solution for HTTP requests.\n\n### Packages\n- `@nuclearplayer/player` - Main Tauri app (React + Rust).  \n- `@nuclearplayer/plugin-sdk` - Plugin system (TS/React).  \n- `@nuclearplayer/ui` - Shared UI components.  \n- `@nuclearplayer/model` - Data model.\n- `@nuclearplayer/docs` - Gitbook documentation.\n- `@nuclearplayer/tailwind-config` - Shared Tailwind config.  \n- `@nuclearplayer/eslint-config` - Shared linting & formatting rules.  \n- `@nuclearplayer/hifi` - Advanced HTML5 audio component for playback.\n- `@nuclearplayer/themes` - Theming system.\n- `@nuclearplayer/tools` - Build and maintenance utilities.\n- `@nuclearplayer/storybook` - Storybook stories.\n\n### Workflow Commands\n\n```bash\npnpm dev        # run the project in dev mode\npnpm build      # build all packages\npnpm lint       # lint all packages\npnpm test       # test all packages\npnpm test:coverage  # run tests with coverage\npnpm type-check     # run TypeScript checks across packages\npnpm tauri          # run Tauri CLI for the player\npnpm storybook      # run Storybook\n```\n\n## Coding principles\n\n### General\n\n- Prioritize readability.\n- This is a production project meant for long term maintenance and development. Cutting corners, using short term solutions, placeholders, half-baked methods, and messy code is unacceptable.\n- Avoid premature abstractions. Start concrete, extract later.\n- Prefer clarity over cleverness.\n- Do not leave comments in code. Their place is in the chat.\n- Stick to existing conventions. This is a monorepo so standardizing everything is very very important. Look at other packages when in doubt. Use centralized configs for tools like Typescript, Eslint, Prettier, Tailwind.\n- Break work into the smallest reasonable steps. Small commits > big dumps.\n- Always be on the lookout for dead code, copy-pasta, and other opportunities to optimize and trim the codebase in a sensible way.\n\n### TypeScript / React\n- Use `type`, not `interface` (except when TS requires merging). Do not use interfaces for props.\n- Use `const Component: FC<Props> = () => {}` instead of `function Component()`.\n- No magic numbers → extract into named constants.\n- Prefer compound components (`Component.Sub`) when building complex widgets.\n- Keep business logic out of UI components.\n\n### Architecture\n- UI components should stay dumb, presentation-only.\n- State management:\n  - **Zustand** for persistent UI state.  \n  - **React state** for local, temporary state.\n  - **TanStack Query v5 (aka react-query)** for HTTP requests.\n- Routing:\n  - **TanStack Router** for client-side routing.\n- Lift complex or performance-critical logic to Tauri (Rust).\n\n### Testing\n- Use **Vitest + React Testing Library**.\n- Test like a user: minimal mocks, simulate interactions.\n- Only mock external dependencies (HTTP, FS, Tauri).\n- Snapshot tests: **basic rendering only**. Start names with `(Snapshot)`.\n- Extract DOM querying into wrappers; assertions stay in tests.\n- Coverage: enabled across packages with V8-based coverage and CI reporting.\n- Use the test runner tool instead of running tests in the terminal manually.\n\n### Design & UX Philosophy\n\n- Visual style: **neo-brutalist with premium polish**.\n- Animations should enhance UX, not slow it down.\n- Disable animations during high-friction moments (e.g., resize).\n- Use **motion** + **tw-animate-css** for smooth springy physics.\n- Feel: professional yet approachable, Discord-like.\n\n#### Typography\n\n- Fonts are standardized at the design system level. Prefer utilities `font-sans` and `font-heading` when needed.\n- Defaults are applied for common HTML tags (e.g., body, headings), so you rarely need to set fonts manually.\n\n## Final Notes\n\n- Treat this as **production code from day one**. No shortcuts, no placeholders.\n- Prioritize long-term maintainability.\n- Work with me iteratively: pause, summarize, ask.\n- Above all: **be reliable, disciplined, and clear.**\n- Use Serena tools wherever possible for searches, lookups, replacements, and file operations.",".agents/skills/creating-components/SKILL.md":"---\nname: creating-components\ndescription: Use when creating new UI components in packages/ui. Covers component structure, tests, stories, and what to avoid.\n---\n\n# Creating UI Components\n\n## Component Structure\n\n```\npackages/ui/src/components/MyComponent/\n├── MyComponent.tsx      # Implementation\n├── MyComponent.test.tsx # Tests\n├── index.ts             # Re-exports\n└── __snapshots__/       # Vitest snapshots (auto-generated)\n```\n\nAfter creating, export from `packages/ui/src/components/index.ts`.\n\n## Implementation Pattern\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\nimport { cn } from '../../utils';\n\nconst variants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype MyComponentProps = ComponentProps<'div'> & VariantProps<typeof variants>;\n\nexport const MyComponent: FC<MyComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(variants({ variant, className }))} {...props} />\n);\n```\n\n## Tests\n\n**What to test:**\n- Snapshots (1-2 covering key variants)\n- User interactions\n- Behavior (callbacks called with correct args)\n\n**What NOT to test:**\n- CSS classes, attributes (use snapshots instead)\n- Internal state\n- Things TypeScript already enforces\n\n**Consolidate tests.** One test can cover multiple related assertions. An exception to that is snapshot tests - one snapshot per variant/state.\n\n## Stories\n\nCreate `packages/storybook/src/MyComponent.stories.tsx`.\n\n**One story can show multiple related variants.** For example, it's wasteful to create a story for each variant of a button. Put them all together in one place. Don't create separate stories for each variant:\n\n```tsx\nimport { Meta, StoryObj } from '@storybook/react-vite';\nimport { useState } from 'react';\nimport { MyComponent } from '@nuclearplayer/ui';\n\nconst meta = {\n  title: 'Components/MyComponent',\n  component: MyComponent,\n  tags: ['autodocs'],\n} satisfies Meta<typeof MyComponent>;\n\nexport default meta;\ntype Story = StoryObj<typeof MyComponent>;\n\nexport const AllVariants: Story = {\n  render: () => {\n    const [state, setState] = useState(initialState);\n    return (\n      <div className=\"flex flex-col gap-4\">\n        {/* Show all variants, states, interactions */}\n      </div>\n    );\n  },\n};\n```\n\nThis isn't an iron rule, sometimes it will make more sense to have separate stories.\n\nDon't run storybook build checks.\n\n## Avoiding Duplication\n\nBefore creating a new component, check if an existing one can be extended.\n\n**Pattern: Discriminated unions for mode variants**\n\nExample: Instead of creating `SingleSelect` and `MultiSelect` components:\n\n```tsx\ntype Props = \n  | { multiple?: false; selected: string; onChange: (id: string) => void }\n  | { multiple: true; selected: string[]; onChange: (ids: string[]) => void };\n```\n\nTypeScript enforces correct types based on the `multiple` prop.\n\n## Classes for customization\n\nWhere it's likely that a component will need custom styling, expose a `className` prop.\n\nIf there are many parts that may need styling, consider exposing a `classes` prop with specific class names for each part. Define a type for the `classes` prop. Refer to `packages/ui/src/components/TrackTable/types.ts` for an example.\n\n## Strings\n\nAll user-facing strings go through i18n - no hardcoded UI text.\nIf a new component in the ui package needs labels and other kinds of localized text, it should accept a `labels` prop with the relevant strings. The prop should have its own type defined. Refer to `packages/ui/src/components/QueueItem/types.ts` for an example.\n\n## Accessibility\n\nWe don't care about that. If there's an opportunity to handle that easily, do it, but don't go out of your way.\n\n## Checklist\n\n- [ ] Component in `packages/ui/src/components/MyComponent/`\n- [ ] Exported from `packages/ui/src/components/index.ts`\n- [ ] Tests cover behavior, not implementation\n- [ ] Tests consolidated (not one per variant)\n- [ ] One story showing all variants\n- [ ] No CSS class assertions in tests\n- [ ] No duplicate component when extending existing one works\n",".agents/skills/host-pattern/SKILL.md":"---\nname: host-pattern\ndescription: Use when adding a new domain to Nuclear's plugin system, or implementing a host. Covers the host pattern (how player functionality is exposed to plugins), the host interface and API class structure, how hosts are implemented in the player, error handling conventions, and what files to create and modify. Trigger phrases include \"add a domain\", \"new domain\", \"host implementation\", \"host pattern\", \"createPluginAPI\".\n---\n\n# Host pattern\n\nEvery feature area follows the same three-layer structure:\n\n1. Host type - the contract (SDK, no implementation)\n2. API class - what plugins actually call (SDK, wraps the host)\n3. Host implementation - bridges the API to player internals (player)\n\nPlugins call methods on an API class (e.g. `api.Queue.addToQueue()`), which holds a reference to the host and delegates to it. Plugins never touch a host directly.\n\n## Files to create/modify\n\n### SDK (`packages/plugin-sdk/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/types/yourDomain.ts` | `YourDomainHost` interface + related types |\n| Create | `src/api/yourDomain.ts` | `YourDomainAPI` class |\n| Modify | `src/api/index.ts` | Add `yourDomainHost` option + `YourDomain` field to `NuclearAPI` |\n| Modify | `src/index.ts` | Export types and API class |\n\n### Player (`packages/player/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/services/yourDomainHost.ts` | Host implementation + singleton export |\n| Modify | `src/services/plugins/createPluginAPI.ts` | Pass singleton to `NuclearPluginAPI` |\n| Modify | `src/services/logger.ts` | Add domain to `LOG_SCOPES` (needed for `reportError`) |\n\nIf the domain needs shared model types: create `packages/model/src/yourDomain.ts` and re-export from `packages/model/src/index.ts`.\n\n## API class pattern\n\nEvery API class follows this structure.\n\n```typescript\n// packages/plugin-sdk/src/api/yourDomain.ts\nexport class YourDomainAPI {\n  #host?: YourDomainHost;\n\n  constructor(host?: YourDomainHost) {\n    this.#host = host;\n  }\n\n  #withHost<T>(fn: (host: YourDomainHost) => T): T {\n    const host = this.#host;\n    if (!host) {\n      throw new Error('YourDomain host not available');\n    }\n    return fn(host);\n  }\n\n  yourMethod(arg: SomeType) {\n    return this.#withHost((host) => host.yourMethod(arg));\n  }\n}\n```\n\nReference: `packages/plugin-sdk/src/api/queue.ts`, `packages/plugin-sdk/src/api/dashboard.ts`\n\n## Connecting to NuclearAPI\n\n```typescript\n// packages/plugin-sdk/src/api/index.ts\nimport type { YourDomainHost } from '../types/yourDomain';\nimport { YourDomainAPI } from './yourDomain';\n\n// Add to class:\nreadonly YourDomain: YourDomainAPI;\n\n// Add to constructor opts type:\nyourDomainHost?: YourDomainHost;\n\n// Add to constructor body:\nthis.YourDomain = new YourDomainAPI(opts?.yourDomainHost);\n```\n\n```typescript\n// packages/player/src/services/plugins/createPluginAPI.ts\nimport { yourDomainHost } from '../../services/yourDomainHost';\n// Add to NuclearPluginAPI constructor call:\nyourDomainHost,\n```\n\n## Host implementation\n\nHosts bridge the SDK contract to whatever backs the domain. Most commonly a Zustand store:\n\n```typescript\n// packages/player/src/services/yourDomainHost.ts\nimport type { YourDomainHost } from '@nuclearplayer/plugin-sdk';\nimport { useYourDomainStore } from '../stores/yourDomainStore';\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  yourMethod: (arg) => useYourDomainStore.getState().doThing(arg),\n  getState: () => useYourDomainStore.getState().value,\n});\n\nexport const yourDomainHost = createYourDomainHost();\n```\n\nA host can also have access to the **provider registry** (metadata, streaming, dashboard), resolving which registered provider to call. Two patterns:\n\n**Single provider** (user picks one in Sources):\n\n```typescript\nconst getProvider = (providerId?: string) =>\n  providersHost.get<YourProvider>(\n    providerId ?? providersHost.getActive('yourkind'), 'yourkind',\n  );\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetch(query, providerId?) {\n    const provider = getProvider(providerId);\n    if (!provider) throw new Error('No provider available');\n    return provider.fetch(query);\n  },\n});\n```\n\n**Fan-out** (aggregate across all providers - dashboard):\n\n```typescript\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetchAll() {\n    const providers = providersHost.list('yourkind') as YourProvider[];\n    const results = await Promise.allSettled(providers.map(async (provider) => {\n      const items = await provider.fetch();\n      return { providerId: provider.id, providerName: provider.name, items };\n    }));\n    return results.filter(isFulfilled).map((r) => r.value);\n  },\n});\n```\n\nReference: `packages/player/src/services/metadataHost.ts` (single), `packages/player/src/services/dashboardHost.ts` (fan-out)\n\n## Error handling\n\n- **Store-backed hosts**: let errors propagate naturally.\n- **Provider-backed, single provider** (metadata, streaming): throw errors so the user sees failures.\n- **Provider-backed, fan-out** (dashboard): catch per-provider so one failure doesn't block others. Log via `reportError`, return partial results.\n- `MissingCapabilityError`: always a plugin bug. Single-provider domains throw it; fan-out domains log it and skip the offending provider.\n","AGENTS.md":"# AGENTS.md\n\nGuidelines for AI coding agents working on Nuclear.\n\n## Project Overview\n\nNuclear is a free, open-source music player without ads or tracking. Search for any song or artist, build playlists, and start listening. It's a desktop app built with Tauri (Rust + React), organized as a pnpm monorepo managed with Turborepo.\n\n### Packages\n\n- `@nuclearplayer/player` - Main Tauri app (React + Rust)\n- `@nuclearplayer/ui` - Shared UI components\n- `@nuclearplayer/plugin-sdk` - Plugin system (published to npm)\n- `@nuclearplayer/model` - Data model\n- `@nuclearplayer/themes` - Theming system\n- `@nuclearplayer/hifi` - Advanced HTML5 audio component\n- `@nuclearplayer/tailwind-config` - Shared Tailwind config\n- `@nuclearplayer/eslint-config` - Shared linting rules\n- `@nuclearplayer/i18n` - Internationalization\n- `@nuclearplayer/storybook` - Component demos\n- `@nuclearplayer/videos` - Remotion videos rendered from UI components\n- `@nuclearplayer/tools` - Build and maintenance utilities\n- `@nuclearplayer/docs` - Documentation\n- `@nuclearplayer/website` - Project website (Astro)\n\n## Commands\n\n```bash\n# Development\npnpm dev                    # Run player in dev mode\npnpm dev:remote             # Same, but binds Vite to 0.0.0.0 so the remote control UI is reachable from other devices\npnpm storybook              # Run Storybook\npnpm --filter @nuclearplayer/videos dev      # Run the Remotion Studio\npnpm --filter @nuclearplayer/videos render   # Render a video to packages/videos/out\n\n# Build\npnpm build                  # Build all packages\npnpm tauri build            # Build Tauri app\n\n# Quality\npnpm lint                   # Lint all packages\npnpm lint:fix               # Lint and auto-fix\npnpm type-check             # TypeScript checks\npnpm test                   # Run all tests\npnpm test:coverage          # Run tests with coverage\npnpm clean                  # Clean build artifacts\n\n# Package-specific testing\npnpm --filter @nuclearplayer/ui test -- src/components/Badge/Badge.test.tsx\npnpm --filter @nuclearplayer/ui test -- --testNamePattern=\"renders\"\n\n# Update snapshots (run at root for all, or filter to a specific package)\n\n# At root\npnpm test -- -u\n\n# Filtering for a specific package\npnpm --filter @nuclearplayer/ui test -- -u\n\n# After cd'ing into a package\npnpm test -u\n```\n\n## Code Style\n\n### General Principles\n\n- Prioritize readability over cleverness\n- No comments in code - explain reasoning in chat/commits\n- Avoid premature abstractions - start concrete, extract later\n- Small, focused changes over large dumps\n- Never commit unless explicitly asked\n\n### TypeScript\n\n- Use `type` not `interface` (except when merging is required)\n- Strict mode with `noUnusedLocals` and `noUnusedParameters`\n- Do not use one-letter variable names.\nAVOID: `(b) => b.buildIndexEntry()`\nPREFER: `(build) => build.buildIndexEntry()`\n\n### React Components\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\n\nimport { cn } from '../../utils';\n\nconst componentVariants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype ComponentProps = ComponentProps<'div'> &\n  VariantProps<typeof componentVariants>;\n\nexport const Component: FC<ComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(componentVariants({ variant, className }))} {...props} />\n);\n```\n\n- Use `const Component: FC<Props>` not `function Component()`\n- Compound components (`Component.Sub`) for complex widgets\n- Keep business logic out of UI components\n\n### Adding UI Components\n\nWhen adding a new component to `@nuclearplayer/ui`:\n\n1. Create component directory: `packages/ui/src/components/MyComponent/`\n   - `MyComponent.tsx` - implementation\n   - `MyComponent.test.tsx` - tests (aim for 100% coverage)\n   - `index.ts` - re-exports\n2. Export from `packages/ui/src/components/index.ts`\n3. Add Storybook story in `packages/storybook/src/MyComponent.stories.tsx`\n4. Include snapshot test(s) covering all variants\n\n### Styling (Tailwind v4)\n\n- CSS-first config in `packages/tailwind-config/global.css`\n- Theme colors: `bg-background`, `text-foreground`, `bg-primary`\n- Accents: `accent-green`, `accent-yellow`, `accent-purple`, `accent-blue`, `accent-orange`, `accent-cyan`, `accent-red`\n- Use `cn()` for conditional classes, `cva()` for variants\n\n### State Management\n\n- **Zustand** - persistent UI state\n- **React state** - local, temporary state\n- **TanStack Query v5** - HTTP requests/server state\n- **TanStack Router** - client-side routing\n\n### Standardized Libraries\n\n- **Icons**: Lucide React (not heroicons, not font-awesome)\n- **Toasts**: Sonner\n- **Dates**: Luxon\n- **Utilities**: lodash-es (use individual imports: `import isEqual from 'lodash-es/isEqual'`)\n- **HTTP**: Native fetch via ApiClient base class (no axios)\n\n### Adding New Domains\n\nA \"domain\" is a feature area exposed to plugins (e.g., settings, queue, favorites). When adding a new domain:\n\n1. **Types** (`packages/plugin-sdk/src/types/myDomain.ts`)\n   - Define the `MyDomainHost` interface (the contract between player and SDK)\n   - Export any related types plugins will use\n\n2. **API class** (`packages/plugin-sdk/src/api/myDomain.ts`)\n   - Create a class that wraps the host and exposes methods to plugins\n   - Add to `NuclearAPI` constructor in `packages/plugin-sdk/src/api/index.ts`\n\n3. **Store** (`packages/player/src/stores/myDomainStore.ts`)\n   - Zustand store holding the domain state\n   - Persists to disk via `@tauri-apps/plugin-store` if needed\n\n4. **Host** (`packages/player/src/services/myDomainHost.ts`)\n   - Implements the `MyDomainHost` interface\n   - Bridges the SDK API to the Zustand store\n   - Passed to `NuclearAPI` when initializing plugins\n\n### External API Clients\n\nLive in `packages/player/src/apis/`. Use `ApiClient` base class (fetch→json→Zod).\n\n- Validate external data with Zod schemas\n- Export singleton instances\n- One class per external service\n\n### Internationalization\n\nAll user-facing strings go through i18n - no hardcoded UI text.\n\n```tsx\nimport { useTranslation } from '@nuclearplayer/i18n';\n\nconst { t } = useTranslation();\n<span>{t('navigation.settings')}</span>\n```\n\nAdd new strings to `packages/i18n/src/locales/en_US.json` only. Other locales come from Crowdin.\n\n## Testing\n\nTests use Vitest + React Testing Library. Globals enabled (`describe`, `it`, `expect`, `vi`).\n\n- Integration tests over unit tests for user-facing behavior. Render real components and assert on DOM content rather than verifying mock calls.\n- Unit tests for utilities - standalone data structures (RingBuffer, parsers) deserve isolated tests. Use them sparingly.\n- Test user behavior, not implementation details\n- Minimize mocks - only mock external deps (HTTP, FS, Tauri)\n- Snapshot tests: prefix with `(Snapshot)`, basic rendering only\n- Never use `querySelector` in tests. Prefer RTL queries.\n- When semantic queries aren't possible, add `data-testid` attributes. And don't be shy with them\n- Don't use defensive measures like try-catch or conditional checks in tests. The test will fail anyway if our assumptions are wrong.\n\n### Test-first for views\n\nWhen building a new view, write the test wrapper and tests **before** any implementation code. The tests describe what the user sees and does — they define the contract. Then implement to make them pass.\n\nDon't start with unit tests for internal utilities (grouping functions, registries, etc.). Start from the outside: what does the user see on the page? The internal structure is an implementation detail that falls out of making the tests green.\n\n### Test Wrappers for Views\n\nPlayer views and some components use a `*.test-wrapper.tsx` file that creates a domain-specific abstraction layer over the DOM. This lets tests read like user stories, and if the implementation changes, only the wrapper needs updating.\n\n**Wrapper conventions:**\n- Use **getters** for element queries: `get emptyState()`, `get cards()` — not `getEmptyState()`\n- Use **nested objects** for interactive elements: `createButton: { get element(), async click() }`\n- Use **methods** for multi-step user actions: `async openContextMenu(title: string)`\n- Tests should use `Wrapper.emptyState`, `Wrapper.cards`, `Wrapper.createButton.click()` — not bare `screen` queries\n- The wrapper is the only place that knows about test IDs, roles, and DOM structure\n- Don't use queryX methods in the wrapper - always get or find as appropriate.\n- Never use fireEvent. Always use userEvent for interactions.\n\n### Test fixtures\n\nTo populate the app with testing data, use fixtures. See `packages/player/src/test/fixtures` for examples.\n\n### Wrapper fixtures\n\nTest wrappers can expose a `fixtures` object with factory methods that return pre-configured builders for common test scenarios. This keeps test setup readable and co-located with the wrapper, while the raw fixture data itself lives in `packages/player/src/test/fixtures/`.\n\n```tsx\n// Dashboard.test-wrapper.tsx\nimport { TOP_TRACKS_RADIOHEAD } from '../../test/fixtures/dashboard';\n\nexport const DashboardWrapper = {\n  // ... mount, getters, etc.\n\n  fixtures: {\n    topTracksProvider() {\n      return new DashboardProviderBuilder()\n        .withCapabilities('topTracks')\n        .withFetchTopTracks(async () => TOP_TRACKS_RADIOHEAD);\n    },\n  },\n};\n\n// Dashboard.test.tsx\nDashboardWrapper.seedProvider(DashboardWrapper.fixtures.topTracksProvider());\n```\n\n### The builder pattern for tests\n\nWe use builders to create test data and various entities cleanly. You can see them in `packages/player/src/test/builders`.\n\n- A builder is a class that has an instance of the object it's building\n- When the builder is instantiated, it creates a default object with reasonable defaults\n- The builder has methods that mutate the object and return `this` for chaining\n- The `build()` method returns the final object, which can then be used in tests\n\n```tsx\n// Playlists.test-wrapper.tsx\nexport const PlaylistsWrapper = {\n  async mount(): Promise<RenderResult> { /* ... */ },\n\n  get emptyState() {\n    return screen.queryByTestId('empty-state');\n  },\n  get cards() {\n    return screen.queryAllByTestId('card');\n  },\n\n  createButton: {\n    get element() {\n      return screen.getByTestId('create-playlist-button');\n    },\n    async click() {\n      await userEvent.click(this.element);\n    },\n  },\n};\n\n// Playlists.test.tsx — reads like a user story\nit('shows empty state when no playlists', async () => {\n  await PlaylistsWrapper.mount();\n  expect(PlaylistsWrapper.emptyState).toBeInTheDocument();\n});\n```\n\n## File Organization\n\n```\npackages/ui/src/components/Badge/\n  Badge.tsx           # Implementation\n  Badge.test.tsx      # Tests\n  index.ts            # Re-exports\n  __snapshots__/      # Vitest snapshots\n```\n\n## Rust Backend\n\nThe Tauri backend lives in `packages/player/src-tauri/src/`. Modules:\n\n- `bridge/` - bidirectional RPC. Lets Rust servers call into the frontend (`Bridge::call` emits a `bridge:request` event, frontend replies via the `bridge_respond` command).\n- `http_api/` - Axum REST + SSE server for Nuclear Jam remote control\n- `mcp/` - MCP server exposing player functions as tools to LLM clients\n- `mpd/` - MPD-protocol TCP server for clients like ncmpcpp\n- `stream_server.rs` - local audio proxy adding CORS + Range so the browser can play blocked streams\n- `http.rs` - `http_fetch` command, a CORS-bypassing HTTP proxy for the frontend\n- `ytdlp.rs` / `ytdlp_setup.rs` - yt-dlp subprocess wrapper; auto-downloads the binary\n- `discord.rs` - Discord Rich Presence\n- `commands.rs` - filesystem helpers (zip, download, flatpak detection)\n- `net.rs`, `setup.rs`, `logging.rs` - port binding, log plugin config, startup log ring buffer\n- `lib.rs` - declares modules, registers all commands, runs `init_*` in `.setup()`. `main.rs` - binary entry, env fixups.\n\nCommands (run from `packages/player/src-tauri/`):\n\n```bash\ncargo test          # Rust tests (no clippy/rustfmt configured; run manually)\n```\n\n`pnpm dev` runs `tauri dev`; `pnpm build` (in `packages/player`) runs the full `tauri build`.\n\n## Design Philosophy\n\n- Neo-brutalist with premium polish - bold borders, purposeful shadows\n- Premium, designed feel\n- Animations via `motion` and `tw-animate-css`\n- Disable animations during high-friction moments (resize, drag)\n- Avoid generic AI patterns (icon-grid cards, stock heroes, \"Built with love\" badges)\n\n## Tooling Notes\n\n- **pnpm** with workspace protocol for internal deps\n- **Turborepo** for task orchestration\n- **ESLint + Prettier** run together\n- **Husky + lint-staged** for pre-commit hooks\n\nUse centralized configs from eslint-config and tailwind-config packages.\n\nAssume TanStack Router routes regenerate on dev - don't regenerate manually.\n\n## Changelog\n\n`packages/player/changelog.json` is the source of truth for the in-app \"What's New\" tab and auto-generated GitHub release notes.\n\nWhen building a user-facing feature, fix, or improvement, add an entry to the top of the array according to the format you find there.\n\n## Releasing\n\n### Nuclear Player\n\nReleases are triggered by git tags. The workflow builds for macOS (arm64/x64), Linux, and Windows. Release notes are auto-generated from `packages/player/changelog.json`.\n\n```bash\n# 1. Bump versions, update the appstream metainfo, commit, and tag\npnpm release:prepare X.Y.Z\n\n# 2. Push the tag\ngit push origin player@X.Y.Z\n```\n\n`release:prepare` does everything that's needed for a release. Do not bump versions or edit any files by hand when preparing a release.\n\nThe `release-player.yml` workflow creates a GitHub release with platform binaries.\n\n### Plugin SDK\n\nPublished to npm via the `release-plugin-sdk.yml` workflow.\n\n```bash\n# 1. Update version in packages/plugin-sdk/package.json\n# 2. Commit the version bump\ngit add packages/plugin-sdk/package.json && git commit -m \"plugin-sdk@X.Y.Z\"\n\n# 3. Tag and push\ngit tag plugin-sdk@X.Y.Z\ngit push origin plugin-sdk@X.Y.Z\n```\n\nThe workflow builds with `build:npm`, runs tests, and publishes to npm.\n","packages/website/tsconfig.json":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"declaration\": false,\n    \"declarationMap\": false\n  },\n  \"include\": [\"src/**/*\", \"astro.config.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n","packages/ui/package.json":"{\n  \"name\": \"@nuclearplayer/ui\",\n  \"version\": \"0.0.10\",\n  \"description\": \"Shared UI component library for Nuclear\",\n  \"type\": \"module\",\n  \"main\": \"./src/index.ts\",\n  \"module\": \"./src/index.ts\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./src/index.ts\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./assets/*\": \"./src/assets/*\"\n  },\n  \"files\": [\n    \"src\"\n  ],\n  \"sideEffects\": [\n    \"*.css\",\n    \"./src/index.ts\"\n  ],\n  \"scripts\": {\n    \"dev\": \"vite build --watch\",\n    \"build\": \"tsc && vite build\",\n    \"test\": \"vitest --run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest --run --coverage\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"type-check\": \"tsc --noEmit\",\n    \"clean\": \"rm -rf dist build .turbo node_modules/.vite\"\n  },\n  \"dependencies\": {\n    \"@dnd-kit/core\": \"^6.3.1\",\n    \"@dnd-kit/sortable\": \"^10.0.0\",\n    \"@dnd-kit/utilities\": \"^3.2.2\",\n    \"@floating-ui/react-dom\": \"^2.1.7\",\n    \"@fontsource/bricolage-grotesque\": \"^5.2.8\",\n    \"@fontsource/dm-sans\": \"^5.2.6\",\n    \"@fontsource/space-mono\": \"^5.2.9\",\n    \"@headlessui/react\": \"^2.2.7\",\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@nuclearplayer/model\": \"workspace:*\",\n    \"@nuclearplayer/tailwind-config\": \"workspace:*\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.16\",\n    \"@tanstack/react-router\": \"^1.170.35\",\n    \"@tanstack/react-table\": \"^8.21.3\",\n    \"@tanstack/react-virtual\": \"^3.14.11\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"d3-scale\": \"^4.0.2\",\n    \"d3-shape\": \"^3.2.0\",\n    \"lodash-es\": \"^4.17.21\",\n    \"lucide-react\": \"^0.542.0\",\n    \"luxon\": \"^3.7.1\",\n    \"react-activity-calendar\": \"3.2.1\",\n    \"recharts\": \"^3.9.2\",\n    \"tailwind-merge\": \"^3.3.1\",\n    \"tailwindcss\": \"^4.1.11\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/vite\": \"^4.1.11\",\n    \"@testing-library/jest-dom\": \"^7.0.0\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@testing-library/user-event\": \"^14.6.1\",\n    \"@types/d3-scale\": \"^4.0.9\",\n    \"@types/d3-shape\": \"^3.1.8\",\n    \"@types/lodash-es\": \"^4.17.12\",\n    \"@types/luxon\": \"^3.7.1\",\n    \"@types/node\": \"^25.4.0\",\n    \"@types/react\": \"^18.3.23\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"@vitejs/plugin-react\": \"^6.0.0\",\n    \"@vitest/coverage-v8\": \"^5.0.0\",\n    \"jsdom\": \"^26.1.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^8.0.0\",\n    \"vite-plugin-dts\": \"^5.0.0\",\n    \"vite-plugin-svgr\": \"^5.0.0\",\n    \"vitest\": \"^5.0.0\"\n  },\n  \"peerDependencies\": {\n    \"motion\": \"^12.23.12 || ^13.0.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"sonner\": \"^2.0.7\"\n  }\n}\n","packages/ui/tsconfig.json":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"noEmit\": false,\n    \"allowImportingTsExtensions\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"types\": [\n      \"node\",\n      \"vitest/globals\",\n      \"@testing-library/jest-dom\",\n      \"vite-plugin-svgr/client\"\n    ]\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"src/**/*.test.ts\", \"src/**/*.test.tsx\"]\n}\n","packages/tools/package.json":"{\n  \"name\": \"@nuclearplayer/tools\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"test\": \"vitest --run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest --run --coverage\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\"\n  },\n  \"devDependencies\": {\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@vitest/coverage-v8\": \"^5.0.0\",\n    \"typescript\": \"^5.9.2\",\n    \"vitest\": \"^5.0.0\"\n  }\n}\n","packages/tools/tsconfig.json":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"vitest/globals\"]\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n","packages/themes/package.json":"# Themes Skill\n\nPath: `packages/themes/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/package.json)","packages/themes/README.md":"# themes Documentation\n\nPath: `packages/themes/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/README.md)","packages/themes/tsconfig.json":"# Themes Skill\n\nPath: `packages/themes/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/tsconfig.json)","packages/tailwind-config/package.json":"# Tailwind-config Skill\n\nPath: `packages/tailwind-config/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/package.json)","packages/tailwind-config/tsconfig.json":"# Tailwind-config Skill\n\nPath: `packages/tailwind-config/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/tsconfig.json)","packages/storybook/package.json":"# Storybook Skill\n\nPath: `packages/storybook/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/package.json)","packages/storybook/tsconfig.json":"# Storybook Skill\n\nPath: `packages/storybook/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/tsconfig.json)","packages/plugin-sdk/api-extractor.json":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/api-extractor.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/api-extractor.json)","packages/plugin-sdk/package.json":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/package.json)","packages/plugin-sdk/README.md":"# plugin-sdk Documentation\n\nPath: `packages/plugin-sdk/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/README.md)","packages/plugin-sdk/tsconfig.json":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/tsconfig.json)","packages/player/changelog.json":"# Player Skill\n\nPath: `packages/player/changelog.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/changelog.json)","packages/player/package.json":"# Player Skill\n\nPath: `packages/player/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/package.json)","packages/player/tsconfig.json":"# Player Skill\n\nPath: `packages/player/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.json)","packages/player/tsconfig.node.json":"# Player Skill\n\nPath: `packages/player/tsconfig.node.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.node.json)","packages/model/package.json":"# Model Skill\n\nPath: `packages/model/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/package.json)","packages/model/tsconfig.json":"# Model Skill\n\nPath: `packages/model/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/tsconfig.json)","packages/i18n/package.json":"# I18n Skill\n\nPath: `packages/i18n/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/package.json)","packages/i18n/tsconfig.json":"# I18n Skill\n\nPath: `packages/i18n/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/tsconfig.json)","packages/hifi/package.json":"# Hifi Skill\n\nPath: `packages/hifi/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/package.json)","packages/hifi/tsconfig.json":"# Hifi Skill\n\nPath: `packages/hifi/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/tsconfig.json)","packages/eslint-config/package.json":"# Eslint-config Skill\n\nPath: `packages/eslint-config/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/package.json)","packages/eslint-config/tsconfig.json":"# Eslint-config Skill\n\nPath: `packages/eslint-config/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/tsconfig.json)","packages/docs/.gitbook.yaml":"# Docs Skill\n\nPath: `packages/docs/.gitbook.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/.gitbook.yaml)","packages/docs/package.json":"# Docs Skill\n\nPath: `packages/docs/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/package.json)","packages/docs/README.md":"# docs Documentation\n\nPath: `packages/docs/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/README.md)","packages/docs/SUMMARY.md":"# Docs Skill\n\nPath: `packages/docs/SUMMARY.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/SUMMARY.md)","packages/player/src-tauri/tauri.conf.json":"# Src-tauri Skill\n\nPath: `packages/player/src-tauri/tauri.conf.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/tauri.conf.json)","packages/docs/core-concepts/favorites.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/favorites.md)","packages/docs/core-concepts/how-nuclear-works.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/how-nuclear-works.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/how-nuclear-works.md)","packages/docs/core-concepts/listening-history.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-history.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-history.md)","packages/docs/core-concepts/listening-stats.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-stats.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-stats.md)","packages/docs/core-concepts/playlists.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/playlists.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/playlists.md)","packages/docs/core-concepts/plugins-and-providers.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/plugins-and-providers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/plugins-and-providers.md)","packages/docs/core-concepts/the-queue.md":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/the-queue.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/the-queue.md)","packages/docs/development/contributing.md":"# Development Skill\n\nPath: `packages/docs/development/contributing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/contributing.md)","packages/docs/development/host-pattern.md":"# Development Skill\n\nPath: `packages/docs/development/host-pattern.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/host-pattern.md)","packages/docs/development/logging.md":"# Development Skill\n\nPath: `packages/docs/development/logging.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/logging.md)","packages/docs/development/mcp-architecture.md":"# Development Skill\n\nPath: `packages/docs/development/mcp-architecture.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/mcp-architecture.md)","packages/docs/integrations/http-api.md":"# Integrations Skill\n\nPath: `packages/docs/integrations/http-api.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/http-api.md)","packages/docs/integrations/mcp-server.md":"# Integrations Skill\n\nPath: `packages/docs/integrations/mcp-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mcp-server.md)","packages/docs/integrations/mpd-server.md":"# Integrations Skill\n\nPath: `packages/docs/integrations/mpd-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mpd-server.md)","packages/docs/misc/discovery.md":"# Misc Skill\n\nPath: `packages/docs/misc/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/discovery.md)","packages/docs/misc/keyboard-shortcuts.md":"# Misc Skill\n\nPath: `packages/docs/misc/keyboard-shortcuts.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/keyboard-shortcuts.md)","packages/docs/misc/platform-specific.md":"# Misc Skill\n\nPath: `packages/docs/misc/platform-specific.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/platform-specific.md)","packages/docs/plugins/dashboard.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/dashboard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/dashboard.md)","packages/docs/plugins/discovery.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/discovery.md)","packages/docs/plugins/events.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/events.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/events.md)","packages/docs/plugins/favorites.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/favorites.md)","packages/docs/plugins/getting-started.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/getting-started.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/getting-started.md)","packages/docs/plugins/http.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/http.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/http.md)","packages/docs/plugins/logger.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/logger.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/logger.md)","packages/docs/plugins/metadata.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/metadata.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/metadata.md)","packages/docs/plugins/playback.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/playback.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playback.md)","packages/docs/plugins/playlists.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/playlists.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playlists.md)","packages/docs/plugins/plugin-store.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/plugin-store.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-store.md)","packages/docs/plugins/plugin-system.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/plugin-system.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-system.md)","packages/docs/plugins/providers.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/providers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/providers.md)","packages/docs/plugins/publishing.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/publishing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/publishing.md)","packages/docs/plugins/queue.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/queue.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/queue.md)","packages/docs/plugins/settings.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/settings.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/settings.md)","packages/docs/plugins/shell.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/shell.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/shell.md)","packages/docs/plugins/streaming.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/streaming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/streaming.md)","packages/docs/plugins/ytdlp.md":"# Plugins Skill\n\nPath: `packages/docs/plugins/ytdlp.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/ytdlp.md)","packages/docs/themes/theme-store.md":"# Themes Skill\n\nPath: `packages/docs/themes/theme-store.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/theme-store.md)","packages/docs/themes/themes.md":"# Themes Skill\n\nPath: `packages/docs/themes/themes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes.md)","packages/docs/themes/themes-advanced.md":"# Themes Skill\n\nPath: `packages/docs/themes/themes-advanced.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-advanced.md)","packages/docs/themes/themes-basic.md":"# Themes Skill\n\nPath: `packages/docs/themes/themes-basic.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-basic.md)","packages/docs/user-manual/getting-started.md":"# User-manual Skill\n\nPath: `packages/docs/user-manual/getting-started.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/getting-started.md)","packages/docs/user-manual/installation.md":"# User-manual Skill\n\nPath: `packages/docs/user-manual/installation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/installation.md)","packages/docs/user-manual/remote-control.md":"# User-manual Skill\n\nPath: `packages/docs/user-manual/remote-control.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/remote-control.md)","packages/player/src-tauri/capabilities/default.json":"# Capabilities Skill\n\nPath: `packages/player/src-tauri/capabilities/default.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/default.json)","packages/player/src-tauri/capabilities/desktop.json":"# Capabilities Skill\n\nPath: `packages/player/src-tauri/capabilities/desktop.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/desktop.json)","packages/i18n/src/locales/de_DE.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/de_DE.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/de_DE.json)","packages/i18n/src/locales/en_US.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/en_US.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/en_US.json)","packages/i18n/src/locales/es_ES.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/es_ES.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/es_ES.json)","packages/i18n/src/locales/fr_FR.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/fr_FR.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/fr_FR.json)","packages/i18n/src/locales/it_IT.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/it_IT.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/it_IT.json)","packages/i18n/src/locales/ja_JP.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/ja_JP.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ja_JP.json)","packages/i18n/src/locales/pl_PL.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/pl_PL.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pl_PL.json)","packages/i18n/src/locales/pt_BR.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/pt_BR.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pt_BR.json)","packages/i18n/src/locales/ru_RU.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/ru_RU.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ru_RU.json)","packages/i18n/src/locales/zh_CN.json":"# Locales Skill\n\nPath: `packages/i18n/src/locales/zh_CN.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/zh_CN.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nGuidelines for AI coding agents working on Nuclear.\n\n## Project Overview\n\nNuclear is a free, open-source music player without ads or tracking. Search for any song or artist, build playlists, and start listening. It's a desktop app built with Tauri (Rust + React), organized as a pnpm monorepo managed with Turborepo.\n\n### Packages\n\n- `@nuclearplayer/player` - Main Tauri app (React + Rust)\n- `@nuclearplayer/ui` - Shared UI components\n- `@nuclearplayer/plugin-sdk` - Plugin system (published to npm)\n- `@nuclearplayer/model` - Data model\n- `@nuclearplayer/themes` - Theming system\n- `@nuclearplayer/hifi` - Advanced HTML5 audio component\n- `@nuclearplayer/tailwind-config` - Shared Tailwind config\n- `@nuclearplayer/eslint-config` - Shared linting rules\n- `@nuclearplayer/i18n` - Internationalization\n- `@nuclearplayer/storybook` - Component demos\n- `@nuclearplayer/videos` - Remotion videos rendered from UI components\n- `@nuclearplayer/tools` - Build and maintenance utilities\n- `@nuclearplayer/docs` - Documentation\n- `@nuclearplayer/website` - Project website (Astro)\n\n## Commands\n\n```bash\n# Development\npnpm dev                    # Run player in dev mode\npnpm dev:remote             # Same, but binds Vite to 0.0.0.0 so the remote control UI is reachable from other devices\npnpm storybook              # Run Storybook\npnpm --filter @nuclearplayer/videos dev      # Run the Remotion Studio\npnpm --filter @nuclearplayer/videos render   # Render a video to packages/videos/out\n\n# Build\npnpm build                  # Build all packages\npnpm tauri build            # Build Tauri app\n\n# Quality\npnpm lint                   # Lint all packages\npnpm lint:fix               # Lint and auto-fix\npnpm type-check             # TypeScript checks\npnpm test                   # Run all tests\npnpm test:coverage          # Run tests with coverage\npnpm clean                  # Clean build artifacts\n\n# Package-specific testing\npnpm --filter @nuclearplayer/ui test -- src/components/Badge/Badge.test.tsx\npnpm --filter @nuclearplayer/ui test -- --testNamePattern=\"renders\"\n\n# Update snapshots (run at root for all, or filter to a specific package)\n\n# At root\npnpm test -- -u\n\n# Filtering for a specific package\npnpm --filter @nuclearplayer/ui test -- -u\n\n# After cd'ing into a package\npnpm test -u\n```\n\n## Code Style\n\n### General Principles\n\n- Prioritize readability over cleverness\n- No comments in code - explain reasoning in chat/commits\n- Avoid premature abstractions - start concrete, extract later\n- Small, focused changes over large dumps\n- Never commit unless explicitly asked\n\n### TypeScript\n\n- Use `type` not `interface` (except when merging is required)\n- Strict mode with `noUnusedLocals` and `noUnusedParameters`\n- Do not use one-letter variable names.\nAVOID: `(b) => b.buildIndexEntry()`\nPREFER: `(build) => build.buildIndexEntry()`\n\n### React Components\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\n\nimport { cn } from '../../utils';\n\nconst componentVariants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype ComponentProps = ComponentProps<'div'> &\n  VariantProps<typeof componentVariants>;\n\nexport const Component: FC<ComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(componentVariants({ variant, className }))} {...props} />\n);\n```\n\n- Use `const Component: FC<Props>` not `function Component()`\n- Compound components (`Component.Sub`) for complex widgets\n- Keep business logic out of UI components\n\n### Adding UI Components\n\nWhen adding a new component to `@nuclearplayer/ui`:\n\n1. Create component directory: `packages/ui/src/components/MyComponent/`\n   - `MyComponent.tsx` - implementation\n   - `MyComponent.test.tsx` - tests (aim for 100% coverage)\n   - `index.ts` - re-exports\n2. Export from `packages/ui/src/components/index.ts`\n3. Add Storybook story in `packages/storybook/src/MyComponent.stories.tsx`\n4. Include snapshot test(s) covering all variants\n\n### Styling (Tailwind v4)\n\n- CSS-first config in `packages/tailwind-config/global.css`\n- Theme colors: `bg-background`, `text-foreground`, `bg-primary`\n- Accents: `accent-green`, `accent-yellow`, `accent-purple`, `accent-blue`, `accent-orange`, `accent-cyan`, `accent-red`\n- Use `cn()` for conditional classes, `cva()` for variants\n\n### State Management\n\n- **Zustand** - persistent UI state\n- **React state** - local, temporary state\n- **TanStack Query v5** - HTTP requests/server state\n- **TanStack Router** - client-side routing\n\n### Standardized Libraries\n\n- **Icons**: Lucide React (not heroicons, not font-awesome)\n- **Toasts**: Sonner\n- **Dates**: Luxon\n- **Utilities**: lodash-es (use individual imports: `import isEqual from 'lodash-es/isEqual'`)\n- **HTTP**: Native fetch via ApiClient base class (no axios)\n\n### Adding New Domains\n\nA \"domain\" is a feature area exposed to plugins (e.g., settings, queue, favorites). When adding a new domain:\n\n1. **Types** (`packages/plugin-sdk/src/types/myDomain.ts`)\n   - Define the `MyDomainHost` interface (the contract between player and SDK)\n   - Export any related types plugins will use\n\n2. **API class** (`packages/plugin-sdk/src/api/myDomain.ts`)\n   - Create a class that wraps the host and exposes methods to plugins\n   - Add to `NuclearAPI` constructor in `packages/plugin-sdk/src/api/index.ts`\n\n3. **Store** (`packages/player/src/stores/myDomainStore.ts`)\n   - Zustand store holding the domain state\n   - Persists to disk via `@tauri-apps/plugin-store` if needed\n\n4. **Host** (`packages/player/src/services/myDomainHost.ts`)\n   - Implements the `MyDomainHost` interface\n   - Bridges the SDK API to the Zustand store\n   - Passed to `NuclearAPI` when initializing plugins\n\n### External API Clients\n\nLive in `packages/player/src/apis/`. Use `ApiClient` base class (fetch→json→Zod).\n\n- Validate external data with Zod schemas\n- Export singleton instances\n- One class per external service\n\n### Internationalization\n\nAll user-facing strings go through i18n - no hardcoded UI text.\n\n```tsx\nimport { useTranslation } from '@nuclearplayer/i18n';\n\nconst { t } = useTranslation();\n<span>{t('navigation.settings')}</span>\n```\n\nAdd new strings to `packages/i18n/src/locales/en_US.json` only. Other locales come from Crowdin.\n\n## Testing\n\nTests use Vitest + React Testing Library. Globals enabled (`describe`, `it`, `expect`, `vi`).\n\n- Integration tests over unit tests for user-facing behavior. Render real components and assert on DOM content rather than verifying mock calls.\n- Unit tests for utilities - standalone data structures (RingBuffer, parsers) deserve isolated tests. Use them sparingly.\n- Test user behavior, not implementation details\n- Minimize mocks - only mock external deps (HTTP, FS, Tauri)\n- Snapshot tests: prefix with `(Snapshot)`, basic rendering only\n- Never use `querySelector` in tests. Prefer RTL queries.\n- When semantic queries aren't possible, add `data-testid` attributes. And don't be shy with them\n- Don't use defensive measures like try-catch or conditional checks in tests. The test will fail anyway if our assumptions are wrong.\n\n### Test-first for views\n\nWhen building a new view, write the test wrapper and tests **before** any implementation code. The tests describe what the user sees and does — they define the contract. Then implement to make them pass.\n\nDon't start with unit tests for internal utilities (grouping functions, registries, etc.). Start from the outside: what does the user see on the page? The internal structure is an implementation detail that falls out of making the tests green.\n\n### Test Wrappers for Views\n\nPlayer views and some components use a `*.test-wrapper.tsx` file that creates a domain-specific abstraction layer over the DOM. This lets tests read like user stories, and if the implementation changes, only the wrapper needs updating.\n\n**Wrapper conventions:**\n- Use **getters** for element queries: `get emptyState()`, `get cards()` — not `getEmptyState()`\n- Use **nested objects** for interactive elements: `createButton: { get element(), async click() }`\n- Use **methods** for multi-step user actions: `async openContextMenu(title: string)`\n- Tests should use `Wrapper.emptyState`, `Wrapper.cards`, `Wrapper.createButton.click()` — not bare `screen` queries\n- The wrapper is the only place that knows about test IDs, roles, and DOM structure\n- Don't use queryX methods in the wrapper - always get or find as appropriate.\n- Never use fireEvent. Always use userEvent for interactions.\n\n### Test fixtures\n\nTo populate the app with testing data, use fixtures. See `packages/player/src/test/fixtures` for examples.\n\n### Wrapper fixtures\n\nTest wrappers can expose a `fixtures` object with factory methods that return pre-configured builders for common test scenarios. This keeps test setup readable and co-located with the wrapper, while the raw fixture data itself lives in `packages/player/src/test/fixtures/`.\n\n```tsx\n// Dashboard.test-wrapper.tsx\nimport { TOP_TRACKS_RADIOHEAD } from '../../test/fixtures/dashboard';\n\nexport const DashboardWrapper = {\n  // ... mount, getters, etc.\n\n  fixtures: {\n    topTracksProvider() {\n      return new DashboardProviderBuilder()\n        .withCapabilities('topTracks')\n        .withFetchTopTracks(async () => TOP_TRACKS_RADIOHEAD);\n    },\n  },\n};\n\n// Dashboard.test.tsx\nDashboardWrapper.seedProvider(DashboardWrapper.fixtures.topTracksProvider());\n```\n\n### The builder pattern for tests\n\nWe use builders to create test data and various entities cleanly. You can see them in `packages/player/src/test/builders`.\n\n- A builder is a class that has an instance of the object it's building\n- When the builder is instantiated, it creates a default object with reasonable defaults\n- The builder has methods that mutate the object and return `this` for chaining\n- The `build()` method returns the final object, which can then be used in tests\n\n```tsx\n// Playlists.test-wrapper.tsx\nexport const PlaylistsWrapper = {\n  async mount(): Promise<RenderResult> { /* ... */ },\n\n  get emptyState() {\n    return screen.queryByTestId('empty-state');\n  },\n  get cards() {\n    return screen.queryAllByTestId('card');\n  },\n\n  createButton: {\n    get element() {\n      return screen.getByTestId('create-playlist-button');\n    },\n    async click() {\n      await userEvent.click(this.element);\n    },\n  },\n};\n\n// Playlists.test.tsx — reads like a user story\nit('shows empty state when no playlists', async () => {\n  await PlaylistsWrapper.mount();\n  expect(PlaylistsWrapper.emptyState).toBeInTheDocument();\n});\n```\n\n## File Organization\n\n```\npackages/ui/src/components/Badge/\n  Badge.tsx           # Implementation\n  Badge.test.tsx      # Tests\n  index.ts            # Re-exports\n  __snapshots__/      # Vitest snapshots\n```\n\n## Rust Backend\n\nThe Tauri backend lives in `packages/player/src-tauri/src/`. Modules:\n\n- `bridge/` - bidirectional RPC. Lets Rust servers call into the frontend (`Bridge::call` emits a `bridge:request` event, frontend replies via the `bridge_respond` command).\n- `http_api/` - Axum REST + SSE server for Nuclear Jam remote control\n- `mcp/` - MCP server exposing player functions as tools to LLM clients\n- `mpd/` - MPD-protocol TCP server for clients like ncmpcpp\n- `stream_server.rs` - local audio proxy adding CORS + Range so the browser can play blocked streams\n- `http.rs` - `http_fetch` command, a CORS-bypassing HTTP proxy for the frontend\n- `ytdlp.rs` / `ytdlp_setup.rs` - yt-dlp subprocess wrapper; auto-downloads the binary\n- `discord.rs` - Discord Rich Presence\n- `commands.rs` - filesystem helpers (zip, download, flatpak detection)\n- `net.rs`, `setup.rs`, `logging.rs` - port binding, log plugin config, startup log ring buffer\n- `lib.rs` - declares modules, registers all commands, runs `init_*` in `.setup()`. `main.rs` - binary entry, env fixups.\n\nCommands (run from `packages/player/src-tauri/`):\n\n```bash\ncargo test          # Rust tests (no clippy/rustfmt configured; run manually)\n```\n\n`pnpm dev` runs `tauri dev`; `pnpm build` (in `packages/player`) runs the full `tauri build`.\n\n## Design Philosophy\n\n- Neo-brutalist with premium polish - bold borders, purposeful shadows\n- Premium, designed feel\n- Animations via `motion` and `tw-animate-css`\n- Disable animations during high-friction moments (resize, drag)\n- Avoid generic AI patterns (icon-grid cards, stock heroes, \"Built with love\" badges)\n\n## Tooling Notes\n\n- **pnpm** with workspace protocol for internal deps\n- **Turborepo** for task orchestration\n- **ESLint + Prettier** run together\n- **Husky + lint-staged** for pre-commit hooks\n\nUse centralized configs from eslint-config and tailwind-config packages.\n\nAssume TanStack Router routes regenerate on dev - don't regenerate manually.\n\n## Changelog\n\n`packages/player/changelog.json` is the source of truth for the in-app \"What's New\" tab and auto-generated GitHub release notes.\n\nWhen building a user-facing feature, fix, or improvement, add an entry to the top of the array according to the format you find there.\n\n## Releasing\n\n### Nuclear Player\n\nReleases are triggered by git tags. The workflow builds for macOS (arm64/x64), Linux, and Windows. Release notes are auto-generated from `packages/player/changelog.json`.\n\n```bash\n# 1. Bump versions, update the appstream metainfo, commit, and tag\npnpm release:prepare X.Y.Z\n\n# 2. Push the tag\ngit push origin player@X.Y.Z\n```\n\n`release:prepare` does everything that's needed for a release. Do not bump versions or edit any files by hand when preparing a release.\n\nThe `release-player.yml` workflow creates a GitHub release with platform binaries.\n\n### Plugin SDK\n\nPublished to npm via the `release-plugin-sdk.yml` workflow.\n\n```bash\n# 1. Update version in packages/plugin-sdk/package.json\n# 2. Commit the version bump\ngit add packages/plugin-sdk/package.json && git commit -m \"plugin-sdk@X.Y.Z\"\n\n# 3. Tag and push\ngit tag plugin-sdk@X.Y.Z\ngit push origin plugin-sdk@X.Y.Z\n```\n\nThe workflow builds with `build:npm`, runs tests, and publishes to npm.\n","isInternal":false,"tokens":3367,"sizeBytes":14018},{"name":"SKILL.md","path":".agents/skills/creating-components/SKILL.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/.agents/skills/creating-components/SKILL.md","title":"creating-components","category":"anthropic-skill","format":"markdown","content":"---\nname: creating-components\ndescription: Use when creating new UI components in packages/ui. Covers component structure, tests, stories, and what to avoid.\n---\n\n# Creating UI Components\n\n## Component Structure\n\n```\npackages/ui/src/components/MyComponent/\n├── MyComponent.tsx      # Implementation\n├── MyComponent.test.tsx # Tests\n├── index.ts             # Re-exports\n└── __snapshots__/       # Vitest snapshots (auto-generated)\n```\n\nAfter creating, export from `packages/ui/src/components/index.ts`.\n\n## Implementation Pattern\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\nimport { cn } from '../../utils';\n\nconst variants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype MyComponentProps = ComponentProps<'div'> & VariantProps<typeof variants>;\n\nexport const MyComponent: FC<MyComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(variants({ variant, className }))} {...props} />\n);\n```\n\n## Tests\n\n**What to test:**\n- Snapshots (1-2 covering key variants)\n- User interactions\n- Behavior (callbacks called with correct args)\n\n**What NOT to test:**\n- CSS classes, attributes (use snapshots instead)\n- Internal state\n- Things TypeScript already enforces\n\n**Consolidate tests.** One test can cover multiple related assertions. An exception to that is snapshot tests - one snapshot per variant/state.\n\n## Stories\n\nCreate `packages/storybook/src/MyComponent.stories.tsx`.\n\n**One story can show multiple related variants.** For example, it's wasteful to create a story for each variant of a button. Put them all together in one place. Don't create separate stories for each variant:\n\n```tsx\nimport { Meta, StoryObj } from '@storybook/react-vite';\nimport { useState } from 'react';\nimport { MyComponent } from '@nuclearplayer/ui';\n\nconst meta = {\n  title: 'Components/MyComponent',\n  component: MyComponent,\n  tags: ['autodocs'],\n} satisfies Meta<typeof MyComponent>;\n\nexport default meta;\ntype Story = StoryObj<typeof MyComponent>;\n\nexport const AllVariants: Story = {\n  render: () => {\n    const [state, setState] = useState(initialState);\n    return (\n      <div className=\"flex flex-col gap-4\">\n        {/* Show all variants, states, interactions */}\n      </div>\n    );\n  },\n};\n```\n\nThis isn't an iron rule, sometimes it will make more sense to have separate stories.\n\nDon't run storybook build checks.\n\n## Avoiding Duplication\n\nBefore creating a new component, check if an existing one can be extended.\n\n**Pattern: Discriminated unions for mode variants**\n\nExample: Instead of creating `SingleSelect` and `MultiSelect` components:\n\n```tsx\ntype Props = \n  | { multiple?: false; selected: string; onChange: (id: string) => void }\n  | { multiple: true; selected: string[]; onChange: (ids: string[]) => void };\n```\n\nTypeScript enforces correct types based on the `multiple` prop.\n\n## Classes for customization\n\nWhere it's likely that a component will need custom styling, expose a `className` prop.\n\nIf there are many parts that may need styling, consider exposing a `classes` prop with specific class names for each part. Define a type for the `classes` prop. Refer to `packages/ui/src/components/TrackTable/types.ts` for an example.\n\n## Strings\n\nAll user-facing strings go through i18n - no hardcoded UI text.\nIf a new component in the ui package needs labels and other kinds of localized text, it should accept a `labels` prop with the relevant strings. The prop should have its own type defined. Refer to `packages/ui/src/components/QueueItem/types.ts` for an example.\n\n## Accessibility\n\nWe don't care about that. If there's an opportunity to handle that easily, do it, but don't go out of your way.\n\n## Checklist\n\n- [ ] Component in `packages/ui/src/components/MyComponent/`\n- [ ] Exported from `packages/ui/src/components/index.ts`\n- [ ] Tests cover behavior, not implementation\n- [ ] Tests consolidated (not one per variant)\n- [ ] One story showing all variants\n- [ ] No CSS class assertions in tests\n- [ ] No duplicate component when extending existing one works\n","frontmatter":{"name":"creating-components","description":"Use when creating new UI components in packages/ui. Covers component structure, tests, stories, and what to avoid."},"isInternal":false,"tokens":953,"sizeBytes":4138},{"name":"SKILL.md","path":".agents/skills/host-pattern/SKILL.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/.agents/skills/host-pattern/SKILL.md","title":"host-pattern","category":"anthropic-skill","format":"markdown","content":"---\nname: host-pattern\ndescription: Use when adding a new domain to Nuclear's plugin system, or implementing a host. Covers the host pattern (how player functionality is exposed to plugins), the host interface and API class structure, how hosts are implemented in the player, error handling conventions, and what files to create and modify. Trigger phrases include \"add a domain\", \"new domain\", \"host implementation\", \"host pattern\", \"createPluginAPI\".\n---\n\n# Host pattern\n\nEvery feature area follows the same three-layer structure:\n\n1. Host type - the contract (SDK, no implementation)\n2. API class - what plugins actually call (SDK, wraps the host)\n3. Host implementation - bridges the API to player internals (player)\n\nPlugins call methods on an API class (e.g. `api.Queue.addToQueue()`), which holds a reference to the host and delegates to it. Plugins never touch a host directly.\n\n## Files to create/modify\n\n### SDK (`packages/plugin-sdk/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/types/yourDomain.ts` | `YourDomainHost` interface + related types |\n| Create | `src/api/yourDomain.ts` | `YourDomainAPI` class |\n| Modify | `src/api/index.ts` | Add `yourDomainHost` option + `YourDomain` field to `NuclearAPI` |\n| Modify | `src/index.ts` | Export types and API class |\n\n### Player (`packages/player/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/services/yourDomainHost.ts` | Host implementation + singleton export |\n| Modify | `src/services/plugins/createPluginAPI.ts` | Pass singleton to `NuclearPluginAPI` |\n| Modify | `src/services/logger.ts` | Add domain to `LOG_SCOPES` (needed for `reportError`) |\n\nIf the domain needs shared model types: create `packages/model/src/yourDomain.ts` and re-export from `packages/model/src/index.ts`.\n\n## API class pattern\n\nEvery API class follows this structure.\n\n```typescript\n// packages/plugin-sdk/src/api/yourDomain.ts\nexport class YourDomainAPI {\n  #host?: YourDomainHost;\n\n  constructor(host?: YourDomainHost) {\n    this.#host = host;\n  }\n\n  #withHost<T>(fn: (host: YourDomainHost) => T): T {\n    const host = this.#host;\n    if (!host) {\n      throw new Error('YourDomain host not available');\n    }\n    return fn(host);\n  }\n\n  yourMethod(arg: SomeType) {\n    return this.#withHost((host) => host.yourMethod(arg));\n  }\n}\n```\n\nReference: `packages/plugin-sdk/src/api/queue.ts`, `packages/plugin-sdk/src/api/dashboard.ts`\n\n## Connecting to NuclearAPI\n\n```typescript\n// packages/plugin-sdk/src/api/index.ts\nimport type { YourDomainHost } from '../types/yourDomain';\nimport { YourDomainAPI } from './yourDomain';\n\n// Add to class:\nreadonly YourDomain: YourDomainAPI;\n\n// Add to constructor opts type:\nyourDomainHost?: YourDomainHost;\n\n// Add to constructor body:\nthis.YourDomain = new YourDomainAPI(opts?.yourDomainHost);\n```\n\n```typescript\n// packages/player/src/services/plugins/createPluginAPI.ts\nimport { yourDomainHost } from '../../services/yourDomainHost';\n// Add to NuclearPluginAPI constructor call:\nyourDomainHost,\n```\n\n## Host implementation\n\nHosts bridge the SDK contract to whatever backs the domain. Most commonly a Zustand store:\n\n```typescript\n// packages/player/src/services/yourDomainHost.ts\nimport type { YourDomainHost } from '@nuclearplayer/plugin-sdk';\nimport { useYourDomainStore } from '../stores/yourDomainStore';\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  yourMethod: (arg) => useYourDomainStore.getState().doThing(arg),\n  getState: () => useYourDomainStore.getState().value,\n});\n\nexport const yourDomainHost = createYourDomainHost();\n```\n\nA host can also have access to the **provider registry** (metadata, streaming, dashboard), resolving which registered provider to call. Two patterns:\n\n**Single provider** (user picks one in Sources):\n\n```typescript\nconst getProvider = (providerId?: string) =>\n  providersHost.get<YourProvider>(\n    providerId ?? providersHost.getActive('yourkind'), 'yourkind',\n  );\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetch(query, providerId?) {\n    const provider = getProvider(providerId);\n    if (!provider) throw new Error('No provider available');\n    return provider.fetch(query);\n  },\n});\n```\n\n**Fan-out** (aggregate across all providers - dashboard):\n\n```typescript\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetchAll() {\n    const providers = providersHost.list('yourkind') as YourProvider[];\n    const results = await Promise.allSettled(providers.map(async (provider) => {\n      const items = await provider.fetch();\n      return { providerId: provider.id, providerName: provider.name, items };\n    }));\n    return results.filter(isFulfilled).map((r) => r.value);\n  },\n});\n```\n\nReference: `packages/player/src/services/metadataHost.ts` (single), `packages/player/src/services/dashboardHost.ts` (fan-out)\n\n## Error handling\n\n- **Store-backed hosts**: let errors propagate naturally.\n- **Provider-backed, single provider** (metadata, streaming): throw errors so the user sees failures.\n- **Provider-backed, fan-out** (dashboard): catch per-provider so one failure doesn't block others. Log via `reportError`, return partial results.\n- `MissingCapabilityError`: always a plugin bug. Single-provider domains throw it; fan-out domains log it and skip the offending provider.\n","frontmatter":{"name":"host-pattern","description":"Use when adding a new domain to Nuclear's plugin system, or implementing a host. Covers the host pattern (how player functionality is exposed to plugins), the host interface and API class structure, how hosts are implemented in the player, error handling conventions, and what files to create and modify. Trigger phrases include \"add a domain\", \"new domain\", \"host implementation\", \"host pattern\", \"createPluginAPI\"."},"isInternal":false,"tokens":1221,"sizeBytes":5306},{"name":"SKILL.md","path":".agents/skills/writing-docs/SKILL.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/.agents/skills/writing-docs/SKILL.md","title":"writing-docs","category":"anthropic-skill","format":"markdown","content":"---\nname: writing-docs\ndescription: Use when writing or editing documentation in packages/docs. Covers Gitbook markdown syntax, special blocks, page structure, and the SUMMARY.md table of contents. Trigger phrases include \"write docs\", \"add documentation\", \"docs page\", \"gitbook\", \"user manual\".\n---\n\n# Writing Documentation\n\nDocs live in `packages/docs/`. Hosted on Gitbook, synced from the repo via `.gitbook.yaml`.\n\n## Page structure\n\nEvery page starts with YAML frontmatter and a top-level heading:\n\n```markdown\n---\ndescription: Short description shown in search and link previews\n---\n\n# Page title\n```\n\n## Table of contents\n\n`packages/docs/SUMMARY.md` defines the sidebar navigation. Group pages under `##` headings:\n\n```markdown\n# Table of contents\n\n* [Nuclear Documentation](README.md)\n\n## User Manual\n\n* [Getting started](user-manual/getting-started.md)\n* [Installation](user-manual/installation.md)\n\n## Plugins\n\n* [Getting started](plugins/getting-started.md)\n```\n\nWhen adding a new page, always add it to SUMMARY.md or it won't appear in the sidebar.\n\n## Gitbook special blocks\n\n### Tabs\n\nUse for platform-specific instructions or language variants:\n\n```markdown\n{% tabs %}\n\n{% tab title=\"Windows\" %}\nContent for Windows.\n{% endtab %}\n\n{% tab title=\"macOS\" %}\nContent for macOS.\n{% endtab %}\n\n{% tab title=\"Linux\" %}\nContent for Linux.\n{% endtab %}\n\n{% endtabs %}\n```\n\nTabs can contain any block type (code, tables, lists, images) but cannot nest other tabs or expandables.\n\n### Hints (callouts)\n\nFour styles: `info`, `success`, `warning`, `danger`.\n\n```markdown\n{% hint style=\"info\" %}\nGeneral information or tips.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nNon-critical warnings or gotchas.\n{% endhint %}\n\n{% hint style=\"danger\" %}\nDestructive actions or critical information.\n{% endhint %}\n\n{% hint style=\"success\" %}\nPositive actions or confirmations.\n{% endhint %}\n```\n\n### Code blocks with options\n\nWrap in `{% code %}` for title, line numbers, or wrapping:\n\n````markdown\n{% code title=\"config.json\" overflow=\"wrap\" lineNumbers=\"true\" %}\n\n```json\n{\n  \"key\": \"value\"\n}\n```\n\n{% endcode %}\n````\n\nWithout the wrapper, standard fenced code blocks work fine.\n\n### Expandable (collapsible sections)\n\nUses standard HTML `<details>` / `<summary>`:\n\n```markdown\n<details>\n\n<summary>Click to expand</summary>\n\nHidden content goes here. Supports lists and code blocks.\n\n</details>\n```\n\nAdd `open` attribute to expand by default: `<details open>`.\n\n### Page links\n\nProminent block-level links to other pages:\n\n```markdown\n{% content-ref url=\"getting-started.md\" %}\n[Getting started](getting-started.md)\n{% endcontent-ref %}\n```\n\n### Cards\n\nGrid layout for landing pages. Uses a table with `data-view=\"cards\"`:\n\n```markdown\n<table data-view=\"cards\">\n  <thead>\n    <tr>\n      <th></th>\n      <th></th>\n      <th data-hidden data-card-target data-type=\"content-ref\"></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><strong>Card title</strong></td>\n      <td>Card description.</td>\n      <td><a href=\"page.md\">page.md</a></td>\n    </tr>\n  </tbody>\n</table>\n```\n\n### Annotations (footnotes)\n\n```markdown\nNuclear uses plugins[^1] to provide functionality.\n\n[^1]: Plugins are installed from the plugin store.\n```\n\n## Content guidelines\n\n- No brand names of third-party services in docs\n- All user-facing strings reference i18n keys, not hardcoded text\n- Use relative links between docs pages (`../themes/themes.md`), not absolute URLs\n- Keep pages focused on one topic. Link to related pages instead of duplicating content.\n- Describe the UI as the user sees it: use actual button labels, menu names, and icon descriptions\n","frontmatter":{"name":"writing-docs","description":"Use when writing or editing documentation in packages/docs. Covers Gitbook markdown syntax, special blocks, page structure, and the SUMMARY.md table of contents. Trigger phrases include \"write docs\", \"add documentation\", \"docs page\", \"gitbook\", \"user manual\"."},"isInternal":false,"tokens":883,"sizeBytes":3625},{"name":"SKILL.md","path":".agents/skills/writing-plugins/SKILL.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/.agents/skills/writing-plugins/SKILL.md","title":"writing-plugins","category":"anthropic-skill","format":"markdown","content":"---\nname: writing-plugins\ndescription: Use when writing, scaffolding, or modifying Nuclear plugins. Covers plugin structure, manifest, entry point, provider types, available APIs, and publishing. Trigger phrases include \"create a plugin\", \"write a plugin\", \"plugin scaffold\", \"streaming provider\", \"metadata provider\".\n---\n\n# Writing Nuclear Plugins\n\nPlugins are standalone repos compiled in-browser via esbuild-wasm.\n\n## Structure\n\n```\nmy-plugin/\n  package.json      # Manifest with nuclear metadata\n  src/\n    index.ts        # Entry point, default exports NuclearPlugin\n```\n\n## Manifest (package.json)\n\n```json\n{\n  \"name\": \"nuclear-plugin-example\",\n  \"version\": \"0.1.0\",\n  \"description\": \"What this plugin does\",\n  \"author\": \"Your Name\",\n  \"license\": \"AGPL-3.0-only\",\n  \"main\": \"src/index.ts\",\n  \"type\": \"module\",\n  \"nuclear\": {\n    \"displayName\": \"Example Plugin\",\n    \"category\": \"streaming\",\n    \"icon\": { \"type\": \"link\", \"link\": \"https://example.com/icon.svg\" }\n  },\n  \"dependencies\": {\n    \"@nuclearplayer/plugin-sdk\": \"^1.1.0\"\n  }\n}\n```\n\nCategories: `streaming`, `metadata`, `lyrics`\n\n## Entry Point\n\n```typescript\nimport type { NuclearPlugin, NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';\n\nconst plugin: NuclearPlugin = {\n  onLoad(api: NuclearPluginAPI) {},\n  onEnable(api: NuclearPluginAPI) {\n    // Register providers\n  },\n  onDisable() {\n    // Unregister providers\n  },\n  onUnload() {},\n};\n\nexport default plugin;\n```\n\n## Provider Types\n\n**Streaming** - resolve tracks to playable audio:\n\n```typescript\nconst provider: StreamingProvider = {\n  id: 'my-streaming',\n  kind: 'streaming',\n  name: 'My Streaming',\n  async searchForTrack(artist, title, album?) { /* return StreamCandidate[] */ },\n  async getStreamUrl(candidateId) { /* return Stream */ },\n};\n\napi.Providers.register(provider);\napi.Providers.unregister('my-streaming');\n```\n\n**Metadata** - search artists/albums, fetch details:\n\n```typescript\nconst provider: MetadataProvider = {\n  id: 'my-metadata',\n  kind: 'metadata',\n  name: 'My Metadata',\n  searchCapabilities: ['artists', 'albums'],\n  streamingProviderId: 'my-streaming', // Optional: locks streaming to this provider\n  async searchArtists(params) { /* ... */ },\n  async searchAlbums(params) { /* ... */ },\n  async fetchArtistBio(id) { /* ... */ },\n  async fetchAlbumDetails(id) { /* ... */ },\n};\n```\n\n## Available APIs\n\n- `api.Providers` - register/unregister providers\n- `api.Settings` - plugin settings storage\n- `api.Http` - fetch wrapper\n- `api.Ytdlp` - yt-dlp integration\n- `api.Queue` - playback queue control\n- `api.Metadata` - search music metadata\n- `api.Streaming` - resolve streams\n- `api.Logger` - logging (trace/debug/info/warn/error)\n\n## Publishing\n\n1. Create a GitHub repo with the plugin code\n2. Add `.github/workflows/release.yml`:\n\n```yaml\nname: Release\non:\n  push:\n    tags: ['v*']\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@v4\n      - run: zip -r plugin.zip src package.json README.md\n      - uses: softprops/action-gh-release@v2\n        with:\n          files: plugin.zip\n          generate_release_notes: true\n```\n\n3. Tag release: `git tag v0.1.0 && git push origin v0.1.0`\n4. Submit PR to `NuclearPlayer/plugin-registry` adding your plugin to `plugins.json`\n","frontmatter":{"name":"writing-plugins","description":"Use when writing, scaffolding, or modifying Nuclear plugins. Covers plugin structure, manifest, entry point, provider types, available APIs, and publishing. Trigger phrases include \"create a plugin\", \"write a plugin\", \"plugin scaffold\", \"streaming provider\", \"metadata provider\"."},"isInternal":false,"tokens":849,"sizeBytes":3299},{"name":".gitbook.yaml","path":"packages/docs/.gitbook.yaml","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/.gitbook.yaml","title":"Docs Skill","category":"anthropic-skill","format":"yaml","content":"# Docs Skill\n\nPath: `packages/docs/.gitbook.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/.gitbook.yaml)","tokens":300,"sizeBytes":162},{"name":"favorites.md","path":"packages/docs/core-concepts/favorites.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/favorites.md","title":"Core-concepts Skill","category":"anthropic-skill","format":"markdown","content":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/favorites.md)","tokens":300,"sizeBytes":197},{"name":"how-nuclear-works.md","path":"packages/docs/core-concepts/how-nuclear-works.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/how-nuclear-works.md","title":"Core-concepts Skill","category":"anthropic-skill","format":"markdown","content":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/how-nuclear-works.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/how-nuclear-works.md)","tokens":300,"sizeBytes":213},{"name":"listening-history.md","path":"packages/docs/core-concepts/listening-history.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-history.md","title":"Core-concepts Skill","category":"anthropic-skill","format":"markdown","content":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-history.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-history.md)","tokens":300,"sizeBytes":213},{"name":"listening-stats.md","path":"packages/docs/core-concepts/listening-stats.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-stats.md","title":"Core-concepts Skill","category":"anthropic-skill","format":"markdown","content":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-stats.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-stats.md)","tokens":300,"sizeBytes":209},{"name":"playlists.md","path":"packages/docs/core-concepts/playlists.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/playlists.md","title":"Core-concepts Skill","category":"anthropic-skill","format":"markdown","content":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/playlists.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/playlists.md)","tokens":300,"sizeBytes":197},{"name":"plugins-and-providers.md","path":"packages/docs/core-concepts/plugins-and-providers.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/plugins-and-providers.md","title":"Core-concepts Skill","category":"anthropic-skill","format":"markdown","content":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/plugins-and-providers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/plugins-and-providers.md)","tokens":300,"sizeBytes":221},{"name":"the-queue.md","path":"packages/docs/core-concepts/the-queue.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/the-queue.md","title":"Core-concepts Skill","category":"anthropic-skill","format":"markdown","content":"# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/the-queue.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/the-queue.md)","tokens":300,"sizeBytes":197},{"name":"contributing.md","path":"packages/docs/development/contributing.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/contributing.md","title":"Development Skill","category":"anthropic-skill","format":"markdown","content":"# Development Skill\n\nPath: `packages/docs/development/contributing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/contributing.md)","tokens":300,"sizeBytes":197},{"name":"host-pattern.md","path":"packages/docs/development/host-pattern.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/host-pattern.md","title":"Development Skill","category":"anthropic-skill","format":"markdown","content":"# Development Skill\n\nPath: `packages/docs/development/host-pattern.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/host-pattern.md)","tokens":300,"sizeBytes":197},{"name":"logging.md","path":"packages/docs/development/logging.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/logging.md","title":"Development Skill","category":"anthropic-skill","format":"markdown","content":"# Development Skill\n\nPath: `packages/docs/development/logging.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/logging.md)","tokens":300,"sizeBytes":187},{"name":"mcp-architecture.md","path":"packages/docs/development/mcp-architecture.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/mcp-architecture.md","title":"Development Skill","category":"anthropic-skill","format":"markdown","content":"# Development Skill\n\nPath: `packages/docs/development/mcp-architecture.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/mcp-architecture.md)","tokens":300,"sizeBytes":205},{"name":"http-api.md","path":"packages/docs/integrations/http-api.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/http-api.md","title":"Integrations Skill","category":"anthropic-skill","format":"markdown","content":"# Integrations Skill\n\nPath: `packages/docs/integrations/http-api.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/http-api.md)","tokens":300,"sizeBytes":192},{"name":"mcp-server.md","path":"packages/docs/integrations/mcp-server.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mcp-server.md","title":"Integrations Skill","category":"anthropic-skill","format":"markdown","content":"# Integrations Skill\n\nPath: `packages/docs/integrations/mcp-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mcp-server.md)","tokens":300,"sizeBytes":196},{"name":"mpd-server.md","path":"packages/docs/integrations/mpd-server.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mpd-server.md","title":"Integrations Skill","category":"anthropic-skill","format":"markdown","content":"# Integrations Skill\n\nPath: `packages/docs/integrations/mpd-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mpd-server.md)","tokens":300,"sizeBytes":196},{"name":"discovery.md","path":"packages/docs/misc/discovery.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/discovery.md","title":"Misc Skill","category":"anthropic-skill","format":"markdown","content":"# Misc Skill\n\nPath: `packages/docs/misc/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/discovery.md)","tokens":300,"sizeBytes":170},{"name":"keyboard-shortcuts.md","path":"packages/docs/misc/keyboard-shortcuts.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/keyboard-shortcuts.md","title":"Misc Skill","category":"anthropic-skill","format":"markdown","content":"# Misc Skill\n\nPath: `packages/docs/misc/keyboard-shortcuts.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/keyboard-shortcuts.md)","tokens":300,"sizeBytes":188},{"name":"platform-specific.md","path":"packages/docs/misc/platform-specific.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/platform-specific.md","title":"Misc Skill","category":"anthropic-skill","format":"markdown","content":"# Misc Skill\n\nPath: `packages/docs/misc/platform-specific.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/platform-specific.md)","tokens":300,"sizeBytes":186},{"name":"package.json","path":"packages/docs/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/package.json","title":"Docs Skill","category":"anthropic-skill","format":"json","content":"# Docs Skill\n\nPath: `packages/docs/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/package.json)","tokens":300,"sizeBytes":160},{"name":"dashboard.md","path":"packages/docs/plugins/dashboard.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/dashboard.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/dashboard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/dashboard.md)","tokens":300,"sizeBytes":179},{"name":"discovery.md","path":"packages/docs/plugins/discovery.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/discovery.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/discovery.md)","tokens":300,"sizeBytes":179},{"name":"events.md","path":"packages/docs/plugins/events.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/events.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/events.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/events.md)","tokens":300,"sizeBytes":173},{"name":"favorites.md","path":"packages/docs/plugins/favorites.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/favorites.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/favorites.md)","tokens":300,"sizeBytes":179},{"name":"getting-started.md","path":"packages/docs/plugins/getting-started.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/getting-started.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/getting-started.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/getting-started.md)","tokens":300,"sizeBytes":191},{"name":"http.md","path":"packages/docs/plugins/http.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/http.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/http.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/http.md)","tokens":300,"sizeBytes":169},{"name":"logger.md","path":"packages/docs/plugins/logger.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/logger.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/logger.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/logger.md)","tokens":300,"sizeBytes":173},{"name":"metadata.md","path":"packages/docs/plugins/metadata.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/metadata.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/metadata.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/metadata.md)","tokens":300,"sizeBytes":177},{"name":"playback.md","path":"packages/docs/plugins/playback.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playback.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/playback.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playback.md)","tokens":300,"sizeBytes":177},{"name":"playlists.md","path":"packages/docs/plugins/playlists.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playlists.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/playlists.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/playlists.md)","tokens":300,"sizeBytes":179},{"name":"plugin-store.md","path":"packages/docs/plugins/plugin-store.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-store.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/plugin-store.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-store.md)","tokens":300,"sizeBytes":185},{"name":"plugin-system.md","path":"packages/docs/plugins/plugin-system.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-system.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/plugin-system.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/plugin-system.md)","tokens":300,"sizeBytes":187},{"name":"providers.md","path":"packages/docs/plugins/providers.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/providers.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/providers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/providers.md)","tokens":300,"sizeBytes":179},{"name":"publishing.md","path":"packages/docs/plugins/publishing.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/publishing.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/publishing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/publishing.md)","tokens":300,"sizeBytes":181},{"name":"queue.md","path":"packages/docs/plugins/queue.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/queue.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/queue.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/queue.md)","tokens":300,"sizeBytes":171},{"name":"settings.md","path":"packages/docs/plugins/settings.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/settings.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/settings.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/settings.md)","tokens":300,"sizeBytes":177},{"name":"shell.md","path":"packages/docs/plugins/shell.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/shell.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/shell.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/shell.md)","tokens":300,"sizeBytes":171},{"name":"streaming.md","path":"packages/docs/plugins/streaming.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/streaming.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/streaming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/streaming.md)","tokens":300,"sizeBytes":179},{"name":"ytdlp.md","path":"packages/docs/plugins/ytdlp.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/ytdlp.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins Skill\n\nPath: `packages/docs/plugins/ytdlp.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/ytdlp.md)","tokens":300,"sizeBytes":171},{"name":"SUMMARY.md","path":"packages/docs/SUMMARY.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/SUMMARY.md","title":"Docs Skill","category":"anthropic-skill","format":"markdown","content":"# Docs Skill\n\nPath: `packages/docs/SUMMARY.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/SUMMARY.md)","tokens":300,"sizeBytes":156},{"name":"theme-store.md","path":"packages/docs/themes/theme-store.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/theme-store.md","title":"Themes Skill","category":"anthropic-skill","format":"markdown","content":"# Themes Skill\n\nPath: `packages/docs/themes/theme-store.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/theme-store.md)","tokens":300,"sizeBytes":180},{"name":"themes-advanced.md","path":"packages/docs/themes/themes-advanced.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-advanced.md","title":"Themes Skill","category":"anthropic-skill","format":"markdown","content":"# Themes Skill\n\nPath: `packages/docs/themes/themes-advanced.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-advanced.md)","tokens":300,"sizeBytes":188},{"name":"themes-basic.md","path":"packages/docs/themes/themes-basic.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-basic.md","title":"Themes Skill","category":"anthropic-skill","format":"markdown","content":"# Themes Skill\n\nPath: `packages/docs/themes/themes-basic.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes-basic.md)","tokens":300,"sizeBytes":182},{"name":"themes.md","path":"packages/docs/themes/themes.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes.md","title":"Themes Skill","category":"anthropic-skill","format":"markdown","content":"# Themes Skill\n\nPath: `packages/docs/themes/themes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/themes/themes.md)","tokens":300,"sizeBytes":170},{"name":"getting-started.md","path":"packages/docs/user-manual/getting-started.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/getting-started.md","title":"User-manual Skill","category":"anthropic-skill","format":"markdown","content":"# User-manual Skill\n\nPath: `packages/docs/user-manual/getting-started.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/getting-started.md)","tokens":300,"sizeBytes":203},{"name":"installation.md","path":"packages/docs/user-manual/installation.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/installation.md","title":"User-manual Skill","category":"anthropic-skill","format":"markdown","content":"# User-manual Skill\n\nPath: `packages/docs/user-manual/installation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/installation.md)","tokens":300,"sizeBytes":197},{"name":"remote-control.md","path":"packages/docs/user-manual/remote-control.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/remote-control.md","title":"User-manual Skill","category":"anthropic-skill","format":"markdown","content":"# User-manual Skill\n\nPath: `packages/docs/user-manual/remote-control.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/user-manual/remote-control.md)","tokens":300,"sizeBytes":201},{"name":"package.json","path":"packages/eslint-config/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/package.json","title":"Eslint-config Skill","category":"anthropic-skill","format":"json","content":"# Eslint-config Skill\n\nPath: `packages/eslint-config/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/package.json)","tokens":300,"sizeBytes":187},{"name":"tsconfig.json","path":"packages/eslint-config/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/tsconfig.json","title":"Eslint-config Skill","category":"anthropic-skill","format":"json","content":"# Eslint-config Skill\n\nPath: `packages/eslint-config/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/eslint-config/tsconfig.json)","tokens":300,"sizeBytes":189},{"name":"package.json","path":"packages/hifi/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/package.json","title":"Hifi Skill","category":"anthropic-skill","format":"json","content":"# Hifi Skill\n\nPath: `packages/hifi/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/package.json)","tokens":300,"sizeBytes":160},{"name":"tsconfig.json","path":"packages/hifi/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/tsconfig.json","title":"Hifi Skill","category":"anthropic-skill","format":"json","content":"# Hifi Skill\n\nPath: `packages/hifi/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/hifi/tsconfig.json)","tokens":300,"sizeBytes":162},{"name":"package.json","path":"packages/i18n/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/package.json","title":"I18n Skill","category":"anthropic-skill","format":"json","content":"# I18n Skill\n\nPath: `packages/i18n/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/package.json)","tokens":300,"sizeBytes":160},{"name":"de_DE.json","path":"packages/i18n/src/locales/de_DE.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/de_DE.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/de_DE.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/de_DE.json)","tokens":300,"sizeBytes":183},{"name":"en_US.json","path":"packages/i18n/src/locales/en_US.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/en_US.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/en_US.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/en_US.json)","tokens":300,"sizeBytes":183},{"name":"es_ES.json","path":"packages/i18n/src/locales/es_ES.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/es_ES.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/es_ES.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/es_ES.json)","tokens":300,"sizeBytes":183},{"name":"fr_FR.json","path":"packages/i18n/src/locales/fr_FR.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/fr_FR.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/fr_FR.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/fr_FR.json)","tokens":300,"sizeBytes":183},{"name":"it_IT.json","path":"packages/i18n/src/locales/it_IT.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/it_IT.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/it_IT.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/it_IT.json)","tokens":300,"sizeBytes":183},{"name":"ja_JP.json","path":"packages/i18n/src/locales/ja_JP.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ja_JP.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/ja_JP.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ja_JP.json)","tokens":300,"sizeBytes":183},{"name":"pl_PL.json","path":"packages/i18n/src/locales/pl_PL.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pl_PL.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/pl_PL.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pl_PL.json)","tokens":300,"sizeBytes":183},{"name":"pt_BR.json","path":"packages/i18n/src/locales/pt_BR.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pt_BR.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/pt_BR.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/pt_BR.json)","tokens":300,"sizeBytes":183},{"name":"ru_RU.json","path":"packages/i18n/src/locales/ru_RU.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ru_RU.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/ru_RU.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/ru_RU.json)","tokens":300,"sizeBytes":183},{"name":"zh_CN.json","path":"packages/i18n/src/locales/zh_CN.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/zh_CN.json","title":"Locales Skill","category":"anthropic-skill","format":"json","content":"# Locales Skill\n\nPath: `packages/i18n/src/locales/zh_CN.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/src/locales/zh_CN.json)","tokens":300,"sizeBytes":183},{"name":"tsconfig.json","path":"packages/i18n/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/tsconfig.json","title":"I18n Skill","category":"anthropic-skill","format":"json","content":"# I18n Skill\n\nPath: `packages/i18n/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/i18n/tsconfig.json)","tokens":300,"sizeBytes":162},{"name":"package.json","path":"packages/model/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/package.json","title":"Model Skill","category":"anthropic-skill","format":"json","content":"# Model Skill\n\nPath: `packages/model/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/package.json)","tokens":300,"sizeBytes":163},{"name":"tsconfig.json","path":"packages/model/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/tsconfig.json","title":"Model Skill","category":"anthropic-skill","format":"json","content":"# Model Skill\n\nPath: `packages/model/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/model/tsconfig.json)","tokens":300,"sizeBytes":165},{"name":"changelog.json","path":"packages/player/changelog.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/changelog.json","title":"Player Skill","category":"anthropic-skill","format":"json","content":"# Player Skill\n\nPath: `packages/player/changelog.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/changelog.json)","tokens":300,"sizeBytes":170},{"name":"package.json","path":"packages/player/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/package.json","title":"Player Skill","category":"anthropic-skill","format":"json","content":"# Player Skill\n\nPath: `packages/player/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/package.json)","tokens":300,"sizeBytes":166},{"name":"default.json","path":"packages/player/src-tauri/capabilities/default.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/default.json","title":"Capabilities Skill","category":"anthropic-skill","format":"json","content":"# Capabilities Skill\n\nPath: `packages/player/src-tauri/capabilities/default.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/default.json)","tokens":300,"sizeBytes":218},{"name":"desktop.json","path":"packages/player/src-tauri/capabilities/desktop.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/desktop.json","title":"Capabilities Skill","category":"anthropic-skill","format":"json","content":"# Capabilities Skill\n\nPath: `packages/player/src-tauri/capabilities/desktop.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/capabilities/desktop.json)","tokens":300,"sizeBytes":218},{"name":"tauri.conf.json","path":"packages/player/src-tauri/tauri.conf.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/tauri.conf.json","title":"Src-tauri Skill","category":"anthropic-skill","format":"json","content":"# Src-tauri Skill\n\nPath: `packages/player/src-tauri/tauri.conf.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/src-tauri/tauri.conf.json)","tokens":300,"sizeBytes":195},{"name":"tsconfig.json","path":"packages/player/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.json","title":"Player Skill","category":"anthropic-skill","format":"json","content":"# Player Skill\n\nPath: `packages/player/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.json)","tokens":300,"sizeBytes":168},{"name":"tsconfig.node.json","path":"packages/player/tsconfig.node.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.node.json","title":"Player Skill","category":"anthropic-skill","format":"json","content":"# Player Skill\n\nPath: `packages/player/tsconfig.node.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/player/tsconfig.node.json)","tokens":300,"sizeBytes":178},{"name":"api-extractor.json","path":"packages/plugin-sdk/api-extractor.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/api-extractor.json","title":"Plugin-sdk Skill","category":"anthropic-skill","format":"json","content":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/api-extractor.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/api-extractor.json)","tokens":300,"sizeBytes":190},{"name":"package.json","path":"packages/plugin-sdk/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/package.json","title":"Plugin-sdk Skill","category":"anthropic-skill","format":"json","content":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/package.json)","tokens":300,"sizeBytes":178},{"name":"tsconfig.json","path":"packages/plugin-sdk/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/tsconfig.json","title":"Plugin-sdk Skill","category":"anthropic-skill","format":"json","content":"# Plugin-sdk Skill\n\nPath: `packages/plugin-sdk/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/tsconfig.json)","tokens":300,"sizeBytes":180},{"name":"package.json","path":"packages/storybook/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/package.json","title":"Storybook Skill","category":"anthropic-skill","format":"json","content":"# Storybook Skill\n\nPath: `packages/storybook/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/package.json)","tokens":300,"sizeBytes":175},{"name":"tsconfig.json","path":"packages/storybook/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/tsconfig.json","title":"Storybook Skill","category":"anthropic-skill","format":"json","content":"# Storybook Skill\n\nPath: `packages/storybook/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/storybook/tsconfig.json)","tokens":300,"sizeBytes":177},{"name":"package.json","path":"packages/tailwind-config/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/package.json","title":"Tailwind-config Skill","category":"anthropic-skill","format":"json","content":"# Tailwind-config Skill\n\nPath: `packages/tailwind-config/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/package.json)","tokens":300,"sizeBytes":193},{"name":"tsconfig.json","path":"packages/tailwind-config/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/tsconfig.json","title":"Tailwind-config Skill","category":"anthropic-skill","format":"json","content":"# Tailwind-config Skill\n\nPath: `packages/tailwind-config/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tailwind-config/tsconfig.json)","tokens":300,"sizeBytes":195},{"name":"package.json","path":"packages/themes/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/package.json","title":"Themes Skill","category":"anthropic-skill","format":"json","content":"# Themes Skill\n\nPath: `packages/themes/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/package.json)","tokens":300,"sizeBytes":166},{"name":"tsconfig.json","path":"packages/themes/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/tsconfig.json","title":"Themes Skill","category":"anthropic-skill","format":"json","content":"# Themes Skill\n\nPath: `packages/themes/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/tsconfig.json)","tokens":300,"sizeBytes":168},{"name":"package.json","path":"packages/tools/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tools/package.json","title":"@nuclearplayer/tools","category":"anthropic-skill","format":"json","content":"{\n  \"name\": \"@nuclearplayer/tools\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"test\": \"vitest --run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest --run --coverage\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\"\n  },\n  \"devDependencies\": {\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@vitest/coverage-v8\": \"^5.0.0\",\n    \"typescript\": \"^5.9.2\",\n    \"vitest\": \"^5.0.0\"\n  }\n}\n","frontmatter":{"name":"@nuclearplayer/tools","version":"0.0.1","private":true,"type":"module","scripts":{"test":"vitest --run","test:watch":"vitest","test:coverage":"vitest --run --coverage","lint":"eslint .","lint:fix":"eslint . --fix"},"devDependencies":{"@nuclearplayer/eslint-config":"workspace:*","@vitest/coverage-v8":"^5.0.0","typescript":"^5.9.2","vitest":"^5.0.0"}},"isInternal":false,"tokens":157,"sizeBytes":446},{"name":"tsconfig.json","path":"packages/tools/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/tools/tsconfig.json","title":"Tools Skill","category":"anthropic-skill","format":"json","content":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"vitest/globals\"]\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n","frontmatter":{"extends":"../../tsconfig.json","compilerOptions":{"types":["vitest/globals"]},"include":["src/**/*.ts"],"exclude":["node_modules"]},"isInternal":false,"tokens":46,"sizeBytes":160},{"name":"package.json","path":"packages/ui/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/ui/package.json","title":"@nuclearplayer/ui","category":"anthropic-skill","format":"json","content":"{\n  \"name\": \"@nuclearplayer/ui\",\n  \"version\": \"0.0.10\",\n  \"description\": \"Shared UI component library for Nuclear\",\n  \"type\": \"module\",\n  \"main\": \"./src/index.ts\",\n  \"module\": \"./src/index.ts\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./src/index.ts\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./assets/*\": \"./src/assets/*\"\n  },\n  \"files\": [\n    \"src\"\n  ],\n  \"sideEffects\": [\n    \"*.css\",\n    \"./src/index.ts\"\n  ],\n  \"scripts\": {\n    \"dev\": \"vite build --watch\",\n    \"build\": \"tsc && vite build\",\n    \"test\": \"vitest --run\",\n    \"test:watch\": \"vitest\",\n    \"test:coverage\": \"vitest --run --coverage\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"type-check\": \"tsc --noEmit\",\n    \"clean\": \"rm -rf dist build .turbo node_modules/.vite\"\n  },\n  \"dependencies\": {\n    \"@dnd-kit/core\": \"^6.3.1\",\n    \"@dnd-kit/sortable\": \"^10.0.0\",\n    \"@dnd-kit/utilities\": \"^3.2.2\",\n    \"@floating-ui/react-dom\": \"^2.1.7\",\n    \"@fontsource/bricolage-grotesque\": \"^5.2.8\",\n    \"@fontsource/dm-sans\": \"^5.2.6\",\n    \"@fontsource/space-mono\": \"^5.2.9\",\n    \"@headlessui/react\": \"^2.2.7\",\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@nuclearplayer/model\": \"workspace:*\",\n    \"@nuclearplayer/tailwind-config\": \"workspace:*\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.16\",\n    \"@tanstack/react-router\": \"^1.170.35\",\n    \"@tanstack/react-table\": \"^8.21.3\",\n    \"@tanstack/react-virtual\": \"^3.14.11\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"d3-scale\": \"^4.0.2\",\n    \"d3-shape\": \"^3.2.0\",\n    \"lodash-es\": \"^4.17.21\",\n    \"lucide-react\": \"^0.542.0\",\n    \"luxon\": \"^3.7.1\",\n    \"react-activity-calendar\": \"3.2.1\",\n    \"recharts\": \"^3.9.2\",\n    \"tailwind-merge\": \"^3.3.1\",\n    \"tailwindcss\": \"^4.1.11\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/vite\": \"^4.1.11\",\n    \"@testing-library/jest-dom\": \"^7.0.0\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@testing-library/user-event\": \"^14.6.1\",\n    \"@types/d3-scale\": \"^4.0.9\",\n    \"@types/d3-shape\": \"^3.1.8\",\n    \"@types/lodash-es\": \"^4.17.12\",\n    \"@types/luxon\": \"^3.7.1\",\n    \"@types/node\": \"^25.4.0\",\n    \"@types/react\": \"^18.3.23\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"@vitejs/plugin-react\": \"^6.0.0\",\n    \"@vitest/coverage-v8\": \"^5.0.0\",\n    \"jsdom\": \"^26.1.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"typescript\": \"^5.9.2\",\n    \"vite\": \"^8.0.0\",\n    \"vite-plugin-dts\": \"^5.0.0\",\n    \"vite-plugin-svgr\": \"^5.0.0\",\n    \"vitest\": \"^5.0.0\"\n  },\n  \"peerDependencies\": {\n    \"motion\": \"^12.23.12 || ^13.0.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"sonner\": \"^2.0.7\"\n  }\n}\n","frontmatter":{"name":"@nuclearplayer/ui","version":"0.0.10","description":"Shared UI component library for Nuclear","type":"module","main":"./src/index.ts","module":"./src/index.ts","types":"./dist/index.d.ts","exports":{".":{"import":"./src/index.ts","types":"./dist/index.d.ts"},"./assets/*":"./src/assets/*"},"files":["src"],"sideEffects":["*.css","./src/index.ts"],"scripts":{"dev":"vite build --watch","build":"tsc && vite build","test":"vitest --run","test:watch":"vitest","test:coverage":"vitest --run --coverage","lint":"eslint .","lint:fix":"eslint . --fix","type-check":"tsc --noEmit","clean":"rm -rf dist build .turbo node_modules/.vite"},"dependencies":{"@dnd-kit/core":"^6.3.1","@dnd-kit/sortable":"^10.0.0","@dnd-kit/utilities":"^3.2.2","@floating-ui/react-dom":"^2.1.7","@fontsource/bricolage-grotesque":"^5.2.8","@fontsource/dm-sans":"^5.2.6","@fontsource/space-mono":"^5.2.9","@headlessui/react":"^2.2.7","@nuclearplayer/eslint-config":"workspace:*","@nuclearplayer/model":"workspace:*","@nuclearplayer/tailwind-config":"workspace:*","@radix-ui/react-dropdown-menu":"^2.1.16","@tanstack/react-router":"^1.170.35","@tanstack/react-table":"^8.21.3","@tanstack/react-virtual":"^3.14.11","class-variance-authority":"^0.7.1","clsx":"^2.1.1","d3-scale":"^4.0.2","d3-shape":"^3.2.0","lodash-es":"^4.17.21","lucide-react":"^0.542.0","luxon":"^3.7.1","react-activity-calendar":"3.2.1","recharts":"^3.9.2","tailwind-merge":"^3.3.1","tailwindcss":"^4.1.11"},"devDependencies":{"@tailwindcss/vite":"^4.1.11","@testing-library/jest-dom":"^7.0.0","@testing-library/react":"^16.3.0","@testing-library/user-event":"^14.6.1","@types/d3-scale":"^4.0.9","@types/d3-shape":"^3.1.8","@types/lodash-es":"^4.17.12","@types/luxon":"^3.7.1","@types/node":"^25.4.0","@types/react":"^18.3.23","@types/react-dom":"^18.3.7","@vitejs/plugin-react":"^6.0.0","@vitest/coverage-v8":"^5.0.0","jsdom":"^26.1.0","react":"^18.3.1","react-dom":"^18.3.1","typescript":"^5.9.2","vite":"^8.0.0","vite-plugin-dts":"^5.0.0","vite-plugin-svgr":"^5.0.0","vitest":"^5.0.0"},"peerDependencies":{"motion":"^12.23.12 || ^13.0.0","react":"^18.3.1","react-dom":"^18.3.1","sonner":"^2.0.7"}},"isInternal":false,"tokens":982,"sizeBytes":2630},{"name":"tsconfig.json","path":"packages/ui/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/ui/tsconfig.json","title":"Ui Skill","category":"anthropic-skill","format":"json","content":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"noEmit\": false,\n    \"allowImportingTsExtensions\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"types\": [\n      \"node\",\n      \"vitest/globals\",\n      \"@testing-library/jest-dom\",\n      \"vite-plugin-svgr/client\"\n    ]\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"src/**/*.test.ts\", \"src/**/*.test.tsx\"]\n}\n","frontmatter":{"extends":"../../tsconfig.json","compilerOptions":{"outDir":"./dist","rootDir":"./src","noEmit":false,"allowImportingTsExtensions":false,"declaration":true,"declarationMap":true,"sourceMap":true,"types":["node","vitest/globals","@testing-library/jest-dom","vite-plugin-svgr/client"]},"include":["src/**/*"],"exclude":["node_modules","dist","src/**/*.test.ts","src/**/*.test.tsx"]},"isInternal":false,"tokens":142,"sizeBytes":486},{"name":"package.json","path":"packages/website/package.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/website/package.json","title":"@nuclearplayer/website","category":"anthropic-skill","format":"json","content":"{\n  \"name\": \"@nuclearplayer/website\",\n  \"version\": \"2.17.2\",\n  \"description\": \"Nuclear website\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"build\": \"astro build\",\n    \"preview\": \"astro preview\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"type-check\": \"astro check\",\n    \"clean\": \"rm -rf dist .turbo node_modules/.cache .astro\"\n  },\n  \"dependencies\": {\n    \"@astrojs/markdown-remark\": \"^7.3.1\",\n    \"@astrojs/mdx\": \"^8.0.0\",\n    \"@astrojs/react\": \"^6.0.0\",\n    \"@astrojs/rss\": \"^4.0.18\",\n    \"@astrojs/sitemap\": \"^3.7.0\",\n    \"@fontsource-variable/bricolage-grotesque\": \"^5.2.10\",\n    \"@fontsource-variable/dm-sans\": \"^5.2.8\",\n    \"@fontsource/space-mono\": \"^5.2.9\",\n    \"@iconify-json/simple-icons\": \"^1.2.67\",\n    \"@nuclearplayer/ui\": \"workspace:*\",\n    \"astro\": \"^7.0.0\",\n    \"astro-expressive-code\": \"^0.44.0\",\n    \"astro-icon\": \"^1.1.5\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.542.0\",\n    \"medium-zoom\": \"^1.1.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\"\n  },\n  \"devDependencies\": {\n    \"@astrojs/check\": \"^0.9.6\",\n    \"@nuclearplayer/eslint-config\": \"workspace:*\",\n    \"@nuclearplayer/tailwind-config\": \"workspace:*\",\n    \"@tailwindcss/typography\": \"^0.5.19\",\n    \"@tailwindcss/vite\": \"^4.1.11\",\n    \"@types/react\": \"^18.3.23\",\n    \"@types/react-dom\": \"^18.3.7\",\n    \"sharp\": \"^0.35.0\",\n    \"tailwindcss\": \"^4.1.11\",\n    \"typescript\": \"^5.9.2\"\n  }\n}\n","frontmatter":{"name":"@nuclearplayer/website","version":"2.17.2","description":"Nuclear website","type":"module","private":true,"scripts":{"dev":"astro dev","build":"astro build","preview":"astro preview","lint":"eslint .","lint:fix":"eslint . --fix","type-check":"astro check","clean":"rm -rf dist .turbo node_modules/.cache .astro"},"dependencies":{"@astrojs/markdown-remark":"^7.3.1","@astrojs/mdx":"^8.0.0","@astrojs/react":"^6.0.0","@astrojs/rss":"^4.0.18","@astrojs/sitemap":"^3.7.0","@fontsource-variable/bricolage-grotesque":"^5.2.10","@fontsource-variable/dm-sans":"^5.2.8","@fontsource/space-mono":"^5.2.9","@iconify-json/simple-icons":"^1.2.67","@nuclearplayer/ui":"workspace:*","astro":"^7.0.0","astro-expressive-code":"^0.44.0","astro-icon":"^1.1.5","clsx":"^2.1.1","lucide-react":"^0.542.0","medium-zoom":"^1.1.0","react":"^18.3.1","react-dom":"^18.3.1"},"devDependencies":{"@astrojs/check":"^0.9.6","@nuclearplayer/eslint-config":"workspace:*","@nuclearplayer/tailwind-config":"workspace:*","@tailwindcss/typography":"^0.5.19","@tailwindcss/vite":"^4.1.11","@types/react":"^18.3.23","@types/react-dom":"^18.3.7","sharp":"^0.35.0","tailwindcss":"^4.1.11","typescript":"^5.9.2"}},"isInternal":false,"tokens":520,"sizeBytes":1432},{"name":"tsconfig.json","path":"packages/website/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/website/tsconfig.json","title":"Website Skill","category":"anthropic-skill","format":"json","content":"{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"declaration\": false,\n    \"declarationMap\": false\n  },\n  \"include\": [\"src/**/*\", \"astro.config.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n","frontmatter":{"extends":"../../tsconfig.json","compilerOptions":{"allowSyntheticDefaultImports":true,"moduleResolution":"bundler","resolveJsonModule":true,"declaration":false,"declarationMap":false},"include":["src/**/*","astro.config.ts"],"exclude":["node_modules","dist"]},"isInternal":false,"tokens":84,"sizeBytes":314},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# Nuclear Music Player - Copilot Instructions\n\n## Project overview\n\nNuclear is a free, open-source music player without ads or tracking. Search for any song or artist, build playlists, and start listening.\n\nIt's a monorepo managed with pnpm and turbo.\n\nThis particular repo is a rewrite project, with the original Nuclear codebase in a different repo. \n\n### Key features\n\n- **Themes**: Support for basic and advanced themes, which work by customizing CSS variables that then control Tailwind classes. Users can load their own themes from JSON, or use built-in ones.\n- **Plugins**: A powerful plugin system that allows you to control any part of the player. No sandboxing.\n\n### Tech stack\n\n- Typescript - primary language.\n- Tauri - creates a native desktop application shell.\n- Tailwind v4 - CSS-first setup. Configured via CSS using @theme and @layer (see `packages/tailwind-config/global.css`). No tailwind.config.js. Don't use built-in colors, prefer the palette defined in `global.css`.\n- Turbo repo - tool for managing the monorepo.\n- pnpm - the preferred package manager. Used for workspaces.\n- eslint + prettier - formatting and linting.\n- Storybook - used for demoing UI components.\n- Vite - build tool\n- Vitest - for all tests\n- Lucide React - icon library\n- motion and tw-animate-css - for animations\n- TanStack Router - chosen solution for routing.\n- TanStack Query v5 - chosen solution for HTTP requests.\n\n### Packages\n- `@nuclearplayer/player` - Main Tauri app (React + Rust).  \n- `@nuclearplayer/plugin-sdk` - Plugin system (TS/React).  \n- `@nuclearplayer/ui` - Shared UI components.  \n- `@nuclearplayer/model` - Data model.\n- `@nuclearplayer/docs` - Gitbook documentation.\n- `@nuclearplayer/tailwind-config` - Shared Tailwind config.  \n- `@nuclearplayer/eslint-config` - Shared linting & formatting rules.  \n- `@nuclearplayer/hifi` - Advanced HTML5 audio component for playback.\n- `@nuclearplayer/themes` - Theming system.\n- `@nuclearplayer/tools` - Build and maintenance utilities.\n- `@nuclearplayer/storybook` - Storybook stories.\n\n### Workflow Commands\n\n```bash\npnpm dev        # run the project in dev mode\npnpm build      # build all packages\npnpm lint       # lint all packages\npnpm test       # test all packages\npnpm test:coverage  # run tests with coverage\npnpm type-check     # run TypeScript checks across packages\npnpm tauri          # run Tauri CLI for the player\npnpm storybook      # run Storybook\n```\n\n## Coding principles\n\n### General\n\n- Prioritize readability.\n- This is a production project meant for long term maintenance and development. Cutting corners, using short term solutions, placeholders, half-baked methods, and messy code is unacceptable.\n- Avoid premature abstractions. Start concrete, extract later.\n- Prefer clarity over cleverness.\n- Do not leave comments in code. Their place is in the chat.\n- Stick to existing conventions. This is a monorepo so standardizing everything is very very important. Look at other packages when in doubt. Use centralized configs for tools like Typescript, Eslint, Prettier, Tailwind.\n- Break work into the smallest reasonable steps. Small commits > big dumps.\n- Always be on the lookout for dead code, copy-pasta, and other opportunities to optimize and trim the codebase in a sensible way.\n\n### TypeScript / React\n- Use `type`, not `interface` (except when TS requires merging). Do not use interfaces for props.\n- Use `const Component: FC<Props> = () => {}` instead of `function Component()`.\n- No magic numbers → extract into named constants.\n- Prefer compound components (`Component.Sub`) when building complex widgets.\n- Keep business logic out of UI components.\n\n### Architecture\n- UI components should stay dumb, presentation-only.\n- State management:\n  - **Zustand** for persistent UI state.  \n  - **React state** for local, temporary state.\n  - **TanStack Query v5 (aka react-query)** for HTTP requests.\n- Routing:\n  - **TanStack Router** for client-side routing.\n- Lift complex or performance-critical logic to Tauri (Rust).\n\n### Testing\n- Use **Vitest + React Testing Library**.\n- Test like a user: minimal mocks, simulate interactions.\n- Only mock external dependencies (HTTP, FS, Tauri).\n- Snapshot tests: **basic rendering only**. Start names with `(Snapshot)`.\n- Extract DOM querying into wrappers; assertions stay in tests.\n- Coverage: enabled across packages with V8-based coverage and CI reporting.\n- Use the test runner tool instead of running tests in the terminal manually.\n\n### Design & UX Philosophy\n\n- Visual style: **neo-brutalist with premium polish**.\n- Animations should enhance UX, not slow it down.\n- Disable animations during high-friction moments (e.g., resize).\n- Use **motion** + **tw-animate-css** for smooth springy physics.\n- Feel: professional yet approachable, Discord-like.\n\n#### Typography\n\n- Fonts are standardized at the design system level. Prefer utilities `font-sans` and `font-heading` when needed.\n- Defaults are applied for common HTML tags (e.g., body, headings), so you rarely need to set fonts manually.\n\n## Final Notes\n\n- Treat this as **production code from day one**. No shortcuts, no placeholders.\n- Prioritize long-term maintainability.\n- Work with me iteratively: pause, summarize, ask.\n- Above all: **be reliable, disciplined, and clear.**\n- Use Serena tools wherever possible for searches, lookups, replacements, and file operations.","isInternal":false,"tokens":1242,"sizeBytes":5374},{"name":"README.md","path":"packages/docs/README.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/README.md","title":"docs Documentation","category":"plugin-manifest","format":"markdown","content":"# docs Documentation\n\nPath: `packages/docs/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/README.md)","tokens":300,"sizeBytes":162},{"name":"README.md","path":"packages/plugin-sdk/README.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/README.md","title":"plugin-sdk Documentation","category":"plugin-manifest","format":"markdown","content":"# plugin-sdk Documentation\n\nPath: `packages/plugin-sdk/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/plugin-sdk/README.md)","tokens":300,"sizeBytes":180},{"name":"README.md","path":"packages/themes/README.md","rawUrl":"https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/README.md","title":"themes Documentation","category":"plugin-manifest","format":"markdown","content":"# themes Documentation\n\nPath: `packages/themes/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/themes/README.md)","tokens":300,"sizeBytes":168}],"systemPromptSnippet":"<yakaai_skills repo=\"nukeop/nuclear\">\n<!-- File: AGENTS.md (Tokens: ~3367 | Category: root-instruction) -->\n# AGENTS.md\n\nGuidelines for AI coding agents working on Nuclear.\n\n## Project Overview\n\nNuclear is a free, open-source music player without ads or tracking. Search for any song or artist, build playlists, and start listening. It's a desktop app built with Tauri (Rust + React), organized as a pnpm monorepo managed with Turborepo.\n\n### Packages\n\n- `@nuclearplayer/player` - Main Tauri app (React + Rust)\n- `@nuclearplayer/ui` - Shared UI components\n- `@nuclearplayer/plugin-sdk` - Plugin system (published to npm)\n- `@nuclearplayer/model` - Data model\n- `@nuclearplayer/themes` - Theming system\n- `@nuclearplayer/hifi` - Advanced HTML5 audio component\n- `@nuclearplayer/tailwind-config` - Shared Tailwind config\n- `@nuclearplayer/eslint-config` - Shared linting rules\n- `@nuclearplayer/i18n` - Internationalization\n- `@nuclearplayer/storybook` - Component demos\n- `@nuclearplayer/videos` - Remotion videos rendered from UI components\n- `@nuclearplayer/tools` - Build and maintenance utilities\n- `@nuclearplayer/docs` - Documentation\n- `@nuclearplayer/website` - Project website (Astro)\n\n## Commands\n\n```bash\n# Development\npnpm dev                    # Run player in dev mode\npnpm dev:remote             # Same, but binds Vite to 0.0.0.0 so the remote control UI is reachable from other devices\npnpm storybook              # Run Storybook\npnpm --filter @nuclearplayer/videos dev      # Run the Remotion Studio\npnpm --filter @nuclearplayer/videos render   # Render a video to packages/videos/out\n\n# Build\npnpm build                  # Build all packages\npnpm tauri build            # Build Tauri app\n\n# Quality\npnpm lint                   # Lint all packages\npnpm lint:fix               # Lint and auto-fix\npnpm type-check             # TypeScript checks\npnpm test                   # Run all tests\npnpm test:coverage          # Run tests with coverage\npnpm clean                  # Clean build artifacts\n\n# Package-specific testing\npnpm --filter @nuclearplayer/ui test -- src/components/Badge/Badge.test.tsx\npnpm --filter @nuclearplayer/ui test -- --testNamePattern=\"renders\"\n\n# Update snapshots (run at root for all, or filter to a specific package)\n\n# At root\npnpm test -- -u\n\n# Filtering for a specific package\npnpm --filter @nuclearplayer/ui test -- -u\n\n# After cd'ing into a package\npnpm test -u\n```\n\n## Code Style\n\n### General Principles\n\n- Prioritize readability over cleverness\n- No comments in code - explain reasoning in chat/commits\n- Avoid premature abstractions - start concrete, extract later\n- Small, focused changes over large dumps\n- Never commit unless explicitly asked\n\n### TypeScript\n\n- Use `type` not `interface` (except when merging is required)\n- Strict mode with `noUnusedLocals` and `noUnusedParameters`\n- Do not use one-letter variable names.\nAVOID: `(b) => b.buildIndexEntry()`\nPREFER: `(build) => build.buildIndexEntry()`\n\n### React Components\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\n\nimport { cn } from '../../utils';\n\nconst componentVariants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype ComponentProps = ComponentProps<'div'> &\n  VariantProps<typeof componentVariants>;\n\nexport const Component: FC<ComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(componentVariants({ variant, className }))} {...props} />\n);\n```\n\n- Use `const Component: FC<Props>` not `function Component()`\n- Compound components (`Component.Sub`) for complex widgets\n- Keep business logic out of UI components\n\n### Adding UI Components\n\nWhen adding a new component to `@nuclearplayer/ui`:\n\n1. Create component directory: `packages/ui/src/components/MyComponent/`\n   - `MyComponent.tsx` - implementation\n   - `MyComponent.test.tsx` - tests (aim for 100% coverage)\n   - `index.ts` - re-exports\n2. Export from `packages/ui/src/components/index.ts`\n3. Add Storybook story in `packages/storybook/src/MyComponent.stories.tsx`\n4. Include snapshot test(s) covering all variants\n\n### Styling (Tailwind v4)\n\n- CSS-first config in `packages/tailwind-config/global.css`\n- Theme colors: `bg-background`, `text-foreground`, `bg-primary`\n- Accents: `accent-green`, `accent-yellow`, `accent-purple`, `accent-blue`, `accent-orange`, `accent-cyan`, `accent-red`\n- Use `cn()` for conditional classes, `cva()` for variants\n\n### State Management\n\n- **Zustand** - persistent UI state\n- **React state** - local, temporary state\n- **TanStack Query v5** - HTTP requests/server state\n- **TanStack Router** - client-side routing\n\n### Standardized Libraries\n\n- **Icons**: Lucide React (not heroicons, not font-awesome)\n- **Toasts**: Sonner\n- **Dates**: Luxon\n- **Utilities**: lodash-es (use individual imports: `import isEqual from 'lodash-es/isEqual'`)\n- **HTTP**: Native fetch via ApiClient base class (no axios)\n\n### Adding New Domains\n\nA \"domain\" is a feature area exposed to plugins (e.g., settings, queue, favorites). When adding a new domain:\n\n1. **Types** (`packages/plugin-sdk/src/types/myDomain.ts`)\n   - Define the `MyDomainHost` interface (the contract between player and SDK)\n   - Export any related types plugins will use\n\n2. **API class** (`packages/plugin-sdk/src/api/myDomain.ts`)\n   - Create a class that wraps the host and exposes methods to plugins\n   - Add to `NuclearAPI` constructor in `packages/plugin-sdk/src/api/index.ts`\n\n3. **Store** (`packages/player/src/stores/myDomainStore.ts`)\n   - Zustand store holding the domain state\n   - Persists to disk via `@tauri-apps/plugin-store` if needed\n\n4. **Host** (`packages/player/src/services/myDomainHost.ts`)\n   - Implements the `MyDomainHost` interface\n   - Bridges the SDK API to the Zustand store\n   - Passed to `NuclearAPI` when initializing plugins\n\n### External API Clients\n\nLive in `packages/player/src/apis/`. Use `ApiClient` base class (fetch→json→Zod).\n\n- Validate external data with Zod schemas\n- Export singleton instances\n- One class per external service\n\n### Internationalization\n\nAll user-facing strings go through i18n - no hardcoded UI text.\n\n```tsx\nimport { useTranslation } from '@nuclearplayer/i18n';\n\nconst { t } = useTranslation();\n<span>{t('navigation.settings')}</span>\n```\n\nAdd new strings to `packages/i18n/src/locales/en_US.json` only. Other locales come from Crowdin.\n\n## Testing\n\nTests use Vitest + React Testing Library. Globals enabled (`describe`, `it`, `expect`, `vi`).\n\n- Integration tests over unit tests for user-facing behavior. Render real components and assert on DOM content rather than verifying mock calls.\n- Unit tests for utilities - standalone data structures (RingBuffer, parsers) deserve isolated tests. Use them sparingly.\n- Test user behavior, not implementation details\n- Minimize mocks - only mock external deps (HTTP, FS, Tauri)\n- Snapshot tests: prefix with `(Snapshot)`, basic rendering only\n- Never use `querySelector` in tests. Prefer RTL queries.\n- When semantic queries aren't possible, add `data-testid` attributes. And don't be shy with them\n- Don't use defensive measures like try-catch or conditional checks in tests. The test will fail anyway if our assumptions are wrong.\n\n### Test-first for views\n\nWhen building a new view, write the test wrapper and tests **before** any implementation code. The tests describe what the user sees and does — they define the contract. Then implement to make them pass.\n\nDon't start with unit tests for internal utilities (grouping functions, registries, etc.). Start from the outside: what does the user see on the page? The internal structure is an implementation detail that falls out of making the tests green.\n\n### Test Wrappers for Views\n\nPlayer views and some components use a `*.test-wrapper.tsx` file that creates a domain-specific abstraction layer over the DOM. This lets tests read like user stories, and if the implementation changes, only the wrapper needs updating.\n\n**Wrapper conventions:**\n- Use **getters** for element queries: `get emptyState()`, `get cards()` — not `getEmptyState()`\n- Use **nested objects** for interactive elements: `createButton: { get element(), async click() }`\n- Use **methods** for multi-step user actions: `async openContextMenu(title: string)`\n- Tests should use `Wrapper.emptyState`, `Wrapper.cards`, `Wrapper.createButton.click()` — not bare `screen` queries\n- The wrapper is the only place that knows about test IDs, roles, and DOM structure\n- Don't use queryX methods in the wrapper - always get or find as appropriate.\n- Never use fireEvent. Always use userEvent for interactions.\n\n### Test fixtures\n\nTo populate the app with testing data, use fixtures. See `packages/player/src/test/fixtures` for examples.\n\n### Wrapper fixtures\n\nTest wrappers can expose a `fixtures` object with factory methods that return pre-configured builders for common test scenarios. This keeps test setup readable and co-located with the wrapper, while the raw fixture data itself lives in `packages/player/src/test/fixtures/`.\n\n```tsx\n// Dashboard.test-wrapper.tsx\nimport { TOP_TRACKS_RADIOHEAD } from '../../test/fixtures/dashboard';\n\nexport const DashboardWrapper = {\n  // ... mount, getters, etc.\n\n  fixtures: {\n    topTracksProvider() {\n      return new DashboardProviderBuilder()\n        .withCapabilities('topTracks')\n        .withFetchTopTracks(async () => TOP_TRACKS_RADIOHEAD);\n    },\n  },\n};\n\n// Dashboard.test.tsx\nDashboardWrapper.seedProvider(DashboardWrapper.fixtures.topTracksProvider());\n```\n\n### The builder pattern for tests\n\nWe use builders to create test data and various entities cleanly. You can see them in `packages/player/src/test/builders`.\n\n- A builder is a class that has an instance of the object it's building\n- When the builder is instantiated, it creates a default object with reasonable defaults\n- The builder has methods that mutate the object and return `this` for chaining\n- The `build()` method returns the final object, which can then be used in tests\n\n```tsx\n// Playlists.test-wrapper.tsx\nexport const PlaylistsWrapper = {\n  async mount(): Promise<RenderResult> { /* ... */ },\n\n  get emptyState() {\n    return screen.queryByTestId('empty-state');\n  },\n  get cards() {\n    return screen.queryAllByTestId('card');\n  },\n\n  createButton: {\n    get element() {\n      return screen.getByTestId('create-playlist-button');\n    },\n    async click() {\n      await userEvent.click(this.element);\n    },\n  },\n};\n\n// Playlists.test.tsx — reads like a user story\nit('shows empty state when no playlists', async () => {\n  await PlaylistsWrapper.mount();\n  expect(PlaylistsWrapper.emptyState).toBeInTheDocument();\n});\n```\n\n## File Organization\n\n```\npackages/ui/src/components/Badge/\n  Badge.tsx           # Implementation\n  Badge.test.tsx      # Tests\n  index.ts            # Re-exports\n  __snapshots__/      # Vitest snapshots\n```\n\n## Rust Backend\n\nThe Tauri backend lives in `packages/player/src-tauri/src/`. Modules:\n\n- `bridge/` - bidirectional RPC. Lets Rust servers call into the frontend (`Bridge::call` emits a `bridge:request` event, frontend replies via the `bridge_respond` command).\n- `http_api/` - Axum REST + SSE server for Nuclear Jam remote control\n- `mcp/` - MCP server exposing player functions as tools to LLM clients\n- `mpd/` - MPD-protocol TCP server for clients like ncmpcpp\n- `stream_server.rs` - local audio proxy adding CORS + Range so the browser can play blocked streams\n- `http.rs` - `http_fetch` command, a CORS-bypassing HTTP proxy for the frontend\n- `ytdlp.rs` / `ytdlp_setup.rs` - yt-dlp subprocess wrapper; auto-downloads the binary\n- `discord.rs` - Discord Rich Presence\n- `commands.rs` - filesystem helpers (zip, download, flatpak detection)\n- `net.rs`, `setup.rs`, `logging.rs` - port binding, log plugin config, startup log ring buffer\n- `lib.rs` - declares modules, registers all commands, runs `init_*` in `.setup()`. `main.rs` - binary entry, env fixups.\n\nCommands (run from `packages/player/src-tauri/`):\n\n```bash\ncargo test          # Rust tests (no clippy/rustfmt configured; run manually)\n```\n\n`pnpm dev` runs `tauri dev`; `pnpm build` (in `packages/player`) runs the full `tauri build`.\n\n## Design Philosophy\n\n- Neo-brutalist with premium polish - bold borders, purposeful shadows\n- Premium, designed feel\n- Animations via `motion` and `tw-animate-css`\n- Disable animations during high-friction moments (resize, drag)\n- Avoid generic AI patterns (icon-grid cards, stock heroes, \"Built with love\" badges)\n\n## Tooling Notes\n\n- **pnpm** with workspace protocol for internal deps\n- **Turborepo** for task orchestration\n- **ESLint + Prettier** run together\n- **Husky + lint-staged** for pre-commit hooks\n\nUse centralized configs from eslint-config and tailwind-config packages.\n\nAssume TanStack Router routes regenerate on dev - don't regenerate manually.\n\n## Changelog\n\n`packages/player/changelog.json` is the source of truth for the in-app \"What's New\" tab and auto-generated GitHub release notes.\n\nWhen building a user-facing feature, fix, or improvement, add an entry to the top of the array according to the format you find there.\n\n## Releasing\n\n### Nuclear Player\n\nReleases are triggered by git tags. The workflow builds for macOS (arm64/x64), Linux, and Windows. Release notes are auto-generated from `packages/player/changelog.json`.\n\n```bash\n# 1. Bump versions, update the appstream metainfo, commit, and tag\npnpm release:prepare X.Y.Z\n\n# 2. Push the tag\ngit push origin player@X.Y.Z\n```\n\n`release:prepare` does everything that's needed for a release. Do not bump versions or edit any files by hand when preparing a release.\n\nThe `release-player.yml` workflow creates a GitHub release with platform binaries.\n\n### Plugin SDK\n\nPublished to npm via the `release-plugin-sdk.yml` workflow.\n\n```bash\n# 1. Update version in packages/plugin-sdk/package.json\n# 2. Commit the version bump\ngit add packages/plugin-sdk/package.json && git commit -m \"plugin-sdk@X.Y.Z\"\n\n# 3. Tag and push\ngit tag plugin-sdk@X.Y.Z\ngit push origin plugin-sdk@X.Y.Z\n```\n\nThe workflow builds with `build:npm`, runs tests, and publishes to npm.\n\n<!-- File: .agents/skills/creating-components/SKILL.md (Tokens: ~953 | Category: anthropic-skill) -->\n---\nname: creating-components\ndescription: Use when creating new UI components in packages/ui. Covers component structure, tests, stories, and what to avoid.\n---\n\n# Creating UI Components\n\n## Component Structure\n\n```\npackages/ui/src/components/MyComponent/\n├── MyComponent.tsx      # Implementation\n├── MyComponent.test.tsx # Tests\n├── index.ts             # Re-exports\n└── __snapshots__/       # Vitest snapshots (auto-generated)\n```\n\nAfter creating, export from `packages/ui/src/components/index.ts`.\n\n## Implementation Pattern\n\n```tsx\nimport { cva, VariantProps } from 'class-variance-authority';\nimport { ComponentProps, FC } from 'react';\nimport { cn } from '../../utils';\n\nconst variants = cva('base-classes', {\n  variants: { /* ... */ },\n  defaultVariants: { /* ... */ },\n});\n\ntype MyComponentProps = ComponentProps<'div'> & VariantProps<typeof variants>;\n\nexport const MyComponent: FC<MyComponentProps> = ({\n  className,\n  variant,\n  ...props\n}) => (\n  <div className={cn(variants({ variant, className }))} {...props} />\n);\n```\n\n## Tests\n\n**What to test:**\n- Snapshots (1-2 covering key variants)\n- User interactions\n- Behavior (callbacks called with correct args)\n\n**What NOT to test:**\n- CSS classes, attributes (use snapshots instead)\n- Internal state\n- Things TypeScript already enforces\n\n**Consolidate tests.** One test can cover multiple related assertions. An exception to that is snapshot tests - one snapshot per variant/state.\n\n## Stories\n\nCreate `packages/storybook/src/MyComponent.stories.tsx`.\n\n**One story can show multiple related variants.** For example, it's wasteful to create a story for each variant of a button. Put them all together in one place. Don't create separate stories for each variant:\n\n```tsx\nimport { Meta, StoryObj } from '@storybook/react-vite';\nimport { useState } from 'react';\nimport { MyComponent } from '@nuclearplayer/ui';\n\nconst meta = {\n  title: 'Components/MyComponent',\n  component: MyComponent,\n  tags: ['autodocs'],\n} satisfies Meta<typeof MyComponent>;\n\nexport default meta;\ntype Story = StoryObj<typeof MyComponent>;\n\nexport const AllVariants: Story = {\n  render: () => {\n    const [state, setState] = useState(initialState);\n    return (\n      <div className=\"flex flex-col gap-4\">\n        {/* Show all variants, states, interactions */}\n      </div>\n    );\n  },\n};\n```\n\nThis isn't an iron rule, sometimes it will make more sense to have separate stories.\n\nDon't run storybook build checks.\n\n## Avoiding Duplication\n\nBefore creating a new component, check if an existing one can be extended.\n\n**Pattern: Discriminated unions for mode variants**\n\nExample: Instead of creating `SingleSelect` and `MultiSelect` components:\n\n```tsx\ntype Props = \n  | { multiple?: false; selected: string; onChange: (id: string) => void }\n  | { multiple: true; selected: string[]; onChange: (ids: string[]) => void };\n```\n\nTypeScript enforces correct types based on the `multiple` prop.\n\n## Classes for customization\n\nWhere it's likely that a component will need custom styling, expose a `className` prop.\n\nIf there are many parts that may need styling, consider exposing a `classes` prop with specific class names for each part. Define a type for the `classes` prop. Refer to `packages/ui/src/components/TrackTable/types.ts` for an example.\n\n## Strings\n\nAll user-facing strings go through i18n - no hardcoded UI text.\nIf a new component in the ui package needs labels and other kinds of localized text, it should accept a `labels` prop with the relevant strings. The prop should have its own type defined. Refer to `packages/ui/src/components/QueueItem/types.ts` for an example.\n\n## Accessibility\n\nWe don't care about that. If there's an opportunity to handle that easily, do it, but don't go out of your way.\n\n## Checklist\n\n- [ ] Component in `packages/ui/src/components/MyComponent/`\n- [ ] Exported from `packages/ui/src/components/index.ts`\n- [ ] Tests cover behavior, not implementation\n- [ ] Tests consolidated (not one per variant)\n- [ ] One story showing all variants\n- [ ] No CSS class assertions in tests\n- [ ] No duplicate component when extending existing one works\n\n<!-- File: .agents/skills/host-pattern/SKILL.md (Tokens: ~1221 | Category: anthropic-skill) -->\n---\nname: host-pattern\ndescription: Use when adding a new domain to Nuclear's plugin system, or implementing a host. Covers the host pattern (how player functionality is exposed to plugins), the host interface and API class structure, how hosts are implemented in the player, error handling conventions, and what files to create and modify. Trigger phrases include \"add a domain\", \"new domain\", \"host implementation\", \"host pattern\", \"createPluginAPI\".\n---\n\n# Host pattern\n\nEvery feature area follows the same three-layer structure:\n\n1. Host type - the contract (SDK, no implementation)\n2. API class - what plugins actually call (SDK, wraps the host)\n3. Host implementation - bridges the API to player internals (player)\n\nPlugins call methods on an API class (e.g. `api.Queue.addToQueue()`), which holds a reference to the host and delegates to it. Plugins never touch a host directly.\n\n## Files to create/modify\n\n### SDK (`packages/plugin-sdk/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/types/yourDomain.ts` | `YourDomainHost` interface + related types |\n| Create | `src/api/yourDomain.ts` | `YourDomainAPI` class |\n| Modify | `src/api/index.ts` | Add `yourDomainHost` option + `YourDomain` field to `NuclearAPI` |\n| Modify | `src/index.ts` | Export types and API class |\n\n### Player (`packages/player/`)\n\n| Action | File | What |\n|--------|------|------|\n| Create | `src/services/yourDomainHost.ts` | Host implementation + singleton export |\n| Modify | `src/services/plugins/createPluginAPI.ts` | Pass singleton to `NuclearPluginAPI` |\n| Modify | `src/services/logger.ts` | Add domain to `LOG_SCOPES` (needed for `reportError`) |\n\nIf the domain needs shared model types: create `packages/model/src/yourDomain.ts` and re-export from `packages/model/src/index.ts`.\n\n## API class pattern\n\nEvery API class follows this structure.\n\n```typescript\n// packages/plugin-sdk/src/api/yourDomain.ts\nexport class YourDomainAPI {\n  #host?: YourDomainHost;\n\n  constructor(host?: YourDomainHost) {\n    this.#host = host;\n  }\n\n  #withHost<T>(fn: (host: YourDomainHost) => T): T {\n    const host = this.#host;\n    if (!host) {\n      throw new Error('YourDomain host not available');\n    }\n    return fn(host);\n  }\n\n  yourMethod(arg: SomeType) {\n    return this.#withHost((host) => host.yourMethod(arg));\n  }\n}\n```\n\nReference: `packages/plugin-sdk/src/api/queue.ts`, `packages/plugin-sdk/src/api/dashboard.ts`\n\n## Connecting to NuclearAPI\n\n```typescript\n// packages/plugin-sdk/src/api/index.ts\nimport type { YourDomainHost } from '../types/yourDomain';\nimport { YourDomainAPI } from './yourDomain';\n\n// Add to class:\nreadonly YourDomain: YourDomainAPI;\n\n// Add to constructor opts type:\nyourDomainHost?: YourDomainHost;\n\n// Add to constructor body:\nthis.YourDomain = new YourDomainAPI(opts?.yourDomainHost);\n```\n\n```typescript\n// packages/player/src/services/plugins/createPluginAPI.ts\nimport { yourDomainHost } from '../../services/yourDomainHost';\n// Add to NuclearPluginAPI constructor call:\nyourDomainHost,\n```\n\n## Host implementation\n\nHosts bridge the SDK contract to whatever backs the domain. Most commonly a Zustand store:\n\n```typescript\n// packages/player/src/services/yourDomainHost.ts\nimport type { YourDomainHost } from '@nuclearplayer/plugin-sdk';\nimport { useYourDomainStore } from '../stores/yourDomainStore';\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  yourMethod: (arg) => useYourDomainStore.getState().doThing(arg),\n  getState: () => useYourDomainStore.getState().value,\n});\n\nexport const yourDomainHost = createYourDomainHost();\n```\n\nA host can also have access to the **provider registry** (metadata, streaming, dashboard), resolving which registered provider to call. Two patterns:\n\n**Single provider** (user picks one in Sources):\n\n```typescript\nconst getProvider = (providerId?: string) =>\n  providersHost.get<YourProvider>(\n    providerId ?? providersHost.getActive('yourkind'), 'yourkind',\n  );\n\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetch(query, providerId?) {\n    const provider = getProvider(providerId);\n    if (!provider) throw new Error('No provider available');\n    return provider.fetch(query);\n  },\n});\n```\n\n**Fan-out** (aggregate across all providers - dashboard):\n\n```typescript\nexport const createYourDomainHost = (): YourDomainHost => ({\n  async fetchAll() {\n    const providers = providersHost.list('yourkind') as YourProvider[];\n    const results = await Promise.allSettled(providers.map(async (provider) => {\n      const items = await provider.fetch();\n      return { providerId: provider.id, providerName: provider.name, items };\n    }));\n    return results.filter(isFulfilled).map((r) => r.value);\n  },\n});\n```\n\nReference: `packages/player/src/services/metadataHost.ts` (single), `packages/player/src/services/dashboardHost.ts` (fan-out)\n\n## Error handling\n\n- **Store-backed hosts**: let errors propagate naturally.\n- **Provider-backed, single provider** (metadata, streaming): throw errors so the user sees failures.\n- **Provider-backed, fan-out** (dashboard): catch per-provider so one failure doesn't block others. Log via `reportError`, return partial results.\n- `MissingCapabilityError`: always a plugin bug. Single-provider domains throw it; fan-out domains log it and skip the offending provider.\n\n<!-- File: .agents/skills/writing-docs/SKILL.md (Tokens: ~883 | Category: anthropic-skill) -->\n---\nname: writing-docs\ndescription: Use when writing or editing documentation in packages/docs. Covers Gitbook markdown syntax, special blocks, page structure, and the SUMMARY.md table of contents. Trigger phrases include \"write docs\", \"add documentation\", \"docs page\", \"gitbook\", \"user manual\".\n---\n\n# Writing Documentation\n\nDocs live in `packages/docs/`. Hosted on Gitbook, synced from the repo via `.gitbook.yaml`.\n\n## Page structure\n\nEvery page starts with YAML frontmatter and a top-level heading:\n\n```markdown\n---\ndescription: Short description shown in search and link previews\n---\n\n# Page title\n```\n\n## Table of contents\n\n`packages/docs/SUMMARY.md` defines the sidebar navigation. Group pages under `##` headings:\n\n```markdown\n# Table of contents\n\n* [Nuclear Documentation](README.md)\n\n## User Manual\n\n* [Getting started](user-manual/getting-started.md)\n* [Installation](user-manual/installation.md)\n\n## Plugins\n\n* [Getting started](plugins/getting-started.md)\n```\n\nWhen adding a new page, always add it to SUMMARY.md or it won't appear in the sidebar.\n\n## Gitbook special blocks\n\n### Tabs\n\nUse for platform-specific instructions or language variants:\n\n```markdown\n{% tabs %}\n\n{% tab title=\"Windows\" %}\nContent for Windows.\n{% endtab %}\n\n{% tab title=\"macOS\" %}\nContent for macOS.\n{% endtab %}\n\n{% tab title=\"Linux\" %}\nContent for Linux.\n{% endtab %}\n\n{% endtabs %}\n```\n\nTabs can contain any block type (code, tables, lists, images) but cannot nest other tabs or expandables.\n\n### Hints (callouts)\n\nFour styles: `info`, `success`, `warning`, `danger`.\n\n```markdown\n{% hint style=\"info\" %}\nGeneral information or tips.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nNon-critical warnings or gotchas.\n{% endhint %}\n\n{% hint style=\"danger\" %}\nDestructive actions or critical information.\n{% endhint %}\n\n{% hint style=\"success\" %}\nPositive actions or confirmations.\n{% endhint %}\n```\n\n### Code blocks with options\n\nWrap in `{% code %}` for title, line numbers, or wrapping:\n\n````markdown\n{% code title=\"config.json\" overflow=\"wrap\" lineNumbers=\"true\" %}\n\n```json\n{\n  \"key\": \"value\"\n}\n```\n\n{% endcode %}\n````\n\nWithout the wrapper, standard fenced code blocks work fine.\n\n### Expandable (collapsible sections)\n\nUses standard HTML `<details>` / `<summary>`:\n\n```markdown\n<details>\n\n<summary>Click to expand</summary>\n\nHidden content goes here. Supports lists and code blocks.\n\n</details>\n```\n\nAdd `open` attribute to expand by default: `<details open>`.\n\n### Page links\n\nProminent block-level links to other pages:\n\n```markdown\n{% content-ref url=\"getting-started.md\" %}\n[Getting started](getting-started.md)\n{% endcontent-ref %}\n```\n\n### Cards\n\nGrid layout for landing pages. Uses a table with `data-view=\"cards\"`:\n\n```markdown\n<table data-view=\"cards\">\n  <thead>\n    <tr>\n      <th></th>\n      <th></th>\n      <th data-hidden data-card-target data-type=\"content-ref\"></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td><strong>Card title</strong></td>\n      <td>Card description.</td>\n      <td><a href=\"page.md\">page.md</a></td>\n    </tr>\n  </tbody>\n</table>\n```\n\n### Annotations (footnotes)\n\n```markdown\nNuclear uses plugins[^1] to provide functionality.\n\n[^1]: Plugins are installed from the plugin store.\n```\n\n## Content guidelines\n\n- No brand names of third-party services in docs\n- All user-facing strings reference i18n keys, not hardcoded text\n- Use relative links between docs pages (`../themes/themes.md`), not absolute URLs\n- Keep pages focused on one topic. Link to related pages instead of duplicating content.\n- Describe the UI as the user sees it: use actual button labels, menu names, and icon descriptions\n\n<!-- File: .agents/skills/writing-plugins/SKILL.md (Tokens: ~849 | Category: anthropic-skill) -->\n---\nname: writing-plugins\ndescription: Use when writing, scaffolding, or modifying Nuclear plugins. Covers plugin structure, manifest, entry point, provider types, available APIs, and publishing. Trigger phrases include \"create a plugin\", \"write a plugin\", \"plugin scaffold\", \"streaming provider\", \"metadata provider\".\n---\n\n# Writing Nuclear Plugins\n\nPlugins are standalone repos compiled in-browser via esbuild-wasm.\n\n## Structure\n\n```\nmy-plugin/\n  package.json      # Manifest with nuclear metadata\n  src/\n    index.ts        # Entry point, default exports NuclearPlugin\n```\n\n## Manifest (package.json)\n\n```json\n{\n  \"name\": \"nuclear-plugin-example\",\n  \"version\": \"0.1.0\",\n  \"description\": \"What this plugin does\",\n  \"author\": \"Your Name\",\n  \"license\": \"AGPL-3.0-only\",\n  \"main\": \"src/index.ts\",\n  \"type\": \"module\",\n  \"nuclear\": {\n    \"displayName\": \"Example Plugin\",\n    \"category\": \"streaming\",\n    \"icon\": { \"type\": \"link\", \"link\": \"https://example.com/icon.svg\" }\n  },\n  \"dependencies\": {\n    \"@nuclearplayer/plugin-sdk\": \"^1.1.0\"\n  }\n}\n```\n\nCategories: `streaming`, `metadata`, `lyrics`\n\n## Entry Point\n\n```typescript\nimport type { NuclearPlugin, NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';\n\nconst plugin: NuclearPlugin = {\n  onLoad(api: NuclearPluginAPI) {},\n  onEnable(api: NuclearPluginAPI) {\n    // Register providers\n  },\n  onDisable() {\n    // Unregister providers\n  },\n  onUnload() {},\n};\n\nexport default plugin;\n```\n\n## Provider Types\n\n**Streaming** - resolve tracks to playable audio:\n\n```typescript\nconst provider: StreamingProvider = {\n  id: 'my-streaming',\n  kind: 'streaming',\n  name: 'My Streaming',\n  async searchForTrack(artist, title, album?) { /* return StreamCandidate[] */ },\n  async getStreamUrl(candidateId) { /* return Stream */ },\n};\n\napi.Providers.register(provider);\napi.Providers.unregister('my-streaming');\n```\n\n**Metadata** - search artists/albums, fetch details:\n\n```typescript\nconst provider: MetadataProvider = {\n  id: 'my-metadata',\n  kind: 'metadata',\n  name: 'My Metadata',\n  searchCapabilities: ['artists', 'albums'],\n  streamingProviderId: 'my-streaming', // Optional: locks streaming to this provider\n  async searchArtists(params) { /* ... */ },\n  async searchAlbums(params) { /* ... */ },\n  async fetchArtistBio(id) { /* ... */ },\n  async fetchAlbumDetails(id) { /* ... */ },\n};\n```\n\n## Available APIs\n\n- `api.Providers` - register/unregister providers\n- `api.Settings` - plugin settings storage\n- `api.Http` - fetch wrapper\n- `api.Ytdlp` - yt-dlp integration\n- `api.Queue` - playback queue control\n- `api.Metadata` - search music metadata\n- `api.Streaming` - resolve streams\n- `api.Logger` - logging (trace/debug/info/warn/error)\n\n## Publishing\n\n1. Create a GitHub repo with the plugin code\n2. Add `.github/workflows/release.yml`:\n\n```yaml\nname: Release\non:\n  push:\n    tags: ['v*']\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@v4\n      - run: zip -r plugin.zip src package.json README.md\n      - uses: softprops/action-gh-release@v2\n        with:\n          files: plugin.zip\n          generate_release_notes: true\n```\n\n3. Tag release: `git tag v0.1.0 && git push origin v0.1.0`\n4. Submit PR to `NuclearPlayer/plugin-registry` adding your plugin to `plugins.json`\n\n<!-- File: packages/docs/.gitbook.yaml (Tokens: ~300 | Category: anthropic-skill) -->\n# Docs Skill\n\nPath: `packages/docs/.gitbook.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/.gitbook.yaml)\n\n<!-- File: packages/docs/core-concepts/favorites.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/favorites.md)\n\n<!-- File: packages/docs/core-concepts/how-nuclear-works.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/how-nuclear-works.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/how-nuclear-works.md)\n\n<!-- File: packages/docs/core-concepts/listening-history.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-history.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-history.md)\n\n<!-- File: packages/docs/core-concepts/listening-stats.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/listening-stats.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/listening-stats.md)\n\n<!-- File: packages/docs/core-concepts/playlists.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/playlists.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/playlists.md)\n\n<!-- File: packages/docs/core-concepts/plugins-and-providers.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/plugins-and-providers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/plugins-and-providers.md)\n\n<!-- File: packages/docs/core-concepts/the-queue.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Core-concepts Skill\n\nPath: `packages/docs/core-concepts/the-queue.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/core-concepts/the-queue.md)\n\n<!-- File: packages/docs/development/contributing.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Development Skill\n\nPath: `packages/docs/development/contributing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/contributing.md)\n\n<!-- File: packages/docs/development/host-pattern.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Development Skill\n\nPath: `packages/docs/development/host-pattern.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/host-pattern.md)\n\n<!-- File: packages/docs/development/logging.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Development Skill\n\nPath: `packages/docs/development/logging.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/logging.md)\n\n<!-- File: packages/docs/development/mcp-architecture.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Development Skill\n\nPath: `packages/docs/development/mcp-architecture.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/development/mcp-architecture.md)\n\n<!-- File: packages/docs/integrations/http-api.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Integrations Skill\n\nPath: `packages/docs/integrations/http-api.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/http-api.md)\n\n<!-- File: packages/docs/integrations/mcp-server.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Integrations Skill\n\nPath: `packages/docs/integrations/mcp-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mcp-server.md)\n\n<!-- File: packages/docs/integrations/mpd-server.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Integrations Skill\n\nPath: `packages/docs/integrations/mpd-server.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/integrations/mpd-server.md)\n\n<!-- File: packages/docs/misc/discovery.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Misc Skill\n\nPath: `packages/docs/misc/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/discovery.md)\n\n<!-- File: packages/docs/misc/keyboard-shortcuts.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Misc Skill\n\nPath: `packages/docs/misc/keyboard-shortcuts.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/keyboard-shortcuts.md)\n\n<!-- File: packages/docs/misc/platform-specific.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Misc Skill\n\nPath: `packages/docs/misc/platform-specific.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/misc/platform-specific.md)\n\n<!-- File: packages/docs/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Docs Skill\n\nPath: `packages/docs/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/package.json)\n\n<!-- File: packages/docs/plugins/dashboard.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Plugins Skill\n\nPath: `packages/docs/plugins/dashboard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/dashboard.md)\n\n<!-- File: packages/docs/plugins/discovery.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Plugins Skill\n\nPath: `packages/docs/plugins/discovery.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/discovery.md)\n\n<!-- File: packages/docs/plugins/events.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Plugins Skill\n\nPath: `packages/docs/plugins/events.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/events.md)\n\n<!-- File: packages/docs/plugins/favorites.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Plugins Skill\n\nPath: `packages/docs/plugins/favorites.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/favorites.md)\n\n<!-- File: packages/docs/plugins/getting-started.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Plugins Skill\n\nPath: `packages/docs/plugins/getting-started.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/getting-started.md)\n\n<!-- File: packages/docs/plugins/http.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Plugins Skill\n\nPath: `packages/docs/plugins/http.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/nukeop/nuclear/HEAD/packages/docs/plugins/http.md)\n\n<!-- ... +65 more modular skills available via /api/skills/nukeop/nuclear?path={path} -->\n</yakaai_skills>"}