Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问许可证需确认审计提醒

r2-storager2 存储

Agent Skill

r2-storage 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

392

周安装

16

GitHub Stars

公开资料未说明

下载量

127
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:r2-storage(r2 存储)
来源仓库:https://github.com/null-shot/cloudflare-skills
仓库路径:skills/r2-storage
安装命令:
npx skills add https://github.com/null-shot/cloudflare-skills --skill r2-storage
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/null-shot/cloudflare-skills --skill r2-storage

简介

r2-storage 用于查找、检索和筛选相关信息,适合在主流 Agent 宿主中快速定位候选结果。

  • 适用于围绕 Cloudflare R2 存储服务进行信息检索与筛选。
  • 通过 npx skills add 命令安装,需结合原始 README 确认具体用法。
  • 安装前建议确认权限范围、维护状态及是否触发联网或文件读写操作。
  • 可结合来源仓库进一步核验功能细节和使用限制。

SKILL.md

R2 Object Storage

Store and retrieve objects at scale using Cloudflare's S3-compatible object storage.

When to Use

  • File uploads (images, videos, documents)
  • AI assets and model artifacts
  • Image and media asset storage
  • Structured data (JSON, CSV, logs)
  • User-facing uploads and downloads
  • Static asset hosting
  • Backup and archival storage
  • S3-compatible workflows with existing tools

FIRST: Create R2 Bucket

# Create bucket
wrangler r2 bucket create my-bucket

# Create with location hint
wrangler r2 bucket create my-bucket --location wnam

# List buckets
wrangler r2 bucket list

Quick Reference

OperationAPI
Upload objectawait bucket.put(key, data, {httpMetadata})
Download objectconst obj = await bucket.get(key); const data = await obj.text()
Delete objectawait bucket.delete(key)
List objectsconst list = await bucket.list({prefix, limit})
Get metadataconst obj = await bucket.head(key)
Multipart uploadconst upload = await bucket.createMultipartUpload(key)
Generate signed URLUse presigned URL patterns with R2's S3 compatibility

Wrangler Configuration

// wrangler.jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-01-01",
  "r2_buckets": [
    {
      "binding": "BUCKET",
      "bucket_name": "my-bucket"
    }
  ]
}

TypeScript Types (run wrangler types to generate):

export interface Env {
  BUCKET: R2Bucket;
}

Basic Upload and Download

import { R2Bucket } from "@cloudflare/workers-types";

export interface Env {
  BUCKET: R2Bucket;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const key = url.pathname.slice(1); // Remove leading /

    // Upload
    if (request.method === "PUT") {
      await env.BUCKET.put(key, request.body, {
        httpMetadata: {
          contentType: request.headers.get("content-type") || "application/octet-stream",
        },
      });
      return new Response("Uploaded", { status: 201 });
    }

    // Download
    if (request.method === "GET") {
      const object = await env.BUCKET.get(key);

      if (!object) {
        return new Response("Not found", { status: 404 });
      }

      return new Response(object.body, {
        headers: {
          "Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
          "ETag": object.httpEtag,
          "Cache-Control": object.httpMetadata?.cacheControl || "public, max-age=3600",
        },
      });
    }

    // Delete
    if (request.method === "DELETE") {
      await env.BUCKET.delete(key);
      return new Response("Deleted", { status: 204 });
    }

    return new Response("Method not allowed", { status: 405 });
  },
};

Multipart Form Upload Handler

Handle file uploads from HTML forms or multipart requests.

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Method not allowed", { status: 405 });
    }

    const formData = await request.formData();
    const file = formData.get("file") as File;

    if (!file) {
      return new Response("No file provided", { status: 400 });
    }

    // Generate unique key
    const key = `uploads/${crypto.randomUUID()}-${file.name}`;

    // Upload to R2
    await env.BUCKET.put(key, file.stream(), {
      httpMetadata: {
        contentType: file.type,
      },
      customMetadata: {
        originalName: file.name,
        uploadedAt: new Date().toISOString(),
      },
    });

    return Response.json({
      success: true,
      key,
      url: `/files/${key}`,
    });
  },
};

List Objects with Pagination

async function listAllObjects(
  bucket: R2Bucket,
  prefix: string = ""
): Promise<R2Object[]> {
  const objects: R2Object[] = [];
  let cursor: string | undefined;

  do {
    const listed = await bucket.list({
      prefix,
      cursor,
      limit: 1000,
    });

    objects.push(...listed.objects);
    cursor = listed.truncated ? listed.cursor : undefined;
  } while (cursor);

  return objects;
}

// Usage
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const prefix = url.searchParams.get("prefix") || "";

    const objects = await listAllObjects(env.BUCKET, prefix);

    return Response.json({
      count: objects.length,
      objects: objects.map((obj) => ({
        key: obj.key,
        size: obj.size,
        uploaded: obj.uploaded,
      })),
    });
  },
};

Conditional Operations (ETags)

Use ETags for conditional reads/writes to prevent race conditions.

// Conditional write (only if not modified)
const existingObject = await env.BUCKET.head("config.json");

if (existingObject) {
  // Update only if ETag matches
  await env.BUCKET.put("config.json", newData, {
    httpMetadata: {
      contentType: "application/json",
    },
    onlyIf: {
      etagMatches: existingObject.httpEtag,
    },
  });
}

// Conditional read (If-None-Match)
const object = await env.BUCKET.get("image.jpg", {
  onlyIf: {
    etagDoesNotMatch: cachedEtag,
  },
});

if (object === null) {
  // Object not modified - return 304
  return new Response(null, {
    status: 304,
    headers: { "ETag": cachedEtag },
  });
}

Custom Metadata

Store application-specific metadata alongside objects.

// Store with custom metadata
await env.BUCKET.put("document.pdf", pdfData, {
  httpMetadata: {
    contentType: "application/pdf",
  },
  customMetadata: {
    userId: "user-123",
    documentType: "invoice",
    version: "2",
    tags: "finance,2024",
  },
});

// Read metadata without downloading body
const object = await env.BUCKET.head("document.pdf");
console.log(object.customMetadata?.userId); // "user-123"

Range Requests (Partial Downloads)

Efficiently download portions of large files.

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const key = new URL(request.url).pathname.slice(1);
    const rangeHeader = request.headers.get("range");

    if (rangeHeader) {
      // Parse range: "bytes=0-1023"
      const match = rangeHeader.match(/bytes=(\d+)-(\d*)/);
      if (match) {
        const start = parseInt(match[1], 10);
        const end = match[2] ? parseInt(match[2], 10) : undefined;

        const object = await env.BUCKET.get(key, {
          range: { offset: start, length: end ? end - start + 1 : undefined },
        });

        if (!object) {
          return new Response("Not found", { status: 404 });
        }

        return new Response(object.body, {
          status: 206,
          headers: {
            "Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
            "Content-Range": `bytes ${start}-${end || object.size - 1}/${object.size}`,
            "Content-Length": object.size.toString(),
          },
        });
      }
    }

    // Regular full download
    const object = await env.BUCKET.get(key);
    if (!object) {
      return new Response("Not found", { status: 404 });
    }

    return new Response(object.body, {
      headers: {
        "Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
      },
    });
  },
};

AWS SDK Integration (S3 Compatible)

R2 is fully compatible with the AWS S3 API. Use the official AWS SDK v3.

Install dependencies:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

wrangler.jsonc (add Node.js compatibility):

{
  "compatibility_flags": ["nodejs_compat_v2"],
  "r2_buckets": [
    { "binding": "BUCKET", "bucket_name": "my-bucket" }
  ]
}

Using AWS SDK:

import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

export interface Env {
  BUCKET: R2Bucket;
  R2_ACCESS_KEY_ID: string;
  R2_SECRET_ACCESS_KEY: string;
  R2_ACCOUNT_ID: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Create S3 client
    const s3 = new S3Client({
      region: "auto",
      endpoint: `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
      credentials: {
        accessKeyId: env.R2_ACCESS_KEY_ID,
        secretAccessKey: env.R2_SECRET_ACCESS_KEY,
      },
    });

    // Upload using S3 API
    await s3.send(
      new PutObjectCommand({
        Bucket: "my-bucket",
        Key: "file.txt",
        Body: "Hello R2",
        ContentType: "text/plain",
      })
    );

    // Generate presigned URL (valid for 1 hour)
    const command = new GetObjectCommand({
      Bucket: "my-bucket",
      Key: "file.txt",
    });
    const signedUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });

    return Response.json({ signedUrl });
  },
};

Note: Using the native R2Bucket binding is more efficient than AWS SDK for basic operations. Use AWS SDK when you need presigned URLs or have existing S3 tooling.

Presigned URLs for Direct Uploads

Allow clients to upload directly to R2 without going through your Worker.

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

export interface Env {
  R2_ACCESS_KEY_ID: string;
  R2_SECRET_ACCESS_KEY: string;
  R2_ACCOUNT_ID: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    // Generate presigned upload URL
    if (url.pathname === "/upload-url") {
      const filename = url.searchParams.get("filename");
      if (!filename) {
        return new Response("Missing filename", { status: 400 });
      }

      const s3 = new S3Client({
        region: "auto",
        endpoint: `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
        credentials: {
          accessKeyId: env.R2_ACCESS_KEY_ID,
          secretAccessKey: env.R2_SECRET_ACCESS_KEY,
        },
      });

      const key = `uploads/${crypto.randomUUID()}-${filename}`;
      const command = new PutObjectCommand({
        Bucket: "my-bucket",
        Key: key,
      });

      const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 }); // 5 minutes

      return Response.json({
        uploadUrl: signedUrl,
        key,
      });
    }

    return new Response("Not found", { status: 404 });
  },
};

Client-side usage:

// 1. Get presigned URL from your Worker
const response = await fetch("/upload-url?filename=photo.jpg");
const { uploadUrl, key } = await response.json();

// 2. Upload file directly to R2
const file = document.querySelector('input[type="file"]').files[0];
await fetch(uploadUrl, {
  method: "PUT",
  body: file,
  headers: {
    "Content-Type": file.type,
  },
});

// 3. File is now available at key in R2

Multipart Upload for Large Files

For files larger than 100MB, use multipart uploads:

// Initiate multipart upload
const multipartUpload = await env.BUCKET.createMultipartUpload(key, {
  httpMetadata: { contentType: "application/zip" }
});

const uploadedParts: R2UploadedPart[] = [];
const chunkSize = 10 * 1024 * 1024; // 10MB chunks

// Upload parts
for (let offset = 0; offset < data.byteLength; offset += chunkSize) {
  const chunk = data.slice(offset, Math.min(offset + chunkSize, data.byteLength));
  const part = await multipartUpload.uploadPart(partNumber++, chunk);
  uploadedParts.push(part);
}

// Complete upload
const object = await multipartUpload.complete(uploadedParts);

See references/operations.md for complete multipart upload patterns

Detailed References

Best Practices

  1. Set appropriate Content-Type: Always specify httpMetadata.contentType for proper browser handling
  2. Use unique keys: Prefix with UUIDs or timestamps to avoid collisions
  3. Leverage custom metadata: Store searchable metadata without downloading objects
  4. Use ETags for consistency: Implement conditional operations for race condition prevention
  5. Stream large uploads: Use multipart uploads for files > 100MB
  6. Cache control headers: Set appropriate cacheControl in httpMetadata
  7. Use presigned URLs for direct uploads: Reduce Worker bandwidth and latency
  8. List with pagination: Always handle truncated results when listing objects
  9. Use R2Bucket binding by default: Only use AWS SDK when you need presigned URLs or S3-specific features
  10. Handle 404s gracefully: bucket.get() returns null when object doesn't exist

Common Patterns

See references/operations.md for complete examples including:

  • Image upload and optimization with metadata
  • Structured data storage (JSON profiles)
  • Backup and archival patterns
  • Streaming large files
  • Conditional operations with ETags

适合场景

01

研究助手

02

事实核查

03

知识库问答

04

带来源的搜索总结

能力概览

能力 1

组合搜索和大模型调用

能力 2

支持多来源检索和总结

能力 3

强调引用来源和事实核查

能力 4

适合研究型 Agent 流程

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

36.99%
按下载量换算47

Claude

29.42%
按下载量换算37

Cursor

17.4%
按下载量换算22

Gemini CLI

9.43%
按下载量换算12

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills