feat(install): align skill dirs with npm and add OpenClaw

This commit is contained in:
tianlei.qjb committed 2026-04-07 16:02:05 +08:00
1 parent afdd47b5a5
commit ec6a27635b
9 files changed
+271 -44

No files matched your search

+1
View File
@@ -52,6 +52,7 @@ __KEG_ONLY_LINE__
Pathname.new(File.join(Dir.home, ".amp/skills/dws")),
Pathname.new(File.join(Dir.home, ".kiro/skills/dws")),
Pathname.new(File.join(Dir.home, ".trae/skills/dws")),
Pathname.new(File.join(Dir.home, ".openclaw/skills/dws")),
]
targets.each_with_index do |dest, index|
+2
View File
@@ -7,6 +7,7 @@ const os = require("os");
const path = require("path");
const childProcess = require("child_process");
// Canonical list: keep scripts/install.sh, scripts/install.ps1, scripts/install-skills.sh in sync.
const AGENT_DIRS = [
".agents/skills",
".claude/skills",
@@ -20,6 +21,7 @@ const AGENT_DIRS = [
".amp/skills",
".kiro/skills",
".trae/skills",
".openclaw/skills",
];
const PLATFORM_MAP = {
+1
View File
@@ -35,6 +35,7 @@ var knownSkillDirs = []string{
".amp/skills",
".kiro/skills",
".trae/skills",
".openclaw/skills",
}
// skillDirBlacklist contains parent directories whose skills are managed by
+108 -26
View File
@@ -1,21 +1,23 @@
#!/bin/sh
set -eu
# Install DWS agent skills from GitHub into detected agent directories.
# Install DWS agent skills from GitHub Releases into agent skill directories.
# Usage:
# curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-skills.sh | sh
#
# The script downloads the dws-skills.zip release asset from GitHub Releases
# and copies it into every detected agent skills directory in the current
# project.
# Downloads dws-skills.zip from GitHub Releases and copies it under each target
# path using the same rules as build/npm/install.js installSkillsToHomes
# (AGENT_DIRS + parent-directory gate), with root defaulting to the current
# directory. Set DWS_SKILLS_ROOT=$HOME to match npm install layout exactly.
#
# Environment variables (optional):
# DWS_VERSION — release tag (default: latest)
# DWS_SKILLS_ROOT — base path for agent dirs (default: $PWD)
REPO="DingTalk-Real-AI/dingtalk-workspace-cli"
VERSION="${DWS_VERSION:-latest}"
SKILL_NAME="dws"
# ── Agent directory to install skills into ───────────────────────────────────
# Only install to .agents/skills — most agents can fall back to this directory.
AGENT_DIR=".agents/skills"
ROOT="${DWS_SKILLS_ROOT:-$PWD}"
# ── Helpers ──────────────────────────────────────────────────────────────────
@@ -51,14 +53,107 @@ extract_zip() {
exit 1
}
# One-line summary copy (2nd+ targets).
_copy_skill_summary() {
_src="$1"
_dest="$2"
_label="$3"
if [ -d "$_dest" ]; then
rm -rf "$_dest"
fi
mkdir -p "$_dest"
cp -R "$_src/"* "$_dest/" 2>/dev/null || cp -r "$_src/"* "$_dest/"
file_count="$(find "$_dest" -type f | wc -l | tr -d ' ')"
printf ' ✅ Skills → %s (%s files)\n' "$_label" "$file_count"
}
# Full copy with top-level listing (1st target).
_copy_skill() {
_src="$1"
_dest="$2"
_label="$3"
if [ -d "$_dest" ]; then
rm -rf "$_dest"
fi
mkdir -p "$_dest"
cp -R "$_src/"* "$_dest/" 2>/dev/null || cp -r "$_src/"* "$_dest/"
file_count="$(find "$_dest" -type f | wc -l | tr -d ' ')"
printf ' ✅ Skills → %s (%s files)\n' "$_label" "$file_count"
for entry in "$_dest"/*; do
entry_name="$(basename "$entry")"
if [ -d "$entry" ]; then
sub_count="$(find "$entry" -type f | wc -l | tr -d ' ')"
printf ' 📁 %s/ (%s files)\n' "$entry_name" "$sub_count"
else
printf ' 📄 %s\n' "$entry_name"
fi
done
}
# Same semantics as build/npm/install.js installSkillsToHomes (root = DWS_SKILLS_ROOT or PWD).
install_skills_to_root() {
skill_src="$1"
root="$2"
installed=0
idx=0
for agent_dir in \
".agents/skills" \
".claude/skills" \
".cursor/skills" \
".gemini/skills" \
".codex/skills" \
".github/skills" \
".windsurf/skills" \
".augment/skills" \
".cline/skills" \
".amp/skills" \
".kiro/skills" \
".trae/skills" \
".openclaw/skills"
do
base_dir="$root/$agent_dir"
parent_gate="$(dirname "$base_dir")"
if [ "$idx" -gt 0 ] && [ ! -e "$parent_gate" ]; then
idx=$((idx + 1))
continue
fi
dest="$base_dir/$SKILL_NAME"
if [ "$root" = "$HOME" ]; then
label="~/$agent_dir/$SKILL_NAME"
else
label="$root/$agent_dir/$SKILL_NAME"
fi
if [ "$installed" -eq 0 ]; then
_copy_skill "$skill_src" "$dest" "$label"
else
_copy_skill_summary "$skill_src" "$dest" "$label"
fi
installed=$((installed + 1))
idx=$((idx + 1))
done
if [ "$installed" -eq 0 ]; then
if [ "$root" = "$HOME" ]; then
flabel="~/.agents/skills/$SKILL_NAME"
else
flabel="$root/.agents/skills/$SKILL_NAME"
fi
_copy_skill "$skill_src" "$root/.agents/skills/$SKILL_NAME" "$flabel"
fi
}
# ── Main ─────────────────────────────────────────────────────────────────────
main() {
need_cmd curl
resolve_version
CWD="$(pwd)"
printf '\n'
printf ' ┌──────────────────────────────────────┐\n'
printf ' │ DWS Skill Installer │\n'
@@ -66,7 +161,6 @@ main() {
printf ' └──────────────────────────────────────┘\n'
printf '\n'
# Download the tarball to a temp directory
TMPDIR_WORK="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_WORK"' EXIT INT TERM
@@ -85,21 +179,9 @@ main() {
exit 1
fi
# Install to .agents/skills only
dest="$CWD/$AGENT_DIR/$SKILL_NAME"
# Remove existing installation
if [ -d "$dest" ]; then
rm -rf "$dest"
fi
# Copy skill files
mkdir -p "$dest"
cp -R "$SKILL_SRC/"* "$dest/"
file_count="$(find "$dest" -type f | wc -l | tr -d ' ')"
printf ' ✅ Universal (.agents)\n'
printf ' → %s/%s (%s files)\n' "$AGENT_DIR" "$SKILL_NAME" "$file_count"
printf '\n'
printf ' Installing under root: %s\n' "$ROOT"
install_skills_to_root "$SKILL_SRC" "$ROOT"
printf '\n'
printf ' 📖 Skill includes:\n'
+69 -8
View File
@@ -17,6 +17,8 @@
# DWS_ARCH — architecture override (amd64 or arm64)
# DWS_NO_SKILLS — set to 1 to skip skills install
# DWS_SKILLS_ONLY — set to 1 to install only skills
#
# Agent skills paths follow build/npm/install.js AGENT_DIRS (order and entries must match).
$ErrorActionPreference = "Stop"
@@ -28,8 +30,22 @@ $NoSkills = $env:DWS_NO_SKILLS -eq "1"
$SkillsOnly = $env:DWS_SKILLS_ONLY -eq "1"
$SkillName = "dws"
# Agent directory to install skills into — most agents can fall back to .agents\skills
$AgentDir = ".agents\skills"
# Agent skill base directories (same order as build/npm/install.js AGENT_DIRS).
$AgentDirs = @(
".agents\skills",
".claude\skills",
".cursor\skills",
".gemini\skills",
".codex\skills",
".github\skills",
".windsurf\skills",
".augment\skills",
".cline\skills",
".amp\skills",
".kiro\skills",
".trae\skills",
".openclaw\skills"
)
# ── Helpers ──────────────────────────────────────────────────────────────────
@@ -170,6 +186,17 @@ function Copy-SkillToDir {
}
}
function Copy-SkillToDirSummary {
param([string]$SkillSrc, [string]$Dest, [string]$Label)
if (Test-Path $Dest) {
Remove-Item -Path $Dest -Recurse -Force
}
$fileCount = Copy-DirRecursive -Source $SkillSrc -Destination $Dest
Write-Say "✅ Skills → $Label ($fileCount files)"
}
function Resolve-SourceRoot {
$scriptPath = $PSScriptRoot
if (-not $scriptPath) { return $null }
@@ -280,9 +307,45 @@ function Install-SkillsLocal {
Write-Say ""
Write-Say "📦 Installing agent skills from local source: $skillSrc"
$dest = Join-Path (Join-Path $HOME $AgentDir) $SkillName
$label = "~\$AgentDir\$SkillName"
Copy-SkillToDir -SkillSrc $skillSrc -Dest $dest -Label $label
Install-SkillsToHomes -SkillSrc $skillSrc -Root $HOME
}
function Install-SkillsToHomes {
param(
[string]$SkillSrc,
[string]$Root = $HOME
)
$installed = 0
for ($i = 0; $i -lt $AgentDirs.Count; $i++) {
$agentDir = $AgentDirs[$i]
$baseDir = Join-Path $Root $agentDir
$parentGate = Split-Path $baseDir -Parent
if ($i -gt 0 -and !(Test-Path $parentGate)) {
continue
}
$dest = Join-Path $baseDir $SkillName
if ($Root -eq $HOME) {
$label = "~\$agentDir\$SkillName"
} else {
$label = Join-Path $Root (Join-Path $agentDir $SkillName)
}
if ($installed -eq 0) {
Copy-SkillToDir -SkillSrc $SkillSrc -Dest $dest -Label $label
} else {
Copy-SkillToDirSummary -SkillSrc $SkillSrc -Dest $dest -Label $label
}
$installed++
}
if ($installed -eq 0) {
$fallback = Join-Path (Join-Path $Root ".agents\skills") $SkillName
if ($Root -eq $HOME) {
$flabel = "~\.agents\skills\$SkillName"
} else {
$flabel = Join-Path $Root (Join-Path ".agents\skills" $SkillName)
}
Copy-SkillToDir -SkillSrc $SkillSrc -Dest $fallback -Label $flabel
}
}
# ── Install Binary from Source ───────────────────────────────────────────────
@@ -359,9 +422,7 @@ function Install-Skills {
return
}
$dest = Join-Path (Join-Path $HOME $AgentDir) $SkillName
$label = "~\$AgentDir\$SkillName"
Copy-SkillToDir -SkillSrc $skillSrc -Dest $dest -Label $label
Install-SkillsToHomes -SkillSrc $skillSrc -Root $HOME
} finally {
Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
}
+78 -10
View File
@@ -14,6 +14,8 @@
# DWS_VERSION — version to install (default: latest)
# DWS_NO_SKILLS — set to 1 to skip skills install
# DWS_SKILLS_ONLY — set to 1 to install only skills (skip binary)
#
# Agent skills paths follow build/npm/install.js AGENT_DIRS (order and entries must match).
set -eu
@@ -26,10 +28,6 @@ NO_SKILLS="${DWS_NO_SKILLS:-0}"
SKILLS_ONLY="${DWS_SKILLS_ONLY:-0}"
SKILL_NAME="dws"
# ── Agent directory to install skills into ───────────────────────────────────
# Only install to .agents/skills — most agents can fall back to this directory.
AGENT_DIR=".agents/skills"
# ── Helpers ──────────────────────────────────────────────────────────────────
say() {
@@ -179,13 +177,85 @@ install_skills_local() {
say ""
say "📦 Installing agent skills from local source: ${skill_src}"
dest="$HOME/$AGENT_DIR/$SKILL_NAME"
display_path="~/$AGENT_DIR/$SKILL_NAME"
_copy_skill "$skill_src" "$dest" "$display_path"
install_skills_to_homes "$skill_src"
return 0
}
# Install skill tree into all agent homes (same rules as build/npm/install.js installSkillsToHomes).
install_skills_to_homes() {
skill_src="$1"
root="${HOME}"
installed=0
idx=0
for agent_dir in \
".agents/skills" \
".claude/skills" \
".cursor/skills" \
".gemini/skills" \
".codex/skills" \
".github/skills" \
".windsurf/skills" \
".augment/skills" \
".cline/skills" \
".amp/skills" \
".kiro/skills" \
".trae/skills" \
".openclaw/skills"
do
base_dir="$root/$agent_dir"
parent_gate="$(dirname "$base_dir")"
if [ "$idx" -gt 0 ] && [ ! -e "$parent_gate" ]; then
idx=$((idx + 1))
continue
fi
dest="$base_dir/$SKILL_NAME"
case "$root" in
"$HOME")
label="~/$agent_dir/$SKILL_NAME"
;;
*)
label="$root/$agent_dir/$SKILL_NAME"
;;
esac
if [ "$installed" -eq 0 ]; then
_copy_skill "$skill_src" "$dest" "$label"
else
_copy_skill_summary "$skill_src" "$dest" "$label"
fi
installed=$((installed + 1))
idx=$((idx + 1))
done
if [ "$installed" -eq 0 ]; then
case "$root" in
"$HOME")
flabel="~/.agents/skills/$SKILL_NAME"
;;
*)
flabel="$root/.agents/skills/$SKILL_NAME"
;;
esac
_copy_skill "$skill_src" "$root/.agents/skills/$SKILL_NAME" "$flabel"
fi
}
# One-line summary copy (used for 2nd+ agent targets).
_copy_skill_summary() {
_src="$1"
_dest="$2"
_label="$3"
if [ -d "$_dest" ]; then
rm -rf "$_dest"
fi
mkdir -p "$_dest"
cp -R "$_src/"* "$_dest/" 2>/dev/null || cp -r "$_src/"* "$_dest/"
file_count="$(find "$_dest" -type f | wc -l | tr -d ' ')"
say "✅ Skills → ${_label} (${file_count} files)"
}
# Helper: copy skill files to a destination and print details
_copy_skill() {
_src="$1"
@@ -349,9 +419,7 @@ install_skills() {
fi
fi
dest="$HOME/$AGENT_DIR/$SKILL_NAME"
display_path="~/$AGENT_DIR/$SKILL_NAME"
_copy_skill "$skill_src" "$dest" "$display_path"
install_skills_to_homes "$skill_src"
rm -rf "$tmpdir_skills"
}
@@ -36,6 +36,7 @@ HOME_AGENT_PARENTS="
.amp
.kiro
.trae
.openclaw
"
HOME_SKILL_TARGETS="
.agents/skills/dws
@@ -50,6 +51,7 @@ HOME_SKILL_TARGETS="
.amp/skills/dws
.kiro/skills/dws
.trae/skills/dws
.openclaw/skills/dws
"
cleanup() {
if command -v brew >/dev/null 2>&1; then
+9
View File
@@ -10,6 +10,7 @@ import (
var expectedHomeSkillTargets = []string{
".agents/skills/dws",
".cursor/skills/dws",
}
func TestInstallScriptSourceModeInstallsBinary(t *testing.T) {
@@ -107,6 +108,11 @@ done
mustWriteFile(t, filepath.Join(stubRoot, "make"), []byte(makeStub), 0o755)
mustWriteFile(t, filepath.Join(stubRoot, "go"), []byte("#!/bin/sh\ntrue\n"), 0o755)
// Gate for index>0 agent dirs (matches build/npm/install.js): parent must exist.
if err := os.MkdirAll(filepath.Join(fakeHome, ".cursor"), 0o755); err != nil {
t.Fatalf("MkdirAll(.cursor) error = %v", err)
}
cmd := exec.Command("sh", scriptPath)
cmd.Env = append(os.Environ(),
"HOME="+fakeHome,
@@ -147,6 +153,9 @@ func TestInstallPowerShellScriptInstallsToAgentsDir(t *testing.T) {
if !strings.Contains(text, ".agents\\skills") {
t.Fatalf("install.ps1 missing .agents\\skills")
}
if !strings.Contains(text, ".cursor\\skills") {
t.Fatalf("install.ps1 missing .cursor\\skills (AGENT_DIRS must match build/npm/install.js)")
}
}
func TestInstallScriptsUseGitHubReleaseSkillsAsset(t *testing.T) {
+1
View File
@@ -22,6 +22,7 @@ var expectedPackagedSkillTargets = []string{
".amp/skills/dws",
".kiro/skills/dws",
".trae/skills/dws",
".openclaw/skills/dws",
}
// seedDistArtifacts creates fake goreleaser output archives (empty tar.gz/zip