✨ feat(skills_linker): 添加一键安装脚本。

This commit is contained in:
--global committed 2026-10-02 01:00:09 +08:00
1 parent 2ce3f0173d
commit 707a6f1ef1
3 files changed
+445 -3

No files matched your search

+21 -3
View File
@@ -33,7 +33,7 @@
- 🧩 **开箱即用**:每个技能自带完整提示词(`SKILL.md`)与接口策略(`agents/openai.yaml`),复制即用、无需额外依赖。 - 🧩 **开箱即用**:每个技能自带完整提示词(`SKILL.md`)与接口策略(`agents/openai.yaml`),复制即用、无需额外依赖。
- 🗂️ **分层分类**:按「开发与规范 → 测试与修复 → 交付与部署 → 全自动化」四层递进组织,形成完整质量闭环。 - 🗂️ **分层分类**:按「开发与规范 → 测试与修复 → 交付与部署 → 全自动化」四层递进组织,形成完整质量闭环。
- 🔗 **技能互链**:技能之间通过 `/技能名` 相互调用(如 `ci-cd-pipeline` 可串联 13 个子技能),可组装成自动化流水线。 - 🔗 **技能互链**:技能之间通过 `/技能名` 相互调用(如 `ci-cd-pipeline` 可串联 13 个子技能),可组装成自动化流水线。
- 🚀 **一键安装**:`skills_linker.sh` 将分类目录下全部技能软链接到 `~/.agents/skills/`,全局可用。 - 🚀 **一键安装**:`skills_linker.sh`(Linux / macOS)与 `skills_linker.bat`(Windows)将分类目录下全部技能链接到 `~/.agents/skills/`,全局可用。
- 📊 **可观测可审计**:每个技能输出带时间戳的结构化 Markdown 报告(`*_<timestamp>.md`),全程留痕、便于审计。 - 📊 **可观测可审计**:每个技能输出带时间戳的结构化 Markdown 报告(`*_<timestamp>.md`),全程留痕、便于审计。
- 🛡️ **安全优先**:内置强制安全规则——生产环境保护、敏感信息脱敏、最小权限、人工确认点。 - 🛡️ **安全优先**:内置强制安全规则——生产环境保护、敏感信息脱敏、最小权限、人工确认点。
@@ -43,7 +43,8 @@
```text ```text
AgentSkills/ AgentSkills/
├── skills_linker.sh # 一键安装脚本(软链接到 ~/.agents/skills/) ├── skills_linker.sh # 一键安装脚本 Linux/macOS(软链接到 ~/.agents/skills/)
├── skills_linker.bat # 一键安装脚本 Windows(junction / 符号链接)
├── README.md # 项目说明(本文件) ├── README.md # 项目说明(本文件)
├── development-and-standards/ # ① 开发与规范(6 个技能) ├── development-and-standards/ # ① 开发与规范(6 个技能)
│ ├── readme-generator/ # README 生成器 │ ├── readme-generator/ # README 生成器
@@ -115,6 +116,23 @@ chmod +x skills_linker.sh
> >
> `skills_linker.sh` 会跳过指向不同目标的同名已存在链接(打印警告),不会覆盖非符号链接目录,可安全重复执行。 > `skills_linker.sh` 会跳过指向不同目标的同名已存在链接(打印警告),不会覆盖非符号链接目录,可安全重复执行。
#### 🪟 Windows
在仓库根目录双击 `skills_linker.bat`,或在 cmd / PowerShell 中执行:
```bat
skills_linker.bat :: 链接全部技能(默认目标 %USERPROFILE%\.agents\skills)
skills_linker.bat /n :: 预演,只打印计划,不写入不删除
skills_linker.bat /u :: 卸载,只删除指向本仓库的链接
skills_linker.bat /? :: 查看全部选项
```
> [!NOTE]
>
> Windows 版优先使用目录联接(junction),无需管理员权限或开发者模式;不可用时自动回退到符号链接,网络路径请用 `/c` 复制模式。已存在且指向同一技能会跳过、指向别处会替换、同名普通目录只告警不覆盖,同样可安全重复执行。
>
> 脚本输出为纯 ASCII 英文:cmd.exe 按**当前控制台代码页**解码批处理文件,中文 Windows 的 936 控制台(含资源管理器双击)与 PowerShell 7 的 65001 控制台无法用同一份非 ASCII 字节同时正确显示,而纯 ASCII 在任何代码页下都一致。
### 3️⃣ 在 Agent 中调用 ### 3️⃣ 在 Agent 中调用
在支持 Skills 的 AI 编码工具(如 Claude Code、VS Code Copilot Chat 等)中,直接以技能名唤起即可,例如 `/blackbox-tester`、`/ci-cd-pipeline`。 在支持 Skills 的 AI 编码工具(如 Claude Code、VS Code Copilot Chat 等)中,直接以技能名唤起即可,例如 `/blackbox-tester`、`/ci-cd-pipeline`。
@@ -247,7 +265,7 @@ flowchart LR
1. 每个技能放在对应分类目录下,包含 `SKILL.md` 与 `agents/openai.yaml`。 1. 每个技能放在对应分类目录下,包含 `SKILL.md` 与 `agents/openai.yaml`。
2. `SKILL.md` 至少包含:角色与描述、工作流程、行为准则、输出格式。 2. `SKILL.md` 至少包含:角色与描述、工作流程、行为准则、输出格式。
3. 涉及破坏性或副作用操作的技能,务必在 `openai.yaml` 中设置 `require_confirmation: true`。 3. 涉及破坏性或副作用操作的技能,务必在 `openai.yaml` 中设置 `require_confirmation: true`。
4. 新增技能后,可运行 `./skills_linker.sh` 验证安装脚本兼容性。 4. 新增技能后,可运行 `./skills_linker.sh`(Windows 上运行 `skills_linker.bat /n` 预演)验证安装脚本兼容性。
<!-- TODO: 补充 CONTRIBUTING.md 与具体的贡献模板 --> <!-- TODO: 补充 CONTRIBUTING.md 与具体的贡献模板 -->
+424
View File
@@ -0,0 +1,424 @@
@echo off
rem ===========================================================================
rem AgentSkills - skills linker for Windows (cmd / .bat version)
rem ---------------------------------------------------------------------------
rem This file is deliberately PURE ASCII, UTF-8/ANSI agnostic, CRLF endings.
rem
rem Why: cmd.exe decodes a batch file with the *current console code page*.
rem A 936 (Simplified-Chinese / Explorer double click) console and a 65001
rem (PowerShell 7) console decode the same non-ASCII bytes differently, and
rem switching the code page inside a batch file (chcp) can desynchronise cmd's
rem parser so that it executes comment text. A pure ASCII file has no such
rem ambiguity: every byte means the same thing under every code page, and the
rem output renders identically everywhere. Keep it ASCII when editing.
rem ===========================================================================
setlocal EnableExtensions DisableDelayedExpansion
rem ===========================================================================
rem AgentSkills skills linker (Windows) - the counterpart of skills_linker.sh
rem
rem What it does: scans <repo root>\<category>\<skill> two levels below this
rem script and links every skill directory into the global skills directory
rem (default %USERPROFILE%\.agents\skills) so every project can use them.
rem How it links: a directory junction first (no admin rights, no developer
rem mode needed), falling back to a directory symlink (mklink /D). Use
rem /c (copy mode) when links are not usable, e.g. on a network path.
rem Safety: it only adds or removes the links themselves inside the target
rem directory - it never enters or deletes the content behind a link, and it
rem never overwrites a real directory that happens to share a skill name.
rem ===========================================================================
set "EXITCODE=0"
set "ROOT=%~dp0"
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
if "%ROOT:~-1%"==":" set "ROOT=%ROOT%\"
set "TARGET="
set "MODE=link"
set "DRYRUN=0"
set "PAUSE_AT_END=0"
set "TMPLIST="
set /a N_LINKED=0 >nul
set /a N_REPLACED=0 >nul
set /a N_REMOVED=0 >nul
set /a N_COPIED=0 >nul
set /a N_SKIPPED=0 >nul
set /a N_WARN=0 >nul
set /a N_FAIL=0 >nul
rem A double click starts us as: cmd /c ""<script>" " (no arguments, the path
rem wrapped in doubled quotes). Only that case waits for a key at the end, so
rem windows opened by Explorer do not vanish; every other call stays scriptable.
if not "%~1"=="" goto pause_checked
echo(%cmdcmdline% | findstr /c:"\"\"" >nul 2>&1 && set "PAUSE_AT_END=1"
:pause_checked
rem ------------------------------ parse arguments ------------------------------
:parse
if "%~1"=="" goto parsed
set "ARG=%~1"
if /i "%ARG%"=="/?" goto usage
if /i "%ARG%"=="-?" goto usage
if /i "%ARG%"=="/h" goto usage
if /i "%ARG%"=="-h" goto usage
if /i "%ARG%"=="--help" goto usage
if /i "%ARG%"=="/l" goto arg_shift
if /i "%ARG%"=="-l" goto arg_shift
if /i "%ARG%"=="--link" goto arg_shift
if /i "%ARG%"=="/c" goto opt_copy
if /i "%ARG%"=="-c" goto opt_copy
if /i "%ARG%"=="--copy" goto opt_copy
if /i "%ARG%"=="/u" goto opt_unlink
if /i "%ARG%"=="-u" goto opt_unlink
if /i "%ARG%"=="--unlink" goto opt_unlink
if /i "%ARG%"=="/n" goto opt_dry
if /i "%ARG%"=="-n" goto opt_dry
if /i "%ARG%"=="--dry-run" goto opt_dry
if /i "%ARG%"=="/t" goto opt_target
if /i "%ARG%"=="-t" goto opt_target
if /i "%ARG%"=="--target" goto opt_target
echo [FAIL] unknown option: "%ARG%"
call :show_usage
set "EXITCODE=2"
goto finish
:opt_copy
set "MODE=copy"
goto arg_shift
:opt_unlink
set "MODE=unlink"
goto arg_shift
:opt_dry
set "DRYRUN=1"
goto arg_shift
:opt_target
set "V=%~2"
if "%V%"=="" goto err_value
if "%V:~0,1%"=="/" goto err_value
if "%V:~0,1%"=="-" goto err_value
set "TARGET=%V%"
shift
goto arg_shift
:err_value
echo [FAIL] option %ARG% needs a directory value
call :show_usage
set "EXITCODE=2"
goto finish
:arg_shift
shift
goto parse
:parsed
if defined TARGET goto have_target
if not defined USERPROFILE goto err_nohome
set "TARGET=%USERPROFILE%\.agents\skills"
:have_target
for %%A in ("%TARGET%") do set "TARGET=%%~fA"
rem ------------------------------ banner ------------------------------
echo(
echo == AgentSkills skills linker ==
echo( repo root : "%ROOT%"
echo( target : "%TARGET%"
if /i "%MODE%"=="link" echo( mode : link (junction, falls back to a symlink)
if /i "%MODE%"=="copy" echo( mode : copy (real copies, no links)
if /i "%MODE%"=="unlink" echo( mode : unlink (removes only links into this repo)
if "%DRYRUN%"=="1" echo( dry run : yes (nothing is written or deleted)
echo(
rem ------------------------------ prepare target ------------------------------
if /i "%MODE%"=="unlink" goto pre_unlink
if "%DRYRUN%"=="1" goto scan
if exist "%TARGET%\" goto scan
mkdir "%TARGET%" >nul 2>&1
if not exist "%TARGET%\" goto fail_target
echo [DIR] created target directory "%TARGET%"
goto scan
:pre_unlink
if not exist "%TARGET%\" goto no_target
goto scan
:no_target
echo nothing to unlink: target directory "%TARGET%" does not exist
goto summary
rem ------------------------------ scan and process ------------------------------
:scan
if not defined TEMP set "TEMP=%TMP%"
if defined TEMP set "TMPLIST=%TEMP%\agentskills_seen_%RANDOM%%RANDOM%.txt"
if not defined TMPLIST set "TMPLIST=%ROOT%\agentskills_seen_%RANDOM%.txt"
type nul > "%TMPLIST%" 2>nul
for /f "delims=" %%C in ('dir /b /ad /on "%ROOT%" 2^>nul') do (
set "CAT=%%C"
call :each_category
)
goto summary
rem ------------------------------ summary ------------------------------
:summary
if defined TMPLIST del /f /q "%TMPLIST%" >nul 2>&1
echo(
echo ------------------------------ summary ------------------------------
echo( new links : %N_LINKED%
echo( replaced : %N_REPLACED%
echo( copied : %N_COPIED%
echo( removed : %N_REMOVED%
echo( skipped : %N_SKIPPED%
echo( warnings : %N_WARN%
echo( failures : %N_FAIL%
echo(
if "%DRYRUN%"=="1" echo (dry run: the lines above are the plan, nothing was changed)
if %N_FAIL% gtr 0 set "EXITCODE=1"
if %N_WARN% gtr 0 if "%EXITCODE%"=="0" set "EXITCODE=2"
goto finish
:usage
call :show_usage
set "EXITCODE=0"
goto finish
rem ------------------------------ cleanup ------------------------------
:finish
if defined TMPLIST del /f /q "%TMPLIST%" >nul 2>&1
if "%PAUSE_AT_END%"=="1" pause
endlocal & exit /b %EXITCODE%
:err_nohome
echo [FAIL] environment variable USERPROFILE is not set, use /t to name a target
set "EXITCODE=2"
goto finish
rem ============================== subroutines ==============================
rem walk the skill directories of one category directory
:each_category
if "%CAT:~0,1%"=="." goto :eof
for /f "delims=" %%S in ('dir /b /ad /on "%ROOT%\%CAT%" 2^>nul') do (
set "SKILL=%%S"
call :each_skill
)
goto :eof
rem process one skill directory
:each_skill
if "%SKILL:~0,1%"=="." goto :eof
set "NAME=%SKILL%"
set "SRC=%ROOT%\%CAT%\%NAME%"
set "DEST=%TARGET%\%NAME%"
rem skill names must be unique; the first category scanned (alphabetical) wins
findstr /x /i /c:"\"%NAME%\"" "%TMPLIST%" >nul 2>&1
if not errorlevel 1 goto warn_dup
>>"%TMPLIST%" echo("%NAME%"
if not exist "%SRC%\SKILL.md" echo [NOTE] no SKILL.md inside "%SRC%", treated as a skill directory anyway
if /i "%MODE%"=="unlink" goto entry_unlink
if /i "%MODE%"=="copy" goto entry_copy
goto entry_link
rem ------------------------------ link mode ------------------------------
:entry_link
if not exist "%DEST%" goto link_new
if not exist "%DEST%\" goto warn_file
call :resolve_dest
if not defined CURTARGET goto warn_dir
call :same_source
if "%SAME%"=="1" goto skip_same
echo [REPL] "%DEST%" (was "%CURTARGET%") -^> "%SRC%"
set /a N_REPLACED+=1 >nul
set "REPLACING=1"
if "%DRYRUN%"=="1" goto :eof
call :remove_link
if "%OK%"=="0" goto fail_remove
:link_new
if "%DRYRUN%"=="1" goto link_done
call :make_link
if "%OK%"=="0" goto fail_link
:link_done
if defined REPLACING goto :eof
set /a N_LINKED+=1 >nul
echo [NEW] "%SRC%" -^> "%DEST%"
goto :eof
rem ------------------------------ copy mode ------------------------------
:entry_copy
if not exist "%DEST%" goto copy_do
if not exist "%DEST%\" goto warn_file
call :resolve_dest
if not defined CURTARGET goto copy_update
echo [REPL] "%DEST%" is a link, copying a real directory instead
set /a N_REPLACED+=1 >nul
if "%DRYRUN%"=="1" goto :eof
call :remove_link
if "%OK%"=="0" goto fail_remove
goto copy_do
:copy_update
echo [SYNC] updating the existing directory "%DEST%"
:copy_do
if "%DRYRUN%"=="1" goto copy_done
robocopy "%SRC%" "%DEST%" /E /NFL /NDL /NJH /NJS /NP /R:1 /W:1 >nul 2>&1
if errorlevel 8 goto fail_copy
:copy_done
set /a N_COPIED+=1 >nul
echo [COPY] "%SRC%" -^> "%DEST%"
goto :eof
rem ------------------------------ unlink mode ------------------------------
:entry_unlink
if not exist "%DEST%" goto :eof
if not exist "%DEST%\" goto skip_unlink_file
call :resolve_dest
if not defined CURTARGET goto skip_unlink_dir
call :in_repo
if "%INREPO%"=="0" goto skip_unlink_other
echo [DEL] "%DEST%" (was "%CURTARGET%")
set /a N_REMOVED+=1 >nul
if "%DRYRUN%"=="1" goto :eof
call :remove_link
if "%OK%"=="0" goto fail_remove
goto :eof
rem ------------------------------ helpers ------------------------------
rem read the link target of %DEST% (junctions and symlinks); stays empty for a real directory
:resolve_dest
set "CURTARGET="
if not exist "%TARGET%\" goto :eof
for /f "tokens=2 delims=[]" %%T in ('dir /a:l "%TARGET%" 2^>nul ^| findstr /i /c:" %NAME% ["') do set "CURTARGET=%%T"
goto :eof
rem does %CURTARGET% denote the same directory as %SRC%? compares long and 8.3
rem names, case-insensitively, so re-runs stay idempotent
:same_source
set "SAME=0"
set "A=%CURTARGET%"
set "B=%SRC%"
if "%A%"=="" goto :eof
if "%A:~0,4%"=="\??\" set "A=%A:~4%"
if /i "%A%"=="%B%" set "SAME=1"
for %%X in ("%A%") do set "A2=%%~fsX"
for %%Y in ("%B%") do set "B2=%%~fsY"
if /i "%A2%"=="%B2%" set "SAME=1"
goto :eof
rem is the link target inside this repository?
:in_repo
set "INREPO=0"
set "P=%ROOT%\"
call set "REST=%%CURTARGET:%P%=%%"
if not "%REST%"=="%CURTARGET%" set "INREPO=1"
goto :eof
rem create the link: junction first, symlink as fallback, clearing stale entries
:make_link
set "OK=0"
call :mk_verify J
if "%OK%"=="1" goto :eof
call :remove_link
set "OK=0"
call :mk_verify J
if "%OK%"=="1" goto :eof
call :mk_verify D
goto :eof
rem run one mklink and verify that the link really resolves to %SRC%
:mk_verify
mklink /%1 "%DEST%" "%SRC%" >nul 2>&1
call :resolve_dest
if not defined CURTARGET goto :eof
call :same_source
if "%SAME%"=="1" set "OK=1"
goto :eof
rem delete the link itself: rd without /s never recurses into the link target
:remove_link
set "OK=0"
rd "%DEST%" >nul 2>&1
if exist "%DEST%\" goto :eof
if exist "%DEST%" del /f /q "%DEST%" >nul 2>&1
if exist "%DEST%" goto :eof
set "OK=1"
goto :eof
rem ------------------------------ notices ------------------------------
:warn_dup
echo [WARN] duplicate skill name, skipped "%ROOT%\%CAT%\%NAME%" (the first category scanned wins)
set /a N_WARN+=1 >nul
goto :eof
:warn_file
echo [WARN] a plain file already exists at "%DEST%", skipped
set /a N_WARN+=1 >nul
goto :eof
:warn_dir
echo [WARN] a real directory (not a link) already exists at "%DEST%", skipped - resolve it manually
set /a N_WARN+=1 >nul
goto :eof
:skip_same
set /a N_SKIPPED+=1 >nul
echo [SKIP] already linked: "%DEST%"
goto :eof
:skip_unlink_file
echo [SKIP] plain file, left untouched: "%DEST%"
set /a N_SKIPPED+=1 >nul
goto :eof
:skip_unlink_dir
echo [SKIP] real directory, left untouched: "%DEST%"
set /a N_SKIPPED+=1 >nul
goto :eof
:skip_unlink_other
echo [SKIP] link points outside this repository, left untouched: "%DEST%" -^> "%CURTARGET%"
set /a N_SKIPPED+=1 >nul
goto :eof
:fail_target
echo [FAIL] cannot create target directory "%TARGET%"
set /a N_FAIL+=1 >nul
goto summary
:fail_remove
echo [FAIL] cannot remove the existing entry "%DEST%"
set /a N_FAIL+=1 >nul
goto :eof
:fail_link
echo [FAIL] cannot create the link "%DEST%"
echo try /c (copy mode); network paths only work with copy mode.
set /a N_FAIL+=1 >nul
goto :eof
:fail_copy
echo [FAIL] copy failed: "%DEST%"
set /a N_FAIL+=1 >nul
goto :eof
rem ------------------------------ help ------------------------------
:show_usage
echo(
echo AgentSkills skills linker for Windows - the counterpart of skills_linker.sh
echo(
echo usage: %~nx0 [options]
echo(
echo /l /link link mode (default): junction, symlink as fallback
echo /c /copy copy mode: place real copies (use when links are unusable)
echo /u /unlink unlink mode: remove only links pointing into this repo
echo /t /target DIR target directory (default %%USERPROFILE%%\.agents\skills)
echo /n /dry-run dry run: print the plan, write and delete nothing
echo /? /help show this help
echo(
echo notes:
echo * repo root = the directory holding this script; it scans the two levels
echo "category\skill" below it. Directories starting with a dot are skipped.
echo * already linked to the same skill -^> skip; linked elsewhere -^> replace;
echo a real directory with the same name -^> skip and warn (never overwritten).
echo * duplicate skill names keep the first category scanned and count as warnings.
echo * a double click waits for a key at the end; calls with arguments or
echo redirected input never wait, so the script stays automation friendly.
echo * exit codes: 0 all good, 1 failures, 2 warnings (name conflict or duplicate).
goto :eof
Executable → Regular
View File
File mode changed.