Mission Control Builder
by @shukiclaw
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...
clawhub install mission-control-builderπ About This Skill
name: mission-control description: 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.
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:
Architecture Overview
βββββββββββββββββββββββββββββββββββββββ
β Next.js Frontend β
β βββββββββββ βββββββββββ ββββββββββ β
β β Task β β Memory β βCalendarβ β
β β Board β β List β β View β β
β ββββββ¬βββββ ββββββ¬βββββ βββββ¬βββββ β
β βββββββββββββ΄βββββββββββ β
β β β
β ββββββββ΄βββββββ β
β β API Routes β β
β ββββββββ¬βββββββ β
ββββββββββββββββΌββββββββββββββββββββββββ
β
βββββββββ΄ββββββββ
β β
ββββββββ΄βββββββ ββββββββ΄βββββββ
β Local JSON β β OpenClaw β
β Files β β Memory β
βββββββββββββββ βββββββββββββββ
Prerequisites
Step-by-Step Guide
Step 1: Initialize Project
npx create-next-app@latest mission-control --typescript --tailwind
Step 2: Install Dependencies
cd mission-control
npm install lucide-react
Step 3: Create Data Layer
Create src/lib/data.ts for file-based storage:
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 {
const filepath = await initDataFile("tasks.json", []);
const data = await fs.readFile(filepath, "utf-8");
return JSON.parse(data);
}
export async function addTask(task: Omit): Promise {
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): Promise {
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:
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):
"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 */}
{item.label} ); })} {/* Mobile Header */}
Mission Control
{/* Mobile Drawer */} {mobileMenuOpen && ( )} > ); }
Step 6: Create Task Board
"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([]);
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
Loading...; return (
{Object.entries(tasksByStatus).map(([status, statusTasks]) => (
{status}
{statusTasks.map((task) => (
{task.title}
))}
))}
);
}
Step 7: OpenClaw Memory Sync
Create src/app/api/sync/route.ts:
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
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 (
{children}
);
}
Step 9: Start Script
Create start.sh:
#!/bin/bash
cd "$(dirname "$0")"
npm run dev
Make it executable:
chmod +x start.sh
Step 10: Run
./start.sh
Open http://localhost:3000
Extending Mission Control
Add GitHub Trends
// 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
src/data/ (gitignored)Troubleshooting
Port already in use?
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
License
MIT - Built for the OpenClaw community π¦
βοΈ Configuration
π Tips & Best Practices
Port already in use?
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.