feat(install): align skill dirs with npm and add OpenClaw
This commit is contained in:
1 parent
afdd47b5a5
commit
ec6a27635b
9 files changed
+271
-44
No files matched your search
@@ -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|
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user