Skip to content

Commit bdc911b

Browse files
authored
Merge pull request #164 from devsapp/feat/skill-install-command
feat: add skill install/update command
2 parents f9c82db + c7e179d commit bdc911b

20 files changed

Lines changed: 753 additions & 21 deletions

File tree

.agents/skills/s-fc3/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ npm i -g @serverless-devs/s
1111
s config add # 配阿里云 AccessKey
1212
```
1313

14+
## 安装本 Skill
15+
16+
`fc3` 组件内置了 `skill` 命令,可一键把本 skill 安装到主流 Agent 工具的 skills 目录:
17+
18+
```bash
19+
# 安装到全部支持的工具(用户级,默认):claude / codex / cursor / qoder / agents
20+
s cli fc3 skill install
21+
22+
# 只装指定工具
23+
s cli fc3 skill install --tools claude,codex
24+
25+
# 装到当前项目(写入 ./.claude/skills 等)
26+
s cli fc3 skill install --project
27+
28+
# 覆盖更新已安装版本
29+
s cli fc3 skill update
30+
```
31+
32+
安装位置统一为 `<工具目录>/skills/s-fc3/`(全局取用户主目录,`--project` 取当前目录)。`install` 遇到已存在目标会跳过(除非 `--force`),`update` 始终覆盖。
33+
1434
## 使用示例
1535

1636
以下对话来自与 Agent 的真实交互(杭州区域,`access: default`):
@@ -121,6 +141,7 @@ Agent 以表格呈现:运行时、CPU/内存/磁盘、超时、代码大小、
121141
| `s session` | 会话管理(创建/列表/更新/删除) |
122142
| `s sync` | 线上配置拉到本地 |
123143
| `s2tos3` | FC2 格式转 FC3 |
144+
| `s skill` | 把本 skill 安装/更新到主流 Agent 工具目录(本地操作,无需凭证) |
124145
| fc3-domain | 自定义域名(多函数路由、HTTPS、TLS、WAF) |
125146

126147
详细用法见 [SKILL.md](./SKILL.md)

.agents/skills/s-fc3/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ npm i -g @serverless-devs/s
3434
s config add
3535
```
3636

37+
### 安装本 Skill 到 Agent 工具
38+
39+
`fc3` 组件自带 `skill` 命令,可把本 skill 安装到主流工具(claude/codex/cursor/qoder/agents)的 `skills/s-fc3/` 目录:
40+
41+
```bash
42+
s cli fc3 skill install # 全部工具,用户级(默认)
43+
s cli fc3 skill install --tools claude,codex # 指定工具
44+
s cli fc3 skill install --project # 装到当前项目
45+
s cli fc3 skill update # 覆盖更新
46+
```
47+
48+
`install` 已存在则跳过(`--force` 覆盖);`update` 始终覆盖。
49+
3750
### 非默认 FC API 接入点
3851

3952
- 使用 `FC_CLIENT_CUSTOM_ENDPOINT`

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ FC3 is the Serverless Devs component for Alibaba Cloud Function Compute 3.0, pro
4040

4141
## Testing
4242

43-
**Current Status**: 986 tests total, 986 passing, 2 skipped (integration tests require cloud credentials)
43+
**Current Status**: 1138 tests total, 1136 passing, 2 skipped (integration tests require cloud credentials)
4444

4545
**Run tests**: `npm test`
4646
**Coverage**: Run with `--coverage` flag
@@ -64,6 +64,7 @@ See `docs/architecture.md` for detailed diagrams.
6464
- ProvisionConfig/ScalingConfig array handling
6565
- LLM metrics in logConfig
6666
- Logs command: multi-topic search (FCLogs + FCInstanceEvents) for --instance-id, SLS field-specific query syntax
67+
- `skill` command: install/update the bundled `s-fc3` skill into agent tools (claude/codex/cursor/qoder/agents), global or project scope; local operation, no credentials
6768

6869
## Development Workflow
6970

__tests__/e2e/ci-mac-linux.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
set -e
88
set -v
99

10+
echo " ********* skill install/update (offline) *********"
11+
cd skill && bash ./run && cd ..
12+
1013
if [[ $(uname -s) == "Linux" ]]; then
1114
# echo "test trigger"
1215
# cd trigger && ./run

__tests__/e2e/ci-windows.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ $ErrorActionPreference = "Stop"
66
# $env:OS="WIN"
77
# $env:PROCESSOR_ARCHITECTURE="NT"
88

9+
Write-Host " ********* skill install/update (offline) *********"
10+
cd skill; ./run-windows.ps1; cd ..
11+
912
Write-Host "[PERMISSIONS_TEST] Testing nodejs permissions validation ..."
1013
cd nodejs
1114
Write-Host "[PERMISSIONS_TEST] Running permissions validation test..." -ForegroundColor Green

__tests__/e2e/ci.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
set -e
77
set -v
88

9+
echo " ********* skill install/update (offline) *********"
10+
cd skill && bash ./run && cd -
11+
912
echo "test custom-domain"
1013
cd custom-domain
1114
s deploy -y

__tests__/e2e/skill/run

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/bin/bash
2+
3+
# E2E for `fc3 skill install/update`.
4+
# Fully sandboxed and offline: installs the bundled s-fc3 skill into temp
5+
# project/home roots and asserts the files land where each tool expects them.
6+
# Needs NO cloud credentials.
7+
#
8+
# The local component build is loaded through a minimal s.yaml that points at
9+
# the repo root (component: <fc3_dir>) and is driven with `s skill ...`. We do
10+
# NOT use `s cli <abs-path> ...`: on Windows the CLI joins that absolute path
11+
# into its per-run log directory, which breaks when cwd and the repo live on
12+
# different drives. The s.yaml path mirrors how every other e2e drives the CLI.
13+
14+
set -e
15+
set -v
16+
17+
current_dir="$PWD"
18+
fc3_dir=$(dirname $(dirname $(dirname "$current_dir")))
19+
echo "fc3 root dir: $fc3_dir"
20+
21+
TOOLS="claude codex cursor qoder agents"
22+
23+
# Sandbox roots; cleaned up on exit.
24+
PROJECT_ROOT=$(mktemp -d)
25+
HOME_ROOT=$(mktemp -d)
26+
WORK_ROOT=$(mktemp -d)
27+
FILTER_ROOT=$(mktemp -d)
28+
cleanup() {
29+
rm -rf "$PROJECT_ROOT" "$HOME_ROOT" "$WORK_ROOT" "$FILTER_ROOT"
30+
}
31+
trap cleanup EXIT
32+
33+
# write_syaml <dir> — drop a minimal offline s.yaml that loads the local build.
34+
write_syaml() {
35+
cat > "$1/s.yaml" <<YAML
36+
edition: 3.0.0
37+
name: skill-e2e
38+
resources:
39+
fc3:
40+
component: '$fc3_dir'
41+
props: {}
42+
YAML
43+
}
44+
45+
# assert_file <path> — fail the script if the file is missing.
46+
assert_file() {
47+
if [ ! -f "$1" ]; then
48+
echo "ASSERT FAILED: expected file not found: $1"
49+
exit 1
50+
fi
51+
echo "OK: $1"
52+
}
53+
54+
assert_missing() {
55+
if [ -e "$1" ]; then
56+
echo "ASSERT FAILED: expected path to be gone: $1"
57+
exit 1
58+
fi
59+
echo "OK (absent): $1"
60+
}
61+
62+
skill_path() {
63+
# <root> <toolDir>
64+
echo "$1/.$2/skills/s-fc3/SKILL.md"
65+
}
66+
67+
write_syaml "$PROJECT_ROOT"
68+
write_syaml "$WORK_ROOT"
69+
write_syaml "$FILTER_ROOT"
70+
71+
echo "=== project-scope install (all tools) ==="
72+
cd "$PROJECT_ROOT"
73+
s skill install --project
74+
for t in $TOOLS; do
75+
assert_file "$(skill_path "$PROJECT_ROOT" "$t")"
76+
done
77+
78+
echo "=== install is idempotent: existing target is skipped, local edits preserved ==="
79+
MARKER="$PROJECT_ROOT/.claude/skills/s-fc3/LOCAL_MARKER"
80+
echo "keep-me" > "$MARKER"
81+
s skill install --project --tools claude
82+
assert_file "$MARKER"
83+
84+
echo "=== --force overwrites and cleans stale files ==="
85+
s skill install --project --tools claude --force
86+
assert_missing "$MARKER"
87+
assert_file "$(skill_path "$PROJECT_ROOT" claude)"
88+
89+
echo "=== update overwrites existing installations ==="
90+
echo "stale" > "$MARKER"
91+
s skill update --project --tools claude
92+
assert_missing "$MARKER"
93+
assert_file "$(skill_path "$PROJECT_ROOT" claude)"
94+
95+
echo "=== --tools filter installs only the requested tools ==="
96+
cd "$FILTER_ROOT"
97+
s skill install --project --tools codex
98+
assert_file "$(skill_path "$FILTER_ROOT" codex)"
99+
assert_missing "$FILTER_ROOT/.cursor/skills/s-fc3"
100+
101+
echo "=== global-scope install writes under the (sandboxed) home directory ==="
102+
cd "$WORK_ROOT"
103+
HOME="$HOME_ROOT" s skill install --global --tools claude,codex
104+
assert_file "$(skill_path "$HOME_ROOT" claude)"
105+
assert_file "$(skill_path "$HOME_ROOT" codex)"
106+
107+
echo "=== skill e2e passed ==="
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# E2E for `fc3 skill install/update` on Windows.
2+
# Fully sandboxed and offline; needs NO cloud credentials.
3+
#
4+
# The local component build is loaded through a minimal s.yaml that points at
5+
# the repo root (component: <fc3_dir>) and is driven with `s skill ...`. We do
6+
# NOT use `s cli <abs-path> ...`: on Windows the CLI joins that absolute path
7+
# into its per-run log directory, which breaks when cwd (the temp sandbox on
8+
# C:) and the repo (on D:) live on different drives.
9+
10+
$ErrorActionPreference = "Stop"
11+
12+
$current_dir = $PWD.Path
13+
$fc3_dir = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $current_dir))
14+
Write-Host "fc3 root dir: $fc3_dir"
15+
16+
$tools = @("claude", "codex", "cursor", "qoder", "agents")
17+
18+
$projectRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-proj-" + [System.Guid]::NewGuid().ToString("N"))
19+
$homeRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-home-" + [System.Guid]::NewGuid().ToString("N"))
20+
$workRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-work-" + [System.Guid]::NewGuid().ToString("N"))
21+
$filterRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-filter-" + [System.Guid]::NewGuid().ToString("N"))
22+
New-Item -ItemType Directory -Force -Path $projectRoot | Out-Null
23+
New-Item -ItemType Directory -Force -Path $homeRoot | Out-Null
24+
New-Item -ItemType Directory -Force -Path $workRoot | Out-Null
25+
New-Item -ItemType Directory -Force -Path $filterRoot | Out-Null
26+
27+
# Drop a minimal offline s.yaml that loads the local build. The component path
28+
# is single-quoted so backslashes stay literal in YAML.
29+
function Write-SYaml($dir) {
30+
$yaml = @"
31+
edition: 3.0.0
32+
name: skill-e2e
33+
resources:
34+
fc3:
35+
component: '$fc3_dir'
36+
props: {}
37+
"@
38+
Set-Content -Path (Join-Path $dir "s.yaml") -Value $yaml -Encoding utf8
39+
}
40+
41+
function Assert-File($p) {
42+
if (-not (Test-Path -PathType Leaf $p)) {
43+
Write-Host "ASSERT FAILED: expected file not found: $p" -ForegroundColor Red
44+
exit 1
45+
}
46+
Write-Host "OK: $p"
47+
}
48+
49+
function Assert-Missing($p) {
50+
if (Test-Path $p) {
51+
Write-Host "ASSERT FAILED: expected path to be gone: $p" -ForegroundColor Red
52+
exit 1
53+
}
54+
Write-Host "OK (absent): $p"
55+
}
56+
57+
function Skill-Path($root, $tool) {
58+
return (Join-Path $root ".$tool\skills\s-fc3\SKILL.md")
59+
}
60+
61+
Write-SYaml $projectRoot
62+
Write-SYaml $workRoot
63+
Write-SYaml $filterRoot
64+
65+
try {
66+
Write-Host "=== project-scope install (all tools) ==="
67+
Set-Location $projectRoot
68+
s skill install --project
69+
foreach ($t in $tools) { Assert-File (Skill-Path $projectRoot $t) }
70+
71+
Write-Host "=== install is idempotent: existing target is skipped ==="
72+
$marker = Join-Path $projectRoot ".claude\skills\s-fc3\LOCAL_MARKER"
73+
Set-Content -Path $marker -Value "keep-me"
74+
s skill install --project --tools claude
75+
Assert-File $marker
76+
77+
Write-Host "=== --force overwrites and cleans stale files ==="
78+
s skill install --project --tools claude --force
79+
Assert-Missing $marker
80+
Assert-File (Skill-Path $projectRoot "claude")
81+
82+
Write-Host "=== update overwrites existing installations ==="
83+
Set-Content -Path $marker -Value "stale"
84+
s skill update --project --tools claude
85+
Assert-Missing $marker
86+
Assert-File (Skill-Path $projectRoot "claude")
87+
88+
Write-Host "=== --tools filter installs only the requested tools ==="
89+
Set-Location $filterRoot
90+
s skill install --project --tools codex
91+
Assert-File (Skill-Path $filterRoot "codex")
92+
Assert-Missing (Join-Path $filterRoot ".cursor\skills\s-fc3")
93+
94+
Write-Host "=== global-scope install writes under the sandboxed home directory ==="
95+
Set-Location $workRoot
96+
$oldHome = $env:USERPROFILE
97+
try {
98+
$env:USERPROFILE = $homeRoot
99+
s skill install --global --tools "claude,codex"
100+
} finally {
101+
$env:USERPROFILE = $oldHome
102+
}
103+
Assert-File (Skill-Path $homeRoot "claude")
104+
Assert-File (Skill-Path $homeRoot "codex")
105+
106+
Write-Host "=== skill e2e passed ==="
107+
} finally {
108+
# Restore cwd to where this script started (the `skill` dir) so the caller's
109+
# `cd ..` returns to the e2e root. Restoring to $fc3_dir instead would leave
110+
# the caller one level too high. $current_dir is outside the temp sandboxes,
111+
# so it is safe to sit here while they are removed.
112+
Set-Location $current_dir
113+
Remove-Item -Recurse -Force $projectRoot -ErrorAction SilentlyContinue
114+
Remove-Item -Recurse -Force $homeRoot -ErrorAction SilentlyContinue
115+
Remove-Item -Recurse -Force $workRoot -ErrorAction SilentlyContinue
116+
Remove-Item -Recurse -Force $filterRoot -ErrorAction SilentlyContinue
117+
}

0 commit comments

Comments
 (0)