用途
盘点店铺 CDN 库中的每一个文件(图片、视频、通用文件),并将每个文件与商品、页面和博客文章进行交叉比对,以判断其是否实际被使用。孤立文件会占用存储空间、拖慢后台搜索并掩盖品牌资产。该技能为只读操作,不执行任何变更,为手动清理或归档工作流提供数据基础。
前置条件
- 已认证的 Shopify CLI 会话:
shopify store auth --store <domain> --scopes read_files,read_products,read_content - API 权限范围:
read_files、read_products、read_content
参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| store | string | 是 | — | 店铺域名(例如 mystore.myshopify.com) |
| min_age_days | integer | 否 | 30 | 仅标记早于该天数的文件(避免标记新上传、尚在处理中的资产) |
| file_types | string | 否 | all | 过滤:IMAGE、VIDEO、GENERIC_FILE 或 all |
| sample_orphans | integer | 否 | 25 | 在人类可读格式的完成横幅中打印的孤立文件数量 |
| format | string | 否 | human | 输出格式:human 或 json |
安全性
ℹ️ 只读技能——不执行任何变更。可随时安全运行。该技能不会删除任何文件,仅生成报告。
工作流步骤
-
OPERATION: files — query
- 输入:first: 250,选择 id、alt、createdAt、fileStatus、__typename,以及各类型特有的 URL/大小字段,分页游标
- 预期输出:完整的文件清单,包含 CDN URL 和字节大小;分页直到 hasNextPage: false
-
OPERATION: products — query
- 输入:first: 250,选择 media { ... on MediaImage { image { url } id }, ... on Video { sources { url } } },分页游标
- 预期输出:被任何商品引用的文件 ID / URL 集合
-
OPERATION: pages — query
- 输入:first: 250,选择 body(用于扫描内联 <img src=...> 引用的 HTML 正文)
- 预期输出:页面正文;提取 cdn.shopify.com/... URL
-
OPERATION: articles — query
- 输入:first: 250,选择 body 和 image { url }
- 预期输出:文章正文和主图;提取被引用的文件 URL
-
交叉比对:步骤 1 中任何 id 或规范 URL 未出现在步骤 2、3、4 引用并集中的文件 → 孤立文件。
-
应用 min_age_days 过滤——从“孤立”列表中排除最近 N 天内创建的文件,以避免标记暂存/处理中的上传。
GraphQL 操作
# files:query — validated against api_version 2025-01
query FileInventory($after: String, $query: String) {
files(first: 250, after: $after, query: $query) {
edges {
node {
id
alt
createdAt
fileStatus
__typename
... on MediaImage {
image { url width height }
originalSource { fileSize }
}
... on Video {
sources { url mimeType fileSize }
}
... on GenericFile {
url
mimeType
originalFileSize
}
}
}
pageInfo { hasNextPage endCursor }
}
}
# products:query — validated against api_version 2025-01
query ProductMediaReferences($after: String) {
products(first: 250, after: $after) {
edges {
node {
id
media(first: 50) {
edges {
node {
... on MediaImage { id image { url } }
... on Video { id sources { url } }
}
}
}
}
}
pageInfo { hasNextPage endCursor }
}
}
# pages:query — validated against api_version 2025-01
query PageBodyReferences($after: String) {
pages(first: 250, after: $after) {
edges { node { id title body } }
pageInfo { hasNextPage endCursor }
}
}
# articles:query — validated against api_version 2025-01
query ArticleBodyReferences($after: String) {
articles(first: 250, after: $after) {
edges { node { id title body image { url } } }
pageInfo { hasNextPage endCursor }
}
}
会话跟踪
Claude 必须在每个阶段发出以下输出。这是强制性的。
开始时,发出:
╔══════════════════════════════════════════════╗
║ SKILL: File Storage Audit ║
║ Store: <store domain> ║
║ Started: <YYYY-MM-DD HH:MM UTC> ║
╚══════════════════════════════════════════════╝
每一步之后,发出:
[N/TOTAL] <QUERY|MUTATION> <OperationName>
→ Params: <brief summary of key inputs>
→ Result: <count or outcome>
完成时,发出:
对于 format: human(默认):
══════════════════════════════════════════════
FILE STORAGE AUDIT
Total files: <n> ( <total_size_mb> MB )
Images: <n>
Videos: <n>
Generic files: <n>
Referenced files: <n> ( <ref_size_mb> MB )
Orphaned files: <n> ( <orphan_size_mb> MB , <pct>%)
Sample orphans:
"<filename>" <size> uploaded: <YYYY-MM-DD>
Output: file_audit_<date>.csv
══════════════════════════════════════════════
对于 format: json,发出:
{
"skill": "file-storage-audit",
"store": "<domain>",
"total_files": 0,
"total_size_bytes": 0,
"referenced_files": 0,
"orphaned_files": 0,
"orphaned_size_bytes": 0,
"orphan_pct": 0,
"output_file": "file_audit_<date>.csv"
}
输出格式
CSV 文件 file_audit_<YYYY-MM-DD>.csv,包含列:
file_id, file_type, url, alt, size_bytes, created_at, age_days, is_referenced, referenced_by_count, referenced_by_sample
错误处理
| 错误 | 原因 | 恢复方式 |
|---|---|---|
| THROTTLED | API 速率限制超出 | 等待 2 秒,最多重试 3 次 |
| ACCESS_DENIED on files | 缺少 read_files 权限范围 | 添加 read_files 后重新认证 |
| File without size field | CDN 元数据仍在传播 | 将 size_bytes 视为 null;在报告中包含并注明 |
| Body URL parsing miss | 页面/文章使用主题资产路径,而非 CDN URL | 标记为 referenced_by: theme,从孤立列表中排除 |
最佳实践
- 在任何大型媒体重新上传(例如目录刷新)之前运行,以建立当前存储基线。
- 使用 min_age_days: 30 以避免标记尚未关联到商品或页面的处理中上传。
- 按 size_bytes 降序排列 CSV——少数大型视频通常主导存储成本。
- 不要直接从报告中进行批量删除。先随机抽查 10 个孤立文件;主题和邮件模板引用并不总能通过 Admin API 发现。
- 保留上个月的 CSV,并与新运行结果进行差异对比,以跟踪净存储增长。