#!/usr/bin/env -S bun --install=fallback /** * share - Upload files to R2 and copy the URL to clipboard * * Usage: * share # Upload clipboard content * share file.png # Upload specific file * cat file.txt | share # Upload from stdin * share -c video.mov # Convert then upload */ import { existsSync, fstatSync } from "fs"; import { tmpdir, platform } from "os"; import { join, basename, extname } from "path"; import { parseArgs } from "util"; import chalk from "chalk"; import { S3Client } from "@aws-sdk/client-s3"; import { Upload } from "@aws-sdk/lib-storage"; import { nanoid } from "nanoid"; import { fileTypeFromBuffer } from "file-type"; import { $ } from "bun"; interface UploadSource { buffer: Buffer; filename?: string; mimeType?: string; } interface UploadResult { url: string; key: string; size: number; } type FixMode = "never" | "prompt" | "always"; interface MediaIssue { id: string; description: string; severity: "error" | "warning"; autoFix: boolean; // true = instant/lossless, false = slow/lossy fix: (buffer: Buffer) => Promise; } interface ProbeResult { issues: MediaIssue[]; metadata: Record; } interface VideoProbe { duration: number | null; codec: string | null; hasFaststart: boolean; } type TimeoutHandle = ReturnType; type IntervalHandle = ReturnType; const DOMAIN = "https://i.xevion.dev"; const ENV = { endpoint: "{{ dopplerProjectJson.R2_ENDPOINT }}", accessKeyId: "{{ dopplerProjectJson.R2_ACCESS_KEY_ID }}", secretAccessKey: "{{ dopplerProjectJson.R2_SECRET_ACCESS_KEY }}", bucket: "{{ dopplerProjectJson.R2_BUCKET }}", }; const TIMING = { spinnerFrame: 80, requestTimeout: 30_000, uploadTimeout: 60_000, }; const SIZE_THRESHOLDS = { image: 10 * 1024 * 1024, video: 50 * 1024 * 1024, binary: 100 * 1024 * 1024, reencodeAuto: 10 * 1024 * 1024, // Auto re-encode incompatible codecs below this size }; const INCOMPATIBLE_VIDEO_CODECS = ["hevc", "h265", "av1"]; const COLORS = { spinner: ["#A5D8DD", "#9DCCB4", "#B8D99A", "#E8D4A2", "#F4B8A4", "#F5A6A6"], success: "#9DCCB4", error: "#E89999", label: "#6B7280", dim: "#9CA3AF", progressFilled: "#A5D8DD", progressEmpty: "#374151", }; const MIME_EXTENSIONS: Record = { "image/png": "png", "image/jpeg": "jpg", "image/jpg": "jpg", "image/gif": "gif", "image/webp": "webp", "image/bmp": "bmp", "image/tiff": "tiff", "image/heic": "heic", "image/heif": "heif", "image/avif": "avif", "video/mp4": "mp4", "video/webm": "webm", "video/quicktime": "mov", "video/x-matroska": "mkv", "text/plain": "txt", "application/json": "json", "application/pdf": "pdf", }; const EXTENSION_MIMES: Record = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", bmp: "image/bmp", tiff: "image/tiff", heic: "image/heic", heif: "image/heif", avif: "image/avif", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", mkv: "video/x-matroska", txt: "text/plain", json: "application/json", pdf: "application/pdf", js: "application/javascript", ts: "application/typescript", rs: "text/x-rust", py: "text/x-python", md: "text/markdown", html: "text/html", css: "text/css", }; const TRUTHY = ["y", "yes", "true", "t", "Y", "YES", "TRUE", "T"]; const FALSY = ["n", "no", "false", "f", "N", "NO", "FALSE", "F", "deny"]; let VERBOSE = false; let usedStdin = false; const IS_WSL = await (async () => { try { const text = await Bun.file("/proc/version").text(); return text.toLowerCase().includes("microsoft"); } catch { return false; } })(); const IS_WINDOWS = platform() === "win32"; class Spinner { private frames = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]; private colors = COLORS.spinner; private index = 0; private interval: IntervalHandle | null = null; start(message: string) { process.stdout.write("\x1B[?25l"); this.interval = setInterval(() => { const colorIndex = this.index % this.colors.length; const frame = chalk.hex(this.colors[colorIndex])(this.frames[this.index]); process.stdout.write(`\r${frame} ${chalk.hex(COLORS.dim)(message)}`); this.index = (this.index + 1) % this.frames.length; }, TIMING.spinnerFrame); } stop(clearLine = true) { if (this.interval) { clearInterval(this.interval); if (clearLine) { process.stdout.write("\r\x1B[K"); } process.stdout.write("\x1B[?25h"); } } } function restoreCursor() { process.stdout.write("\x1B[?25h"); } process.on("SIGINT", () => { restoreCursor(); process.exit(130); }); process.on("SIGTERM", () => { restoreCursor(); process.exit(143); }); process.on("uncaughtException", (err) => { restoreCursor(); console.error(err); process.exit(1); }); class ProgressBar { private total: number; private barWidth = 30; constructor(total: number) { this.total = Math.max(1, total); } update(loaded: number) { const percentage = Math.min(100, Math.floor((loaded / this.total) * 100)); const filled = Math.floor((loaded / this.total) * this.barWidth); const empty = this.barWidth - filled; const bar = chalk.hex(COLORS.progressFilled)("█".repeat(filled)) + chalk.hex(COLORS.progressEmpty)("░".repeat(empty)); const stats = `${formatBytes(loaded)}/${formatBytes(this.total)}`; process.stdout.write(`\r${bar} ${percentage}% · ${stats}`); } finish() { process.stdout.write("\r\x1B[K"); } } function formatBytes(bytes: number): string { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; } function log( message: string, type: "success" | "error" | "info" | "dim" = "info", ) { const icons = { success: chalk.hex(COLORS.success)("✓"), error: chalk.hex(COLORS.error)("✗"), info: chalk.hex(COLORS.dim)("→"), dim: chalk.hex(COLORS.dim)("·"), }; console.log(`${icons[type]} ${message}`); } function debug(message: string, data?: unknown) { if (!VERBOSE) return; console.error(chalk.hex(COLORS.label)("[debug]"), message); if (data !== undefined) { console.error(chalk.hex(COLORS.dim)(JSON.stringify(data, null, 2))); } } async function detectMimeType( buffer: Buffer, filename?: string, ): Promise { const result = await fileTypeFromBuffer(buffer); if (result) { debug("Detected MIME via file-type", { mime: result.mime }); return result.mime; } if (filename) { const ext = extname(filename).slice(1).toLowerCase(); if (EXTENSION_MIMES[ext]) { debug("Detected MIME via extension", { ext, mime: EXTENSION_MIMES[ext] }); return EXTENSION_MIMES[ext]; } } debug("Defaulting to application/octet-stream"); return "application/octet-stream"; } function getExtensionForMime(mime: string): string { return MIME_EXTENSIONS[mime] || "bin"; } function getMimeForExtension(ext: string): string { const normalized = ext.replace(/^\.+/, ""); return EXTENSION_MIMES[normalized] || "application/octet-stream"; } function isTextMime(mime: string): boolean { return ( mime.startsWith("text/") || mime === "application/json" || mime === "application/javascript" || mime === "application/typescript" ); } function isImageMime(mime: string): boolean { return mime.startsWith("image/"); } function isVideoMime(mime: string): boolean { return mime.startsWith("video/"); } async function readFromFile(path: string): Promise { const spinner = new Spinner(); spinner.start("Reading file..."); try { if (!existsSync(path)) { throw new Error(`File not found: ${path}`); } const buffer = Buffer.from(await Bun.file(path).arrayBuffer()); const filename = basename(path); const mimeType = await detectMimeType(buffer, filename); spinner.stop(); debug("Read file", { path, size: buffer.length, mimeType }); return { buffer, filename, mimeType }; } catch (e) { spinner.stop(); throw e; } } function hasStdinData(): boolean { if (Bun.stdin.isTTY === true) { return false; } try { const stats = fstatSync(0); return stats.isFIFO() || stats.isSocket(); } catch (error) { debug( "fstat stdin detection failed", error instanceof Error ? error.message : String(error), ); return false; } } async function readFromStdin(): Promise { usedStdin = true; const spinner = new Spinner(); spinner.start("Reading from stdin..."); try { const chunks: Buffer[] = []; for await (const chunk of Bun.stdin.stream()) { chunks.push(Buffer.from(chunk)); } const buffer = Buffer.concat(chunks); if (buffer.length === 0) { throw new Error("No data received from stdin"); } const mimeType = await detectMimeType(buffer); spinner.stop(); debug("Read stdin", { size: buffer.length, mimeType }); return { buffer, mimeType }; } catch (e) { spinner.stop(); throw e; } } async function copyToClipboard(text: string): Promise { if (IS_WSL || IS_WINDOWS) { const proc = Bun.spawn(["clip.exe"], { stdin: "pipe" }); proc.stdin.write(text); proc.stdin.end(); await proc.exited; } else { const proc = Bun.spawn(["xclip", "-selection", "clipboard"], { stdin: "pipe", }); proc.stdin.write(text); proc.stdin.end(); await proc.exited; } } async function readFromClipboard(): Promise { const spinner = new Spinner(); spinner.start("Reading clipboard..."); try { if (IS_WSL || IS_WINDOWS) { const result = await $`powershell.exe -NoProfile -Command "Get-Clipboard -Raw"`.text(); const text = result.trim(); if (!text) { throw new Error("Clipboard is empty"); } if (text.startsWith("/") && existsSync(text)) { spinner.stop(); const shouldUpload = await confirm( `Clipboard contains a file path: ${text}\nUpload this file?`, ); if (shouldUpload) { return readFromFile(text); } throw new Error("Upload cancelled"); } spinner.stop(); const buffer = Buffer.from(text, "utf-8"); debug("Read clipboard text (WSL)", { size: buffer.length }); return { buffer, mimeType: "text/plain" }; } const targets = await $`xclip -selection clipboard -t TARGETS -o`.text(); const targetList = targets.trim().split("\n"); debug("Clipboard targets", targetList); if ( targetList.includes("text/uri-list") || targetList.includes("x-special/gnome-copied-files") ) { const uris = await $`xclip -selection clipboard -t text/uri-list -o`.text(); const filePath = uris .split("\n")[0] .replace(/^file:\/\//, "") .trim(); if (filePath && existsSync(filePath)) { spinner.stop(); const shouldUpload = await confirm( `Clipboard contains a file path: ${filePath}\nUpload this file?`, ); if (shouldUpload) { return readFromFile(filePath); } else { throw new Error("Upload cancelled"); } } } const imageTarget = targetList.find((t) => t.startsWith("image/")); if (imageTarget) { const isBmp = imageTarget === "image/bmp"; const buffer = Buffer.from( await $`xclip -selection clipboard -t ${imageTarget} -o`.arrayBuffer(), ); spinner.stop(); debug("Read clipboard image", { target: imageTarget, size: buffer.length, }); if (isBmp) { log("Converting BMP to PNG...", "info"); return { buffer, mimeType: "image/bmp", filename: "paste.bmp" }; } return { buffer, mimeType: imageTarget, filename: `paste.${getExtensionForMime(imageTarget)}`, }; } const text = await $`xclip -selection clipboard -o`.text(); if (!text.trim()) { throw new Error("Clipboard is empty or contains unsupported data"); } const trimmedText = text.trim(); if (trimmedText.startsWith("/") && existsSync(trimmedText)) { spinner.stop(); const shouldUpload = await confirm( `Clipboard contains a file path: ${trimmedText}\nUpload this file?`, ); if (shouldUpload) { return readFromFile(trimmedText); } } spinner.stop(); const buffer = Buffer.from(text, "utf-8"); debug("Read clipboard text", { size: buffer.length }); return { buffer, mimeType: "text/plain" }; } catch (e) { spinner.stop(); throw e; } } async function confirm(message: string): Promise { process.stdout.write( `${chalk.hex(COLORS.label)("?")} ${message} ${chalk.hex(COLORS.dim)("(y/n)")}: `, ); const input = await new Promise((resolve) => { process.stdin.resume(); process.stdin.once("data", (data) => { process.stdin.pause(); resolve(data.toString().trim()); }); }); return TRUTHY.includes(input); } async function promptExtension(): Promise { while (true) { process.stdout.write( `${chalk.hex(COLORS.label)("?")} File extension ${chalk.hex(COLORS.dim)("(default: .txt)")}: `, ); const input = await new Promise((resolve) => { process.stdin.resume(); process.stdin.once("data", (data) => { process.stdin.pause(); resolve(data.toString().trim()); }); }); if (!input) { const useTxt = await confirm("Upload as .txt?"); if (useTxt) return "txt"; return null; } if (!input.startsWith(".")) { if (TRUTHY.includes(input)) return "txt"; if (FALSY.includes(input)) return null; } let ext = input.startsWith(".") ? input.slice(1) : input; if (ext.startsWith(".") || ext.includes("..")) { log( 'Invalid extension format. Use format like "txt", ".txt", or ".ts.map"', "error", ); continue; } return ext; } } async function cleanupTempFiles(...paths: string[]): Promise { for (const path of paths) { try { await $`rm -f ${path}`.quiet(); } catch { debug("Failed to cleanup temp file", path); } } } async function hasCommand(cmd: string): Promise { try { await $`which ${cmd}`.quiet(); return true; } catch { return false; } } async function runMediaCommand(cmd: string[]): Promise { try { const proc = Bun.spawn(cmd, { stdout: "ignore", stderr: "pipe", }); const exitCode = await proc.exited; if (exitCode !== 0) { const stderr = await new Response(proc.stderr).text(); const errorLines = stderr .split('\n') .filter(line => line.trim()) .slice(-10) // Last 10 non-empty lines .join('\n'); throw new Error(`Command failed with exit code ${exitCode}\n${errorLines}`); } } catch (e) { if (e instanceof Error) { throw e; } throw new Error(`Command failed: ${String(e)}`); } } async function writeTempFile(buffer: Buffer, ext = ""): Promise { const path = join(tmpdir(), `share-probe-${nanoid(8)}${ext}`); await Bun.write(path, buffer); return path; } function parseFixMode(value: string | undefined): FixMode { if (!value) return "prompt"; const v = value.toLowerCase(); if (v.startsWith("n")) return "never"; if (v.startsWith("a")) return "always"; return "prompt"; } function isAudioMime(mime: string): boolean { return mime.startsWith("audio/"); } async function probeVideoMetadata(buffer: Buffer): Promise { if (!(await hasCommand("ffprobe"))) { debug("ffprobe not found, skipping video validation"); return null; } const tmpPath = await writeTempFile(buffer); try { const result = await $`ffprobe -v error -show_format -show_streams -of json ${tmpPath}`.json(); const format = result.format || {}; const videoStream = result.streams?.find( (s: { codec_type: string }) => s.codec_type === "video", ); // Check for faststart by looking at format tags or probing atom order // A proper check would require parsing the file, but we can infer from tags const hasFaststart = format.tags?.major_brand === "isom" || (format.format_name?.includes("mov") && format.tags?.compatible_brands?.includes("isom")); return { duration: format.duration ? parseFloat(format.duration) : null, codec: videoStream?.codec_name?.toLowerCase() || null, hasFaststart: !!hasFaststart, }; } catch (e) { debug("ffprobe failed", e instanceof Error ? e.message : String(e)); return null; } finally { await cleanupTempFiles(tmpPath); } } async function probeVideo(buffer: Buffer): Promise { const probe = await probeVideoMetadata(buffer); if (!probe) return null; const issues: MediaIssue[] = []; // Missing duration metadata - remux fixes this and also adds faststart if (probe.duration === null) { issues.push({ id: "missing-duration", description: "Missing duration metadata", severity: "error", autoFix: true, fix: remuxVideo, }); // Skip faststart check since remux handles it } else if (!probe.hasFaststart) { // Only check faststart if duration exists (otherwise remux already handles it) issues.push({ id: "missing-faststart", description: "MP4 not optimized for streaming", severity: "warning", autoFix: true, fix: addFaststart, }); } // Incompatible codec - this is separate since it requires re-encoding if (probe.codec && INCOMPATIBLE_VIDEO_CODECS.includes(probe.codec)) { const isSmall = buffer.length < SIZE_THRESHOLDS.reencodeAuto; issues.push({ id: "incompatible-codec", description: `Codec '${probe.codec}' has limited browser/Discord support`, severity: "warning", autoFix: isSmall, fix: reencodeVideo, }); } return { issues, metadata: probe }; } async function probeImage( _buffer: Buffer, mime: string, ): Promise { const issues: MediaIssue[] = []; if (mime === "image/heic" || mime === "image/heif") { issues.push({ id: "heic-compat", description: "HEIC not supported in browsers", severity: "error", autoFix: true, fix: convertImageToJpeg, }); } if (mime === "image/avif") { issues.push({ id: "avif-compat", description: "AVIF has limited browser support", severity: "warning", autoFix: true, fix: convertImageToWebp, }); } return issues.length > 0 ? { issues, metadata: { originalMime: mime } } : null; } async function probeAudio(buffer: Buffer): Promise { if (!(await hasCommand("ffprobe"))) { debug("ffprobe not found, skipping audio validation"); return null; } const tmpPath = await writeTempFile(buffer); try { const result = await $`ffprobe -v error -show_format -of json ${tmpPath}`.json(); const format = result.format || {}; const issues: MediaIssue[] = []; if (!format.duration || format.duration === "N/A") { issues.push({ id: "missing-duration", description: "Missing duration metadata", severity: "error", autoFix: true, fix: remuxAudio, }); } return issues.length > 0 ? { issues, metadata: format } : null; } catch (e) { debug( "ffprobe failed for audio", e instanceof Error ? e.message : String(e), ); return null; } finally { await cleanupTempFiles(tmpPath); } } async function validateMedia( buffer: Buffer, mimeType: string, ): Promise { if (isVideoMime(mimeType)) { return probeVideo(buffer); } else if (isImageMime(mimeType)) { return probeImage(buffer, mimeType); } else if (isAudioMime(mimeType)) { return probeAudio(buffer); } return null; } async function applyFixes( buffer: Buffer, mimeType: string, issues: MediaIssue[], mode: FixMode, skipPrompts: boolean, ): Promise<{ buffer: Buffer; mimeType: string; applied: string[] }> { if (mode === "never" || issues.length === 0) { for (const issue of issues) { log(`Skipped: ${issue.description}`, "dim"); } return { buffer, mimeType, applied: [] }; } const applied: string[] = []; for (const issue of issues) { const shouldAuto = mode === "always" || (mode === "prompt" && issue.autoFix); if (shouldAuto) { log(`Fixing: ${issue.description}`, "info"); buffer = await issue.fix(buffer); applied.push(issue.id); } else if (mode === "prompt") { if (skipPrompts) { log(`Skipped: ${issue.description}`, "dim"); continue; } const proceed = await confirm(`Fix: ${issue.description}?`); if (proceed) { buffer = await issue.fix(buffer); applied.push(issue.id); } } } // Update mimeType based on applied fixes if (applied.includes("heic-compat")) { mimeType = "image/jpeg"; } else if (applied.includes("avif-compat")) { mimeType = "image/webp"; } else if (applied.includes("incompatible-codec")) { mimeType = "video/mp4"; } return { buffer, mimeType, applied }; } async function remuxVideo(buffer: Buffer): Promise { const spinner = new Spinner(); spinner.start("Remuxing video..."); const inputPath = await writeTempFile(buffer); const outputPath = join(tmpdir(), `share-remux-${nanoid(8)}.webm`); try { await runMediaCommand([ "ffmpeg", "-y", "-i", inputPath, "-c", "copy", "-fflags", "+genpts", outputPath ]); const result = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log("Fixed video metadata", "success"); return result; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nRemux error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("Video remux failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function addFaststart(buffer: Buffer): Promise { const spinner = new Spinner(); spinner.start("Optimizing for streaming..."); const inputPath = await writeTempFile(buffer); const outputPath = join(tmpdir(), `share-faststart-${nanoid(8)}.mp4`); try { await runMediaCommand([ "ffmpeg", "-y", "-i", inputPath, "-c", "copy", "-movflags", "+faststart", outputPath ]); const result = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log("Optimized for streaming", "success"); return result; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nFaststart optimization error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("Faststart optimization failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function reencodeVideo(buffer: Buffer): Promise { const spinner = new Spinner(); spinner.start("Re-encoding video for compatibility..."); const inputPath = await writeTempFile(buffer); const outputPath = join(tmpdir(), `share-reencode-${nanoid(8)}.mp4`); try { await runMediaCommand([ "ffmpeg", "-y", "-i", inputPath, "-c:v", "libx264", "-crf", "23", "-preset", "medium", "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", outputPath ]); const result = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log("Re-encoded to H.264", "success"); return result; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nRe-encode error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("Video re-encode failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function remuxAudio(buffer: Buffer): Promise { const spinner = new Spinner(); spinner.start("Fixing audio metadata..."); const inputPath = await writeTempFile(buffer); const outputPath = join(tmpdir(), `share-audio-${nanoid(8)}.mka`); try { await runMediaCommand([ "ffmpeg", "-y", "-i", inputPath, "-c", "copy", outputPath ]); const result = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log("Fixed audio metadata", "success"); return result; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nAudio remux error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("Audio remux failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function convertImageToJpeg(buffer: Buffer): Promise { const spinner = new Spinner(); spinner.start("Converting to JPEG..."); const inputPath = await writeTempFile(buffer); const outputPath = join(tmpdir(), `share-convert-${nanoid(8)}.jpg`); try { await runMediaCommand([ "convert", inputPath, "-quality", "90", outputPath ]); const result = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log("Converted to JPEG", "success"); return result; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nJPEG conversion error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("JPEG conversion failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function convertImageToWebp(buffer: Buffer): Promise { const spinner = new Spinner(); spinner.start("Converting to WebP..."); const inputPath = await writeTempFile(buffer); const outputPath = join(tmpdir(), `share-convert-${nanoid(8)}.webp`); try { await runMediaCommand([ "convert", inputPath, "-quality", "85", outputPath ]); const result = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log("Converted to WebP", "success"); return result; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nWebP conversion error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("WebP conversion failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function convertImage( buffer: Buffer, fromMime: string, shouldConvert: boolean, ): Promise { const needsConversion = fromMime === "image/bmp" || (shouldConvert && (fromMime === "image/heic" || fromMime === "image/heif" || fromMime === "image/tiff")); if (!needsConversion) return buffer; const spinner = new Spinner(); spinner.start("Converting image..."); const inputPath = join(tmpdir(), `share-input-${nanoid(8)}`); const outputPath = join(tmpdir(), `share-output-${nanoid(8)}.png`); try { await Bun.write(inputPath, buffer); if (fromMime === "image/heic" || fromMime === "image/heif") { await runMediaCommand(["convert", inputPath, "-quality", "90", outputPath]); } else { await runMediaCommand(["convert", inputPath, outputPath]); } const converted = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log(`Converted to ${fromMime === "image/bmp" ? "PNG" : "JPEG"}`, "success"); return converted; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nImage conversion error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("Image conversion failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function convertVideo(buffer: Buffer): Promise { const spinner = new Spinner(); spinner.start("Converting video (this may take a while)..."); const inputPath = join(tmpdir(), `share-input-${nanoid(8)}`); const outputPath = join(tmpdir(), `share-output-${nanoid(8)}.mp4`); try { await Bun.write(inputPath, buffer); await runMediaCommand([ "ffmpeg", "-y", "-i", inputPath, "-c:v", "libx264", "-crf", "28", "-preset", "slow", "-vf", "scale=-2:'min(720,ih)'", "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", outputPath ]); const converted = Buffer.from(await Bun.file(outputPath).arrayBuffer()); spinner.stop(); log("Converted to web-optimized MP4", "success"); return converted; } catch (e) { spinner.stop(); console.error(chalk.hex(COLORS.error)("\nConversion error:")); console.error(chalk.hex(COLORS.dim)(e instanceof Error ? e.message : String(e))); throw new Error("Video conversion failed"); } finally { await cleanupTempFiles(inputPath, outputPath); } } async function uploadToR2( buffer: Buffer, filename: string, mimeType: string, ): Promise { if ( !ENV.endpoint || !ENV.accessKeyId || !ENV.secretAccessKey || !ENV.bucket ) { throw new Error( "Missing R2 credentials - ensure all R2_* environment variables are set", ); } debug("S3 Client Config", { endpoint: ENV.endpoint, bucket: ENV.bucket, accessKeyIdLength: ENV.accessKeyId.length, }); const client = new S3Client({ region: "auto", endpoint: ENV.endpoint, credentials: { accessKeyId: ENV.accessKeyId, secretAccessKey: ENV.secretAccessKey, }, requestHandler: { requestTimeout: TIMING.requestTimeout, }, }); const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const key = `${year}/${month}/${filename}`; debug("Uploading to R2", { key, size: buffer.length, mimeType }); const progressBar = new ProgressBar(buffer.length); try { const upload = new Upload({ client, params: { Bucket: ENV.bucket, Key: key, Body: buffer, ContentType: mimeType, }, }); upload.on("httpUploadProgress", (progress) => { if (progress.loaded) { progressBar.update(progress.loaded); } }); console.log(`Uploading ${chalk.hex(COLORS.dim)(filename)}`); let timeoutId: TimeoutHandle; const uploadPromise = upload.done(); const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout( () => reject(new Error("Upload timeout after 60 seconds")), TIMING.uploadTimeout, ); }); debug("Waiting for upload to complete..."); try { await Promise.race([uploadPromise, timeoutPromise]); debug("Upload promise resolved"); } finally { clearTimeout(timeoutId!); } progressBar.finish(); debug("Progress bar finished"); upload.removeAllListeners(); debug("Event listeners removed"); const url = `${DOMAIN}/${key}`; debug("Returning result"); return { url, key, size: buffer.length }; } catch (e) { progressBar.finish(); if (VERBOSE && e instanceof Error) { debug("Upload error details", { error: e.message, stack: e.stack }); } throw new Error( `Upload failed: ${e instanceof Error ? e.message : String(e)}`, ); } finally { client.destroy(); } } function hasNanoidSuffix(filename: string): boolean { // nanoid default alphabet: A-Za-z0-9_- return /-[A-Za-z0-9_-]{8}\.[^.]+$/.test(filename); } async function main() { const { values, positionals } = parseArgs({ args: Bun.argv.slice(2), options: { verbose: { type: "boolean", short: "v" }, convert: { type: "boolean", short: "c" }, fix: { type: "string", short: "F" }, yes: { type: "boolean", short: "y" }, name: { type: "string", short: "n" }, help: { type: "boolean", short: "h" }, }, allowPositionals: true, }); if (values.help) { console.log(` ${chalk.hex(COLORS.success)("share")} - Upload files to R2 and copy the URL to clipboard ${chalk.hex(COLORS.label)("Usage:")} share [options] [file] ${chalk.hex(COLORS.label)("Arguments:")} file Optional file path to upload ${chalk.hex(COLORS.label)("Options:")} -v, --verbose Enable debug output -c, --convert Convert media before upload (re-encode video, etc.) -F, --fix Fix media issues: n=never, p=prompt (default), a=always -y, --yes Skip confirmation prompts -n, --name Custom filename (without extension) -h, --help Show this help ${chalk.hex(COLORS.label)("Fix Modes:")} -Fn, --fix=never Skip all fixes, upload as-is -Fp, --fix=prompt Auto-fix quick/lossless, prompt for slow/lossy (default) -Fa, --fix=always Apply all fixes automatically ${chalk.hex(COLORS.label)("Media Fixes (auto-applied by default):")} • Missing video duration/metadata (remux) • MP4 streaming optimization (faststart) • HEIC/AVIF browser compatibility (convert to JPEG/WebP) ${chalk.hex(COLORS.label)("Media Fixes (prompted or with -Fa):")} • Video re-encoding for codec compatibility (H.265/AV1 → H.264) • Large file optimizations ${chalk.hex(COLORS.label)("Examples:")} share # Upload clipboard content share screenshot.png # Upload specific file cat file.txt | share # Upload from stdin share -c large-video.mov # Convert/re-encode then upload share -Fa video.webm # Fix all issues automatically ${chalk.hex(COLORS.label)("Note:")} R2 credentials are embedded via chezmoi template from Doppler. Use 'chezmoi apply' to update the deployed script with new credentials. `); return; } VERBOSE = values.verbose || false; // Credentials are embedded via chezmoi template - check ENV object instead if (!ENV.endpoint || !ENV.accessKeyId || !ENV.secretAccessKey || !ENV.bucket) { log("Missing R2 credentials - chezmoi template may not have been processed correctly", "error"); process.exit(1); } try { let source: UploadSource; if (positionals[0]) { source = await readFromFile(positionals[0]); } else if (hasStdinData()) { source = await readFromStdin(); } else { source = await readFromClipboard(); } let { buffer, filename, mimeType } = source; if (!mimeType) { mimeType = await detectMimeType(buffer, filename); } log(`Detected: ${mimeType} (${formatBytes(buffer.length)})`, "success"); if (isTextMime(mimeType) && !values.yes) { const ext = await promptExtension(); if (!ext) { log("Upload cancelled", "error"); process.exit(0); } mimeType = getMimeForExtension(ext); const baseName = values.name || (filename ? basename(filename, extname(filename)) : "text"); filename = `${baseName}-${nanoid(8)}.${ext}`; } // Determine fix mode: -c implies -Fa (always fix), otherwise parse -F flag const fixMode = values.convert ? "always" : parseFixMode(values.fix); // Validate and fix media issues if (fixMode !== "never") { const probeResult = await validateMedia(buffer, mimeType); if (probeResult && probeResult.issues.length > 0) { debug("Media issues detected", probeResult.metadata); const fixResult = await applyFixes( buffer, mimeType, probeResult.issues, fixMode, values.yes || false, ); buffer = fixResult.buffer; mimeType = fixResult.mimeType; if (fixResult.applied.length > 0) { debug("Applied fixes", fixResult.applied); } } } if (!values.yes) { let needsConfirm = false; let reason = ""; if (isImageMime(mimeType) && buffer.length > SIZE_THRESHOLDS.image) { needsConfirm = true; reason = `image is larger than ${formatBytes(SIZE_THRESHOLDS.image)}`; } else if ( isVideoMime(mimeType) && buffer.length > SIZE_THRESHOLDS.video ) { needsConfirm = true; reason = `video is larger than ${formatBytes(SIZE_THRESHOLDS.video)}`; } else if ( !isImageMime(mimeType) && !isVideoMime(mimeType) && buffer.length > SIZE_THRESHOLDS.binary ) { needsConfirm = true; reason = `file is larger than ${formatBytes(SIZE_THRESHOLDS.binary)}`; } if (needsConfirm) { const shouldContinue = await confirm(`Upload large file? (${reason})`); if (!shouldContinue) { log("Upload cancelled", "error"); process.exit(0); } } } // Legacy conversion path: BMP always converts, -c triggers full re-encode if (isImageMime(mimeType) && mimeType === "image/bmp") { buffer = await convertImage(buffer, mimeType, false); mimeType = "image/png"; } else if (isVideoMime(mimeType) && values.convert) { // Full video conversion (re-encode with scaling, etc.) only with -c buffer = await convertVideo(buffer); mimeType = "video/mp4"; } if (!filename) { const ext = getExtensionForMime(mimeType); const baseName = values.name || "upload"; filename = `${baseName}-${nanoid(8)}.${ext}`; } else if (!hasNanoidSuffix(filename)) { const ext = extname(filename); const base = basename(filename, ext); const finalName = values.name || base; filename = `${finalName}-${nanoid(8)}${ext}`; } debug("Calling uploadToR2..."); const result = await uploadToR2(buffer, filename, mimeType); debug("uploadToR2 returned", { url: result.url }); debug("Copying to clipboard..."); await copyToClipboard(result.url); debug("Clipboard copy complete"); log(result.url, "success"); log("Copied to clipboard", "success"); debug("About to exit..."); if (usedStdin) { process.stdin.destroy(); } process.exit(0); } catch (e) { if (e instanceof Error) { log(e.message, "error"); } else { log("Unknown error occurred", "error"); } process.exit(1); } } main();