1. 项目定位:为什么我要做"技能领域的GitHub"
1.1 AI技能碎片化:这个项目要解决的痛点
这两年在做AI Agent相关的东西,我最大的感受是:写一个能干活的大模型提示词、技能模板、工具调用配置不难,难的是把这些东西系统化地管理起来、分发出去、让团队里的人都能用上。市面上各种Agent框架百花齐放,每个框架都有自己的技能加载方式,有的是一个prompt目录,有的是把工具函数注册进去,还有的是直接约定一套YAML格式让模型自己读。结果就是,一个能用的"技能"往往散落在博客的代码片段里、GitHub仓库的某个角落、群聊的聊天记录里,甚至只有原作者自己电脑上有。
真正让我下定决心做SkillHub的,是一次团队内部的事故。同事在某个Agent框架里手写了一套数据清洗技能,效果很好,但因为没有统一的存放位置,另一个项目组根本不知道有这个东西,结果花了两个星期重新造了一个几乎一样的轮子。反过来,需要跨框架复用技能的时候就更痛苦了,从格式转换到依赖配置,全部要手动来。当时我就想,代码有GitHub托管,但AI技能没有一个对应的集中地。技能本身就应该是可发现、可安装、可更新的"包",就像npm包、pip包一样。
1.2 SkillHub的核心价值与设计目标
SkillHub不是一个简单的静态网页索引,它要做的是整个技能分发的闭环。我把它的核心价值拆成四点。
第一,统一技能包格式。SkillHub定义了一套基于SKILL.md描述的技能规范,不管底层的Agent框架是Claude Code、自研框架还是别的什么,技能作者只需要按规范打包,平台统一解析。这个格式借鉴了社区里实际上已经在用的目录约定,但补齐了版本、依赖、许可证、作者信息这些工程化必备的字段。
第二,集中发现与检索。在SkillHub上,技能可以用标签、分类、评分、下载量来筛选,也可以直接搜关键词。这个对社区很重要,因为技能的价值只有在被复用的时候才真正体现出来。平台上目前已经有了代码审查、SQL生成、日志分析、日报自动撰写等一批高热度技能。
第三,一键安装与自动更新。这是SkillHub和普通文档站最大的区别。用户不需要手动下载压缩包再解压到某个目录,通过SkillHub提供的CLI工具,一条命令就能把技能安装到本地的Agent框架中,后续作者发布新版本,客户端可以检测到并提示升级。
第四,许可证合规检查。这个功能是我踩过坑之后坚持要做的。很多人从网上抄技能的时候根本不管许可证,但一旦用于商业项目就是隐患。SkillHub在技能发布时强制校验LICENSE字段,并自动解析和展示许可证类型,这样"开源"这件事才有底线。
1.3 为什么不直接丢在GitHub上
这个项目开源之后,我被问得最多的一个问题就是:不就是一个放技能的仓库吗,直接建一个GitHub组织不就行了?我确实考虑过,而且一开始就是这么做的。但实际用下来发现,GitHub在代码协作层面无可替代,但它不是为"技能消费"设计的。
GitHub解决的"代码怎么协作"的问题,SkillHub解决的是"技能怎么被发现、安装、更新"的问题。GitHub上一个仓库可以放很多东西,但你要找一个合适的技能时,没有统一的格式校验,没有依赖声明,没有评分体系,没有安装统计。更麻烦的是,不同的技能作者会用完全不同的目录结构组织文件,这就导致"能用"和"好用"之间差得很远。
所以SkillHub的定位不是替代GitHub,而是跑在GitHub协作模式之上的一个分发层。如果拿npm来类比,GitHub相当于源码托管平台,SkillHub则相当于npm registry加上一套配套管理工具。底层源码用GitHub托管,SkillHub做的是索引、校验、分发、统计这些脏活累活。两者互补,而不是互斥。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构与核心设计:一个开源平台是怎么长出来的
2.1 技术栈选型与理由
技术选型的时候我设了几个硬性约束:单机部署要能跑起来,代码改动要快,AI生态的第三方库要好接。最终定下的方案是:后端Node.js + Fastify,前端Vue3 + Vite + Naive UI,数据库用PostgreSQL,缓存交给Redis,对象存储直接上MinIO,整体用Docker Compose编排。
选Node.js而不是Go,最直接的原因是AI生态社区里JavaScript/TypeScript的渗透率太高了,各种SDK、官方示例、社区封装基本都是TS优先,后端用Node生态对接起来省心。Fastify比Express性能好、原生支持Schema校验,比NestJS轻很多,适合这种有点规模但不过度复杂的项目。
前端为什么用Vue3而不是React?团队的实际情况是我对Vue3更熟,Naive UI的组件质量和TypeScript支持都不错,写后台管理系统效率很高。这个不是技术上的胜负手,项目本身是前后端分离的,后续就算有人想用React重写前端也不影响整体架构。
数据存储这块,PostgreSQL负责技能元数据、用户、评分、评论、安装记录这些核心业务数据。MinIO存技能包的原始压缩文件,等于是把"文件存储"和"元数据存储"拆开,后续流量大了可以把MinIO换成任何S3兼容的对象存储服务,不需要改代码。
选型给到大家一个参照表:
| 模块 | 选型 | 核心考量 |
|---|---|---|
| 后端框架 | Fastify | 轻量、Schema校验、TS友好 |
| 前端框架 | Vue3 + Vite | 开发效率高、生态成熟 |
| 主数据库 | PostgreSQL | 数据一致性要求高,事务能力强 |
| 缓存与计数 | Redis | 热点数据缓存、下载计数、分布式锁 |
| 对象存储 | MinIO | S3协议兼容,可平滑替换 |
| 反向代理 | Nginx | 静态资源 + 接口反向代理 |
| 部署方式 | Docker Compose | 一键启动,降低分发门槛 |
| 认证方式 | GitHub OAuth | 开源社区用户天然熟悉,零门槛注册 |
2.2 SKILL.md:技能包格式是怎样设计的
技能包格式是整个项目的地基,格式没定好,后面所有的校验、分发、安装逻辑都得返工。我花了很长时间研究社区已有的约定,最后定下来的结构是这样的:
text复制my-skill/
├── SKILL.md # 技能描述文件(必填)
├── skill.yaml # 机器可读元数据(必填)
├── assets/ # 技能用到的静态资源(可选)
│ ├── icon.png
│ └── template.xlsx
├── prompts/ # 提示词模板(可选)
│ └── main.txt
├── scripts/ # 辅助脚本(可选)
│ └── preprocess.py
└── README.md # 人类可读的说明文档(建议)
SKILL.md是给人看的,用自然语言详细描述这个技能解决什么问题、适用场景、输入输出是什么。skill.yaml是给机器读的,平台所有校验逻辑都基于这个文件。两者分工明确,避免混在一起。
skill.yaml的核心字段长这样:
yaml复制name: data-cleaner
display_name: 数据清洗助手
version: 1.2.0
description: 自动识别常见脏数据模式并生成清洗方案与代码。
author:
name: skillhub-team
email: team@skillhub.dev
license: MIT
tags:
- data
- etl
- pandas
dependencies:
frameworks:
- type: claude-code
min_version: "1.0.0"
- type: custom-agent
min_version: "0.8.0"
runtime:
engine: python
entry: scripts/run.py
每个字段都有讲究。version必须遵循语义化版本规范,1.2.0就是主版本.次版本.修订号,主版本号变化代表不兼容的修改。dependencies声明的是这个技能需要运行在哪些Agent框架上以及最低版本要求,安装器根据这个字段判断本地环境是否满足。license字段是硬校验,发布时如果缺失或者不是SPDX标准标识符,直接拒绝发布。
2.3 数据模型与核心接口
数据模型我按业务域拆成了四块:用户域、技能域、互动域、安装域。
用户域就是users表加organizations表,users记录GitHub用户ID、用户名、头像、邮箱、角色。organizations是后来为了团队场景加的,支持创建组织然后把技能归属到组织名下,方便团队内部搞私有技能库。
技能域是核心,有三张表:skills、skill_versions、skill_tags。skills表里存技能的基本信息和最新版本聚合数据,skill_versions表存每次发布的版本记录,包括压缩包对象存储的地址、SHA256校验和、版本号、变更日志。之所以把版本单独拆表,是因为用户可能只想安装某个特定版本而不是最新版,同时版本数据要支持回滚。
互动域就是ratings、comments、stars三张表,这没什么特殊的,但评分表上做了一个均值缓存字段,每次插入评分时同步更新skills表上的rating_avg字段,避免每次展示列表都要跑聚合查询。
安装域是installation_records表,每次通过CLI安装成功就会写一条记录。这个表不单单是为了展示下载量,更重要的是统计哪些技能在哪些框架上装得多,反过来指导技能的兼容性优化。
接口设计遵循REST风格,对外暴露的主要接口有这些:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/skills | 技能列表,支持搜索与分页 |
| GET | /api/v1/skills/:id | 技能详情,含版本列表 |
| POST | /api/v1/skills | 发布新技能(需登录) |
| POST | /api/v1/skills/:id/versions | 发布新版本(需权限) |
| GET | /api/v1/skills/:id/download | 下载指定版本压缩包 |
| POST | /api/v1/skills/:id/install | 登记一次安装记录 |
| GET | /api/v1/organizations/:id/skills | 组织技能列表 |
2.4 版本管理与语义化发布
版本管理这一块我直接照搬了npm的思路,但针对技能的特殊场景做了一些调整。所有版本号严格遵循semver规范,主版本号不兼容、次版本号加功能、修订号修bug。发布新版本时,平台会自动生成一个对比视图,展示当前版本和上一个版本在skill.yaml上的差异,帮助用户判断这次版本更新带不带破坏性。
技能包发布时的流程是:CLI先把技能目录打成ZIP包,计算SHA256,然后把压缩包上传到MinIO,再把元数据写入PostgreSQL,最后在GitHub仓库上打一个对应版本的tag。这个流程保证了三处数据的一致性:对象存储里有文件、数据库里有元数据、GitHub上有源码快照。
回滚做成了"一键切版本"的方式。管理员或者技能作者可以把默认版本切换到任意历史版本,新的安装请求会拿到旧版本的压缩包,但已经安装的用户不会被强制回滚,只会收到一个版本更新提示。这个策略和大多数包管理器的行为保持一致,不搞强制覆盖。
3. 从0到1上手体验:部署平台与发布技能完整流程
3.1 本地快速启动:Docker Compose一键拉起
为了降低大家玩这个项目的门槛,我把整个平台封装成了Docker Compose编排,理论上只要机器上有Docker和Docker Compose就能跑起来。推荐配置是4核8G内存,想跑得更舒服就上8核16G,硬盘主要看技能包数量,初期20G足够了。
第一步是把代码clone下来:
bash复制git clone https://github.com/skillhub/skillhub.git
cd skillhub
cp .env.example .env
.env文件里需要配置的核心环境变量就这么几个:
bash复制DB_CONNECTION_STRING=postgresql://skillhub:skillhub@postgres:5432/skillhub
REDIS_URL=redis://redis:6379/0
S3_ENDPOINT=http://minio:9000
S3_ACCESS_KEY=skillhub
S3_SECRET_KEY=skillhub-secret
S3_BUCKET=skillhub-packages
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GitHub OAuth的Client ID和Client Secret需要去GitHub的Developer Settings页面创建一个OAuth App,Authorization callback URL填http://localhost:8080/api/v1/auth/github/callback。这里有一个很隐蔽的坑,callback地址必须和GitHub上填的完全一致,包括协议和端口,否则OAuth流程会一直报redirect_uri错误。
配置好之后直接启动:
bash复制docker compose up -d
启动完访问http://localhost:8080就能看到SkillHub的首页。如果只是想体验功能而不想配GitHub OAuth,我留了一个开发模式,把AUTH_MODE=dev打开之后,页面底部会出现一个"一键体验登录"的按钮,直接以管理员身份进入系统。
3.2 通过CLI发布一个技能包
平台本身是一个Web应用,但对技能作者来说,CLI工具才是日常接触最多的入口。CLI的作用是把"开发技能"到"发布技能"的整个流程标准化,操作路径是init、validate、login、publish四步。
bash复制npm install -g @skillhub/cli
装好之后,进入一个空目录初始化技能项目:
bash复制skillhub init my-skill
cd my-skill
skillhub validate
init命令会交互式地询问技能名称、描述、许可证、标签、支持的Agent框架等,然后生成一份标准的目录结构和skill.yaml模板。validate命令在本地对整个包做校验,检查字段是否合法、必填项是否缺失、版本号格式、许可证是否为SPDX标识符、目录结构是否符合规范。这一步能在上传前就发现问题,省得在平台上来回驳回。
校验通过后登录并发布:
bash复制skillhub login
skillhub publish
publish命令会打一个ZIP包、计算SHA256、上传、写入元数据、打Git tag,一条龙完成。发布成功后终端会输出一个链接,打开就是技能在平台上的详情页。之后每次改代码,只要把版本号往上提,再跑一遍publish就会生成新版本,整个流程不会超过三十秒。
3.3 将技能导入到Agent工作流
发布只是前半程,技能得要能装到实际工作的Agent框架里才算真正闭环。SkillHub的CLI安装命令是install,核心逻辑是按目标框架把技能包解压到对应目录。
bash复制# 安装到 Claude Code 风格目录
skillhub install data-cleaner --target .claude/skills/
# 安装到自定义框架目录
skillhub install data-cleaner --target ./agent/skills/
install命令干的事情比看上去多一点。它先解析skill.yaml里的dependencies字段,检查本地Agent框架的版本是否满足要求。然后从平台下载对应版本的ZIP包,验证SHA256,解压后有一步安全检查,防止压缩包里带了路径穿越文件——这个后面会详细说。都通过之后,再往平台发一条安装记录,更新这个技能的下载量。
装完之后还可以跑一遍doctor命令做体检:
bash复制skillhub doctor
这个命令会扫描当前所有已安装的技能,逐个检查目录结构是否完整、SKILL.md是否存在、skill.yaml是否能被正确解析,然后把异常项列成一张表格。我自己团队里现在把doctor命令加进了CI流程,每次发版前跑一遍,确保线上环境不会因为缺文件挂掉。
3.4 平台管理与团队协作
个人用SkillHub很简单,但一旦引入团队协作,权限、私有、审核这些需求就全出来了。SkillHub在v0.6版本加入了组织功能,可以建一个Organization,把团队成员都拉进去,然后技能仓库可以在组织名下创建。
组织里的角色分为owner、maintainer、contributor三档。owner有全部权限,包括解散组织、改组织名、转移技能归属。maintainer可以审核发布请求、修改技能元数据、回滚版本。contributor只能提交技能和更新自己名下的版本,不能碰别人的。
这个权限模型很大程度上是参考GitHub的Organization设计,但增加了"技能审核"这个环节。在maintainer视角下,每个提交上来的新版本都要过一道审核,确认SKILL.md写清楚了、依赖声明没有问题、许可证合规,然后才正式发布。私有技能包则通过访问控制列表来限制,只有组织成员列表里的人才能看到、安装。
4. 开源社区运营与踩坑记录:开发半年我总结的实战经验
4.1 GitHub开源项目的推广与社区参与
项目做到能开源,代码只是第一步,更现实的问题是"开源了没人知道"。我自己在推广上踩了不少弯路,总结下来有几点值得分享。
第一,README就是你的门面,比你想的更重要。我一开始README写得特别工程化,半天不说人话,后来发现独立开发者、小团队的用户根本不会细读架构说明,他们最关心的就是"这个东西到底能帮我解决什么问题""装起来麻不麻烦"。后来我把README重写了一遍,开头放一段30秒的gif演示,然后是五行的价值说明加三个安装命令,star数和issue反馈明显上来了。
第二,要做"活文档",而不是文档站。SkillHub的文档直接放在docs目录里,用GitHub Pages渲染,任何用户都可以提PR改文档。我个人的感受是,一个和代码同仓库的文档会跟着版本走,不会出现"文档写的功能代码里根本没有"的尴尬情况。
第三,积极参与周边开源社区比单纯发帖有效得多。SkillHub的技能格式尽量兼容主流Agent框架的目录约定,然后我在这些框架的社区里帮人解答技能管理相关的问题,顺手提一嘴SkillHub可以怎么用。这种"先从解决别人的问题开始"的方式,比在各种平台刷链接体面得多。
4.2 开发中遇到的典型BUG与排查
半年开发积累了不少调试经验,挑几个有代表性的问题说一下,每一个都是真实踩过的坑。
先说GitHub OAuth回调在HTTPS反代下丢失的问题。开发环境用HTTP一切正常,但部署到服务器上通过Nginx做HTTPS终结之后,OAuth登录就开始报redirect_uri不匹配。排查了一天最后定位到是代理层没有透传协议头,Node.js拿到的原始请求协议是HTTP而不是HTTPS,导致动态拼接的回调地址变成了http链接,和GitHub上注册的https回调对不上。修复方法是给Fastify加一个信任代理的配置,并显式检查X-Forwarded-Proto头。
再说路径穿越漏洞。技能包本质上是ZIP压缩包,解压时如果不检查文件名,恶意构造的ZIP里可能包含../../etc/cron.d/evil这样的路径,解压后直接覆盖系统文件。这个问题我在做install命令时专门做了防御:解压前遍历所有entry的文件名,用path.resolve规范化之后判断目标绝对路径是否在安装目录内,不在就报错退出。这里给所有做文件解压功能的同行提个醒,不管信任程度多高,只要是解压外部输入,路径穿越校验就是红线。
第三个问题是PostgreSQL连接池耗尽。上线初期并发量一上来,数据库连接就被打满,报错日志全是remaining connection slots are reserved for non-replication superuser connections。检查发现是Fastify的数据库插件默认连接池配置了10个连接,而高并发接口比如技能列表页直接把连接全占了。后来把连接池调整到50,同时给慢查询加了索引,问题解决。
第四个问题是安装计数并发写导致的热点行锁冲突。installation_records表每次都走PostgreSQL主库写,并发一高,同一行记录的计数器互相等锁。后来改成了异步链路:install命令只把原始记录发到Redis的Stream里,一个后台worker定时批量刷到PostgreSQL,计数聚合走Redis的ZSet,读路径基本不再碰主库。
典型问题整理成一张速查表,方便大家排查时对照:
| 问题 | 根因 | 解决方案 |
|---|---|---|
| OAuth回调丢失 | Nginx反代未透传协议头 | 开启代理信任,校验X-Forwarded-Proto |
| ZIP解压文件逃逸 | 未校验压缩包entry路径 | 用path.resolve校验目标绝对路径 |
| 连接池耗尽 | 默认连接数过低 | 调大连接池并优化慢查询索引 |
| 计数行锁冲突 | 热点行高并发直接写库 | 引入Redis Stream异步批处理 |
| 安装后Agent无法识别 | 目录结构不规范 | 提供skillhub doctor体检命令 |
4.3 开源的可持续性与商业化思考
开源项目的可持续性是一个绕不开的问题。SkillHub选了MIT许可证,这个决定是考虑过的。对于平台类项目,MIT最宽松,企业用户集成到内部系统没有心里包袱。如果选AGPL,虽然能防止别人拿去改闭源,但也把想接的企业挡在门外,社区增长会慢很多。
在项目路线图上,我把所有能力分成社区版和可选的托管服务两条线。社区版完全开源,自己部署,所有核心功能都能用。未来如果想做商业化,方向会在托管服务上,比如帮你管理技能仓库、提供技能分发CDN、生成团队使用报表、做私有技能审计,这些天然适合作为SaaS能力来交付。
作为开源作者,我对社区贡献者非常感激。CONTRIBUTING.md里面写了三种参与方式:报bug、提功能建议、直接提PR改代码。收到PR之后,我尽量在48小时内回复,即使暂时不合并也把原因和修改建议说清楚。开源项目能不能活下去,代码质量只是一部分,作者对贡献者的反馈速度同样重要。
4.4 常见问题速查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| publish时license校验失败 | 许可证不是SPDX标准标识符 | 改用MIT / Apache-2.0 / GPL-3.0等标准写法 |
| install之后Agent框架识别不到技能 | skill.yaml的frameworks字段与目标框架不匹配 | 检查dependencies.frameworks.type字段 |
| 安装的版本不是最新的 | 安装命令默认安装最新稳定版 | 使用skillhub install skill@版本号 指定版本 |
| OAuth登录循环跳转 | .env里回调地址与GitHub配置不一致 | 核对协议、域名、端口完全一致 |
| Docker启动后前端显示500 | MinIO或PostgreSQL未完成初始化 | 查看docker compose logs定位具体服务 |
在我实际推广SkillHub的这些日子里,看到有人把技能包传上来、给项目提issue、甚至帮我把英文文档翻译成日文的时候,那种感觉确实比写代码本身还充实。如果你想找一个真正能落地的开源项目来练手,或者你手头正好攒了一批Agent技能找不到地方放,SkillHub这个项目本身就很值得拉下来跑一跑。最后分享一个小技巧:开发新技能的时候,第一版不要追求功能完整,先把自己能想到的最小可用场景跑通,发布上去,再根据使用反馈迭代版本。技能这个东西,只有被别人用了,才算真正完成。
