# Agent UI

Dynamic canvas interface for Claude Code interaction via MCP (Model Context Protocol).

---

## Nucleo

<p align="center">
  <img src="docs/nucleo-logo.svg" alt="Nucleo" width="120"/>
</p>

**Nucleo** is the main AI agent powering Agent UI. It serves as the bridge between Claude Code and your visual interface, providing real-time status feedback through an animated FAB (Floating Action Button).

### Visual States

Nucleo communicates its current state through distinct animations:

| State | Color | Animation | Trigger |
|-------|-------|-----------|---------|
| **Idle** | Purple | Rotating atom | Default state |
| **Processing** | Orange | Pulsing dots | User prompt submitted |
| **Reading** | Cyan | Eye icon + scan | Reading files (Read/Glob/Grep) |
| **Writing** | Green | Pencil icon + pulse | Writing files (Edit/Write) |
| **Subagent** | Purple | Orbital ring | Task tool spawned |
| **Permission** | Red | Alert + shake | Awaiting user permission |
| **Session Start** | Green | Ripple waves | Session initialized |
| **Notification** | Yellow | Bounce | System notification |

### Integration

Nucleo's status is synchronized via Claude Code hooks:

```json
{
  "hooks": {
    "UserPromptSubmit": [{ "hooks": [{ "command": "... status: processing ..." }] }],
    "PreToolUse": [{ "hooks": [{ "command": "... status: toolUse ..." }] }],
    "PostToolUse": [{ "hooks": [{ "command": "... status: toolDone ..." }] }],
    "Stop": [{ "hooks": [{ "command": "... status: idle ..." }] }]
  }
}
```

The FAB receives these status updates via WebSocket and displays the corresponding animation, giving you real-time visibility into what Claude is doing.

---

## Overview

Agent UI provides a visual canvas where Claude Code can render dynamic Vue 3 components, HTML content, and interactive UIs in real-time. It bridges the gap between CLI-based AI assistance and rich visual interfaces.

## Architecture

```
agent-ui/
├── frontend/          # Vue 3 + Vite + Pinia
│   └── src/
│       ├── components/    # Canvas, Toolbar, StatusBar
│       ├── services/      # Dynamic components system
│       ├── stores/        # Pinia state management
│       └── styles/        # Global CSS
├── server/            # Bun HTTP API + SQLite
└── .mcp.json          # MCP server configuration
```

## Quick Start

```bash
# Install dependencies
npm install

# Start development (frontend + server)
npm start
```

- **Frontend**: http://localhost:4100
- **API Server**: http://localhost:4101
- **WebMCP**: ws://localhost:4102

## Connecting to Claude Code

1. Start the development server: `npm start`
2. In Claude Code, run `/mcp` to reconnect
3. Click the widget (bottom-right) in the browser
4. Paste the token from Claude Code

---

# Dynamic Canvas

The dynamic canvas system allows Claude Code to render fully-featured Vue 3 components at runtime.

## MCP Tools

### `render_vue_component`

Renders a Vue 3 component directly in the canvas.

```javascript
{
  id: "my-counter",           // Unique component ID
  name: "MyCounter",          // Component name
  template: `<div>...</div>`, // Vue template
  setup: `...`,               // Composition API setup code
  style: `.class { ... }`,    // Scoped CSS
  props: ["title"],           // Props list
  imports: ["ref", "computed"], // Vue imports needed
  componentProps: { title: "Hello" }, // Props values
  mode: "replace" | "append"  // Render mode
}
```

### `save_vue_component`

Saves a component to SQLite for later reuse.

```javascript
{
  id: "my-component",     // Optional, auto-generated if empty
  name: "MyComponent",
  template: `...`,
  setup: `...`,
  style: `...`,
  props: [...],
  imports: [...]
}
```

### `load_vue_component`

Loads and renders a previously saved component.

```javascript
{
  id: "my-component",
  componentProps: { ... },
  mode: "replace" | "append"
}
```

### `list_vue_components`

Lists all saved components in the database.

### `delete_vue_component`

Deletes a component from the database.

```javascript
{
  id: "my-component"
}
```

### `render_html`

Renders raw HTML with script/style support (legacy, simpler option).

```javascript
{
  html: "<div>...</div>",
  mode: "replace" | "append" | "prepend"
}
```

---

## Setup Function API

Inside the `setup` string, components have access to:

### Vue Reactivity (via `imports`)

```javascript
ref, reactive, computed, watch, watchEffect,
onMounted, onUnmounted, nextTick, provide, inject, h
```

### Props & Context

```javascript
props    // Component props
ctx      // { emit, attrs, slots, expose }
```

### Event Bus

```javascript
$emit(event, ...args)     // Emit global event
$on(event, callback)      // Listen (returns unsubscribe fn)
$once(event, callback)    // Listen once
$off(event, callback)     // Stop listening
```

### Utilities

```javascript
$fetch(url, options)      // Native fetch
$nextTick(callback)       // Vue nextTick
useCanvasStore()          // Pinia store (shared with main app)
```

### Components API

```javascript
$components.load(id)      // Load component from DB
$components.list()        // List all components
$components.save(comp)    // Save component to DB
```

---

## Example: Interactive Counter

```javascript
// MCP call: render_vue_component
{
  id: "counter",
  name: "Counter",
  template: `
    <div class="counter">
      <h2>{{ title }}</h2>
      <p>Count: {{ count }}</p>
      <button @click="decrement">-</button>
      <button @click="increment">+</button>
      <p>Double: {{ doubled }}</p>
    </div>
  `,
  setup: `
    const count = ref(0);
    const doubled = computed(() => count.value * 2);

    const increment = () => count.value++;
    const decrement = () => count.value--;

    onMounted(() => {
      console.log('Counter mounted!');
    });

    return { count, doubled, increment, decrement, title: props.title };
  `,
  style: `
    .counter { padding: 20px; background: #1e1e28; border-radius: 8px; }
    .counter button { margin: 0 5px; padding: 8px 16px; }
  `,
  props: ["title"],
  imports: ["ref", "computed", "onMounted"],
  componentProps: { title: "My Counter" }
}
```

---

## Example: Async Data Fetching

```javascript
{
  id: "data-loader",
  name: "DataLoader",
  template: `
    <div class="loader">
      <p v-if="loading">Loading...</p>
      <pre v-else>{{ data }}</pre>
    </div>
  `,
  setup: `
    const loading = ref(true);
    const data = ref(null);

    // Async setup - uses Suspense internally
    const res = await $fetch('http://localhost:4101/api/health');
    data.value = await res.json();
    loading.value = false;

    return { loading, data };
  `,
  imports: ["ref"]
}
```

---

## Example: Event Communication

```javascript
// Component A - Emitter
{
  id: "emitter",
  name: "Emitter",
  template: `<button @click="send">Send Event</button>`,
  setup: `
    const send = () => {
      $emit('my-event', { message: 'Hello!', timestamp: Date.now() });
    };
    return { send };
  `
}

// Component B - Listener
{
  id: "listener",
  name: "Listener",
  template: `<div>Last event: {{ lastEvent }}</div>`,
  setup: `
    const lastEvent = ref('none');

    $on('my-event', (data) => {
      lastEvent.value = JSON.stringify(data);
    });

    return { lastEvent };
  `,
  imports: ["ref"]
}
```

---

## Features

### CSS Scoping

Styles are automatically scoped to prevent collisions:

```css
/* You write: */
.btn { background: red; }

/* Generated: */
#canvas-content [data-v-abc123] .btn { background: red; }
```

### Async Setup

Components can use `await` directly in setup - automatically wrapped in Suspense:

```javascript
setup: `
  const data = await $fetch('/api/data');
  return { data };
`
```

### Shared State

Components share Pinia stores with the main app:

```javascript
setup: `
  const store = useCanvasStore();
  console.log(store.isConnected);  // Same state as main app
`
```

### No App Overhead

Components are rendered using `render()` + `createVNode()` instead of creating separate Vue apps, sharing the main app's context efficiently.

---

## Technical Notes

### Vite Configuration

Runtime template compilation requires this alias in `vite.config.ts`:

```typescript
resolve: {
  alias: {
    'vue': 'vue/dist/vue.esm-bundler.js'
  }
}
```

### How It Works

1. **MCP Tool Call** → Claude Code sends component definition via WebSocket
2. **Build Component** → `buildComponent()` creates Vue component with dynamic `setup` function using `new Function()`
3. **CSS Scoping** → Styles are transformed: `.btn` → `#canvas-content [data-v-xxx] .btn`
4. **Render** → `createVNode()` + `render()` mounts component without creating new Vue app
5. **Context Inheritance** → Component inherits `appContext` from main app (Pinia, directives, etc.)

### File Structure

```
frontend/src/services/dynamicComponents.ts  (~300 lines)
├── EventBus         # Inter-component communication
├── CSS Scoping      # Style isolation
├── Components API   # CRUD with server
├── Vue Helpers      # What setup() receives
├── buildComponent   # Create component from definition
└── renderInlineComponent  # Mount to DOM
```

---

## API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/health` | GET | Health check |
| `/api/history` | GET | Get tool execution history |
| `/api/history` | POST | Log tool execution |
| `/api/history` | DELETE | Clear history |
| `/api/components` | GET | List saved components |
| `/api/components` | POST | Save component |
| `/api/components/:id` | GET | Get component by ID |
| `/api/components/:id` | DELETE | Delete component |
| `/api/config` | GET/POST | Key-value config storage |

---

---

# Theme System

Agent UI includes a powerful theming engine with visual editor, presets, and design tokens for consistent styling across dynamic components.

## Overview

The theme system provides:

- **Visual Editor**: Edit CSS variables with live preview
- **Presets**: System themes (Dark/Light) + custom user themes
- **Persistence**: Themes saved to SQLite database
- **Export/Import**: Share themes as JSON files
- **Design Tokens**: Structured guide for LLM agents to follow

## Accessing the Theme Editor

Navigate to `/themes` in the UI or click the palette icon in the sidebar.

## Theme Structure

Themes are organized by categories:

```javascript
{
  "colors": {
    "bg-primary": "#0f0f14",
    "bg-secondary": "#16161d",
    "bg-hover": "#1e1e28",
    "bg-tertiary": "#252530",
    "border-color": "#2a2a3a"
  },
  "text": {
    "text-primary": "#e4e4e7",
    "text-secondary": "#a1a1aa",
    "text-muted": "#52525b"
  },
  "accent": {
    "accent": "#6366f1",
    "accent-hover": "#818cf8",
    "accent-muted": "rgba(99, 102, 241, 0.2)",
    "accent-text": "#ffffff"
  },
  "semantic": {
    "success": "#22c55e",
    "success-bg": "rgba(34, 197, 94, 0.15)",
    "warning": "#eab308",
    "warning-bg": "rgba(234, 179, 8, 0.15)",
    "error": "#ef4444",
    "error-bg": "rgba(239, 68, 68, 0.15)",
    "info": "#3b82f6",
    "info-bg": "rgba(59, 130, 246, 0.15)"
  },
  "spacing": {
    "radius-sm": "4px",
    "radius-md": "8px",
    "radius-lg": "12px",
    "radius-full": "9999px"
  },
  "typography": {
    "font-sans": "Inter, system-ui, sans-serif",
    "font-mono": "JetBrains Mono, monospace"
  },
  "effects": {
    "shadow-sm": "0 1px 2px rgba(0,0,0,0.3)",
    "shadow-md": "0 4px 12px rgba(0,0,0,0.4)",
    "transition-fast": "0.15s ease"
  }
}
```

## MCP Tools

### `get_design_tokens`

Returns design tokens for the active theme. Use this to create components with consistent styling.

```javascript
{
  category: "all" | "colors" | "text" | "accent" | "semantic" | "spacing" | "typography" | "effects"
}
```

**Response example:**

```
Design Tokens del tema "Dark":

[COLORS]
  --bg-primary: #0f0f14
  --bg-secondary: #16161d
  ...

GUÍA DE USO:
- Usa var(--nombre-variable) en CSS
- Los componentes dinámicos tienen acceso a $theme.getVariable('nombre')
- Colores semánticos: success, warning, error, info (con -bg para fondos)
- Radius: radius-sm (4px), radius-md (8px), radius-lg (12px), radius-full (9999px)
```

## Theme API in Dynamic Components

Components have access to `$theme` helper:

```javascript
// Get CSS variable value
$theme.getVariable('accent')  // "#6366f1"

// Set variable temporarily (runtime only)
$theme.setVariable('accent', '#ff0000')

// Get all design tokens
$theme.getTokens()

// Get active theme object
$theme.getActiveTheme()

// Get current variables
$theme.getVariables()
```

### Example: Theme-Aware Component

```javascript
{
  id: "themed-card",
  name: "ThemedCard",
  template: `
    <div class="card">
      <h3>{{ title }}</h3>
      <p>Current accent: {{ accentColor }}</p>
      <button @click="randomizeAccent">Randomize Accent</button>
    </div>
  `,
  setup: `
    const accentColor = ref($theme.getVariable('accent'));

    const randomizeAccent = () => {
      const hue = Math.floor(Math.random() * 360);
      const newColor = \`hsl(\${hue}, 70%, 60%)\`;
      $theme.setVariable('accent', newColor);
      accentColor.value = newColor;
    };

    return { title: props.title, accentColor, randomizeAccent };
  `,
  style: `
    .card {
      padding: var(--radius-lg);
      background: var(--bg-secondary);
      border: 1px solid var(--border-color);
      border-radius: var(--radius-md);
    }
    .card h3 { color: var(--text-primary); }
    .card p { color: var(--text-secondary); }
    .card button {
      background: var(--accent);
      color: var(--accent-text);
      border: none;
      padding: 0.5rem 1rem;
      border-radius: var(--radius-sm);
      cursor: pointer;
    }
    .card button:hover { background: var(--accent-hover); }
  `,
  props: ["title"],
  imports: ["ref"],
  componentProps: { title: "Themed Card" }
}
```

## API Endpoints (Themes)

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/themes` | GET | List all themes |
| `/api/themes` | POST | Create/update theme |
| `/api/themes/active` | GET | Get active (default) theme |
| `/api/themes/:id` | GET | Get theme by ID |
| `/api/themes/:id` | DELETE | Delete theme |
| `/api/themes/:id/default` | POST | Set as default theme |
| `/api/themes/export/:id` | GET | Export theme as JSON |
| `/api/design-tokens` | GET | Get design tokens guide |

## Theme Editor Features

### Desktop View

- **Sidebar**: List of system and custom themes
- **Editor**: Category tabs (Colors, Text, Accent, etc.)
- **Variables Grid**: Color pickers and input fields
- **Live Preview**: Buttons, cards, badges, inputs

### Mobile View

- **Compact Header**: Theme dropdown + action buttons
- **Collapsible Variables**: Toggle to show/hide editors
- **Responsive Preview**: Adapts to screen size

### Actions

- **Save**: Saves current changes (creates copy if editing system theme)
- **Reset**: Reverts unsaved changes
- **Export**: Downloads theme as JSON file
- **Clone**: Creates a copy of any theme
- **Set Default**: Makes theme load on startup

## File Structure

```
frontend/src/
├── stores/theme.ts           # Pinia store
├── services/themeService.ts  # API client + utilities
├── pages/ThemesPage.vue      # Main editor page
└── components/themes/
    ├── ColorPicker.vue       # HSL color picker
    ├── VariableEditor.vue    # Variable input (color/size/text)
    ├── ThemePreview.vue      # Live preview component
    └── ThemeListItem.vue     # Theme list item with actions
```

---

## Tech Stack

- **Frontend**: Vue 3, Vite, Pinia, TypeScript
- **Server**: Bun, SQLite
- **MCP**: @nucleoriofrio/webmcp
- **PWA**: vite-plugin-pwa

---

## License

MIT
