Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .agents/skills/s-fc3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ npm i -g @serverless-devs/s
s config add # 配阿里云 AccessKey
```

## 安装本 Skill

`fc3` 组件内置了 `skill` 命令,可一键把本 skill 安装到主流 Agent 工具的 skills 目录:

```bash
# 安装到全部支持的工具(用户级,默认):claude / codex / cursor / qoder / agents
s cli fc3 skill install

# 只装指定工具
s cli fc3 skill install --tools claude,codex

# 装到当前项目(写入 ./.claude/skills 等)
s cli fc3 skill install --project

# 覆盖更新已安装版本
s cli fc3 skill update
```

安装位置统一为 `<工具目录>/skills/s-fc3/`(全局取用户主目录,`--project` 取当前目录)。`install` 遇到已存在目标会跳过(除非 `--force`),`update` 始终覆盖。

## 使用示例

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

详细用法见 [SKILL.md](./SKILL.md)。
13 changes: 13 additions & 0 deletions .agents/skills/s-fc3/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ npm i -g @serverless-devs/s
s config add
```

### 安装本 Skill 到 Agent 工具

`fc3` 组件自带 `skill` 命令,可把本 skill 安装到主流工具(claude/codex/cursor/qoder/agents)的 `skills/s-fc3/` 目录:

```bash
s cli fc3 skill install # 全部工具,用户级(默认)
s cli fc3 skill install --tools claude,codex # 指定工具
s cli fc3 skill install --project # 装到当前项目
s cli fc3 skill update # 覆盖更新
```

`install` 已存在则跳过(`--force` 覆盖);`update` 始终覆盖。

### 非默认 FC API 接入点

- 使用 `FC_CLIENT_CUSTOM_ENDPOINT`。
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ FC3 is the Serverless Devs component for Alibaba Cloud Function Compute 3.0, pro

## Testing

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

**Run tests**: `npm test`
**Coverage**: Run with `--coverage` flag
Expand All @@ -64,6 +64,7 @@ See `docs/architecture.md` for detailed diagrams.
- ProvisionConfig/ScalingConfig array handling
- LLM metrics in logConfig
- Logs command: multi-topic search (FCLogs + FCInstanceEvents) for --instance-id, SLS field-specific query syntax
- `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

## Development Workflow

Expand Down
3 changes: 3 additions & 0 deletions __tests__/e2e/ci-mac-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
set -e
set -v

echo " ********* skill install/update (offline) *********"
cd skill && bash ./run && cd ..

if [[ $(uname -s) == "Linux" ]]; then
# echo "test trigger"
# cd trigger && ./run
Expand Down
3 changes: 3 additions & 0 deletions __tests__/e2e/ci-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ $ErrorActionPreference = "Stop"
# $env:OS="WIN"
# $env:PROCESSOR_ARCHITECTURE="NT"

Write-Host " ********* skill install/update (offline) *********"
cd skill; ./run-windows.ps1; cd ..

Write-Host "[PERMISSIONS_TEST] Testing nodejs permissions validation ..."
cd nodejs
Write-Host "[PERMISSIONS_TEST] Running permissions validation test..." -ForegroundColor Green
Expand Down
3 changes: 3 additions & 0 deletions __tests__/e2e/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
set -e
set -v

echo " ********* skill install/update (offline) *********"
cd skill && bash ./run && cd -

echo "test custom-domain"
cd custom-domain
s deploy -y
Expand Down
107 changes: 107 additions & 0 deletions __tests__/e2e/skill/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/bin/bash

# E2E for `fc3 skill install/update`.
# Fully sandboxed and offline: installs the bundled s-fc3 skill into temp
# project/home roots and asserts the files land where each tool expects them.
# Needs NO cloud credentials.
#
# The local component build is loaded through a minimal s.yaml that points at
# the repo root (component: <fc3_dir>) and is driven with `s skill ...`. We do
# NOT use `s cli <abs-path> ...`: on Windows the CLI joins that absolute path
# into its per-run log directory, which breaks when cwd and the repo live on
# different drives. The s.yaml path mirrors how every other e2e drives the CLI.

set -e
set -v

current_dir="$PWD"
fc3_dir=$(dirname $(dirname $(dirname "$current_dir")))
echo "fc3 root dir: $fc3_dir"

TOOLS="claude codex cursor qoder agents"

# Sandbox roots; cleaned up on exit.
PROJECT_ROOT=$(mktemp -d)
HOME_ROOT=$(mktemp -d)
WORK_ROOT=$(mktemp -d)
FILTER_ROOT=$(mktemp -d)
cleanup() {
rm -rf "$PROJECT_ROOT" "$HOME_ROOT" "$WORK_ROOT" "$FILTER_ROOT"
}
trap cleanup EXIT

# write_syaml <dir> — drop a minimal offline s.yaml that loads the local build.
write_syaml() {
cat > "$1/s.yaml" <<YAML
edition: 3.0.0
name: skill-e2e
resources:
fc3:
component: '$fc3_dir'
props: {}
YAML
}

# assert_file <path> — fail the script if the file is missing.
assert_file() {
if [ ! -f "$1" ]; then
echo "ASSERT FAILED: expected file not found: $1"
exit 1
fi
echo "OK: $1"
}

assert_missing() {
if [ -e "$1" ]; then
echo "ASSERT FAILED: expected path to be gone: $1"
exit 1
fi
echo "OK (absent): $1"
}

skill_path() {
# <root> <toolDir>
echo "$1/.$2/skills/s-fc3/SKILL.md"
}

write_syaml "$PROJECT_ROOT"
write_syaml "$WORK_ROOT"
write_syaml "$FILTER_ROOT"

echo "=== project-scope install (all tools) ==="
cd "$PROJECT_ROOT"
s skill install --project
for t in $TOOLS; do
assert_file "$(skill_path "$PROJECT_ROOT" "$t")"
done

echo "=== install is idempotent: existing target is skipped, local edits preserved ==="
MARKER="$PROJECT_ROOT/.claude/skills/s-fc3/LOCAL_MARKER"
echo "keep-me" > "$MARKER"
s skill install --project --tools claude
assert_file "$MARKER"

echo "=== --force overwrites and cleans stale files ==="
s skill install --project --tools claude --force
assert_missing "$MARKER"
assert_file "$(skill_path "$PROJECT_ROOT" claude)"

echo "=== update overwrites existing installations ==="
echo "stale" > "$MARKER"
s skill update --project --tools claude
assert_missing "$MARKER"
assert_file "$(skill_path "$PROJECT_ROOT" claude)"

echo "=== --tools filter installs only the requested tools ==="
cd "$FILTER_ROOT"
s skill install --project --tools codex
assert_file "$(skill_path "$FILTER_ROOT" codex)"
assert_missing "$FILTER_ROOT/.cursor/skills/s-fc3"

echo "=== global-scope install writes under the (sandboxed) home directory ==="
cd "$WORK_ROOT"
HOME="$HOME_ROOT" s skill install --global --tools claude,codex
assert_file "$(skill_path "$HOME_ROOT" claude)"
assert_file "$(skill_path "$HOME_ROOT" codex)"

echo "=== skill e2e passed ==="
117 changes: 117 additions & 0 deletions __tests__/e2e/skill/run-windows.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# E2E for `fc3 skill install/update` on Windows.
# Fully sandboxed and offline; needs NO cloud credentials.
#
# The local component build is loaded through a minimal s.yaml that points at
# the repo root (component: <fc3_dir>) and is driven with `s skill ...`. We do
# NOT use `s cli <abs-path> ...`: on Windows the CLI joins that absolute path
# into its per-run log directory, which breaks when cwd (the temp sandbox on
# C:) and the repo (on D:) live on different drives.

$ErrorActionPreference = "Stop"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$current_dir = $PWD.Path
$fc3_dir = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $current_dir))
Write-Host "fc3 root dir: $fc3_dir"

$tools = @("claude", "codex", "cursor", "qoder", "agents")

$projectRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-proj-" + [System.Guid]::NewGuid().ToString("N"))
$homeRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-home-" + [System.Guid]::NewGuid().ToString("N"))
$workRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-work-" + [System.Guid]::NewGuid().ToString("N"))
$filterRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-filter-" + [System.Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Force -Path $projectRoot | Out-Null
New-Item -ItemType Directory -Force -Path $homeRoot | Out-Null
New-Item -ItemType Directory -Force -Path $workRoot | Out-Null
New-Item -ItemType Directory -Force -Path $filterRoot | Out-Null

# Drop a minimal offline s.yaml that loads the local build. The component path
# is single-quoted so backslashes stay literal in YAML.
function Write-SYaml($dir) {
$yaml = @"
edition: 3.0.0
name: skill-e2e
resources:
fc3:
component: '$fc3_dir'
props: {}
"@
Set-Content -Path (Join-Path $dir "s.yaml") -Value $yaml -Encoding utf8
Comment on lines +27 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape $fc3_dir before embedding it in YAML.

A single quote in a valid Windows path is not escaped. For example, D:\O'Brien\fc3 produces invalid YAML. Replace each ' with '' before interpolation.

Proposed fix
 function Write-SYaml($dir) {
+  $yamlPath = $fc3_dir -replace "'", "''"
   $yaml = @"
...
-    component: '$fc3_dir'
+    component: '$yamlPath'
...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Drop a minimal offline s.yaml that loads the local build. The component path
# is single-quoted so backslashes stay literal in YAML.
function Write-SYaml($dir) {
$yaml = @"
edition: 3.0.0
name: skill-e2e
resources:
fc3:
component: '$fc3_dir'
props: {}
"@
Set-Content -Path (Join-Path $dir "s.yaml") -Value $yaml -Encoding utf8
# Drop a minimal offline s.yaml that loads the local build. The component path
# is single-quoted so backslashes stay literal in YAML.
function Write-SYaml($dir) {
$yamlPath = $fc3_dir -replace "'", "''"
$yaml = @"
edition: 3.0.0
name: skill-e2e
resources:
fc3:
component: '$yamlPath'
props: {}
"@
Set-Content -Path (Join-Path $dir "s.yaml") -Value $yaml -Encoding utf8
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/e2e/skill/run-windows.ps1` around lines 27 - 38, Update Write-SYaml
to escape apostrophes in fc3_dir by replacing each single quote with two single
quotes before interpolating it into the single-quoted YAML component value.
Preserve the existing path and YAML structure for paths without apostrophes.

}

function Assert-File($p) {
if (-not (Test-Path -PathType Leaf $p)) {
Write-Host "ASSERT FAILED: expected file not found: $p" -ForegroundColor Red
exit 1
}
Write-Host "OK: $p"
}

function Assert-Missing($p) {
if (Test-Path $p) {
Write-Host "ASSERT FAILED: expected path to be gone: $p" -ForegroundColor Red
exit 1
}
Write-Host "OK (absent): $p"
}

function Skill-Path($root, $tool) {
return (Join-Path $root ".$tool\skills\s-fc3\SKILL.md")
}

Write-SYaml $projectRoot
Write-SYaml $workRoot
Write-SYaml $filterRoot

try {
Write-Host "=== project-scope install (all tools) ==="
Set-Location $projectRoot
s skill install --project
foreach ($t in $tools) { Assert-File (Skill-Path $projectRoot $t) }

Write-Host "=== install is idempotent: existing target is skipped ==="
$marker = Join-Path $projectRoot ".claude\skills\s-fc3\LOCAL_MARKER"
Set-Content -Path $marker -Value "keep-me"
s skill install --project --tools claude
Assert-File $marker

Write-Host "=== --force overwrites and cleans stale files ==="
s skill install --project --tools claude --force
Assert-Missing $marker
Assert-File (Skill-Path $projectRoot "claude")

Write-Host "=== update overwrites existing installations ==="
Set-Content -Path $marker -Value "stale"
s skill update --project --tools claude
Assert-Missing $marker
Assert-File (Skill-Path $projectRoot "claude")

Write-Host "=== --tools filter installs only the requested tools ==="
Set-Location $filterRoot
s skill install --project --tools codex
Assert-File (Skill-Path $filterRoot "codex")
Assert-Missing (Join-Path $filterRoot ".cursor\skills\s-fc3")
Comment on lines +88 to +92

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that every non-selected tool is absent.

The test proves only that codex is installed and cursor is absent. It does not detect unexpected installations of claude, qoder, or agents. Check every tool except codex.

Proposed assertion loop
-  Assert-Missing (Join-Path $filterRoot ".cursor\skills\s-fc3")
+  foreach ($t in $tools) {
+    if ($t -ne "codex") {
+      Assert-Missing (Join-Path $filterRoot ".$t\skills\s-fc3")
+    }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Write-Host "=== --tools filter installs only the requested tools ==="
Set-Location $filterRoot
s skill install --project --tools codex
Assert-File (Skill-Path $filterRoot "codex")
Assert-Missing (Join-Path $filterRoot ".cursor\skills\s-fc3")
Write-Host "=== --tools filter installs only the requested tools ==="
Set-Location $filterRoot
s skill install --project --tools codex
Assert-File (Skill-Path $filterRoot "codex")
foreach ($t in $tools) {
if ($t -ne "codex") {
Assert-Missing (Join-Path $filterRoot ".$t\skills\s-fc3")
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/e2e/skill/run-windows.ps1` around lines 88 - 92, Expand the --tools
filter test near the existing codex assertions to verify that every non-selected
tool is absent: claude, cursor, qoder, and agents. Retain the positive codex
assertion and use the existing Skill-Path, Assert-File, and Assert-Missing
helpers.


Write-Host "=== global-scope install writes under the sandboxed home directory ==="
Set-Location $workRoot
$oldHome = $env:USERPROFILE
try {
$env:USERPROFILE = $homeRoot
s skill install --global --tools "claude,codex"
} finally {
$env:USERPROFILE = $oldHome
}
Assert-File (Skill-Path $homeRoot "claude")
Assert-File (Skill-Path $homeRoot "codex")

Write-Host "=== skill e2e passed ==="
} finally {
# Restore cwd to where this script started (the `skill` dir) so the caller's
# `cd ..` returns to the e2e root. Restoring to $fc3_dir instead would leave
# the caller one level too high. $current_dir is outside the temp sandboxes,
# so it is safe to sit here while they are removed.
Set-Location $current_dir
Remove-Item -Recurse -Force $projectRoot -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $homeRoot -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $workRoot -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $filterRoot -ErrorAction SilentlyContinue
}
Loading
Loading