diff --git a/plugins/skill-hub/.editorconfig b/plugins/skill-hub/.editorconfig
new file mode 100644
index 00000000..554080f6
--- /dev/null
+++ b/plugins/skill-hub/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{json,yaml,yml}]
+indent_size = 2
diff --git a/plugins/skill-hub/.gitignore b/plugins/skill-hub/.gitignore
new file mode 100644
index 00000000..deb2620e
--- /dev/null
+++ b/plugins/skill-hub/.gitignore
@@ -0,0 +1,38 @@
+# 依赖
+node_modules
+
+# 构建产物
+dist/
+*.tsbuildinfo
+
+# 环境变量
+.env
+.env.local
+.env.*.local
+
+# 工具
+.claude/
+.mimocode/
+.agents/
+.skill-archive/
+api-tests/
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# 测试
+coverage/
+test-builtin-skills.html
+
+# 日志
+*.log
+
+# OS
+Thumbs.db
+.DS_Store
+
+# 文档(本地副本,不提交)
+docs/ztools-doc/
diff --git a/plugins/skill-hub/AGENTS.md b/plugins/skill-hub/AGENTS.md
new file mode 100644
index 00000000..8f98c104
--- /dev/null
+++ b/plugins/skill-hub/AGENTS.md
@@ -0,0 +1,65 @@
+# Skill Hub
+
+ZTools 插件 — Vue 3 + Vite + TypeScript 技能商店与一键分发工具。
+
+## 命令
+
+```bash
+pnpm dev # Vite 开发服务器 → http://localhost:5173
+pnpm build # vue-tsc 类型检查 → vite 构建 → dist/
+pnpm build-zip # 构建 + 打包 zip 到系统下载文件夹
+```
+
+未配置测试/lint/格式化工具。`build` 是唯一的验证步骤。
+
+## 架构
+
+- **单页应用**:无 vue-router,手动管理路由状态(`route` ref in `App.vue`)
+- **分栏布局**:左侧可调宽度面板 + 右侧内容区,通过 `startResize` 实现拖拽调整
+- **Preload 脚本**:`public/preload/services.js` 提供 Node.js 能力(文件系统、GitHub API、zip 解压)
+- **技能扫描**:通过 `window.services.scanForSkillFiles()` 扫描目录中的 SKILL.md 文件
+
+## 目录结构
+
+```
+src/
+├── views/ # 页面组件(Home, SkillStore, AgentSkills, ProjectSkills, Settings, Sources)
+├── components/ # 共享组件(Modal, Toast, PlatformIcon 等)
+├── composables/ # Vue 组合式函数(useSettings 等)
+├── utils/ # 工具函数(storage, theme 等)
+├── data/ # 静态数据(platforms 等)
+└── types.ts # TypeScript 类型定义
+public/
+├── preload/
+│ └── services.js # Node.js 服务(文件操作、GitHub API、压缩包处理)
+└── plugin.json # ZTools 插件配置
+```
+
+## 关键约束
+
+- **禁止使用浏览器内置弹窗 API**:所有需要用户确认/提示/输入的场景,必须使用项目中已有的自定义 Modal 组件(如 `DeployModal`、`SkillDetailModal`、`AddProjectModal` 等)或 `AppToast`,不得使用 `alert()`、`confirm()`、`prompt()`
+
+- TypeScript `strict: false`,`noImplicitAny: false`(宽松模式)
+- 无测试框架,构建即验证
+- Preload 脚本使用 CommonJS(`public/preload/package.json` 中 `"type": "commonjs"`)
+- 主应用使用 ES Modules(`package.json` 中 `"type": "module"`)
+- 样式使用 CSS 变量系统,支持明暗主题切换
+
+## 开发注意
+
+- 添加新功能需在 `src/App.vue` 的路由表中注册
+- Preload 服务通过 `window.services` 全局访问
+- ZTools API 通过 `window.ztools` 全局访问
+- 构建产物在 `dist/` 目录,需复制到 ZTools 插件目录测试
+- **打包 zip 文件时,保存到系统下载文件夹**:是系统下载路径,而非应用默认路径
+
+## API 测试
+
+- `api-tests/openai-api.http` — kulala.nvim 测试文件,包含 OpenAI API 多个接口
+- 使用前设置环境变量 `OPENAI_API_KEY`
+
+## ZTools 文档
+
+- 本地副本:`docs/ztools-doc/`(9 个 markdown 文件)
+- 在线地址:https://ztoolscenter.github.io/ZTools-doc/
+- 包含:快速开始、目录结构、plugin.json 配置、preload.js、Node.js 能力、插件 API、发布流程
diff --git a/plugins/skill-hub/LICENSE b/plugins/skill-hub/LICENSE
new file mode 100644
index 00000000..36e125b2
--- /dev/null
+++ b/plugins/skill-hub/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 zibo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/plugins/skill-hub/README.md b/plugins/skill-hub/README.md
new file mode 100644
index 00000000..4739298a
--- /dev/null
+++ b/plugins/skill-hub/README.md
@@ -0,0 +1,130 @@
+# Skill Hub
+
+> 一个 ZTools 插件 — AI 编写代码,本人负责使用与维护。
+
+Vue 3 + Vite + TypeScript 构建的技能商店与一键分发工具。浏览 GitHub / skills.sh / 本地的 SKILL.md,扫描项目或 AI Agent 中的已有技能,并通过符号链接或复制的方式一键分发到多个 AI 平台。
+
+## 快速开始
+
+```bash
+pnpm dev # 开发服务器 → http://localhost:5173
+pnpm build # vue-tsc 类型检查 → vite 构建 → dist/
+```
+
+## 功能
+
+### 我的 Skill(默认首页)
+
+列出已下载的技能,支持按分类筛选(全部/收藏/已分发/待分发)。每个技能卡片显示名称、作者、标签、来源,可一键安装到已检测到的 AI Agent 平台,或卸载、收藏、查看详情。
+
+### Skill 商店
+
+从多个来源浏览和搜索技能:
+
+- **Claude Code** — 官方 [anthropics/skills](https://github.com/anthropics/skills) 仓库
+- **OpenAI Codex** — 官方 [openai/skills](https://github.com/openai/skills) 仓库
+- **skills.sh** — 社区技能市场,支持搜索、热门排序、精选作者
+- **自定义源** — 可添加任意 GitHub 仓库或本地目录作为来源
+
+商店支持模糊搜索和语义搜索,提供技能安全审计信息(风险等级、审查摘要)。
+
+### 项目 Skill
+
+注册本地项目目录,自动扫描其中约定的子目录(`.claude/skills`、`.agents/skills`、`skills`、`.cursor/skills`、`.windsurf/skills` 等),识别项目内定义的 SKILL.md 文件。支持添加、编辑、删除项目,查看项目内技能的原始内容和元数据。
+
+### Agent Skill
+
+自动检测本地已安装的 AI Agent 平台(Claude Code / Codex / Cursor / Windsurf / Cline 等),扫描各平台的技能目录,列出已有技能。支持查看技能详情和原始文件内容。
+
+### 商店源管理
+
+管理自定义技能来源,支持三种类型:
+
+- **GitHub 仓库** — 指定仓库名、分支和子目录
+- **skills.sh 源** — 社区市场
+- **本地目录** — 文件系统中的任意目录
+
+### 设置
+
+- 默认安装模式(符号链接/复制)
+- GitHub Token 配置(用于 API 限频提升)
+- 各 AI Agent 平台的路径自定义
+- 界面主题(浅色/深色/跟随系统)
+- 主题色系(雾霾蓝/烟熏紫/豆沙绿/杏色橙/青碧色等莫兰迪色系)
+- 字体大小、动画偏好、紧凑模式
+- 自定义背景图片
+- AI 翻译模型配置(用于技能内容的翻译)
+
+## 架构
+
+```
+src/
+├── views/ 页面视图(8 个路由页面)
+│ ├── MySkills/ 我的技能库
+│ ├── SkillStore/ 技能商店(列表 + 详情)
+│ ├── ProjectSkills/ 项目技能管理
+│ ├── AgentSkills/ Agent 平台技能(列表 + 详情)
+│ ├── Sources/ 商店源管理
+│ └── Settings/ 设置页面
+├── components/ 共享组件(15 个)
+│ ├── Modal 家族 AddProjectModal / SkillDetailModal / ConfirmModal 等
+│ ├── AppToast 全局 Toast 提示
+│ ├── DownloadIndicator 下载进度指示器
+│ ├── PlatformIcon 平台图标
+│ ├── QuickSwitcher 快捷切换
+│ └── ... 编辑器、选择器等
+├── composables/ Vue 组合式函数
+│ ├── useSettings 设置读写与持久化
+│ ├── useProjectState 项目状态管理
+│ └── useDownloadQueue 下载队列
+├── utils/ 工具函数(9 个)
+│ ├── storage.ts 本地存储(localStorage 封装)
+│ ├── ai.ts AI 翻译与模型调用
+│ ├── theme.ts 主题切换
+│ ├── github.ts GitHub API 调用
+│ ├── frontmatter.ts SKILL.md 解析
+│ ├── skill-registry.ts 技能注册表
+│ ├── skills-sh.ts skills.sh API 客户端
+│ ├── source-info.ts 来源信息
+│ └── translate.ts 翻译服务
+├── data/ 静态数据
+│ ├── platforms.ts AI Agent 平台定义
+│ ├── ai-providers.ts AI 提供方配置
+│ └── skill-categories.ts 技能分类
+└── types.ts 全部 TypeScript 类型定义
+
+public/
+├── plugin.json ZTools 插件配置(4 个 feature / 命令词)
+└── preload/
+ └── services.js Node.js 桥接层(30+ 方法)
+```
+
+## 平台支持
+
+自动检测以下 AI Agent 平台的安装位置,支持一键分发技能:
+
+| 平台 | 类型 |
+|---|---|
+| Claude Code | 官方 |
+| OpenAI Codex | 官方 |
+| Cursor | 官方 |
+| Windsurf | 官方 |
+| Cline | 社区 |
+| Gemini | 官方 |
+| Trae | 社区 |
+| Cherry Studio | 社区 |
+| Kiro | 社区 |
+| 及其他社区客户端 | — |
+
+## 技术栈
+
+- **框架** Vue 3 + TypeScript
+- **构建** Vite 6
+- **编辑器** CodeMirror 6(支持 YAML / JSON / Markdown / Python / JS / CSS / HTML 语法高亮)
+- **后端** ZTools Preload 机制(Node.js 桥接)
+- **包管理** pnpm
+
+## 说明
+
+- 源代码由 AI 辅助生成,本人负责功能设计、使用与持续维护。
+- 详细开发约束与项目规范见 `AGENTS.md`。
diff --git a/plugins/skill-hub/index.html b/plugins/skill-hub/index.html
new file mode 100644
index 00000000..eaa17316
--- /dev/null
+++ b/plugins/skill-hub/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/package.json b/plugins/skill-hub/package.json
new file mode 100644
index 00000000..dd013581
--- /dev/null
+++ b/plugins/skill-hub/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "skill-hub",
+ "version": "1.0.0",
+ "description": "A ZTools plugin",
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc && vite build",
+ "build-zip": "pnpm build && node scripts/build-zip.cjs"
+ },
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.20.3",
+ "@codemirror/commands": "^6.10.3",
+ "@codemirror/lang-css": "^6.3.1",
+ "@codemirror/lang-html": "^6.4.11",
+ "@codemirror/lang-javascript": "^6.2.5",
+ "@codemirror/lang-json": "^6.0.2",
+ "@codemirror/lang-markdown": "^6.5.0",
+ "@codemirror/lang-python": "^6.2.1",
+ "@codemirror/lang-yaml": "^6.1.3",
+ "@codemirror/language": "^6.12.3",
+ "@codemirror/search": "^6.7.1",
+ "@codemirror/state": "^6.6.0",
+ "@codemirror/view": "^6.43.1",
+ "@lezer/highlight": "^1.2.3",
+ "codemirror": "^6.0.2",
+ "vue": "^3.5.13"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^5.2.1",
+ "@ztools-center/ztools-api-types": "^1.0.1",
+ "typescript": "^5.3.0",
+ "vite": "^6.0.11",
+ "vue-tsc": "^2.0.0"
+ }
+}
diff --git a/plugins/skill-hub/pnpm-lock.yaml b/plugins/skill-hub/pnpm-lock.yaml
new file mode 100644
index 00000000..14e890e1
--- /dev/null
+++ b/plugins/skill-hub/pnpm-lock.yaml
@@ -0,0 +1,1157 @@
+lockfileVersion: '6.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+dependencies:
+ '@codemirror/autocomplete':
+ specifier: ^6.20.3
+ version: 6.20.3
+ '@codemirror/commands':
+ specifier: ^6.10.3
+ version: 6.10.3
+ '@codemirror/lang-css':
+ specifier: ^6.3.1
+ version: 6.3.1
+ '@codemirror/lang-html':
+ specifier: ^6.4.11
+ version: 6.4.11
+ '@codemirror/lang-javascript':
+ specifier: ^6.2.5
+ version: 6.2.5
+ '@codemirror/lang-json':
+ specifier: ^6.0.2
+ version: 6.0.2
+ '@codemirror/lang-markdown':
+ specifier: ^6.5.0
+ version: 6.5.0
+ '@codemirror/lang-python':
+ specifier: ^6.2.1
+ version: 6.2.1
+ '@codemirror/lang-yaml':
+ specifier: ^6.1.3
+ version: 6.1.3
+ '@codemirror/language':
+ specifier: ^6.12.3
+ version: 6.12.3
+ '@codemirror/search':
+ specifier: ^6.7.1
+ version: 6.7.1
+ '@codemirror/state':
+ specifier: ^6.6.0
+ version: 6.6.0
+ '@codemirror/view':
+ specifier: ^6.43.1
+ version: 6.43.1
+ '@lezer/highlight':
+ specifier: ^1.2.3
+ version: 1.2.3
+ codemirror:
+ specifier: ^6.0.2
+ version: 6.0.2
+ vue:
+ specifier: ^3.5.13
+ version: 3.5.38(typescript@5.9.3)
+
+devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^5.2.1
+ version: 5.2.4(vite@6.4.3)(vue@3.5.38)
+ '@ztools-center/ztools-api-types':
+ specifier: ^1.0.1
+ version: 1.0.3
+ typescript:
+ specifier: ^5.3.0
+ version: 5.9.3
+ vite:
+ specifier: ^6.0.11
+ version: 6.4.3
+ vue-tsc:
+ specifier: ^2.0.0
+ version: 2.2.12(typescript@5.9.3)
+
+packages:
+
+ /@babel/helper-string-parser@7.29.7:
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
+ /@babel/helper-validator-identifier@7.29.7:
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
+ /@babel/parser@7.29.7:
+ resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.29.7
+
+ /@babel/types@7.29.7:
+ resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+
+ /@codemirror/autocomplete@6.20.3:
+ resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==}
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ '@lezer/common': 1.5.2
+ dev: false
+
+ /@codemirror/commands@6.10.3:
+ resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==}
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ '@lezer/common': 1.5.2
+ dev: false
+
+ /@codemirror/lang-css@6.3.1:
+ resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@lezer/common': 1.5.2
+ '@lezer/css': 1.3.3
+ dev: false
+
+ /@codemirror/lang-html@6.4.11:
+ resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==}
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/lang-css': 6.3.1
+ '@codemirror/lang-javascript': 6.2.5
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ '@lezer/common': 1.5.2
+ '@lezer/css': 1.3.3
+ '@lezer/html': 1.3.13
+ dev: false
+
+ /@codemirror/lang-javascript@6.2.5:
+ resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==}
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/language': 6.12.3
+ '@codemirror/lint': 6.9.7
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ '@lezer/common': 1.5.2
+ '@lezer/javascript': 1.5.4
+ dev: false
+
+ /@codemirror/lang-json@6.0.2:
+ resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==}
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@lezer/json': 1.0.3
+ dev: false
+
+ /@codemirror/lang-markdown@6.5.0:
+ resolution: {integrity: sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==}
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/lang-html': 6.4.11
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ '@lezer/common': 1.5.2
+ '@lezer/markdown': 1.6.4
+ dev: false
+
+ /@codemirror/lang-python@6.2.1:
+ resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==}
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@lezer/common': 1.5.2
+ '@lezer/python': 1.1.19
+ dev: false
+
+ /@codemirror/lang-yaml@6.1.3:
+ resolution: {integrity: sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==}
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ '@lezer/yaml': 1.0.4
+ dev: false
+
+ /@codemirror/language@6.12.3:
+ resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==}
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ style-mod: 4.1.3
+ dev: false
+
+ /@codemirror/lint@6.9.7:
+ resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==}
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ crelt: 1.0.6
+ dev: false
+
+ /@codemirror/search@6.7.1:
+ resolution: {integrity: sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==}
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ crelt: 1.0.6
+ dev: false
+
+ /@codemirror/state@6.6.0:
+ resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==}
+ dependencies:
+ '@marijn/find-cluster-break': 1.0.2
+ dev: false
+
+ /@codemirror/view@6.43.1:
+ resolution: {integrity: sha512-+BIjw/AG3tDQ4pJgTLPYdAW25eDE66YsvM4LKyVPgGzVgZ4a9Wj1SRX8kPVKgBDdPt8oHtZ15F0qx7p0oOHdHw==}
+ dependencies:
+ '@codemirror/state': 6.6.0
+ crelt: 1.0.6
+ style-mod: 4.1.3
+ w3c-keyname: 2.2.8
+ dev: false
+
+ /@esbuild/aix-ppc64@0.25.12:
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-arm64@0.25.12:
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-arm@0.25.12:
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-x64@0.25.12:
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-arm64@0.25.12:
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-x64@0.25.12:
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-arm64@0.25.12:
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-x64@0.25.12:
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm64@0.25.12:
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm@0.25.12:
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ia32@0.25.12:
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-loong64@0.25.12:
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-mips64el@0.25.12:
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ppc64@0.25.12:
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-riscv64@0.25.12:
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-s390x@0.25.12:
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-x64@0.25.12:
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/netbsd-arm64@0.25.12:
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/netbsd-x64@0.25.12:
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/openbsd-arm64@0.25.12:
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/openbsd-x64@0.25.12:
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/openharmony-arm64@0.25.12:
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/sunos-x64@0.25.12:
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-arm64@0.25.12:
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-ia32@0.25.12:
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-x64@0.25.12:
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@jridgewell/sourcemap-codec@1.5.5:
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ /@lezer/common@1.5.2:
+ resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==}
+ dev: false
+
+ /@lezer/css@1.3.3:
+ resolution: {integrity: sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ dev: false
+
+ /@lezer/highlight@1.2.3:
+ resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ dev: false
+
+ /@lezer/html@1.3.13:
+ resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ dev: false
+
+ /@lezer/javascript@1.5.4:
+ resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ dev: false
+
+ /@lezer/json@1.0.3:
+ resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ dev: false
+
+ /@lezer/lr@1.4.10:
+ resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ dev: false
+
+ /@lezer/markdown@1.6.4:
+ resolution: {integrity: sha512-N0SxazMj4k65DBfaf1azqtMZd6u7MqluP84/NZnB/io8Td9aleFmAhz9hcbvSfsxT5tdYlJ5qgv5aMJGY4zEtA==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ dev: false
+
+ /@lezer/python@1.1.19:
+ resolution: {integrity: sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ dev: false
+
+ /@lezer/yaml@1.0.4:
+ resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==}
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ dev: false
+
+ /@marijn/find-cluster-break@1.0.2:
+ resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+ dev: false
+
+ /@rollup/rollup-android-arm-eabi@4.62.0:
+ resolution: {integrity: sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-android-arm64@4.62.0:
+ resolution: {integrity: sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-arm64@4.62.0:
+ resolution: {integrity: sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-x64@4.62.0:
+ resolution: {integrity: sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-freebsd-arm64@4.62.0:
+ resolution: {integrity: sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-freebsd-x64@4.62.0:
+ resolution: {integrity: sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-gnueabihf@4.62.0:
+ resolution: {integrity: sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-musleabihf@4.62.0:
+ resolution: {integrity: sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-gnu@4.62.0:
+ resolution: {integrity: sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-musl@4.62.0:
+ resolution: {integrity: sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-loong64-gnu@4.62.0:
+ resolution: {integrity: sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-loong64-musl@4.62.0:
+ resolution: {integrity: sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-ppc64-gnu@4.62.0:
+ resolution: {integrity: sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-ppc64-musl@4.62.0:
+ resolution: {integrity: sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-gnu@4.62.0:
+ resolution: {integrity: sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-musl@4.62.0:
+ resolution: {integrity: sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-s390x-gnu@4.62.0:
+ resolution: {integrity: sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-gnu@4.62.0:
+ resolution: {integrity: sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-musl@4.62.0:
+ resolution: {integrity: sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-openbsd-x64@4.62.0:
+ resolution: {integrity: sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-openharmony-arm64@4.62.0:
+ resolution: {integrity: sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==}
+ cpu: [arm64]
+ os: [openharmony]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-arm64-msvc@4.62.0:
+ resolution: {integrity: sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-ia32-msvc@4.62.0:
+ resolution: {integrity: sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-gnu@4.62.0:
+ resolution: {integrity: sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-msvc@4.62.0:
+ resolution: {integrity: sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@types/estree@1.0.9:
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+ dev: true
+
+ /@vitejs/plugin-vue@5.2.4(vite@6.4.3)(vue@3.5.38):
+ resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ peerDependencies:
+ vite: ^5.0.0 || ^6.0.0
+ vue: ^3.2.25
+ dependencies:
+ vite: 6.4.3
+ vue: 3.5.38(typescript@5.9.3)
+ dev: true
+
+ /@volar/language-core@2.4.15:
+ resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==}
+ dependencies:
+ '@volar/source-map': 2.4.15
+ dev: true
+
+ /@volar/source-map@2.4.15:
+ resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==}
+ dev: true
+
+ /@volar/typescript@2.4.15:
+ resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==}
+ dependencies:
+ '@volar/language-core': 2.4.15
+ path-browserify: 1.0.1
+ vscode-uri: 3.1.0
+ dev: true
+
+ /@vue/compiler-core@3.5.38:
+ resolution: {integrity: sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==}
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@vue/shared': 3.5.38
+ entities: 7.0.1
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
+ /@vue/compiler-dom@3.5.38:
+ resolution: {integrity: sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==}
+ dependencies:
+ '@vue/compiler-core': 3.5.38
+ '@vue/shared': 3.5.38
+
+ /@vue/compiler-sfc@3.5.38:
+ resolution: {integrity: sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==}
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@vue/compiler-core': 3.5.38
+ '@vue/compiler-dom': 3.5.38
+ '@vue/compiler-ssr': 3.5.38
+ '@vue/shared': 3.5.38
+ estree-walker: 2.0.2
+ magic-string: 0.30.21
+ postcss: 8.5.15
+ source-map-js: 1.2.1
+
+ /@vue/compiler-ssr@3.5.38:
+ resolution: {integrity: sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==}
+ dependencies:
+ '@vue/compiler-dom': 3.5.38
+ '@vue/shared': 3.5.38
+
+ /@vue/compiler-vue2@2.7.16:
+ resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
+ dependencies:
+ de-indent: 1.0.2
+ he: 1.2.0
+ dev: true
+
+ /@vue/language-core@2.2.12(typescript@5.9.3):
+ resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@volar/language-core': 2.4.15
+ '@vue/compiler-dom': 3.5.38
+ '@vue/compiler-vue2': 2.7.16
+ '@vue/shared': 3.5.38
+ alien-signals: 1.0.13
+ minimatch: 9.0.9
+ muggle-string: 0.4.1
+ path-browserify: 1.0.1
+ typescript: 5.9.3
+ dev: true
+
+ /@vue/reactivity@3.5.38:
+ resolution: {integrity: sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==}
+ dependencies:
+ '@vue/shared': 3.5.38
+
+ /@vue/runtime-core@3.5.38:
+ resolution: {integrity: sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==}
+ dependencies:
+ '@vue/reactivity': 3.5.38
+ '@vue/shared': 3.5.38
+
+ /@vue/runtime-dom@3.5.38:
+ resolution: {integrity: sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==}
+ dependencies:
+ '@vue/reactivity': 3.5.38
+ '@vue/runtime-core': 3.5.38
+ '@vue/shared': 3.5.38
+ csstype: 3.2.3
+
+ /@vue/server-renderer@3.5.38(vue@3.5.38):
+ resolution: {integrity: sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==}
+ peerDependencies:
+ vue: 3.5.38
+ dependencies:
+ '@vue/compiler-ssr': 3.5.38
+ '@vue/shared': 3.5.38
+ vue: 3.5.38(typescript@5.9.3)
+
+ /@vue/shared@3.5.38:
+ resolution: {integrity: sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==}
+
+ /@ztools-center/ztools-api-types@1.0.3:
+ resolution: {integrity: sha512-dv1eOAIasAupqKaQL/gESk1i2+RebdM/1gvZhrvH2D/bo3enCUsAGJ8nrHnlCLBSOGB81eC/SU0IH8BNsUlmvA==}
+ dev: true
+
+ /alien-signals@1.0.13:
+ resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==}
+ dev: true
+
+ /balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ dev: true
+
+ /brace-expansion@2.1.1:
+ resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
+ /codemirror@6.0.2:
+ resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/commands': 6.10.3
+ '@codemirror/language': 6.12.3
+ '@codemirror/lint': 6.9.7
+ '@codemirror/search': 6.7.1
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.43.1
+ dev: false
+
+ /crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+ dev: false
+
+ /csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ /de-indent@1.0.2:
+ resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
+ dev: true
+
+ /entities@7.0.1:
+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
+ engines: {node: '>=0.12'}
+
+ /esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+ dev: true
+
+ /estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ /fdir@6.5.0(picomatch@4.0.4):
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+ dependencies:
+ picomatch: 4.0.4
+ dev: true
+
+ /fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+ dev: true
+
+ /magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ /minimatch@9.0.9:
+ resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ brace-expansion: 2.1.1
+ dev: true
+
+ /muggle-string@0.4.1:
+ resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
+ dev: true
+
+ /nanoid@3.3.12:
+ resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ /path-browserify@1.0.1:
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+ dev: true
+
+ /picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ /picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /postcss@8.5.15:
+ resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.12
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ /rollup@4.62.0:
+ resolution: {integrity: sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+ dependencies:
+ '@types/estree': 1.0.9
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.62.0
+ '@rollup/rollup-android-arm64': 4.62.0
+ '@rollup/rollup-darwin-arm64': 4.62.0
+ '@rollup/rollup-darwin-x64': 4.62.0
+ '@rollup/rollup-freebsd-arm64': 4.62.0
+ '@rollup/rollup-freebsd-x64': 4.62.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.62.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.62.0
+ '@rollup/rollup-linux-arm64-gnu': 4.62.0
+ '@rollup/rollup-linux-arm64-musl': 4.62.0
+ '@rollup/rollup-linux-loong64-gnu': 4.62.0
+ '@rollup/rollup-linux-loong64-musl': 4.62.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.62.0
+ '@rollup/rollup-linux-ppc64-musl': 4.62.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.62.0
+ '@rollup/rollup-linux-riscv64-musl': 4.62.0
+ '@rollup/rollup-linux-s390x-gnu': 4.62.0
+ '@rollup/rollup-linux-x64-gnu': 4.62.0
+ '@rollup/rollup-linux-x64-musl': 4.62.0
+ '@rollup/rollup-openbsd-x64': 4.62.0
+ '@rollup/rollup-openharmony-arm64': 4.62.0
+ '@rollup/rollup-win32-arm64-msvc': 4.62.0
+ '@rollup/rollup-win32-ia32-msvc': 4.62.0
+ '@rollup/rollup-win32-x64-gnu': 4.62.0
+ '@rollup/rollup-win32-x64-msvc': 4.62.0
+ fsevents: 2.3.3
+ dev: true
+
+ /source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ /style-mod@4.1.3:
+ resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
+ dev: false
+
+ /tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+ dev: true
+
+ /typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ /vite@6.4.3:
+ resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+ postcss: 8.5.15
+ rollup: 4.62.0
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
+ dev: true
+
+ /vue-tsc@2.2.12(typescript@5.9.3):
+ resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==}
+ hasBin: true
+ peerDependencies:
+ typescript: '>=5.0.0'
+ dependencies:
+ '@volar/typescript': 2.4.15
+ '@vue/language-core': 2.2.12(typescript@5.9.3)
+ typescript: 5.9.3
+ dev: true
+
+ /vue@3.5.38(typescript@5.9.3):
+ resolution: {integrity: sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@vue/compiler-dom': 3.5.38
+ '@vue/compiler-sfc': 3.5.38
+ '@vue/runtime-dom': 3.5.38
+ '@vue/server-renderer': 3.5.38(vue@3.5.38)
+ '@vue/shared': 3.5.38
+ typescript: 5.9.3
+
+ /w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+ dev: false
diff --git a/plugins/skill-hub/public/app-icon.png b/plugins/skill-hub/public/app-icon.png
new file mode 100644
index 00000000..826ee103
Binary files /dev/null and b/plugins/skill-hub/public/app-icon.png differ
diff --git a/plugins/skill-hub/public/logo.png b/plugins/skill-hub/public/logo.png
new file mode 100644
index 00000000..826ee103
Binary files /dev/null and b/plugins/skill-hub/public/logo.png differ
diff --git a/plugins/skill-hub/public/plugin.json b/plugins/skill-hub/public/plugin.json
new file mode 100644
index 00000000..38eaf4b8
--- /dev/null
+++ b/plugins/skill-hub/public/plugin.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "../node_modules/@ztools-center/ztools-api-types/resource/ztools.schema.json",
+ "name": "skill-hub",
+ "title": "Skill Hub",
+ "description": "Skill 管理技能中心 — Skill 商店浏览下载、项目/Agent 扫描、一键分发",
+ "author": "zibo",
+ "version": "1.0.0",
+ "main": "index.html",
+ "preload": "preload/services.js",
+ "logo": "logo.png",
+ "development": {
+ "main": "http://localhost:5173"
+ },
+ "features": [
+ {
+ "code": "skills",
+ "explain": "Skill Hub — 技能商店浏览下载、项目/Agent 扫描、一键分发",
+ "icon": "logo.png",
+ "cmds": ["skills"]
+ }
+ ]
+}
diff --git a/plugins/skill-hub/public/preload/package-lock.json b/plugins/skill-hub/public/preload/package-lock.json
new file mode 100644
index 00000000..0b59dbe5
--- /dev/null
+++ b/plugins/skill-hub/public/preload/package-lock.json
@@ -0,0 +1,243 @@
+{
+ "name": "preload",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "adm-zip": "^0.5.17",
+ "extract-zip": "^2.0.1",
+ "tar": "^7.5.16"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "25.9.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz",
+ "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "undici-types": ">=7.24.0 <7.24.7"
+ }
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/adm-zip": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz",
+ "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.5.16",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
+ "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ }
+ }
+}
diff --git a/plugins/skill-hub/public/preload/package.json b/plugins/skill-hub/public/preload/package.json
new file mode 100644
index 00000000..fa097681
--- /dev/null
+++ b/plugins/skill-hub/public/preload/package.json
@@ -0,0 +1,8 @@
+{
+ "type": "commonjs",
+ "dependencies": {
+ "adm-zip": "^0.5.17",
+ "extract-zip": "^2.0.1",
+ "tar": "^7.5.16"
+ }
+}
diff --git a/plugins/skill-hub/public/preload/services.js b/plugins/skill-hub/public/preload/services.js
new file mode 100644
index 00000000..4c3a69e0
--- /dev/null
+++ b/plugins/skill-hub/public/preload/services.js
@@ -0,0 +1,442 @@
+const fs = require('node:fs')
+const path = require('node:path')
+const https = require('node:https')
+const http = require('node:http')
+const os = require('node:os')
+const { execFile } = require('node:child_process')
+const AdmZip = require('adm-zip')
+function homeDir() {
+ return os.homedir()
+}
+
+function expandPath(p) {
+ if (!p) return ''
+ return p.replace(/^~/, homeDir())
+}
+
+function isWindows() {
+ return process.platform === 'win32'
+}
+
+function isMacOS() {
+ return process.platform === 'darwin'
+}
+
+function _downloadFileInternal(url, redirectCount) {
+ return new Promise((resolve, reject) => {
+ if (redirectCount > 5) {
+ return reject(new Error('Too many redirects'))
+ }
+ const client = url.startsWith('https') ? https : http
+ const chunks = []
+ client.get(url, { headers: { 'User-Agent': 'skill-hub' } }, (res) => {
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
+ return resolve(_downloadFileInternal(res.headers.location, redirectCount + 1))
+ }
+ if (res.statusCode !== 200) {
+ return reject(new Error(`Download failed: ${res.statusCode}`))
+ }
+ res.on('data', (c) => chunks.push(c))
+ res.on('end', () => resolve(Buffer.concat(chunks)))
+ res.on('error', reject)
+ }).on('error', reject)
+ })
+}
+
+function _fetchGitHubTextInternal(url, token, redirectCount) {
+ return new Promise((resolve, reject) => {
+ if (redirectCount > 5) {
+ return reject(new Error('Too many redirects'))
+ }
+ const headers = {
+ Accept: 'application/vnd.github.v3.raw',
+ 'User-Agent': 'skill-hub',
+ }
+ if (token) headers['Authorization'] = `Bearer ${token}`
+ const client = url.startsWith('https') ? https : http
+ client.get(url, { headers }, (res) => {
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
+ return resolve(_fetchGitHubTextInternal(res.headers.location, token, redirectCount + 1))
+ }
+ let data = ''
+ res.on('data', (c) => (data += c))
+ res.on('end', () => {
+ if (res.statusCode !== 200) reject(new Error(`GitHub raw: ${res.statusCode}`))
+ else resolve(data)
+ })
+ res.on('error', reject)
+ }).on('error', reject)
+ })
+}
+
+window.services = {
+ // === 原始保留服务 ===
+ readFile(file) {
+ try {
+ return fs.readFileSync(expandPath(file), { encoding: 'utf-8' })
+ } catch {
+ return null
+ }
+ },
+ // === 路径工具 ===
+ expandPath,
+ homeDir,
+ isWindows,
+ isMacOS,
+ pathJoin: (...parts) => path.join(...parts),
+ pathExists(p) {
+ const full = expandPath(p)
+ try {
+ fs.lstatSync(full)
+ return true
+ } catch {
+ return false
+ }
+ },
+ mkdir(dir) {
+ fs.mkdirSync(expandPath(dir), { recursive: true })
+ },
+ openFolder(dir) {
+ const fullPath = expandPath(dir)
+ if (!fs.existsSync(fullPath)) return
+ if (isWindows()) {
+ execFile('explorer', [fullPath])
+ } else if (process.platform === 'darwin') {
+ execFile('open', [fullPath])
+ } else {
+ execFile('xdg-open', [fullPath])
+ }
+ },
+ readDir(dir) {
+ try {
+ return fs.readdirSync(expandPath(dir), { withFileTypes: true }).map((d) => ({
+ name: d.name,
+ path: path.join(expandPath(dir), d.name),
+ isDirectory: d.isDirectory(),
+ isFile: d.isFile(),
+ isSymlink: d.isSymbolicLink(),
+ }))
+ } catch {
+ return []
+ }
+ },
+ readFileText(filePath) {
+ try {
+ return fs.readFileSync(expandPath(filePath), { encoding: 'utf-8' })
+ } catch {
+ return null
+ }
+ },
+ writeFile(filePath, content) {
+ const full = expandPath(filePath)
+ this.mkdir(path.dirname(full))
+ fs.writeFileSync(full, content, { encoding: 'utf-8' })
+ },
+ removeFile(filePath) {
+ const full = expandPath(filePath)
+ try {
+ fs.lstatSync(full)
+ fs.rmSync(full, { recursive: true })
+ } catch {}
+ },
+ copyFile(src, dest) {
+ const fullSrc = expandPath(src)
+ const fullDest = expandPath(dest)
+ this.mkdir(path.dirname(fullDest))
+ const tempDest = fullDest + `.tmp.${Date.now()}`
+ try {
+ fs.cpSync(fullSrc, tempDest, { recursive: true, dereference: true })
+ try {
+ fs.lstatSync(fullDest)
+ fs.rmSync(fullDest, { recursive: true })
+ } catch {}
+ fs.renameSync(tempDest, fullDest)
+ } catch (err) {
+ try { fs.rmSync(tempDest, { recursive: true, force: true }) } catch {}
+ throw err
+ }
+ },
+ stat(p) {
+ try {
+ const s = fs.statSync(expandPath(p))
+ return { exists: true, isDirectory: s.isDirectory(), isFile: s.isFile(), isSymlink: s.isSymbolicLink(), size: s.size, mtime: s.mtime.toISOString() }
+ } catch {
+ return { exists: false }
+ }
+ },
+
+ // === Symlink 支持 ===
+ createSymlink(target, linkPath) {
+ const fullTarget = expandPath(target)
+ const fullLink = expandPath(linkPath)
+ this.mkdir(path.dirname(fullLink))
+ try {
+ fs.lstatSync(fullLink)
+ fs.rmSync(fullLink, { recursive: true })
+ } catch {}
+ if (isWindows()) {
+ try {
+ const stats = fs.statSync(fullTarget)
+ if (stats.isDirectory()) {
+ fs.symlinkSync(fullTarget, fullLink, 'junction')
+ } else {
+ fs.symlinkSync(fullTarget, fullLink, 'file')
+ }
+ } catch (e) {
+ throw new Error(`Cannot create symlink: target "${fullTarget}" does not exist or is not accessible`)
+ }
+ } else {
+ fs.symlinkSync(fullTarget, fullLink)
+ }
+ return fullLink
+ },
+ // === 下载 ===
+ downloadFile(url) {
+ return _downloadFileInternal(url, 0)
+ },
+ fetchGitHubText(url, token) {
+ return _fetchGitHubTextInternal(url, token, 0)
+ },
+
+ // === 压缩包解压 ===
+ extractBufferZip(buffer, dest) {
+ const fullDest = expandPath(dest)
+ this.mkdir(fullDest)
+ const zip = new AdmZip(Buffer.from(buffer))
+ const entries = zip.getEntries()
+ for (const entry of entries) {
+ const entryPath = entry.entryName
+ const resolved = path.resolve(fullDest, entryPath)
+ if (!resolved.startsWith(fullDest + path.sep) && resolved !== fullDest) {
+ throw new Error(`Zip Slip detected: "${entryPath}" escapes destination`)
+ }
+ }
+ zip.extractAllTo(fullDest, true)
+ return fullDest
+ },
+ // === SKILL.md 扫描 ===
+ scanForSkills(rootDir) {
+ const fullRoot = expandPath(rootDir)
+ const results = []
+ if (!fs.existsSync(fullRoot)) return results
+ try {
+ const entries = fs.readdirSync(fullRoot, { withFileTypes: true })
+ for (const entry of entries) {
+ const entryPath = path.join(fullRoot, entry.name)
+ let isDir = entry.isDirectory()
+ let isSymlinkEntry = entry.isSymbolicLink ? entry.isSymbolicLink() : false
+
+ if (!isDir && !isSymlinkEntry) {
+ try {
+ const st = fs.statSync(entryPath)
+ isDir = st.isDirectory()
+ } catch {}
+ }
+
+ if (isDir || isSymlinkEntry) {
+ const skillPath = path.join(entryPath, 'SKILL.md')
+ if (fs.existsSync(skillPath)) {
+ const content = fs.readFileSync(skillPath, 'utf-8')
+ let isSymlink = isSymlinkEntry
+ if (!isSymlink) {
+ try {
+ const lstats = fs.lstatSync(entryPath)
+ isSymlink = lstats.isSymbolicLink()
+ } catch {}
+ }
+ results.push({
+ name: entry.name,
+ dir: entryPath,
+ skillFile: skillPath,
+ content,
+ isSymlink,
+ manifest: parseSkillFrontmatter(content),
+ })
+ }
+ }
+ }
+ } catch {}
+ return results
+ },
+ scanForSkillFiles(dirs) {
+ const all = []
+ for (const dir of dirs) {
+ const found = this.scanForSkills(dir)
+ all.push(...found)
+ }
+ return all
+ },
+ parseSkillFile(filePath) {
+ const full = expandPath(filePath)
+ if (!fs.existsSync(full)) return null
+ const content = fs.readFileSync(full, 'utf-8')
+ return { content, manifest: parseSkillFrontmatter(content) }
+ },
+
+ // === Skill Update Check ===
+ async checkSkillUpdate(repo, skillPath, token, branch) {
+ const pathCandidates = [
+ skillPath ? `${skillPath}/SKILL.md` : null,
+ skillPath ? `skills/${skillPath}/SKILL.md` : null,
+ skillPath ? `agent-skills/${skillPath}/SKILL.md` : null,
+ 'SKILL.md',
+ ].filter(Boolean)
+ const branches = branch ? [branch, 'main', 'master'] : ['main', 'master']
+ const tried = new Set()
+ for (const b of branches) {
+ if (tried.has(b)) continue
+ tried.add(b)
+ for (const p of pathCandidates) {
+ const url = `https://raw.githubusercontent.com/${repo}/${b}/${p}`
+ try {
+ const text = await this.fetchGitHubText(url, token)
+ return text
+ } catch (err) {
+ if (err.message?.includes('404')) continue
+ throw err
+ }
+ }
+ }
+ return null
+ },
+ async updateSkillFromGitHub(repo, skillPath, targetDir, token, branch) {
+ let buffer
+ const branches = branch ? [branch, 'main', 'master'] : ['main', 'master']
+ const tried = new Set()
+ for (const b of branches) {
+ if (tried.has(b)) continue
+ tried.add(b)
+ try {
+ buffer = await this.downloadFile(`https://api.github.com/repos/${repo}/zipball/${b}`)
+ break
+ } catch (err) {
+ if (b === branches[branches.length - 1]) throw err
+ }
+ }
+ const tempDir = path.join(homeDir(), '.cache/skill-hub/')
+ fs.mkdirSync(tempDir, { recursive: true })
+ const extractDir = path.join(tempDir, `update-${Date.now()}`)
+ if (fs.existsSync(extractDir)) fs.rmSync(extractDir, { recursive: true })
+ this.extractBufferZip(buffer, extractDir)
+ const extractedItems = this.readDir(extractDir)
+ const rootDir = extractedItems.find((d) => d.isDirectory)
+ const sourceRoot = rootDir ? rootDir.path : extractDir
+ const pathCandidates = [
+ skillPath,
+ `skills/${skillPath}`,
+ `agent-skills/${skillPath}`,
+ ]
+ let skillSourceDir = ''
+ for (const p of pathCandidates) {
+ const candidate = path.join(sourceRoot, p)
+ if (fs.existsSync(candidate)) { skillSourceDir = candidate; break }
+ }
+ if (!skillSourceDir) { fs.rmSync(extractDir, { recursive: true }); return false }
+ if (fs.existsSync(targetDir)) fs.rmSync(targetDir, { recursive: true })
+ fs.mkdirSync(targetDir, { recursive: true })
+ this.copyFile(skillSourceDir, targetDir)
+ fs.rmSync(extractDir, { recursive: true })
+ return true
+ },
+
+ // === Skills Repo ===
+ getStateDir() {
+ const stateDir = path.join(window.ztools.getPath('userData'), 'skills-repo')
+ if (!fs.existsSync(stateDir)) {
+ fs.mkdirSync(stateDir, { recursive: true })
+ }
+ return stateDir
+ },
+}
+
+function parseSkillFrontmatter(content) {
+ const manifest = { name: '', description: '', author: '', tags: [], format: 'opencode', language: 'en' }
+ const normalized = content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n')
+ const match = normalized.match(/^---\n([\s\S]*?)\n---/)
+ if (!match) {
+ manifest.name = path.basename(process.cwd())
+ } else {
+ const lines = match[1].split('\n')
+ let i = 0
+ while (i < lines.length) {
+ const line = lines[i]
+ const kv = line.match(/^(\w+):\s*(.*)$/)
+ if (!kv) { i++; continue }
+ const [, key, val] = kv
+ if (key === 'tags') {
+ manifest.tags = val.replace(/[[\]']/g, '').split(',').map((t) => t.trim()).filter(Boolean)
+ } else if (key === 'name') manifest.name = val.trim()
+ else if (key === 'description') {
+ let d = val.trim()
+ const blockMatch = d.match(/^([>|])([+-]?)$/)
+ if (blockMatch) {
+ const style = blockMatch[1]
+ const chomp = blockMatch[2]
+ const blockLines = []
+ i++
+ while (i < lines.length) {
+ const next = lines[i]
+ if (next === '' || next.startsWith(' ') || next.startsWith('\t')) {
+ blockLines.push(next.trimEnd())
+ i++
+ } else {
+ break
+ }
+ }
+ i--
+ if (style === '>') {
+ const paragraphs = []
+ let current = []
+ for (const bl of blockLines) {
+ if (bl === '') {
+ if (current.length) { paragraphs.push(current.join(' ')); current = [] }
+ } else {
+ current.push(bl)
+ }
+ }
+ if (current.length) paragraphs.push(current.join(' '))
+ d = paragraphs.join('\n\n')
+ if (chomp === '+' && blockLines.length > 0) {
+ const trailing = blockLines.reduce((n, l) => l === '' ? n + 1 : 0, 0)
+ for (let t = 0; t < trailing; t++) d += '\n'
+ }
+ } else {
+ d = blockLines.join('\n').trimEnd()
+ }
+ } else if (d === '' || d === '""' || d === "''") {
+ const nextIdx = i + 1
+ if (nextIdx < lines.length && (lines[nextIdx].startsWith(' ') || lines[nextIdx].startsWith('\t'))) {
+ const blockLines = []
+ i++
+ while (i < lines.length) {
+ const curr = lines[i]
+ if (curr.startsWith(' ') || curr.startsWith('\t')) {
+ blockLines.push(curr.trimEnd())
+ i++
+ } else {
+ break
+ }
+ }
+ i--
+ d = blockLines.join(' ').replace(/\s+/g, ' ').trim()
+ }
+ }
+ if (d.startsWith('[') && d.endsWith(']')) {
+ d = d.slice(1, -1).trim()
+ }
+ manifest.description = d
+ }
+ else if (key === 'author') manifest.author = val.trim()
+ else if (key === 'format') manifest.format = val.trim()
+ else if (key === 'language') manifest.language = val.trim()
+ i++
+ }
+ }
+ if (!manifest.description) {
+ const bodyStart = match ? match[0].length : 0
+ const body = normalized.slice(bodyStart).trim()
+ const firstLine = body.split('\n').find((l) => l.trim() && !l.startsWith('#'))
+ if (firstLine) manifest.description = firstLine.trim().slice(0, 200)
+ }
+ return manifest
+}
diff --git a/plugins/skill-hub/scripts/build-zip.cjs b/plugins/skill-hub/scripts/build-zip.cjs
new file mode 100644
index 00000000..a8442d94
--- /dev/null
+++ b/plugins/skill-hub/scripts/build-zip.cjs
@@ -0,0 +1,58 @@
+const fs = require('fs')
+const path = require('path')
+const os = require('os')
+const { execSync } = require('child_process')
+
+const projectRoot = path.resolve(__dirname, '..')
+const distDir = path.join(projectRoot, 'dist')
+
+if (!fs.existsSync(distDir)) {
+ console.error('dist/ directory not found. Run `pnpm build` first.')
+ process.exit(1)
+}
+
+const pluginJson = JSON.parse(fs.readFileSync(path.join(distDir, 'plugin.json'), 'utf-8'))
+const pluginName = pluginJson.name || 'skill-hub'
+const version = pluginJson.version || '1.0.0'
+const zipName = `${pluginName}-${version}.zip`
+const AdmZip = require(path.join(projectRoot, 'public', 'preload', 'node_modules', 'adm-zip'))
+const zip = new AdmZip()
+
+function addDirToZip(zipInstance, dirPath, zipPath) {
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true })
+ for (const entry of entries) {
+ const fullPath = path.join(dirPath, entry.name)
+ const entryPath = zipPath ? `${zipPath}/${entry.name}` : entry.name
+ if (entry.isDirectory()) {
+ addDirToZip(zipInstance, fullPath, entryPath)
+ } else {
+ zipInstance.addLocalFile(fullPath, path.dirname(entryPath) || '.')
+ }
+ }
+}
+
+addDirToZip(zip, distDir, '')
+
+const tmpPath = path.join(distDir, zipName)
+zip.writeZip(tmpPath)
+
+function getDownloadsDir() {
+ if (process.platform === 'win32') {
+ try {
+ const psScript = `(New-Object -ComObject Shell.Application).NameSpace('Shell:Downloads').Self.Path`
+ const result = execSync(`powershell -NoProfile -Command "${psScript}"`, { encoding: 'utf-8' }).trim()
+ if (result && fs.existsSync(result)) return result
+ } catch {}
+ }
+ return path.join(os.homedir(), 'Downloads')
+}
+
+const downloadsDir = getDownloadsDir()
+fs.mkdirSync(downloadsDir, { recursive: true })
+const outputPath = path.join(downloadsDir, zipName)
+fs.copyFileSync(tmpPath, outputPath)
+fs.unlinkSync(tmpPath)
+
+const stats = fs.statSync(outputPath)
+const sizeKB = (stats.size / 1024).toFixed(1)
+console.log(`Plugin zip moved to: ${outputPath} (${sizeKB} KB)`)
diff --git a/plugins/skill-hub/src/App.vue b/plugins/skill-hub/src/App.vue
new file mode 100644
index 00000000..8caec5a7
--- /dev/null
+++ b/plugins/skill-hub/src/App.vue
@@ -0,0 +1,893 @@
+
+
+
+
+
+
+
+
{ showImportModal = false; navigate(route) }" />
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/assets/platforms/antigravity.svg b/plugins/skill-hub/src/assets/platforms/antigravity.svg
new file mode 100644
index 00000000..093cce54
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/antigravity.svg
@@ -0,0 +1 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/cherry-studio.png b/plugins/skill-hub/src/assets/platforms/cherry-studio.png
new file mode 100644
index 00000000..0f15b3af
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/cherry-studio.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/claude.png b/plugins/skill-hub/src/assets/platforms/claude.png
new file mode 100644
index 00000000..64b6a212
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/claude.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/claude.svg b/plugins/skill-hub/src/assets/platforms/claude.svg
new file mode 100644
index 00000000..b6aa3242
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/claude.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/cline.svg b/plugins/skill-hub/src/assets/platforms/cline.svg
new file mode 100644
index 00000000..33474a8c
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/cline.svg
@@ -0,0 +1,16 @@
+
+
diff --git a/plugins/skill-hub/src/assets/platforms/codebuddy-light.svg b/plugins/skill-hub/src/assets/platforms/codebuddy-light.svg
new file mode 100644
index 00000000..b453b877
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/codebuddy-light.svg
@@ -0,0 +1,37 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/codex.png b/plugins/skill-hub/src/assets/platforms/codex.png
new file mode 100644
index 00000000..91bc9f53
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/codex.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/copilot.png b/plugins/skill-hub/src/assets/platforms/copilot.png
new file mode 100644
index 00000000..c9adf868
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/copilot.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/copilot.svg b/plugins/skill-hub/src/assets/platforms/copilot.svg
new file mode 100644
index 00000000..3095f2ee
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/copilot.svg
@@ -0,0 +1,8 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/cursor.png b/plugins/skill-hub/src/assets/platforms/cursor.png
new file mode 100644
index 00000000..86fe0427
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/cursor.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/gemini.png b/plugins/skill-hub/src/assets/platforms/gemini.png
new file mode 100644
index 00000000..15aaef48
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/gemini.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/gemini.svg b/plugins/skill-hub/src/assets/platforms/gemini.svg
new file mode 100644
index 00000000..bbd87eab
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/gemini.svg
@@ -0,0 +1,24 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/generic.svg b/plugins/skill-hub/src/assets/platforms/generic.svg
new file mode 100644
index 00000000..8c80ba27
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/generic.svg
@@ -0,0 +1 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/hermes.svg b/plugins/skill-hub/src/assets/platforms/hermes.svg
new file mode 100644
index 00000000..30219254
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/hermes.svg
@@ -0,0 +1,25 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/kilo-light.svg b/plugins/skill-hub/src/assets/platforms/kilo-light.svg
new file mode 100644
index 00000000..53469c85
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/kilo-light.svg
@@ -0,0 +1,4 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/kiro.png b/plugins/skill-hub/src/assets/platforms/kiro.png
new file mode 100644
index 00000000..04527185
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/kiro.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/mimo.png b/plugins/skill-hub/src/assets/platforms/mimo.png
new file mode 100644
index 00000000..5570c340
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/mimo.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/openclaw.png b/plugins/skill-hub/src/assets/platforms/openclaw.png
new file mode 100644
index 00000000..71781843
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/openclaw.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/opencode.png b/plugins/skill-hub/src/assets/platforms/opencode.png
new file mode 100644
index 00000000..71b86683
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/opencode.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/opencode.svg b/plugins/skill-hub/src/assets/platforms/opencode.svg
new file mode 100644
index 00000000..6100225a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/opencode.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/skill-hub/src/assets/platforms/qoder.png b/plugins/skill-hub/src/assets/platforms/qoder.png
new file mode 100644
index 00000000..17c53f9d
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/qoder.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/qoder.svg b/plugins/skill-hub/src/assets/platforms/qoder.svg
new file mode 100644
index 00000000..9c47ae67
--- /dev/null
+++ b/plugins/skill-hub/src/assets/platforms/qoder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/skill-hub/src/assets/platforms/qoderwork.png b/plugins/skill-hub/src/assets/platforms/qoderwork.png
new file mode 100644
index 00000000..17c53f9d
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/qoderwork.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/skills-sh-favicon.ico b/plugins/skill-hub/src/assets/platforms/skills-sh-favicon.ico
new file mode 100644
index 00000000..718d6fea
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/skills-sh-favicon.ico differ
diff --git a/plugins/skill-hub/src/assets/platforms/trae.png b/plugins/skill-hub/src/assets/platforms/trae.png
new file mode 100644
index 00000000..c27ce0c8
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/trae.png differ
diff --git a/plugins/skill-hub/src/assets/platforms/windsurf.png b/plugins/skill-hub/src/assets/platforms/windsurf.png
new file mode 100644
index 00000000..767ad05c
Binary files /dev/null and b/plugins/skill-hub/src/assets/platforms/windsurf.png differ
diff --git a/plugins/skill-hub/src/assets/providers/302ai.svg b/plugins/skill-hub/src/assets/providers/302ai.svg
new file mode 100644
index 00000000..8144a108
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/302ai.svg
@@ -0,0 +1,17 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/3min-top.svg b/plugins/skill-hub/src/assets/providers/3min-top.svg
new file mode 100644
index 00000000..c49d5c23
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/3min-top.svg
@@ -0,0 +1,11 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/abacus.svg b/plugins/skill-hub/src/assets/providers/abacus.svg
new file mode 100644
index 00000000..f17dd1e5
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/abacus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/skill-hub/src/assets/providers/ai-only.svg b/plugins/skill-hub/src/assets/providers/ai-only.svg
new file mode 100644
index 00000000..b23b7c63
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ai-only.svg
@@ -0,0 +1,6 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ai-studio.svg b/plugins/skill-hub/src/assets/providers/ai-studio.svg
new file mode 100644
index 00000000..f0a4976b
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ai-studio.svg
@@ -0,0 +1,17 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ai21.svg b/plugins/skill-hub/src/assets/providers/ai21.svg
new file mode 100644
index 00000000..210bb2fb
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ai21.svg
@@ -0,0 +1,4 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/aihubmix.svg b/plugins/skill-hub/src/assets/providers/aihubmix.svg
new file mode 100644
index 00000000..d08badd7
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/aihubmix.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/aionlabs.svg b/plugins/skill-hub/src/assets/providers/aionlabs.svg
new file mode 100644
index 00000000..570cb362
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/aionlabs.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/skill-hub/src/assets/providers/alayanew.svg b/plugins/skill-hub/src/assets/providers/alayanew.svg
new file mode 100644
index 00000000..ce431297
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/alayanew.svg
@@ -0,0 +1,6 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/allenai.svg b/plugins/skill-hub/src/assets/providers/allenai.svg
new file mode 100644
index 00000000..9284bda4
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/allenai.svg
@@ -0,0 +1,8 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/anthropic.svg b/plugins/skill-hub/src/assets/providers/anthropic.svg
new file mode 100644
index 00000000..c270b3dc
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/anthropic.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/application.svg b/plugins/skill-hub/src/assets/providers/application.svg
new file mode 100644
index 00000000..2d8de64d
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/application.svg
@@ -0,0 +1,6 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/arcee-ai.svg b/plugins/skill-hub/src/assets/providers/arcee-ai.svg
new file mode 100644
index 00000000..3068ed52
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/arcee-ai.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/aws-bedrock.svg b/plugins/skill-hub/src/assets/providers/aws-bedrock.svg
new file mode 100644
index 00000000..cb9ae000
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/aws-bedrock.svg
@@ -0,0 +1,25 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/azureai.svg b/plugins/skill-hub/src/assets/providers/azureai.svg
new file mode 100644
index 00000000..1ed8fd82
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/azureai.svg
@@ -0,0 +1,54 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/baai.svg b/plugins/skill-hub/src/assets/providers/baai.svg
new file mode 100644
index 00000000..c7d5cdce
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/baai.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/baichuan.svg b/plugins/skill-hub/src/assets/providers/baichuan.svg
new file mode 100644
index 00000000..c2af3b45
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/baichuan.svg
@@ -0,0 +1,9 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/baidu-cloud.svg b/plugins/skill-hub/src/assets/providers/baidu-cloud.svg
new file mode 100644
index 00000000..0d51263b
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/baidu-cloud.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/baidu.svg b/plugins/skill-hub/src/assets/providers/baidu.svg
new file mode 100644
index 00000000..9521ffa1
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/baidu.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/bailian.svg b/plugins/skill-hub/src/assets/providers/bailian.svg
new file mode 100644
index 00000000..a11e9197
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/bailian.svg
@@ -0,0 +1,9 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/bfl.svg b/plugins/skill-hub/src/assets/providers/bfl.svg
new file mode 100644
index 00000000..bfae844a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/bfl.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/bing.svg b/plugins/skill-hub/src/assets/providers/bing.svg
new file mode 100644
index 00000000..579fc7be
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/bing.svg
@@ -0,0 +1,24 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/bocha.svg b/plugins/skill-hub/src/assets/providers/bocha.svg
new file mode 100644
index 00000000..ddc96e5f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/bocha.svg
@@ -0,0 +1,6 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/bolt-new.svg b/plugins/skill-hub/src/assets/providers/bolt-new.svg
new file mode 100644
index 00000000..63cfed78
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/bolt-new.svg
@@ -0,0 +1,23 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/burncloud.svg b/plugins/skill-hub/src/assets/providers/burncloud.svg
new file mode 100644
index 00000000..908ae5b1
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/burncloud.svg
@@ -0,0 +1,9 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/bytedance.svg b/plugins/skill-hub/src/assets/providers/bytedance.svg
new file mode 100644
index 00000000..ba9b9ad4
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/bytedance.svg
@@ -0,0 +1,6 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/cephalon.svg b/plugins/skill-hub/src/assets/providers/cephalon.svg
new file mode 100644
index 00000000..7cc15232
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/cephalon.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/cerebras.svg b/plugins/skill-hub/src/assets/providers/cerebras.svg
new file mode 100644
index 00000000..2ddbfc10
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/cerebras.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/cherryin.svg b/plugins/skill-hub/src/assets/providers/cherryin.svg
new file mode 100644
index 00000000..3cdc9d3f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/cherryin.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/cloudflare.svg b/plugins/skill-hub/src/assets/providers/cloudflare.svg
new file mode 100644
index 00000000..c7819b12
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/cloudflare.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/deepseek.svg b/plugins/skill-hub/src/assets/providers/deepseek.svg
new file mode 100644
index 00000000..39203e7f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/deepseek.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/devv.svg b/plugins/skill-hub/src/assets/providers/devv.svg
new file mode 100644
index 00000000..d322a443
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/devv.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/dify.svg b/plugins/skill-hub/src/assets/providers/dify.svg
new file mode 100644
index 00000000..765dd71d
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/dify.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/dmxapi.svg b/plugins/skill-hub/src/assets/providers/dmxapi.svg
new file mode 100644
index 00000000..c6ac368f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/dmxapi.svg
@@ -0,0 +1,17 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/doc2x.svg b/plugins/skill-hub/src/assets/providers/doc2x.svg
new file mode 100644
index 00000000..97a55a8c
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/doc2x.svg
@@ -0,0 +1,4 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/dola.svg b/plugins/skill-hub/src/assets/providers/dola.svg
new file mode 100644
index 00000000..f7b3be85
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/dola.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/dolphin-ai.svg b/plugins/skill-hub/src/assets/providers/dolphin-ai.svg
new file mode 100644
index 00000000..30471cc3
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/dolphin-ai.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/plugins/skill-hub/src/assets/providers/doubao.svg b/plugins/skill-hub/src/assets/providers/doubao.svg
new file mode 100644
index 00000000..6c4f2471
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/doubao.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/duck.svg b/plugins/skill-hub/src/assets/providers/duck.svg
new file mode 100644
index 00000000..0ea2053d
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/duck.svg
@@ -0,0 +1,21 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/elevenlabs.svg b/plugins/skill-hub/src/assets/providers/elevenlabs.svg
new file mode 100644
index 00000000..c26cc0a0
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/elevenlabs.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/essential-ai.svg b/plugins/skill-hub/src/assets/providers/essential-ai.svg
new file mode 100644
index 00000000..879fe07d
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/essential-ai.svg
@@ -0,0 +1,5 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/exa.svg b/plugins/skill-hub/src/assets/providers/exa.svg
new file mode 100644
index 00000000..d009cbdf
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/exa.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/felo.svg b/plugins/skill-hub/src/assets/providers/felo.svg
new file mode 100644
index 00000000..453fa6cb
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/felo.svg
@@ -0,0 +1,55 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/fireworks.svg b/plugins/skill-hub/src/assets/providers/fireworks.svg
new file mode 100644
index 00000000..a3dfacdc
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/fireworks.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/flowith.svg b/plugins/skill-hub/src/assets/providers/flowith.svg
new file mode 100644
index 00000000..86916162
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/flowith.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/genspark.svg b/plugins/skill-hub/src/assets/providers/genspark.svg
new file mode 100644
index 00000000..92c16b5e
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/genspark.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/gitee-ai.svg b/plugins/skill-hub/src/assets/providers/gitee-ai.svg
new file mode 100644
index 00000000..0b39dcd8
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/gitee-ai.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/github-copilot.svg b/plugins/skill-hub/src/assets/providers/github-copilot.svg
new file mode 100644
index 00000000..b0566cbb
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/github-copilot.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/github.svg b/plugins/skill-hub/src/assets/providers/github.svg
new file mode 100644
index 00000000..3394ad48
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/github.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/glama.svg b/plugins/skill-hub/src/assets/providers/glama.svg
new file mode 100644
index 00000000..59fed594
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/glama.svg
@@ -0,0 +1,5 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/google.svg b/plugins/skill-hub/src/assets/providers/google.svg
new file mode 100644
index 00000000..d1f0cd3c
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/google.svg
@@ -0,0 +1,156 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/gpustack.svg b/plugins/skill-hub/src/assets/providers/gpustack.svg
new file mode 100644
index 00000000..d7388d24
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/gpustack.svg
@@ -0,0 +1,20 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/graph-rag.svg b/plugins/skill-hub/src/assets/providers/graph-rag.svg
new file mode 100644
index 00000000..25b12445
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/graph-rag.svg
@@ -0,0 +1,11 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/grok.svg b/plugins/skill-hub/src/assets/providers/grok.svg
new file mode 100644
index 00000000..346a99ab
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/grok.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/groq.svg b/plugins/skill-hub/src/assets/providers/groq.svg
new file mode 100644
index 00000000..a8774bf2
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/groq.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/higress.svg b/plugins/skill-hub/src/assets/providers/higress.svg
new file mode 100644
index 00000000..aed25d6a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/higress.svg
@@ -0,0 +1,22 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/huggingface.svg b/plugins/skill-hub/src/assets/providers/huggingface.svg
new file mode 100644
index 00000000..4785042a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/huggingface.svg
@@ -0,0 +1,8 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/hyperbolic.svg b/plugins/skill-hub/src/assets/providers/hyperbolic.svg
new file mode 100644
index 00000000..57dde1f7
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/hyperbolic.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ideogram.svg b/plugins/skill-hub/src/assets/providers/ideogram.svg
new file mode 100644
index 00000000..41d28572
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ideogram.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ima.svg b/plugins/skill-hub/src/assets/providers/ima.svg
new file mode 100644
index 00000000..ed9ed6b4
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ima.svg
@@ -0,0 +1,45 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/inceptionlabs.svg b/plugins/skill-hub/src/assets/providers/inceptionlabs.svg
new file mode 100644
index 00000000..d9422565
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/inceptionlabs.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/infini.svg b/plugins/skill-hub/src/assets/providers/infini.svg
new file mode 100644
index 00000000..b254c02f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/infini.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/inflection.svg b/plugins/skill-hub/src/assets/providers/inflection.svg
new file mode 100644
index 00000000..cfc9c542
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/inflection.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/intel.svg b/plugins/skill-hub/src/assets/providers/intel.svg
new file mode 100644
index 00000000..074d85b8
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/intel.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/internlm.svg b/plugins/skill-hub/src/assets/providers/internlm.svg
new file mode 100644
index 00000000..54f275e5
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/internlm.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/jimeng.svg b/plugins/skill-hub/src/assets/providers/jimeng.svg
new file mode 100644
index 00000000..e34d3aed
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/jimeng.svg
@@ -0,0 +1,70 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/kling.svg b/plugins/skill-hub/src/assets/providers/kling.svg
new file mode 100644
index 00000000..6b2776c3
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/kling.svg
@@ -0,0 +1,33 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/kwaipilot.svg b/plugins/skill-hub/src/assets/providers/kwaipilot.svg
new file mode 100644
index 00000000..1b7e8986
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/kwaipilot.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/lambda.svg b/plugins/skill-hub/src/assets/providers/lambda.svg
new file mode 100644
index 00000000..093250c5
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/lambda.svg
@@ -0,0 +1,17 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/lanyun.svg b/plugins/skill-hub/src/assets/providers/lanyun.svg
new file mode 100644
index 00000000..94639f45
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/lanyun.svg
@@ -0,0 +1,39 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/lepton.svg b/plugins/skill-hub/src/assets/providers/lepton.svg
new file mode 100644
index 00000000..b2bd3d9f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/lepton.svg
@@ -0,0 +1,6 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/lingxi.svg b/plugins/skill-hub/src/assets/providers/lingxi.svg
new file mode 100644
index 00000000..48ba5e7e
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/lingxi.svg
@@ -0,0 +1,38 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/liquid.svg b/plugins/skill-hub/src/assets/providers/liquid.svg
new file mode 100644
index 00000000..c94b9ce0
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/liquid.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/lmstudio.svg b/plugins/skill-hub/src/assets/providers/lmstudio.svg
new file mode 100644
index 00000000..6cd6e4bc
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/lmstudio.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/longcat.svg b/plugins/skill-hub/src/assets/providers/longcat.svg
new file mode 100644
index 00000000..234a4364
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/longcat.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/macos.svg b/plugins/skill-hub/src/assets/providers/macos.svg
new file mode 100644
index 00000000..9a5c32a5
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/macos.svg
@@ -0,0 +1,20 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/mcp.svg b/plugins/skill-hub/src/assets/providers/mcp.svg
new file mode 100644
index 00000000..17fbbec1
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/mcp.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/mcprouter.svg b/plugins/skill-hub/src/assets/providers/mcprouter.svg
new file mode 100644
index 00000000..c92f8af7
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/mcprouter.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/mcpso.svg b/plugins/skill-hub/src/assets/providers/mcpso.svg
new file mode 100644
index 00000000..3ff76a8f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/mcpso.svg
@@ -0,0 +1,23 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/meta.svg b/plugins/skill-hub/src/assets/providers/meta.svg
new file mode 100644
index 00000000..a6dee8bd
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/meta.svg
@@ -0,0 +1,27 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/metaso.svg b/plugins/skill-hub/src/assets/providers/metaso.svg
new file mode 100644
index 00000000..52d88b86
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/metaso.svg
@@ -0,0 +1,13 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/mineru.svg b/plugins/skill-hub/src/assets/providers/mineru.svg
new file mode 100644
index 00000000..68e881b4
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/mineru.svg
@@ -0,0 +1,22 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/minimax-agent.svg b/plugins/skill-hub/src/assets/providers/minimax-agent.svg
new file mode 100644
index 00000000..27ed8886
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/minimax-agent.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/minimax.svg b/plugins/skill-hub/src/assets/providers/minimax.svg
new file mode 100644
index 00000000..8e733d67
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/minimax.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/mistral.svg b/plugins/skill-hub/src/assets/providers/mistral.svg
new file mode 100644
index 00000000..18303bb9
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/mistral.svg
@@ -0,0 +1,19 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/mixedbread.svg b/plugins/skill-hub/src/assets/providers/mixedbread.svg
new file mode 100644
index 00000000..39ed59c9
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/mixedbread.svg
@@ -0,0 +1,73 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/modelscope.svg b/plugins/skill-hub/src/assets/providers/modelscope.svg
new file mode 100644
index 00000000..ceee3f11
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/modelscope.svg
@@ -0,0 +1,9 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/monica.svg b/plugins/skill-hub/src/assets/providers/monica.svg
new file mode 100644
index 00000000..1637c537
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/monica.svg
@@ -0,0 +1,17 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/moonshot.svg b/plugins/skill-hub/src/assets/providers/moonshot.svg
new file mode 100644
index 00000000..269cf3ce
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/moonshot.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/n8n.svg b/plugins/skill-hub/src/assets/providers/n8n.svg
new file mode 100644
index 00000000..c23bfa5d
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/n8n.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/nami-ai.svg b/plugins/skill-hub/src/assets/providers/nami-ai.svg
new file mode 100644
index 00000000..a69053ff
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/nami-ai.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/netease-youdao.svg b/plugins/skill-hub/src/assets/providers/netease-youdao.svg
new file mode 100644
index 00000000..7764fe8f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/netease-youdao.svg
@@ -0,0 +1,4 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/newapi.svg b/plugins/skill-hub/src/assets/providers/newapi.svg
new file mode 100644
index 00000000..97689f4e
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/newapi.svg
@@ -0,0 +1,19 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/nomic.svg b/plugins/skill-hub/src/assets/providers/nomic.svg
new file mode 100644
index 00000000..cdcde999
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/nomic.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/notebooklm.svg b/plugins/skill-hub/src/assets/providers/notebooklm.svg
new file mode 100644
index 00000000..a7fbbd5e
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/notebooklm.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/nousresearch.svg b/plugins/skill-hub/src/assets/providers/nousresearch.svg
new file mode 100644
index 00000000..75aa28ce
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/nousresearch.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/nvidia.svg b/plugins/skill-hub/src/assets/providers/nvidia.svg
new file mode 100644
index 00000000..2fa6dbdc
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/nvidia.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/o3.svg b/plugins/skill-hub/src/assets/providers/o3.svg
new file mode 100644
index 00000000..6201b6a0
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/o3.svg
@@ -0,0 +1,8 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ocoolai.svg b/plugins/skill-hub/src/assets/providers/ocoolai.svg
new file mode 100644
index 00000000..f8fa00af
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ocoolai.svg
@@ -0,0 +1,30 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ollama.svg b/plugins/skill-hub/src/assets/providers/ollama.svg
new file mode 100644
index 00000000..37ea6fa9
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ollama.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/openai.svg b/plugins/skill-hub/src/assets/providers/openai.svg
new file mode 100644
index 00000000..29d30ecd
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/openai.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/openclaw.svg b/plugins/skill-hub/src/assets/providers/openclaw.svg
new file mode 100644
index 00000000..26d48902
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/openclaw.svg
@@ -0,0 +1,25 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/openrouter.svg b/plugins/skill-hub/src/assets/providers/openrouter.svg
new file mode 100644
index 00000000..715ed5c7
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/openrouter.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/paddleocr.svg b/plugins/skill-hub/src/assets/providers/paddleocr.svg
new file mode 100644
index 00000000..a0ce67ec
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/paddleocr.svg
@@ -0,0 +1,16 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/perplexity.svg b/plugins/skill-hub/src/assets/providers/perplexity.svg
new file mode 100644
index 00000000..3796a8c9
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/perplexity.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ph8.svg b/plugins/skill-hub/src/assets/providers/ph8.svg
new file mode 100644
index 00000000..42741e0e
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ph8.svg
@@ -0,0 +1,20 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/poe.svg b/plugins/skill-hub/src/assets/providers/poe.svg
new file mode 100644
index 00000000..26ab9c05
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/poe.svg
@@ -0,0 +1,21 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/ppio.svg b/plugins/skill-hub/src/assets/providers/ppio.svg
new file mode 100644
index 00000000..ea27e81a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/ppio.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/pulse.svg b/plugins/skill-hub/src/assets/providers/pulse.svg
new file mode 100644
index 00000000..e58c66de
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/pulse.svg
@@ -0,0 +1,12 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/qiniu.svg b/plugins/skill-hub/src/assets/providers/qiniu.svg
new file mode 100644
index 00000000..c52f6a2f
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/qiniu.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/querit.svg b/plugins/skill-hub/src/assets/providers/querit.svg
new file mode 100644
index 00000000..88c9da04
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/querit.svg
@@ -0,0 +1,20 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/qwen.svg b/plugins/skill-hub/src/assets/providers/qwen.svg
new file mode 100644
index 00000000..ab654dfd
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/qwen.svg
@@ -0,0 +1,9 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/recraft.svg b/plugins/skill-hub/src/assets/providers/recraft.svg
new file mode 100644
index 00000000..0f6c4869
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/recraft.svg
@@ -0,0 +1,13 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/relace.svg b/plugins/skill-hub/src/assets/providers/relace.svg
new file mode 100644
index 00000000..55424759
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/relace.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/riverflow.svg b/plugins/skill-hub/src/assets/providers/riverflow.svg
new file mode 100644
index 00000000..69844133
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/riverflow.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/runway.svg b/plugins/skill-hub/src/assets/providers/runway.svg
new file mode 100644
index 00000000..b4d516a5
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/runway.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/searxng.svg b/plugins/skill-hub/src/assets/providers/searxng.svg
new file mode 100644
index 00000000..2a585fad
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/searxng.svg
@@ -0,0 +1,15 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/sensetime.svg b/plugins/skill-hub/src/assets/providers/sensetime.svg
new file mode 100644
index 00000000..e27ea28d
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/sensetime.svg
@@ -0,0 +1,30 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/silicon.svg b/plugins/skill-hub/src/assets/providers/silicon.svg
new file mode 100644
index 00000000..814d06ce
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/silicon.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/skywork.svg b/plugins/skill-hub/src/assets/providers/skywork.svg
new file mode 100644
index 00000000..8f8db229
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/skywork.svg
@@ -0,0 +1,11 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/smithery.svg b/plugins/skill-hub/src/assets/providers/smithery.svg
new file mode 100644
index 00000000..c944e4f2
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/smithery.svg
@@ -0,0 +1,11 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/sophnet.svg b/plugins/skill-hub/src/assets/providers/sophnet.svg
new file mode 100644
index 00000000..d62cbbbc
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/sophnet.svg
@@ -0,0 +1,6 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/stability.svg b/plugins/skill-hub/src/assets/providers/stability.svg
new file mode 100644
index 00000000..e3f3c7cc
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/stability.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/step.svg b/plugins/skill-hub/src/assets/providers/step.svg
new file mode 100644
index 00000000..b359e8f6
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/step.svg
@@ -0,0 +1,19 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/streamlake.svg b/plugins/skill-hub/src/assets/providers/streamlake.svg
new file mode 100644
index 00000000..83bbcb11
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/streamlake.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/suno.svg b/plugins/skill-hub/src/assets/providers/suno.svg
new file mode 100644
index 00000000..f369fc7c
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/suno.svg
@@ -0,0 +1,11 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/tavily.svg b/plugins/skill-hub/src/assets/providers/tavily.svg
new file mode 100644
index 00000000..01531fb8
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/tavily.svg
@@ -0,0 +1,20 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/tencent-cloud-ti.svg b/plugins/skill-hub/src/assets/providers/tencent-cloud-ti.svg
new file mode 100644
index 00000000..94137930
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/tencent-cloud-ti.svg
@@ -0,0 +1,5 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/tesseract-js.svg b/plugins/skill-hub/src/assets/providers/tesseract-js.svg
new file mode 100644
index 00000000..8f7388d8
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/tesseract-js.svg
@@ -0,0 +1,20 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/think-any.svg b/plugins/skill-hub/src/assets/providers/think-any.svg
new file mode 100644
index 00000000..57d6f461
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/think-any.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/tng.svg b/plugins/skill-hub/src/assets/providers/tng.svg
new file mode 100644
index 00000000..86402c93
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/tng.svg
@@ -0,0 +1,18 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/together.svg b/plugins/skill-hub/src/assets/providers/together.svg
new file mode 100644
index 00000000..7551f3b4
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/together.svg
@@ -0,0 +1,4 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/tokenflux.svg b/plugins/skill-hub/src/assets/providers/tokenflux.svg
new file mode 100644
index 00000000..78b1d1f6
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/tokenflux.svg
@@ -0,0 +1,25 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/twitter.svg b/plugins/skill-hub/src/assets/providers/twitter.svg
new file mode 100644
index 00000000..00eb0eaf
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/twitter.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/upstage.svg b/plugins/skill-hub/src/assets/providers/upstage.svg
new file mode 100644
index 00000000..5318136e
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/upstage.svg
@@ -0,0 +1,21 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/vercel.svg b/plugins/skill-hub/src/assets/providers/vercel.svg
new file mode 100644
index 00000000..608d02da
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/vercel.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/vertexai.svg b/plugins/skill-hub/src/assets/providers/vertexai.svg
new file mode 100644
index 00000000..b06b8bab
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/vertexai.svg
@@ -0,0 +1,22 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/vidu.svg b/plugins/skill-hub/src/assets/providers/vidu.svg
new file mode 100644
index 00000000..4d5f4791
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/vidu.svg
@@ -0,0 +1,19 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/volcengine.svg b/plugins/skill-hub/src/assets/providers/volcengine.svg
new file mode 100644
index 00000000..2ce100ca
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/volcengine.svg
@@ -0,0 +1,7 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/wenxin.svg b/plugins/skill-hub/src/assets/providers/wenxin.svg
new file mode 100644
index 00000000..8c627fa0
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/wenxin.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/workers-ai.svg b/plugins/skill-hub/src/assets/providers/workers-ai.svg
new file mode 100644
index 00000000..2176381a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/workers-ai.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/xiaoyi.svg b/plugins/skill-hub/src/assets/providers/xiaoyi.svg
new file mode 100644
index 00000000..2740c1f0
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/xiaoyi.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/xinghuo.svg b/plugins/skill-hub/src/assets/providers/xinghuo.svg
new file mode 100644
index 00000000..8e72b54d
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/xinghuo.svg
@@ -0,0 +1,5 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/xirang.svg b/plugins/skill-hub/src/assets/providers/xirang.svg
new file mode 100644
index 00000000..1ad6f0e7
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/xirang.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/you.svg b/plugins/skill-hub/src/assets/providers/you.svg
new file mode 100644
index 00000000..8a44e38b
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/you.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/yuanbao.svg b/plugins/skill-hub/src/assets/providers/yuanbao.svg
new file mode 100644
index 00000000..7d387b51
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/yuanbao.svg
@@ -0,0 +1,13 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/z-ai.svg b/plugins/skill-hub/src/assets/providers/z-ai.svg
new file mode 100644
index 00000000..9101944a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/z-ai.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/zero-one.svg b/plugins/skill-hub/src/assets/providers/zero-one.svg
new file mode 100644
index 00000000..ce834c25
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/zero-one.svg
@@ -0,0 +1,19 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/zhida.svg b/plugins/skill-hub/src/assets/providers/zhida.svg
new file mode 100644
index 00000000..d3ba2f0a
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/zhida.svg
@@ -0,0 +1,14 @@
+
diff --git a/plugins/skill-hub/src/assets/providers/zhipu.svg b/plugins/skill-hub/src/assets/providers/zhipu.svg
new file mode 100644
index 00000000..c30ab269
--- /dev/null
+++ b/plugins/skill-hub/src/assets/providers/zhipu.svg
@@ -0,0 +1,10 @@
+
diff --git a/plugins/skill-hub/src/components/AddProjectModal.vue b/plugins/skill-hub/src/components/AddProjectModal.vue
new file mode 100644
index 00000000..fc9e3101
--- /dev/null
+++ b/plugins/skill-hub/src/components/AddProjectModal.vue
@@ -0,0 +1,455 @@
+
+
+
+
+
+
+
+
+
+
选择根目录后自动填写项目名称并开始扫描。
+
项目根目录在添加后不可修改。
+
+
+
+
+
+
+
+
+
+
+
+
+
仅当技能放在默认扫描目录之外时,才需要添加额外路径。
+
+
暂无额外扫描路径
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/AppToast.vue b/plugins/skill-hub/src/components/AppToast.vue
new file mode 100644
index 00000000..35039afa
--- /dev/null
+++ b/plugins/skill-hub/src/components/AppToast.vue
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
{{ t.message }}
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/BatchSyncModal.vue b/plugins/skill-hub/src/components/BatchSyncModal.vue
new file mode 100644
index 00000000..efa71695
--- /dev/null
+++ b/plugins/skill-hub/src/components/BatchSyncModal.vue
@@ -0,0 +1,307 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ skill.name.charAt(0).toUpperCase() }}
+
+
+ {{ skill.name }}
+
+
+
+
+
+
+
+
+
分发方式
+
+
{{ installMode === 'copy' ? '将 SKILL.md 复制到每个平台目录,各平台独立更新。' : '创建指向源文件的软链接,所有平台共享同一文件。' }}
+
+
+
+
+
+
+
+
+
+ 正在分发 {{ deployProgress.skill }} 到 {{ deployProgress.platform }}... ({{ deployProgress.current }}/{{ deployProgress.total }})
+
+
+
+
+
+
+
+
+
+
{{ r.skill }} → {{ r.platform }}
+
{{ r.msg }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/CleanupSelectModal.vue b/plugins/skill-hub/src/components/CleanupSelectModal.vue
new file mode 100644
index 00000000..80036e17
--- /dev/null
+++ b/plugins/skill-hub/src/components/CleanupSelectModal.vue
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
以下技能文件夹在 skills-repo 中但未在注册表中登记,请选择要删除的项:
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/ConfirmBatchDeleteModal.vue b/plugins/skill-hub/src/components/ConfirmBatchDeleteModal.vue
new file mode 100644
index 00000000..cdd3b084
--- /dev/null
+++ b/plugins/skill-hub/src/components/ConfirmBatchDeleteModal.vue
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
确定要删除选中的 {{ skills.length }} 个 Skill 吗?此操作不可撤销。
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/ConfirmDeleteModal.vue b/plugins/skill-hub/src/components/ConfirmDeleteModal.vue
new file mode 100644
index 00000000..e0674458
--- /dev/null
+++ b/plugins/skill-hub/src/components/ConfirmDeleteModal.vue
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
确定要删除 {{ skill.name }} 吗?此操作不可撤销。
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/ConfirmModal.vue b/plugins/skill-hub/src/components/ConfirmModal.vue
new file mode 100644
index 00000000..da30b7a6
--- /dev/null
+++ b/plugins/skill-hub/src/components/ConfirmModal.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/DeployModal.vue b/plugins/skill-hub/src/components/DeployModal.vue
new file mode 100644
index 00000000..f2080d30
--- /dev/null
+++ b/plugins/skill-hub/src/components/DeployModal.vue
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
分发方式
+
+
{{ installMode === 'copy' ? '将 SKILL.md 复制到每个平台目录,各平台独立更新。' : '创建指向源文件的软链接,所有平台共享同一文件。' }}
+
+
+
+
+
+
+
+
+
+ 正在分发到 {{ deployProgress.platform }}... ({{ deployProgress.current }}/{{ deployProgress.total }})
+
+
+
+
+
+
+
+
+
+
{{ r.platform }}
+
{{ r.msg }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/DownloadIndicator.vue b/plugins/skill-hub/src/components/DownloadIndicator.vue
new file mode 100644
index 00000000..a8ff9cce
--- /dev/null
+++ b/plugins/skill-hub/src/components/DownloadIndicator.vue
@@ -0,0 +1,228 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.skillName }}
+
+ {{ item.type === 'download' ? '下载' : `分发到 ${item.platformNames?.join(', ') || ''}` }}
+ · {{ formatTime(item.startedAt) }}
+
+
+
!
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/GlobalDistPanel.vue b/plugins/skill-hub/src/components/GlobalDistPanel.vue
new file mode 100644
index 00000000..20209a21
--- /dev/null
+++ b/plugins/skill-hub/src/components/GlobalDistPanel.vue
@@ -0,0 +1,285 @@
+
+
+
+
+
+
{{ installMode === 'copy' ? '将 SKILL.md 复制到每个平台目录,各平台独立更新。' : '创建指向源文件的软链接,所有平台共享同一文件,编辑即时同步。' }}
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/NewSkillModal.vue b/plugins/skill-hub/src/components/NewSkillModal.vue
new file mode 100644
index 00000000..4ced6048
--- /dev/null
+++ b/plugins/skill-hub/src/components/NewSkillModal.vue
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+ 选择添加技能的方式:
+
+
+
+
+
+ 输入 GitHub 仓库 URL 或名称:
+
+
+
+
+
+ ⚠ {{ scanError }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/PlatformIcon.vue b/plugins/skill-hub/src/components/PlatformIcon.vue
new file mode 100644
index 00000000..618a651c
--- /dev/null
+++ b/plugins/skill-hub/src/components/PlatformIcon.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/ProjectDistPanel.vue b/plugins/skill-hub/src/components/ProjectDistPanel.vue
new file mode 100644
index 00000000..cc9458b7
--- /dev/null
+++ b/plugins/skill-hub/src/components/ProjectDistPanel.vue
@@ -0,0 +1,460 @@
+
+
+
+
+
+
{{ installMode === 'copy' ? '将 SKILL.md 复制到每个平台目录,各平台独立更新。' : '创建指向源文件的软链接,所有平台共享同一文件,编辑即时同步。' }}
+
+
+
+
选择项目
+
+
+
+
+
+
+
暂无项目,请先添加
+
{{ selectedProjects.length ? `已选 ${selectedProjects.length} 个项目,将同时分发到所有选中项目` : '尚未选择项目' }}
+
+
+
+
+
请先选择项目
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/ProviderIcon.vue b/plugins/skill-hub/src/components/ProviderIcon.vue
new file mode 100644
index 00000000..b7bc41c6
--- /dev/null
+++ b/plugins/skill-hub/src/components/ProviderIcon.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+ ⚙
+
+
+
+
+ ⚙
+
+
+
\ No newline at end of file
diff --git a/plugins/skill-hub/src/components/QuickSwitcher.vue b/plugins/skill-hub/src/components/QuickSwitcher.vue
new file mode 100644
index 00000000..eb256218
--- /dev/null
+++ b/plugins/skill-hub/src/components/QuickSwitcher.vue
@@ -0,0 +1,513 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ search ? '未找到匹配项' : (items.length ? '' : '暂无项目') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/SkillCodeEditor.vue b/plugins/skill-hub/src/components/SkillCodeEditor.vue
new file mode 100644
index 00000000..3efe69af
--- /dev/null
+++ b/plugins/skill-hub/src/components/SkillCodeEditor.vue
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/SkillDetailBase.vue b/plugins/skill-hub/src/components/SkillDetailBase.vue
new file mode 100644
index 00000000..5228dd1f
--- /dev/null
+++ b/plugins/skill-hub/src/components/SkillDetailBase.vue
@@ -0,0 +1,936 @@
+
+
+
+
+
+
+
+
+
+
+
+ 预览
+
+
+
+
+ 源码/内容
+
+
+
+
+ 文件
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/SkillDetailModal.vue b/plugins/skill-hub/src/components/SkillDetailModal.vue
new file mode 100644
index 00000000..f550cdc2
--- /dev/null
+++ b/plugins/skill-hub/src/components/SkillDetailModal.vue
@@ -0,0 +1,523 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SKILL 描述
+
+
{{ descTranslationDone && showDescTranslation ? translatedDesc : (skillDesc || skill.description || '暂无描述') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/SkillFileEditor.vue b/plugins/skill-hub/src/components/SkillFileEditor.vue
new file mode 100644
index 00000000..35df6147
--- /dev/null
+++ b/plugins/skill-hub/src/components/SkillFileEditor.vue
@@ -0,0 +1,910 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getFileIcon(item.relativePath, true, item.expanded) }}
+ {{ item.relativePath.split('/').pop() }}
+
+
+
+
+ {{ getFileIcon(child.relativePath, false) }}
+ {{ child.relativePath.split('/').pop() }}
+
+
+
+
+
+
+
+ {{ getFileIcon(item.relativePath, false) }}
+ {{ item.relativePath.split('/').pop() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ selectedFile }}
+
+ {{ formatSize(currentSize) }}
+ UTF-8
+ {{ currentLanguage }}
+ 未保存
+
+
+
+
+
+
+
+
+
+
+
+
+
新建文件夹
+
+
+ 取消
+ 创建
+
+
+
+
+
+
+
+
未保存的更改
+
有未保存的更改,确定要切换文件吗?
+
+ 取消
+ 确定切换
+
+
+
+
+
+
+
+
确认删除
+
确定要删除 {{ deleteDialogFile }} 吗?此操作不可撤销。
+
+ 取消
+ 删除
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/SkillPickModal.vue b/plugins/skill-hub/src/components/SkillPickModal.vue
new file mode 100644
index 00000000..97148f93
--- /dev/null
+++ b/plugins/skill-hub/src/components/SkillPickModal.vue
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ s.name.charAt(0).toUpperCase() }}
+
+
+
{{ s.name || '未命名' }}
+
{{ s.description || '暂无描述' }}
+
{{ s.dir }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/components/StoreConfigModal.vue b/plugins/skill-hub/src/components/StoreConfigModal.vue
new file mode 100644
index 00000000..b4bcf047
--- /dev/null
+++ b/plugins/skill-hub/src/components/StoreConfigModal.vue
@@ -0,0 +1,402 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ examples[sourceType].label }}
+
{{ line }}
+
+ 默认图标:
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/composables/useDownloadQueue.ts b/plugins/skill-hub/src/composables/useDownloadQueue.ts
new file mode 100644
index 00000000..8ad9422d
--- /dev/null
+++ b/plugins/skill-hub/src/composables/useDownloadQueue.ts
@@ -0,0 +1,75 @@
+import { ref, computed } from 'vue'
+
+export interface QueueItem {
+ id: string
+ skillId: string
+ skillName: string
+ type: 'download' | 'install'
+ platformNames?: string[]
+ status: 'pending' | 'running' | 'success' | 'error'
+ progress?: string
+ error?: string
+ startedAt: number
+}
+
+const queue = ref([])
+const isExpanded = ref(false)
+
+export function useDownloadQueue() {
+ function addDownload(skillId: string, skillName: string) {
+ const item: QueueItem = {
+ id: `dl-${skillId}-${Date.now()}`,
+ skillId,
+ skillName,
+ type: 'download',
+ status: 'running',
+ startedAt: Date.now(),
+ }
+ queue.value.push(item)
+ return item
+ }
+
+ function addInstall(skillId: string, skillName: string, platformNames: string[]) {
+ const item: QueueItem = {
+ id: `inst-${skillId}-${Date.now()}`,
+ skillId,
+ skillName,
+ type: 'install',
+ platformNames,
+ status: 'running',
+ startedAt: Date.now(),
+ }
+ queue.value.push(item)
+ return item
+ }
+
+ function updateItem(id: string, patch: Partial) {
+ const idx = queue.value.findIndex((item) => item.id === id)
+ if (idx >= 0) {
+ queue.value[idx] = { ...queue.value[idx], ...patch }
+ }
+ }
+
+ function removeItem(id: string) {
+ queue.value = queue.value.filter((item) => item.id !== id)
+ }
+
+ function clearCompleted() {
+ queue.value = queue.value.filter((item) => item.status === 'running' || item.status === 'pending')
+ }
+
+ const activeCount = computed(() => queue.value.filter((item) => item.status === 'running' || item.status === 'pending').length)
+ const hasItems = computed(() => queue.value.length > 0)
+
+ return {
+ queue,
+ isExpanded,
+ activeCount,
+ hasItems,
+ addDownload,
+ addInstall,
+ updateItem,
+ removeItem,
+ clearCompleted,
+ }
+}
diff --git a/plugins/skill-hub/src/composables/useProjectState.ts b/plugins/skill-hub/src/composables/useProjectState.ts
new file mode 100644
index 00000000..0336167c
--- /dev/null
+++ b/plugins/skill-hub/src/composables/useProjectState.ts
@@ -0,0 +1,31 @@
+import { ref } from 'vue'
+import { storage } from '../utils/storage'
+import type { RegisteredProject, SkillScanResult } from '../types'
+
+const registeredProjects = ref(storage.getRegisteredProjects())
+const selectedProject = ref(null)
+const selectedProjectSkill = ref(null)
+
+const savedState = storage.getPageState('project-skills')
+if (savedState?.projectId) {
+ const found = registeredProjects.value.find((p) => p.id === savedState.projectId)
+ if (found) {
+ selectedProject.value = found
+ selectedProjectSkill.value = found.skills?.[0] || null
+ }
+}
+
+export function useProjectState() {
+ function persistSelectedProject() {
+ if (selectedProject.value) {
+ storage.savePageState('project-skills', { projectId: selectedProject.value.id })
+ }
+ }
+
+ return {
+ registeredProjects,
+ selectedProject,
+ selectedProjectSkill,
+ persistSelectedProject,
+ }
+}
diff --git a/plugins/skill-hub/src/composables/useSettings.ts b/plugins/skill-hub/src/composables/useSettings.ts
new file mode 100644
index 00000000..992bd1f0
--- /dev/null
+++ b/plugins/skill-hub/src/composables/useSettings.ts
@@ -0,0 +1,21 @@
+import { reactive } from 'vue'
+import type { AppSettings, ThemeMode, FontSize, MotionPreference } from '../types'
+import { storage } from '../utils/storage'
+import { applyTheme } from '../utils/theme'
+
+const state = reactive(storage.getSettings())
+
+function patch(patch: Partial) {
+ const next = { ...state, ...patch }
+ Object.assign(state, next)
+ try {
+ storage.saveSettings(next)
+ } catch (e) {
+ console.error('[useSettings] saveSettings failed:', e)
+ }
+ applyTheme(next)
+}
+
+export function useSettings() {
+ return { settings: state, updateSettings: patch }
+}
diff --git a/plugins/skill-hub/src/composables/useTheme.ts b/plugins/skill-hub/src/composables/useTheme.ts
new file mode 100644
index 00000000..d2f9aba6
--- /dev/null
+++ b/plugins/skill-hub/src/composables/useTheme.ts
@@ -0,0 +1,20 @@
+import { computed } from 'vue'
+import { useSettings } from './useSettings'
+
+export function useTheme() {
+ const { settings, updateSettings } = useSettings()
+
+ const isDarkMode = computed(() => {
+ if (settings.themeMode === 'auto') {
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
+ }
+ return settings.themeMode === 'dark'
+ })
+
+ function toggleTheme() {
+ const next = isDarkMode.value ? 'light' : 'dark'
+ updateSettings({ themeMode: next })
+ }
+
+ return { isDarkMode, toggleTheme }
+}
diff --git a/plugins/skill-hub/src/data/ai-providers.ts b/plugins/skill-hub/src/data/ai-providers.ts
new file mode 100644
index 00000000..263eeda0
--- /dev/null
+++ b/plugins/skill-hub/src/data/ai-providers.ts
@@ -0,0 +1,49 @@
+export interface AiProviderPreset {
+ id: string
+ name: string
+ defaultBaseUrl: string
+ defaultApiPath: string
+ icon: string
+ isBuiltin: boolean
+ getKeyUrl?: string
+}
+
+export const BUILTIN_PROVIDERS: AiProviderPreset[] = [
+ { id: 'openai', name: 'OpenAI', defaultBaseUrl: 'https://api.openai.com', defaultApiPath: '/v1/chat/completions', icon: 'openai', isBuiltin: true, getKeyUrl: 'https://platform.openai.com/api-keys' },
+ { id: 'gemini', name: 'Gemini', defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai', defaultApiPath: '/chat/completions', icon: 'google', isBuiltin: true, getKeyUrl: 'https://aistudio.google.com/apikey' },
+ { id: 'claude', name: 'Claude', defaultBaseUrl: 'https://api.anthropic.com', defaultApiPath: '/v1/messages', icon: 'anthropic', isBuiltin: true, getKeyUrl: 'https://console.anthropic.com/settings/keys' },
+ { id: 'deepseek', name: 'DeepSeek', defaultBaseUrl: 'https://api.deepseek.com', defaultApiPath: '/v1/chat/completions', icon: 'deepseek', isBuiltin: true, getKeyUrl: 'https://platform.deepseek.com/api_keys' },
+ { id: 'xai', name: 'xAI', defaultBaseUrl: 'https://api.x.ai', defaultApiPath: '/v1/chat/completions', icon: 'grok', isBuiltin: true, getKeyUrl: 'https://console.x.ai/' },
+ { id: 'glm', name: 'GLM (智谱)', defaultBaseUrl: 'https://open.bigmodel.cn/api/paas', defaultApiPath: '/v4/chat/completions', icon: 'zhipu', isBuiltin: true, getKeyUrl: 'https://open.bigmodel.cn/usercenter/apikeys' },
+ { id: 'siliconflow', name: 'SiliconFlow', defaultBaseUrl: 'https://api.siliconflow.cn', defaultApiPath: '/v1/chat/completions', icon: 'silicon', isBuiltin: true, getKeyUrl: 'https://cloud.siliconflow.cn/account/ak' },
+ { id: 'minimax', name: 'MiniMax', defaultBaseUrl: 'https://api.minimax.chat', defaultApiPath: '/v1/text/chatcompletion_v2', icon: 'minimax', isBuiltin: true, getKeyUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key' },
+]
+
+export function getProviderInfo(id: string): AiProviderPreset | undefined {
+ return BUILTIN_PROVIDERS.find((p) => p.id === id)
+}
+
+
+export const AVAILABLE_ICONS = [
+ '302ai', '3min-top', 'abacus', 'ai-only', 'ai-studio', 'ai21', 'aihubmix', 'aionlabs',
+ 'alayanew', 'allenai', 'anthropic', 'application', 'arcee-ai', 'aws-bedrock', 'azureai',
+ 'baai', 'baichuan', 'baidu', 'baidu-cloud', 'bailian', 'bfl', 'bing', 'bocha',
+ 'bolt-new', 'burncloud', 'bytedance', 'cephalon', 'cerebras', 'cherryin', 'cloudflare',
+ 'inceptionlabs', 'infini', 'inflection', 'intel', 'internlm', 'jimeng', 'kling',
+ 'deepseek', 'devv', 'dify', 'dmxapi', 'doc2x', 'dola', 'dolphin-ai', 'doubao', 'duck',
+ 'elevenlabs', 'essential-ai', 'exa', 'felo', 'fireworks', 'flowith', 'genspark',
+ 'gitee-ai', 'github', 'github-copilot', 'glama', 'google', 'gpustack', 'graph-rag',
+ 'grok', 'groq', 'higress', 'huggingface', 'hyperbolic', 'ideogram', 'ima',
+ 'inceptionlabs', 'infini', 'inflection', 'intel', 'internlm', 'jimeng', 'kling',
+ 'kwaipilot', 'lambda', 'lanyun', 'lepton', 'lingxi', 'liquid', 'lmstudio', 'longcat',
+ 'macos', 'mcp', 'mcprouter', 'mcpso', 'meta', 'metaso', 'mineru', 'minimax',
+ 'minimax-agent', 'mistral', 'mixedbread', 'modelscope', 'monica', 'moonshot', 'n8n',
+ 'nami-ai', 'netease-youdao', 'newapi', 'nomic', 'notebooklm', 'nousresearch', 'nvidia',
+ 'o3', 'ocoolai', 'ollama', 'openai', 'openclaw', 'openrouter', 'paddleocr', 'perplexity',
+ 'ph8', 'poe', 'ppio', 'pulse', 'qiniu', 'querit', 'qwen', 'recraft', 'relace',
+ 'riverflow', 'runway', 'searxng', 'sensetime', 'silicon', 'skywork', 'smithery',
+ 'sophnet', 'stability', 'step', 'streamlake', 'suno', 'tavily', 'tencent-cloud-ti',
+ 'tesseract-js', 'think-any', 'tng', 'together', 'tokenflux', 'twitter', 'upstage',
+ 'vercel', 'vertexai', 'vidu', 'volcengine', 'wenxin', 'workers-ai', 'xiaoyi',
+ 'xinghuo', 'xirang', 'you', 'yuanbao', 'z-ai', 'zero-one', 'zhida', 'zhipu',
+]
diff --git a/plugins/skill-hub/src/data/brand-colors.ts b/plugins/skill-hub/src/data/brand-colors.ts
new file mode 100644
index 00000000..ccb0e3d7
--- /dev/null
+++ b/plugins/skill-hub/src/data/brand-colors.ts
@@ -0,0 +1,47 @@
+const BRAND_COLORS: Record = {
+ openai: '#10A37F',
+ deepseek: '#4D6BFE',
+ xai: '#000',
+ anthropic: '#D97757',
+ google: '#4285F4',
+ minimax: '#F23F5D',
+ silicon: '#6E29F6',
+ siliconcloud: '#6E29F6',
+ zhipu: '#3859FF',
+ chatglm: '#3859FF',
+ gemini: '#4285F4',
+ claude: '#D97757',
+ grok: '#000',
+ perplexity: '#1B3A5C',
+ together: '#B02D5E',
+ fireworks: '#F97316',
+ groq: '#F97316',
+ mistral: '#FF6B6B',
+ replicate: '#1B1B1B',
+ huggingface: '#FBBF24',
+ azure: '#0078D4',
+ aws: '#FF9900',
+ cloudflare: '#F38020',
+ meta: '#1877F2',
+ microsoft: '#00A4EF',
+ nvidia: '#76B900',
+ apple: '#555',
+ github: '#24292F',
+ ollama: '#000',
+}
+
+const DEFAULT_PALETTE = [
+ '#6366f1', '#8b5cf6', '#a855f7', '#d946ef',
+ '#ec4899', '#f43f5e', '#ef4444', '#f97316',
+ '#eab308', '#22c55e', '#14b8a6', '#06b6d4',
+ '#0ea5e9', '#3b82f6', '#6366f1', '#8b5cf6',
+]
+
+// export function getBrandColor(iconName: string): string {
+// if (BRAND_COLORS[iconName]) return BRAND_COLORS[iconName]
+// let hash = 0
+// for (let i = 0; i < iconName.length; i++) {
+// hash = iconName.charCodeAt(i) + ((hash << 5) - hash)
+// }
+// return DEFAULT_PALETTE[Math.abs(hash) % DEFAULT_PALETTE.length]
+// }
diff --git a/plugins/skill-hub/src/data/platform-icons.ts b/plugins/skill-hub/src/data/platform-icons.ts
new file mode 100644
index 00000000..06e79730
--- /dev/null
+++ b/plugins/skill-hub/src/data/platform-icons.ts
@@ -0,0 +1,43 @@
+import antigravityIcon from '../assets/platforms/antigravity.svg'
+import cherryStudioIcon from '../assets/platforms/cherry-studio.png'
+import claudeIcon from '../assets/platforms/claude.png'
+import clineIcon from '../assets/platforms/cline.svg'
+import codebuddyIcon from '../assets/platforms/codebuddy-light.svg'
+import codexIcon from '../assets/platforms/codex.png'
+import copilotIcon from '../assets/platforms/copilot.png'
+import cursorIcon from '../assets/platforms/cursor.png'
+import geminiIcon from '../assets/platforms/gemini.png'
+import genericIcon from '../assets/platforms/generic.svg'
+import hermesIcon from '../assets/platforms/hermes.svg'
+import kiloIcon from '../assets/platforms/kilo-light.svg'
+import kiroIcon from '../assets/platforms/kiro.png'
+import mimoIcon from '../assets/platforms/mimo.png'
+import openclawIcon from '../assets/platforms/openclaw.png'
+import opencodeIcon from '../assets/platforms/opencode.png'
+import qoderIcon from '../assets/platforms/qoder.png'
+import qoderworkIcon from '../assets/platforms/qoderwork.png'
+import traeIcon from '../assets/platforms/trae.png'
+import windsurfIcon from '../assets/platforms/windsurf.png'
+
+export const PLATFORM_ICONS: Record = {
+ antigravity: antigravityIcon,
+ 'cherry-studio': cherryStudioIcon,
+ claude: claudeIcon,
+ cline: clineIcon,
+ codebuddy: codebuddyIcon,
+ codex: codexIcon,
+ copilot: copilotIcon,
+ cursor: cursorIcon,
+ gemini: geminiIcon,
+ generic: genericIcon,
+ hermes: hermesIcon,
+ kilo: kiloIcon,
+ kiro: kiroIcon,
+ mimo: mimoIcon,
+ openclaw: openclawIcon,
+ opencode: opencodeIcon,
+ qoder: qoderIcon,
+ qoderwork: qoderworkIcon,
+ trae: traeIcon,
+ windsurf: windsurfIcon,
+}
diff --git a/plugins/skill-hub/src/data/platforms.ts b/plugins/skill-hub/src/data/platforms.ts
new file mode 100644
index 00000000..c6d048fe
--- /dev/null
+++ b/plugins/skill-hub/src/data/platforms.ts
@@ -0,0 +1,235 @@
+import type { PlatformInfo } from '../types'
+
+type OsKey = 'darwin' | 'win32' | 'linux'
+
+function joinPath(base: string, relative: string): string {
+ if (!relative.trim()) return base
+ const sep = base.includes('\\') ? '\\' : '/'
+ const normBase = base.replace(/[\\/]+$/, '').replace(/[\\/]/g, sep)
+ const normRel = relative.trim().split(/[\\/]+/).filter(Boolean).join(sep)
+ return normRel ? `${normBase}${sep}${normRel}` : normBase
+}
+
+function getOsKey(): OsKey {
+ const svc = typeof window !== 'undefined' ? window.services : null
+ if (svc?.isWindows()) return 'win32'
+ if (typeof navigator !== 'undefined' && navigator.platform?.includes('Mac')) return 'darwin'
+ return 'linux'
+}
+
+function resolveRootDir(platform: { rootDir?: { darwin: string; win32: string; linux: string } }): string {
+ if (!platform.rootDir) return ''
+ return platform.rootDir[getOsKey()] || platform.rootDir.linux
+}
+
+function resolveSkillsPath(platform: { rootDir?: { darwin: string; win32: string; linux: string }; skillsRelativePath?: string }): string {
+ const root = resolveRootDir(platform)
+ if (!root || !platform.skillsRelativePath) return ''
+ return joinPath(root, platform.skillsRelativePath)
+}
+
+export const defaultPlatforms: PlatformInfo[] = [
+ {
+ id: 'claude',
+ name: 'Claude Code',
+ rootDir: { darwin: '~/.claude', win32: '~/.claude', linux: '~/.claude' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.claude/skills/',
+ projectPath: '.claude/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'copilot',
+ name: 'GitHub Copilot',
+ rootDir: { darwin: '~/.copilot', win32: '~/.copilot', linux: '~/.copilot' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.copilot/skills/',
+ projectPath: '.copilot/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'cursor',
+ name: 'Cursor',
+ rootDir: { darwin: '~/.cursor', win32: '~/.cursor', linux: '~/.cursor' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.cursor/skills/',
+ projectPath: '.cursor/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'cherry-studio',
+ name: 'Cherry Studio',
+ rootDir: { darwin: '~/Library/Application Support/CherryStudio', win32: '~/AppData/Roaming/CherryStudio', linux: '~/.config/CherryStudio' },
+ skillsRelativePath: 'Data/Skills',
+ defaultPath: '~/.config/CherryStudio/Data/Skills/',
+ projectPath: 'Data/Skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'windsurf',
+ name: 'Windsurf',
+ rootDir: { darwin: '~/.codeium/windsurf', win32: '~/.codeium/windsurf', linux: '~/.codeium/windsurf' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.codeium/windsurf/skills/',
+ projectPath: '.codeium/windsurf/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'kiro',
+ name: 'Kiro',
+ rootDir: { darwin: '~/.kiro', win32: '~/.kiro', linux: '~/.kiro' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.kiro/skills/',
+ projectPath: '.kiro/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'gemini',
+ name: 'Gemini CLI',
+ rootDir: { darwin: '~/.gemini', win32: '~/.gemini', linux: '~/.gemini' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.gemini/skills/',
+ projectPath: '.gemini/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'antigravity',
+ name: 'Antigravity',
+ rootDir: { darwin: '~/.gemini/antigravity', win32: '~/.gemini/antigravity', linux: '~/.gemini/antigravity' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.gemini/antigravity/skills/',
+ projectPath: '.gemini/antigravity/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'trae',
+ name: 'Trae',
+ rootDir: { darwin: '~/.trae', win32: '~/.trae', linux: '~/.trae' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.trae/skills/',
+ projectPath: '.trae/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'trae-cn',
+ name: 'Trae CN',
+ rootDir: { darwin: '~/.trae-cn', win32: '~/.trae-cn', linux: '~/.trae-cn' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.trae-cn/skills/',
+ projectPath: '.trae-cn/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'opencode',
+ name: 'OpenCode',
+ rootDir: { darwin: '~/.config/opencode', win32: '~/.config/opencode', linux: '~/.config/opencode' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.config/opencode/skills/',
+ projectPath: '.opencode/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'mimo',
+ name: 'MiMo Code',
+ rootDir: { darwin: '~/.config/mimocode', win32: '~/.config/mimocode', linux: '~/.config/mimocode' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.config/mimocode/skills/',
+ projectPath: '.mimocode/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'cline',
+ name: 'Cline',
+ rootDir: { darwin: '~/.cline', win32: '~/.cline', linux: '~/.cline' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.cline/skills/',
+ projectPath: '.cline/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'codex',
+ name: 'Codex CLI',
+ rootDir: { darwin: '~/.codex', win32: '~/.codex', linux: '~/.codex' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.codex/skills/',
+ projectPath: '.codex/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'kilo',
+ name: 'Kilo Code',
+ rootDir: { darwin: '~/.kilo', win32: '~/.kilo', linux: '~/.kilo' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.kilo/skills/',
+ projectPath: '.kilo/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'openclaw',
+ name: 'OpenClaw',
+ rootDir: { darwin: '~/.openclaw', win32: '~/.openclaw', linux: '~/.openclaw' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.openclaw/skills/',
+ projectPath: '.openclaw/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'qoder',
+ name: 'Qoder',
+ rootDir: { darwin: '~/.qoder', win32: '~/.qoder', linux: '~/.qoder' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.qoder/skills/',
+ projectPath: '.qoder/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'hermes',
+ name: 'Hermes Agent',
+ rootDir: { darwin: '~/.hermes', win32: '~/.hermes', linux: '~/.hermes' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.hermes/skills/',
+ projectPath: '.hermes/skills/',
+ enabled: true, detected: false,
+ },
+ {
+ id: 'codebuddy',
+ name: 'CodeBuddy',
+ rootDir: { darwin: '~/.codebuddy', win32: '~/.codebuddy', linux: '~/.codebuddy' },
+ skillsRelativePath: 'skills',
+ defaultPath: '~/.codebuddy/skills/',
+ projectPath: '.codebuddy/skills/',
+ enabled: true, detected: false,
+ },
+]
+
+export function detectPlatforms(): PlatformInfo[] {
+ const svc = typeof window !== 'undefined' ? window.services : null
+ if (!svc) return defaultPlatforms.map((p) => ({ ...p, detected: false }))
+
+ return defaultPlatforms.map((p) => {
+ let detected = false
+ if (p.rootDir) {
+ const rootExpanded = resolveRootDir(p).replace(/^~/, svc.homeDir())
+ detected = svc.pathExists(rootExpanded)
+ } else {
+ const checkPath = p.defaultPath || p.projectPath || ''
+ if (checkPath) {
+ const expanded = checkPath.replace(/^~/, svc.homeDir())
+ detected = svc.pathExists(expanded)
+ }
+ }
+ return { ...p, detected }
+ })
+}
+
+export function getPlatformPath(platform: PlatformInfo, mode: 'global' | 'project' = 'global'): string {
+ const svc = typeof window !== 'undefined' ? window.services : null
+ if (mode === 'global' && platform.rootDir && platform.skillsRelativePath) {
+ const root = resolveRootDir(platform).replace(/^~/, svc ? svc.homeDir() : '~')
+ return joinPath(root, platform.skillsRelativePath)
+ }
+ const base = mode === 'global'
+ ? platform.customPath || platform.defaultPath
+ : platform.customProjectPath || platform.projectPath
+ return base ? base.replace(/^~/, svc ? svc.homeDir() : '~') : ''
+}
diff --git a/plugins/skill-hub/src/data/skill-categories.ts b/plugins/skill-hub/src/data/skill-categories.ts
new file mode 100644
index 00000000..ea0e4808
--- /dev/null
+++ b/plugins/skill-hub/src/data/skill-categories.ts
@@ -0,0 +1,31 @@
+export type SkillCategory = 'search' | 'content' | 'dev' | 'testing' | 'ops' | 'other'
+
+export const SKILL_CATEGORIES: Record = {
+ search: { label: '搜索', labelEn: 'Search', icon: '🔍' },
+ content: { label: '内容', labelEn: 'Content', icon: '✨' },
+ dev: { label: '开发', labelEn: 'Development', icon: '🔧' },
+ testing: { label: '测试', labelEn: 'Testing', icon: '✏️' },
+ ops: { label: '运维', labelEn: 'Ops', icon: '🚀' },
+ other: { label: '其他', labelEn: 'Other', icon: '📋' },
+}
+
+export const ALL_CATEGORIES = Object.keys(SKILL_CATEGORIES) as SkillCategory[]
+
+export function inferCategory(slug: string, description: string): SkillCategory {
+ const text = `${slug} ${description}`.toLowerCase()
+ if (/(test|lint|debug|playwright|screenshot|quality|validate|verify|check|jest|vitest|mocha|cypress|eslint|prettier|lint)/.test(text)) return 'testing'
+ if (/(deploy|vercel|docker|cloudflare|netlify|k8s|kubernetes|helm|terraform|ci[\s\/]|cd[\s\/]|monitor|ops|sentry|container|server|infra|cloud|netlify)/.test(text)) return 'ops'
+ if (/(search|web|crawl|scrape|research|lookup|find|query|browser|rss|feed|data|sql|chart|report|analy|crawl|scrape|rss)/.test(text)) return 'search'
+ if (/(generate|ai|image|video|art|prompt|llm|translation|speech|pptx|docx|pdf|xlsx|office|design|figma|canvas|brand|notepad|write|content|create|layout|responsive|media|photo|audio|voice|translate|summarize|write|blog|article|copy)/.test(text)) return 'content'
+ if (/(github|git|code|cli|dev|pr|api|sdk|mcp|plugin|template|boilerplate|starter|meta|extend|notion|linear|jira|ticket|kanban|sprint|manage|project|security|audit|auth|secret|encrypt|vulnerability|html|css|frontend|backend|openapi|swagger)/.test(text)) return 'dev'
+ return 'other'
+}
+
+export const CATEGORY_ICONS: Record = {
+ search: '🔍',
+ content: '✨',
+ dev: '🔧',
+ testing: '✏️',
+ ops: '🚀',
+ other: '📋',
+}
diff --git a/plugins/skill-hub/src/data/skill-icons.ts b/plugins/skill-hub/src/data/skill-icons.ts
new file mode 100644
index 00000000..9b814fcc
--- /dev/null
+++ b/plugins/skill-hub/src/data/skill-icons.ts
@@ -0,0 +1,76 @@
+const ICON_OPENAI = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+T3BlbkFJPC90aXRsZT48cGF0aCBkPSJNMjIuMjgxOSA5LjgyMTFhNS45ODQ3IDUuOTg0NyAwIDAgMC0uNTE1Ny00LjkxMDggNi4wNDYyIDYuMDQ2MiAwIDAgMC02LjUwOTgtMi45QTYuMDY1MSA2LjA2NTEgMCAwIDAgNC45ODA3IDQuMTgxOGE1Ljk4NDcgNS45ODQ3IDAgMCAwLTMuOTk3NyAyLjkgNi4wNDYyIDYuMDQ2MiAwIDAgMCAuNzQyNyA3LjA5NjYgNS45OCA1Ljk4IDAgMCAwIC41MTEgNC45MTA3IDYuMDUxIDYuMDUxIDAgMCAwIDYuNTE0NiAyLjkwMDFBNS45ODQ3IDUuOTg0NyAwIDAgMCAxMy4yNTk5IDI0YTYuMDU1NyA2LjA1NTcgMCAwIDAgNS43NzE4LTQuMjA1OCA1Ljk4OTQgNS45ODk0IDAgMCAwIDMuOTk3Ny0yLjkwMDEgNi4wNTU3IDYuMDU1NyAwIDAgMC0uNzQ3NS03LjA3Mjl6Ii8+PC9zdmc+'
+const ICON_GITHUB = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViPC90aXRsZT48cGF0aCBkPSJNMTIgLjI5N2MtNi42MyAwLTEyIDUuNzczLTEyIDEyIDAgNS4zMDMgMy40MzggOS44IDguMjA1IDExLjM4NS42LjExMy44Mi0uMjU4LjgyLS41NzcgMC0uMjg1LS4wMS0xLjA0LS4wMTUtMi4wNC0zLjMzOC43MjQtNC4wNDItMS42MS00LjA0Mi0xLjYxQzQuNDIyIDE4LjA3IDMuNjMzIDE3LjcgMy42MzMgMTcuN2MtMS4wODctLjc0NC4wODQtLjcyOS4wODQtLjcyOSAxLjIwNS4wODQgMS44MzggMS4yMzYgMS44MzggMS4yMzYgMS4wNyAxLjgzNSAyLjgwOSAxLjMwNSAzLjQ5NS45OTguMTA4LS43NzYuNDE3LTEuMzA1Ljc2LTEuNjA1LTIuNjY1LS4zLTUuNDY2LTEuMzMyLTUuNDY2LTUuOTMgMC0xLjMxLjQ2NS0yLjM4IDEuMjM1LTMuMjItLjEzNS0uMzAzLS41NC0xLjUyMy4xMDUtMy4xNzYgMCAwIDEuMDA1LS4zMjIgMy4zIDEuMjMuOTYtLjI2NyAxLjk4LS4zOTkgMy0uNDA1IDEuMDIuMDA2IDIuMDQuMTM4IDMgLjQwNSAyLjI4LTEuNTUyIDMuMjg1LTEuMjMgMy4yODUtMS4yMy42NDUgMS42NTMuMjQgMi44NzMuMTIgMy4xNzYuNzY1Ljg0IDEuMjMgMS45MSAxLjIzIDMuMjIgMCA0LjYxLTIuODA1IDUuNjI1LTUuNDc1IDUuOTIuNDIuMzYuODEgMS4wOTYuODEgMi4yMiAwIDEuNjA2LS4wMTUgMi44OTYtLjAxNSAzLjI4NiAwIC4zMTUuMjEuNjkuODI1LjU3QzIwLjU2NSAyMi4wOTIgMjQgMTcuNTkyIDI0IDEyLjI5N2MwLTYuNjI3LTUuMzczLTEyLTEyLTEyIi8+PC9zdmc+'
+const ICON_GITHUB_ACTIONS = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViIEFjdGlvbnM8L3RpdGxlPjxwYXRoIGQ9Ik0xMC45ODQgMTMuODM2YS41LjUgMCAwIDEtLjM1My0uMTQ2bC0uNzQ1LS43NDNhLjUuNSAwIDEgMSAuNzA2LS43MDhsLjM5Mi4zOTEgMS4xODEtMS4xOGEuNS41IDAgMCAxIC43MDguNzA3bC0xLjUzNSAxLjUzM2EuNTA0LjUwNCAwIDAgMS0uMzU0LjE0NnptOS4zNTMtLjE0N2wxLjUzNC0xLjUzMmEuNS41IDAgMCAwLS43MDctLjcwN2wtMS4xODEgMS4xOC0uMzkyLS4zOTFhLjUuNSAwIDEgMC0uNzA2LjcwOGwuNzQ2Ljc0M2EuNDk3LjQ5NyAwIDAgMCAuNzA2LS4wMDF6TTQuNTI3IDcuNDUybDIuNTU3LTEuNTg1QTEgMSAwIDAgMCA3LjA5IDQuMTdMNC41MzMgMi41NkExIDEgMCAwIDAgMyAzLjQwNnYzLjE5NmExLjAwMSAxLjAwMSAwIDAgMCAxLjUyNy44NXptMi4wMy0yLjQzNkw0IDYuNjAyVjMuNDA2bDIuNTU3IDEuNjF6TTI0IDEyLjVjMCAxLjkzLTEuNTcgMy41LTMuNSAzLjVhMy41MDMgMy41MDMgMCAwIDEtMy40Ni0zaC0yLjA4YTMuNTAzIDMuNTAzIDAgMCAxLTMuNDYgMyAzLjUwMiAzLjUwMiAwIDAgMS0zLjQ2LTNoLS41NThjLS45NzIgMC0xLjg1LS4zOTktMi40ODItMS4wNDJWMTdjMCAxLjY1NCAxLjM0NiAzIDMgM2guMDRjLjI0NC0xLjY5MyAxLjctMyAzLjQ2LTMgMS45MyAwIDMuNSAxLjU3IDMuNSAzLjVTMTMuNDMgMjQgMTEuNSAyNGEzLjUwMiAzLjUwMiAwIDAgMS0zLjQ2LTNIOGMtMi4yMDYgMC00LTEuNzk0LTQtNFY5Ljg5OUE1LjAwOCA1LjAwOCAwIDAgMSAwIDVjMC0yLjc1NyAyLjI0My01IDUtNXM1IDIuMjQzIDUgNWE1LjAwNSA1LjAwNSAwIDAgMS00Ljk1MiA0Ljk5OEEyLjQ4MiAyLjQ4MiAwIDAgMCA3LjQ4MiAxMmguNTU4Yy4yNDQtMS42OTMgMS43LTMgMy40Ni0zYTMuNTAyIDMuNTAyIDAgMCAxIDMuNDYgM2gyLjA4YTMuNTAzIDMuNTAzIDAgMCAxIDMuNDYtM2MxLjkzIDAgMy41IDEuNTcgMy41IDMuNXptLTE1IDhjMCAxLjM3OCAxLjEyMiAyLjUgMi41IDIuNXMyLjUtMS4xMjIgMi41LTIuNS0xLjEyMi0yLjUtMi41LTIuNVM5IDE5LjEyMiA5IDIwLjV6TTUgOWMyLjIwNiAwIDQtMS43OTQgNC00UzcuMjA2IDEgNSAxIDEgMi43OTQgMSA1czEuNzk0IDQgNCA0em05IDMuNWMwLTEuMzc4LTEuMTIyLTIuNS0yLjUtMi41UzkgMTEuMTIyIDkgMTIuNXMxLjEyMiAyLjUgMi41IDIuNSAyLjUtMS4xMjIgMi41LTIuNXptOSAwYzAtMS4zNzgtMS4xMjItMi41LTIuNS0yLjVTMTggMTEuMTIyIDE4IDEyLjVzMS4xMjIgMi41IDIuNSAyLjUgMi41LTEuMTIyIDIuNS0yLjV6Ii8+PC9zdmc+'
+const ICON_PLAYWRIGHT = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+UGxheXdyaWdodDwvdGl0bGU+PHBhdGggZD0iTTIzLjk5NiA3LjQ2MmMtLjA1Ni44MzctLjI1NyAyLjEzNS0uNzE2IDMuODUtLjk5NSAzLjcxNS00LjI3IDEwLjg3NC0xMC40MiA5LjIyNy02LjE1LTEuNjUtNS40MDctOS40ODctNC40MTItMTMuMjAxLjQ2LTEuNzE2LjkzNC0yLjk0IDEuMzA1LTMuNjk0LjQyLS44NTMuODQ2LS4yODkgMS44MTUuNTIzLjY4NC41NzMgMi40MSAxLjc5MSA1LjAxMSAyLjQ4OCAyLjYwMS42OTcgNC43MDYuNTA2IDUuNTgzLjM1MiAxLjI0NS0uMjE5IDEuODk3LS40OTQgMS44MzQuNDU1Wm0tOS44MDcgMy44NjNzLS4xMjctMS44MTktMS43NzMtMi4yODZjLTEuNjQ0LS40NjctMi42MTMgMS4wNC0yLjYxMyAxLjA0Wm00LjA1OCA0LjUzOS03Ljc2OS0yLjE3MnMuNDQ2IDIuMzA2IDMuMzM4IDMuMTUzYzIuODYyLjgzNiA0LjQzLS45OCA0LjQzLS45ODFabTIuNzAxLTIuNTFzLS4xMy0xLjgxOC0xLjc3My0yLjI4NmMtMS42NDQtLjQ2OS0yLjYxMiAxLjAzOC0yLjYxMiAxLjAzOFpNOC41NyAxOC4yM2MtNC43NDkgMS4yNzktNy4yNjEtNC4yMjQtOC4wMjEtNy4wOEMuMTk3IDkuODMxLjA0NCA4LjgzMi4wMDMgOC4xODhjLS4wNDctLjczLjQ1NS0uNTIgMS40MTUtLjM1NC42NzcuMTE4IDIuMy4yNjEgNC4zMDgtLjI4YTExLjI4IDExLjI4IDAgMCAwIDIuNDEtLjk1NmMtLjA1OC4xOTctLjExNC40LS4xNy42MS0uNDMzIDEuNjE4LS44MjcgNC4wNTUtLjYzMiA2LjQyNi0xLjk3Ni43MzItMi4yNjcgMi40MjMtMi4yNjcgMi40MjNsMi41MjQtLjcxNWMuMjI3IDEuMDAyLjYgMS45ODcgMS4xNSAyLjgzOGE1LjkxNCA1LjkxNCAwIDAgMS0uMTcxLjA0OVptLTQuMTg4LTYuMjk4YzEuMjY1LS4zMzMgMS4zNjMtMS42MzEgMS4zNjMtMS42MzFsLTMuMzc0Ljg4OHMuNzQ1IDEuMDc2IDIuMDEuNzQzWiIvPjwvc3ZnPg=='
+const ICON_HTML5 = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+SFRNTDU8L3RpdGxlPjxwYXRoIGQ9Ik0xLjUgMGgyMWwtMS45MSAyMS41NjNMMTEuOTc3IDI0bC04LjU2NC0yLjQzOEwxLjUgMHptNy4wMzEgOS43NWwtLjIzMi0yLjcxOCAxMC4wNTkuMDAzLjIzLTIuNjIyTDUuNDEyIDQuNDFsLjY5OCA4LjAxaDkuMTI2bC0uMzI2IDMuNDI2LTIuOTEuODA0LTIuOTU1LS44MS0uMTg4LTIuMTFINi4yNDhsLjMzIDQuMTcxTDEyIDE5LjM1MWw1LjM3OS0xLjQ0My43NDQtOC4xNTdIOC41MzF6Ii8+PC9zdmc+'
+const ICON_DOCKER = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+RG9ja2VyPC90aXRsZT48cGF0aCBkPSJNMTMuOTgzIDExLjA3OGgyLjExOWEuMTg2LjE4NiAwIDAgMCAuMTg2LS4xODVWOS4wMDZhLjE4Ni4xODYgMCAwIDAtLjE4Ni0uMTg2aC0yLjExOWEuMTg1LjE4NSAwIDAgMC0uMTg1LjE4NXYxLjg4OGMwIC4xMDIuMDgzLjE4NS4xODUuMTg1bS0yLjk1NC01LjQzaDIuMTE4YS4xODYuMTg2IDAgMCAwIC4xODYtLjE4NlYzLjU3NGEuMTg2LjE4NiAwIDAgMC0uMTg2LS4xODVoLTIuMTE4YS4xODUuMTg1IDAgMCAwLS4xODUuMTg1djEuODg0YzAgLjEwMi4wODIuMTg1LjE4NS4xODZtMCAyLjcxNmgyLjExOGEuMTg3LjE4NyAwIDAgMCAuMTg2LS4xODZWNi4yOWEuMTg2LjE4NiAwIDAgMC0uMTg2LS4xODVoLTIuMTE4YS4xODUuMTg1IDAgMCAwLS4xODUuMTg1djEuODg3YzAgLjEwMi4wODIuMTg1LjE4NS4xODZtLTIuOTMgMGgyLjEyYS4xODYuMTg2IDAgMCAwIC4xODQtLjE4NlY2LjI5YS4xODUuMTg1IDAgMCAwLS4xODUtLjE4NUg4LjFhLjE4NS4xODUgMCAwIDAtLjE4NS4xODV2MS44ODdjMCAuMTAyLjA4My4xODUuMTg1LjE4Nm0tMi45NjQgMGgyLjExOWEuMTg2LjE4NiAwIDAgMCAuMTg1LS4xODZWNi4yOWEuMTg1LjE4NSAwIDAgMC0uMTg1LS4xODVINS4xMzZhLjE4Ni4xODYgMCAwIDAtLjE4Ni4xODV2MS44ODdjMCAuMTAyLjA4NC4xODUuMTg2LjE4Nm01Ljg5MyAyLjcxNWgyLjExOGEuMTg2LjE4NiAwIDAgMCAuMTg2LS4xODVWOS4wMDZhLjE4Ni4xODYgMCAwIDAtLjE4Ni0uMTg2aC0yLjExOGEuMTg1LjE4NSAwIDAgMC0uMTg1LjE4NXYxLjg4OGMwIC4xMDIuMDgyLjE4NS4xODUuMTg1bS0yLjkzIDBoMi4xMmEuMTg1LjE4NSAwIDAgMCAuMTg0LS4xODVWOS4wMDZhLjE4NS4xODUgMCAwIDAtLjE4NC0uMTg2aC0yLjEyYS4xODUuMTg1IDAgMCAwLS4xODQuMTg1djEuODg4YzAgLjEwMi4wODMuMTg1LjE4NS4xODVtLTIuOTY0IDBoMi4xMTlhLjE4NS4xODUgMCAwIDAgLjE4NS0uMTg1VjkuMDA2YS4xODUuMTg1IDAgMCAwLS4xODUtLjE4NmgtMi4xMTlhLjE4NS4xODUgMCAwIDAtLjE4NS4xODV2MS44ODhjMCAuMTAyLjA4My4xODUuMTg1LjE4NW0tMi45MiAwaDIuMTJhLjE4NS4xODUgMCAwIDAgLjE4NC0uMTg1VjkuMDA2YS4xODUuMTg1IDAgMCAwLS4xODQtLjE4NmgtMi4xMmEuMTg1LjE4NSAwIDAgMC0uMTg0LjE4NXYxLjg4OGMwIC4xMDIuMDgyLjE4NS4xODUuMTg1TTIzLjc2MyA5Ljg5Yy0uMDY1LS4wNTEtLjY3Mi0uNTEtMS45NTQtLjUxLS4zMzguMDAxLS42NzYuMDMtMS4wMS4wODctLjI0OC0xLjctMS42NTMtMi41My0xLjcxNi0yLjU2NmwtLjM0NC0uMTk5LS4yMjYuMzI3Yy0uMjg0LjQzOC0uNDkuOTIyLS42MTIgMS40My0uMjMuOTctLjA5IDEuODgyLjQwMyAyLjY2MS0uNTk1LjMzMi0xLjA0M... (truncated)'
+const ICON_JUPYTER = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+SnVweXRlcjwvdGl0bGU+PHBhdGggZD0iTTcuMTU3IDIyLjIwMUExLjc4NCAxLjc5OSAwIDAgMSA1LjM3NCAyNGExLjc4NCAxLjc5OSAwIDAgMS0xLjc4NC0xLjc5OSAxLjc4NCAxLjc5OSAwIDAgMSAxLjc4NC0xLjc5OSAxLjc4NCAxLjc5OSAwIDAgMSAxLjc4MyAxLjc5OXpNMjAuNTgyIDEuNDI3YTEuNDE1IDEuNDI3IDAgMCAxLTEuNDE1IDEuNDI4IDEuNDE1IDEuNDI3IDAgMCAxLTEuNDE2LTEuNDI4QTEuNDE1IDEuNDI3IDAgMCAxIDE5LjE2NyAwYTEuNDE1IDEuNDI3IDAgMCAxIDEuNDE1IDEuNDI3ek00Ljk5MiAzLjMzNkExLjA0NyAxLjA1NiAwIDAgMSAzLjk0NiA0LjM5YTEuMDQ3IDEuMDU2IDAgMCAxLTEuMDQ3LTEuMDU1QTEuMDQ3IDEuMDU2IDAgMCAxIDMuOTQ2IDIuMjhhMS4wNDcgMS4wNTYgMCAwIDEgMS4wNDYgMS4wNTZ6bTcuMzM2IDEuNTE3YzMuNzY5IDAgNy4wNiAxLjM4IDguNzY4IDMuNDI0YTkuMzYzIDkuMzYzIDAgMCAwLTMuMzkzLTQuNTQ3IDkuMjM4IDkuMjM4IDAgMCAwLTUuMzc3LTEuNzI4QTkuMjM4IDkuMjM4IDAgMCAwIDYuOTUgMy43M2E5LjM2MyA5LjM2MyAwIDAgMC0zLjM5NCA0LjU0N2MxLjcxMy0yLjA0IDUuMDA0LTMuNDI0IDguNzcyLTMuNDI0em0uMDAxIDEzLjI5NWMtMy43NjggMC03LjA2LTEuMzgxLTguNzY4LTMuNDI1YTkuMzYzIDkuMzYzIDAgMCAwIDMuMzk0IDQuNTQ3QTkuMjM4IDkuMjM4IDAgMCAwIDEyLjMzIDIxYTkuMjM4IDkuMjM4IDAgMCAwIDUuMzc3LTEuNzI5IDkuMzYzIDkuMzYzIDAgMCAwIDMuOTk3LTQuNTQ3Yy0xLjcxMiAyLjA0NC01LjAwMyAzLjQyNS04Ljc3MiAzLjQyNVoiLz48L3N2Zz4='
+const ICON_LINEAR = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+TGluZWFyPC90aXRsZT48cGF0aCBkPSJNMi44ODYgNC4xOEExMS45ODIgMTEuOTgyIDAgMCAxIDExLjk5IDBDMTguNjI0IDAgMjQgNS4zNzYgMjQgMTIuMDA5YzAgMy42NC0xLjYyIDYuOTAzLTQuMTggOS4xMDVMMi44ODcgNC4xOFpNMS44MTcgNS42MjZsMTYuNTU2IDE2LjU1NmMtLjUyNC4zMy0xLjA3NS42Mi0xLjY1Ljg2NkwuOTUxIDcuMjc3Yy4yNDctLjU3NS41MzctMS4xMjYuODY2LTEuNjVaTS4zMjIgOS4xNjNsMTQuNTE1IDE0LjUxNWMtLjcxLjE3Mi0xLjQ0My4yODItMi4xOTUuMzIyTDAgMTEuMzU4YTEyIDEyIDAgMCAxIC4zMjItMi4xOTVabS0uMTcgNC44NjIgOS44MjMgOS44MjRhMTIuMDIgMTIuMDIgMCAwIDEtOS44MjQtOS44MjRaIi8+PC9zdmc+'
+const ICON_NOTION = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+Tm90aW9uPC90aXRsZT48cGF0aCBkPSJNNC40NTkgNC4yMDhjLjc0Ni42MDYgMS4wMjYuNTYgMi40MjguNDY2bDEzLjIxNS0uNzkzYy4yOCAwIC4wNDctLjI4LS4wNDYtLjMyNkwxNy44NiAxLjk2OGMtLjQyLS4zMjYtLjk4MS0uNy0yLjA1NS0uNjA3TDMuMDEgMi4yOTVjLS40NjYuMDQ2LS41Ni4yOC0uMzc0LjQ2NnptLjc5MyAzLjA4djEzLjkwNGMwIC43NDcuMzczIDEuMDI3IDEuMjE0Ljk4bDE0LjUyMy0uODRjLjg0MS0uMDQ2LjkzNS0uNTYuOTM1LTEuMTY3VjYuMzU0YzAtLjYwNi0uMjMzLS45MzMtLjc0OC0uODg3bC0xNS4xNzcuODg3Yy0uNTYuMDQ3LS43NDcuMzI3LS43NDcuOTMzem0xNC4zMzcuNzQ1Yy4wOTMuNDIgMCAuODQtLjQyLjg4OGwtLjcuMTR2MTAuMjY0Yy0uNjA4LjMyNy0xLjE2OC41MTQtMS42MzUuNTE0LS43NDggMC0uOTM1LS4yMzQtMS40OTUtLjkzM2wtNC41NzctNy4xODZ2Ni45NTJMMTIuMjEgMTlzMCAuODQtMS4xNjguODRsLTMuMjIyLjE4NmMtLjA5My0uMTg2IDAtLjY1My4zMjctLjc0NmwuODQtLjIzM1Y5Ljg1NEw3LjgyMiA5Ljc2Yy0uMDk0LS40Mi4xNC0xLjAyNi43OTMtMS4wNzNsMy40NTYtLjIzMyA0Ljc2NCA3LjI3OXYtNi40NGwtMS4yMTUtLjEzOWMtLjA5My0uNTE0LjI4LS44ODcuNzQ3LS45MzN6TTEuOTM2IDEuMDM1bDEzLjMxLS45OGMxLjYzNC0uMTQgMi4wNTUtLjA0NyAzLjA4Mi43bDQuMjQ5IDIuOTg2Yy43LjUxMy45MzQuNjUzLjkzNCAxLjIxM3YxNi4zNzhjMCAxLjAyNi0uMzczIDEuNjM0LTEuNjggMS43MjZsLTE1LjQ1OC45MzRjLS45OC4wNDctMS40NDgtLjA5My0xLjk2Mi0uNzQ3bC0zLjEyOS00LjA2Yy0uNTYtLjc0Ny0uNzkzLTEuMzA2LS43OTMtMS45NlYyLjY2N2MwLS44MzkuMzc0LTEuNTQgMS40NDctMS42MzJ6Ii8+PC9zdmc+'
+const ICON_SENTRY = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+U2VudHJ5PC90aXRsZT48cGF0aCBkPSJNMTMuOTEgMi41MDVjLS44NzMtMS40NDgtMi45NzItMS40NDgtMy44NDQgMEw2LjkwNCA3LjkyYTE1LjQ3OCAxNS40NzggMCAwIDEgOC41MyAxMi44MTFoLTIuMjIxQTEzLjMwMSAxMy4zMDEgMCAwIDAgNS43ODQgOS44MTRsLTIuOTI2IDUuMDZhNy42NSA3LjY1IDAgMCAxIDQuNDM1IDUuODQ4SDIuMTk0YS4zNjUuMzY1IDAgMCAxLS4yOTgtLjUzNGwxLjQxMy0yLjQwMmE1LjE2IDUuMTYgMCAwIDAtMS42MTQtLjkxM0wuMjk2IDE5LjI3NWEyLjE4MiAyLjE4MiAwIDAgMCAuODEyIDIuOTk5IDIuMjQgMi4yNCAwIDAgMCAxLjA4Ni4yODhoNi45ODNhOS4zMjIgOS4zMjIgMCAwIDAtMy44NDUtOC4zMThsMS4xMS0xLjkyMmExMS40NyAxMS40NyAwIDAgMSA0Ljk1IDEwLjI0aDUuOTE1YTE3LjI0MiAxNy4yNDIgMCAwIDAtNy44ODUtMTUuMjhsMi4yNDQtMy44NDVhLjM3LjM3IDAgMCAxIC41MDQtLjEzYy4yNTUuMTQgOS43NSAxNi43MDggOS45MjggMTYuOWEuMzY1LjM2NSAwIDAgMS0uMzI3LjU0M2gtMi4yODdjLjAyOS42MTIuMDI5IDEuMjIzIDAgMS44MzFoMi4yOTdhMi4yMDYgMi4yMDYgMCAwIDAgMS45MjItMy4zMXoiLz48L3N2Zz4='
+const ICON_VERCEL = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+VmVyY2VsPC90aXRsZT48cGF0aCBkPSJtMTIgMS42MDggMTIgMjAuNzg0SDBaIi8+PC9zdmc+'
+const ICON_NETLIFY = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+TmV0bGlmeTwvdGl0bGU+PHBhdGggZD0iTTYuNDkgMTkuMDRoLS4yM0w1LjEzIDE3Ljl2LS4yM2wxLjczLTEuNzFoMS4ybC4xNS4xNXYxLjJMNi41IDE5LjA0Wk01LjEzIDYuMzFWNi4xbDEuMTMtMS4xM2guMjNMOC4yIDYuNjh2MS4ybC0uMTUuMTVoLTEuMkw1LjEzIDYuMzFabTkuOTYgOS4wOWgtMS42NWwtLjE0LS4xM3YtMy44M2MwLS42OC0uMjctMS4yLTEuMS0xLjIzLS40MiAwLS45IDAtMS40My4wMmwtLjA3LjA4djQuOTZsLS4xNC4xNEg4LjlsLS4xMy0uMTRWOC43M2wuMTMtLjE0aDMuN2EyLjYgMi42IDAgMCAxIDIuNjEgMi42djQuMDhsLS4xMy4xNFptLTguMzctMi40NEguMTRMMCAxMi44MnYtMS42NGwuMTQtLjE0aDYuNThsLjE0LjE0djEuNjRsLS4xNC4xNFptMTcuMTQgMGgtNi41OGwtLjE0LS4xNHYtMS42NGwuMTQtLjE0aDYuNThsLjE0LjE0djEuNjRsLS4xNC4xNFpNMTEuMDUgNi41NVYxLjY0bC4xNC0uMTRoMS42NWwuMTQuMTR2NC45bC0uMTQuMTRoLTEuNjVsLS4xNC0uMTNabTAgMTUuODF2LTQuOWwuMTQtLjE0aDEuNjVsLjE0LjEzdjQuOTFsLS4xNC4xNGgtMS42NWwtLjE0LS4xNFoiLz48L3N2Zz4='
+const ICON_CLOUDFLARE = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+Q2xvdWRmbGFyZTwvdGl0bGU+PHBhdGggZD0iTTE2LjUwODggMTYuODQ0N2MuMTQ3NS0uNTA2OC4wOTA4LS45NzA3LS4xNTUzLTEuMzE1NC0uMjI0Ni0uMzE2NC0uNjA0NS0uNDk5LTEuMDYxNS0uNTIwNWwtOC42NTkyLS4xMTIzYS4xNTU5LjE1NTkgMCAwIDEtLjEzMzMtLjA3MTNjLS4wMjgzLS4wNDItLjAzNTEtLjA5ODYtLjAyMS0uMTU1My4wMjc4LS4wODQuMTEyMy0uMTQ4NC4yMDM2LS4xNTYybDguNzM1OS0uMTEyM2MxLjAzNTEtLjA0ODkgMi4xNjAxLS44ODY4IDIuNTUzNy0xLjkxMzZsLjQ5OS0xLjMwMTNjLjAyMTUtLjA1NjEuMDI5My0uMTEyOC4wMTQ3LS4xNjgtLjU2MjUtMi41NDYzLTIuODM1LTQuNDQ1My01LjU0OTktNC40NDUzLTIuNTAzOSAwLTQuNjI4NCAxLjYxNzctNS4zODc2IDMuODYxNC0uNDkyNy0uMzY1OC0xLjExODctLjU2MjUtMS43OTQtLjQ5OS0xLjIwMjYuMTE5LTIuMTY2NSAxLjA4My0yLjI4NjEgMi4yODU2LS4wMjgzLjMxLS4wMDY5LjYxMjguMDYzNS44OTRDMS41NjgzIDEzLjE3MSAwIDE0Ljc3NTQgMCAxNi43NTJjMCAuMTc0OC4wMTQyLjM1MTUuMDM1Mi41MjczLjAxNDEuMDgzLjA4NDQuMTQ3NS4xNjg5LjE0NzVoMTUuOTgxNGMuMDkwOSAwIC4xNzU4LS4wNjQ1LjIwMzItLjE1NTNsLjEyLS40MjY4em0yLjc1NjgtNS41NjM0Yy0uMDc3MSAwLS4xNjExIDAtLjIzODMuMDExMi0uMDU2NiAwLS4xMDU0LjA0MTUtLjEyNy4wOTc2bC0uMzM3OCAxLjE3NDRjLS4xNDc1LjUwNjgtLjA5MTguOTcwNy4xNTQzIDEuMzE2NC4yMjU2LjMxNjQuNjA1NS40OTggMS4wNjI1LjUxOTVsMS44NDM3LjExMzNjLjA1NTcgMCAuMTA1NS4wMjYzLjEzMjkuMDcwMy4wMjgzLjA0My4wMzUxLjEwNzQuMDIxNC4xNTYyLS4wMjgzLjA4NC0uMTEzMi4xNDg1LS4yMDQuMTU1M2wtMS45MjEuMTEyM2MtMS4wNDEuMDQ4OC0yLjE1ODIuODg2Ny0yLjU1MjcgMS45MTRsLS4xNDA2LjM1ODVjLS4wMjgzLjA3MTMuMDIxNS4xNDE2LjA5ODYuMTQxNmg2LjU5NzdjLjA3NzEgMCAuMTQ3NC0uMDQ4OS4xNjktLjEyNi4xMTIyLS40MDgyLjE3NTctLjgzNy4xNzU3LTEuMjgwMyAwLTIuNjAyNS0yLjEyNS00LjcyNy00LjczNDQtNC43MjciLz48L3N2Zz4='
+const ICON_FIGMA = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+RmlnbWE8L3RpdGxlPjxwYXRoIGQ9Ik0xNS44NTIgOC45ODFoLTQuNTg4VjBoNC41ODhjMi40NzYgMCA0LjQ5IDIuMDE0IDQuNDkgNC40OXMtMi4wMTQgNC40OTEtNC40OSA0LjQ5MXpNMTIuNzM1IDcuNTFoMy4xMTdjMS42NjUgMCAzLjAxOS0xLjM1NSAzLjAxOS0zLjAxOXMtMS4zNTUtMy4wMTktMy4wMTktMy4wMTloLTMuMTE3VjcuNTF6bTAgMS40NzFIOC4xNDhjLTIuNDc2IDAtNC40OS0yLjAxNC00LjQ5LTQuNDlTNS42NzIgMCA4LjE0OCAwaDQuNTg4djguOTgxem0tNC41ODctNy41MWMtMS42NjUgMC0zLjAxOSAxLjM1NS0zLjAxOSAzLjAxOXMxLjM1NCAzLjAyIDMuMDE5IDMuMDJoMy4xMTdWMS40NzFIOC4xNDh6bTQuNTg3IDE1LjAxOUg4LjE0OGMtMi40NzYgMC00LjQ5LTIuMDE0LTQuNDktNC40OXMyLjAxNC00LjQ5IDQuNDktNC40OWg0LjU4OHY4Ljk4ek04LjE0OCA4Ljk4MWMtMS42NjUgMC0zLjAxOSAxLjM1NS0zLjAxOSAzLjAxOXMxLjM1NSAzLjAxOSAzLjAxOSAzLjAxOWgzLjExN1Y4Ljk4MUg4LjE0OHpNOC4xNzIgMjRjLTIuNDg5IDAtNC41MTUtMi4wMTQtNC41MTUtNC40OXMyLjAxNC00LjQ5IDQuNDktNC40OWg0LjU4OHY0LjQ0MWMwIDIuNTAzLTIuMDQ3IDQuNTM5LTQuNTYzIDQuNTM5em0tLjAyNC03LjUxYTMuMDIzIDMuMDIzIDAgMCAwLTMuMDE5IDMuMDE5YzAgMS42NjUgMS4zNjUgMy4wMTkgMy4wNDQgMy4wMTkgMS43MDUgMCAzLjA5My0xLjM3NiAzLjA5My0zLjA2OHYtMi45N0g4LjE0OHptNy43MDQgMGgtLjA5OGMtMi40NzYgMC00LjQ5LTIuMDE0LTQuNDktNC40OXMyLjAxNC00LjQ5IDQuNDktNC40OWguMDk4YzIuNDc2IDAgNC40OSAyLjAxNCA0LjQ5IDQuNDlzLTIuMDE0IDQuNDktNC40OSA0LjQ5em0tLjA5Ny03LjUwOWMtMS42NjUgMC0zLjAxOSAxLjM1NS0zLjAxOSAzLjAxOXMxLjM1NSAzLjAxOSAzLjAxOSAzLjAxOWguMDk4YzEuNjY1IDAgMy4wMTktMS4zNTUgMy4wMTktMy4wMTlzLTEuMzU1LTMuMDE5LTMuMDE5LTMuMDE5aC0uMDk4eiIvPjwvc3ZnPg=='
+const ICON_LOCK = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+TGV0J3MgRW5jcnlwdDwvdGl0bGU+PHBhdGggZD0iTTExLjk5MTQgMGEuODgyOS44ODI5IDAgMDAtLjg3MTguODE3djMuMDIwOUEuODgyOS44ODI5IDAgMDAxMiA0LjcyMDdhLjg4MjkuODgyOSAwIDAwLjg4MDMtLjg4MDNWLjgxN2EuODgyOS44ODI5IDAgMDAtLjg4OS0uODE3em03LjcwNDggMy4xMDg5YS44ODA0Ljg4MDQgMCAwMC0uNTIxNC4xNzQybC0yLjM3NCAxLjk0ODJhLjg4MDQuODgwNCAwIDAwLjU1OTIgMS41NjIyLjg3OTQuODc5NCAwIDAwLjU1OTItLjIwMDFsMi4zNzE0LTEuOTUwNmEuODgwNC44ODA0IDAgMDAtLjU5NDQtMS41MzR6bS0xNS4zNzYzLjAxMzNhLjg4MjkuODgyOSAwIDAwLS42MTEgMS41MjA2bDIuMzcgMS45NTA2YS44NzYuODc2IDAgMDAuNTYwNi4yMDAxdi0uMDAyYS44ODA0Ljg4MDQgMCAwMC41NTk3LTEuNTYwMkw0LjgyNzcgMy4yODMxYS44ODI5Ljg4MjkgMCAwMC0uNTA3OC0uMTYxem03LjY1OTggMy4yMjc1YTUuMDQ1NiA1LjA0NTYgMCAwMC01LjAyNjIgNS4wNDU1djEuNDg3Nkg1Ljc4N2EuOTY3Mi45NjcyIDAgMDAtLjk2NDcuOTY0M3Y5LjE4ODdhLjk2NzIuOTY3MiAwIDAwLjk2NDcuOTY0M0gxOC4yMTNhLjk2NzIuOTY3MiAwIDAwLjk2NDMtLjk2NDN2LTkuMTkwN2EuOTY3Mi45NjcyIDAgMDAtLjk2NDMtLjk2MjNoLTEuMTY4NHYtMS40ODc2YTUuMDQ1NiA1LjA0NTYgMCAwMC01LjA2NDktNS4wNDU1em0uMDEyNyAyLjg5MzNhMi4xNTIyIDIuMTUyMiAwIDAxMi4xNTkzIDIuMTUyMnYxLjQ4NzZIOS44NDczdi0xLjQ4NzZhMi4xNTIyIDIuMTUyMiAwIDAxMi4xNDUtMi4xNTIyem03LjM4MTIuNTAzM2EuODgyOS44ODI5IDAgMTAuMDcwNSAxLjc2MzJoMy4wMjY3YS44ODI5Ljg4MjkgMCAwMDAtMS43NjA5SDE5LjQ0NGEuODgyOS44ODI5IDAgMDAtLjA3MDUtLjAwMjN6bS0xNy44NDQ0LjAwMjNhLjg4MjkuODgyOSAwIDAwMCAxLjc2MDloMi45OTgzYS44ODI5Ljg4MjkgMCAwMDAtMS43NjA5em0xMC40NTk2IDYuNzc0NmExLjI3OTIgMS4yNzkyIDAgMDEuNjQxIDIuMzkyNnYxLjI0NTNhLjYyOTguNjI5OCAwIDAxLTEuMjU5NSAwdi0xLjI0NTNhMS4yNzkyIDEuMjc5MiAwIDAxLjYxODUtMi4zOTI2eiIvPjwvc3ZnPg=='
+const ICON_SHEETS = 'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R29vZ2xlIFNoZWV0czwvdGl0bGU+PHBhdGggZD0iTTExLjMxOCAxMi41NDVINy45MXYtMS45MDloMy40MXYxLjkxek0xNC43MjggMHY2aDZsLTYtNnptMS4zNjMgMTAuNjM2aC0zLjQxdjEuOTFoMy40MXYtMS45MXptMCAzLjI3M2gtMy40MXYxLjkxaDMuNDF2LTEuOTF6TTIwLjcyNyA2LjV2MTUuODY0YzAgLjkwNC0uNzMyIDEuNjM2LTEuNjM2IDEuNjM2SDQuOTA5YTEuNjM2IDEuNjM2IDAgMCAxLTEuNjM2LTEuNjM2VjEuNjM2QzMuMjczLjczMiA0LjAwNSAwIDQuOTA5IDBoOS4zMTh2Ni41aDYuNXptLTMuMjczIDIuNzczSDYuNTQ1djcuOTA5aDEwLjkxdi03Ljkxem0tNi4xMzYgNC42MzZINy45MXYxLjkxaDMuNDF2LTEuOTF6Ii8+PC9zdmc+'
+
+export interface BuiltinRegistryEntry {
+ slug: string
+ iconUrl: string
+ category: string
+ author: string
+}
+
+export const OPENAI_SKILL_REGISTRY: Record = {
+ 'spreadsheet': { slug: 'spreadsheet-openai', iconUrl: ICON_SHEETS, category: 'dev', author: 'OpenAI' },
+ 'yeet': { slug: 'yeet', iconUrl: ICON_GITHUB, category: 'dev', author: 'OpenAI' },
+ 'playwright': { slug: 'playwright', iconUrl: ICON_PLAYWRIGHT, category: 'testing', author: 'OpenAI' },
+ 'gh-fix-ci': { slug: 'gh-fix-ci', iconUrl: ICON_GITHUB_ACTIONS, category: 'ops', author: 'OpenAI' },
+ 'gh-address-comments': { slug: 'gh-address-comments', iconUrl: ICON_GITHUB, category: 'dev', author: 'OpenAI' },
+ 'develop-web-game': { slug: 'develop-web-game', iconUrl: ICON_HTML5, category: 'dev', author: 'OpenAI' },
+ 'screenshot': { slug: 'screenshot', iconUrl: ICON_OPENAI, category: 'testing', author: 'OpenAI' },
+ 'docker-compose': { slug: 'docker-compose', iconUrl: ICON_DOCKER, category: 'ops', author: 'OpenAI' },
+ 'imagegen': { slug: 'imagegen', iconUrl: ICON_OPENAI, category: 'content', author: 'OpenAI' },
+ 'transcribe': { slug: 'transcribe', iconUrl: ICON_OPENAI, category: 'content', author: 'OpenAI' },
+ 'jupyter-notebook': { slug: 'jupyter-notebook', iconUrl: ICON_JUPYTER, category: 'search', author: 'OpenAI' },
+ 'linear': { slug: 'linear', iconUrl: ICON_LINEAR, category: 'dev', author: 'OpenAI' },
+ 'notion-knowledge-capture': { slug: 'notion-knowledge-capture', iconUrl: ICON_NOTION, category: 'dev', author: 'OpenAI' },
+ 'sentry': { slug: 'sentry', iconUrl: ICON_SENTRY, category: 'ops', author: 'OpenAI' },
+ 'vercel-deploy': { slug: 'vercel-deploy', iconUrl: ICON_VERCEL, category: 'ops', author: 'OpenAI' },
+ 'netlify-deploy': { slug: 'netlify-deploy', iconUrl: ICON_NETLIFY, category: 'ops', author: 'OpenAI' },
+ 'cloudflare-deploy': { slug: 'cloudflare-deploy', iconUrl: ICON_CLOUDFLARE, category: 'ops', author: 'OpenAI' },
+ 'figma': { slug: 'figma', iconUrl: ICON_FIGMA, category: 'content', author: 'OpenAI' },
+ 'security-best-practices': { slug: 'security-best-practices', iconUrl: ICON_LOCK, category: 'dev', author: 'OpenAI' },
+}
+
+export const CLAUDE_SKILL_REGISTRY: Record = {
+ 'webapp-testing': { slug: 'webapp-testing', iconUrl: ICON_GITHUB, category: 'testing', author: 'Anthropic' },
+ 'pptx': { slug: 'pptx', iconUrl: ICON_OPENAI, category: 'content', author: 'Anthropic' },
+ 'pdf': { slug: 'pdf', iconUrl: ICON_OPENAI, category: 'content', author: 'Anthropic' },
+ 'docx': { slug: 'docx', iconUrl: ICON_OPENAI, category: 'content', author: 'Anthropic' },
+ 'xlsx': { slug: 'xlsx', iconUrl: ICON_OPENAI, category: 'content', author: 'Anthropic' },
+ 'pptx-generator': { slug: 'pptx-generator', iconUrl: ICON_OPENAI, category: 'content', author: 'Anthropic' },
+ 'mcp-builder': { slug: 'mcp-builder', iconUrl: ICON_OPENAI, category: 'dev', author: 'Anthropic' },
+ 'skill-creator': { slug: 'skill-creator', iconUrl: ICON_OPENAI, category: 'other', author: 'Anthropic' },
+}
+
+export function lookupBuiltinIcon(repo: string, slug: string): string | undefined {
+ if (repo.includes('openai/skills')) {
+ return OPENAI_SKILL_REGISTRY[slug]?.iconUrl
+ }
+ if (repo.includes('anthropics/skills')) {
+ return CLAUDE_SKILL_REGISTRY[slug]?.iconUrl
+ }
+ return undefined
+}
+
+export function lookupBuiltinCategory(repo: string, slug: string): string | undefined {
+ if (repo.includes('openai/skills')) {
+ return OPENAI_SKILL_REGISTRY[slug]?.category
+ }
+ if (repo.includes('anthropics/skills')) {
+ return CLAUDE_SKILL_REGISTRY[slug]?.category
+ }
+ return undefined
+}
diff --git a/plugins/skill-hub/src/data/store-icons.ts b/plugins/skill-hub/src/data/store-icons.ts
new file mode 100644
index 00000000..d6b22f3a
--- /dev/null
+++ b/plugins/skill-hub/src/data/store-icons.ts
@@ -0,0 +1,41 @@
+import { PLATFORM_ICONS } from './platform-icons'
+import skillsShIcon from '../assets/platforms/skills-sh-favicon.ico'
+
+export const STORE_ICONS: Record = {
+ 'skills-sh': skillsShIcon,
+ 'claude': PLATFORM_ICONS.claude,
+ 'codex': PLATFORM_ICONS.codex,
+}
+
+export type StoreId = keyof typeof STORE_ICONS
+
+export function getStoreIcon(storeId: string): string | undefined {
+ return STORE_ICONS[storeId as StoreId]
+}
+
+export const ICON_GITHUB = ``
+
+export const ICON_MARKETPLACE = ``
+
+export const ICON_FOLDER = ``
+
+export const ICON_STORE = ``
+
+export function getDefaultStoreIcon(type: string): string {
+ switch (type) {
+ case 'git-repo':
+ case 'github':
+ return ICON_GITHUB
+ case 'marketplace-json':
+ return ICON_MARKETPLACE
+ case 'local-dir':
+ return ICON_FOLDER
+ default:
+ return ICON_STORE
+ }
+}
+
+export function getStoreIconFromSource(source: { type: string; icon?: string }): string {
+ if (source.icon) return source.icon
+ return getDefaultStoreIcon(source.type)
+}
diff --git a/plugins/skill-hub/src/env.d.ts b/plugins/skill-hub/src/env.d.ts
new file mode 100644
index 00000000..8c1c7634
--- /dev/null
+++ b/plugins/skill-hub/src/env.d.ts
@@ -0,0 +1,94 @@
+///
+///
+
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+ const component: DefineComponent, Record, unknown>
+ export default component
+}
+
+interface DirEntry {
+ name: string
+ path: string
+ isDirectory: boolean
+ isFile: boolean
+ isSymlink: boolean
+}
+
+interface StatResult {
+ exists: boolean
+ isDirectory?: boolean
+ isFile?: boolean
+ isSymlink?: boolean
+ size?: number
+ mtime?: string
+}
+
+interface SkillScanResult {
+ name: string
+ dir: string
+ skillFile: string
+ content: string
+ manifest: {
+ name: string
+ description: string
+ author: string
+ tags: string[]
+ format: string
+ language: string
+ }
+}
+
+interface Services {
+ readFile: (file: string) => string
+ writeTextFile: (text: string) => string
+ writeImageFile: (base64Url: string) => string | undefined
+
+ expandPath: (p: string) => string
+ homeDir: () => string
+ isWindows: () => boolean
+ isMacOS: () => boolean
+ pathJoin: (...parts: string[]) => string
+ pathExists: (p: string) => boolean
+ mkdir: (dir: string) => void
+ openFolder: (dir: string) => void
+ readDir: (dir: string) => DirEntry[]
+ readFileText: (filePath: string) => string
+ writeFile: (filePath: string, content: string) => void
+ removeFile: (filePath: string) => void
+ copyFile: (src: string, dest: string) => void
+ stat: (p: string) => StatResult
+
+ createSymlink: (target: string, linkPath: string) => string
+ readSymlink: (linkPath: string) => string | null
+
+ downloadFile: (url: string) => Promise
+ downloadFileTo: (url: string, destPath: string) => Promise
+
+ fetchGitHubAPI: (url: string, token?: string) => Promise
+ fetchGitHubText: (url: string, token?: string) => Promise
+
+ extractZip: (zipPath: string, dest: string) => string
+ extractBufferZip: (buffer: ArrayBuffer, dest: string) => string
+ extractTarGz: (tarPath: string, dest: string) => Promise
+
+ scanForSkills: (rootDir: string) => SkillScanResult[]
+ scanForSkillFiles: (dirs: string[]) => SkillScanResult[]
+ parseSkillFile: (filePath: string) => { content: string; manifest: SkillScanResult['manifest'] } | null
+
+ checkSkillUpdate: (repo: string, skillPath: string, token?: string, branch?: string) => Promise
+ updateSkillFromGitHub: (repo: string, skillPath: string, targetDir: string, token?: string, branch?: string) => Promise
+
+ getStateDir: () => string
+
+ createPluginZip: (sourceDir: string) => { outputPath: string; fileName: string }
+}
+
+declare global {
+ interface Window {
+ services: Services
+ ztools: ZToolsApi
+ }
+}
+
+export {}
diff --git a/plugins/skill-hub/src/main.css b/plugins/skill-hub/src/main.css
new file mode 100644
index 00000000..1b23fcb8
--- /dev/null
+++ b/plugins/skill-hub/src/main.css
@@ -0,0 +1,19 @@
+/* Global reset — design tokens live in App.vue */
+html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow: hidden;
+}
+
+button {
+ font-family: inherit;
+}
+
+input, textarea, select {
+ font-family: inherit;
+}
+
+::selection {
+ background: hsl(var(--primary) / 0.25);
+}
diff --git a/plugins/skill-hub/src/main.ts b/plugins/skill-hub/src/main.ts
new file mode 100644
index 00000000..8a3c77bc
--- /dev/null
+++ b/plugins/skill-hub/src/main.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './main.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')
diff --git a/plugins/skill-hub/src/types.ts b/plugins/skill-hub/src/types.ts
new file mode 100644
index 00000000..54cc1f01
--- /dev/null
+++ b/plugins/skill-hub/src/types.ts
@@ -0,0 +1,279 @@
+export type SkillFormat = 'opencode' | 'claude' | 'codex' | 'cline' | 'generic'
+export type SkillSource = 'builtin' | 'github' | 'skills-sh' | 'local' | 'marketplace-json' | 'git-repo' | 'local-dir'
+export type StoreSourceType = 'marketplace-json' | 'git-repo' | 'local-dir'
+export type InstallMode = 'symlink' | 'copy'
+
+export interface Skill {
+ id: string
+ name: string
+ description: string
+ author: string
+ tags: string[]
+ format: SkillFormat
+ source: SkillSource
+ sourceUrl?: string
+ repo?: string
+ path?: string
+ homepage?: string
+ readme?: string
+ category?: string
+ installCount?: number
+ iconUrl?: string
+ userTags?: string[]
+ storeSourceId?: string
+ canonicalId?: string
+ branch?: string
+}
+
+export interface SkillSourceLocation {
+ type: SkillSource
+ location: string
+ platformId?: string
+ projectId?: string
+ installedAt: string
+}
+
+export interface SkillIdentity {
+ canonicalId: string
+ name: string
+ description: string
+ author: string
+ tags: string[]
+ format: SkillFormat
+ contentHash: string
+ sources: SkillSourceLocation[]
+ primarySource?: SkillSourceLocation
+ createdAt: string
+ updatedAt: string
+}
+
+export interface PlatformInfo {
+ id: string
+ name: string
+ rootDir?: { darwin: string; win32: string; linux: string }
+ skillsRelativePath?: string
+ defaultPath: string
+ projectPath?: string
+ rulesPath?: string
+ customPath?: string
+ customProjectPath?: string
+ enabled: boolean
+ detected: boolean
+ notes?: string
+}
+
+export interface InstallRecord {
+ skillId: string
+ platformId: string
+ mode: InstallMode
+ scope?: 'global' | 'project'
+ targetPath: string
+ sourceDir: string
+ installedAt: string
+ updatedAt?: string
+}
+
+export interface StoreSource {
+ id: string
+ type: SkillSource
+ name: string
+ url?: string
+ branch?: string
+ directory?: string
+ enabled: boolean
+ icon?: string
+}
+
+export type ThemeMode = 'light' | 'dark' | 'auto'
+export type FontSize = 'small' | 'medium' | 'large'
+export type MotionPreference = 'off' | 'reduced' | 'standard'
+
+export interface MorandiTheme {
+ id: string
+ name: string
+ hue: number
+ saturation: number
+}
+
+export const MORANDI_THEMES: MorandiTheme[] = [
+ { id: 'royal-blue', name: '皇家蓝', hue: 220, saturation: 70 },
+ { id: 'blue', name: '雾霾蓝', hue: 210, saturation: 35 },
+ { id: 'purple', name: '烟熏紫', hue: 260, saturation: 30 },
+ { id: 'green', name: '豆沙绿', hue: 150, saturation: 30 },
+ { id: 'orange', name: '杏色橙', hue: 25, saturation: 40 },
+ { id: 'teal', name: '青碧色', hue: 175, saturation: 30 },
+]
+
+export const FONT_SIZES: Record = {
+ small: '14px',
+ medium: '16px',
+ large: '18px',
+}
+
+export interface AppSettings {
+ defaultInstallMode: InstallMode
+ githubToken: string
+ theme: 'auto' | 'light' | 'dark'
+ themeMode: ThemeMode
+ themeColor: string
+ fontSize: FontSize
+ motionPreference: MotionPreference
+ compactMode: boolean
+ backgroundImage: string
+ backgroundImageEnabled: boolean
+ backgroundOpacity: number
+ backgroundBlur: number
+ aiModels: ModelConfig[]
+ translationModelId: string
+}
+
+export interface SkillManifest {
+ name: string
+ description: string
+ author: string
+ tags: string[]
+ format: SkillFormat
+ language: string
+}
+
+// === skills.sh API types ===
+export interface V1Skill {
+ id: string
+ slug: string
+ name: string
+ source: string
+ installs: number
+ sourceType: 'github' | 'well-known'
+ installUrl: string | null
+ url: string
+ isDuplicate?: boolean
+ // hot view
+ installsYesterday?: number
+ change?: number
+}
+
+export interface SkillDetailFile {
+ path: string
+ contents: string
+}
+
+export interface V1SkillDetail {
+ id: string
+ source: string
+ slug: string
+ installs: number
+ hash: string | null
+ files: SkillDetailFile[] | null
+}
+
+export interface AuditEntry {
+ provider: string
+ slug: string
+ status: 'pass' | 'warn' | 'fail'
+ summary: string
+ auditedAt: string
+ riskLevel: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
+ categories?: string[]
+}
+
+export interface SkillAuditResult {
+ id: string
+ source: string
+ slug: string
+ audits: AuditEntry[]
+}
+
+export interface CuratedOwner {
+ owner: string
+ totalInstalls: number
+ featuredRepo: string
+ featuredSkill: string
+ skills: V1Skill[]
+}
+
+export interface CuratedResponse {
+ data: CuratedOwner[]
+ totalOwners: number
+ totalSkills: number
+ generatedAt: string
+}
+
+export interface PaginationInfo {
+ page: number
+ perPage: number
+ total: number
+ hasMore: boolean
+}
+
+export interface SkillsListResponse {
+ data: V1Skill[]
+ pagination: PaginationInfo
+}
+
+export interface SearchResponse {
+ data: V1Skill[]
+ query: string
+ searchType: 'fuzzy' | 'semantic'
+ count: number
+ durationMs: number
+}
+
+export interface ApiError {
+ error: string
+ message: string
+}
+
+export interface ApiKeyEntry {
+ id: string
+ key: string
+ enabled: boolean
+}
+
+export interface ModelConfig {
+ id: string
+ name: string
+ provider: string
+ baseUrl: string
+ apiPath: string
+ apiKeys: ApiKeyEntry[]
+ model: string
+ isDefault: boolean
+ isBuiltin: boolean
+ enabled: boolean
+ translationModelId?: string
+ icon?: string
+ models?: Array<{ id: string; name: string; enabled: boolean; owned_by?: string; capabilities?: string[] }>
+}
+
+export interface TranslationResult {
+ sourceLang: string
+ targetLang: string
+ sourceContent: string
+ translatedContent: string
+ sidecarPath?: string
+}
+
+export interface RegisteredProject {
+ id: string
+ name: string
+ rootDir: string
+ scanPaths: string[]
+ skills: SkillScanResult[]
+ createdAt: string
+}
+
+export interface SkillScanResult {
+ name: string
+ dir: string
+ skillFile: string
+ content: string
+ isSymlink?: boolean
+ manifest: {
+ name: string
+ description: string
+ author: string
+ tags: string[]
+ format: string
+ language: string
+ }
+}
diff --git a/plugins/skill-hub/src/utils/ai.ts b/plugins/skill-hub/src/utils/ai.ts
new file mode 100644
index 00000000..80fbebbf
--- /dev/null
+++ b/plugins/skill-hub/src/utils/ai.ts
@@ -0,0 +1,160 @@
+import type { ModelConfig } from '../types'
+
+export interface ChatMessage {
+ role: 'system' | 'user' | 'assistant'
+ content: string
+}
+
+export interface ChatOptions {
+ temperature?: number
+ maxTokens?: number
+}
+
+export interface ChatResult {
+ content: string
+}
+
+export interface ModelInfo {
+ id: string
+ name: string
+ owned_by?: string
+}
+
+export interface FetchModelsResult {
+ success: boolean
+ models: ModelInfo[]
+ error?: string
+}
+
+export async function chatCompletion(
+ model: ModelConfig,
+ messages: ChatMessage[],
+ options?: ChatOptions,
+): Promise {
+ const activeKey = model.apiKeys?.find(k => k.enabled)?.key
+ if (!activeKey || !model.baseUrl || !model.apiPath || !model.model) {
+ throw new Error('AI_NOT_CONFIGURED')
+ }
+
+ const endpoint = `${model.baseUrl.replace(/\/+$/, '')}${model.apiPath}`
+
+ const body = {
+ model: model.model,
+ messages,
+ temperature: options?.temperature ?? 0.3,
+ max_tokens: options?.maxTokens ?? 8192,
+ stream: false,
+ }
+
+ const res = await fetch(endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${activeKey}`,
+ },
+ body: JSON.stringify(body),
+ })
+
+ if (!res.ok) {
+ const text = await res.text().catch(() => '')
+ if (res.status === 401 || res.status === 403) {
+ throw new Error('AI_AUTH_ERROR')
+ }
+ throw new Error(`API error ${res.status}: ${text.substring(0, 200)}`)
+ }
+
+ let data: any
+ try {
+ data = await res.json()
+ } catch {
+ throw new Error('AI_INVALID_RESPONSE')
+ }
+ const content = data?.choices?.[0]?.message?.content
+ if (!content) {
+ throw new Error('AI_EMPTY_RESPONSE')
+ }
+
+ return { content }
+}
+
+export async function fetchAvailableModels(
+ baseUrl: string,
+ apiKey: string,
+): Promise {
+ if (!apiKey || !baseUrl) {
+ return { success: false, models: [], error: '请先填写 API Key 和 API 地址' }
+ }
+
+ try {
+ const endpoint = `${baseUrl.replace(/\/+$/, '')}/v1/models`
+ const res = await fetch(endpoint, {
+ method: 'GET',
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ Accept: 'application/json',
+ },
+ })
+
+ if (!res.ok) {
+ const text = await res.text().catch(() => '')
+ const reason =
+ res.status === 401 || res.status === 403
+ ? 'auth'
+ : res.status === 404 || res.status === 405
+ ? 'unsupported'
+ : 'http'
+ return {
+ success: false,
+ models: [],
+ error: `获取模型列表失败: ${res.status} - ${text.substring(0, 100)}`,
+ reason,
+ } as any
+ }
+
+ let data: any
+ try {
+ data = await res.json()
+ } catch {
+ return {
+ success: false,
+ models: [],
+ error: `获取模型列表失败: 无效的 JSON 响应`,
+ reason: 'http',
+ } as any
+ }
+
+ if (data?.data && Array.isArray(data.data)) {
+ const models = data.data
+ .filter((m: any) => m.id)
+ .map((m: any) => ({
+ id: m.id,
+ name: m.id,
+ owned_by: m.owned_by,
+ }))
+ .sort((a: ModelInfo, b: ModelInfo) => a.id.localeCompare(b.id))
+ return { success: true, models }
+ }
+
+ if (data?.models && Array.isArray(data.models)) {
+ const models = data.models
+ .filter((m: any) => m.name)
+ .map((m: any) => {
+ const id = m.name.replace(/^models\//, '')
+ return { id, name: m.displayName ? `${m.displayName} (${id})` : id, owned_by: 'Google' }
+ })
+ .sort((a: ModelInfo, b: ModelInfo) => a.id.localeCompare(b.id))
+ return { success: true, models }
+ }
+
+ if (Array.isArray(data)) {
+ const models = data
+ .filter((m: any) => m.id || m.model)
+ .map((m: any) => ({ id: m.id || m.model || '', name: m.name || m.id || m.model }))
+ return { success: true, models }
+ }
+
+ return { success: false, models: [], error: '无法解析模型列表响应' }
+ } catch (err: any) {
+ return { success: false, models: [], error: err.message || '获取模型列表失败' }
+ }
+}
diff --git a/plugins/skill-hub/src/utils/color.ts b/plugins/skill-hub/src/utils/color.ts
new file mode 100644
index 00000000..aae0a235
--- /dev/null
+++ b/plugins/skill-hub/src/utils/color.ts
@@ -0,0 +1,7 @@
+const avatarColors = ['#7c3aed', '#f59e0b', '#e11d48', '#059669', '#0891b2', '#f97316', '#8b5cf6', '#db2777']
+
+export function getAvatarColor(name: string): string {
+ let hash = 0
+ for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash)
+ return avatarColors[Math.abs(hash) % avatarColors.length]
+}
diff --git a/plugins/skill-hub/src/utils/frontmatter.ts b/plugins/skill-hub/src/utils/frontmatter.ts
new file mode 100644
index 00000000..c859e8a6
--- /dev/null
+++ b/plugins/skill-hub/src/utils/frontmatter.ts
@@ -0,0 +1,86 @@
+export function parseFrontmatter(text: string): Record {
+ const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
+ const match = normalized.match(/^---\n([\s\S]*?)\n---/)
+ const fm: Record = {}
+ if (match) {
+ const lines = match[1].split('\n')
+ let i = 0
+ while (i < lines.length) {
+ const line = lines[i]
+ const sep = line.indexOf(':')
+ if (sep <= 0) { i++; continue }
+ const key = line.slice(0, sep).trim()
+ let val = line.slice(sep + 1).trim()
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
+ fm[key] = val.slice(1, -1)
+ i++
+ continue
+ }
+ const blockMatch = val.match(/^([>|])([+-]?)$/)
+ if (blockMatch) {
+ const style = blockMatch[1]
+ const chomp = blockMatch[2]
+ const blockLines: string[] = []
+ i++
+ while (i < lines.length) {
+ const next = lines[i]
+ if (next === '' || next.startsWith(' ') || next.startsWith('\t')) {
+ blockLines.push(next.trimEnd())
+ i++
+ } else {
+ break
+ }
+ }
+ i--
+ if (style === '>') {
+ const paragraphs: string[] = []
+ let current: string[] = []
+ for (const bl of blockLines) {
+ if (bl === '') {
+ if (current.length) { paragraphs.push(current.join(' ')); current = [] }
+ } else {
+ current.push(bl)
+ }
+ }
+ if (current.length) paragraphs.push(current.join(' '))
+ val = paragraphs.join('\n\n')
+ if (chomp === '+' && blockLines.length > 0) {
+ const trailing = blockLines.reduce((n, l) => l === '' ? n + 1 : 0, 0)
+ for (let t = 0; t < trailing; t++) val += '\n'
+ }
+ } else {
+ val = blockLines.join('\n').trimEnd()
+ }
+ } else if (val === '' || val === '""' || val === "''") {
+ const nextIdx = i + 1
+ if (nextIdx < lines.length && (lines[nextIdx].startsWith(' ') || lines[nextIdx].startsWith('\t'))) {
+ const blockLines: string[] = []
+ i++
+ while (i < lines.length) {
+ const curr = lines[i]
+ if (curr.startsWith(' ') || curr.startsWith('\t')) {
+ blockLines.push(curr.trimEnd())
+ i++
+ } else {
+ break
+ }
+ }
+ i--
+ val = blockLines.join(' ').replace(/\s+/g, ' ').trim()
+ }
+ }
+ if (val.startsWith('[') && val.endsWith(']')) {
+ val = val.slice(1, -1).trim()
+ }
+ fm[key] = val
+ i++
+ }
+ }
+ if (!fm.description) {
+ const bodyStart = match ? match[0].length : 0
+ const body = normalized.slice(bodyStart).trim()
+ const firstLine = body.split('\n').find((l) => l.trim() && !l.startsWith('#'))
+ if (firstLine) fm.description = firstLine.trim().slice(0, 200)
+ }
+ return fm
+}
diff --git a/plugins/skill-hub/src/utils/github.ts b/plugins/skill-hub/src/utils/github.ts
new file mode 100644
index 00000000..6909b731
--- /dev/null
+++ b/plugins/skill-hub/src/utils/github.ts
@@ -0,0 +1,113 @@
+export interface GitHubRepoInfo {
+ owner: string
+ repo: string
+ defaultBranch: string
+}
+
+export function parseGitHubUrl(url: string): GitHubRepoInfo | null {
+ const trimmed = url.trim()
+ const fullMatch = trimmed.match(
+ /^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/]+)\/([^/]+?)(?:\/|$)/
+ )
+ if (fullMatch) {
+ return {
+ owner: fullMatch[1],
+ repo: fullMatch[2].replace(/\.git$/, ''),
+ defaultBranch: 'main',
+ }
+ }
+ const shortMatch = trimmed.match(/^([^/]+)\/([^/]+?)$/)
+ if (shortMatch) {
+ return {
+ owner: shortMatch[1],
+ repo: shortMatch[2].replace(/\.git$/, ''),
+ defaultBranch: 'main',
+ }
+ }
+ return null
+}
+
+
+export async function fetchGitHubFile(
+ owner: string,
+ repo: string,
+ path: string,
+ branch = 'main'
+): Promise {
+ const branches = [branch, 'main', 'master']
+ const tried = new Set()
+ for (const b of branches) {
+ if (tried.has(b)) continue
+ tried.add(b)
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${b}/${path.split('/').map(encodeURIComponent).join('/')}`
+ try {
+ const resp = await fetch(url, { headers: { 'User-Agent': 'skill-hub' } })
+ if (resp.ok) return resp.text()
+ } catch {}
+ }
+ throw new Error(`获取文件失败: ${path}`)
+}
+
+export async function fetchGitHubRepoTree(
+ owner: string,
+ repo: string,
+ branch = 'main',
+ token?: string
+): Promise<{ path: string; type: 'blob' | 'tree' }[]> {
+ const branches = [branch, 'main', 'master']
+ const tried = new Set()
+ for (const b of branches) {
+ if (tried.has(b)) continue
+ tried.add(b)
+ const url = `https://api.github.com/repos/${owner}/${repo}/git/trees/${b}?recursive=1`
+ const headers: Record = {
+ Accept: 'application/vnd.github.v3+json',
+ 'User-Agent': 'skill-hub',
+ }
+ if (token) headers.Authorization = `Bearer ${token}`
+
+ const resp = await fetch(url, { headers })
+ if (resp.ok) {
+ const data = await resp.json()
+ return data.tree || []
+ }
+ if (resp.status === 403) throw new Error('GitHub API 速率限制,请在设置中添加 Token 提升额度')
+ if (resp.status === 404) continue
+ throw new Error(`获取仓库树失败: ${resp.status}`)
+ }
+ throw new Error(`仓库或分支未找到: ${owner}/${repo}`)
+}
+
+export const SKILL_MANIFEST_FILES = ['SKILL.md', 'skill.json', 'skill.yaml', 'skill.yml']
+
+export function detectSkillDirectories(
+ tree: { path: string; type: 'blob' | 'tree' }[]
+): { dir: string; manifestFile: string }[] {
+ const manifestFiles = tree.filter(
+ (item) =>
+ item.type === 'blob' && SKILL_MANIFEST_FILES.includes(item.path.split('/').pop() || '')
+ )
+
+ const dirSet = new Set()
+ for (const mf of manifestFiles) {
+ const dir = mf.path.split('/').slice(0, -1).join('/') || '.'
+ dirSet.add(dir)
+ }
+
+ const rootManifests = manifestFiles.filter((mf) => {
+ const parts = mf.path.split('/')
+ return parts.length === 1
+ })
+
+ return Array.from(dirSet).map((dir) => {
+ const dirManifest = manifestFiles.find((mf) => {
+ if (dir === '.') return mf.path.split('/').length === 1
+ const parts = mf.path.split('/')
+ return parts.length === dir.split('/').length + 1 && mf.path.startsWith(dir + '/')
+ })
+ return {
+ dir,
+ manifestFile: dirManifest ? dirManifest.path : (rootManifests[0]?.path || ''),
+ }
+ })
+}
diff --git a/plugins/skill-hub/src/utils/path.ts b/plugins/skill-hub/src/utils/path.ts
new file mode 100644
index 00000000..d70d5ca7
--- /dev/null
+++ b/plugins/skill-hub/src/utils/path.ts
@@ -0,0 +1,7 @@
+export function normalizePath(p: string): string {
+ return (p || '')
+ .replace(/\\/g, '/')
+ .replace(/\/+/g, '/')
+ .replace(/\/+$/, '')
+ .replace(/^\.\//, '')
+}
diff --git a/plugins/skill-hub/src/utils/skill-registry.ts b/plugins/skill-hub/src/utils/skill-registry.ts
new file mode 100644
index 00000000..05cd851a
--- /dev/null
+++ b/plugins/skill-hub/src/utils/skill-registry.ts
@@ -0,0 +1,176 @@
+import type { SkillIdentity, SkillSourceLocation, SkillScanResult } from '../types'
+
+const STORAGE_KEY = 'sh_skill_registry'
+
+
+export function loadRegistry(): Map {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY)
+ if (!raw) return new Map()
+ const entries: SkillIdentity[] = JSON.parse(raw)
+ return new Map(entries.map(e => [e.canonicalId, e]))
+ } catch {
+ return new Map()
+ }
+}
+
+export function saveRegistry(registry: Map): void {
+ const entries = Array.from(registry.values())
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(entries))
+}
+
+export function getOrCreateIdentity(
+ registry: Map,
+ canonicalId: string,
+ scanResult: SkillScanResult,
+ source: SkillSourceLocation
+): SkillIdentity {
+ const existing = registry.get(canonicalId)
+ if (existing) {
+ const alreadyHasSource = existing.sources.some(
+ s => s.type === source.type && s.location === source.location
+ )
+ if (!alreadyHasSource) {
+ existing.sources.push(source)
+ }
+ existing.updatedAt = new Date().toISOString()
+ return existing
+ }
+
+ const identity: SkillIdentity = {
+ canonicalId,
+ name: scanResult.manifest.name || scanResult.name,
+ description: scanResult.manifest.description,
+ author: scanResult.manifest.author,
+ tags: scanResult.manifest.tags || [],
+ format: scanResult.manifest.format as any,
+ contentHash: '',
+ sources: [source],
+ primarySource: source,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ }
+
+ registry.set(canonicalId, identity)
+ return identity
+}
+
+export function findIdentityByScanResult(
+ registry: Map,
+ scanResult: SkillScanResult
+): SkillIdentity | undefined {
+ for (const identity of registry.values()) {
+ if (
+ identity.name.toLowerCase() === (scanResult.manifest.name || scanResult.name).toLowerCase()
+ ) {
+ return identity
+ }
+ }
+ return undefined
+}
+
+export function registerSkillFromStore(
+ registry: Map,
+ canonicalId: string,
+ scanResult: SkillScanResult,
+ sourceType: SkillSourceLocation['type'],
+ location: string
+): SkillIdentity {
+ const source: SkillSourceLocation = {
+ type: sourceType,
+ location,
+ installedAt: new Date().toISOString(),
+ }
+ const existing = registry.get(canonicalId)
+ if (existing) {
+ existing.sources = [source]
+ existing.primarySource = source
+ existing.updatedAt = new Date().toISOString()
+ saveRegistry(registry)
+ return existing
+ }
+ const identity: SkillIdentity = {
+ canonicalId,
+ name: scanResult.manifest.name || scanResult.name,
+ description: scanResult.manifest.description,
+ author: scanResult.manifest.author,
+ tags: scanResult.manifest.tags || [],
+ format: scanResult.manifest.format as any,
+ contentHash: '',
+ sources: [source],
+ primarySource: source,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ }
+ registry.set(canonicalId, identity)
+ saveRegistry(registry)
+ return identity
+}
+
+
+export function registerSkillFromProject(
+ registry: Map,
+ scanResult: SkillScanResult,
+ projectId: string
+): SkillIdentity {
+ const existing = findIdentityByScanResult(registry, scanResult)
+ if (existing) {
+ const source: SkillSourceLocation = {
+ type: 'local-dir',
+ location: scanResult.dir,
+ projectId,
+ installedAt: new Date().toISOString(),
+ }
+ const alreadyHasSource = existing.sources.some(
+ s => s.type === 'local-dir' && s.projectId === projectId && s.location === scanResult.dir
+ )
+ if (!alreadyHasSource) {
+ existing.sources.push(source)
+ }
+ existing.updatedAt = new Date().toISOString()
+ saveRegistry(registry)
+ return existing
+ }
+
+ const canonicalId = `project:${projectId}/${scanResult.manifest?.name || scanResult.name}`
+ const source: SkillSourceLocation = {
+ type: 'local-dir',
+ location: scanResult.dir,
+ projectId,
+ installedAt: new Date().toISOString(),
+ }
+ const identity = getOrCreateIdentity(registry, canonicalId, scanResult, source)
+ saveRegistry(registry)
+ return identity
+}
+
+export function removeFromRegistry(registry: Map, skillName: string): void {
+ const lowerName = skillName.toLowerCase()
+ for (const [key, identity] of registry) {
+ if (identity.name.toLowerCase() === lowerName) {
+ registry.delete(key)
+ }
+ }
+ saveRegistry(registry)
+}
+
+export function getSourceLabel(source: SkillSourceLocation): string {
+ switch (source.type) {
+ case 'github':
+ return 'GitHub'
+ case 'skills-sh':
+ return 'skills.sh'
+ case 'local':
+ return source.platformId ? `Agent (${source.platformId})` : 'Agent'
+ case 'local-dir':
+ return source.projectId ? `Project` : 'Local'
+ case 'marketplace-json':
+ return 'Marketplace'
+ case 'git-repo':
+ return 'Git Repo'
+ case 'builtin':
+ return 'Built-in'
+ default:
+ return source.type
+ }
+}
diff --git a/plugins/skill-hub/src/utils/skills-sh.ts b/plugins/skill-hub/src/utils/skills-sh.ts
new file mode 100644
index 00000000..30724fa2
--- /dev/null
+++ b/plugins/skill-hub/src/utils/skills-sh.ts
@@ -0,0 +1,346 @@
+import type { Skill } from '../types'
+import { parseFrontmatter } from './frontmatter'
+
+const BASE = 'https://skills.sh'
+
+// ── Filter definitions (like PromptHub) ──
+
+export interface LeaderboardFilter {
+ key: string
+ label: string
+ path: string
+}
+
+export const LEADERBOARD_FILTERS: LeaderboardFilter[] = [
+ { key: 'all', label: '全部', path: '/' },
+ { key: 'trending', label: '趋势', path: '/trending' },
+ { key: 'hot', label: '热门', path: '/hot' },
+]
+
+export function getFilterByKey(key: string): LeaderboardFilter {
+ return LEADERBOARD_FILTERS.find((f) => f.key === key) || LEADERBOARD_FILTERS[0]
+}
+
+// ── Data types ──
+
+export interface LeaderboardEntry {
+ owner: string
+ repo: string
+ skillName: string
+ detailPath: string
+ detailUrl: string
+ installs: number
+}
+
+export interface SkillDetailRaw {
+ name: string
+ description: string
+ content: string
+}
+
+export interface SkillDetailMeta extends SkillDetailRaw {
+ githubStars?: string
+ weeklyInstalls?: string
+}
+
+// ── Error ──
+
+class SkillsShError extends Error {
+ constructor(public status: number, message: string) {
+ super(message)
+ this.name = 'SkillsShError'
+ }
+}
+
+// ── HTTP helper ──
+
+async function fetchText(url: string): Promise {
+ const resp = await fetch(url, { headers: { 'User-Agent': 'skill-hub' } })
+ if (!resp.ok) throw new SkillsShError(resp.status, `HTTP ${resp.status}`)
+ return resp.text()
+}
+
+// ── HTML normalization ──
+
+function normalizeHtml(html: string): string {
+ return html.replace(/\\"/g, '"').replace(/\\n/g, ' ').replace(/\s+/g, ' ').trim()
+}
+
+function decodeHtmlEntities(input: string): string {
+ return input
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .replace(/ /g, ' ')
+}
+
+function normalizeWhitespace(input: string): string {
+ return input.replace(/\r/g, '').replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim()
+}
+
+function stripTags(input: string): string {
+ return normalizeWhitespace(
+ decodeHtmlEntities(
+ input
+ .replace(/
/gi, '\n')
+ .replace(/<\/(p|div|section|article|li|ul|ol|h1|h2|h3|h4|h5|h6|pre|code)>/gi, '\n')
+ .replace(/<[^>]+>/g, ' '),
+ ).replace(/[ \t]{2,}/g, ' '),
+ )
+}
+
+function htmlToText(html: string): string {
+ return normalizeWhitespace(
+ decodeHtmlEntities(
+ html
+ .replace(/
+
+
+ 未选择技能
+
+
+
+
+
+
+ {{ getDirLabel(duplicatesForDropdown[selectedDuplicateIndex], selectedDuplicateIndex) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getDirLabel(d, i) }}
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/views/AgentSkills/index.vue b/plugins/skill-hub/src/views/AgentSkills/index.vue
new file mode 100644
index 00000000..c6ce65a4
--- /dev/null
+++ b/plugins/skill-hub/src/views/AgentSkills/index.vue
@@ -0,0 +1,1131 @@
+
+
+
+
+
+
+
+
p.id === $event)!)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部
+ {{ selectedSkills.length }}
+
+
+
+ 本地
+ {{ localCount }}
+
+
+
+ 已管理
+ {{ managedCount }}
+
+
+
+
+
+
+ 批量模式
+ 已选 {{ selectedIds.size }} 项
+
+
+
+
+ 全选
+
+
+
+ 删除
+
+
+
+
+
+
+
+
选择 Agent 查看技能
+
+
+
+
+
+
选择要导入到 {{ selectedPlatform?.name }} 的技能
+
+
+
+
+ {{ selectedImportIds.size === filteredMySkills.filter(s => !isAlreadyInAgent(s)).length ? '取消全选' : '全选' }}
+
+
+
+
+
+
+
+
+
{{ skill.name.charAt(0).toUpperCase() }}
+
+
+
+
{{ skill.name }}
+
{{ skill.description || '暂无描述' }}
+
已在 Agent 中
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ uninstallScopeSkillName }} 同时存在于以下位置,请选择要卸载的范围:
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/views/MySkills/index.vue b/plugins/skill-hub/src/views/MySkills/index.vue
new file mode 100644
index 00000000..d48930f5
--- /dev/null
+++ b/plugins/skill-hub/src/views/MySkills/index.vue
@@ -0,0 +1,1306 @@
+
+
+
+
+
+
+
+
+
+
+ 全部 Skill
+ {{ totalDownloaded }}
+
+
+
+ 收藏
+ {{ totalFavorites }}
+
+
+
+ 已分发
+ {{ totalDistributed }}
+
+
+
+ 待分发
+ {{ totalPending }}
+
+
+
+
+ {{ filterSource || '全部来源' }}
+ {{ sourceFilterCount }}
+
+
+
+
+
+
+
+
+
+ 全部来源
+ {{ totalSources }}
+
+
+ {{ src }}
+ {{ cnt }}
+
+
+
+
+
+
+
+ 全部
+ {{ filteredBaseCount }}
+
+
+ {{ cat.icon }} {{ cat.label }}
+ {{ cat.count }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/views/ProjectSkills/index.vue b/plugins/skill-hub/src/views/ProjectSkills/index.vue
new file mode 100644
index 00000000..6c9010e4
--- /dev/null
+++ b/plugins/skill-hub/src/views/ProjectSkills/index.vue
@@ -0,0 +1,1651 @@
+
+
+
+
+
+
+
+
p.id === $event)!)"
+ @add="openAddProjectModal()"
+ >
+
+ {{ (registeredProjects.find(p => p.id === item.id)?.name || item.label).charAt(0).toUpperCase() }}
+
+
+
+
+
+
+
+
+ 全部
+ {{ allProjectSkills.length }}
+
+
+
+ 本地
+ {{ localCount }}
+
+
+
+ 已管理
+ {{ managedCount }}
+
+
+
+ 源文件
+ {{ sourceCount }}
+
+
+
+ {{ agentLabel || '全部位置' }}
+ {{ agentFilterCount }}
+
+
+
+
+
+
+
+
+
+ 全部位置
+ {{ totalAgents }}
+
+
+ 通用
+ {{ agentCounts.find(([id]) => id === '_generic')?.[1] || 0 }}
+
+
+ {{ defaultPlatforms.find(p => p.id === pid)?.name || pid }}
+ {{ cnt }}
+
+
+
+
+
+
+ 批量模式
+ 已选 {{ selectedIds.size }} 项
+
+
+
+
+ 全选
+
+
+
+ 导入到我的 Skill
+
+
+
+ 移除
+
+
+
+
+
+
+
+
+
+
+
还没有项目
+
添加项目根目录后,即可扫描并管理项目内的本地 Skill。
+
+
+ 添加项目
+
+
+
+
+
+
+
+
+
选择要导入到项目 {{ selectedProject?.name }} 的技能
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ selectedImportIds.size === filteredMySkills.filter(s => !isAlreadyInProject(s)).length ? '取消全选' : '全选' }}
+
+
+
+
+
+
+
+
+
{{ skill.name.charAt(0).toUpperCase() }}
+
+
+
+
{{ skill.name }}
+
{{ skill.description || '暂无描述' }}
+
已在项目中
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/views/Settings/index.vue b/plugins/skill-hub/src/views/Settings/index.vue
new file mode 100644
index 00000000..d534b20c
--- /dev/null
+++ b/plugins/skill-hub/src/views/Settings/index.vue
@@ -0,0 +1,4824 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ fetchError }}
+
暂无可用模型
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/views/SkillStore/Detail.vue b/plugins/skill-hub/src/views/SkillStore/Detail.vue
new file mode 100644
index 00000000..9d430002
--- /dev/null
+++ b/plugins/skill-hub/src/views/SkillStore/Detail.vue
@@ -0,0 +1,389 @@
+
+
+
+ 未选择技能
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/views/SkillStore/index.vue b/plugins/skill-hub/src/views/SkillStore/index.vue
new file mode 100644
index 00000000..034acb75
--- /dev/null
+++ b/plugins/skill-hub/src/views/SkillStore/index.vue
@@ -0,0 +1,1495 @@
+
+
+
+
+
+
+
+
{ activePresetId = id; searchQuery = ''; filterTab = 'all'; leaderboardFilter = 'all'; sourceSkills = []; error = ''; storage.savePageState('skill-store', { presetId: id }); fetchCurrentSkills() }"
+ @add="openAddStoreModal"
+ @delete="onDeleteStore"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ f.label }}
+
+
+
+
+
+ 全部
+ {{ sourceSkills.length }}
+
+
+ {{ CATEGORY_ICONS[cat] }} {{ SKILL_CATEGORIES[cat].label }}
+ {{ sourceSkills.filter(s => s.category === cat).length }}
+
+
+
+
+
+
+
+
+
+
+
+
+
确认删除 "{{ skillToDelete?.name }}"?
此操作将从本地移除该技能。
+
+
+
+
+
+
diff --git a/plugins/skill-hub/src/views/Sources/index.vue b/plugins/skill-hub/src/views/Sources/index.vue
new file mode 100644
index 00000000..8c3fecf1
--- /dev/null
+++ b/plugins/skill-hub/src/views/Sources/index.vue
@@ -0,0 +1,654 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ s.name }}
+
+ {{ getSourceLabel(s.type) }}
+ {{ s.url }}
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
暂无自定义商店
+
使用上方表单添加商店。
+
+
+
+
+
+
+
diff --git a/plugins/skill-hub/tsconfig.json b/plugins/skill-hub/tsconfig.json
new file mode 100644
index 00000000..39b73bc6
--- /dev/null
+++ b/plugins/skill-hub/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "preserve",
+ "strict": false,
+ "noImplicitAny": false,
+ "types": ["@ztools-center/ztools-api-types"]
+ },
+ "include": ["src"]
+}
diff --git a/plugins/skill-hub/vite.config.js b/plugins/skill-hub/vite.config.js
new file mode 100644
index 00000000..7bfab103
--- /dev/null
+++ b/plugins/skill-hub/vite.config.js
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [vue()],
+ base: './'
+})