# ClawX 项目架构与版本大纲 > 基于 OpenClaw 的图形化 AI 助手应用 > 技术栈:Electron + React + TypeScript > 代码规范:全部英文注释、 > 开发规范:每一个模块开发完成后,写好完整单测,在{project}/build_process/目录中,更新proecess.md, 增加当前feature.md文档(保持commit_X_feat.md格式),提交commit。 > 图形支持语言:与openClaw保持一致 > 如果有疑问请重新参考当前文档 --- ## 一、项目概述 ### 1.1 项目定位 **ClawX** 是 OpenClaw 的图形化封装层,旨在提供: - 🎯 **零命令行体验** - 通过 GUI 完成所有安装、配置和使用 - 🎨 **现代化 UI** - 美观、直观的桌面应用界面 - 📦 **开箱即用** - 预装精选技能包,即刻可用 - 🖥️ **跨平台** - macOS / Windows / Linux 统一体验 - 🔄 **无缝集成** - 与 OpenClaw 生态完全兼容 ### 1.2 目标用户 | 用户群体 | 痛点 | ClawX 解决方案 | |----------|------|----------------| | 非技术用户 | 命令行恐惧 | 可视化安装向导 | | 效率追求者 | 配置繁琐 | 一键预设技能包 | | 跨平台用户 | 体验不一致 | 统一 UI 设计语言 | | AI 尝鲜者 | 门槛高 | 引导式 onboarding | ### 1.3 与 OpenClaw 的关系 ``` ┌─────────────────────────────────────────────────────────┐ │ ClawX App │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Electron Main Process │ │ │ │ - 窗口管理、系统托盘、自动更新 │ │ │ │ - Gateway 进程管理 │ │ │ │ - Node.js 环境检测/安装 │ │ │ └─────────────────────────────────────────────────┘ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ React Renderer Process │ │ │ │ - 现代化 UI 界面 │ │ │ │ - WebSocket 通信层 │ │ │ └─────────────────────────────────────────────────┘ │ └────────────────────────┬────────────────────────────────┘ │ WebSocket (JSON-RPC) ▼ ┌─────────────────────────────────────────────────────────┐ │ OpenClaw Gateway (上游) │ │ - 消息通道管理 (WhatsApp/Telegram/Discord...) │ │ - AI Agent 运行时 │ │ - 技能/插件系统 │ └─────────────────────────────────────────────────────────┘ ``` **核心原则**: - ✅ **封装而非 Fork** - 通过 npm 依赖引入 openclaw - ✅ **不修改上游** - 所有定制通过配置、插件实现 - ✅ **版本绑定** - 每个 ClawX 版本绑定特定 openclaw 版本 - ✅ **CLI 兼容** - 命令行保持 `openclaw` 命令,不引入 `clawx` CLI openclaw project Local: /Users/guoyuliang/Project/openclaw remote: https://github.com/openclaw/openclaw ### 1.4 CLI 兼容性设计 ClawX 是 OpenClaw 的**图形化增强层**,而非替代品。用户可以同时使用 GUI 和 CLI: ``` ┌─────────────────────────────────────────────────────────────────┐ │ ClawX + OpenClaw 共存模式 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 用户交互方式 │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ ClawX GUI │ │ openclaw CLI │ │ │ │ (图形界面) │ │ (命令行) │ │ │ │ │ │ │ │ │ │ • 点击操作 │ │ • openclaw doctor │ │ │ │ • 可视化配置 │ │ • openclaw plugins │ │ │ │ • 安装向导 │ │ • openclaw config │ │ │ │ • 普通用户首选 │ │ • 高级用户/脚本 │ │ │ └──────────┬──────────┘ └──────────┬──────────┘ │ │ │ │ │ │ └────────────┬─────────────┘ │ │ ▼ │ │ ┌─────────────────────────┐ │ │ │ OpenClaw Gateway │ │ │ │ (共享同一实例) │ │ │ └─────────────────────────┘ │ │ │ │ │ ┌────────────┴────────────┐ │ │ ▼ ▼ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ ~/.openclaw/ │ │ OpenClaw 配置/数据 │ │ │ │ (共享配置目录) │ │ (技能/插件/会话) │ │ │ └─────────────────────┘ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` #### CLI 兼容性原则 | 原则 | 说明 | |------|------| | **命令一致** | 使用 `openclaw` 命令,不引入 `clawx` CLI | | **配置共享** | GUI 和 CLI 共享 `~/.openclaw/` 配置目录 | | **Gateway 共享** | GUI 和 CLI 连接同一个 Gateway 实例 | | **功能互补** | GUI 简化常用操作,CLI 支持高级/自动化场景 | #### 用户使用场景 **场景 A: 纯 GUI 用户 (新手)** ``` 1. 安装 ClawX.app 2. 通过安装向导完成配置 3. 日常使用 GUI 界面 4. 无需接触命令行 ``` **场景 B: GUI + CLI 混合用户 (进阶)** ``` 1. 安装 ClawX.app (自动包含 openclaw CLI) 2. 日常使用 GUI 界面 3. 需要时打开终端使用 CLI: - openclaw doctor # 健康检查 - openclaw plugins list # 查看插件 - openclaw config get # 查看配置 ``` **场景 C: CLI 优先用户 (开发者)** ``` 1. 安装 ClawX.app 或单独安装 openclaw CLI 2. 主要使用命令行操作 3. 偶尔打开 GUI 查看状态或配置复杂选项 ``` #### ClawX 安装时的 CLI 处理 ```typescript // electron/installer/cli-setup.ts /** * ClawX 安装时确保 openclaw CLI 可用 * 不创建 clawx 命令,保持与上游一致 */ export async function ensureOpenClawCli(): Promise { const isInstalled = await checkCliInstalled('openclaw'); if (!isInstalled) { // 通过 npm 全局安装 openclaw await privilegeManager.execAsAdmin( 'npm install -g openclaw', { reason: '安装 OpenClaw 命令行工具' } ); } // 确保 PATH 包含 npm 全局目录 await pathManager.ensureNpmGlobalPath(); // 验证安装 const version = await exec('openclaw --version'); console.log(`OpenClaw CLI installed: ${version}`); } /** * ClawX 不创建自己的 CLI 命令 * 所有命令行操作都通过 openclaw 完成 */ // ❌ 不会有 clawx CLI // ✅ 只有 openclaw CLI ``` #### GUI 与 CLI 的功能映射 | 操作 | ClawX GUI | openclaw CLI | |------|-----------|--------------| | 健康检查 | 设置 → 诊断 → 运行检查 | `openclaw doctor` | | 安装插件 | 技能市场 → 安装 | `openclaw plugins install ` | | 查看配置 | 设置 → 高级 | `openclaw config get` | | 修改配置 | 设置页面表单 | `openclaw config set ` | | 查看状态 | Dashboard | `openclaw status` | | 查看日志 | 设置 → 日志 | `openclaw logs` | | 连接通道 | 通道 → 添加 → 扫码 | `openclaw channels add whatsapp` | | 发送消息 | 对话界面 | `openclaw message send ` | #### 开发者模式: 终端集成 ```typescript // src/pages/Settings/DeveloperSettings.tsx export function DeveloperSettings() { const openTerminal = async () => { // 在内置终端或系统终端中打开,预设好环境 await window.electron.ipcRenderer.invoke('terminal:open', { cwd: '~/.openclaw', env: { // 确保 openclaw 命令可用 PATH: `${process.env.PATH}:${npmGlobalBinPath}`, }, }); }; return ( 开发者工具 {/* 快捷命令按钮 */}
{/* 打开终端 */}

ClawX 与 OpenClaw CLI 完全兼容,您可以在终端中使用 openclaw 命令进行高级操作。

); } ``` #### 配置同步机制 ```typescript // electron/config/sync.ts /** * ClawX 和 openclaw CLI 共享同一配置文件 * 任何一方的修改都会实时反映到另一方 */ export class ConfigSync { private configPath = join(homedir(), '.openclaw', 'config.json'); private watcher: FSWatcher | null = null; /** * 监听配置文件变化 (CLI 修改时同步到 GUI) */ startWatching(): void { this.watcher = watch(this.configPath, async () => { const config = await this.readConfig(); // 通知渲染进程配置已更新 mainWindow?.webContents.send('config:updated', config); }); } /** * GUI 修改配置时,写入共享配置文件 (CLI 可读取) */ async updateConfig(updates: Partial): Promise { const current = await this.readConfig(); const merged = deepMerge(current, updates); await writeFile(this.configPath, JSON.stringify(merged, null, 2)); } /** * 配置文件位置说明 * * ~/.openclaw/ * ├── config.json # 主配置 (GUI 和 CLI 共享) * ├── credentials/ # 凭证存储 * ├── sessions/ # 会话数据 * └── skills/ # 用户技能 * * ~/.clawx/ * ├── presets.json # ClawX 专属配置 (技能包选择等) * └── ui-state.json # GUI 状态 (窗口位置等) */ } ``` --- ## 二、技术架构 ### 2.1 技术栈选型 | 层级 | 技术 | 版本 | 选型理由 | |------|------|------|----------| | **运行时** | Electron | 33+ | 跨平台、嵌入 Node.js | | **前端框架** | React | 19 | 生态丰富、hooks 模式 | | **UI 组件** | shadcn/ui | latest | 可定制、现代设计 | | **样式** | Tailwind CSS | 4.x | 原子化、快速开发 | | **状态管理** | Zustand | 5.x | 轻量、TypeScript 友好 | | **路由** | React Router | 7.x | 声明式、嵌套路由 | | **构建工具** | Vite | 6.x | 极速 HMR | | **打包工具** | electron-builder | latest | 多平台打包、自动更新 | | **测试** | Vitest + Playwright | latest | 单元测试 + E2E | | **语言** | TypeScript | 5.x | 类型安全 | ### 2.2 双端口架构与开发者模式 ClawX 采用**双端口分层架构**,区分普通用户界面与开发者管理后台: ``` ┌─────────────────────────────────────────────────────────────────┐ │ 用户访问入口 │ ├─────────────────────────────┬───────────────────────────────────┤ │ │ │ │ ┌─────────────────────┐ │ ┌─────────────────────────┐ │ │ │ ClawX GUI │ │ │ OpenClaw Control UI │ │ │ │ Port: 23333 │ │ │ Port: 18789 │ │ │ │ │ │ │ │ │ │ │ 🎨 现代化界面 │ │ │ ⚙️ 原生管理后台 │ │ │ │ 📦 预装技能包 │──▶│ │ 🔧 高级配置 │ │ │ │ 🚀 一键安装向导 │ │ │ 📊 调试日志 │ │ │ │ 👤 普通用户 │ │ │ 👨‍💻 开发者/高级用户 │ │ │ └─────────────────────┘ │ └─────────────────────────┘ │ │ │ │ │ [开发者模式] ────────┼──────────────▶ │ │ │ │ └─────────────────────────────┴───────────────────────────────────┘ │ │ WebSocket (JSON-RPC) ▼ ┌────────────────────────┐ │ OpenClaw Gateway │ │ 内部服务端口: 18789 │ └────────────────────────┘ ``` #### 端口分配 | 端口 | 服务 | 用途 | 目标用户 | |------|------|------|----------| | **23333** | ClawX GUI | 默认图形化界面 | 所有用户 | | **18789** | OpenClaw Control UI | Gateway 管理后台 | 开发者/高级用户 | > **端口选择说明**: > - `23333` - ClawX 专属端口,易记且不与常见服务冲突 > - `18789` - OpenClaw 原生端口,保持上游兼容 #### 开发者模式入口 ```typescript // src/components/layout/Sidebar.tsx export function Sidebar() { const [devModeClicks, setDevModeClicks] = useState(0); // 连续点击 5 次版本号解锁开发者模式 const handleVersionClick = () => { const clicks = devModeClicks + 1; setDevModeClicks(clicks); if (clicks >= 5) { toast.success('开发者模式已解锁'); setDevModeClicks(0); } }; return ( ); } ``` #### 设置页面快捷入口 ```typescript // src/pages/Settings/AdvancedSettings.tsx export function AdvancedSettings() { return ( 高级设置 {/* 其他设置项 */} {/* 开发者工具区域 */}

访问 OpenClaw 原生管理后台,进行高级配置和调试

将在浏览器中打开 http://localhost:18789

); } ``` #### 配置文件 ```typescript // electron/utils/config.ts export const PORTS = { /** ClawX 默认 GUI 端口 */ CLAWX_GUI: 23333, /** OpenClaw Gateway 端口 (上游默认) */ OPENCLAW_GATEWAY: 18789, } as const; // 环境变量覆盖 export function getPort(key: keyof typeof PORTS): number { const envKey = `CLAWX_PORT_${key}`; const envValue = process.env[envKey]; return envValue ? parseInt(envValue, 10) : PORTS[key]; } ``` #### 使用场景对比 | 场景 | ClawX GUI (23333) | OpenClaw 后台 (18789) | |------|-------------------|----------------------| | 日常对话 | ✅ | ❌ | | 查看消息记录 | ✅ | ✅ | | 添加/管理通道 | ✅ (简化版) | ✅ (完整版) | | 安装技能包 | ✅ | ❌ | | 编辑技能配置 | ❌ | ✅ | | 查看原始日志 | ❌ | ✅ | | 插件管理 | ❌ | ✅ | | JSON 配置编辑 | ❌ | ✅ | | WebSocket 调试 | ❌ | ✅ | | Cron 任务管理 | ✅ | ✅ | ### 2.3 目录结构 ``` clawx/ ├── .github/ │ ├── workflows/ │ │ ├── ci.yml # 持续集成 │ │ ├── release.yml # 自动发布 │ │ └── test.yml # 测试流水线 │ └── ISSUE_TEMPLATE/ │ ├── electron/ # Electron 主进程 │ ├── main/ │ │ ├── index.ts # 主入口 │ │ ├── window.ts # 窗口管理 │ │ ├── tray.ts # 系统托盘 │ │ ├── menu.ts # 原生菜单 │ │ └── ipc-handlers.ts # IPC 处理器 │ │ │ ├── updater/ # 自动更新模块 │ │ ├── index.ts # 更新管理器入口 │ │ ├── checker.ts # 版本检查器 │ │ ├── downloader.ts # 下载管理 │ │ ├── notifier.ts # 更新通知 │ │ └── channels.ts # 更新通道配置 │ │ │ ├── gateway/ │ │ ├── manager.ts # Gateway 进程生命周期 │ │ ├── client.ts # WebSocket 客户端 │ │ ├── protocol.ts # JSON-RPC 协议定义 │ │ └── health.ts # 健康检查 │ │ │ ├── installer/ │ │ ├── node-installer.ts # Node.js 自动安装 │ │ ├── openclaw-installer.ts # openclaw npm 安装 │ │ ├── skill-installer.ts # 技能包安装 │ │ └── platform/ │ │ ├── darwin.ts # macOS 特定逻辑 │ │ ├── win32.ts # Windows 特定逻辑 │ │ └── linux.ts # Linux 特定逻辑 │ │ │ ├── privilege/ # 权限提升模块 │ │ ├── index.ts # 统一权限管理器 │ │ ├── darwin-admin.ts # macOS 管理员权限 (osascript) │ │ ├── win32-admin.ts # Windows UAC 提升 │ │ └── linux-admin.ts # Linux pkexec/polkit │ │ │ ├── env-config/ # 环境配置模块 │ │ ├── index.ts # 环境配置管理器 │ │ ├── path-manager.ts # PATH 环境变量管理 │ │ ├── api-keys.ts # API Key 安全存储 │ │ └── shell-profile.ts # Shell 配置文件管理 │ │ │ ├── preload/ │ │ └── index.ts # 预加载脚本 (contextBridge) │ │ │ └── utils/ │ ├── logger.ts # 日志 │ ├── paths.ts # 路径管理 │ └── store.ts # 持久化存储 (electron-store) │ ├── src/ # React 渲染进程 │ ├── main.tsx # React 入口 │ ├── App.tsx # 根组件 │ │ │ ├── pages/ # 页面组件 │ │ ├── Dashboard/ │ │ │ ├── index.tsx │ │ │ ├── StatusCard.tsx │ │ │ └── QuickActions.tsx │ │ ├── Chat/ │ │ │ ├── index.tsx │ │ │ ├── MessageList.tsx │ │ │ ├── InputArea.tsx │ │ │ └── ToolCallCard.tsx │ │ ├── Channels/ │ │ │ ├── index.tsx │ │ │ ├── ChannelCard.tsx │ │ │ └── QRScanner.tsx │ │ ├── Skills/ │ │ │ ├── index.tsx │ │ │ ├── SkillCard.tsx │ │ │ ├── SkillMarket.tsx │ │ │ └── BundleSelector.tsx │ │ ├── Cron/ # 定时任务 │ │ │ ├── index.tsx # 任务列表页 │ │ │ ├── CronJobCard.tsx # 任务卡片组件 │ │ │ ├── CronEditor.tsx # 任务编辑器 │ │ │ ├── CronSchedulePicker.tsx # Cron 表达式选择器 │ │ │ └── CronHistory.tsx # 执行历史 │ │ ├── Settings/ │ │ │ ├── index.tsx │ │ │ ├── GeneralSettings.tsx │ │ │ ├── ProviderSettings.tsx │ │ │ └── AdvancedSettings.tsx │ │ └── Setup/ # 安装向导 │ │ ├── index.tsx │ │ ├── WelcomeStep.tsx │ │ ├── RuntimeStep.tsx │ │ ├── ProviderStep.tsx │ │ ├── ChannelStep.tsx │ │ └── SkillStep.tsx │ │ │ ├── components/ # 通用组件 │ │ ├── ui/ # shadcn/ui 组件 │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dialog.tsx │ │ │ └── ... │ │ ├── layout/ │ │ │ ├── Sidebar.tsx │ │ │ ├── Header.tsx │ │ │ └── MainLayout.tsx │ │ └── common/ │ │ ├── LoadingSpinner.tsx │ │ ├── ErrorBoundary.tsx │ │ └── StatusBadge.tsx │ │ │ ├── hooks/ # 自定义 Hooks │ │ ├── useGateway.ts # Gateway 连接状态 │ │ ├── useChannels.ts # 通道数据 │ │ ├── useSkills.ts # 技能数据 │ │ ├── useChat.ts # 聊天会话 │ │ ├── useCron.ts # 定时任务数据 │ │ └── useElectron.ts # IPC 通信 │ │ │ ├── stores/ # Zustand 状态管理 │ │ ├── gateway.ts # Gateway 状态 │ │ ├── channels.ts # 通道状态 │ │ ├── chat.ts # 聊天状态 │ │ ├── skills.ts # 技能状态 │ │ ├── cron.ts # 定时任务状态 │ │ └── settings.ts # 设置状态 │ │ │ ├── services/ # 服务层 │ │ ├── gateway-rpc.ts # Gateway RPC 调用封装 │ │ ├── skill-service.ts # 技能服务 │ │ ├── cron-service.ts # 定时任务服务 │ │ └── update-service.ts # 更新服务 │ │ │ ├── types/ # TypeScript 类型定义 │ │ ├── gateway.ts # Gateway 协议类型 │ │ ├── channel.ts # 通道类型 │ │ ├── skill.ts # 技能类型 │ │ ├── cron.ts # 定时任务类型 │ │ └── electron.d.ts # Electron API 类型 │ │ │ ├── utils/ # 工具函数 │ │ ├── format.ts │ │ └── platform.ts │ │ │ └── styles/ │ ├── globals.css # 全局样式 │ └── themes/ # 主题定义 │ ├── light.css │ └── dark.css │ ├── resources/ # 静态资源 │ ├── icons/ # 应用图标 │ │ ├── icon.icns # macOS │ │ ├── icon.ico # Windows │ │ └── icon.png # Linux │ ├── skills/ # 预装技能包 │ │ ├── productivity.json │ │ ├── developer.json │ │ └── smart-home.json │ └── locales/ # 国际化 (可选) │ ├── en.json │ └── zh-CN.json │ ├── scripts/ # 构建/工具脚本 │ ├── build.ts # 构建脚本 │ ├── release.ts # 发布脚本 │ ├── notarize.ts # macOS 公证 │ └── dev.ts # 开发脚本 │ ├── tests/ # 测试 │ ├── unit/ # 单元测试 │ ├── integration/ # 集成测试 │ └── e2e/ # E2E 测试 │ ├── setup.spec.ts # 安装向导测试 │ ├── chat.spec.ts # 聊天功能测试 │ └── channels.spec.ts # 通道配置测试 │ ├── .env.example # 环境变量示例 ├── .eslintrc.cjs # ESLint 配置 ├── .prettierrc # Prettier 配置 ├── electron-builder.yml # 打包配置 ├── package.json ├── tsconfig.json # TypeScript 配置 (主) ├── tsconfig.node.json # TypeScript 配置 (Node) ├── vite.config.ts # Vite 配置 ├── tailwind.config.js # Tailwind 配置 ├── CHANGELOG.md ├── LICENSE └── README.md ``` ### 2.4 核心模块设计 #### 2.4.1 Gateway 管理器 ```typescript // electron/gateway/manager.ts import { spawn, ChildProcess } from 'child_process'; import { EventEmitter } from 'events'; import WebSocket from 'ws'; export interface GatewayStatus { state: 'stopped' | 'starting' | 'running' | 'error'; port: number; pid?: number; uptime?: number; error?: string; } export class GatewayManager extends EventEmitter { private process: ChildProcess | null = null; private ws: WebSocket | null = null; private status: GatewayStatus = { state: 'stopped', port: 18789 }; // 启动 Gateway async start(): Promise { if (this.status.state === 'running') return; this.setStatus({ state: 'starting' }); try { // 检查已有进程 const existing = await this.findExisting(); if (existing) { await this.connect(existing.port); return; } // 启动新进程 this.process = spawn('openclaw', [ 'gateway', 'run', '--port', String(this.status.port), '--force' ], { stdio: ['ignore', 'pipe', 'pipe'], detached: true, }); this.process.on('exit', (code) => { this.setStatus({ state: 'stopped' }); this.emit('exit', code); }); // 等待就绪并连接 await this.waitForReady(); await this.connect(this.status.port); } catch (error) { this.setStatus({ state: 'error', error: String(error) }); throw error; } } // 停止 Gateway async stop(): Promise { this.ws?.close(); this.process?.kill(); this.setStatus({ state: 'stopped' }); } // RPC 调用 async rpc(method: string, params?: unknown): Promise { return new Promise((resolve, reject) => { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { reject(new Error('Gateway not connected')); return; } const id = crypto.randomUUID(); const handler = (data: WebSocket.Data) => { const msg = JSON.parse(data.toString()); if (msg.id === id) { this.ws?.off('message', handler); if (msg.error) reject(msg.error); else resolve(msg.result as T); } }; this.ws.on('message', handler); this.ws.send(JSON.stringify({ jsonrpc: '2.0', id, method, params })); }); } private setStatus(update: Partial): void { this.status = { ...this.status, ...update }; this.emit('status', this.status); } } ``` #### 2.4.2 安装向导流程 ```typescript // src/pages/Setup/index.tsx import { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; interface SetupStep { id: string; title: string; description: string; component: React.ComponentType; } const steps: SetupStep[] = [ { id: 'welcome', title: '欢迎使用 ClawX', description: '您的 AI 助手,即将启程', component: WelcomeStep, }, { id: 'runtime', title: '环境检测', description: '检测并安装必要运行环境', component: RuntimeStep, }, { id: 'provider', title: '选择 AI 模型', description: '配置您的 AI 服务提供商', component: ProviderStep, }, { id: 'channel', title: '连接消息应用', description: '绑定 WhatsApp、Telegram 等', component: ChannelStep, }, { id: 'skills', title: '选择技能包', description: '挑选预装技能,稍后可调整', component: SkillStep, }, { id: 'complete', title: '设置完成', description: '一切就绪,开始使用吧!', component: CompleteStep, }, ]; export function SetupWizard() { const [currentStep, setCurrentStep] = useState(0); const [setupData, setSetupData] = useState({}); const step = steps[currentStep]; const StepComponent = step.component; const handleNext = (data: Partial) => { setSetupData({ ...setupData, ...data }); setCurrentStep((i) => Math.min(i + 1, steps.length - 1)); }; return (
{/* 进度指示器 */}
{steps.map((s, i) => (
{i < steps.length - 1 && (
)}
))}
{/* 步骤内容 */}

{step.title}

{step.description}

setCurrentStep((i) => Math.max(i - 1, 0))} />
); } ``` #### 2.4.3 预装技能包定义 ```typescript // resources/skills/bundles.ts export interface SkillBundle { id: string; name: string; nameZh: string; description: string; descriptionZh: string; icon: string; skills: string[]; recommended?: boolean; } export const skillBundles: SkillBundle[] = [ { id: 'productivity', name: 'Productivity', nameZh: '效率办公', description: 'Calendar, reminders, notes, email management', descriptionZh: '日历、提醒、笔记、邮件管理', icon: '📋', skills: [ 'apple-reminders', 'apple-notes', 'himalaya', 'notion', 'obsidian', 'trello', ], recommended: true, }, { id: 'developer', name: 'Developer Tools', nameZh: '开发者工具', description: 'GitHub, coding assistant, terminal management', descriptionZh: 'GitHub、代码助手、终端管理', icon: '💻', skills: [ 'github', 'coding-agent', 'tmux', ], recommended: true, }, { id: 'smart-home', name: 'Smart Home', nameZh: '智能家居', description: 'Lights, music, device control', descriptionZh: '灯光、音乐、设备控制', icon: '🏠', skills: [ 'openhue', 'sonoscli', 'spotify-player', ], }, { id: 'media', name: 'Media & Creative', nameZh: '多媒体创作', description: 'Image generation, video processing, audio transcription', descriptionZh: '图片生成、视频处理、音频转写', icon: '🎨', skills: [ 'openai-image-gen', 'nano-banana-pro', 'video-frames', 'openai-whisper-api', ], }, { id: 'communication', name: 'Communication', nameZh: '通讯增强', description: 'Messaging, voice calls, notifications', descriptionZh: '消息管理、语音通话、通知', icon: '💬', skills: [ 'discord', 'slack', 'voice-call', 'imsg', ], }, { id: 'security', name: 'Security & Privacy', nameZh: '安全隐私', description: 'Password management, secrets', descriptionZh: '密码管理、密钥存储', icon: '🔐', skills: [ '1password', ], }, { id: 'information', name: 'Information', nameZh: '信息获取', description: 'Weather, news, web browsing', descriptionZh: '天气、新闻、网页浏览', icon: '🌐', skills: [ 'weather', 'blogwatcher', 'summarize', ], }, ]; ``` ### 2.5 默认预装机制设计 ClawX 需要在代码层面实现**默认技能**和**默认扩展**的预装机制,确保用户开箱即用。 #### 2.5.1 预装配置定义 ```typescript // electron/presets/defaults.ts /** * ClawX 预装配置 * 定义首次安装时默认启用的技能和扩展 */ export interface ClawXPresets { /** 默认启用的技能 ID 列表 */ skills: string[]; /** 默认启用的扩展 ID 列表 */ extensions: string[]; /** 默认技能包 (用户可在安装向导中选择) */ defaultBundles: string[]; /** 核心技能 (始终启用,用户不可禁用) */ coreSkills: string[]; /** 核心扩展 (始终启用,用户不可禁用) */ coreExtensions: string[]; } /** * ClawX 默认预装配置 */ export const CLAWX_PRESETS: ClawXPresets = { // 默认启用的技能 (首次安装自动启用) skills: [ // Tier 1: 核心体验技能 'coding-agent', // 代码助手 (类似 opencode) 'canvas', // Canvas UI 'summarize', // 内容摘要 // Tier 2: 常用工具技能 'weather', // 天气查询 'github', // GitHub 集成 'clawhub', // 技能市场 ], // 默认启用的扩展 extensions: [ 'lobster', // UI 美化 'memory-core', // 记忆系统 ], // 默认推荐的技能包 (安装向导中预选) defaultBundles: [ 'productivity', 'developer', ], // 核心技能 (不可禁用) coreSkills: [ 'coding-agent', // 代码能力是核心体验 ], // 核心扩展 (不可禁用) coreExtensions: [ 'memory-core', // 记忆是核心功能 ], }; ``` #### 2.5.2 预装加载器 ```typescript // electron/installer/preset-loader.ts import { CLAWX_PRESETS } from '../presets/defaults'; import { GatewayManager } from '../gateway/manager'; export interface PresetLoadResult { skills: { id: string; status: 'loaded' | 'failed'; error?: string }[]; extensions: { id: string; status: 'loaded' | 'failed'; error?: string }[]; } export class PresetLoader { constructor(private gateway: GatewayManager) {} /** * 首次安装时加载所有预装项 */ async loadInitialPresets(): Promise { const result: PresetLoadResult = { skills: [], extensions: [] }; // 1. 加载核心扩展 (优先级最高) for (const extId of CLAWX_PRESETS.coreExtensions) { const status = await this.loadExtension(extId, { required: true }); result.extensions.push({ id: extId, ...status }); } // 2. 加载默认扩展 for (const extId of CLAWX_PRESETS.extensions) { if (!CLAWX_PRESETS.coreExtensions.includes(extId)) { const status = await this.loadExtension(extId, { required: false }); result.extensions.push({ id: extId, ...status }); } } // 3. 加载核心技能 for (const skillId of CLAWX_PRESETS.coreSkills) { const status = await this.loadSkill(skillId, { required: true }); result.skills.push({ id: skillId, ...status }); } // 4. 加载默认技能 for (const skillId of CLAWX_PRESETS.skills) { if (!CLAWX_PRESETS.coreSkills.includes(skillId)) { const status = await this.loadSkill(skillId, { required: false }); result.skills.push({ id: skillId, ...status }); } } return result; } /** * 加载用户选择的技能包 */ async loadBundles(bundleIds: string[]): Promise { const { skillBundles } = await import('../../resources/skills/bundles'); for (const bundleId of bundleIds) { const bundle = skillBundles.find(b => b.id === bundleId); if (bundle) { for (const skillId of bundle.skills) { await this.loadSkill(skillId, { required: false }); } } } } private async loadSkill( skillId: string, opts: { required: boolean } ): Promise<{ status: 'loaded' | 'failed'; error?: string }> { try { // 通过 Gateway RPC 启用技能 await this.gateway.rpc('skills.enable', { skillId }); return { status: 'loaded' }; } catch (error) { if (opts.required) { throw new Error(`Failed to load required skill: ${skillId}`); } return { status: 'failed', error: String(error) }; } } private async loadExtension( extId: string, opts: { required: boolean } ): Promise<{ status: 'loaded' | 'failed'; error?: string }> { try { // 通过 Gateway RPC 启用扩展 await this.gateway.rpc('plugins.enable', { pluginId: extId }); return { status: 'loaded' }; } catch (error) { if (opts.required) { throw new Error(`Failed to load required extension: ${extId}`); } return { status: 'failed', error: String(error) }; } } } ``` #### 2.5.3 预装状态管理 ```typescript // src/stores/presets.ts import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface PresetState { /** 是否已完成首次预装 */ initialized: boolean; /** 用户选择的技能包 */ selectedBundles: string[]; /** 用户额外启用的技能 */ enabledSkills: string[]; /** 用户禁用的默认技能 (不包括核心技能) */ disabledSkills: string[]; /** 用户额外启用的扩展 */ enabledExtensions: string[]; /** 用户禁用的默认扩展 (不包括核心扩展) */ disabledExtensions: string[]; // Actions setInitialized: (value: boolean) => void; setSelectedBundles: (bundles: string[]) => void; toggleSkill: (skillId: string, enabled: boolean) => void; toggleExtension: (extId: string, enabled: boolean) => void; // Computed getEffectiveSkills: () => string[]; getEffectiveExtensions: () => string[]; } export const usePresetStore = create()( persist( (set, get) => ({ initialized: false, selectedBundles: [], enabledSkills: [], disabledSkills: [], enabledExtensions: [], disabledExtensions: [], setInitialized: (value) => set({ initialized: value }), setSelectedBundles: (bundles) => set({ selectedBundles: bundles }), toggleSkill: (skillId, enabled) => { const { enabledSkills, disabledSkills } = get(); if (enabled) { set({ enabledSkills: [...enabledSkills, skillId], disabledSkills: disabledSkills.filter(id => id !== skillId), }); } else { set({ enabledSkills: enabledSkills.filter(id => id !== skillId), disabledSkills: [...disabledSkills, skillId], }); } }, toggleExtension: (extId, enabled) => { const { enabledExtensions, disabledExtensions } = get(); if (enabled) { set({ enabledExtensions: [...enabledExtensions, extId], disabledExtensions: disabledExtensions.filter(id => id !== extId), }); } else { set({ enabledExtensions: enabledExtensions.filter(id => id !== extId), disabledExtensions: [...disabledExtensions, extId], }); } }, // 计算实际生效的技能列表 getEffectiveSkills: () => { const { selectedBundles, enabledSkills, disabledSkills } = get(); const { CLAWX_PRESETS } = require('../../electron/presets/defaults'); const { skillBundles } = require('../../resources/skills/bundles'); // 1. 核心技能 (始终包含) const skills = new Set(CLAWX_PRESETS.coreSkills); // 2. 默认技能 CLAWX_PRESETS.skills.forEach((id: string) => skills.add(id)); // 3. 选中的技能包 for (const bundleId of selectedBundles) { const bundle = skillBundles.find((b: any) => b.id === bundleId); bundle?.skills.forEach((id: string) => skills.add(id)); } // 4. 用户额外启用的 enabledSkills.forEach(id => skills.add(id)); // 5. 移除用户禁用的 (但不能移除核心技能) disabledSkills.forEach(id => { if (!CLAWX_PRESETS.coreSkills.includes(id)) { skills.delete(id); } }); return Array.from(skills); }, getEffectiveExtensions: () => { // 类似逻辑... const { enabledExtensions, disabledExtensions } = get(); const { CLAWX_PRESETS } = require('../../electron/presets/defaults'); const extensions = new Set(CLAWX_PRESETS.coreExtensions); CLAWX_PRESETS.extensions.forEach((id: string) => extensions.add(id)); enabledExtensions.forEach(id => extensions.add(id)); disabledExtensions.forEach(id => { if (!CLAWX_PRESETS.coreExtensions.includes(id)) { extensions.delete(id); } }); return Array.from(extensions); }, }), { name: 'clawx-presets', } ) ); ``` #### 2.5.4 安装向导集成 ```typescript // src/pages/Setup/SkillStep.tsx import { usePresetStore } from '@/stores/presets'; import { skillBundles } from '@/resources/skills/bundles'; import { CLAWX_PRESETS } from '@electron/presets/defaults'; export function SkillStep({ onNext, onBack }: StepProps) { const { selectedBundles, setSelectedBundles } = usePresetStore(); // 默认预选推荐的技能包 const [selected, setSelected] = useState( selectedBundles.length > 0 ? selectedBundles : CLAWX_PRESETS.defaultBundles ); const handleToggle = (bundleId: string) => { setSelected(prev => prev.includes(bundleId) ? prev.filter(id => id !== bundleId) : [...prev, bundleId] ); }; const handleNext = () => { setSelectedBundles(selected); onNext({ bundles: selected }); }; return (
{skillBundles.map(bundle => ( handleToggle(bundle.id)} >
{bundle.icon}
{bundle.nameZh} {bundle.descriptionZh}
{bundle.skills.slice(0, 4).map(skill => ( {skill} ))} {bundle.skills.length > 4 && ( +{bundle.skills.length - 4} )}
{bundle.recommended && (
推荐
)}
))}
{/* 核心技能提示 */} 以下核心技能将始终启用: {CLAWX_PRESETS.coreSkills.map(id => ( {id} ))}
); } ``` #### 2.5.5 预装层级与优先级 ``` ┌─────────────────────────────────────────────────────────────────┐ │ ClawX 预装层级架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Layer 0: 核心层 (Core) - 不可禁用 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Skills: coding-agent │ │ │ │ Extensions: memory-core │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ 始终启用 │ │ │ │ Layer 1: 默认层 (Default) - 可禁用 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Skills: canvas, summarize, weather, github, clawhub │ │ │ │ Extensions: lobster │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ 首次安装自动启用 │ │ │ │ Layer 2: 技能包层 (Bundle) - 用户选择 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ productivity: apple-reminders, notion, obsidian... │ │ │ │ developer: github, coding-agent, tmux... │ │ │ │ smart-home: openhue, sonoscli, spotify-player... │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ 安装向导中选择 │ │ │ │ Layer 3: 用户层 (User) - 完全自定义 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 用户手动启用/禁用的技能和扩展 │ │ │ │ (通过设置页面或技能市场) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ 优先级: Layer 3 > Layer 2 > Layer 1 > Layer 0 (不可覆盖) ``` #### 2.5.6 配置文件结构 ```json5 // ~/.clawx/presets.json (用户本地配置) { "version": 1, "initialized": true, "initializedAt": "2026-02-05T12:00:00Z", // 用户选择的技能包 "bundles": ["productivity", "developer"], // 用户自定义覆盖 "overrides": { "skills": { "enabled": ["custom-skill-1"], // 额外启用 "disabled": ["weather"] // 禁用默认 }, "extensions": { "enabled": ["custom-ext-1"], "disabled": [] } }, // 同步到 OpenClaw 的配置 (生成后写入 ~/.openclaw/config.json) "syncedConfig": { "skills": { "enabled": ["coding-agent", "canvas", "github", "notion", "custom-skill-1"] }, "plugins": { "entries": { "memory-core": { "enabled": true }, "lobster": { "enabled": true } } } } } ``` ### 2.6 跨平台自动更新系统 ClawX 需要实现**主动式**自动更新机制,在新版本发布后主动通知用户,同时支持用户手动检查更新。 #### 2.6.1 更新策略设计 ``` ┌─────────────────────────────────────────────────────────────────┐ │ ClawX 自动更新流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 启动检查 │ │ 定时检查 │ │ 手动检查 │ │ │ │ (App Start) │ │ (每6小时) │ │ (用户触发) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ └───────────────────┼───────────────────┘ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ Version Checker │ │ │ │ 检查 GitHub/CDN │ │ │ └──────────┬──────────┘ │ │ │ │ │ ┌──────────────┴──────────────┐ │ │ ▼ ▼ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ 无新版本 │ │ 有新版本 │ │ │ │ 静默结束 │ │ │ │ │ └────────────────┘ └───────┬────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 主动通知弹窗 │ │ 静默下载 │ │ │ │ (询问用户) │ │ (后台进行) │ │ │ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ ┌────────────┴────────────┐ │ │ │ ▼ ▼ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ │ 立即更新 │ │ 稍后提醒 │ │ │ │ │ (下载安装) │ │ (记录时间) │ │ │ │ └───────┬────────┘ └────────────────┘ │ │ │ │ │ │ │ └────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ 下载完成通知 │ │ │ │ "重启以完成更新" │ │ │ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` #### 2.6.2 更新管理器实现 ```typescript // electron/updater/index.ts import { autoUpdater, UpdateInfo } from 'electron-updater'; import { app, BrowserWindow, dialog, Notification } from 'electron'; import { EventEmitter } from 'events'; import log from 'electron-log'; export interface UpdateConfig { /** 更新通道: stable | beta | dev */ channel: 'stable' | 'beta' | 'dev'; /** 是否自动下载 */ autoDownload: boolean; /** 是否允许降级 */ allowDowngrade: boolean; /** 检查间隔 (毫秒) */ checkInterval: number; /** 是否显示主动通知 */ showNotification: boolean; } export interface UpdateStatus { state: 'idle' | 'checking' | 'available' | 'downloading' | 'downloaded' | 'error'; currentVersion: string; latestVersion?: string; releaseNotes?: string; downloadProgress?: number; error?: string; } const DEFAULT_CONFIG: UpdateConfig = { channel: 'stable', autoDownload: false, // 默认不自动下载,先询问用户 allowDowngrade: false, checkInterval: 6 * 60 * 60 * 1000, // 6小时 showNotification: true, }; export class UpdateManager extends EventEmitter { private config: UpdateConfig; private status: UpdateStatus; private checkTimer: NodeJS.Timeout | null = null; private mainWindow: BrowserWindow | null = null; constructor(config: Partial = {}) { super(); this.config = { ...DEFAULT_CONFIG, ...config }; this.status = { state: 'idle', currentVersion: app.getVersion(), }; this.setupAutoUpdater(); } private setupAutoUpdater(): void { // 配置 electron-updater autoUpdater.logger = log; autoUpdater.autoDownload = this.config.autoDownload; autoUpdater.allowDowngrade = this.config.allowDowngrade; // 设置更新通道 autoUpdater.channel = this.config.channel; // GitHub Releases 作为更新源 autoUpdater.setFeedURL({ provider: 'github', owner: 'clawx', repo: 'clawx', }); // 事件监听 autoUpdater.on('checking-for-update', () => { this.setStatus({ state: 'checking' }); }); autoUpdater.on('update-available', (info: UpdateInfo) => { this.setStatus({ state: 'available', latestVersion: info.version, releaseNotes: this.formatReleaseNotes(info.releaseNotes), }); // 主动通知用户 if (this.config.showNotification) { this.showUpdateNotification(info); } }); autoUpdater.on('update-not-available', () => { this.setStatus({ state: 'idle' }); }); autoUpdater.on('download-progress', (progress) => { this.setStatus({ state: 'downloading', downloadProgress: Math.round(progress.percent), }); }); autoUpdater.on('update-downloaded', (info: UpdateInfo) => { this.setStatus({ state: 'downloaded' }); this.showDownloadedNotification(info); }); autoUpdater.on('error', (error) => { this.setStatus({ state: 'error', error: error.message }); }); } /** * 初始化更新器 (应用启动时调用) */ initialize(mainWindow: BrowserWindow): void { this.mainWindow = mainWindow; // 启动时检查更新 (延迟5秒,避免影响启动速度) setTimeout(() => this.checkForUpdates(true), 5000); // 定时检查 this.startPeriodicCheck(); } /** * 启动定时检查 */ private startPeriodicCheck(): void { if (this.checkTimer) { clearInterval(this.checkTimer); } this.checkTimer = setInterval(() => { this.checkForUpdates(true); }, this.config.checkInterval); } /** * 检查更新 * @param silent 是否静默 (不显示"已是最新版本"提示) */ async checkForUpdates(silent: boolean = false): Promise { try { const result = await autoUpdater.checkForUpdates(); if (!result?.updateInfo && !silent) { // 手动检查且无更新时,显示提示 this.showNoUpdateDialog(); } } catch (error) { if (!silent) { this.showErrorDialog(error as Error); } } } /** * 下载更新 */ async downloadUpdate(): Promise { if (this.status.state !== 'available') return; await autoUpdater.downloadUpdate(); } /** * 安装更新并重启 */ quitAndInstall(): void { autoUpdater.quitAndInstall(false, true); } /** * 显示更新可用通知 */ private showUpdateNotification(info: UpdateInfo): void { // 系统通知 if (Notification.isSupported()) { const notification = new Notification({ title: 'ClawX 有新版本可用', body: `版本 ${info.version} 已发布,点击查看详情`, icon: app.isPackaged ? undefined : 'resources/icons/icon.png', }); notification.on('click', () => { this.showUpdateDialog(info); }); notification.show(); } // 同时发送到渲染进程 this.mainWindow?.webContents.send('update:available', { version: info.version, releaseNotes: this.formatReleaseNotes(info.releaseNotes), }); } /** * 显示更新对话框 (主动询问用户) */ private async showUpdateDialog(info: UpdateInfo): Promise { const releaseNotes = this.formatReleaseNotes(info.releaseNotes); const result = await dialog.showMessageBox(this.mainWindow!, { type: 'info', title: '发现新版本', message: `ClawX ${info.version} 已发布`, detail: `当前版本: ${this.status.currentVersion}\n\n更新内容:\n${releaseNotes}`, buttons: ['立即更新', '稍后提醒', '跳过此版本'], defaultId: 0, cancelId: 1, }); switch (result.response) { case 0: // 立即更新 this.downloadUpdate(); break; case 1: // 稍后提醒 // 30分钟后再次提醒 setTimeout(() => this.showUpdateNotification(info), 30 * 60 * 1000); break; case 2: // 跳过此版本 this.skipVersion(info.version); break; } } /** * 显示下载完成通知 */ private showDownloadedNotification(info: UpdateInfo): void { if (Notification.isSupported()) { const notification = new Notification({ title: '更新已就绪', body: `ClawX ${info.version} 已下载完成,点击重启应用以完成更新`, }); notification.on('click', () => { this.showRestartDialog(); }); notification.show(); } this.mainWindow?.webContents.send('update:downloaded', { version: info.version, }); } /** * 显示重启对话框 */ private async showRestartDialog(): Promise { const result = await dialog.showMessageBox(this.mainWindow!, { type: 'info', title: '更新已就绪', message: '重启应用以完成更新', detail: '更新已下载完成,是否立即重启应用?', buttons: ['立即重启', '稍后'], defaultId: 0, }); if (result.response === 0) { this.quitAndInstall(); } } /** * 显示无更新对话框 (手动检查时) */ private showNoUpdateDialog(): void { dialog.showMessageBox(this.mainWindow!, { type: 'info', title: '检查更新', message: '已是最新版本', detail: `当前版本 ${this.status.currentVersion} 已是最新`, buttons: ['确定'], }); } private showErrorDialog(error: Error): void { dialog.showMessageBox(this.mainWindow!, { type: 'error', title: '更新检查失败', message: '无法检查更新', detail: error.message, buttons: ['确定'], }); } private formatReleaseNotes(notes: string | any): string { if (typeof notes === 'string') return notes; if (Array.isArray(notes)) { return notes.map(n => n.note || n).join('\n'); } return ''; } private skipVersion(version: string): void { // 存储跳过的版本 const store = require('electron-store'); const settings = new store(); const skipped = settings.get('update.skippedVersions', []) as string[]; if (!skipped.includes(version)) { settings.set('update.skippedVersions', [...skipped, version]); } } private setStatus(update: Partial): void { this.status = { ...this.status, ...update }; this.emit('status', this.status); this.mainWindow?.webContents.send('update:status', this.status); } getStatus(): UpdateStatus { return this.status; } setConfig(config: Partial): void { this.config = { ...this.config, ...config }; autoUpdater.channel = this.config.channel; autoUpdater.autoDownload = this.config.autoDownload; // 重启定时检查 this.startPeriodicCheck(); } } // 导出单例 export const updateManager = new UpdateManager(); ``` #### 2.6.3 渲染进程更新 UI ```typescript // src/hooks/useUpdate.ts import { useState, useEffect } from 'react'; interface UpdateStatus { state: 'idle' | 'checking' | 'available' | 'downloading' | 'downloaded' | 'error'; currentVersion: string; latestVersion?: string; releaseNotes?: string; downloadProgress?: number; error?: string; } export function useUpdate() { const [status, setStatus] = useState({ state: 'idle', currentVersion: '0.0.0', }); useEffect(() => { // 监听主进程更新状态 const handleStatus = (_: any, newStatus: UpdateStatus) => { setStatus(newStatus); }; window.electron.ipcRenderer.on('update:status', handleStatus); // 获取初始状态 window.electron.ipcRenderer.invoke('update:getStatus').then(setStatus); return () => { window.electron.ipcRenderer.off('update:status', handleStatus); }; }, []); const checkForUpdates = () => { window.electron.ipcRenderer.invoke('update:check'); }; const downloadUpdate = () => { window.electron.ipcRenderer.invoke('update:download'); }; const installUpdate = () => { window.electron.ipcRenderer.invoke('update:install'); }; return { status, checkForUpdates, downloadUpdate, installUpdate, }; } ``` ```typescript // src/components/UpdateNotification.tsx import { useUpdate } from '@/hooks/useUpdate'; import { motion, AnimatePresence } from 'framer-motion'; import { Download, RefreshCw, X, Loader2 } from 'lucide-react'; export function UpdateNotification() { const { status, downloadUpdate, installUpdate } = useUpdate(); const [dismissed, setDismissed] = useState(false); // 只在有更新可用或已下载时显示 const shouldShow = !dismissed && (status.state === 'available' || status.state === 'downloaded'); return ( {shouldShow && (
{status.state === 'available' ? ( <> 新版本可用 ) : ( <> 更新已就绪 )}

ClawX {status.latestVersion} 已发布

{status.state === 'downloading' && (

下载中 {status.downloadProgress}%

)}
{status.state === 'available' && ( )} {status.state === 'downloaded' && ( )}
)}
); } ``` #### 2.6.4 设置页面手动检查入口 ```typescript // src/pages/Settings/GeneralSettings.tsx import { useUpdate } from '@/hooks/useUpdate'; export function GeneralSettings() { const { status, checkForUpdates } = useUpdate(); return ( 通用设置 {/* 版本与更新 */}

ClawX

当前版本: {status.currentVersion}

{status.latestVersion && status.state === 'available' && (

新版本可用: {status.latestVersion}

)}
{/* 更新设置 */}

自动检查更新

启动时和每6小时自动检查

自动下载更新

发现新版本后在后台自动下载

更新通道

选择接收更新的类型

{/* 其他设置... */}
); } ``` #### 2.6.5 跨平台配置 ```typescript // electron/updater/channels.ts import { Platform } from 'electron-builder'; export interface PlatformUpdateConfig { /** 更新源 URL */ feedUrl: string; /** 是否支持差量更新 */ supportsDelta: boolean; /** 安装方式 */ installMethod: 'nsis' | 'dmg' | 'appimage' | 'deb' | 'rpm'; } export const PLATFORM_CONFIGS: Record = { darwin: { feedUrl: 'https://releases.clawx.app/mac', supportsDelta: true, // macOS 支持 Sparkle 差量更新 installMethod: 'dmg', }, win32: { feedUrl: 'https://releases.clawx.app/win', supportsDelta: true, // Windows NSIS 支持差量 installMethod: 'nsis', }, linux: { feedUrl: 'https://releases.clawx.app/linux', supportsDelta: false, // AppImage 不支持差量 installMethod: 'appimage', }, } as Record; // electron-builder.yml 配置示例 export const ELECTRON_BUILDER_CONFIG = ` appId: app.clawx.desktop productName: ClawX copyright: Copyright © 2026 ClawX publish: - provider: github owner: clawx repo: clawx releaseType: release mac: category: public.app-category.productivity target: - target: dmg arch: [universal] - target: zip arch: [universal] notarize: teamId: \${env.APPLE_TEAM_ID} win: target: - target: nsis arch: [x64, arm64] publisherName: ClawX Inc. nsis: oneClick: false allowToChangeInstallationDirectory: true differentialPackage: true # 启用差量更新 linux: target: - target: AppImage arch: [x64, arm64] - target: deb arch: [x64] category: Utility `; ``` #### 2.6.6 更新时间线与用户交互 ``` 用户视角的更新流程: ┌─────────────────────────────────────────────────────────────────┐ │ 场景 A: 有新版本时主动通知 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. 用户正在使用 ClawX │ │ │ │ │ 2. 系统检测到新版本 (启动时/定时检查) │ │ │ │ │ 3. 右上角弹出通知卡片 ────────────────────┐ │ │ │ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ 🔔 新版本可用 │ │ │ │ │ │ ClawX 1.2.0 已发布 │ │ │ │ │ │ [立即下载] [稍后] │ │ │ │ │ └─────────────────────────────┘ │ │ │ │ │ │ │ 4. 用户点击"立即下载" │ │ │ │ │ 5. 后台下载,通知卡片显示进度 │ │ │ │ │ 6. 下载完成,通知"重启以完成更新" │ │ │ │ │ 7. 用户选择"立即重启"或继续工作后重启 │ │ │ └─────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────┐ │ 场景 B: 用户手动检查更新 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. 用户打开 设置 → 通用 → 检查更新 │ │ │ │ │ 2. 显示"检查中..." │ │ │ │ │ 3a. 无更新 → 弹窗提示"已是最新版本 (1.1.0)" │ │ │ │ │ 3b. 有更新 → 弹窗显示更新详情 │ │ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ 发现新版本 │ │ │ │ │ │ │ │ │ │ ClawX 1.2.0 已发布 │ │ │ │ │ 当前版本: 1.1.0 │ │ │ │ │ │ │ │ │ │ 更新内容: │ │ │ │ │ • 新增技能市场 │ │ │ │ │ • 修复 Windows 启动问题 │ │ │ │ │ • 性能优化 │ │ │ │ │ │ │ │ │ │ [立即更新] [稍后提醒] [跳过此版本] │ │ │ │ └─────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.7 GUI 权限提升机制 ClawX 需要在某些操作时获取管理员权限(如全局安装 Node.js、修改系统 PATH),必须通过**图形化密码弹窗**而非命令行 `sudo`。 #### 2.7.1 需要管理员权限的场景 | 场景 | 操作 | 原因 | |------|------|------| | Node.js 安装 | 写入 `/usr/local/bin` (macOS/Linux) | 系统目录需要 root | | 全局 npm 安装 | `npm install -g openclaw` | 系统 node_modules | | PATH 配置 | 修改 `/etc/paths.d/` | 系统级环境变量 | | 端口 < 1024 | Gateway 使用低端口 | 特权端口 | | 系统服务注册 | LaunchDaemon / systemd | 开机自启 | #### 2.7.2 跨平台权限提升架构 ``` ┌─────────────────────────────────────────────────────────────────┐ │ ClawX 权限提升流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────┐ │ │ │ 需要管理员权限 │ │ │ │ 的操作触发 │ │ │ └────────┬─────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ PrivilegeManager │ │ │ │ 检测当前平台 │ │ │ └────────┬─────────┘ │ │ │ │ │ ┌─────┴─────┬─────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │macOS │ │Windows│ │Linux │ │ │ └──┬───┘ └──┬───┘ └──┬───┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │osascript│ │ UAC │ │pkexec │ │ │ │密码弹窗 │ │提升弹窗│ │密码弹窗│ │ │ └────────┘ └────────┘ └────────┘ │ │ │ │ │ │ │ └──────────┴───────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────┐ │ │ │ 以管理员身份执行命令 │ │ │ │ (无需终端 sudo 输入) │ │ │ └──────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` #### 2.7.3 权限管理器实现 ```typescript // electron/privilege/index.ts import { dialog } from 'electron'; import { platform } from 'os'; import { DarwinAdmin } from './darwin-admin'; import { Win32Admin } from './win32-admin'; import { LinuxAdmin } from './linux-admin'; export interface PrivilegeResult { success: boolean; stdout?: string; stderr?: string; error?: string; } export interface PrivilegeOptions { /** 向用户展示的操作说明 */ reason: string; /** 图标路径 */ icon?: string; /** 超时时间 (毫秒) */ timeout?: number; } /** * 跨平台权限提升管理器 * 通过 GUI 弹窗获取管理员权限,而非命令行 sudo */ export class PrivilegeManager { private admin: DarwinAdmin | Win32Admin | LinuxAdmin; constructor() { switch (platform()) { case 'darwin': this.admin = new DarwinAdmin(); break; case 'win32': this.admin = new Win32Admin(); break; case 'linux': this.admin = new LinuxAdmin(); break; default: throw new Error(`Unsupported platform: ${platform()}`); } } /** * 检查是否已有管理员权限 */ async hasAdminPrivilege(): Promise { return this.admin.hasAdminPrivilege(); } /** * 以管理员权限执行命令 (会弹出密码框) */ async execAsAdmin( command: string, options: PrivilegeOptions ): Promise { // 先检查是否真的需要提权 if (await this.hasAdminPrivilege()) { // 已经是 admin,直接执行 return this.admin.exec(command); } // 显示确认对话框 const confirmed = await this.showConfirmDialog(options.reason); if (!confirmed) { return { success: false, error: 'User cancelled' }; } // 通过平台特定方式获取权限并执行 return this.admin.execWithPrivilege(command, options); } /** * 显示权限请求确认对话框 */ private async showConfirmDialog(reason: string): Promise { const result = await dialog.showMessageBox({ type: 'warning', title: '需要管理员权限', message: 'ClawX 需要管理员权限来完成此操作', detail: `${reason}\n\n点击"继续"后将弹出系统密码输入框。`, buttons: ['继续', '取消'], defaultId: 0, cancelId: 1, }); return result.response === 0; } } export const privilegeManager = new PrivilegeManager(); ``` #### 2.7.4 macOS 实现 (osascript) ```typescript // electron/privilege/darwin-admin.ts import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export class DarwinAdmin { /** * 检查是否以 root 运行 */ async hasAdminPrivilege(): Promise { try { await execAsync('test -w /usr/local/bin'); return true; } catch { return false; } } /** * 直接执行命令 */ async exec(command: string): Promise { try { const { stdout, stderr } = await execAsync(command); return { success: true, stdout, stderr }; } catch (error: any) { return { success: false, error: error.message }; } } /** * 通过 osascript 弹出系统密码框执行命令 * 这会显示 macOS 原生的授权对话框 */ async execWithPrivilege( command: string, options: PrivilegeOptions ): Promise { // 使用 osascript 调用 Authorization Services // 这会弹出 macOS 原生密码输入框 const escapedCommand = command.replace(/"/g, '\\"'); const escapedReason = options.reason.replace(/"/g, '\\"'); const appleScript = ` do shell script "${escapedCommand}" \\ with administrator privileges \\ with prompt "${escapedReason}" `; try { const { stdout, stderr } = await execAsync( `osascript -e '${appleScript.replace(/'/g, "'\"'\"'")}'`, { timeout: options.timeout || 60000 } ); return { success: true, stdout, stderr }; } catch (error: any) { // 用户取消会抛出错误 if (error.message.includes('User canceled')) { return { success: false, error: 'User cancelled' }; } return { success: false, error: error.message }; } } } ``` #### 2.7.5 Windows 实现 (UAC) ```typescript // electron/privilege/win32-admin.ts import { exec, spawn } from 'child_process'; import { promisify } from 'util'; import { writeFileSync, unlinkSync } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; const execAsync = promisify(exec); export class Win32Admin { /** * 检查是否以管理员运行 */ async hasAdminPrivilege(): Promise { try { // 尝试写入 System32,如果成功说明有管理员权限 await execAsync('fsutil dirty query %systemdrive%'); return true; } catch { return false; } } async exec(command: string): Promise { try { const { stdout, stderr } = await execAsync(command, { shell: 'powershell.exe' }); return { success: true, stdout, stderr }; } catch (error: any) { return { success: false, error: error.message }; } } /** * 通过 UAC 提升权限执行命令 * 这会弹出 Windows UAC 确认对话框 */ async execWithPrivilege( command: string, options: PrivilegeOptions ): Promise { return new Promise((resolve) => { // 创建临时 PowerShell 脚本 const scriptPath = join(tmpdir(), `clawx-admin-${Date.now()}.ps1`); const outputPath = join(tmpdir(), `clawx-admin-${Date.now()}.txt`); // PowerShell 脚本内容 const script = ` try { ${command} "SUCCESS" | Out-File -FilePath "${outputPath}" } catch { $_.Exception.Message | Out-File -FilePath "${outputPath}" exit 1 } `; writeFileSync(scriptPath, script, 'utf-8'); // 使用 PowerShell 的 Start-Process 触发 UAC const elevateCommand = ` Start-Process powershell.exe \ -ArgumentList '-ExecutionPolicy Bypass -File "${scriptPath}"' \ -Verb RunAs \ -Wait `; const child = spawn('powershell.exe', ['-Command', elevateCommand], { stdio: 'pipe', }); child.on('close', (code) => { try { const output = require('fs').readFileSync(outputPath, 'utf-8').trim(); unlinkSync(scriptPath); unlinkSync(outputPath); if (output === 'SUCCESS') { resolve({ success: true }); } else { resolve({ success: false, error: output }); } } catch (error: any) { resolve({ success: false, error: 'UAC cancelled or failed' }); } }); }); } } ``` #### 2.7.6 Linux 实现 (pkexec/polkit) ```typescript // electron/privilege/linux-admin.ts import { exec, spawn } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export class LinuxAdmin { async hasAdminPrivilege(): Promise { return process.getuid?.() === 0; } async exec(command: string): Promise { try { const { stdout, stderr } = await execAsync(command); return { success: true, stdout, stderr }; } catch (error: any) { return { success: false, error: error.message }; } } /** * 通过 pkexec 弹出图形化密码框 * pkexec 是 polkit 的一部分,大多数 Linux 桌面都支持 */ async execWithPrivilege( command: string, options: PrivilegeOptions ): Promise { return new Promise((resolve) => { // 检查 pkexec 是否可用 exec('which pkexec', (error) => { if (error) { // 回退到 gksudo 或 kdesudo this.execWithFallback(command, options).then(resolve); return; } // 使用 pkexec (会弹出 polkit 密码框) const child = spawn('pkexec', ['bash', '-c', command], { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, // 设置 polkit 显示的描述 POLKIT_AGENT_HELPER_NAME: 'ClawX', }, }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data; }); child.stderr.on('data', (data) => { stderr += data; }); child.on('close', (code) => { if (code === 0) { resolve({ success: true, stdout, stderr }); } else if (code === 126) { // 用户取消 resolve({ success: false, error: 'User cancelled' }); } else { resolve({ success: false, error: stderr || `Exit code: ${code}` }); } }); }); }); } /** * pkexec 不可用时的回退方案 */ private async execWithFallback( command: string, options: PrivilegeOptions ): Promise { // 尝试 gksudo (GNOME) 或 kdesudo (KDE) const fallbacks = ['gksudo', 'kdesudo', 'sudo -A']; for (const fallback of fallbacks) { try { const { stdout, stderr } = await execAsync(`${fallback} ${command}`); return { success: true, stdout, stderr }; } catch { continue; } } return { success: false, error: 'No GUI sudo helper available. Please run ClawX as root.' }; } } ``` #### 2.7.7 使用示例 ```typescript // electron/installer/node-installer.ts import { privilegeManager } from '../privilege'; export async function installNodeGlobally(): Promise { const isNodeInstalled = await checkNodeInstalled(); if (!isNodeInstalled) { // 通过 GUI 弹窗获取管理员权限安装 Node.js const result = await privilegeManager.execAsAdmin( // macOS: 使用 Homebrew process.platform === 'darwin' ? '/opt/homebrew/bin/brew install node@22' // Windows: 使用 winget : process.platform === 'win32' ? 'winget install OpenJS.NodeJS.LTS' // Linux: 使用包管理器 : 'apt-get install -y nodejs', { reason: '安装 Node.js 运行时环境,这是 ClawX 运行的必要组件。', } ); if (!result.success) { throw new Error(`Node.js 安装失败: ${result.error}`); } } } ``` ### 2.8 GUI 环境变量配置 ClawX 需要管理多种环境变量(API Keys、PATH、代理设置等),必须通过**图形界面**配置,而非要求用户编辑 `.env` 文件或终端。 #### 2.8.1 环境变量分类 | 类型 | 示例 | 存储位置 | 安全级别 | |------|------|----------|----------| | **API Keys** | `ANTHROPIC_API_KEY` | 系统密钥链 | 🔐 加密存储 | | **PATH** | Node.js/npm 路径 | Shell Profile | 普通 | | **代理** | `HTTP_PROXY` | 应用配置 | 普通 | | **应用配置** | `CLAWX_PORT` | 应用配置 | 普通 | #### 2.8.2 环境配置管理器 ```typescript // electron/env-config/index.ts import { safeStorage, app } from 'electron'; import Store from 'electron-store'; import { PathManager } from './path-manager'; import { ApiKeyManager } from './api-keys'; import { ShellProfileManager } from './shell-profile'; export interface EnvConfig { // API Keys (加密存储) apiKeys: { anthropic?: string; openai?: string; google?: string; [key: string]: string | undefined; }; // 代理设置 proxy: { http?: string; https?: string; noProxy?: string[]; }; // 应用配置 app: { port?: number; logLevel?: string; dataDir?: string; }; } export class EnvConfigManager { private store: Store; private pathManager: PathManager; private apiKeyManager: ApiKeyManager; private shellProfileManager: ShellProfileManager; constructor() { this.store = new Store({ name: 'env-config' }); this.pathManager = new PathManager(); this.apiKeyManager = new ApiKeyManager(); this.shellProfileManager = new ShellProfileManager(); } /** * 获取所有环境配置 (API Keys 脱敏) */ getConfig(): EnvConfig { return { apiKeys: this.apiKeyManager.getMaskedKeys(), proxy: this.store.get('proxy', {}) as EnvConfig['proxy'], app: this.store.get('app', {}) as EnvConfig['app'], }; } /** * 设置 API Key (自动加密存储) */ async setApiKey(provider: string, key: string): Promise { await this.apiKeyManager.setKey(provider, key); // 同步到 OpenClaw 配置 await this.syncToOpenClaw(); } /** * 设置代理 */ setProxy(proxy: EnvConfig['proxy']): void { this.store.set('proxy', proxy); this.applyProxy(proxy); } /** * 确保 PATH 包含必要的路径 */ async ensurePath(): Promise { const requiredPaths = [ '/usr/local/bin', '/opt/homebrew/bin', // macOS ARM `${app.getPath('home')}/.nvm/versions/node/*/bin`, // nvm `${app.getPath('home')}/.local/bin`, // pip install --user ]; for (const p of requiredPaths) { await this.pathManager.addToPath(p); } } /** * 同步配置到 OpenClaw */ private async syncToOpenClaw(): Promise { const openclawConfigPath = `${app.getPath('home')}/.openclaw/config.json`; // 读取现有配置,合并 API keys,写回 // ... } private applyProxy(proxy: EnvConfig['proxy']): void { if (proxy.http) process.env.HTTP_PROXY = proxy.http; if (proxy.https) process.env.HTTPS_PROXY = proxy.https; if (proxy.noProxy) process.env.NO_PROXY = proxy.noProxy.join(','); } } export const envConfigManager = new EnvConfigManager(); ``` #### 2.8.3 API Key 安全存储 ```typescript // electron/env-config/api-keys.ts import { safeStorage } from 'electron'; import Store from 'electron-store'; import keytar from 'keytar'; const SERVICE_NAME = 'ClawX'; /** * API Key 管理器 * 使用系统密钥链安全存储敏感信息 */ export class ApiKeyManager { private store: Store; constructor() { this.store = new Store({ name: 'api-keys-meta' }); } /** * 安全存储 API Key * - macOS: 使用 Keychain * - Windows: 使用 Credential Manager * - Linux: 使用 libsecret (GNOME Keyring / KWallet) */ async setKey(provider: string, key: string): Promise { try { // 首选: 系统密钥链 (通过 keytar) await keytar.setPassword(SERVICE_NAME, provider, key); this.store.set(`providers.${provider}`, { stored: true, method: 'keychain' }); } catch { // 回退: Electron safeStorage (本地加密) if (safeStorage.isEncryptionAvailable()) { const encrypted = safeStorage.encryptString(key); this.store.set(`providers.${provider}`, { stored: true, method: 'safeStorage', encrypted: encrypted.toString('base64'), }); } else { throw new Error('No secure storage available'); } } } /** * 获取 API Key */ async getKey(provider: string): Promise { const meta = this.store.get(`providers.${provider}`) as any; if (!meta?.stored) return null; if (meta.method === 'keychain') { return keytar.getPassword(SERVICE_NAME, provider); } else if (meta.method === 'safeStorage' && meta.encrypted) { const buffer = Buffer.from(meta.encrypted, 'base64'); return safeStorage.decryptString(buffer); } return null; } /** * 删除 API Key */ async deleteKey(provider: string): Promise { const meta = this.store.get(`providers.${provider}`) as any; if (!meta?.stored) return; if (meta.method === 'keychain') { await keytar.deletePassword(SERVICE_NAME, provider); } this.store.delete(`providers.${provider}`); } /** * 获取所有已配置的 Key (脱敏显示) */ getMaskedKeys(): Record { const result: Record = {}; const providers = this.store.get('providers', {}) as Record; for (const [provider, meta] of Object.entries(providers)) { if (meta?.stored) { result[provider] = '••••••••••••'; // 脱敏显示 } } return result; } /** * 检查 Key 是否已配置 */ hasKey(provider: string): boolean { const meta = this.store.get(`providers.${provider}`) as any; return meta?.stored === true; } } ``` #### 2.8.4 PATH 环境变量管理 ```typescript // electron/env-config/path-manager.ts import { exec } from 'child_process'; import { promisify } from 'util'; import { existsSync, writeFileSync, readFileSync } from 'fs'; import { join } from 'path'; import { homedir, platform } from 'os'; import { privilegeManager } from '../privilege'; const execAsync = promisify(exec); export class PathManager { /** * 添加路径到 PATH 环境变量 * 通过 GUI 完成,用户无需手动编辑 shell 配置文件 */ async addToPath(newPath: string): Promise { // 检查路径是否已存在 const currentPath = process.env.PATH || ''; if (currentPath.includes(newPath)) return; // 检查路径是否有效 if (!existsSync(newPath) && !newPath.includes('*')) return; switch (platform()) { case 'darwin': await this.addToPathMacOS(newPath); break; case 'win32': await this.addToPathWindows(newPath); break; case 'linux': await this.addToPathLinux(newPath); break; } } /** * macOS: 添加到 /etc/paths.d/ (系统级) 或 shell profile (用户级) */ private async addToPathMacOS(newPath: string): Promise { // 优先使用用户级配置 (~/.zshrc 或 ~/.bash_profile) const shellProfile = this.getShellProfile(); if (shellProfile) { const content = existsSync(shellProfile) ? readFileSync(shellProfile, 'utf-8') : ''; if (!content.includes(newPath)) { const exportLine = `\nexport PATH="${newPath}:$PATH"\n`; writeFileSync(shellProfile, content + exportLine); } } else { // 回退到系统级 (需要管理员权限) const pathFile = `/etc/paths.d/clawx`; await privilegeManager.execAsAdmin( `echo "${newPath}" >> ${pathFile}`, { reason: '配置系统 PATH 环境变量' } ); } } /** * Windows: 修改用户环境变量 (通过注册表) */ private async addToPathWindows(newPath: string): Promise { // 使用 PowerShell 修改用户级 PATH (无需管理员权限) const command = ` $currentPath = [Environment]::GetEnvironmentVariable("Path", "User") if ($currentPath -notlike "*${newPath}*") { [Environment]::SetEnvironmentVariable("Path", "${newPath};$currentPath", "User") } `; await execAsync(`powershell -Command "${command}"`); // 通知系统环境变量已更改 await execAsync('setx CLAWX_PATH_UPDATED 1'); } /** * Linux: 添加到 shell profile */ private async addToPathLinux(newPath: string): Promise { const shellProfile = this.getShellProfile(); if (shellProfile) { const content = existsSync(shellProfile) ? readFileSync(shellProfile, 'utf-8') : ''; if (!content.includes(newPath)) { const exportLine = `\nexport PATH="${newPath}:$PATH"\n`; writeFileSync(shellProfile, content + exportLine); } } } /** * 获取当前用户的 shell profile 文件 */ private getShellProfile(): string | null { const home = homedir(); const shell = process.env.SHELL || ''; if (shell.includes('zsh')) { return join(home, '.zshrc'); } else if (shell.includes('bash')) { const bashProfile = join(home, '.bash_profile'); const bashrc = join(home, '.bashrc'); return existsSync(bashProfile) ? bashProfile : bashrc; } else if (shell.includes('fish')) { return join(home, '.config/fish/config.fish'); } // 默认 return join(home, '.profile'); } } ``` #### 2.8.5 设置页面 UI ```typescript // src/pages/Settings/ProviderSettings.tsx import { useState, useEffect } from 'react'; import { Eye, EyeOff, Check, AlertCircle } from 'lucide-react'; interface ApiKeyInputProps { provider: string; label: string; placeholder: string; hasKey: boolean; onSave: (key: string) => Promise; onDelete: () => Promise; } function ApiKeyInput({ provider, label, placeholder, hasKey, onSave, onDelete }: ApiKeyInputProps) { const [value, setValue] = useState(''); const [showKey, setShowKey] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const handleSave = async () => { if (!value.trim()) return; setSaving(true); setError(null); try { await onSave(value); setValue(''); // 清空输入,显示"已配置"状态 } catch (e: any) { setError(e.message); } finally { setSaving(false); } }; return (
{hasKey ? ( // 已配置状态
已配置 ••••••••••••
) : ( // 未配置状态
setValue(e.target.value)} placeholder={placeholder} className="pr-10" />
)} {error && (
{error}
)}

API Key 将安全存储在系统密钥链中,不会以明文保存。

); } export function ProviderSettings() { const [config, setConfig] = useState(null); useEffect(() => { // 从主进程获取配置 window.electron.ipcRenderer.invoke('env:getConfig').then(setConfig); }, []); const handleSaveApiKey = async (provider: string, key: string) => { await window.electron.ipcRenderer.invoke('env:setApiKey', provider, key); // 刷新配置 const newConfig = await window.electron.ipcRenderer.invoke('env:getConfig'); setConfig(newConfig); }; const handleDeleteApiKey = async (provider: string) => { await window.electron.ipcRenderer.invoke('env:deleteApiKey', provider); const newConfig = await window.electron.ipcRenderer.invoke('env:getConfig'); setConfig(newConfig); }; if (!config) return ; return ( AI 模型配置 配置您的 AI 服务提供商 API Key handleSaveApiKey('anthropic', key)} onDelete={() => handleDeleteApiKey('anthropic')} /> handleSaveApiKey('openai', key)} onDelete={() => handleDeleteApiKey('openai')} /> handleSaveApiKey('google', key)} onDelete={() => handleDeleteApiKey('google')} /> ); } ``` #### 2.8.6 安装向导集成 ```typescript // src/pages/Setup/ProviderStep.tsx export function ProviderStep({ onNext, onBack }: StepProps) { const [selectedProvider, setSelectedProvider] = useState('anthropic'); const [apiKey, setApiKey] = useState(''); const [validating, setValidating] = useState(false); const [error, setError] = useState(null); const providers = [ { id: 'anthropic', name: 'Anthropic', model: 'Claude', icon: '🤖' }, { id: 'openai', name: 'OpenAI', model: 'GPT-4', icon: '💚' }, { id: 'google', name: 'Google', model: 'Gemini', icon: '🔷' }, ]; const handleNext = async () => { if (!apiKey.trim()) { setError('请输入 API Key'); return; } setValidating(true); setError(null); try { // 验证 API Key 是否有效 const valid = await window.electron.ipcRenderer.invoke( 'provider:validateKey', selectedProvider, apiKey ); if (!valid) { setError('API Key 无效,请检查后重试'); return; } // 安全存储 API Key await window.electron.ipcRenderer.invoke( 'env:setApiKey', selectedProvider, apiKey ); onNext({ provider: selectedProvider }); } catch (e: any) { setError(e.message); } finally { setValidating(false); } }; return (
{providers.map((p) => ( setSelectedProvider(p.id)} >
{p.icon}

{p.name}

{p.model}

))}
setApiKey(e.target.value)} placeholder={`输入您的 ${providers.find(p => p.id === selectedProvider)?.name} API Key`} /> {error && (

{error}

)}

您的 API Key 将安全存储在系统密钥链中,不会上传到任何服务器。

); } ``` --- ## 三、UI/UX 设计规范 ### 3.1 设计原则 | 原则 | 描述 | |------|------| | **简洁优先** | 隐藏复杂性,暴露必要功能 | | **渐进式披露** | 基础功能易达,高级功能可探索 | | **一致性** | 跨页面/组件保持视觉和交互一致 | | **响应式反馈** | 所有操作有即时视觉反馈 | | **原生感** | 遵循各平台设计语言 | ### 3.2 色彩系统 ```css /* src/styles/themes/tokens.css */ :root { /* Primary */ --color-primary-50: #eff6ff; --color-primary-500: #3b82f6; --color-primary-600: #2563eb; /* Semantic */ --color-success: #22c55e; --color-warning: #f59e0b; --color-error: #ef4444; --color-info: #3b82f6; /* Neutral (Light) */ --color-bg-primary: #ffffff; --color-bg-secondary: #f8fafc; --color-text-primary: #0f172a; --color-text-secondary: #64748b; --color-border: #e2e8f0; } [data-theme="dark"] { --color-bg-primary: #0f172a; --color-bg-secondary: #1e293b; --color-text-primary: #f8fafc; --color-text-secondary: #94a3b8; --color-border: #334155; } ``` ### 3.3 核心页面线框图 ``` ┌─────────────────────────────────────────────────────────┐ │ ClawX ─ □ × │ ├──────────┬──────────────────────────────────────────────┤ │ │ │ │ ● 概览 │ ┌─────────────┐ ┌─────────────┐ │ │ ○ 对话 │ │ Gateway │ │ Channels │ │ │ ○ 通道 │ │ ● 运行中 │ │ 3 已连接 │ │ │ ○ 技能 │ └─────────────┘ └─────────────┘ │ │ ○ 设置 │ │ │ ○ 定时任务│ ┌───────────────────────────────────────┐ │ │ │ │ 最近对话 │ │ │ │ ├───────────────────────────────────────┤ │ │ │ │ 📱 WhatsApp · 用户A · 2分钟前 │ │ │ │ │ 💬 Telegram · 用户B · 15分钟前 │ │ │ │ │ 💬 Discord · 用户C · 1小时前 │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ 快捷操作 │ │ │ │ │ [添加通道] [浏览技能] [查看日志] │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ ├──────────┴──────────────────────────────────────────────┤ │ Gateway: 运行中 · 3 通道 · 12 技能 · v2026.2.3 │ └─────────────────────────────────────────────────────────┘ ``` ### 3.4 定时任务页面设计 ``` ┌─────────────────────────────────────────────────────────┐ │ ClawX ─ □ × │ ├──────────┬──────────────────────────────────────────────┤ │ │ │ │ ○ 概览 │ 定时任务 [+ 新建任务] │ │ ○ 对话 │ │ │ ○ 通道 │ ┌───────────────────────────────────────┐ │ │ ○ 技能 │ │ 📋 每日天气播报 ● 已启用 │ │ │ ● 定时 │ │ ⏰ 每天 08:00 │ │ │ ○ 设置 │ │ 📍 发送到: WhatsApp - 家庭群 │ │ │ │ │ 上次执行: 今天 08:00 ✓ │ │ │ │ │ [编辑] [暂停] [删除] │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ 📊 周报汇总 ○ 已暂停 │ │ │ │ │ ⏰ 每周五 18:00 │ │ │ │ │ 📍 发送到: Telegram - 工作频道 │ │ │ │ │ 上次执行: 上周五 18:00 ✓ │ │ │ │ │ [编辑] [启用] [删除] │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ 🔔 服务器健康检查 ● 已启用 │ │ │ │ │ ⏰ 每 30 分钟 │ │ │ │ │ 📍 发送到: Discord - 运维通知 │ │ │ │ │ 上次执行: 10分钟前 ✓ │ │ │ │ │ [编辑] [暂停] [删除] │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ ├──────────┴──────────────────────────────────────────────┤ │ 3 个任务 · 2 个运行中 · 1 个暂停 │ └─────────────────────────────────────────────────────────┘ ``` #### 定时任务编辑器弹窗 ``` ┌─────────────────────────────────────────────────────────┐ │ 新建定时任务 [×] │ ├─────────────────────────────────────────────────────────┤ │ │ │ 任务名称 │ │ ┌─────────────────────────────────────────────────┐ │ │ │ 每日天气播报 │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 执行内容 (发送给 AI 的消息) │ │ ┌─────────────────────────────────────────────────┐ │ │ │ 请查询北京今天的天气,并生成一条适合发送到群里 │ │ │ │ 的天气播报消息,包含穿衣建议。 │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 执行时间 │ │ ┌──────────────┐ ┌────────────────────────────┐ │ │ │ ○ 每天 │ │ 08 : 00 │ │ │ │ ○ 每周 │ └────────────────────────────┘ │ │ │ ○ 每月 │ │ │ │ ○ 自定义Cron │ Cron: 0 8 * * * │ │ └──────────────┘ │ │ │ │ 发送到 │ │ ┌─────────────────────────────────────────────────┐ │ │ │ WhatsApp - 家庭群 ▼ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ ☑ 启用任务 │ │ │ │ [取消] [保存] │ └─────────────────────────────────────────────────────────┘ ``` #### 定时任务类型定义 ```typescript // src/types/cron.ts export interface CronJob { /** 任务 ID */ id: string; /** 任务名称 */ name: string; /** 发送给 AI 的消息内容 */ message: string; /** Cron 表达式 */ schedule: string; /** 目标通道 */ target: { channelType: 'whatsapp' | 'telegram' | 'discord' | 'slack'; channelId: string; channelName: string; }; /** 是否启用 */ enabled: boolean; /** 创建时间 */ createdAt: string; /** 更新时间 */ updatedAt: string; /** 上次执行信息 */ lastRun?: { time: string; success: boolean; error?: string; }; /** 下次执行时间 */ nextRun?: string; } export interface CronJobCreateInput { name: string; message: string; schedule: string; target: CronJob['target']; enabled?: boolean; } export interface CronJobUpdateInput { name?: string; message?: string; schedule?: string; target?: CronJob['target']; enabled?: boolean; } ``` #### 定时任务 Hook ```typescript // src/hooks/useCron.ts import { useState, useEffect, useCallback } from 'react'; import type { CronJob, CronJobCreateInput, CronJobUpdateInput } from '@/types/cron'; export function useCron() { const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // 加载任务列表 const fetchJobs = useCallback(async () => { try { setLoading(true); const result = await window.electron.ipcRenderer.invoke('cron:list'); setJobs(result); setError(null); } catch (e: any) { setError(e.message); } finally { setLoading(false); } }, []); useEffect(() => { fetchJobs(); // 监听任务更新事件 const handleUpdate = (_: any, updatedJobs: CronJob[]) => { setJobs(updatedJobs); }; window.electron.ipcRenderer.on('cron:updated', handleUpdate); return () => { window.electron.ipcRenderer.off('cron:updated', handleUpdate); }; }, [fetchJobs]); // 创建任务 const createJob = async (input: CronJobCreateInput): Promise => { const job = await window.electron.ipcRenderer.invoke('cron:create', input); await fetchJobs(); return job; }; // 更新任务 const updateJob = async (id: string, input: CronJobUpdateInput): Promise => { await window.electron.ipcRenderer.invoke('cron:update', id, input); await fetchJobs(); }; // 删除任务 const deleteJob = async (id: string): Promise => { await window.electron.ipcRenderer.invoke('cron:delete', id); await fetchJobs(); }; // 启用/禁用任务 const toggleJob = async (id: string, enabled: boolean): Promise => { await window.electron.ipcRenderer.invoke('cron:toggle', id, enabled); await fetchJobs(); }; // 手动触发任务 const triggerJob = async (id: string): Promise => { await window.electron.ipcRenderer.invoke('cron:trigger', id); }; return { jobs, loading, error, createJob, updateJob, deleteJob, toggleJob, triggerJob, refresh: fetchJobs, }; } ``` #### 定时任务页面组件 ```typescript // src/pages/Cron/index.tsx import { useState } from 'react'; import { useCron } from '@/hooks/useCron'; import { CronJobCard } from './CronJobCard'; import { CronEditor } from './CronEditor'; import { Plus, Clock } from 'lucide-react'; export function CronPage() { const { jobs, loading, createJob, updateJob, deleteJob, toggleJob } = useCron(); const [editorOpen, setEditorOpen] = useState(false); const [editingJob, setEditingJob] = useState(null); const activeJobs = jobs.filter(j => j.enabled); const pausedJobs = jobs.filter(j => !j.enabled); const handleCreate = () => { setEditingJob(null); setEditorOpen(true); }; const handleEdit = (job: CronJob) => { setEditingJob(job); setEditorOpen(true); }; const handleSave = async (input: CronJobCreateInput) => { if (editingJob) { await updateJob(editingJob.id, input); } else { await createJob(input); } setEditorOpen(false); }; if (loading) return ; return (
{/* 页头 */}

定时任务

设置自动执行的 AI 任务,按计划发送消息

{/* 统计卡片 */}

{jobs.length}

总任务数

{activeJobs.length}

运行中

{pausedJobs.length}

已暂停

{/* 任务列表 */} {jobs.length === 0 ? (

暂无定时任务

创建您的第一个定时任务,让 AI 自动为您工作

) : (
{jobs.map(job => ( handleEdit(job)} onDelete={() => deleteJob(job.id)} onToggle={(enabled) => toggleJob(job.id, enabled)} /> ))}
)} {/* 编辑器弹窗 */} setEditorOpen(false)} onSave={handleSave} initialData={editingJob} />
); } ``` #### Cron 表达式选择器 ```typescript // src/pages/Cron/CronSchedulePicker.tsx import { useState, useEffect } from 'react'; interface CronSchedulePickerProps { value: string; onChange: (cron: string) => void; } type ScheduleType = 'daily' | 'weekly' | 'monthly' | 'interval' | 'custom'; export function CronSchedulePicker({ value, onChange }: CronSchedulePickerProps) { const [type, setType] = useState('daily'); const [time, setTime] = useState('08:00'); const [weekday, setWeekday] = useState(1); // 周一 const [monthday, setMonthday] = useState(1); const [interval, setInterval] = useState(30); // 分钟 const [customCron, setCustomCron] = useState(value); // 解析现有 cron 表达式 useEffect(() => { // 简单解析,实际可用 cron-parser 库 if (value.match(/^\d+ \d+ \* \* \*$/)) { setType('daily'); const [min, hour] = value.split(' '); setTime(`${hour.padStart(2, '0')}:${min.padStart(2, '0')}`); } // ... 其他模式解析 }, []); // 生成 cron 表达式 useEffect(() => { let cron = ''; const [hour, min] = time.split(':'); switch (type) { case 'daily': cron = `${parseInt(min)} ${parseInt(hour)} * * *`; break; case 'weekly': cron = `${parseInt(min)} ${parseInt(hour)} * * ${weekday}`; break; case 'monthly': cron = `${parseInt(min)} ${parseInt(hour)} ${monthday} * *`; break; case 'interval': cron = `*/${interval} * * * *`; break; case 'custom': cron = customCron; break; } if (cron !== value) { onChange(cron); } }, [type, time, weekday, monthday, interval, customCron]); const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; return (
{/* 类型选择 */} setType(v as ScheduleType)}>
{/* 时间选择 */} {(type === 'daily' || type === 'weekly' || type === 'monthly') && (
setTime(e.target.value)} className="w-32" />
)} {/* 周几选择 */} {type === 'weekly' && (
)} {/* 日期选择 */} {type === 'monthly' && (
)} {/* 间隔选择 */} {type === 'interval' && (
setInterval(parseInt(e.target.value))} className="w-20" /> 分钟
)} {/* 自定义 Cron */} {type === 'custom' && (
setCustomCron(e.target.value)} placeholder="分 时 日 月 周 (如: 0 8 * * 1-5)" />

Cron 表达式格式: 分钟(0-59) 小时(0-23) 日(1-31) 月(1-12) 周(0-6)

)} {/* 预览 */}

Cron 表达式: {value}

下次执行: {getNextRunTime(value)}

); } function getNextRunTime(cron: string): string { // 使用 croner 或类似库计算下次执行时间 // 这里简化处理 return '今天 08:00'; } ``` --- ## 四、版本规划 ### 4.1 版本号策略 ``` ClawX 版本: X.Y.Z[-prerelease] │ │ │ │ │ └── Patch: Bug 修复、性能优化 │ └──── Minor: 新功能、新技能包、UI 改进 └────── Major: 重大变更、不兼容更新 示例: - 1.0.0 首个稳定版 - 1.1.0 新增技能市场功能 - 1.1.1 修复 Windows 安装问题 - 2.0.0 UI 重构、新架构 - 1.2.0-beta.1 下一版本测试版 ``` ### 4.2 OpenClaw 兼容性矩阵 | ClawX 版本 | OpenClaw 版本 | Node.js 版本 | 备注 | |------------|---------------|--------------|------| | 1.0.x | 2026.2.x | 22.x | 首发版本 | | 1.1.x | 2026.3.x | 22.x | 功能增强 | | 2.0.x | 2026.6.x | 24.x | 大版本升级 | ### 4.3 里程碑规划 #### 🚀 v0.1.0 - Alpha (内部测试) **目标**: 核心架构验证 | 任务 | 优先级 | 状态 | |------|--------|------| | Electron + React 项目骨架 | P0 | ⬜ | | Gateway 进程管理 | P0 | ⬜ | | 基础 UI 框架 (侧边栏/布局) | P0 | ⬜ | | WebSocket 通信层 | P0 | ⬜ | | 状态管理 (Zustand) | P1 | ⬜ | **交付物**: - 可运行的桌面应用 - 能启动/停止 Gateway - 基础 Dashboard 页面 --- #### 🎯 v0.5.0 - Beta (公开测试) **目标**: 安装向导 MVP | 任务 | 优先级 | 状态 | |------|--------|------| | 安装向导 UI | P0 | ⬜ | | Node.js 自动检测/安装 | P0 | ⬜ | | openclaw npm 安装 | P0 | ⬜ | | Provider 配置 (API Key) | P0 | ⬜ | | 首个通道连接 (WhatsApp QR) | P1 | ⬜ | | 错误处理与提示 | P1 | ⬜ | **交付物**: - 完整安装向导流程 - 支持 macOS (Apple Silicon + Intel) - 可配置 Anthropic/OpenAI --- #### 📦 v1.0.0 - Stable (首个正式版) **目标**: 生产可用 | 任务 | 优先级 | 状态 | |------|--------|------| | 完整 Dashboard | P0 | ⬜ | | 通道管理页面 | P0 | ⬜ | | 对话界面 | P0 | ⬜ | | 技能浏览/启用 | P0 | ⬜ | | 设置页面 | P0 | ⬜ | | 预装技能包选择 | P1 | ⬜ | | 自动更新 (Sparkle/electron-updater) | P1 | ⬜ | | Windows 支持 | P1 | ⬜ | | 深色模式 | P2 | ⬜ | | 崩溃报告 | P2 | ⬜ | **交付物**: - macOS + Windows 安装包 - 自动更新能力 - 用户文档 --- #### 🌟 v1.1.0 - 功能增强 **目标**: 技能生态 | 任务 | 优先级 | 状态 | |------|--------|------| | 技能市场 UI | P0 | ⬜ | | 在线技能安装 | P0 | ⬜ | | 技能配置界面 | P1 | ⬜ | | 技能使用统计 | P2 | ⬜ | | Linux 支持 | P2 | ⬜ | --- #### 🚀 v2.0.0 - 重大升级 **目标**: 多 Agent / 高级功能 | 任务 | 优先级 | 状态 | |------|--------|------| | 多 Agent 支持 | P0 | ⬜ | | 工作流编排 | P1 | ⬜ | | 插件 SDK | P1 | ⬜ | | 自定义主题 | P2 | ⬜ | | 性能监控面板 | P2 | ⬜ | --- ## 五、开发规范 ### 5.1 代码风格 ```typescript // ✅ 命名约定 const MY_CONSTANT = 'value'; // 常量: SCREAMING_SNAKE_CASE function getUserData() {} // 函数: camelCase class GatewayManager {} // 类: PascalCase interface ChannelConfig {} // 接口: PascalCase type StatusType = 'running'; // 类型: PascalCase // ✅ 文件命名 // 组件: PascalCase.tsx // Dashboard.tsx, SkillCard.tsx // 工具/hooks: kebab-case.ts // gateway-manager.ts, use-gateway.ts // ✅ 目录命名 // kebab-case // src/pages/skill-market/ // ✅ React 组件 export function SkillCard({ skill, onSelect }: SkillCardProps) { // hooks 在顶部 const [loading, setLoading] = useState(false); // handlers const handleClick = () => { /* ... */ }; // render return ( {/* ... */} ); } ``` ### 5.2 Git 提交规范 ``` ():