agentsclimarketplace

File handling

Skill PIXARTSeu/Synapse/packages/codegraph/data/skill/file-handling

Self-improving AI brain for Claude Code & Desktop — 28 MCP tools, 253 skills, collective memory, project tracking, work logs. One server, all your sessions share the same knowledge. Deploy on Coolify in 2 minutes.

Install
npx -y skills add PIXARTSeu/Synapse --skill file-handling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

File handling patterns — S3/R2/Vercel Blob storage, PDF generation, CSV/Excel import-export, file streaming, presigned URLs, multipart upload. Use when implementing file uploads to cloud storage, generating documents, processing large files, or building download endpoints.

SKILL.md

12.1 KB, ~3.2k tokens by cl100k_base, as published. Nobody here has run it

File Handling — Next.js

1. Cloud Storage

AWS S3

pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
// lib/s3.ts
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const s3 = new S3Client({
  region: process.env.AWS_REGION!,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

const BUCKET = process.env.S3_BUCKET!

// Presigned URL for direct upload from browser
export async function getUploadUrl(key: string, contentType: string) {
  const command = new PutObjectCommand({
    Bucket: BUCKET,
    Key: key,
    ContentType: contentType,
  })
  return getSignedUrl(s3, command, { expiresIn: 3600 })
}

// Presigned URL for download
export async function getDownloadUrl(key: string) {
  const command = new GetObjectCommand({ Bucket: BUCKET, Key: key })
  return getSignedUrl(s3, command, { expiresIn: 3600 })
}

// Server-side upload
export async function uploadFile(key: string, body: Buffer, contentType: string) {
  await s3.send(new PutObjectCommand({
    Bucket: BUCKET, Key: key, Body: body, ContentType: contentType,
  }))
  return `https://${BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`
}

// Delete
export async function deleteFile(key: string) {
  await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: key }))
}
// app/api/upload/presign/route.ts
import { getUploadUrl } from '@/lib/s3'
import { NextRequest, NextResponse } from 'next/server'
import { nanoid } from 'nanoid'

export async function POST(req: NextRequest) {
  const { filename, contentType } = await req.json()
  const ext = filename.split('.').pop()
  const key = `uploads/${nanoid()}.${ext}`
  const url = await getUploadUrl(key, contentType)
  return NextResponse.json({ url, key })
}
// Client: direct upload to S3 via presigned URL
async function uploadToS3(file: File) {
  const { url, key } = await fetch('/api/upload/presign', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ filename: file.name, contentType: file.type }),
  }).then(r => r.json())

  await fetch(url, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } })
  return key
}

Cloudflare R2 (S3-compatible)

// lib/r2.ts — same S3Client, different endpoint
const r2 = new S3Client({
  region: 'auto',
  endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY!,
    secretAccessKey: process.env.R2_SECRET_KEY!,
  },
})
// Everything else is identical to S3

Vercel Blob

pnpm add @vercel/blob
// app/api/upload/route.ts
import { put, del } from '@vercel/blob'
import { NextResponse } from 'next/server'

export async function POST(req: Request) {
  const form = await req.formData()
  const file = form.get('file') as File

  const blob = await put(file.name, file, {
    access: 'public',
    addRandomSuffix: true,
  })

  return NextResponse.json(blob) // { url, downloadUrl, pathname }
}

2. PDF Generation

React PDF (@react-pdf/renderer)

pnpm add @react-pdf/renderer
// lib/pdf/invoice-template.tsx
import { Document, Page, Text, View, StyleSheet, Font } from '@react-pdf/renderer'

const styles = StyleSheet.create({
  page: { padding: 40, fontSize: 11, fontFamily: 'Helvetica' },
  header: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 30 },
  title: { fontSize: 24, fontWeight: 'bold' },
  table: { width: '100%', marginTop: 20 },
  row: { flexDirection: 'row', borderBottomWidth: 1, borderColor: '#e5e7eb', padding: 8 },
  total: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 10, fontSize: 14, fontWeight: 'bold' },
})

interface InvoiceProps {
  number: string; date: string; items: { name: string; qty: number; price: number }[]
  customer: { name: string; email: string }
}

export function InvoicePDF({ number, date, items, customer }: InvoiceProps) {
  const total = items.reduce((sum, i) => sum + i.qty * i.price, 0)
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <View style={styles.header}>
          <Text style={styles.title}>Invoice #{number}</Text>
          <Text>{date}</Text>
        </View>
        <Text>Bill to: {customer.name} ({customer.email})</Text>
        <View style={styles.table}>
          <View style={[styles.row, { fontWeight: 'bold' }]}>
            <Text style={{ flex: 3 }}>Item</Text>
            <Text style={{ flex: 1 }}>Qty</Text>
            <Text style={{ flex: 1 }}>Price</Text>
            <Text style={{ flex: 1 }}>Total</Text>
          </View>
          {items.map((item, i) => (
            <View key={i} style={styles.row}>
              <Text style={{ flex: 3 }}>{item.name}</Text>
              <Text style={{ flex: 1 }}>{item.qty}</Text>
              <Text style={{ flex: 1 }}>€{item.price}</Text>
              <Text style={{ flex: 1 }}>€{item.qty * item.price}</Text>
            </View>
          ))}
        </View>
        <View style={styles.total}>
          <Text>Total: €{total.toFixed(2)}</Text>
        </View>
      </Page>
    </Document>
  )
}
// app/api/invoice/[id]/pdf/route.ts
import { renderToBuffer } from '@react-pdf/renderer'
import { InvoicePDF } from '@/lib/pdf/invoice-template'
import { NextResponse } from 'next/server'

export async function GET(req: Request, { params }: { params: { id: string } }) {
  const invoice = await getInvoice(params.id) // your DB call

  const buffer = await renderToBuffer(
    <InvoicePDF {...invoice} />
  )

  return new NextResponse(buffer, {
    headers: {
      'Content-Type': 'application/pdf',
      'Content-Disposition': `attachment; filename="invoice-${invoice.number}.pdf"`,
    },
  })
}

HTML to PDF (Puppeteer)

// lib/pdf/html-to-pdf.ts
import puppeteer from 'puppeteer'

export async function htmlToPdf(html: string): Promise<Buffer> {
  const browser = await puppeteer.launch({ headless: true })
  const page = await browser.newPage()
  await page.setContent(html, { waitUntil: 'networkidle0' })
  const pdf = await page.pdf({
    format: 'A4',
    margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
    printBackground: true,
  })
  await browser.close()
  return Buffer.from(pdf)
}

3. CSV/Excel

CSV Export (Server Action)

'use server'
import { stringify } from 'csv-stringify/sync'

export async function exportUsersCSV() {
  const users = await db.user.findMany({ select: { name: true, email: true, createdAt: true } })

  const csv = stringify(users, {
    header: true,
    columns: { name: 'Name', email: 'Email', createdAt: 'Created At' },
  })

  return csv // return string, client creates download
}
// Client download helper
async function downloadCSV() {
  const csv = await exportUsersCSV()
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url; a.download = 'users.csv'; a.click()
  URL.revokeObjectURL(url)
}

CSV Import with Validation

// app/api/import/route.ts
import { parse } from 'csv-parse/sync'
import { z } from 'zod'

const RowSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  role: z.enum(['admin', 'user']),
})

export async function POST(req: Request) {
  const form = await req.formData()
  const file = form.get('file') as File
  const text = await file.text()

  const records = parse(text, { columns: true, skip_empty_lines: true, trim: true })
  const results = { success: 0, errors: [] as string[] }

  for (const [i, record] of records.entries()) {
    const parsed = RowSchema.safeParse(record)
    if (!parsed.success) {
      results.errors.push(`Row ${i + 2}: ${parsed.error.issues[0].message}`)
      continue
    }
    await db.user.create({ data: parsed.data })
    results.success++
  }

  return NextResponse.json(results)
}

Excel with ExcelJS

pnpm add exceljs
// app/api/export/excel/route.ts
import ExcelJS from 'exceljs'

export async function GET() {
  const workbook = new ExcelJS.Workbook()
  const sheet = workbook.addWorksheet('Users')

  sheet.columns = [
    { header: 'Name', key: 'name', width: 25 },
    { header: 'Email', key: 'email', width: 30 },
    { header: 'Role', key: 'role', width: 15 },
  ]

  // Style header
  sheet.getRow(1).font = { bold: true }
  sheet.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } }

  const users = await db.user.findMany()
  users.forEach((u) => sheet.addRow(u))

  const buffer = await workbook.xlsx.writeBuffer()

  return new Response(buffer, {
    headers: {
      'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      'Content-Disposition': 'attachment; filename="users.xlsx"',
    },
  })
}

4. File Streaming

Stream Large File Download

// app/api/download/[key]/route.ts
import { GetObjectCommand } from '@aws-sdk/client-s3'
import { s3, BUCKET } from '@/lib/s3'

export async function GET(req: Request, { params }: { params: { key: string } }) {
  const command = new GetObjectCommand({ Bucket: BUCKET, Key: params.key })
  const response = await s3.send(command)

  return new Response(response.Body as ReadableStream, {
    headers: {
      'Content-Type': response.ContentType || 'application/octet-stream',
      'Content-Length': String(response.ContentLength),
      'Content-Disposition': `attachment; filename="${params.key.split('/').pop()}"`,
    },
  })
}

Upload with Progress

function UploadWithProgress() {
  const [progress, setProgress] = useState(0)

  async function upload(file: File) {
    const { url, key } = await getPresignedUrl(file)

    const xhr = new XMLHttpRequest()
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) setProgress(Math.round((e.loaded / e.total) * 100))
    })

    await new Promise((resolve, reject) => {
      xhr.onload = resolve; xhr.onerror = reject
      xhr.open('PUT', url)
      xhr.setRequestHeader('Content-Type', file.type)
      xhr.send(file)
    })
  }

  return <progress value={progress} max={100} />
}

5. Validation & Security

File Type Validation (Magic Bytes)

const MAGIC_BYTES: Record<string, number[]> = {
  'image/jpeg': [0xFF, 0xD8, 0xFF],
  'image/png':  [0x89, 0x50, 0x4E, 0x47],
  'application/pdf': [0x25, 0x50, 0x44, 0x46],
}

export function validateFileType(buffer: ArrayBuffer, expectedType: string): boolean {
  const bytes = new Uint8Array(buffer)
  const magic = MAGIC_BYTES[expectedType]
  if (!magic) return false
  return magic.every((byte, i) => bytes[i] === byte)
}

// Usage in API route
const file = form.get('file') as File
const buffer = await file.arrayBuffer()
if (!validateFileType(buffer, file.type)) {
  return NextResponse.json({ error: 'Invalid file type' }, { status: 400 })
}

Image Resize Pipeline (Sharp)

import sharp from 'sharp'

export async function processImage(buffer: Buffer) {
  const variants = await Promise.all([
    sharp(buffer).resize(1200, 800, { fit: 'inside' }).webp({ quality: 80 }).toBuffer(),
    sharp(buffer).resize(600, 400, { fit: 'inside' }).webp({ quality: 80 }).toBuffer(),
    sharp(buffer).resize(200, 200, { fit: 'cover' }).webp({ quality: 70 }).toBuffer(),
  ])

  return { large: variants[0], medium: variants[1], thumb: variants[2] }
}

Decision Matrix

StorageCostLatencyBest For
Vercel Blob$$$LowQuick setup, small files
Cloudflare R2$LowCost-sensitive, no egress fees
AWS S3$$MediumEnterprise, existing AWS infra
Uploadthing$$LowFile upload UI out of the box

Gives 0 of the 12 instructions most pdf office docs skills give in ~3.2k tokens

Counted across 635 of the 690 authors here whose files we hold, read 2026-08-06

  • extract text using pdfplumberin 92 of 635, across 25 files
  • create PDFs using reportlabin 83 of 635, across 16 files
  • read FORMS.md to fill out PDF formsin 80 of 635, across 13 files
  • OCR scanned PDFs using pytesseractin 77 of 635, across 10 files
  • merge or split PDFs using qpdfin 70 of 635, across 3 files
  • use Excel formulas instead of hardcoded calculated valuesin 68 of 635, across 12 files
  • unpack edit xml and repack existing documentsin 63 of 635, across 8 files
  • document sources for hardcoded valuesin 61 of 635, across 9 files
  • write minimal python code without unnecessary commentsin 59 of 635, across 7 files
  • run the recalculation script after adding or modifying formulasin 58 of 635, across 6 files
  • fix all identified formula errors and recalculatein 58 of 635, across 6 files
  • format years as text stringsin 57 of 635, across 5 files

Said here and by no other author read

  • use presigned URLs for direct browser uploads
  • validate file type using magic bytes before processing
  • stream large files for download endpoints
  • use server actions for CSV exports
  • validate imported CSV rows with zod schemas

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.