Token导航 LogoToken导航TokenDH.com
研究检索操作浏览器clawhub未标认证来源可访问clear审计提醒

mission-control-builder任务控制构建者

Agent Skill

mission-control-builder 用于处理浏览器自动化、网页检查和页面信息提取,适合在 OpenClaw 中需要让 Agent 打开页面、读取网页或验证前端流程时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

21,765

周安装

872

GitHub Stars

1

下载量

7,046
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:mission-control-builder(任务控制构建者)
来源仓库:https://github.com/shukiclaw/mission-control-builder
安装命令:
openclaw skills install mission-control-builder
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install mission-control-builder

简介

mission-control-builder 用于浏览器自动化、网页检查与信息提取,适合让 Agent 打开页面或读取内容。

  • 适用于需要验证前端流程、抓取页面数据或进行界面交互测试的场景。
  • 通过 clawhub 安装,使用 openclaw skills install mission-control-builder 命令部署。
  • 使用前需确认权限边界、系统依赖及是否触发网络访问或文件操作,避免越权行为。
  • 建议参考 GitHub 仓库与 README 文档,了解具体实现细节与限制条件。

SKILL.md

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:

  • 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

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<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:

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 */}
      <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

"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:

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 (
    <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:

#!/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

  • 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?

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 🦞

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

86.63%
按下载量换算6,104

安全审计

VirusTotal

可疑

ClawScan

可疑

Static analysis

未展示

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills