# Mission Control

> Build a personal dashboard for OpenClaw with task management, memory browser, calendar, team tracking, and GitHub trends. Use when the user wants to create a web-based mission control dashboard for their OpenClaw instance, track tasks, view memories, manage calendar events, or build a custom dashboard interface.

- Skill: `johnalbertini14-glitch/mission-control-2` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add johnalbertini14-glitch/mission-control-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/johnalbertini14-glitch/mission-control-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: johnalbertini14-glitch (https://skillmd.com/u/johnalbertini14-glitch)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/johnalbertini14-glitch/mission-control-2

---


# Mission Control Skill

Build your own personal dashboard for OpenClaw - a central command center for tasks, memories, calendar, and team management.

## What You'll Build

A Next.js web dashboard that connects to your OpenClaw instance and provides:

- **Task Board** - Kanban-style task management
- **Memory Browser** - Search and view your OpenClaw memories
- **Calendar View** - See scheduled events and cron jobs
- **Team Status** - Track who's working on what
- **GitHub Trends** - Discover trending repositories

## Architecture Overview

```
┌─────────────────────────────────────┐
│         Next.js Frontend            │
│  ┌─────────┐ ┌─────────┐ ┌────────┐ │
│  │  Task   │ │ Memory  │ │Calendar│ │
│  │  Board  │ │  List   │ │  View  │ │
│  └────┬────┘ └────┬────┘ └───┬────┘ │
│       └───────────┴──────────┘       │
│              │                       │
│       ┌──────┴──────┐                │
│       │   API Routes │               │
│       └──────┬──────┘                │
└──────────────┼───────────────────────┘
               │
       ┌───────┴───────┐
       │               │
┌──────┴──────┐ ┌──────┴──────┐
│ Local JSON  │ │  OpenClaw   │
│   Files     │ │   Memory    │
└─────────────┘ └─────────────┘
```

## Prerequisites

- Node.js 18+
- OpenClaw installed and running
- Basic knowledge of React/Next.js

## Step-by-Step Guide

### Step 1: Initialize Project

```bash
npx create-next-app@latest mission-control --typescript --tailwind
```

### Step 2: Install Dependencies

```bash
cd mission-control
npm install lucide-react
```

### Step 3: Create Data Layer

Create `src/lib/data.ts` for file-based storage:

```typescript
import fs from "fs/promises";
import path from "path";

const DATA_DIR = path.join(process.cwd(), "src", "data");

// Types
export interface Task {
  id: string;
  title: string;
  description: string;
  status: "todo" | "in-progress" | "done";
  assignee: string;
  createdAt: string;
  updatedAt: string;
}

// Initialize data files
async function initDataFile(filename: string, defaultData: unknown) {
  const filepath = path.join(DATA_DIR, filename);
  try {
    await fs.access(filepath);
  } catch {
    await fs.mkdir(DATA_DIR, { recursive: true });
    await fs.writeFile(filepath, JSON.stringify(defaultData, null, 2));
  }
  return filepath;
}

// Tasks
export async function getTasks(): Promise<Task[]> {
  const filepath = await initDataFile("tasks.json", []);
  const data = await fs.readFile(filepath, "utf-8");
  return JSON.parse(data);
}

export async function addTask(task: Omit<Task, "id" | "createdAt" | "updatedAt">): Promise<Task> {
  const tasks = await getTasks();
  const newTask: Task = {
    ...task,
    id: Date.now().toString(),
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
  };
  tasks.push(newTask);
  await fs.writeFile(
    path.join(DATA_DIR, "tasks.json"),
    JSON.stringify(tasks, null, 2)
  );
  return newTask;
}

export async function updateTask(id: string, updates: Partial<Task>): Promise<Task | null> {
  const tasks = await getTasks();
  const index = tasks.findIndex((t) => t.id === id);
  if (index === -1) return null;
  
  tasks[index] = { 
    ...tasks[index], 
    ...updates, 
    updatedAt: new Date().toISOString() 
  };
  await fs.writeFile(
    path.join(DATA_DIR, "tasks.json"),
    JSON.stringify(tasks, null, 2)
  );
  return tasks[index];
}
```

### Step 4: Create API Routes

Create `src/app/api/tasks/route.ts`:

```typescript
import { NextRequest, NextResponse } from "next/server";
import { getTasks, addTask, updateTask } from "@/lib/data";

export async function GET() {
  const tasks = await getTasks();
  return NextResponse.json(tasks);
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const task = await addTask(body);
    return NextResponse.json(task, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to create task" },
      { status: 500 }
    );
  }
}

export async function PUT(request: NextRequest) {
  try {
    const { id, ...updates } = await request.json();
    const task = await updateTask(id, updates);
    if (!task) {
      return NextResponse.json({ error: "Task not found" }, { status: 404 });
    }
    return NextResponse.json(task);
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to update task" },
      { status: 500 }
    );
  }
}
```

### Step 5: Create Components

**Navigation Component** (`src/components/Navigation.tsx`):

```typescript
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { LayoutDashboard, ClipboardList, Menu, X } from "lucide-react";

const navItems = [
  { href: "/", label: "Dashboard", icon: LayoutDashboard },
  { href: "/tasks", label: "Tasks", icon: ClipboardList },
];

export function Navigation() {
  const pathname = usePathname();
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  return (
    <>
      {/* Desktop Sidebar */}
      <aside className="hidden lg:flex w-64 bg-gray-800 border-r border-gray-700 flex-col h-screen">
        <div className="p-6 border-b border-gray-700">
          <h1 className="text-xl font-bold text-blue-400">Mission Control</h1>
        </div>
        <nav className="flex-1 p-4">
          <ul className="space-y-2">
            {navItems.map((item) => {
              const Icon = item.icon;
              const isActive = pathname === item.href;
              return (
                <li key={item.href}>
                  <Link
                    href={item.href}
                    className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${
                      isActive
                        ? "bg-blue-600 text-white"
                        : "text-gray-300 hover:bg-gray-700"
                    }`}
                  >
                    <Icon size={20} />
                    <span>{item.label}</span>
                  </Link>
                </li>
              );
            })}
          </ul>
        </nav>
      </aside>

      {/* Mobile Header */}
      <div className="lg:hidden fixed top-0 left-0 right-0 z-40 bg-gray-800 border-b border-gray-700">
        <div className="flex items-center justify-between px-4 py-3">
          <h1 className="text-lg font-bold text-blue-400">Mission Control</h1>
          <button
            onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
            className="p-2 rounded-lg bg-gray-700"
          >
            {mobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
          </button>
        </div>
      </div>

      {/* Mobile Drawer */}
      {mobileMenuOpen && (
        <aside className="lg:hidden fixed top-[60px] left-0 bottom-0 w-64 bg-gray-800 z-40">
          {/* Same nav as desktop */}
        </aside>
      )}
    </>
  );
}
```

### Step 6: Create Task Board

```typescript
"use client";

import { useState, useEffect } from "react";

interface Task {
  id: string;
  title: string;
  description: string;
  status: "todo" | "in-progress" | "done";
  assignee: string;
}

export function TaskBoard() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    try {
      const response = await fetch("/api/tasks");
      const data = await response.json();
      setTasks(data);
    } finally {
      setLoading(false);
    }
  };

  const handleUpdateStatus = async (taskId: string, newStatus: Task["status"]) => {
    await fetch("/api/tasks", {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ id: taskId, status: newStatus }),
    });
    fetchTasks();
  };

  const tasksByStatus = {
    todo: tasks.filter((t) => t.status === "todo"),
    "in-progress": tasks.filter((t) => t.status === "in-progress"),
    done: tasks.filter((t) => t.status === "done"),
  };

  if (loading) return <div>Loading...</div>;

  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
      {Object.entries(tasksByStatus).map(([status, statusTasks]) => (
        <div key={status} className="bg-gray-800/50 rounded-xl p-4">
          <h3 className="font-semibold text-gray-300 mb-4 capitalize">{status}</h3>
          <div className="space-y-3">
            {statusTasks.map((task) => (
              <div key={task.id} className="bg-gray-800 p-4 rounded-lg">
                <h4 className="font-medium">{task.title}</h4>
                <select
                  value={task.status}
                  onChange={(e) => handleUpdateStatus(task.id, e.target.value as Task["status"])}
                  className="mt-2 text-sm bg-gray-700 border border-gray-600 rounded px-2 py-1"
                >
                  <option value="todo">To Do</option>
                  <option value="in-progress">In Progress</option>
                  <option value="done">Done</option>
                </select>
              </div>
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}
```

### Step 7: OpenClaw Memory Sync

Create `src/app/api/sync/route.ts`:

```typescript
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";

export async function GET() {
  try {
    // Read OpenClaw memory files
    const memoryDir = path.join(process.env.HOME || "", "clawd", "memory");
    const files = await fs.readdir(memoryDir);
    
    const memories = [];
    for (const file of files.filter(f => f.endsWith('.md'))) {
      const content = await fs.readFile(path.join(memoryDir, file), 'utf-8');
      memories.push({
        id: file,
        title: file.replace('.md', ''),
        content: content.slice(0, 500) + '...',
        createdAt: new Date().toISOString(),
      });
    }
    
    return NextResponse.json(memories);
  } catch (error) {
    return NextResponse.json([]);
  }
}
```

### Step 8: Create Layout

```typescript
import type { Metadata } from "next";
import "./globals.css";
import { Navigation } from "@/components/Navigation";

export const metadata: Metadata = {
  title: "Mission Control",
  description: "Personal dashboard for OpenClaw",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="bg-gray-900 text-white">
        <div className="flex flex-col lg:flex-row min-h-screen">
          <Navigation />
          <main className="flex-1 overflow-auto pt-[60px] lg:pt-0 p-6">
            {children}
          </main>
        </div>
      </body>
    </html>
  );
}
```

### Step 9: Start Script

Create `start.sh`:

```bash
#!/bin/bash
cd "$(dirname "$0")"
npm run dev
```

Make it executable:

```bash
chmod +x start.sh
```

### Step 10: Run

```bash
./start.sh
```

Open http://localhost:3000

## Extending Mission Control

### Add GitHub Trends

```typescript
// src/app/api/github-trends/route.ts
export async function GET() {
  const response = await fetch(
    "https://api.github.com/search/repositories?q=stars:>1000&sort=stars&per_page=10"
  );
  const data = await response.json();
  return NextResponse.json(data.items);
}
```

### Add Calendar Events

Store events in `src/data/calendar.json` and create similar API routes.

### Add Team Members

Create `src/data/team.json` with member info and current tasks.

## Mobile Responsiveness Tips

1. **Use Tailwind breakpoints**: `lg:` for desktop, default for mobile
2. **Touch targets**: Minimum 40px for buttons
3. **Horizontal scroll**: For Kanban board on mobile
4. **Drawer navigation**: Slide-in menu for mobile

## Security Considerations

- Store personal data in `src/data/` (gitignored)
- Keep template data in the skill
- No authentication included - add your own if needed
- Run locally or behind a VPN

## Troubleshooting

**Port already in use?**
```bash
PORT=3001 ./start.sh
```

**Data not saving?**
Ensure `src/data/` directory exists and is writable.

**OpenClaw sync not working?**
Check that OpenClaw memory path is correct in your environment.

## Resources

- Next.js docs: https://nextjs.org/docs
- Tailwind CSS: https://tailwindcss.com
- Lucide icons: https://lucide.dev

## License

MIT - Built for the OpenClaw community 🦞

