mirror of
https://github.com/sweetwisdom/everything-claude-code-zh.git
synced 2026-03-22 06:20:10 +00:00
chore: sync with upstream e7cb442 + update zh translations
This commit is contained in:
90
.claude/skills/oneskill/.gitignore
vendored
Normal file
90
.claude/skills/oneskill/.gitignore
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# parcel-bundler cache
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line if your project uses Gatsby and you want to debug the
|
||||
# build, as it contains some public assets. These assets are usually
|
||||
# generated from other files and don't need to be in version control.
|
||||
# public
|
||||
|
||||
# vue-cli build runner target
|
||||
target/
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Tern device bin file
|
||||
.tern-port
|
||||
|
||||
# Stores VS Code state
|
||||
.vscode
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# OS metadata
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
7
.claude/skills/oneskill/.openskills.json
Normal file
7
.claude/skills/oneskill/.openskills.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"source": "xu-xiang/oneskill",
|
||||
"sourceType": "git",
|
||||
"repoUrl": "https://github.com/xu-xiang/oneskill",
|
||||
"subpath": "",
|
||||
"installedAt": "2026-01-31T16:23:32.899Z"
|
||||
}
|
||||
176
.claude/skills/oneskill/LICENSE
Normal file
176
.claude/skills/oneskill/LICENSE
Normal file
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of Your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
88
.claude/skills/oneskill/README.md
Normal file
88
.claude/skills/oneskill/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
<div align="center">
|
||||
|
||||
# OneSkill 元管理器(Meta-Manager)
|
||||
|
||||
**AI 智能体技能(Agent Skills)的通用桥梁。**
|
||||
从 OpenSkills 注册表中发现、安装并映射功能到您的环境。
|
||||
|
||||
[](https://www.npmjs.com/package/oneskill)
|
||||
[](LICENSE)
|
||||
|
||||
[**🇺🇸 English**](README.md) | [**🇨🇳 中文指南**](README_CN.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## ⚡️ 什么是 OneSkill?
|
||||
|
||||
**OneSkill** 是一款专为 AI 智能体(Agent)(以及人类)设计的元工具,用于轻松扩展其功能。它是 [OpenSkills](https://github.com/Starttoaster/openskills) 生态系统的搜索引擎和工作流管理器(Workflow Manager)。
|
||||
|
||||
虽然 `openskills` 处理文件的原始安装,但 **OneSkill** 提供:
|
||||
1. **智能搜索(Intelligent Search)**:使用自然语言或关键词找到适合该任务的工具。
|
||||
2. **工作流指南(Workflow Guidance)**:为智能体(Agent)安全获取新技能提供标准化流程。
|
||||
3. **环境映射(Environment Mapping)**:至关重要的一点是,它弥合了 `openskills`(标准结构)与 **Gemini CLI**(自定义结构)等使用者之间的鸿沟。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
您无需永久安装。只需使用 `npx` 运行即可。
|
||||
|
||||
```bash
|
||||
# 搜索技能(例如,用于浏览网页)
|
||||
npx oneskill search "puppeteer browser"
|
||||
|
||||
# 搜索按流行度排序的数据库工具
|
||||
npx oneskill search "database" --sort stars
|
||||
```
|
||||
|
||||
## 🛠 工作流
|
||||
|
||||
为您的智能体(Agent)添加新功能的标准生命周期:
|
||||
|
||||
1. **搜索(Search)**:查找技能。
|
||||
```bash
|
||||
npx oneskill search "github integration"
|
||||
```
|
||||
2. **安装(Install)**:使用标准的 `openskills` 安装程序。
|
||||
```bash
|
||||
npx openskills install anthropics/skills
|
||||
```
|
||||
3. **映射(Map)(对 Gemini 至关重要)**:如果您正在使用 **Gemini CLI**,则必须将安装的技能映射到您的配置中。
|
||||
```bash
|
||||
# 将安装的技能映射到 Gemini 的配置
|
||||
npx oneskill map --target gemini
|
||||
```
|
||||
|
||||
## 📖 命令参考
|
||||
|
||||
### `search`
|
||||
在全局注册表中搜索技能。
|
||||
```bash
|
||||
npx oneskill search <query> [options]
|
||||
|
||||
# 选项:
|
||||
# --category <name> 按类别过滤
|
||||
# --sort <field> 按 'stars'、'created' 或 'updated' 排序
|
||||
# --limit <number> 限制结果数量(默认值:10)
|
||||
```
|
||||
|
||||
### `map`
|
||||
为特定的智能体(Agent)环境生成配置。
|
||||
```bash
|
||||
npx oneskill map --target <env>
|
||||
|
||||
# 目标:
|
||||
# gemini 生成/更新 Gemini CLI 配置
|
||||
```
|
||||
|
||||
### `list`
|
||||
列出本地映射的技能(`openskills list` 的封装)。
|
||||
```bash
|
||||
npx oneskill list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<sub>由 OneSkill 社区用 ❤️ 构建</sub>
|
||||
</div>
|
||||
92
.claude/skills/oneskill/README_CN.md
Normal file
92
.claude/skills/oneskill/README_CN.md
Normal file
@@ -0,0 +1,92 @@
|
||||
<div align="center">
|
||||
|
||||
# OneSkill 元管理器 (Meta-Manager)
|
||||
|
||||
**AI 智能体 (Agent) 技能的通用桥梁**
|
||||
帮助你发现、安装并将 OpenSkills 注册表中的能力映射到你的运行环境。
|
||||
|
||||
[](https://www.npmjs.com/package/oneskill)
|
||||
[](LICENSE)
|
||||
|
||||
[**🇺🇸 English**](README.md) | [**🇨🇳 中文指南**](README_CN.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## ⚡️ 什么是 OneSkill?
|
||||
|
||||
**OneSkill** 是一个为 AI 智能体 (Agent) 设计的通用技能管理工具。它作为 [OpenSkills](https://github.com/Starttoaster/openskills) 生态系统的搜索引擎和工作流管理器 (Workflow Manager),帮助你发现、安装并将能力映射到你的运行环境中。
|
||||
|
||||
虽然 `openskills` 负责文件的下载安装,但 **OneSkill** 提供了:
|
||||
1. **智能搜索**: 支持通过自然语言或关键词搜索注册表中的技能 (Skill)。
|
||||
2. **工作流引导**: 为智能体 (Agent) 提供了一套标准的扩展能力流程(搜索 -> 确认 -> 安装)。
|
||||
3. **环境映射 (Mapping)**: 解决了安装路径与运行环境不一致的问题。特别是对于 **Gemini CLI** 用户,OneSkill 能自动将下载的技能 (Skill) 映射到 Gemini 的配置文件中。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
无需全局安装,直接使用 `npx` 运行即可:
|
||||
|
||||
```bash
|
||||
# 搜索技能 (例如:想要网页浏览能力)
|
||||
npx oneskill search "puppeteer browser"
|
||||
|
||||
# 搜索数据库相关技能,并按星级排序
|
||||
npx oneskill search "database" --sort stars
|
||||
```
|
||||
|
||||
## 🛠 使用流程
|
||||
|
||||
为你的智能体 (Agent) 添加新能力的推荐步骤:
|
||||
|
||||
1. **搜索 (Search)**: 查找你需要的技能 (Skill)。
|
||||
```bash
|
||||
npx oneskill search "github integration"
|
||||
```
|
||||
|
||||
2. **安装 (Install)**: 使用 `openskills` 标准命令进行下载。
|
||||
```bash
|
||||
npx openskills install anthropics/skills
|
||||
```
|
||||
|
||||
3. **映射 (Map)**: **(Gemini 用户必读)**
|
||||
`openskills` 默认将文件下载到通用目录,Gemini CLI 无法直接读取。必须执行映射 (Mapping) 命令:
|
||||
```bash
|
||||
# 自动识别已安装的技能 (Skill) 并配置到 Gemini
|
||||
npx oneskill map --target gemini
|
||||
```
|
||||
*如果你的技能 (Skill) 是全局安装的 (加了 --global),这里也需要加 --global。*
|
||||
|
||||
## 📖 命令参考
|
||||
|
||||
### `search` (搜索)
|
||||
在全局注册表中搜索技能 (Skill)。
|
||||
```bash
|
||||
npx oneskill search <查询词> [选项]
|
||||
|
||||
# 选项:
|
||||
# --category <name> 按分类筛选
|
||||
# --sort <field> 排序方式: 'stars' (星级), 'created' (创建时间), 'updated' (更新时间)
|
||||
# --limit <number> 限制返回数量 (默认: 10)
|
||||
```
|
||||
|
||||
### `map` (映射)
|
||||
为特定环境生成配置。
|
||||
```bash
|
||||
npx oneskill map --target <环境>
|
||||
|
||||
# 支持的目标:
|
||||
# gemini 更新 Gemini CLI 的配置与路径映射
|
||||
```
|
||||
|
||||
### `list` (列表)
|
||||
查看本地已安装的技能 (Skill)。
|
||||
```bash
|
||||
npx oneskill list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<sub>Built with ❤️ by the OneSkill Community</sub>
|
||||
</div>
|
||||
59
.claude/skills/oneskill/SKILL.md
Normal file
59
.claude/skills/oneskill/SKILL.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: oneskill
|
||||
description: 发现技能 (Skill),迭代查询,并在任何环境中自动安装技能。
|
||||
---
|
||||
|
||||
# OneSkill 元管理器 (Meta-Manager)
|
||||
|
||||
使用此技能来发现新功能、优化搜索查询,并使用 OpenSkills 简化技能设置。这为扩展环境功能提供了一种统一的方式。
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 当用户要求的某些功能你目前不具备时。
|
||||
- 当任务复杂、属于特定领域,或在尝试 2 次后仍被反复阻断时。
|
||||
- 当可能存在更好的技能(Skill)时(例如:网页浏览、GitHub 集成、数据库管理、云基础设施)。
|
||||
|
||||
## 工作流 (Workflow)
|
||||
|
||||
1. 搜索注册表:
|
||||
- 运行:`npx oneskill search "<query>" [options]`
|
||||
- 支持的选项:`--category`、`--limit`、`--offset`、`--sort`。
|
||||
- 示例:
|
||||
- `npx oneskill search "browser" --sort stars`
|
||||
- `npx oneskill search "" --category database --limit 5`
|
||||
2. 分析结果:
|
||||
- 确定最佳匹配项,或优化查询并再次搜索。
|
||||
3. 与用户确认:
|
||||
- 说明该技能的功能及其来源。
|
||||
4. 在获得明确批准后进行安装(使用 openskills):
|
||||
- 运行:`npx openskills install <slug-or-repo>`
|
||||
- 示例:`npx openskills install anthropics/skills`
|
||||
5. 处理特定环境的设置:
|
||||
- **Gemini CLI 用户:** `openskills` 不会自动配置 Gemini。安装后你**必须**运行映射命令:
|
||||
- `npx oneskill map --target gemini`(如果是全局安装,请添加 `--global`)
|
||||
6. 应用新技能以完成原始请求。
|
||||
|
||||
## OpenSkills 基础
|
||||
|
||||
- `npx openskills install <source> [options]` # 从 GitHub、本地路径或私有仓库安装
|
||||
- `npx openskills sync [-y] [-o <path>]` # 更新 AGENTS.md (或自定义输出)
|
||||
- `npx openskills list` # 显示已安装的技能
|
||||
- `npx openskills read <name>` # 加载技能(供智能体 (Agent) 使用)
|
||||
- `npx openskills update [name...]` # 更新已安装的技能(默认:全部)
|
||||
- `npx openskills manage` # 移除技能(交互式)
|
||||
- `npx openskills remove <name>` # 移除特定技能
|
||||
|
||||
示例:
|
||||
- `npx openskills install anthropics/skills`
|
||||
- `npx openskills sync`
|
||||
|
||||
默认设置:安装在项目本地(`./.claude/skills`,或者带 `--universal` 参数安装在 `./.agent/skills`)。使用 `--global` 安装在 `~/.claude/skills`。
|
||||
|
||||
## 安全提示 (Safety Reminders)
|
||||
|
||||
- 未经用户明确确认,请勿安装。
|
||||
- 除非用户同意覆盖现有目标,否则避免使用 `--force-map`。
|
||||
- 使用 openskills 进行安装/更新;OneSkill 仅为 Gemini 提供搜索和映射。
|
||||
- 对于 Gemini,请在安装后运行 `npx oneskill map --target gemini`。
|
||||
- 默认安装/映射是项目本地的,与 openskills 相同;全局安装请使用 `--global`。
|
||||
- 安装 OneSkill 本身时,建议使用 `--global`,以便在跨项目时可用。
|
||||
2026
.claude/skills/oneskill/package-lock.json
generated
Normal file
2026
.claude/skills/oneskill/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
.claude/skills/oneskill/package.json
Normal file
45
.claude/skills/oneskill/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "oneskill",
|
||||
"version": "0.1.0",
|
||||
"description": "Meta-skill manager for AI coding agents",
|
||||
"type": "module",
|
||||
"main": "./dist/cli.js",
|
||||
"bin": {
|
||||
"oneskill": "dist/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"SKILL.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"keywords": [
|
||||
"skills",
|
||||
"ai",
|
||||
"agents",
|
||||
"meta-skill",
|
||||
"codex",
|
||||
"gemini",
|
||||
"claude"
|
||||
],
|
||||
"author": "OneSkill Contributors",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=20.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"commander": "^12.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.9.1",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.3"
|
||||
}
|
||||
}
|
||||
82
.claude/skills/oneskill/src/cli.ts
Normal file
82
.claude/skills/oneskill/src/cli.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
import { Command } from 'commander';
|
||||
import { runSearch } from './commands/search.js';
|
||||
import { runInfo } from './commands/info.js';
|
||||
import { runList } from './commands/list.js';
|
||||
import { runDoctor } from './commands/doctor.js';
|
||||
import { runSync } from './commands/sync.js';
|
||||
import { runMap } from './commands/map.js';
|
||||
import { getOneskillVersion } from './core/versions.js';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('oneskill')
|
||||
.description('Meta-skill manager for AI coding agents')
|
||||
.version(getOneskillVersion());
|
||||
|
||||
program
|
||||
.command('search <query>')
|
||||
.description('Search the skill registry')
|
||||
.option('--registry <url>', 'Registry base URL override')
|
||||
.option('--category <slug>', 'Filter by category slug')
|
||||
.option('--limit <n>', 'Results per page (max 100)', (value) => Number.parseInt(value, 10))
|
||||
.option('--offset <n>', 'Pagination offset', (value) => Number.parseInt(value, 10))
|
||||
.option('--sort <value>', 'Sort by: votes, recent, stars')
|
||||
.action(async (query: string, options: { registry?: string; category?: string; limit?: number; offset?: number; sort?: string }) => {
|
||||
await runSearch(query, options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('info <slug>')
|
||||
.description('Fetch skill info from registry')
|
||||
.option('--registry <url>', 'Registry base URL override')
|
||||
.action(async (slug: string, options: { registry?: string }) => {
|
||||
await runInfo(slug, options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('sync')
|
||||
.description('Forward to openskills sync')
|
||||
.option('-y, --yes', 'Skip interactive selection, sync all skills')
|
||||
.option('-o, --output <path>', 'Output file path (default: AGENTS.md)')
|
||||
.action(async (options: { yes?: boolean; output?: string }) => {
|
||||
await runSync(options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('map')
|
||||
.description('Map installed skills into Gemini directory')
|
||||
.option('--target <target>', 'Target environment (gemini only)')
|
||||
.option('--global', 'Map from global openskills install', false)
|
||||
.option('--universal', 'Map from universal (.agent/skills)', false)
|
||||
.option('--force-map', 'Overwrite target mapping if it exists', false)
|
||||
.action(async (options: { target?: string; global?: boolean; universal?: boolean; forceMap?: boolean }) => {
|
||||
await runMap({
|
||||
target: options.target as 'gemini' | undefined,
|
||||
global: options.global,
|
||||
universal: options.universal,
|
||||
forceMap: options.forceMap,
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('List managed skills')
|
||||
.option('--root <path>', 'Override workspace root')
|
||||
.action(async (options: { root?: string }) => {
|
||||
await runList(options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('doctor')
|
||||
.description('Diagnose OneSkill environment')
|
||||
.option('--root <path>', 'Override workspace root')
|
||||
.action(async (options: { root?: string }) => {
|
||||
await runDoctor(options);
|
||||
});
|
||||
|
||||
program.parseAsync(process.argv).catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
19
.claude/skills/oneskill/src/commands/doctor.ts
Normal file
19
.claude/skills/oneskill/src/commands/doctor.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { detectRoot } from '../core/root.js';
|
||||
import { printJson } from '../utils/json.js';
|
||||
|
||||
export interface DoctorCommandOptions {
|
||||
root?: string;
|
||||
}
|
||||
|
||||
export async function runDoctor(options: DoctorCommandOptions): Promise<void> {
|
||||
const rootInfo = detectRoot(options.root || process.cwd());
|
||||
const root = rootInfo.root;
|
||||
const paths = {
|
||||
agent: `${root}/.agent/skills`,
|
||||
claude: `${root}/.claude/skills`,
|
||||
gemini: `${root}/.gemini/skills`,
|
||||
codex: `${root}/.codex/skills`,
|
||||
oneskillLogs: `${root}/.oneskill/logs`,
|
||||
};
|
||||
printJson({ schemaVersion: '1', root: rootInfo.root, reason: rootInfo.reason, paths });
|
||||
}
|
||||
11
.claude/skills/oneskill/src/commands/info.ts
Normal file
11
.claude/skills/oneskill/src/commands/info.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { fetchRegistryInfo } from '../core/registry.js';
|
||||
import { printJson } from '../utils/json.js';
|
||||
|
||||
export interface InfoCommandOptions {
|
||||
registry?: string;
|
||||
}
|
||||
|
||||
export async function runInfo(slug: string, options: InfoCommandOptions): Promise<void> {
|
||||
const result = await fetchRegistryInfo(slug, options.registry);
|
||||
printJson({ schemaVersion: '1', item: result.item });
|
||||
}
|
||||
16
.claude/skills/oneskill/src/commands/list.ts
Normal file
16
.claude/skills/oneskill/src/commands/list.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { spawnSync } from 'child_process';
|
||||
import { resolveOpenskillsCli } from '../core/openskills.js';
|
||||
|
||||
export interface ListCommandOptions {}
|
||||
|
||||
export async function runList(_options: ListCommandOptions): Promise<void> {
|
||||
const cliPath = resolveOpenskillsCli();
|
||||
const args = [cliPath, 'list'];
|
||||
const result = spawnSync(process.execPath, args, {
|
||||
cwd: process.cwd(),
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error('openskills list failed');
|
||||
}
|
||||
}
|
||||
23
.claude/skills/oneskill/src/commands/map.ts
Normal file
23
.claude/skills/oneskill/src/commands/map.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { TargetEnvironment } from '../core/types.js';
|
||||
import { mapInstalledSkills } from '../core/map.js';
|
||||
import { printJson } from '../utils/json.js';
|
||||
|
||||
export interface MapCommandOptions {
|
||||
target?: TargetEnvironment;
|
||||
global?: boolean;
|
||||
universal?: boolean;
|
||||
forceMap?: boolean;
|
||||
}
|
||||
|
||||
export async function runMap(options: MapCommandOptions): Promise<void> {
|
||||
if (!options.target) {
|
||||
throw new Error('map requires --target gemini');
|
||||
}
|
||||
const result = mapInstalledSkills({
|
||||
target: options.target,
|
||||
global: options.global,
|
||||
universal: options.universal,
|
||||
forceMap: options.forceMap,
|
||||
});
|
||||
printJson({ schemaVersion: '1', ...result });
|
||||
}
|
||||
32
.claude/skills/oneskill/src/commands/search.ts
Normal file
32
.claude/skills/oneskill/src/commands/search.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { searchRegistry } from '../core/registry.js';
|
||||
import { printJson } from '../utils/json.js';
|
||||
|
||||
export interface SearchCommandOptions {
|
||||
registry?: string;
|
||||
category?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
export async function runSearch(query: string, options: SearchCommandOptions): Promise<void> {
|
||||
const result = await searchRegistry(
|
||||
{
|
||||
q: query,
|
||||
category: options.category,
|
||||
limit: options.limit,
|
||||
offset: options.offset,
|
||||
sort: options.sort,
|
||||
},
|
||||
options.registry
|
||||
);
|
||||
const raw = result.raw as { registry?: unknown; version?: unknown; pagination?: unknown } | undefined;
|
||||
printJson({
|
||||
schemaVersion: '1',
|
||||
query,
|
||||
registry: raw?.registry,
|
||||
version: raw?.version,
|
||||
pagination: raw?.pagination,
|
||||
items: result.items,
|
||||
});
|
||||
}
|
||||
25
.claude/skills/oneskill/src/commands/sync.ts
Normal file
25
.claude/skills/oneskill/src/commands/sync.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { spawnSync } from 'child_process';
|
||||
import { resolveOpenskillsCli } from '../core/openskills.js';
|
||||
|
||||
export interface SyncCommandOptions {
|
||||
yes?: boolean;
|
||||
output?: string;
|
||||
}
|
||||
|
||||
export async function runSync(options: SyncCommandOptions): Promise<void> {
|
||||
const cliPath = resolveOpenskillsCli();
|
||||
const args = [cliPath, 'sync'];
|
||||
if (options.yes) {
|
||||
args.push('--yes');
|
||||
}
|
||||
if (options.output) {
|
||||
args.push('--output', options.output);
|
||||
}
|
||||
const result = spawnSync(process.execPath, args, {
|
||||
cwd: process.cwd(),
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error('openskills sync failed');
|
||||
}
|
||||
}
|
||||
24
.claude/skills/oneskill/src/core/fs.ts
Normal file
24
.claude/skills/oneskill/src/core/fs.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { dirname, resolve, sep } from 'path';
|
||||
|
||||
export function ensureDir(path: string): void {
|
||||
mkdirSync(path, { recursive: true });
|
||||
}
|
||||
|
||||
export function readJsonFile<T>(path: string): T | null {
|
||||
if (!existsSync(path)) return null;
|
||||
const content = readFileSync(path, 'utf-8');
|
||||
return JSON.parse(content) as T;
|
||||
}
|
||||
|
||||
export function writeJsonFile(path: string, data: unknown): void {
|
||||
ensureDir(dirname(path));
|
||||
writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
export function isPathInside(targetPath: string, baseDir: string): boolean {
|
||||
const resolvedTarget = resolve(targetPath);
|
||||
const resolvedBase = resolve(baseDir);
|
||||
const baseWithSep = resolvedBase.endsWith(sep) ? resolvedBase : resolvedBase + sep;
|
||||
return resolvedTarget === resolvedBase || resolvedTarget.startsWith(baseWithSep);
|
||||
}
|
||||
23
.claude/skills/oneskill/src/core/lock.ts
Normal file
23
.claude/skills/oneskill/src/core/lock.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { join } from 'path';
|
||||
import { readJsonFile, writeJsonFile, ensureDir } from './fs.js';
|
||||
import type { LockFile, LockedSkill } from './types.js';
|
||||
|
||||
const LOCK_FILE_NAME = 'lock.json';
|
||||
|
||||
export function getLockPath(root: string): string {
|
||||
return join(root, '.oneskill', LOCK_FILE_NAME);
|
||||
}
|
||||
|
||||
export function readLock(root: string): LockFile | null {
|
||||
return readJsonFile<LockFile>(getLockPath(root));
|
||||
}
|
||||
|
||||
export function writeLock(root: string, lock: LockFile): void {
|
||||
ensureDir(join(root, '.oneskill'));
|
||||
writeJsonFile(getLockPath(root), lock);
|
||||
}
|
||||
|
||||
export function upsertLockSkill(lock: LockFile, skill: LockedSkill): void {
|
||||
lock.skills[skill.id] = skill;
|
||||
lock.updatedAt = new Date().toISOString();
|
||||
}
|
||||
11
.claude/skills/oneskill/src/core/log.ts
Normal file
11
.claude/skills/oneskill/src/core/log.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { appendFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { ensureDir } from './fs.js';
|
||||
|
||||
export function appendLog(root: string, event: Record<string, unknown>): void {
|
||||
const logDir = join(root, '.oneskill', 'logs');
|
||||
ensureDir(logDir);
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
const logPath = join(logDir, `${date}.jsonl`);
|
||||
appendFileSync(logPath, JSON.stringify(event) + '\n', 'utf-8');
|
||||
}
|
||||
33
.claude/skills/oneskill/src/core/manifest.ts
Normal file
33
.claude/skills/oneskill/src/core/manifest.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { lstatSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import type { ManifestSummary } from './types.js';
|
||||
|
||||
export function buildManifestSummary(root: string): ManifestSummary {
|
||||
let files = 0;
|
||||
let bytes = 0;
|
||||
let hasSymlinks = false;
|
||||
|
||||
const walk = (dir: string): void => {
|
||||
const entries = readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
const stat = lstatSync(fullPath);
|
||||
if (stat.isSymbolicLink()) {
|
||||
hasSymlinks = true;
|
||||
files += 1;
|
||||
continue;
|
||||
}
|
||||
if (stat.isDirectory()) {
|
||||
walk(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (stat.isFile()) {
|
||||
files += 1;
|
||||
bytes += stat.size;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walk(root);
|
||||
return { files, bytes, hasSymlinks };
|
||||
}
|
||||
49
.claude/skills/oneskill/src/core/map.ts
Normal file
49
.claude/skills/oneskill/src/core/map.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { existsSync, readdirSync } from 'fs';
|
||||
import { basename, join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { mapSkill } from './mapping.js';
|
||||
import { detectRoot } from './root.js';
|
||||
import type { TargetEnvironment } from './types.js';
|
||||
|
||||
export interface MapOptions {
|
||||
target: TargetEnvironment;
|
||||
global?: boolean;
|
||||
universal?: boolean;
|
||||
forceMap?: boolean;
|
||||
}
|
||||
|
||||
function getSourceBase(root: string, options: MapOptions): string {
|
||||
const baseRoot = options.global ? homedir() : root;
|
||||
const folder = options.universal ? '.agent/skills' : '.claude/skills';
|
||||
return join(baseRoot, folder);
|
||||
}
|
||||
|
||||
function getTargetRoot(root: string, options: MapOptions): string {
|
||||
return options.global ? homedir() : root;
|
||||
}
|
||||
|
||||
export function mapInstalledSkills(options: MapOptions): { mapped: number; sourceBase: string; targetRoot: string } {
|
||||
if (options.target !== 'gemini') {
|
||||
throw new Error('map currently supports only --target gemini');
|
||||
}
|
||||
const rootInfo = detectRoot(process.cwd());
|
||||
const sourceBase = getSourceBase(rootInfo.root, options);
|
||||
const targetRoot = getTargetRoot(rootInfo.root, options);
|
||||
|
||||
if (!existsSync(sourceBase)) {
|
||||
return { mapped: 0, sourceBase, targetRoot };
|
||||
}
|
||||
|
||||
const entries = readdirSync(sourceBase, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => join(sourceBase, entry.name));
|
||||
|
||||
let mapped = 0;
|
||||
for (const dir of entries) {
|
||||
const name = basename(dir);
|
||||
mapSkill(targetRoot, 'gemini', name, dir, { forceMap: options.forceMap });
|
||||
mapped += 1;
|
||||
}
|
||||
|
||||
return { mapped, sourceBase, targetRoot };
|
||||
}
|
||||
85
.claude/skills/oneskill/src/core/mapping.ts
Normal file
85
.claude/skills/oneskill/src/core/mapping.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { existsSync, lstatSync, readlinkSync, rmSync, symlinkSync, cpSync, writeFileSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
import { ensureDir, isPathInside } from './fs.js';
|
||||
import type { SkillMappingRecord, TargetEnvironment } from './types.js';
|
||||
|
||||
const TARGET_DIRS: Record<TargetEnvironment, string> = {
|
||||
codex: '.codex/skills',
|
||||
gemini: '.gemini/skills',
|
||||
claude: '.claude/skills',
|
||||
agent: '.agent/skills',
|
||||
};
|
||||
|
||||
export interface MapOptions {
|
||||
forceMap?: boolean;
|
||||
codexHome?: string;
|
||||
}
|
||||
|
||||
function getTargetBase(root: string, target: TargetEnvironment, options?: MapOptions): string {
|
||||
if (target === 'codex' && options?.codexHome) {
|
||||
return resolve(options.codexHome, 'skills');
|
||||
}
|
||||
return join(root, TARGET_DIRS[target]);
|
||||
}
|
||||
|
||||
function readLinkTarget(path: string): string | null {
|
||||
try {
|
||||
return readlinkSync(path);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function mapSkill(
|
||||
root: string,
|
||||
target: TargetEnvironment,
|
||||
skillName: string,
|
||||
storePath: string,
|
||||
options?: MapOptions
|
||||
): SkillMappingRecord {
|
||||
const baseDir = getTargetBase(root, target, options);
|
||||
const targetPath = join(baseDir, skillName);
|
||||
|
||||
if (target !== 'codex' && !isPathInside(targetPath, root)) {
|
||||
throw new Error(`Target path escapes root: ${targetPath}`);
|
||||
}
|
||||
|
||||
ensureDir(baseDir);
|
||||
|
||||
if (existsSync(targetPath)) {
|
||||
const stat = lstatSync(targetPath);
|
||||
if (stat.isSymbolicLink()) {
|
||||
const linkTarget = readLinkTarget(targetPath);
|
||||
if (linkTarget && resolve(linkTarget) === resolve(storePath)) {
|
||||
return { target, path: targetPath, mode: process.platform === 'win32' ? 'junction' : 'symlink', updatedAt: new Date().toISOString() };
|
||||
}
|
||||
if (!options?.forceMap) {
|
||||
throw new Error(`Target already exists (symlink): ${targetPath}`);
|
||||
}
|
||||
} else {
|
||||
if (!options?.forceMap) {
|
||||
throw new Error(`Target already exists: ${targetPath}`);
|
||||
}
|
||||
}
|
||||
rmSync(targetPath, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
||||
try {
|
||||
symlinkSync(storePath, targetPath, linkType);
|
||||
return { target, path: targetPath, mode: process.platform === 'win32' ? 'junction' : 'symlink', updatedAt: new Date().toISOString() };
|
||||
} catch (error) {
|
||||
if (!options?.forceMap) {
|
||||
// Fall through to copy on Windows permission errors or other failures.
|
||||
}
|
||||
}
|
||||
|
||||
cpSync(storePath, targetPath, { recursive: true, dereference: false });
|
||||
writeFileSync(
|
||||
join(targetPath, '.oneskill-meta.json'),
|
||||
JSON.stringify({ source: storePath, mappedAt: new Date().toISOString(), mode: 'copy' }, null, 2) + '\n',
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
return { target, path: targetPath, mode: 'copy', updatedAt: new Date().toISOString() };
|
||||
}
|
||||
42
.claude/skills/oneskill/src/core/openskills.ts
Normal file
42
.claude/skills/oneskill/src/core/openskills.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { createRequire } from 'module';
|
||||
import { spawnSync } from 'child_process';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export function resolveOpenskillsCli(): string {
|
||||
const pkgPath = require.resolve('openskills/package.json');
|
||||
const pkgDir = dirname(pkgPath);
|
||||
const content = readFileSync(pkgPath, 'utf-8');
|
||||
const parsed = JSON.parse(content) as { bin?: Record<string, string> | string; main?: string };
|
||||
const bin = parsed.bin;
|
||||
let candidate: string | null = null;
|
||||
if (typeof bin === 'string') {
|
||||
candidate = resolve(pkgDir, bin);
|
||||
} else if (bin && typeof bin === 'object') {
|
||||
const first = Object.values(bin)[0];
|
||||
if (first) candidate = resolve(pkgDir, first);
|
||||
} else if (parsed.main) {
|
||||
candidate = resolve(pkgDir, parsed.main);
|
||||
}
|
||||
|
||||
if (candidate && existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
// Build openskills locally if the CLI entry is missing.
|
||||
const result = spawnSync('npm', ['run', 'build'], {
|
||||
cwd: pkgDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error('Failed to build openskills');
|
||||
}
|
||||
|
||||
if (candidate && existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
throw new Error('Unable to resolve openskills CLI entry');
|
||||
}
|
||||
110
.claude/skills/oneskill/src/core/registry.ts
Normal file
110
.claude/skills/oneskill/src/core/registry.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { RegistryInfoResponse, RegistrySearchResponse, SkillListItem } from './types.js';
|
||||
|
||||
const DEFAULT_REGISTRY_URL = 'https://skillsdirectory.com/api/registry';
|
||||
|
||||
export interface SearchParams {
|
||||
q?: string;
|
||||
category?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
function getRegistryBase(override?: string): string {
|
||||
return (override || process.env.ONESKILL_REGISTRY_URL || DEFAULT_REGISTRY_URL).replace(/\/$/, '');
|
||||
}
|
||||
|
||||
async function fetchJson(url: string): Promise<unknown> {
|
||||
const res = await fetch(url, { headers: { 'accept': 'application/json' } });
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Registry request failed (${res.status}): ${text.slice(0, 200)}`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function normalizeSkill(item: Record<string, unknown>): SkillListItem {
|
||||
const slug = String(item.slug || item.id || item.name || '');
|
||||
const name = String(item.name || item.title || slug || '');
|
||||
const description = String(item.description || item.summary || '');
|
||||
const repository = String(item.repository || item.repo || item.url || '');
|
||||
const verified = typeof item.verified === 'boolean' ? item.verified : undefined;
|
||||
const stars =
|
||||
typeof item.stars === 'number'
|
||||
? item.stars
|
||||
: typeof (item.github as { stars?: unknown } | undefined)?.stars === 'number'
|
||||
? (item.github as { stars: number }).stars
|
||||
: undefined;
|
||||
const tags = Array.isArray(item.tags) ? item.tags.map(String) : undefined;
|
||||
const authorObj = item.author as { name?: unknown; url?: unknown } | undefined;
|
||||
const authorName = typeof item.author === 'string' ? String(item.author) : authorObj?.name ? String(authorObj.name) : undefined;
|
||||
const author = authorName
|
||||
? { name: authorName, url: authorObj?.url ? String(authorObj.url) : undefined }
|
||||
: undefined;
|
||||
|
||||
const signals = {
|
||||
lastUpdated: item.lastUpdated ? String(item.lastUpdated) : undefined,
|
||||
license:
|
||||
item.license ? String(item.license)
|
||||
: typeof (item.github as { license?: unknown } | undefined)?.license === 'string'
|
||||
? String((item.github as { license: string }).license)
|
||||
: undefined,
|
||||
riskHints: Array.isArray(item.riskHints) ? item.riskHints.map(String) : undefined,
|
||||
};
|
||||
|
||||
return {
|
||||
schemaVersion: '1',
|
||||
slug,
|
||||
name,
|
||||
description,
|
||||
repository,
|
||||
verified,
|
||||
stars,
|
||||
tags,
|
||||
author,
|
||||
signals,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSearchPayload(payload: unknown): SkillListItem[] {
|
||||
if (!payload || typeof payload !== 'object') return [];
|
||||
const data = payload as Record<string, unknown>;
|
||||
const candidates =
|
||||
(Array.isArray(data.skills) ? data.skills : null) ||
|
||||
(Array.isArray(data.items) ? data.items : null) ||
|
||||
(Array.isArray(data.results) ? data.results : null) ||
|
||||
(Array.isArray(data.data) ? data.data : null) ||
|
||||
(Array.isArray(payload) ? (payload as unknown[]) : null);
|
||||
|
||||
if (!candidates) return [];
|
||||
return candidates
|
||||
.filter((item) => item && typeof item === 'object')
|
||||
.map((item) => normalizeSkill(item as Record<string, unknown>));
|
||||
}
|
||||
|
||||
export async function searchRegistry(params: SearchParams, overrideUrl?: string): Promise<RegistrySearchResponse> {
|
||||
const base = getRegistryBase(overrideUrl);
|
||||
const url = new URL(base);
|
||||
if (params.q) url.searchParams.set('q', params.q);
|
||||
if (params.category) url.searchParams.set('category', params.category);
|
||||
if (typeof params.limit === 'number') url.searchParams.set('limit', String(params.limit));
|
||||
if (typeof params.offset === 'number') url.searchParams.set('offset', String(params.offset));
|
||||
if (params.sort) url.searchParams.set('sort', params.sort);
|
||||
const raw = await fetchJson(url.toString());
|
||||
const items = normalizeSearchPayload(raw);
|
||||
return { items, raw };
|
||||
}
|
||||
|
||||
export async function fetchRegistryInfo(slug: string, overrideUrl?: string): Promise<RegistryInfoResponse> {
|
||||
const base = getRegistryBase(overrideUrl);
|
||||
const url = `${base}/${encodeURIComponent(slug)}`;
|
||||
const raw = await fetchJson(url);
|
||||
if (raw && typeof raw === 'object') {
|
||||
const record = raw as Record<string, unknown>;
|
||||
const candidate = (record.skill as Record<string, unknown> | undefined) || (record.item as Record<string, unknown> | undefined) || record;
|
||||
if (candidate && typeof candidate === 'object') {
|
||||
return { item: normalizeSkill(candidate as Record<string, unknown>), raw };
|
||||
}
|
||||
}
|
||||
throw new Error('Registry info failed');
|
||||
}
|
||||
38
.claude/skills/oneskill/src/core/root.ts
Normal file
38
.claude/skills/oneskill/src/core/root.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { dirname, join, resolve } from 'path';
|
||||
import type { RootDetection } from './types.js';
|
||||
|
||||
const MARKER_DIRS = ['.git', '.agent', '.claude', '.gemini', '.codex'];
|
||||
|
||||
function hasOneskillConfig(pkgPath: string): boolean {
|
||||
try {
|
||||
const content = readFileSync(pkgPath, 'utf-8');
|
||||
const parsed = JSON.parse(content) as { oneskill?: unknown };
|
||||
return Boolean(parsed.oneskill);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasMarkers(dir: string): string | null {
|
||||
for (const marker of MARKER_DIRS) {
|
||||
if (existsSync(join(dir, marker))) return marker;
|
||||
}
|
||||
if (existsSync(join(dir, 'AGENTS.md'))) return 'AGENTS.md';
|
||||
const pkgPath = join(dir, 'package.json');
|
||||
if (existsSync(pkgPath) && hasOneskillConfig(pkgPath)) return 'package.json:oneskill';
|
||||
return null;
|
||||
}
|
||||
|
||||
export function detectRoot(startDir: string): RootDetection {
|
||||
let current = resolve(startDir);
|
||||
while (true) {
|
||||
const reason = hasMarkers(current);
|
||||
if (reason) return { root: current, reason };
|
||||
const parent = dirname(current);
|
||||
if (parent === current) {
|
||||
return { root: resolve(startDir), reason: 'fallback:cwd' };
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
81
.claude/skills/oneskill/src/core/types.ts
Normal file
81
.claude/skills/oneskill/src/core/types.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
export type TargetEnvironment = 'codex' | 'gemini' | 'claude' | 'agent';
|
||||
|
||||
export interface SkillListItem {
|
||||
schemaVersion: '1';
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
repository: string;
|
||||
verified?: boolean;
|
||||
stars?: number;
|
||||
tags?: string[];
|
||||
author?: {
|
||||
name: string;
|
||||
url?: string;
|
||||
};
|
||||
signals?: {
|
||||
lastUpdated?: string;
|
||||
license?: string;
|
||||
riskHints?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface RegistryInfoResponse {
|
||||
item: SkillListItem;
|
||||
raw: unknown;
|
||||
}
|
||||
|
||||
export interface RegistrySearchResponse {
|
||||
items: SkillListItem[];
|
||||
raw: unknown;
|
||||
}
|
||||
|
||||
export interface ManifestSummary {
|
||||
files: number;
|
||||
bytes: number;
|
||||
hasSymlinks: boolean;
|
||||
}
|
||||
|
||||
export interface SkillSource {
|
||||
input: string;
|
||||
type: 'slug' | 'repository' | 'local';
|
||||
repository?: string;
|
||||
ref?: string;
|
||||
}
|
||||
|
||||
export interface SkillMappingRecord {
|
||||
target: TargetEnvironment;
|
||||
path: string;
|
||||
mode: 'symlink' | 'junction' | 'copy';
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface LockedSkill {
|
||||
id: string;
|
||||
name: string;
|
||||
source: SkillSource;
|
||||
installedAt: string;
|
||||
storePath: string;
|
||||
manifest: ManifestSummary;
|
||||
mappings: SkillMappingRecord[];
|
||||
}
|
||||
|
||||
export interface LockFile {
|
||||
schemaVersion: '1';
|
||||
root: string;
|
||||
oneskillVersion: string;
|
||||
openskillsVersion: string;
|
||||
updatedAt: string;
|
||||
skills: Record<string, LockedSkill>;
|
||||
}
|
||||
|
||||
export interface RootDetection {
|
||||
root: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface InstallResult {
|
||||
root: string;
|
||||
skills: LockedSkill[];
|
||||
target?: TargetEnvironment;
|
||||
}
|
||||
29
.claude/skills/oneskill/src/core/versions.ts
Normal file
29
.claude/skills/oneskill/src/core/versions.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createRequire } from 'module';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export function getOneskillVersion(): string {
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pkgPath = join(__dirname, '../../package.json');
|
||||
try {
|
||||
const content = readFileSync(pkgPath, 'utf-8');
|
||||
const parsed = JSON.parse(content) as { version?: string };
|
||||
return parsed.version || '0.0.0';
|
||||
} catch {
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
export function getOpenskillsVersion(): string {
|
||||
try {
|
||||
const pkgPath = require.resolve('openskills/package.json');
|
||||
const content = readFileSync(pkgPath, 'utf-8');
|
||||
const parsed = JSON.parse(content) as { version?: string };
|
||||
return parsed.version || '0.0.0';
|
||||
} catch {
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
3
.claude/skills/oneskill/src/utils/json.ts
Normal file
3
.claude/skills/oneskill/src/utils/json.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function printJson(data: unknown): void {
|
||||
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
||||
}
|
||||
14
.claude/skills/oneskill/tsconfig.json
Normal file
14
.claude/skills/oneskill/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Bundler",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
10
.claude/skills/oneskill/tsup.config.ts
Normal file
10
.claude/skills/oneskill/tsup.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/cli.ts'],
|
||||
format: ['esm'],
|
||||
dts: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
target: 'node20'
|
||||
});
|
||||
Reference in New Issue
Block a user