1. 项目概述:一个本地优先的OpenClaw技能仪表盘

如果你和我一样,是OpenClaw的深度用户,那你肯定经历过这样的场景:想找一个特定功能的技能,却不得不在终端里反复敲 openclaw skills list ,然后在一堆路径里翻找 SKILL.md 文件,只为搞清楚这个技能到底能干什么、需要什么配置。或者更糟,你从社区下载了一个酷炫的新技能,扔进了自定义文件夹,结果在OpenClaw里死活调用不了,你只能像个考古学家一样,在文件系统的不同层级里“挖坟”,试图找出是依赖没装,还是配置文件写错了。

这正是我开发 OpenClaw Skill Viewer 的初衷。这不是一个云端服务,也不是一个复杂的技能管理平台。它就是一个纯粹的、 本地优先 的Web仪表盘,目标只有一个:把你机器上所有OpenClaw技能(无论是OpenClaw自带的、工作区内的,还是你个人收藏的)都清晰地、可视化地展现在你面前。你可以一眼看出哪些技能“就绪可用”,哪些还“缺胳膊少腿”,并且能直接点进去,像浏览一个微型代码仓库一样,查看技能的所有文件和说明文档。

简单说,它解决的核心痛点是 “技能发现与状态可视化” 的最后一公里问题。OpenClaw本身提供了强大的CLI和运行时,但缺乏一个友好的界面来管理你本地日益增长的技能库。这个工具就是来补上这块短板的,它面向所有OpenClaw的开发者、研究者和高级用户,特别是那些需要频繁切换、调试、评估不同技能的人。

2. 核心设计思路:为什么是“本地优先”与“只读视图”

在动手编码之前,我花了很长时间思考这个工具的定位。市面上已经有一些AI Agent的“应用商店”或“技能市场”概念,但我的需求更朴素:我只想管好我自己电脑上的这一亩三分地。因此,我确立了几个核心设计原则,这些原则也决定了项目的技术选型和功能边界。

2.1 坚守“本地优先”哲学

“本地优先”意味着所有数据都来自你的本地文件系统和本地OpenClaw CLI。工具不连接任何外部服务器,不上传你的技能代码,也不依赖任何在线服务。这样做有几个关键好处:

  • 隐私与安全 :你的技能,尤其是那些包含自定义逻辑、API密钥或内部工具调用的技能,永远不需要离开你的机器。这对于处理敏感信息或企业内部工具的开发至关重要。
  • 离线可用 :只要你的Node.js环境和OpenClaw CLI是正常的,这个仪表盘就能工作。你可以在飞机上、在没有网络的环境里,安心地浏览和规划你的技能栈。
  • 极速响应 :所有操作(读取文件、检查状态)都在本地完成,延迟几乎为零。点击一个技能,其详情和文件树是瞬间加载的,体验非常流畅。

这个原则直接影响了架构。后端(一个轻量级Express服务器)的唯一职责就是作为本地文件系统和前端React应用之间的桥梁。它调用本地的 openclaw CLI来获取技能状态,使用Node.js的 fs 模块读取文件,并通过 chokidar 库监听文件变化。整个数据流是一个闭环。

2.2 明确“只读视图”的边界

我刻意将这个工具设计成一个 “查看器(Viewer)” ,而非“编辑器(Editor)”或“管理器(Manager)”。它不会让你在网页里直接修改 skill.json SKILL.md ,也不会提供一键发布到技能市场的功能。

为什么这么“保守”?原因有三:

  1. 专注核心价值 :编辑和管理是另一套复杂得多的交互和权限体系。一旦引入编辑功能,就需要处理文件锁、冲突解决、撤销重做等一系列问题,这会迅速让项目变得臃肿,偏离“快速浏览和诊断”的初衷。
  2. 尊重现有工作流 :开发者最习惯的编辑环境是VS Code、Vim或其它IDE。强行在浏览器里做一个半吊子编辑器,体验远不如直接打开熟悉的编辑器。因此,我在技能详情页提供了清晰的路径,你可以一键在终端或文件管理器中打开该目录,回归你最舒适的工作流。
  3. 降低复杂度与风险 :“只读”意味着更安全。你不用担心误操作删除了某个关键文件,或者因为网页应用的bug导致技能配置被破坏。它就是一个安全的“镜子”,只反映现状,不改变现状。

这个边界让项目保持小巧、锐利,也更容易维护。它的目标不是取代你的开发工具链,而是增强你对工具链中“技能”这一环的感知和控制力。

2.3 多源技能发现的策略

OpenClaw的技能可以存在于多个位置,这是其灵活性的体现,但也带来了管理的混乱。我的工具需要智能地发现并整合这些来源。默认策略覆盖了最常见的三种根目录:

  1. 内置技能根目录 :通常位于 ~/.nvm/versions/node/<version>/lib/node_modules/openclaw/skills 。这里存放着随OpenClaw一起安装的核心技能包。
  2. 工作区技能根目录 :通常位于 ~/.openclaw/workspace/skills 。这是为特定工作区或项目配置的技能,具有较高的优先级。
  3. 其他/自定义技能根目录 :我将其默认指向 ~/.agents/skills ,这是一个常见的用于存放用户自定义或从社区下载技能的位置。

实现上,后端启动时会依次扫描这些配置的路径。对于每个路径,它会递归查找包含 skill.json 文件的目录,并将其识别为一个独立的技能。每个技能都会被赋予一个唯一的ID(通常是其文件夹名的slug化版本),并记录其来源( builtin workspace other )。

注意 :这个默认路径是基于典型的Node.js全局安装模式(通过nvm管理)设定的。如果你通过Docker、全局npm(无nvm)或其它方式安装OpenClaw,内置技能的路径可能会不同。别担心,这正是提供环境变量覆盖功能的原因。

3. 关键技术实现与实操要点

理解了设计思路,我们深入到代码层面,看看几个关键功能是如何实现的,以及在实际操作中需要注意哪些细节。

3.1 技能“就绪状态”的动态检测

仪表盘最核心的功能之一,就是用红绿标签清晰标示出技能是否“就绪(Ready)”。这个状态不是猜的,而是通过查询本地OpenClaw CLI的权威状态得来的。

在后端,我实现了一个 getSkillStatus 函数。它的工作流程如下:

  1. 系统启动或定时触发时,会执行命令 openclaw skills list --json
  2. 解析这个JSON输出,它会返回一个技能ID到其详细状态的映射。一个“就绪”的技能通常意味着它的所有依赖已安装,配置完整,并且通过了CLI的预检。
  3. 将这个映射表缓存在内存中。当前端请求技能列表时,后端会将文件系统扫描到的技能元数据(名称、路径、描述)与这个状态缓存进行匹配,为每个技能附加一个 isReady 布尔值。
// 示例:后端状态获取与匹配的逻辑伪代码
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);

async function fetchOpenClawSkillStatus() {
  try {
    const { stdout } = await execAsync('openclaw skills list --json');
    const statusList = JSON.parse(stdout);
    // 转换为 { [skillId]: { status: 'ready' | 'error', ... } } 的映射
    return statusList.reduce((map, skill) => {
      map[skill.id] = skill;
      return map;
    }, {});
  } catch (error) {
    console.error('Failed to fetch skill status from OpenClaw CLI:', error);
    return {}; // 返回空映射,前端将显示为未知状态
  }
}

实操要点与避坑

  • CLI路径 :确保 openclaw 命令在你的服务器进程的 PATH 环境变量中。如果你是在VS Code的集成终端里运行 npm run dev ,但通过系统服务启动生产服务器,可能会遇到 command not found 的错误。在生产部署时,可能需要使用绝对路径或显式设置 PATH
  • 状态缓存与更新 :频繁执行CLI命令会有性能开销。因此我设置了合理的缓存时间(例如30秒),并配合文件监听,在技能文件发生变化时主动刷新相关技能的状态。你需要根据自己对状态实时性的要求来调整这个缓存策略。
  • 优雅降级 :如果CLI命令执行失败(例如OpenClaw服务未启动),后端会捕获这个异常,并返回一个降级的状态——将所有技能的“就绪”状态标记为“未知”或“检查失败”。前端UI会相应地显示为灰色或警告图标,而不是直接崩溃,这保证了工具的鲁棒性。

3.2 基于文件监听的实时刷新

为了让仪表盘感觉像一个“活”的应用,而不是一个静态页面,我实现了实时刷新功能。当你在外部的IDE里修改了某个技能的 SKILL.md 文件,或者通过CLI安装了一个新技能,仪表盘应该能几乎实时地反映这个变化。

这得益于 chokidar 这个强大的Node.js文件监听库。后端在启动时,会对所有配置的技能根目录建立一个监听器:

const chokidar = require('chokidar');

// 监听所有技能根目录
const watcher = chokidar.watch(skillRoots, {
  ignored: /(^|[\/\\])\../, // 忽略隐藏文件
  persistent: true,
  depth: 2, // 适当深度,避免监听整个node_modules
});

watcher
  .on('add', (path) => handleFileChange('add', path))
  .on('change', (path) => handleFileChange('change', path))
  .on('unlink', (path) => handleFileChange('unlink', path))
  .on('addDir', (path) => handleSkillDirChange('addDir', path))
  .on('unlinkDir', (path) => handleSkillDirChange('unlinkDir', path));

当文件变化事件被触发时,后端会做两件事:

  1. 更新内部技能列表缓存 :分析变化的路径,判断它属于哪个技能,然后更新该技能在内存中的元数据(例如,更新 SKILL.md 的解析内容)。
  2. 通过Server-Sent Events推送通知 :后端维护着一个SSE连接列表。一旦技能数据有更新,它会向所有连接的客户端发送一个标准的EventStream消息。前端收到消息后,会自动重新请求技能列表或特定技能的详情,从而实现UI的无感更新。

注意事项

  • 性能考量 :文件监听,尤其是在深层嵌套的目录上,可能消耗较多inotify资源(在Linux上)。 chokidar 已经做了很多优化,但如果你将技能根目录指向一个包含成千上万个文件的大项目(比如整个用户主目录),可能会遇到问题。务必将其指向精确的技能存放目录。
  • WSL环境 :在Windows Subsystem for Linux环境下开发时,文件系统事件有时会有延迟或丢失。这是WSL本身文件系统跨界的已知问题。对于绝大多数技能文件变更,监听是有效的,但在极端情况下,手动点击仪表盘上的刷新按钮作为后备方案是必要的。

3.3 技能详情页与文件树渲染

点击列表中的任何一个技能,你会进入一个详情页。这个页面由两部分核心内容组成:渲染后的 SKILL.md 文档,以及该技能目录的文件树。

Markdown渲染 :我选择了 markdown-it 作为渲染引擎,因为它速度快、扩展性好,并且与GitHub风格的Markdown兼容度高。为了能解析 SKILL.md 文件头部的YAML front matter(常用于定义技能的名称、描述、版本等元数据),我结合使用了 gray-matter 库。流程是:先通过 gray-matter 分离出元数据和内容主体,然后将元数据展示在页面侧边栏或顶部,再将内容主体交给 markdown-it 渲染成HTML。

文件树生成 :文件树的生成是一个递归读取目录的过程。我写了一个函数,它接收一个路径,返回一个嵌套的树形结构对象。每个节点包含名称、类型(文件/文件夹)、相对路径等信息。前端收到这个结构后,使用递归组件(在React中)来渲染出可展开/折叠的树形控件。

// 文件树节点接口示例
interface FileTreeNode {
  name: string;
  path: string; // 相对技能根目录的路径
  type: 'file' | 'directory';
  children?: FileTreeNode[]; // 仅目录有此属性
}

一个实用的技巧 :在生成文件树时,我默认忽略了一些常见的、对技能功能无影响的文件和目录,比如 .git , node_modules , *.log , .DS_Store 等。这能让文件树更加清爽,聚焦在真正的技能源代码和资源文件上。这个忽略列表可以通过配置来调整。

4. 从零开始的部署与配置指南

让我们抛开代码,从用户角度看看如何真正用起这个工具。假设你已经在本地安装了Node.js和OpenClaw。

4.1 环境准备与快速启动

首先,把项目克隆到本地:

git clone https://github.com/GanglyPuma22/openclaw-skill-viewer.git
cd openclaw-skill-viewer

安装依赖。这里我推荐使用 pnpm ,因为它速度更快,磁盘空间利用更高效(这个项目依赖不多,但养成好习惯):

npm install -g pnpm # 如果还没安装pnpm
pnpm install

接下来是构建。项目采用Vite作为构建工具,开发和生产构建是分离的。对于首次使用,直接进行生产构建并启动即可:

pnpm run build  # 这会执行TypeScript类型检查并打包优化
pnpm run start  # 启动一个生产模式的静态文件服务器

执行 pnpm run start 后,终端会输出类似下面的信息:

> openclaw-skill-viewer@0.1.0 start
> node server.js

Server running on http://127.0.0.1:4174
OpenClaw Skill Viewer is ready.
Skill roots configured: [...]

此时,打开浏览器,访问 http://127.0.0.1:4174 ,你应该就能看到仪表盘了。它应该已经自动扫描到了你本地默认位置的OpenClaw技能。

4.2 自定义技能根目录

如果你的技能没有放在默认的 ~/.openclaw/workspace/skills ~/.agents/skills ,别担心,通过环境变量可以轻松配置。

方法一:临时设置(针对单次运行) 在启动命令前设置环境变量。例如,你的自定义技能都放在 ~/my-custom-skills

OPENCLAW_SKILL_VIEWER_OTHER_ROOT="$HOME/my-custom-skills" pnpm run start

方法二:持久化设置(修改启动脚本) 你可以创建一个简单的启动脚本。在项目根目录创建一个新文件,比如 start-custom.sh

#!/bin/bash
export OPENCLAW_SKILL_VIEWER_BUILTIN_ROOT="$HOME/.nvm/versions/node/v20.18.0/lib/node_modules/openclaw/skills"
export OPENCLAW_SKILL_VIEWER_WORKSPACE_ROOT="$HOME/Projects/my-ai-workspace/.openclaw/skills"
export OPENCLAW_SKILL_VIEWER_OTHER_ROOT="$HOME/Downloads/community-skills"
pnpm run start

然后给脚本执行权限并运行: chmod +x start-custom.sh && ./start-custom.sh

重要提示 :环境变量会完全覆盖默认值。如果你设置了 OPENCLAW_SKILL_VIEWER_OTHER_ROOT ,那么默认的 ~/.agents/skills 就不会再被扫描。如果你想 同时扫描 多个自定义目录,目前版本需要你将它们通过符号链接(symlink)组织到一个父目录下,然后将该父目录指定为 OTHER_ROOT 。这是一个已知的简化设计,未来版本可能会支持目录数组。

4.3 开发模式与热重载

如果你想贡献代码或修改功能,你需要进入开发模式:

pnpm run dev

这个命令会同时启动两个服务:

  1. Vite开发服务器 :通常运行在 http://localhost:5173 ,负责前端React应用的热模块替换,你修改前端代码后,浏览器会即时更新,无需刷新。
  2. 后端API服务器 :运行在另一个端口(如 3000 ),提供技能数据接口。在开发模式下,它也会监听文件变化并推送SSE事件。

Vite开发服务器配置了代理,将 /api/* 的请求转发到了后端服务器。所以你在浏览器访问 http://localhost:5173 时,前后端是联通的。这种分离有利于前后端独立开发和调试。

5. 常见问题排查与使用技巧

在实际使用中,你可能会遇到一些小问题。这里我记录下我自己和早期用户遇到的一些典型情况及其解决方法。

5.1 技能列表为空或缺少技能

这是最常见的问题。请按以下步骤排查:

现象 可能原因 解决方案
列表完全为空,提示“未发现技能”。 1. 默认的技能根目录路径不正确。
2. OpenClaw未安装或不在PATH中。
3. 指定的自定义目录路径错误。
1. 检查终端输出的“Skill roots configured”日志,确认路径是否存在。
2. 在终端运行 which openclaw 确认CLI可用。
3. 使用绝对路径设置环境变量,并确保路径有读取权限。
能看到部分技能(如内置技能),但看不到工作区或自定义技能。 1. 工作区或自定义目录下没有包含 skill.json 的有效技能文件夹。
2. 环境变量覆盖错误,导致默认路径失效。
1. 确认你的技能目录结构是 skill-name/skill.json ,而不是散乱的文件。
2. 暂时去掉所有自定义环境变量,用默认设置启动,看是否能发现技能。如果能,说明你的环境变量设置有问题。
技能显示为“未知”状态。 OpenClaw CLI命令执行失败。 1. 确保OpenClaw后台服务正在运行(例如,通过 openclaw --version 测试)。
2. 检查后端服务日志,看是否有 Failed to fetch skill status 的错误。可能是CLI版本不兼容或权限问题。

一个高级技巧 :你可以直接访问后端API来调试。在浏览器中打开 http://127.0.0.1:4174/api/skills (如果是在开发模式,可能是 http://localhost:3000/api/skills ),这会返回原始的JSON格式技能列表。通过这个接口,你可以直接看到后端扫描到了哪些数据,以及状态信息是否准确,这能帮你快速定位问题是出在前端展示还是后端数据获取。

5.2 文件更改后UI没有自动刷新

如果你修改了 SKILL.md 但详情页内容没变,可能是以下原因:

  1. SSE连接中断 :检查浏览器开发者工具的“网络(Network)”选项卡,过滤“事件流(EventStream)”。你应该能看到一个到 /api/events 的长连接。如果这个连接是红色(失败)或不存在,说明SSE连接建立失败。刷新页面通常可以解决。
  2. 文件监听未生效 :确保你修改的文件位于已配置的技能根目录之下。如果你是通过IDE的“保存”或命令行 touch 来修改文件,监听通常是有效的。但某些文件同步工具(如Dropbox、云盘)的同步操作可能不会触发标准的文件系统事件。
  3. 缓存问题 :前端应用可能缓存了旧的Markdown HTML。尝试在详情页点击“原始(Raw)”视图切换按钮,或者强制刷新浏览器页面(Ctrl+F5)。

5.3 性能优化与小技巧

  • 技能数量很多时 :如果本地有上百个技能,首次加载列表可能会稍慢,因为后端需要并行读取所有 SKILL.md 文件来提取描述。后续操作(过滤、排序)都在前端内存中进行,会非常快。可以考虑将不常用的技能目录移出扫描路径。
  • 使用“就绪”过滤器 :这是最高效的用法。当你只想快速找到一个能立刻投入使用的技能时,直接点击左上角的“Ready only”过滤器,列表会瞬间精简。
  • 键盘导航 :在技能列表页面,你可以使用键盘上下箭头进行导航,按回车键进入选中技能的详情页,这能显著提升浏览效率。
  • 直接打开目录 :在技能详情页,文件树上方通常会有一个“在文件管理器中打开”或“在终端中打开”的按钮。善用这个功能,可以快速从“查看”模式无缝切换到“编辑”模式。

6. 技术栈选型的考量与未来可能的演进

最后,聊聊技术选型。这个项目没有使用最时髦的全栈框架,而是选择了经典、稳定且轻量的组合。

  • 前端:React + TypeScript + Vite 。React的组件化非常适合构建这种动态UI;TypeScript对于管理技能数据、API接口这类复杂对象类型提供了绝佳的安全性和开发体验;Vite则提供了闪电般的启动速度和热更新体验,让开发过程非常愉悦。
  • 后端:Express + TypeScript 。对于一个主要提供静态文件服务和简单API代理的应用来说,Express绰绰有余。用TypeScript写后端,可以和前端共享类型定义(比如 Skill 接口),保证前后端数据模型的一致性,减少错误。
  • 关键库 chokidar 是文件监听的行业标准; markdown-it gray-matter 是Markdown处理的最佳拍档; express-sse 用于简化Server-Sent Events的实现。

这个技术栈带来的好处是 依赖少、启动快、易于理解 。任何有一定Node.js和React经验的开发者都能在几分钟内上手项目代码。

关于未来,这个工具的核心功能已经稳定。我收到的一些有趣的建议包括:

  • 技能搜索 :在当前过滤和排序基础上,增加按名称、描述全文搜索的功能。
  • 技能对比视图 :并排查看两个相似技能的文档和文件结构,方便评估选择。
  • 导出技能清单 :将当前技能列表及状态导出为JSON或Markdown表格,用于文档或分享。
  • 更灵活的目录配置 :通过配置文件而非环境变量来管理多个技能扫描路径。

不过,我仍然会坚持“本地优先”和“只读视图”的核心原则。任何新功能都必须服务于“更好地浏览、理解和诊断本地技能”这个唯一目标,避免让工具变得笨重。毕竟,最好的工具往往是那些做好一件事的简单工具。

更多推荐