/r2-multipart-init
Set up multipart upload for large files (>100MB)
$ npx -y skills add secondsky/claude-skills --agent claude-codeHow it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/r2-multipart-init
Context preview
What this command does when you run it.
Set up multipart upload for large files (>100MB)
Command definition
r2-multipart-init.mdname: cloudflare-r2:multipart-init
description: Set up multipart upload for large files (>100MB)
R2 Multipart Upload Setup
Initialize and implement multipart upload workflow for handling large files (>100MB) with chunking, error recovery, and progress tracking.
Required Information
1. **File size** (in MB): `{{file_size}}` 2. **Object key** (destination path): `{{key}}` 3. **Part size** (5-100 MB, recommended 10MB): `{{part_size}}` 4. **Content type**: `{{content_type}}`
What This Command Does
1. **Calculates optimal part size** based on file size 2. **Shows createMultipartUpload code** for initialization 3. **Generates part upload loop** with retry logic 4. **Provides completion/abort handlers** 5. **Adds progress tracking** for user feedback
Multipart Upload Constraints
- **Minimum part size**: 5 MB (except last part)
- **Maximum part size**: 100 MB
- **Maximum parts**: 10,000 parts per upload
- **Part numbers**: 1 to 10,000 (1-based indexing)
Part Size Calculator
function calculatePartSize(fileSizeMB: number): number {
const MIN_PART_SIZE = 5 * 1024 * 1024; // 5MB
const MAX_PART_SIZE = 100 * 1024 * 1024; // 100MB
const MAX_PARTS = 10000;
const fileSize = fileSizeMB * 1024 * 1024;
const recommendedSize = Math.ceil(fileSize / MAX_PARTS);
if (recommendedSize < MIN_PART_SIZE) {
return MIN_PART_SIZE;
} else if (recommendedSize > MAX_PART_SIZE) {
return MAX_PART_SIZE;
} else {
return recommendedSize;
}
}
// Example: 500MB file = 10MB parts (50 parts)
// Example: 50GB file = 10MB parts (5000 parts)Complete Implementation
import { Hono } from 'hono';
type Bindings = {
MY_BUCKET: R2Bucket;
};
const app = new Hono<{ Bindings: Bindings }>();
// Step 1: Initialize multipart upload
app.post('/multipart/create', async (c) => {
const { key, contentType } = await c.req.json();
const multipart = await c.env.MY_BUCKET.createMultipartUpload(key, {
httpMetadata: {
contentType: contentType || 'application/octet-stream',
},
customMetadata: {
uploadedAt: new Date().toISOString(),
},
});
return c.json({
uploadId: multipart.uploadId,
key: multipart.key,
});
});
// Step 2: Upload individual parts
app.put('/multipart/upload-part', async (c) => {
const { uploadId, key, partNumber } = await c.req.json();
const data = await c.req.arrayBuffer();
// Validate part number (1-10000)
if (partNumber < 1 || partNumber > 10000) {
return c.json({ error: 'Invalid part number' }, 400);
}
const multipart = c.env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
const uploadedPart = await multipart.uploadPart(partNumber, data);
return c.json({
partNumber,
etag: uploadedPart.etag,
});
});
// Step 3: Complete multipart upload
app.post('/multipart/complete', async (c) => {
const { uploadId, key, parts } = await c.req.json();
const multipart = c.env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
// parts = [{ partNumber: 1, etag: 'abc' }, { partNumber: 2, etag: 'def' }, ...]
const object = await multipart.complete(parts);
return c.json({
success: true,
key: object.key,
size: object.size,
etag: object.httpEtag,
});
});
// Step 4: Abort multipart upload (cleanup)
app.delete('/multipart/abort', async (c) => {
const { uploadId, key } = await c.req.json();
const multipart = c.env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
await multipart.abort();
return c.json({ success: true, message: 'Upload aborted' });
});
export default app;Client-Side Upload Flow
async function uploadLargeFile(file: File, onProgress: (percent: number) => void) {
const partSize = 10 * 1024 * 1024; // 10MB
const totalParts = Math.ceil(file.size / partSize);
// Step 1: Create multipart upload
const { uploadId, key } = await fetch('/multipart/create', {
method: 'POST',
body: JSON.stringify({
key: `uploads/${file.name}`,
contentType: file.type,
}),
}).then(r => r.json());
// Step 2: Upload parts
const uploadedParts = [];
for (let partNumber = 1; partNumber <= totalParts; partNumber++) {
const start = (partNumber - 1) * partSize;
const end = Math.min(start + partSize, file.size);
const chunk = file.slice(start, end);
const { etag } = await fetch('/multipart/upload-part', {
method: 'PUT',
body: JSON.stringify({
uploadId,
key,
partNumber,
}),
headers: { 'Content-Type': 'application/octet-stream' },
}).then(r => r.json());
uploadedParts.push({ partNumber, etag });
const progress = (partNumber / totalParts) * 100;
onProgress(progress);
}
// Step 3: Complete upload
await fetch('/multipart/complete', {
method: 'POST',
body: JSON.stringify({
uploadId,
key,
parts: uploadedParts,
}),
});
console.log('Upload complete!');
}Error Handling with Retry
async function uploadPartWithRetry(
uploadId: string,
key: string,
partNumber: number,
data: ArrayBuffer,
maxRetries = 3
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch('/multipart/upload-part', {
method: 'PUT',
body: JSON.stringify({ uploadId, key, partNumber }),
});
if (response.ok) {
return await response.json();
}
} catch (error) {
if (attempt === maxRetries - 1) {
throw error;
}
// Exponential backoff: 1s, 2s, 4s
const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}Progress Tracking
function createProgressTracker(totalParts: number) {
let completedParts = 0;
return {
increment() {
completedParts++;
const percent = (completedParts / totalParts) * 100;
console.log(`Upload progress: ${percent.toFixed(1)}%`);Read more
name: cloudflare-r2:multipart-init description: Set up multipart upload for large files (>100MB)
R2 Multipart Upload Setup
Initialize and implement multipart upload workflow for handling large files (>100MB) with chunking, error recovery, and progress tracking.
Required Information
1. **File size** (in MB): `{{file_size}}` 2. **Object key** (destination path): `{{key}}` 3. **Part size** (5-100 MB, recommended 10MB): `{{part_size}}` 4. **Content type**: `{{content_type}}`
What This Command Does
1. **Calculates optimal part size** based on file size 2. **Shows createMultipartUpload code** for initialization 3. **Generates part upload loop** with retry logic 4. **Provides completion/abort handlers** 5. **Adds progress tracking** for user feedback
Multipart Upload Constraints
- **Minimum part size**: 5 MB (except last part)
- **Maximum part size**: 100 MB
- **Maximum parts**: 10,000 parts per upload
- **Part numbers**: 1 to 10,000 (1-based indexing)
Part Size Calculator
function calculatePartSize(fileSizeMB: number): number {
const MIN_PART_SIZE = 5 * 1024 * 1024; // 5MB
const MAX_PART_SIZE = 100 * 1024 * 1024; // 100MB
const MAX_PARTS = 10000;
const fileSize = fileSizeMB * 1024 * 1024;
const recommendedSize = Math.ceil(fileSize / MAX_PARTS);
if (recommendedSize < MIN_PART_SIZE) {
return MIN_PART_SIZE;
} else if (recommendedSize > MAX_PART_SIZE) {
return MAX_PART_SIZE;
} else {
return recommendedSize;
}
}
// Example: 500MB file = 10MB parts (50 parts)
// Example: 50GB file = 10MB parts (5000 parts)Complete Implementation
import { Hono } from 'hono';
type Bindings = {
MY_BUCKET: R2Bucket;
};
const app = new Hono<{ Bindings: Bindings }>();
// Step 1: Initialize multipart upload
app.post('/multipart/create', async (c) => {
const { key, contentType } = await c.req.json();
const multipart = await c.env.MY_BUCKET.createMultipartUpload(key, {
httpMetadata: {
contentType: contentType || 'application/octet-stream',
},
customMetadata: {
uploadedAt: new Date().toISOString(),
},
});
return c.json({
uploadId: multipart.uploadId,
key: multipart.key,
});
});
// Step 2: Upload individual parts
app.put('/multipart/upload-part', async (c) => {
const { uploadId, key, partNumber } = await c.req.json();
const data = await c.req.arrayBuffer();
// Validate part number (1-10000)
if (partNumber < 1 || partNumber > 10000) {
return c.json({ error: 'Invalid part number' }, 400);
}
const multipart = c.env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
const uploadedPart = await multipart.uploadPart(partNumber, data);
return c.json({
partNumber,
etag: uploadedPart.etag,
});
});
// Step 3: Complete multipart upload
app.post('/multipart/complete', async (c) => {
const { uploadId, key, parts } = await c.req.json();
const multipart = c.env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
// parts = [{ partNumber: 1, etag: 'abc' }, { partNumber: 2, etag: 'def' }, ...]
const object = await multipart.complete(parts);
return c.json({
success: true,
key: object.key,
size: object.size,
etag: object.httpEtag,
});
});
// Step 4: Abort multipart upload (cleanup)
app.delete('/multipart/abort', async (c) => {
const { uploadId, key } = await c.req.json();
const multipart = c.env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
await multipart.abort();
return c.json({ success: true, message: 'Upload aborted' });
});
export default app;Client-Side Upload Flow
async function uploadLargeFile(file: File, onProgress: (percent: number) => void) {
const partSize = 10 * 1024 * 1024; // 10MB
const totalParts = Math.ceil(file.size / partSize);
// Step 1: Create multipart upload
const { uploadId, key } = await fetch('/multipart/create', {
method: 'POST',
body: JSON.stringify({
key: `uploads/${file.name}`,
contentType: file.type,
}),
}).then(r => r.json());
// Step 2: Upload parts
const uploadedParts = [];
for (let partNumber = 1; partNumber <= totalParts; partNumber++) {
const start = (partNumber - 1) * partSize;
const end = Math.min(start + partSize, file.size);
const chunk = file.slice(start, end);
const { etag } = await fetch('/multipart/upload-part', {
method: 'PUT',
body: JSON.stringify({
uploadId,
key,
partNumber,
}),
headers: { 'Content-Type': 'application/octet-stream' },
}).then(r => r.json());
uploadedParts.push({ partNumber, etag });
const progress = (partNumber / totalParts) * 100;
onProgress(progress);
}
// Step 3: Complete upload
await fetch('/multipart/complete', {
method: 'POST',
body: JSON.stringify({
uploadId,
key,
parts: uploadedParts,
}),
});
console.log('Upload complete!');
}Error Handling with Retry
async function uploadPartWithRetry(
uploadId: string,
key: string,
partNumber: number,
data: ArrayBuffer,
maxRetries = 3
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch('/multipart/upload-part', {
method: 'PUT',
body: JSON.stringify({ uploadId, key, partNumber }),
});
if (response.ok) {
return await response.json();
}
} catch (error) {
if (attempt === maxRetries - 1) {
throw error;
}
// Exponential backoff: 1s, 2s, 4s
const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}Progress Tracking
function createProgressTracker(totalParts: number) {
let completedParts = 0;
return {
increment() {
completedParts++;
const percent = (completedParts / totalParts) * 100;
console.log(`Upload progress: ${percent.toFixed(1)}%`);142 production-ready skills for Claude Code CLI ๐ Platform / Harness Support These plugins ship as Claude Code marketplace plugins (.claude-plugin/ manifests) and Codex CLI plugins (.codex-plugin/ manifests).
Repo: secondsky/claude-skills
Other commands on secondsky-claude-skills.
- /better-auth-add-plugin
Add a better-auth plugin to an existing project. Configures server and client plugins with proper imports.
Open command - /better-auth-setup
Interactive setup wizard for better-auth authentication. Guides through database, framework, OAuth providers, and plugin configuration.
Open command - /explain-error
Explain Better Auth error codes and provide solutions with code examples
Open command - /providers
Display Better Auth available authentication providers and their configuration
Open command - /bun-debug
Type of issue to debug (runtime, test, build, memory, performance)
Open command - /bun-deploy
Target platform (docker, cloudflare, vercel, fly, railway)
Open command

