feat: support registry collections and docker publishing

This commit is contained in:
2026-07-27 23:25:46 +08:00
parent b78126830b
commit 983183d5b3
13 changed files with 891 additions and 53 deletions
+64
View File
@@ -0,0 +1,64 @@
---
name: publish-docker-image
description: >-
构建当前项目的 Docker 镜像,并将其上传到用户指定的镜像仓库。仅当用户显式指定
$publish-docker-image 或明确说“使用 publish-docker-image skill”时使用;
不要因普通编码、编辑 Dockerfile、本地构建、测试或一般 Docker 问题而自动触发。
---
# 发布 Docker 镜像
安全、可复现地构建当前提交对应的 Docker 镜像,并按用户指定的目标上传。
## 执行流程
1. 读取项目的 `AGENTS.md`、Dockerfile、构建脚本和相关发布文档。
2. 收集目标 registry、repository、tag、platform、构建上下文和 Dockerfile。优先使用用户已明确提供的值;缺少会改变发布结果的值时,停止并询问。
3. 检查 Git 工作区与当前提交。若存在未提交修改,明确说明镜像将包含哪些修改。
4. 按 [registry.md](references/registry.md) 检查仓库规则和认证状态。
5. 在执行外部写操作前,向用户展示完整镜像引用、platform、Dockerfile、构建上下文和源 commit。只有用户已明确要求上传到该目标时才继续。
6. 使用 [publish.sh](scripts/publish.sh) 构建并上传。不要自行拼接包含凭据的命令。
7. 检查命令退出状态,并尽可能获取远端 digest。
8. 汇报完整镜像引用、digest、platform、源 commit,以及是否包含未提交修改。
## 命令
默认构建并上传:
```bash
scripts/publish.sh \
--registry REGISTRY \
--repository NAMESPACE/IMAGE \
--tag TAG \
--platform PLATFORM
```
先验证而不构建或上传:
```bash
scripts/publish.sh \
--registry REGISTRY \
--repository NAMESPACE/IMAGE \
--tag TAG \
--platform PLATFORM \
--dry-run
```
仅当用户明确要求本地构建时使用 `--load`。多平台镜像不能使用 `--load`
## 安全边界
- 不把密码、访问令牌或 Docker 配置写入 skill、项目文件、命令参数或输出。
- 不主动执行 `docker login`;认证缺失时让用户通过交互式登录或其凭据管理器完成。
- 不覆盖已存在的 release tag,除非用户明确授权。无法可靠检查远端 tag 时说明这一限制。
- 不把 `latest` 作为隐含默认 tag。
- 不上传用户未指定的附加 tag。
- 不擅自修改 Dockerfile、发布配置、仓库权限或镜像保留策略。
- 若仓库、tag、platform 或目标环境存在歧义,在上传前询问用户。
## 验证
- 确认 `docker buildx build` 成功且启用了 `--push`
- 优先用 `docker buildx imagetools inspect FULL_IMAGE_REF` 验证远端引用及平台。
- 记录远端 digest;若仓库不允许检查,明确报告只验证了 push 命令成功。
- 将发布所用的 Git commit 与工作区状态一并报告。
@@ -0,0 +1,39 @@
# 镜像仓库规则
执行发布前,从用户输入和当前项目文档中确定以下信息:
| 字段 | 要求 |
| --- | --- |
| Registry | 必须显式确定,例如 `registry.example.com` |
| Repository | 必须包含项目约定的 namespace,例如 `team/service` |
| Tag | 必须显式确定;优先使用版本号或 Git SHA |
| Platform | 必须显式确定,例如 `linux/amd64``linux/amd64,linux/arm64` |
| Dockerfile | 默认 `Dockerfile`,不存在或项目另有约定时明确指定 |
| Context | 默认当前项目根目录 |
## 信息来源优先级
1. 用户本次请求中明确给出的值。
2. 当前项目的 `AGENTS.md` 和发布文档。
3. `Makefile`、CI 配置、Compose 文件或现有构建脚本中一致且无歧义的配置。
4. 询问用户。
不要从其他项目、shell history 或无关的本地配置中猜测发布目标。
## 认证
使用 Docker 当前配置的 credential helper 或已有登录状态。可用不泄露凭据的只读操作检查目标是否可访问。认证缺失或过期时,停止并让用户自行完成登录。
不要读取、打印或复制以下内容:
- registry 密码或访问令牌
- `~/.docker/config.json` 中的认证字段
- CI secret 的值
- 包含凭据的环境变量值
## Tag 策略
- release tag(如 `v1.2.3`)默认视为不可变。
- Git SHA tag 应对应当前源 commit。
- `latest``stable` 等浮动 tag 只有在用户明确要求时才发布。
- 用户未给 tag 且项目没有唯一明确规则时,必须询问,不要自行选择。
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
sed -n '2,22p' "$0"
}
# Build and publish a Docker image with buildx.
#
# Usage:
# publish.sh --registry HOST --repository PATH --tag TAG --platform PLATFORMS [options]
#
# Options:
# --registry HOST Registry host, without a URL scheme
# --repository PATH Repository path, such as team/service
# --tag TAG Image tag
# --platform LIST Comma-separated platforms
# --file PATH Dockerfile path (default: Dockerfile)
# --context PATH Build context (default: .)
# --builder NAME Existing buildx builder
# --load Load a single-platform image instead of pushing
# --dry-run Print the resolved build without executing it
# --help Show this help
registry=
repository=
tag=
platform=
dockerfile=Dockerfile
build_context=.
builder=
mode=push
dry_run=false
while (($#)); do
case "$1" in
--registry) registry=${2-}; shift 2 ;;
--repository) repository=${2-}; shift 2 ;;
--tag) tag=${2-}; shift 2 ;;
--platform) platform=${2-}; shift 2 ;;
--file) dockerfile=${2-}; shift 2 ;;
--context) build_context=${2-}; shift 2 ;;
--builder) builder=${2-}; shift 2 ;;
--load) mode=load; shift ;;
--dry-run) dry_run=true; shift ;;
--help|-h) usage; exit 0 ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; usage >&2; exit 2 ;;
esac
done
for required_name in registry repository tag platform; do
if [[ -z ${!required_name} ]]; then
printf 'Missing required option: --%s\n' "$required_name" >&2
exit 2
fi
done
if [[ $registry == *://* || $registry == */* ]]; then
printf '%s\n' 'Registry must be a host without a URL scheme or path.' >&2
exit 2
fi
if [[ $repository == /* || $repository == */ || $repository != */* ]]; then
printf '%s\n' 'Repository must look like namespace/image.' >&2
exit 2
fi
if [[ $tag == *:* || $tag == */* ]]; then
printf '%s\n' 'Tag must not contain ":" or "/".' >&2
exit 2
fi
if [[ $mode == load && $platform == *,* ]]; then
printf '%s\n' '--load supports only one platform.' >&2
exit 2
fi
if [[ ! -f $dockerfile ]]; then
printf 'Dockerfile not found: %s\n' "$dockerfile" >&2
exit 2
fi
if [[ ! -d $build_context ]]; then
printf 'Build context not found: %s\n' "$build_context" >&2
exit 2
fi
if [[ $dry_run == false ]] && ! command -v docker >/dev/null 2>&1; then
printf '%s\n' 'docker is not installed or not available in PATH.' >&2
exit 127
fi
image_ref="${registry}/${repository}:${tag}"
build_cmd=(docker buildx build --file "$dockerfile" --platform "$platform" --tag "$image_ref")
if [[ -n $builder ]]; then
build_cmd+=(--builder "$builder")
fi
if [[ $mode == push ]]; then
build_cmd+=(--push)
else
build_cmd+=(--load)
fi
build_cmd+=("$build_context")
printf 'Image: %s\n' "$image_ref"
printf 'Platform: %s\n' "$platform"
printf 'Dockerfile: %s\n' "$dockerfile"
printf 'Context: %s\n' "$build_context"
printf 'Mode: %s\n' "$mode"
if [[ $dry_run == true ]]; then
printf 'Command:'
printf ' %q' "${build_cmd[@]}"
printf '\n'
exit 0
fi
"${build_cmd[@]}"
if [[ $mode == push ]]; then
docker buildx imagetools inspect "$image_ref"
fi
+10
View File
@@ -106,6 +106,16 @@ skiff add discussion-notes -a cursor -g -y
skiff add discussion-notes -a cursor -a codex -g -y
```
registry 条目既可以指向单个 skill,也可以指向包含多个 skill 目录的
collection。安装 collection 全部内容或其中一个:
```bash
skiff add waza -a codex -g -y
skiff add waza/think -a codex -g -y
```
`skiff select` 会把 collection 展开为 `waza/think``waza/ui` 等候选项。
卸载:
```bash