name: fmode-storage description: "把二进制大文件(图片/音频/视频/HTML 报告)上传到对象存储并拿到公开分享链接,本地零长期占用。默认通道免配置密钥:只要有登录 sessionToken,平台云函数就签发上传地址(对象键 user/<你的id>/...,公网 https://s3.fmode.cn/)。适用:(1) 报告/课件发布即分享 (2) 图片/音视频素材托管 (3) 批量上传+公开读 (4) 需要公开 URL 供转发或嵌入。" description_en: "Upload binary files (images/audio/video/HTML reports) to object storage and get public share URLs, keeping local disk clean. The default channel needs no credentials: with a login sessionToken the platform cloud function issues an upload URL (object key user//..., public https://s3.fmode.cn/). Use for publishing reports/courseware, hosting media assets, batch upload with public-read, or any scenario needing public URLs." schema_version: 0.2.0 level: system category: publishing keywords:
把本地二进制文件交给对象存储,生成公开 URL 供分享、转发或嵌入。
设计原则:本地零长期占用;公开即所得;凭据零入库——密钥永不落盘、永不进日志、永不进对话;诚实失败——通道全失败时打印初始化向导并 exit 2,绝不伪装成功。
不需要申请 AK/SK、不需要 init。只要容器里有登录态 sessionToken(数字生命容器默认就有),直接:
node <skill_dir>/scripts/uploader.mjs put ./report.html --key report/202609/report.html
# → { "ok": true, "key": "user/<你的id>/report/202609/report.html",
# "url": "https://s3.fmode.cn/user/<你的id>/report/202609/report.html",
# "via": "level0:cloudfunc(AlP56LCKFm)" }
自检(验证这条通道是否可用):
node <skill_dir>/scripts/uploader.mjs test
# → { "ok": true, "via": "level0:cloudfunc(...)", "message": "云函数免密钥通道自检成功(本机无需任何 AK/SK)" }
--key给相对路径即可,脚本/云函数会强制挂到user/<你的id>/前缀下——写不进别人的前缀。 省略--key则自动生成user/<你的id>/report/<YYYYMM>/<uuid>-<文件名>。 上传即public-read,不需要再调setacl。
┌─ 第0级【首选·免密钥】sessionToken → 云函数 fmodeagent-upload-url 签发预签名 PUT
│ → AK/SK 由云函数保管,容器只拿 5 分钟有效的上传地址。无需任何本地配置。
│ 第1级 环境变量 OBS_AK/OBS_SK(/OBS_ENDPOINT/OBS_BUCKET)
│ 第2级 obsutil config(OBSUTIL_CONFIG 或 ~/.obsutilconfig)
│ 第3级 sessionToken + storageProjectId → POST /api/apig/deploy/huaweicloud
│ (项目级 STS,作用域 obs://nova-cloud/dev/<projectId>/,非个人空间)
│ 第4级 项目级 ./.fmode/config.json(obsBucket/obsEndpoint/cdnDomain)
│ 全失败 → 打印初始化向导并 exit 2
└──────────────────────────────────────────────────────────────────────────┘
为什么第 0 级是首选:第 1/2 级要每台容器各自配 OBS 密钥、第 3 级要 storageProjectId 且作用域是项目目录、第 4 级只有 bucket 名没有凭据——都无法做到"开箱即用"。第 0 级只依赖登录态,是唯一零配置通道。
第 1–4 级作为兜底保留(例如需要写 obs://www-fmode.cn/ 之类平台桶,而不是个人空间)。
put 后把 URL 发出putput 完成后可删本地原件node uploader.mjs test # 自检(优先走第0级)
node uploader.mjs put <file> --key <相对key> # 上传并返回公开 URL
node uploader.mjs put <file> # 省略 --key:自动命名
node uploader.mjs put <file> --key k --ns report
node uploader.mjs setacl --key <key> # 第0级下上传即 public-read,无需调用
node uploader.mjs config # 诊断(输出绝不含密钥本体)
node uploader.mjs init --ak .. --sk .. # 仅第1/2级兜底场景需要
import { putViaCloudFunction } from './scripts/uploader.mjs';
const r = await putViaCloudFunction('/path/report.html', 'report/202609/report.html', 'report');
// r = { ok, key, url, bytes, via }
见仓库根 README.md(多工具安装:FmodeCode / Claude Code / Codex / Gemini CLI / WorkBuddy / FmodeAgent / Hermes Agent + 完整 changelog)。
user/<自己的id>/ 前缀(云函数在服务端强制)