调研对象:
skills-manage/目录,版本 0.10.0,Apache-2.0 项目定位:跨平台 AI 编码 Agent 技能管理桌面应用(Tauri v2 + React + Rust + SQLite) 参考用途:为本项目的"技能(Skill)"管理能力提供桌面端借鉴(多平台分发、符号链接、市场导入、AI 解释)
skills-manage 是一个桌面端的 AI 技能中央管理器。核心思想:
~/.agents/skills/ 作为唯一中央目录(canonical source of truth),所有 SKILL.md 物理上只在这里存一份它遵循 Anthropic Agent Skills 开放模式,SKILL.md 用 YAML frontmatter 声明 name / description。
React 前端 (src/)
↓ Tauri IPC(invoke() 调 #[tauri::command] 函数,50+ 个命令)
Rust 后端 (src-tauri/src/)
↓ SQLx 异步驱动
SQLite 数据库(~/.skillsmanage/db.sqlite,WAL 模式)
| 层 | 技术 | 关键依赖 |
|---|---|---|
| 桌面框架 | Tauri v2 | tauri 2 + 4 个官方插件(sql/fs/dialog/shell) |
| 前端框架 | React 18 + TypeScript 5.8 | react、react-dom |
| UI 组件 | shadcn/ui(base-ui + Tailwind CSS 4) | @base-ui/react、tailwindcss 4、class-variance-authority、tailwind-merge、lucide-react |
| 路由 | React Router v7 | react-router-dom ^7.5.0 |
| 状态管理 | Zustand 5 | zustand ^5.0.3(每业务域一个 store) |
| 国际化 | i18next + react-i18next | 中英双语,fallbackLng: "zh" |
| Markdown | react-markdown + remark-gfm | 前端展示;后端解析用 serde_yaml |
| 主题 | Catppuccin 4 flavor × 14 accent | 自定义 data-theme / data-accent HTML 属性 |
| 构建 | Vite 6 + tsc | dev 端口 24200 |
| 后端语言 | Rust 2021 | serde、chrono、uuid |
| 数据库 | SQLite via sqlx 0.8 | sqlx { sqlite, runtime-tokio } + WAL 模式 |
| HTTP | reqwest 0.12 | json、rustls-tls、stream,用于 GitHub API + AI 解释 |
| 压缩 | flate2 + tar | 解 GitHub tarball 到内存快照 |
| YAML | serde_yaml 0.9 | 解析 SKILL.md frontmatter |
| 测试 | Vitest 3 + jsdom + RTL(前端);cargo test + tempfile(后端) | 前端 370+ 用例,后端 214+ 用例 |
从代码可推断(README 未明说):
std::os::unix::fs::symlink vs std::os::windows::fs::symlink_dir)better-sqlite3 等原生 Node 模块db.rs:113 显式 PRAGMA journal_mode=WAL——允许读(UI 查询)与写(扫描 upsert)并发,避免扫描大量项目期间 UI 卡顿;同时嵌入式零配置,跨平台单文件。
sqlx::query_as! 宏宏要求编译期 DATABASE_URL 连接真实库做类型校验,对 CI 和开发者机构成负担。全项目统一 sqlx::query() + Row::get() 手动映射。
| 概念 | 说明 | 存储 |
|---|---|---|
| Skill | 一个含 SKILL.md(YAML frontmatter)的目录 | skills 表 |
| Agent(平台) | AI 编码工具(Claude Code / Cursor / Codex / Hermes / Copilot 等 27+ 内置) | agents 表 |
| 中央目录 | ~/.agents/skills/,技能的唯一权威位置 |
由特殊 agent id=central 标记 |
| 安装(Installation) | 中央 → 平台的符号链接/复制关系 | skill_installations 表(复合主键 skill_id+agent_id) |
| 观测(Observation) | 每次扫描平台目录时的技能快照,用于区分"平台原生/只读/本应用安装" | agent_skill_observations 表 |
| 集合(Collection) | 用户自定义的技能分组,可批量安装 | collections + collection_skills(多对多) |
| 市场源(Registry) | GitHub 仓库,作为远程技能目录 | skill_registries 表(带 etag/last-modified 缓存) |
| 市场技能 | 远程同步下来的技能元数据 | marketplace_skills 表 |
| 发现(Discover) | 全盘扫描到的项目级技能 | discovered_skills 表 |
| AI 解释缓存 | 按 (skill_id, lang) 缓存的 AI 说明 |
skill_explanations 表 |
| 设置 | KV 存储(github_pat / ai_api_key / ai_model 等) | settings 表 |
支持的 27+ 平台(节选):Claude Code、Codex CLI、Cursor、Gemini CLI、Trae、Factory Droid、Junie、Qwen、Windsurf、Qoder、Augment、OpenCode、KiloCode、OB1、Amp、Kiro、CodeBuddy、Hermes、Copilot、Aider;以及国内"龙虾系"(OpenClaw / QClaw / EasyClaw / AutoClaw / WorkBuddy)。特殊:central 即中央目录本身。
scanner.rs::parse_skill_md(scanner.rs:120):
SKILL.md---,截取至下一个 \n--- 作为 frontmattername 必填(缺失静默跳过),description 可选扫描时同步 upsert skills 表,并刷新 agent_skill_observations 记录观测。
入口:前端 centralSkillsStore.batchInstall 或卡片图标单击 togglePlatformLink → invoke("batch_install_to_agents" | "install_skill_to_agent", { skillId, agentIds, method })
核心流程(linker.rs::install_skill_to_agent_impl,:254):
1. 禁止安装到 central 自身
2. 查 agent 与 central agent,确定 canonical_dir
3. 【关键】ensure_centralized:
if canonical_dir/SKILL.md 不存在:
从 DB 取该技能当前实际 file_path
将其所在目录 copy_dir_all 到中央目录
回写 DB 的 canonical_path / is_central=true / file_path
→ "仅存在于某平台的技能被自动收养进中央目录",调用方对此透明
4. 支持"通用 ~/.agents/skills" 的平台(universal_available)直接返回成功
5. 目标已存在 symlink → 删除重建
目标已存在真实目录/文件 → 拒绝覆盖,报错(防误删用户手动放置的技能)
6. 计算相对路径 symlink_target_path(Windows 跨盘符时回退绝对路径)
7. 创建 symlink → upsert skill_installations (link_type="symlink")
回退策略(install_skill_to_agent_auto_impl,:338):
install_skill_to_agent_copy_impl 整目录复制为什么用相对路径 symlink:用户移动整个 home 目录或同步到另一台机器时链接不失效。
linker.rs::uninstall_skill_from_agent:删除 symlink/copy 目录 → 删除 skill_installations 行。中央目录的物理目录不动,仅断开分发关系。
discover.rs::start_project_scan(:1387):
~/projects、~/Documents、~/Developer、/Applications 等),存在的自动启用;叠加用户在 settings 里加的自定义目录MAX_SCAN_DEPTH = 8SKIP_DIRS:node_modules / target / .git / build / dist / .next / venv 等 16 个. 开头的隐藏目录,深层放行以命中 .claude/skills、.agents/skills 等模式project_skills_dirapp.emit("discover:progress" | "found" | "complete"),前端 store 订阅SCAN_CANCEL: AtomicBool + thread-local 覆盖(测试隔离)is_already_central 实时重算:每次从 DB 加载时检查 ~/.agents/skills/<dir_name> 是否存在,不是静态快照import_discovered_skill_to_central / import_discovered_skill_to_platform,特意不删 discovered 记录,以支持连续导入到多个平台抓取方式(github_import.rs::download_repository_archive,:824):
GET /repos/{owner}/{repo}/tarball/{branch} 整包下载flate2::GzDecoder + tar 解压到内存快照classify_skill_manifest_path 识别三种布局:
<dir>/SKILL.md(多技能仓库,排除 .github)skills/**/SKILL.md(含 .curated/.system 命名空间子目录)认证与限流:
github_pat 从 settings 表读取(github_import.rs:649)GITHUB_MIRROR_ENDPOINTS:github 直连 + ghfast.top / ghproxy.net / mirror.ghproxy.com 三个国内镜像,403/429 时依次回退x-ratelimit-remaining/reset 头构造结构化错误(含可操作建议的中文提示)缓存策略:
marketplace_skills(ON CONFLICT(id) DO UPDATE)last_sync_status="error"is_builtin=true)禁止删除;删除自定义源会先清掉其缓存技能安装:install_marketplace_skill 直接 GET download_url(raw.githubusercontent.com 直链)→ 写 ~/.agents/skills/<name>/SKILL.md。注意:仅下载单文件 SKILL.md,不带附属文件;完整仓库需走 import_github_repo_skills。
marketplace.rs::explain_skill_stream(:1307):
ai_api_url 路径后缀区分 Anthropic(/v1/messages + x-api-key + anthropic-version)与 OpenAI 兼容(/v1/chat/completions + Authorization: Bearer)content_block_delta.delta.text(跳过 thinking_delta),OpenAI 取 choices[0].delta.content;每块 app.emit("skill:explanation:chunk") 推前端classify_reqwest_error 把 reqwest 错误分为 Proxy/Connect/Timeout/Dns/Tls/Auth/Response/Unknown,附中文可操作提示skill_explanations 表按 (skill_id, lang) 存;INSERT OR REPLACE 保留原 created_at;空文本视为缓存腐败会删除并重新生成src/data/aiProviders.ts 7 个预设)collections.rs:分组管理 + batch_install_collection(对集合内每个技能 × 每个 agent 调 linker)+ JSON 导入/导出(CollectionExport { version, name, description, skills[], created_at, exported_from })。
按模块分组(lib.rs:41-112 注册):
| 模块 | 主要命令 | 职责 |
|---|---|---|
| scanner | scan_all_skills |
全量扫描中央目录 + 所有启用平台 |
| agents | get_agents / detect_agents / add_custom_agent / update_custom_agent / remove_custom_agent |
平台 CRUD;检测写回 is_detected |
| linker | install_skill_to_agent / uninstall_skill_from_agent / batch_install_to_agents |
symlink/copy 安装与卸载 |
| skills | get_skills_by_agent / get_central_skills / get_central_skill_bundles / get_skill_detail / read_skill_content / list_skill_directory / open_in_file_manager / preview_delete_central_skill_bundle / delete_central_skill* |
技能查询/内容/删除 |
| collections | create/get/update/delete_collection / add/remove_skill_to_collection / batch_install_collection / export/import_collection |
集合管理 |
| settings | get/set_scan_directories / set_scan_directory_active / get_setting / set_setting |
扫描目录 + KV 设置 |
| discover | discover_scan_roots / get_scan_roots / set_scan_root_enabled / start/stop_project_scan / get_discovered_skills / import_discovered_skill_to_central/platform / clear_discovered_skills / get_obsidian_vaults/skills |
项目级技能发现 |
| github_import | preview_github_repo_import / import_github_repo_skills / fetch_github_skill_markdown |
GitHub 仓库导入(带进度事件 + 冲突解决) |
| marketplace | list_registries / add/remove_registry / sync_registry(_with_options) / search_marketplace_skills / install_marketplace_skill / explain_skill(_stream) / get_skill_explanation / refresh_skill_explanation |
市场源 + AI 解释 |
| 路由 | 页面文件 | 布局模式 |
|---|---|---|
/ → 重定向 /central |
— | — |
/central |
CentralSkillsView.tsx |
技能卡片两列 |
/platform/:agentId |
PlatformView.tsx |
技能卡片两列 |
/skill/:skillId |
SkillDetailPage.tsx |
双栏:左 SKILL.md 预览全高,右 metadata + 紧凑图标式安装状态 + collections |
/collections |
CollectionsListView.tsx / CollectionView.tsx |
卡片横排选中 + 下方技能列表 |
/marketplace |
MarketplaceView.tsx |
三 Tab:推荐 / 官方源目录 / 我的源 |
/discover, /discover/:projectPath |
DiscoverView.tsx |
左面板项目列表 + 右面板技能详情 |
/obsidian/:vaultId |
ObsidianVaultView.tsx |
独立视图 |
/settings |
SettingsView.tsx |
卡片分区 |
约定:组件不直接 invoke(),一律走 store。
| Store | 职责 |
|---|---|
centralSkillsStore |
中央库 + bundle + 平台链接切换 |
platformStore |
平台视图 + 全量扫描 |
skillStore |
单平台技能列表 |
skillDetailStore |
技能详情 + AI 解释流式订阅 |
collectionStore |
集合 CRUD/批量安装/导入导出 |
discoverStore |
Discover 扫描与导入(监听 discover:* 事件) |
marketplaceStore |
市场三 Tab + GitHub 导入 + 解释 |
obsidianStore |
Obsidian vault |
settingsStore |
扫描目录 + GitHub PAT + 自定义平台 |
themeStore |
Catppuccin 主题(纯前端 localStorage,无 IPC) |
UnifiedSkillCard(src/components/skill/UnifiedSkillCard.tsx):所有页面的技能卡片唯一实现,通过可选 props 自适应 5 种场景:
| 场景 | 特征 props |
|---|---|
| central | platformIcons{agents, linkedAgents, readOnlyAgents, onToggle} 渲染 LOBSTER/CODING 两行图标即时切换 |
| platform | sourceType: "symlink"\|"copy"\|"native" + originKind + isReadOnly |
| discover | checkbox{checked, onChange} + platformBadge + projectBadge + isCentral |
| marketplace | isInstalled + tags[] + publisher |
| collection | 复用基础 + onRemove |
统一样式:rounded-xl ring-1 ring-border bg-card shadow-sm。约定:不要为特定场景新建卡片组件。
InstallDialog(src/components/central/InstallDialog.tsx):
onInstallfallbackLng: "zh";检测顺序 localStorage(i18nextLng) → navigatorthemeStore.applyFlavor/applyAccent 在 <html> 上写 data-theme / data-accent,持久化到 localStorage catppuccin-flavor / catppuccin-accent;init() 在渲染前调用防闪烁| 类别 | 依赖 | 说明 |
|---|---|---|
| 运行时 | Node.js LTS + pnpm | package.json 中 pnpm.onlyBuiltDependencies 声明 pnpm 专用 |
| Rust | stable toolchain(rustup) | 2021 edition |
| 系统库 | Tauri v2 系统依赖 | Windows:WebView2 + MSVC;macOS:Xcode CLT;Linux:webkit2gtk 等(详见 https://v2.tauri.app/start/prerequisites/) |
| Rust crate | tauri 2 + 4 插件 / sqlx 0.8 / serde_yaml 0.9 / reqwest 0.12 / flate2 / tar / chrono / uuid / futures-util | 见 src-tauri/Cargo.toml:16-31 |
| 前端依赖 | react 18 / react-router-dom 7 / zustand 5 / i18next / tailwindcss 4 / @base-ui/react / lucide-react / cmdk / sonner / react-markdown / gray-matter | 见 package.json:17-44 |
| 测试 | vitest 3 + jsdom + @testing-library/* / tokio(full) / tempfile | 前端 370+ 用例,后端 214+ 用例 |
不需要 Node 也不需要 Rust——pnpm tauri build 产出平台原生安装包。
tauri.conf.json 配置 webviewInstallMode: embedBootstrapper 自动引导).dmg / .app.zip),未 notarize,首次运行需 xattr -dr com.apple.quarantine| 数据 | 位置 |
|---|---|
| 应用数据库 | ~/.skillsmanage/db.sqlite(含 collections / settings / 市场缓存 / AI 解释缓存) |
| 中央技能目录 | ~/.agents/skills/ |
| 各平台技能目录 | ~/.claude/skills/ / ~/.cursor/skills/ / ~/.gemini/skills/ 等(由 agents 表 global_skills_dir 定义) |
| 前端主题/语言偏好 | localStorage |
应用本地优先,仅以下功能联网:
| 功能 | 目标端点 | 是否带 token |
|---|---|---|
| Marketplace 源同步 / GitHub 仓库预览/导入 | api.github.com + raw.githubusercontent.com;镜像回退 ghfast.top / ghproxy.net / mirror.ghproxy.com |
直连 github.com 带 PAT;镜像不带 |
| Marketplace 技能安装 | raw.githubusercontent.com(download_url 直链) |
不带 |
| AI 技能解释 | 用户配置的 ai_api_url(Anthropic / GLM / MiniMax / Kimi / DeepSeek / OpenRouter) |
带用户配置的 api_key |
遥测:无;崩溃上报:无;凭证:仅存本地 SQLite settings 表(明文)。
| 决策 | 价值 | 对应代码 |
|---|---|---|
~/.agents/skills/ 作为唯一中央目录 + symlink 分发 |
一份技能多平台共享,避免散落与不一致 | path_utils.rs:45-47、linker.rs:154 |
ensure_centralized 自动收养 |
仅存在于某平台的技能被自动拷贝进中央,调用方透明 | linker.rs:154-195 |
| 相对路径 symlink | 用户移动 home 目录链接不失效 | linker.rs:89-100 |
| 拒绝覆盖真实目录 | 防误删用户手动放置的技能 | linker.rs:301-312 |
| Windows symlink 失败自动回退 copy | 兼容无开发者模式的 Windows 环境 | linker.rs:338-360 |
| SQLite WAL + sqlx 手动映射 | 读写并发不卡 UI;避开 query_as! 宏对 DATABASE_URL 的依赖 |
db.rs:113、全项目 |
| GitHub tarball 整包解压到内存 | 规避 Trees API 限流;flate2 + tar 流式 |
github_import.rs:824-870 |
| 镜像回退 + 限流头解析 | 国内可用性;结构化错误提示可操作 | github_import.rs:219-240、:1327-1341 |
AI 解释 (skill_id, lang) 缓存 + 空文本腐败检测 |
降本;防缓存损坏被永久复用 | marketplace.rs:889-905 |
SSE 流式 + app.emit |
前端无感知渲染;错误分类可操作 | marketplace.rs:1220-1251 |
is_already_central 实时重算 |
避免静态快照过期导致的状态错乱 | discover.rs:1445 |
| 前端组件不直接 invoke,统一走 store | 便于测试 mock + 集中错误处理 | src/test/setup.ts:88-95 |
Vitest 通过 window.__TAURI_INTERNALS__ mock IPC |
前端单测无需启动真实 Tauri | src/test/setup.ts:88-95 |
MAX_SCAN_DEPTH=8 + 16 个 SKIP_DIRS):项目内做本地知识库或代码库扫描时可参考。is_already_central 实时重算:状态派生自文件系统而非数据库快照,避免缓存漂移——项目内做"技能是否已安装"判定可直接采用。skills-manage/
├── src/ # React 前端
│ ├── components/ # UI 组件(含 UnifiedSkillCard / InstallDialog)
│ ├── data/ # officialSources.ts / aiProviders.ts 静态数据
│ ├── hooks/ # 通用 hooks
│ ├── i18n/ # zh.json / en.json / index.ts
│ ├── lib/ # 前端工具
│ ├── pages/ # 9 个路由页面
│ ├── stores/ # 10 个 Zustand store
│ ├── test/ # Vitest + RTL(370+ 用例)
│ ├── types/ # 共享 TS 类型
│ ├── App.tsx # 路由声明
│ ├── main.tsx # 入口(含 theme init)
│ └── index.css # Catppuccin 4 flavor × 14 accent CSS 变量
├── src-tauri/
│ ├── src/
│ │ ├── commands/ # 10 个 IPC 模块(scanner/agents/linker/skills/collections/settings/discover/github_import/marketplace)
│ │ ├── db.rs # SQLite schema + 迁移 + 内置 seed(11 张表)
│ │ ├── path_utils.rs # home 解析 + ~/.skillsmanage + ~/.agents/skills
│ │ ├── lib.rs # Tauri Builder + invoke_handler 注册
│ │ └── main.rs # 桌面入口
│ └── Cargo.toml # Rust 依赖
├── package.json # 前端依赖
├── tauri.conf.json # Tauri 配置(bundle / window / plugins)
└── docs/ # 项目内文档