# S3 Upload

> S3-compatible object storage CLI (upload, list, find, download, delete, public URL) via scripts/s3-cli.js with env-backed config aligned to this repo. Use when uploading files to S3/Kodo/MinIO, listing or searching objects by prefix, verifying S3 env, or when the user mentions /s3-upload, s3-cli, or object storage operations.

- Skill: `liuhuapiaoyuan/s3-upload` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add liuhuapiaoyuan/s3-upload`
- Raw SKILL.md: https://api.skillmd.com/api/skills/liuhuapiaoyuan/s3-upload/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: liuhuapiaoyuan (https://skillmd.com/u/liuhuapiaoyuan)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/liuhuapiaoyuan/s3-upload

---


# S3 Upload

通过 `scripts/s3-cli.js` 操作 S3 兼容存储（七牛 Kodo、MinIO、AWS S3 等）。配置来自环境变量，与 `src/app-agent/lib/server/s3-storage.ts` 及 `.env-template` 一致。

## Agent 工作流

1. **先检查配置** — 未配置时不要猜测密钥，提示用户补全 `.env`：
   ```bash
   node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js config
   ```
2. **选命令** — 上传用 `upload`；浏览用 `list`；按名称搜用 `find`；取公网链用 `url`。
3. **优先 `--json`** — 需要解析结果给后续步骤时用 JSON 输出。
4. **从仓库根目录执行** — 依赖根目录 `node_modules` 中的 `@aws-sdk/client-s3`。
5. **删除需 `--yes`** — 避免误删；向用户确认后再删。
6. **失败时必须给出正确反馈** — 见下方「错误处理」；不要编造 URL 或假装上传成功。

## 错误处理（Agent 必遵）

CLI 失败时 **exit code = 1**。加 `--json` 时 stdout 为结构化 JSON（含 `code`、`error`、`hint`），Agent **必须解析后再回复用户**。

### 回复用户格式

失败时按此结构告知用户（用中文，不要只贴原始 stack）：

1. **发生了什么** — 引用 `error` 字段
2. **可能原因** — 结合 `code` 与上下文
3. **下一步** — 引用 `hint`；若可自动修复（如 `--no-acl`），先尝试再汇报

**禁止：** 忽略 exit code；把 stderr 当成功；未验证就回复公网 URL；向用户展示完整 Secret Key。

### 错误码对照

| `code` | 含义 | Agent 动作 |
|--------|------|------------|
| `CONFIG_MISSING` | 缺环境变量 | 列出 `missing`，指向 `.env-template`，请用户补全后重跑 `config` |
| `INVALID_CREDENTIALS` | 密钥错误 | 请用户核对 AK/SK，**不要**重复用同一错误密钥盲试 |
| `ACCESS_DENIED` | 无权限 | 说明权限不足；upload 时若 hint 提到 `--no-acl`，自动重试一次 |
| `ACL_NOT_SUPPORTED` | 不支持 ACL | 用 `--no-acl` 重试 upload |
| `BUCKET_NOT_FOUND` | 桶不存在 | 检查 `S3_BUCKET_NAME` 与 `S3_ENDPOINT` 是否匹配 |
| `OBJECT_NOT_FOUND` | 对象不存在 | 建议 `list --prefix` / `find` 查正确 key |
| `LOCAL_FILE_NOT_FOUND` | 本地文件不存在 | 检查路径、cwd 是否在仓库根目录 |
| `DELETE_REQUIRES_CONFIRM` | 未加 `--yes` | 向用户确认后再加 `--yes` |
| `USAGE_ERROR` | 参数缺失 | 按 hint 修正命令，不要猜参数 |
| `NETWORK_ERROR` | 网络/端点不可达 | 检查 endpoint、region、代理 |
| `DEPENDENCY_MISSING` | 缺 SDK | 在仓库根目录 `bun install` 后重试 |
| `S3_ERROR` | 其他 S3 错误 | 汇报 `error` + `details`/`httpStatus`，建议跑 `config` |

### 诊断流程

```
命令失败 (exit 1)
  ├─ 加 --json 重跑，读取 code
  ├─ CONFIG_* / INVALID_CREDENTIALS → config
  ├─ OBJECT_NOT_FOUND → list/find 定位 key
  ├─ ACCESS_DENIED + upload → 试 --no-acl
  └─ 仍失败 → 原样汇报 error/hint/details，不要擅自改密钥
```

### 示例：向用户反馈

**配置缺失：**
> S3 未配置完整，缺少 `S3_SECRET_ACCESS_KEY`。请在项目根目录 `.env` 中补全（参考 `.env-template`），保存后我再验证。

**上传 ACL 失败（Agent 应先重试）：**
> 存储不支持 ACL，已改用 `--no-acl` 重新上传并成功。公网 URL: …

**对象不存在：**
> 桶内没有 `assets/demo/missing.png`。当前 `assets/demo/` 下共有 3 个文件：…

## 快速开始

```bash
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js --help
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js config
```

上传并拿到公网 URL：

```bash
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js upload ./local.png --prefix assets/demo/
```

列出 / 查找：

```bash
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js list --prefix uploads/ --max 50
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js find "photo" --prefix assets/
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js find "*.png" --prefix assets/
```

下载 / 删除 / URL：

```bash
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js download assets/demo/photo.png --output ./photo.png
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js url assets/demo/photo.png
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js delete assets/demo/old.png --yes
```

## 环境变量

| 变量 | 必填 | 说明 |
|------|------|------|
| `S3_BUCKET_NAME` | 是 | 存储桶 |
| `S3_ACCESS_KEY_ID` | 是 | Access Key |
| `S3_SECRET_ACCESS_KEY` | 是 | Secret Key |
| `S3_ENDPOINT` | 是 | 服务端点（如 `https://s3.cn-south-1.qiniucs.com`） |
| `S3_REGION` | 否 | 默认 `cn-south-1`（七牛）；阿里云填 `cn-hangzhou` 等 |
| `S3_CDN` | 否 | CDN 域名；`url` / 上传结果优先用 CDN 拼公网 URL |
| `S3_PROVIDER` | 否 | 提供商标识（`kodo` / `aliyun` / `minio` 等）；`aliyun` 时 upload 默认不带 ACL |

CLI 会自动读取仓库根目录 `.env`（不覆盖已有 `process.env`）。也可显式传 `node --env-file-if-exists=.env`。

**禁止** 把密钥写入 skill、脚本或提交到 git。只读 `.env` 或用户提供的 env。

### 阿里云 OSS（`S3_PROVIDER=aliyun`）

```env
S3_BUCKET_NAME=<bucket>
S3_ACCESS_KEY_ID=<RAM AccessKey ID>
S3_SECRET_ACCESS_KEY=<RAM AccessKey Secret>
S3_ENDPOINT=https://<bucket>.s3.oss-<region>.aliyuncs.com
S3_REGION=cn-hangzhou
S3_CDN=https://<your-cdn-domain>
S3_PROVIDER=aliyun
```

要点：

- **Endpoint** — 推荐 S3 兼容格式：`https://<bucket>.s3.oss-cn-hangzhou.aliyuncs.com`（与 `S3_BUCKET_NAME` 一致）；亦可用 `https://s3.oss-cn-hangzhou.aliyuncs.com`（path-style）。CLI 会自动识别 endpoint 是否已含 bucket 子域。
- **Region** — 与 endpoint 中的地域一致（如 `cn-hangzhou`），不要用占位符 `<your-region>`。
- **ACL** — 新版 OSS 桶通常禁用 Object ACL；CLI 在 `aliyun` 下 **默认不上传 ACL**。若仍报 ACL 错，显式加 `--no-acl`。
- **公网 URL** — 配置了 `S3_CDN` 时：`https://cdn.example.com/<objectKey>`；需确保 CDN 已绑定该桶并开启回源。
- **RAM 权限** — 密钥需有目标桶的 `oss:PutObject`、`oss:GetObject`、`oss:ListObjects`、`oss:DeleteObject` 等。

## 命令参考

| 命令 | 用途 |
|------|------|
| `config` | 验证配置，密钥脱敏输出 |
| `upload <file>` | 上传；`--key` / `--prefix` / `--content-type` / `--acl` / `--no-acl` |
| `list` | 按 `--prefix` 列出；`--max` 限制条数 |
| `find <pattern>` | key 子串或 `*` `?` 通配；配合 `--prefix` |
| `head <key>` | 是否存在及元数据 |
| `download <key>` | 下载；`--output` 指定路径 |
| `delete <key> --yes` | 删除对象 |
| `url <key>` | 输出公网 URL |

全局：`--json`、`-h` / `--help`。

## 与应用代码的关系

- 服务端上传逻辑见 `src/app-agent/lib/server/s3-storage.ts`（Presigned POST、CDN URL 解析等）。
- **Agent 运维/一次性上传** 用本 skill 的 CLI，不要在对话里重写 SDK 逻辑。
- **应用内功能** 继续用 `s3-storage.ts` 或现有 OSS 管道，不要混用 CLI。

## 常见场景

**用户要上传截图/构建产物并分享链接**

```bash
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js upload ./dist/bundle.zip --key releases/v1.2.0/bundle.zip --json
```

从 JSON 取 `publicUrl` 回复用户。

**用户问某前缀下有哪些文件**

```bash
node --env-file-if-exists=.env .cursor/skills/s3-upload/scripts/s3-cli.js list --prefix static/_next/ --max 100 --json
```

**用户给了 CDN URL，要确认 key 是否存在**

用 `objectKeyFromPublicUrl` 的逻辑：去掉 CDN 前缀得 key，再 `head`。

**配置报错**

缺少必填 env → CLI 返回 `CONFIG_MISSING` 及 `missing` 数组 → 对照 `.env-template` 请用户补全 → 重跑 `config` 直到 `ok: true`。

**CLI 返回 JSON 错误示例**

```json
{
  "ok": false,
  "code": "ACL_NOT_SUPPORTED",
  "error": "当前存储不支持 ACL 字段",
  "hint": "重试: upload <file> ... --no-acl",
  "details": "…"
}
```

Agent 应执行 hint 中的重试，成功后再回复用户。

**`.env` 中勿重复定义 `S3_*`**

同一文件内出现多组 `S3_BUCKET_NAME` 等变量时，后出现的会覆盖先前的（Node `--env-file` 行为）。只保留一组配置，旧配置注释掉。

