Best use case
Skill: Pastebin Core is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
## Purpose
Teams using Skill: Pastebin Core should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/pastebin/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How Skill: Pastebin Core Compares
| Feature / Agent | Skill: Pastebin Core | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
## Purpose
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Skill: Pastebin Core
## Purpose
Implement the full pastebin backend: paste CRUD, Base62 ID generation, expiry enforcement, password protection with bcrypt, fork relationships, visibility rules, and the raw/public endpoints.
---
## Paste ID Generation
```typescript
// api/src/lib/base62.ts
const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const ID_LENGTH = 8;
export function generatePasteId(): string {
// rejection-sampling avoids modulo bias
const bytes = new Uint8Array(ID_LENGTH * 2);
crypto.getRandomValues(bytes);
let result = '';
for (const byte of bytes) {
if (byte < 248 && result.length < ID_LENGTH) {
result += ALPHABET[byte % 62];
}
}
if (result.length < ID_LENGTH) return generatePasteId(); // retry (extremely rare)
return result;
}
```
---
## TypeScript Types
```typescript
// api/src/types.ts
export type Visibility = 'public' | 'unlisted' | 'private';
export interface Paste {
id: string; // 8-char Base62
user_id: number | null;
title: string | null;
content: string;
language: string; // 'plaintext' | 'typescript' | ... (hljs alias)
visibility: Visibility;
password_hash: string | null;
fork_of: string | null; // paste.id of parent
views: number;
expired: 0 | 1;
expires_at: string | null; // ISO-8601 or null
created_at: string;
updated_at: string;
}
export interface PasteRow extends Paste {
author_name: string | null;
}
export interface CreatePasteBody {
title?: string;
content: string;
language?: string;
visibility?: Visibility;
password?: string;
expires_in_seconds?: number; // null = never
fork_of?: string;
}
export interface UpdatePasteBody {
title?: string;
content?: string;
language?: string;
visibility?: Visibility;
password?: string | null; // null = remove password
expires_in_seconds?: number | null;
}
export interface UnlockBody {
password: string;
}
```
---
## Database Schema
```sql
-- pastes table
CREATE TABLE IF NOT EXISTS pastes (
id TEXT PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
title TEXT,
content TEXT NOT NULL CHECK(length(content) <= 524288), -- 512 KB
language TEXT NOT NULL DEFAULT 'plaintext',
visibility TEXT NOT NULL DEFAULT 'public' CHECK(visibility IN ('public','unlisted','private')),
password_hash TEXT,
fork_of TEXT REFERENCES pastes(id) ON DELETE SET NULL,
views INTEGER NOT NULL DEFAULT 0,
expired INTEGER NOT NULL DEFAULT 0,
expires_at TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
);
CREATE INDEX IF NOT EXISTS idx_pastes_user ON pastes(user_id);
CREATE INDEX IF NOT EXISTS idx_pastes_public ON pastes(visibility, expired, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_pastes_expiry ON pastes(expires_at) WHERE expires_at IS NOT NULL;
```
---
## Create Paste
```typescript
// api/src/routes/pastes.ts
import bcrypt from 'bcrypt';
import { generatePasteId } from '../lib/base62.js';
import { db } from '../db.js';
const BCRYPT_ROUNDS = 10;
const MAX_RETRY = 5;
export async function createPaste(
body: CreatePasteBody,
userId: number | null
): Promise<string> { // returns new paste id
// Anonymous users cannot create private pastes
if (body.visibility === 'private' && userId === null) {
throw Object.assign(new Error('Private pastes require an account'), { status: 403 });
}
const passwordHash = body.password
? await bcrypt.hash(body.password, BCRYPT_ROUNDS)
: null;
const expiresAt = body.expires_in_seconds
? new Date(Date.now() + body.expires_in_seconds * 1000).toISOString()
: null;
for (let attempt = 0; attempt < MAX_RETRY; attempt++) {
const id = generatePasteId();
try {
db.prepare(`
INSERT INTO pastes (id, user_id, title, content, language, visibility,
password_hash, fork_of, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
id,
userId ?? null,
body.title?.trim() || null,
body.content,
body.language ?? 'plaintext',
body.visibility ?? 'public',
passwordHash,
body.fork_of ?? null,
expiresAt,
);
return id;
} catch (err: unknown) {
if ((err as NodeJS.ErrnoException).code === 'SQLITE_CONSTRAINT_PRIMARYKEY') continue;
throw err;
}
}
throw new Error('Failed to generate unique paste ID after max retries');
}
```
---
## Expiry Middleware
```typescript
// api/src/middleware/expiry.ts
import { db } from '../db.js';
import type { RequestHandler } from 'express';
// Call before serving any paste to enforce soft-expiry
export const enforceExpiry: RequestHandler = (req, _res, next) => {
const { id } = req.params as { id: string };
// Mark paste as expired if expires_at has passed
db.prepare(`
UPDATE pastes SET expired = 1
WHERE id = ? AND expired = 0 AND expires_at IS NOT NULL AND expires_at < strftime('%Y-%m-%dT%H:%M:%SZ','now')
`).run(id);
next();
};
```
---
## Get Paste (with password and visibility enforcement)
```typescript
export async function getPaste(
id: string,
sessionUserId: number | null,
providedPassword: string | null
): Promise<{ paste: PasteRow; locked: boolean }> {
const paste = db.prepare(`
SELECT p.*, u.username AS author_name
FROM pastes p
LEFT JOIN users u ON p.user_id = u.id
WHERE p.id = ?
`).get(id) as PasteRow | undefined;
if (!paste) throw Object.assign(new Error('Not found'), { status: 404 });
if (paste.expired) throw Object.assign(new Error('Paste has expired'), { status: 410 });
// Private: only owner can view
if (paste.visibility === 'private' && paste.user_id !== sessionUserId) {
throw Object.assign(new Error('Forbidden'), { status: 403 });
}
// Password locked
if (paste.password_hash) {
if (!providedPassword) {
// Return metadata only, locked = true
return { paste: { ...paste, content: '' }, locked: true };
}
const match = await bcrypt.compare(providedPassword, paste.password_hash);
if (!match) throw Object.assign(new Error('Incorrect password'), { status: 401 });
}
// Increment view count (fire-and-forget; skip for owner)
if (paste.user_id !== sessionUserId) {
db.prepare('UPDATE pastes SET views = views + 1 WHERE id = ?').run(id);
}
return { paste, locked: false };
}
```
---
## Unlock Endpoint
```typescript
// POST /api/pastes/:id/unlock
// Body: { password: string }
// Returns: { content: string } on success, 401 on wrong password
router.post('/:id/unlock', enforceExpiry, async (req, res, next) => {
try {
const { id } = req.params;
const { password } = req.body as UnlockBody;
const { paste } = await getPaste(id, req.session?.userId ?? null, password);
res.json({ content: paste.content });
} catch (err) {
next(err);
}
});
```
---
## Public Recent Pastes
```typescript
// GET /api/public/recent?lang=&page=&per_page=
export function getRecentPastes(opts: {
lang?: string;
page: number;
perPage: number;
}): { pastes: PublicPasteRow[]; total: number } {
const offset = (opts.page - 1) * opts.perPage;
const langClause = opts.lang ? 'AND p.language = ?' : '';
const langParam = opts.lang ? [opts.lang] : [];
const pastes = db.prepare(`
SELECT p.id, p.title, p.language, p.views, p.created_at,
u.username AS author_name,
substr(p.content, 1, 200) AS preview,
CASE WHEN p.password_hash IS NOT NULL THEN 1 ELSE 0 END AS is_locked
FROM pastes p
LEFT JOIN users u ON p.user_id = u.id
WHERE p.visibility = 'public' AND p.expired = 0 ${langClause}
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?
`).all(...langParam, opts.perPage, offset) as PublicPasteRow[];
const { total } = db.prepare(`
SELECT COUNT(*) AS total FROM pastes
WHERE visibility = 'public' AND expired = 0 ${langClause}
`).get(...langParam) as { total: number };
return { pastes, total };
}
```
---
## Fork a Paste
```typescript
// POST /api/pastes/:id/fork (requires auth)
router.post('/:id/fork', requireAuth, enforceExpiry, async (req, res, next) => {
try {
const { id } = req.params;
const { paste } = await getPaste(id, req.session.userId, null);
if (paste.locked) throw Object.assign(new Error('Cannot fork a password-locked paste without unlocking'), { status: 403 });
const body: CreatePasteBody = {
title: paste.title ? `${paste.title} (fork)` : undefined,
content: paste.content,
language: paste.language,
visibility: 'public',
fork_of: paste.id,
};
const newId = await createPaste(body, req.session.userId);
res.status(201).json({ id: newId });
} catch (err) {
next(err);
}
});
```
---
## Raw Content Endpoint
```typescript
// GET /raw/:id -- plain text response, no JSON wrapper
router.get('/:id', enforceExpiry, async (req, res, next) => {
try {
const { id } = req.params;
// Raw endpoint never serves password-protected pastes without unlock
const { paste, locked } = await getPaste(id, req.session?.userId ?? null, null);
if (locked) return res.status(403).send('Password protected paste: use /api/pastes/:id/unlock');
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.send(paste.content);
} catch (err) {
next(err);
}
});
```
---
## User Pastes List
```typescript
// GET /api/user/pastes?visibility=&page=&per_page= (requires auth)
export function getUserPastes(opts: {
userId: number;
visibility?: Visibility;
page: number;
perPage: number;
}): { pastes: Paste[]; total: number } {
const offset = (opts.page - 1) * opts.perPage;
const visClause = opts.visibility ? 'AND visibility = ?' : '';
const visParam = opts.visibility ? [opts.visibility] : [];
const pastes = db.prepare(`
SELECT * FROM pastes
WHERE user_id = ? ${visClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`).all(opts.userId, ...visParam, opts.perPage, offset) as Paste[];
const { total } = db.prepare(`
SELECT COUNT(*) AS total FROM pastes WHERE user_id = ? ${visClause}
`).get(opts.userId, ...visParam) as { total: number };
return { pastes, total };
}
```
---
## Scheduled Expiry Cleanup
```typescript
// api/src/jobs/expireOld.ts
import cron from 'node-cron';
import { db } from '../db.js';
// Run every 10 minutes; mark expired pastes
export function startExpiryJob(): void {
cron.schedule('*/10 * * * *', () => {
const result = db.prepare(`
UPDATE pastes SET expired = 1
WHERE expired = 0 AND expires_at IS NOT NULL AND expires_at < strftime('%Y-%m-%dT%H:%M:%SZ','now')
`).run();
if (result.changes > 0) {
console.log(`[expiry] marked ${result.changes} paste(s) as expired`);
}
});
}
```
---
## Validation Rules
| Field | Rule |
|---|---|
| `content` | 1 byte minimum, 524288 bytes (512 KB) maximum |
| `title` | max 255 chars, trimmed |
| `language` | must be a valid hljs language alias or 'plaintext' |
| `visibility` | 'public' | 'unlisted' | 'private'; anonymous forces 'public'/'unlisted' |
| `password` | 6-128 chars when provided |
| `expires_in_seconds` | 3600 (1h) | 86400 (24h) | 604800 (7d) | 2592000 (30d) | null (never) |
| `fork_of` | must reference existing, non-expired, non-private paste |
---
## API Routes Summary
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/pastes | optional | Create paste |
| GET | /api/pastes/:id | optional | Get paste (returns locked:true if password required) |
| PUT | /api/pastes/:id | owner | Update paste |
| DELETE | /api/pastes/:id | owner | Delete paste |
| POST | /api/pastes/:id/unlock | - | Verify password, return content |
| POST | /api/pastes/:id/fork | required | Fork paste |
| GET | /api/public/recent | - | Paginated public list |
| GET | /api/user/pastes | required | Owner's pastes |
| GET | /raw/:id | optional | Plain text content |
---
## pnpm Commands
```
pnpm --filter api dev # start API with tsx watch
pnpm --filter web dev # start Vite dev server
pnpm --filter api test # run Vitest
pnpm --filter api build # tsc compile
pnpm --filter web build # Vite production build
pnpm --filter api db:migrate # run migrations
```Related Skills
Skill: Invoice Generator Core
## Purpose
Skill: Form Builder Core
## Purpose
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview