---
created: 2026-03-12
source: Growth-Engine
tags: [agent-archive, growth-engine]
---

# Contributing to Control Centre

## Quick Start

```bash
cd control-centre
cp .env.example .env
# Edit .env with your Supabase credentials
npm install
npm run dev
```

Open http://localhost:5173

## Understanding the Architecture

### The Config System
`fleet.config.js` at the root defines EVERYTHING about the fleet:
- **agents[]** — each agent with id, name, role, model, port, avatar, color, service name
- **nav[]** — sidebar navigation (set `enabled: false` to hide a page)
- **branding** — name, product name, accent color
- **projects[]** — project definitions
- **status** — color mapping for healthy/warning/error/offline/busy

**To deploy for a different fleet:** Copy this file, change the values. Done.

### Data Flow
```
fleet.config.js  →  src/lib/fleet.js (helpers)  →  Components read via getAgents() etc.
.env             →  src/lib/supabase.js          →  Database queries
.env             →  src/lib/api.js               →  REST API calls to backend
```

### Design System
All styles use CSS variables from `src/index.css`, prefixed `--cc-`:
- `--cc-bg` — page background (#07080d)
- `--cc-surface` — card backgrounds (#12141d)
- `--cc-border` — borders (#1e2231)
- `--cc-text` — primary text (#e8eaf0)
- `--cc-text-secondary` — secondary text
- `--cc-text-muted` — muted/disabled text
- `--cc-accent` — accent/brand color (#3b82f6)
- `--cc-healthy/warning/error/offline` — status colors

**Never use hardcoded hex values in components.** Always use `var(--cc-*)` or the
corresponding Tailwind utilities.

### Component Patterns
```jsx
// Standard card
<Card>
  <CardHeader title="Title" icon={SomeIcon} />
  <div className="p-4">Content</div>
</Card>

// Agent status indicator
<StatusDot status="healthy" />

// Status badge
<Badge variant="healthy">Online</Badge>
<Badge variant="accent">claude-sonnet</Badge>

// Reading fleet config
import { getAgents, getAgent, getStatusDisplay } from '../lib/fleet';
const agents = getAgents();
const rivet = getAgent('rivet');
```

## Building Pages

### Page Template
Every page follows this pattern:
```jsx
/**
 * PageName
 * ========
 * Brief description of what this page does.
 * What data it needs and where it comes from.
 */
import Card, { CardHeader } from '../components/ui/Card';
import { SomeIcon } from 'lucide-react';

export default function PageName() {
  // TODO: Replace with useQuery + API calls
  const demoData = [...];

  return (
    <div className="max-w-7xl mx-auto space-y-6">
      {/* Your content */}
    </div>
  );
}
```

### Pages To Build (Priority Order)

#### 1. Tasks — `/tasks` (P0)
**What:** Kanban board with Backlog → In Progress → Review → Done
**Data:** `cc_tasks` table in Supabase
**API:** `tasksApi` in `src/lib/api.js` (already defined)
**Key features:**
- Drag-and-drop columns (use `@hello-pangea/dnd`)
- Create task → assign to agent(s) → set priority
- Approval workflow: agent proposes → Michael approves → agent executes
- Filter by agent, project, priority

#### 2. Council — `/council` (P0)
**What:** Multi-agent chat with topics
**Data:** `cc_council_messages` + `cc_council_topics` tables
**API:** `councilApi` in `src/lib/api.js` (already defined)
**Key features:**
- Topic list (left sidebar)
- Message thread (main area)
- Send as Michael → broadcasts to selected agents
- Pin important messages
- Supabase Realtime for live updates

#### 3. Agent Detail — `/fleet/[agentId]` (P1)
**What:** Deep dive into a single agent
**Data:** Agent config from fleet.config.js + live status from API
**Key features:**
- Config viewer (JSON with syntax highlighting)
- Recent sessions list
- Error log
- Memory file browser (reads from agent's memory/ directory)
- Start/stop/restart controls

#### 4. Memory — `/memory` (P1)
**What:** Browse and edit agent memory files
**Data:** File system on VPS (via memoryApi)
**API:** `memoryApi` in `src/lib/api.js` (already defined)
**Key features:**
- Tree view of each agent's memory directory
- Markdown editor for file content
- Search across all agent memories
- Health check (find stale/duplicate memories)

#### 5. Analytics — `/analytics` (P1)
**What:** Fleet performance + cost tracking
**Data:** `cc_agent_snapshots` + `cc_api_costs` tables
**Key features:**
- Tasks/day per agent (bar chart)
- API cost breakdown per agent per model
- Response time trends
- Use Recharts for visualizations

#### 6. Projects — `/projects` (P2)
**What:** Cross-agent project tracking
**Data:** `cc_projects` table
**Key features:**
- Project cards with status indicators
- Linked tasks (from cc_tasks)
- Assigned agents

#### 7. Messages — `/messages` (P2)
**What:** Unified inbox across channels
**Key features:**
- Telegram, SMS, Slack in one view
- Filter by agent, channel
- Reply inline

#### 8. Settings — `/settings` (P2)
**What:** System configuration
**Key features:**
- Agent config editor (JSON with validation)
- Notification preferences

## Backend API

The frontend is ready — API methods are defined in `src/lib/api.js`.
The backend needs these endpoints:

### Fleet Endpoints
```
GET  /api/fleet/status              → All agent statuses
GET  /api/fleet/:id/status          → Single agent status
POST /api/fleet/:id/start           → Start agent (systemctl start)
POST /api/fleet/:id/stop            → Stop agent (systemctl stop)
POST /api/fleet/:id/restart         → Restart agent (systemctl restart)
GET  /api/fleet/:id/logs?lines=100  → Agent logs (journalctl)
GET  /api/fleet/resources            → CPU/RAM/disk usage
```

### Tasks Endpoints (CRUD → Supabase cc_tasks table)
```
GET    /api/tasks                    → List tasks
GET    /api/tasks/:id                → Get task
POST   /api/tasks                    → Create task
PATCH  /api/tasks/:id                → Update task
DELETE /api/tasks/:id                → Delete task
```

### Council Endpoints (CRUD → Supabase)
```
GET  /api/council/topics             → List topics
POST /api/council/topics             → Create topic
GET  /api/council/topics/:id/messages → Get messages
POST /api/council/topics/:id/messages → Send message
POST /api/council/messages/:id/pin   → Toggle pin
```

### Memory Endpoints (File system access)
```
GET  /api/memory/:agentId            → List files
GET  /api/memory/:agentId/file?path= → Read file
PUT  /api/memory/:agentId/file       → Write file
GET  /api/memory/search?q=           → Search all
```

### Approvals Endpoints
```
GET  /api/approvals?status=pending   → Pending approvals
POST /api/approvals/:id/approve      → Approve
POST /api/approvals/:id/reject       → Reject
```

## Supabase Schema

Run these in Supabase SQL editor to create the tables:

```sql
-- Tasks board
CREATE TABLE cc_tasks (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'backlog' CHECK (status IN ('backlog', 'in_progress', 'review', 'done')),
  priority TEXT DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
  agent_id TEXT,
  project_id TEXT,
  created_by TEXT DEFAULT 'michael',
  approved_by TEXT,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now(),
  due_date TIMESTAMPTZ
);

-- Projects
CREATE TABLE cc_projects (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'active' CHECK (status IN ('active', 'on_hold', 'completed')),
  agents TEXT[] DEFAULT '{}',
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Council topics
CREATE TABLE cc_council_topics (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title TEXT NOT NULL,
  pinned BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now(),
  last_activity_at TIMESTAMPTZ DEFAULT now()
);

-- Council messages
CREATE TABLE cc_council_messages (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  topic_id UUID REFERENCES cc_council_topics(id) ON DELETE CASCADE,
  sender_type TEXT NOT NULL CHECK (sender_type IN ('michael', 'agent')),
  sender_id TEXT NOT NULL,
  content TEXT NOT NULL,
  mentions TEXT[] DEFAULT '{}',
  pinned BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Agent status snapshots
CREATE TABLE cc_agent_snapshots (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  agent_name TEXT NOT NULL,
  status TEXT NOT NULL,
  model TEXT,
  port INTEGER,
  uptime_seconds INTEGER,
  active_sessions INTEGER DEFAULT 0,
  error_count INTEGER DEFAULT 0,
  snapshot_at TIMESTAMPTZ DEFAULT now()
);

-- API cost tracking
CREATE TABLE cc_api_costs (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  agent_name TEXT NOT NULL,
  model TEXT NOT NULL,
  tokens_in INTEGER DEFAULT 0,
  tokens_out INTEGER DEFAULT 0,
  cost_usd DECIMAL(10,4) DEFAULT 0,
  recorded_at TIMESTAMPTZ DEFAULT now()
);

-- Approvals queue
CREATE TABLE cc_approvals (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  task_id UUID REFERENCES cc_tasks(id),
  agent_id TEXT NOT NULL,
  action_description TEXT NOT NULL,
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
  decided_by TEXT,
  decided_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Enable realtime on council messages (for live chat)
ALTER PUBLICATION supabase_realtime ADD TABLE cc_council_messages;
ALTER PUBLICATION supabase_realtime ADD TABLE cc_approvals;
```

## Deployment

### On VPS (recommended — same machine as agents)
```bash
cd /home/ccuser/control-centre
npm install
npm run build
pm2 start npm --name "control-centre" -- run preview -- --host 0.0.0.0 --port 3100
```

### Nginx reverse proxy
```nginx
server {
    server_name cc.yourdomain.com;

    location / {
        proxy_pass http://localhost:3100;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
```
