contextui

TotalClaw 作者 totalclaw

在 ContextUI(面向 AI 代理的本地优先桌面平台)上构建、运行和发布可视化工作流程。创建 React TSX 工作流程(仪表板、工具、应用程序、可视化)、管理本地 Python 后端服务器、通过 ContextUI 应用程序窗口中的范围 UI 自动化测试工作流程,并可选择发布到 ContextUI Exchange。所有工具都在标准操作系统权限下在用户计算机上本地运行 - 无需远程执行或权限升级。 Python 后端绑定到本地主机。有关完整的功能范围和信任模型,请参阅 SECURITY.md。需要本地安装 ContextUI 并配置 MCP 服务器。

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install totalclaw:totalclaw~midz99-contextui
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Atotalclaw~midz99-contextui/file -o midz99-contextui.md
## 概述(中文)

在 ContextUI(面向 AI 代理的本地优先桌面平台)上构建、运行和发布可视化工作流程。创建 React TSX 工作流程(仪表板、工具、应用程序、可视化)、管理本地 Python 后端服务器、通过 ContextUI 应用程序窗口中的范围 UI 自动化测试工作流程,并可选择发布到 ContextUI Exchange。所有工具都在标准操作系统权限下在用户计算机上本地运行 - 无需远程执行或权限升级。 Python 后端绑定到本地主机。有关完整的功能范围和信任模型,请参阅 SECURITY.md。需要本地安装 ContextUI 并配置 MCP 服务器。

## 原文

# ContextUI — Agent Workflow Platform

ContextUI is a local-first desktop platform where AI agents build, run, and sell visual workflows. Think of it as your workbench — you write React TSX, it renders instantly. No framework setup, no bundler config, no browser needed.

**What you can build:** Dashboards, data tools, chat interfaces, 3D visualizations, music generators, video editors, PDF processors, presentations, terminals — anything React can render.

**Why it matters:** You get a visual interface. You can build tools for yourself, for your human, or publish them to the Exchange for other agents to buy.

## Quick Start

### 1. Prerequisites

- ContextUI installed locally (download from [contextui.ai](https://contextui.ai))
- MCP server configured (connects your agent to ContextUI)

### 2. Connect via MCP

Configure your MCP client to connect to the ContextUI server:

```json
{
  "contextui": {
    "command": "node",
    "args": ["/path/to/contextui-mcp/server.cjs"],
    "transport": "stdio"
  }
}
```

The MCP server exposes 32 tools. See `references/mcp-tools.md` for the full API.

### 3. Verify Connection

```bash
mcporter call contextui.list_workflows
```

If you get back folder names (`examples`, `user_workflows`), you're connected.

## Building Workflows

Workflows are single React TSX files with optional metadata and Python backends.

### File Structure

```
WorkflowName/
├── WorkflowNameWindow.tsx     # Main React component (required)
├── WorkflowName.meta.json     # Icon, color metadata (required)
├── description.txt            # What it does (required for Exchange)
├── backend.py                 # Optional Python backend
└── components/                # Optional sub-components
    └── MyComponent.tsx
```

### Key Rules

1. **NO IMPORTS for globals** — React, hooks, and utilities are provided globally by ContextUI
2. **Tailwind CSS** — Use Tailwind classes for all styling. NO styled-components.
3. **Component declaration** — `export const MyToolWindow: React.FC = () => { ... }` or `const MyToolWindow: React.FC = () => { ... }` — both work
4. **Naming** — File should be `WorkflowNameWindow.tsx` (all shipped examples use this). Folder name is `WorkflowName/` (no "Window"). E.g. `CowsayDemo/CowsayDemoWindow.tsx`
5. **Python backends** — Use the ServerLauncher pattern (see `references/server-launcher.md`)
6. **No nested buttons** — React/HTML forbids `<button>` inside `<button>`. Use `<div onClick>` for outer clickable containers.
7. **Local imports only** — You CAN import from local `./ui/` sub-components. You CANNOT import from npm packages.

## ⚠️ CRITICAL: Imports & Globals

This is the #1 source of bugs. Get this wrong and the workflow won't open.

### What's Available as Globals (NO imports needed)

```tsx
// These are just available — don't import them
React
useState, useEffect, useRef, useCallback, useMemo, useReducer, useContext
```

### What You CAN Import

```tsx
// Local sub-components within your workflow folder — this is the ONLY kind of import allowed
import { MyComponent } from './ui/MyComponent';
import { useServerLauncher } from './ui/ServerLauncher/useServerLauncher';
import { ServerLauncher } from './ui/ServerLauncher/ServerLauncher';
import { MyTab } from './ui/MyTab';
```

### ❌ WRONG - Common Bugs That Break Workflows

```tsx
// ❌ NEVER - window.ContextUI is not reliably defined
const { React, Card, Button } = window.ContextUI;

// ❌ NEVER - no npm/node_modules imports
import React from 'react';
import styled from 'styled-components';
import axios from 'axios';

// ❌ NEVER - styled-components is NOT available
const Container = styled.div`...`;
```

### ✅ CORRECT Patterns

Both hook access styles work — pick one and be consistent:

```tsx
// Style 1: Bare globals (used by CowsayDemo, Localchat2, ImageToText)
const [count, setCount] = useState(0);
const ref = useRef<HTMLDivElement>(null);

// Style 2: React.* prefix (used by ThemedWorkflowTemplate, MultiColorWorkflowTemplate)
const [count, setCount] = React.useState(0);
const ref = React.useRef<HTMLDivElement>(null);
```

Full example:
```tsx
// Only import from LOCAL files in your workflow folder
import { useServerLauncher } from './ui/ServerLauncher/useServerLauncher';
import { ServerLauncher } from './ui/ServerLauncher/ServerLauncher';
import { MyFeatureTab } from './ui/MyFeatureTab';

// Globals are just available — use them directly
export const MyToolWindow: React.FC = () => {
  const [count, setCount] = useState(0);      // useState is global
  const ref = useRef<HTMLDivElement>(null);    // useRef is global
  
  useEffect(() => {
    // useEffect is global
  }, []);

  return (
    <div className="bg-slate-950 text-white p-4">
      {/* Tailwind classes for all styling */}
    </div>
  );
};
```

### Sub-Components

Sub-components in `./ui/` follow the same rules — globals are available, no npm imports:

```tsx
// ui/MyFeatureTab.tsx
// No imports needed for React/hooks — they're globals here too

interface MyFeatureTabProps {
  serverUrl: string;
  connected: boolean;
}

export const MyFeatureTab: React.FC<MyFeatureTabProps> = ({ serverUrl, connected }) => {
  const [data, setData] = useState<string[]>([]);
  
  // Fetch from Python backend
  const loadData = async () => {
    const res = await fetch(`${serverUrl}/data`);
    const json = await res.json();
    setData(json.items);
  };

  return (
    <div className="p-4">
      <button onClick={loadData} className="px-4 py-2 bg-blue-600 text-white rounded">
        Load Data
      </button>
    </div>
  );
};
```

### Minimal Complete Example (No Backend)

```tsx
// MyTool/MyTool.tsx — simplest possible workflow

export const MyToolWindow: React.FC = () => {
  const [count, setCount] = useState(0);

  return (
    <div className="min-h-full bg-slate-950 text-slate-100 p-6">
      <h1 className="text-2xl font-bold mb-4">My Tool</h1>
      <button
        onClick={() => setCount(c => c + 1)}
        className="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg"
      >
        Clicked {count} times
      </button>
    </div>
  );
};
```

### Minimal Complete Example (With Python Backend)

```tsx
// MyServer/MyServerWindow.tsx — simplest workflow with a Python backend

import { useServerLauncher } from './ui/ServerLauncher/useServerLauncher';
import { ServerLauncher } from './ui/ServerLauncher/ServerLauncher';

export const MyServerWindow: React.FC = () => {
  const server = useServerLauncher({
    workflowFolder: 'MyServer',
    scriptName: 'server.py',
    port: 8800,
    serverName: 'my-server',
    packages: ['fastapi', 'uvicorn[standard]'],
  });

  const [tab, setTab] = useState<'setup' | 'main'>('setup');

  useEffect(() => {
    if (server.connected) setTab('main');
  }, [server.connected]);

  return (
    <div className="flex flex-col h-full bg-slate-950 text-white">
      {/* Tab Bar */}
      <div className="flex border-b border-slate-700">
        <button onClick={() => setTab('setup')}
          className={`px-4 py-2 text-sm font-medium transition-colors ${
            tab === 'setup' ? 'text-cyan-400 border-b-2 border-cyan-400' : 'text-slate-400 hover:text-slate-300'
          }`}>Setup</button>
        <button onClick={() => setTab('main')}
          className={`px-4 py-2 text-sm font-medium transition-colors ${
            tab === 'main' ? 'text-cyan-400 border-b-2 border-cyan-400' : 'text-slate-400 hover:text-slate-300'
          }`}>Main</button>
        <div className="flex-1" />
        <div className={`px-4 py-2 text-xs ${server.connected ? 'text-green-400' : 'text-slate-500'}`}>
          {server.connected ? '● Connected' : '○ Disconnected'}
        </div>
      </div>

      {/* Con