vidpipe 1.3.2 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6521 -4003
- package/dist/index.js.map +1 -1
- package/dist/public/index.html +235 -295
- package/package.json +5 -5
- package/assets/features-infographic.png +0 -3
- package/assets/models/ultraface-320.onnx +0 -0
- package/assets/review-ui.png +0 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/cli.ts","../src/core/paths.ts","../src/core/fileSystem.ts","../src/core/env.ts","../src/config/environment.ts","../src/core/watcher.ts","../src/core/logger.ts","../src/services/fileWatcher.ts","../src/core/text.ts","../src/core/ffmpeg.ts","../src/services/videoIngestion.ts","../src/tools/ffmpeg/audioExtraction.ts","../src/core/ai.ts","../src/config/brand.ts","../src/config/pricing.ts","../src/services/costTracker.ts","../src/tools/whisper/whisperClient.ts","../src/services/transcription.ts","../src/tools/captions/captionGenerator.ts","../src/services/captionGeneration.ts","../src/providers/CopilotProvider.ts","../src/providers/OpenAIProvider.ts","../src/providers/ClaudeProvider.ts","../src/providers/index.ts","../src/config/modelConfig.ts","../src/agents/BaseAgent.ts","../src/tools/ffmpeg/frameCapture.ts","../src/agents/SummaryAgent.ts","../src/core/process.ts","../src/tools/ffmpeg/clipExtraction.ts","../src/tools/ffmpeg/captionBurning.ts","../src/core/media.ts","../src/tools/ffmpeg/faceDetection.ts","../src/tools/ffmpeg/aspectRatio.ts","../src/agents/ShortsAgent.ts","../src/agents/MediumVideoAgent.ts","../src/types/index.ts","../src/agents/SocialMediaAgent.ts","../src/agents/BlogAgent.ts","../src/agents/ChapterAgent.ts","../src/services/gitOperations.ts","../src/services/platformContentStrategy.ts","../src/services/postStore.ts","../src/services/queueBuilder.ts","../src/tools/ffmpeg/silenceDetection.ts","../src/tools/ffmpeg/singlePassEdit.ts","../src/agents/SilenceRemovalAgent.ts","../src/pipeline.ts","../src/core/network.ts","../src/services/lateApi.ts","../src/services/scheduleConfig.ts","../src/commands/doctor.ts","../src/commands/init.ts","../src/services/scheduler.ts","../src/commands/schedule.ts","../src/core/http.ts","../src/services/accountMapping.ts","../src/review/routes.ts","../src/review/server.ts","../src/index.ts"],"sourcesContent":["import { Command } from 'commander'\nimport readline from 'readline'\nimport open from 'open'\n\nexport { Command }\nexport type { ReadLine } from 'readline'\n\n/** Create a readline interface for interactive prompts. */\nexport function createReadlineInterface(opts?: readline.ReadLineOptions): readline.Interface {\n return readline.createInterface(opts ?? { input: process.stdin, output: process.stdout })\n}\n\n/** Open a URL in the default browser. */\nexport async function openUrl(url: string): Promise<void> {\n await open(url)\n}\n","// Re-export all commonly used path functions\nexport { join, resolve, dirname, basename, extname, parse, sep, relative, normalize } from 'path'\nexport { fileURLToPath } from 'url'\n\n// Also re-export the path module itself for the rare cases where namespace import is needed\nimport pathMod from 'path'\nexport { pathMod }\n\nimport { existsSync } from 'fs'\nimport { join, resolve, dirname, parse } from 'path'\nimport { fileURLToPath } from 'url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n/**\n * Walk up from `startDir` until a directory containing `package.json` is found.\n * Throws if the filesystem root is reached without finding one.\n */\nexport function findRoot(startDir: string): string {\n let dir = resolve(startDir)\n while (true) {\n if (existsSync(join(dir, 'package.json'))) return dir\n const parent = dirname(dir)\n if (parent === dir) throw new Error(`Could not find project root from ${startDir}`)\n dir = parent\n }\n}\n\nlet _cachedRoot: string | undefined\n\n/** Get the project root directory. */\nexport function projectRoot(): string {\n if (!_cachedRoot) _cachedRoot = findRoot(__dirname)\n return _cachedRoot\n}\n\n/** Get path within the assets directory. */\nexport function assetsDir(...segments: string[]): string {\n return join(projectRoot(), 'assets', ...segments)\n}\n\n/**\n * Resolve the fonts directory — checks bundled (dist/fonts/) first,\n * falls back to dev (assets/fonts/).\n */\nexport function fontsDir(): string {\n const bundled = resolve(projectRoot(), 'dist', 'fonts')\n return existsSync(bundled) ? bundled : assetsDir('fonts')\n}\n\n/**\n * Resolve the models directory — checks bundled (dist/models/) first,\n * falls back to dev (assets/models/).\n */\nexport function modelsDir(): string {\n const bundled = resolve(projectRoot(), 'dist', 'models')\n return existsSync(bundled) ? bundled : assetsDir('models')\n}\n\n/** Get the recordings directory, optionally for a specific slug. */\nexport function recordingsDir(slug?: string): string {\n return slug ? join(projectRoot(), 'recordings', slug) : join(projectRoot(), 'recordings')\n}\n","import {\n promises as fsp,\n existsSync,\n statSync,\n readdirSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n createReadStream,\n createWriteStream,\n closeSync,\n} from 'fs'\nimport type { Stats, Dirent, ReadStream, WriteStream } from 'fs'\nimport os from 'os'\nimport { join, dirname } from './paths.js'\n\nexport type { Stats, Dirent, ReadStream, WriteStream }\n\n// ── Reads ──────────────────────────────────────────────────────\n\n/** Read and parse a JSON file. Throws descriptive error on ENOENT or parse failure. */\nexport async function readJsonFile<T>(filePath: string, defaultValue?: T): Promise<T> {\n let raw: string\n try {\n raw = await fsp.readFile(filePath, 'utf-8')\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n if (arguments.length >= 2) return defaultValue as T\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n try {\n return JSON.parse(raw) as T\n } catch (err: unknown) {\n throw new Error(`Failed to parse JSON at ${filePath}: ${(err as Error).message}`)\n }\n}\n\n/** Read a text file as UTF-8 string. Throws \"File not found: <path>\" on ENOENT. */\nexport async function readTextFile(filePath: string): Promise<string> {\n try {\n return await fsp.readFile(filePath, 'utf-8')\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** Sync variant of readTextFile. */\nexport function readTextFileSync(filePath: string): string {\n try {\n return readFileSync(filePath, 'utf-8')\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** List directory contents. Throws \"Directory not found: <path>\" on ENOENT. */\nexport async function listDirectory(dirPath: string): Promise<string[]> {\n try {\n return await fsp.readdir(dirPath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dirPath}`)\n }\n throw err\n }\n}\n\n/** List directory with Dirent objects. Throws \"Directory not found: <path>\" on ENOENT. */\nexport async function listDirectoryWithTypes(dirPath: string): Promise<Dirent[]> {\n try {\n return await fsp.readdir(dirPath, { withFileTypes: true })\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dirPath}`)\n }\n throw err\n }\n}\n\n/** Sync variant of listDirectory. */\nexport function listDirectorySync(dirPath: string): string[] {\n try {\n return readdirSync(dirPath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dirPath}`)\n }\n throw err\n }\n}\n\n/** Check if file/dir exists (async, using stat). */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await fsp.stat(filePath)\n return true\n } catch {\n return false\n }\n}\n\n/** Check if file/dir exists (sync). */\nexport function fileExistsSync(filePath: string): boolean {\n return existsSync(filePath)\n}\n\n/** Get file stats. Throws \"File not found: <path>\" on ENOENT. */\nexport async function getFileStats(filePath: string): Promise<Stats> {\n try {\n return await fsp.stat(filePath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** Sync variant. */\nexport function getFileStatsSync(filePath: string): Stats {\n try {\n return statSync(filePath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** Create a read stream. */\nexport function openReadStream(filePath: string): ReadStream {\n return createReadStream(filePath)\n}\n\n// ── Writes ─────────────────────────────────────────────────────\n\n/** Write data as JSON. Creates parent dirs. */\nexport async function writeJsonFile(filePath: string, data: unknown): Promise<void> {\n await fsp.mkdir(dirname(filePath), { recursive: true })\n await fsp.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')\n}\n\n/** Write text file. Creates parent dirs. */\nexport async function writeTextFile(filePath: string, content: string): Promise<void> {\n if (typeof content !== 'string') throw new TypeError('content must be a string')\n await fsp.mkdir(dirname(filePath), { recursive: true })\n await fsp.writeFile(filePath, content, 'utf-8')\n}\n\n/** Sync variant of writeTextFile. */\nexport function writeTextFileSync(filePath: string, content: string): void {\n if (typeof content !== 'string') throw new TypeError('content must be a string')\n mkdirSync(dirname(filePath), { recursive: true })\n writeFileSync(filePath, content, 'utf-8')\n}\n\n/** Ensure directory exists (recursive). */\nexport async function ensureDirectory(dirPath: string): Promise<void> {\n await fsp.mkdir(dirPath, { recursive: true })\n}\n\n/** Sync variant. */\nexport function ensureDirectorySync(dirPath: string): void {\n mkdirSync(dirPath, { recursive: true })\n}\n\n/** Copy file. Ensures destination parent dir exists. */\nexport async function copyFile(src: string, dest: string): Promise<void> {\n await fsp.mkdir(dirname(dest), { recursive: true })\n await fsp.copyFile(src, dest)\n}\n\n/** Move/rename file. Falls back to copy+delete on EXDEV. */\nexport async function moveFile(src: string, dest: string): Promise<void> {\n await fsp.mkdir(dirname(dest), { recursive: true })\n try {\n await fsp.rename(src, dest)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'EXDEV') {\n await copyFile(src, dest)\n await removeFile(src)\n return\n }\n throw err\n }\n}\n\n/** Remove file (ignores ENOENT). */\nexport async function removeFile(filePath: string): Promise<void> {\n try {\n await fsp.unlink(filePath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return\n throw err\n }\n}\n\n/** Remove directory. */\nexport async function removeDirectory(\n dirPath: string,\n opts?: { recursive?: boolean; force?: boolean },\n): Promise<void> {\n try {\n await fsp.rm(dirPath, { recursive: opts?.recursive ?? false, force: opts?.force ?? false })\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return\n throw err\n }\n}\n\n/** Create a write stream. */\nexport function openWriteStream(filePath: string): WriteStream {\n return createWriteStream(filePath)\n}\n\n/** Close a file descriptor (sync). */\nexport function closeFileDescriptor(fd: number): void {\n closeSync(fd)\n}\n\n// ── Temp Dir ───────────────────────────────────────────────────\n\n/** Create a temporary directory with the given prefix. Caller is responsible for cleanup. */\nexport async function makeTempDir(prefix: string): Promise<string> {\n return fsp.mkdtemp(join(os.tmpdir(), prefix))\n}\n\n/** Run fn inside a temp directory, auto-cleanup on completion or error. */\nexport async function withTempDir<T>(prefix: string, fn: (tempDir: string) => Promise<T>): Promise<T> {\n const tempDir = await fsp.mkdtemp(join(os.tmpdir(), prefix))\n try {\n return await fn(tempDir)\n } finally {\n await removeDirectory(tempDir, { recursive: true, force: true })\n }\n}\n\n/** Rename/move a file or directory (fs.rename). Falls back to copy+delete on EXDEV. */\nexport async function renameFile(oldPath: string, newPath: string): Promise<void> {\n try {\n await fsp.rename(oldPath, newPath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'EXDEV') {\n await copyFile(oldPath, newPath)\n await removeFile(oldPath)\n } else {\n throw err\n }\n }\n}\n\n/** Copy directory recursively (fs.cp). */\nexport async function copyDirectory(src: string, dest: string): Promise<void> {\n await fsp.cp(src, dest, { recursive: true })\n}\n\n/** Write file with raw options (flag, mode, etc.) for security-sensitive writes. */\nexport async function writeFileRaw(\n filePath: string,\n data: string,\n opts: { encoding?: BufferEncoding; flag?: string; mode?: number },\n): Promise<void> {\n await fsp.writeFile(filePath, data, opts)\n}\n\n// ── Specialized ────────────────────────────────────────────────\n\n/** List .ttf and .otf font files in a directory. Throws if dir missing. */\nexport async function listFontFiles(fontsDir: string): Promise<string[]> {\n const entries = await listDirectory(fontsDir)\n return entries.filter((f) => /\\.(ttf|otf)$/i.test(f))\n}\n\n/** Copy all .ttf/.otf fonts from fontsDir to destDir. */\nexport async function copyFontsToDir(fontsDir: string, destDir: string): Promise<void> {\n const fonts = await listFontFiles(fontsDir)\n await ensureDirectory(destDir)\n await Promise.all(fonts.map((f) => copyFile(join(fontsDir, f), join(destDir, f))))\n}\n\n// ── Third-party re-exports ─────────────────────────────────────\n\nexport { default as tmp } from 'tmp'\n","import dotenv from 'dotenv'\n\n/** Load environment variables from a .env file. */\nexport function loadEnvFile(envPath?: string): void {\n dotenv.config(envPath ? { path: envPath } : undefined)\n}\n","import { join } from '../core/paths.js'\nimport { fileExistsSync } from '../core/fileSystem.js'\nimport { loadEnvFile } from '../core/env.js'\n\n// Load .env file from repo root\nconst envPath = join(process.cwd(), '.env')\nif (fileExistsSync(envPath)) {\n loadEnvFile(envPath)\n}\n\nexport interface AppEnvironment {\n OPENAI_API_KEY: string\n WATCH_FOLDER: string\n REPO_ROOT: string\n FFMPEG_PATH: string\n FFPROBE_PATH: string\n EXA_API_KEY: string\n EXA_MCP_URL: string\n LLM_PROVIDER: string\n LLM_MODEL: string\n ANTHROPIC_API_KEY: string\n OUTPUT_DIR: string\n BRAND_PATH: string\n VERBOSE: boolean\n SKIP_GIT: boolean\n SKIP_SILENCE_REMOVAL: boolean\n SKIP_SHORTS: boolean\n SKIP_MEDIUM_CLIPS: boolean\n SKIP_SOCIAL: boolean\n SKIP_CAPTIONS: boolean\n LATE_API_KEY: string\n LATE_PROFILE_ID: string\n SKIP_SOCIAL_PUBLISH: boolean\n}\n\nexport interface CLIOptions {\n watchDir?: string\n outputDir?: string\n openaiKey?: string\n exaKey?: string\n brand?: string\n verbose?: boolean\n git?: boolean\n silenceRemoval?: boolean\n shorts?: boolean\n mediumClips?: boolean\n social?: boolean\n captions?: boolean\n socialPublish?: boolean\n lateApiKey?: string\n lateProfileId?: string\n}\n\nlet config: AppEnvironment | null = null\n\nexport function validateRequiredKeys(): void {\n if (!config?.OPENAI_API_KEY && !process.env.OPENAI_API_KEY) {\n throw new Error('Missing required: OPENAI_API_KEY (set via --openai-key or env var)')\n }\n}\n\n/** Merge CLI options → env vars → defaults. Call before getConfig(). */\nexport function initConfig(cli: CLIOptions = {}): AppEnvironment {\n const repoRoot = process.env.REPO_ROOT || process.cwd()\n\n config = {\n OPENAI_API_KEY: cli.openaiKey || process.env.OPENAI_API_KEY || '',\n WATCH_FOLDER: cli.watchDir || process.env.WATCH_FOLDER || join(repoRoot, 'watch'),\n REPO_ROOT: repoRoot,\n FFMPEG_PATH: process.env.FFMPEG_PATH || 'ffmpeg', // legacy; prefer ffmpegResolver\n FFPROBE_PATH: process.env.FFPROBE_PATH || 'ffprobe', // legacy; prefer ffmpegResolver\n EXA_API_KEY: cli.exaKey || process.env.EXA_API_KEY || '',\n EXA_MCP_URL: process.env.EXA_MCP_URL || 'https://mcp.exa.ai/mcp',\n LLM_PROVIDER: process.env.LLM_PROVIDER || 'copilot',\n LLM_MODEL: process.env.LLM_MODEL || '',\n ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || '',\n OUTPUT_DIR:cli.outputDir || process.env.OUTPUT_DIR || join(repoRoot, 'recordings'),\n BRAND_PATH: cli.brand || process.env.BRAND_PATH || join(repoRoot, 'brand.json'),\n VERBOSE: cli.verbose ?? false,\n SKIP_GIT: cli.git === false,\n SKIP_SILENCE_REMOVAL: cli.silenceRemoval === false,\n SKIP_SHORTS: cli.shorts === false,\n SKIP_MEDIUM_CLIPS: cli.mediumClips === false,\n SKIP_SOCIAL: cli.social === false,\n SKIP_CAPTIONS: cli.captions === false,\n LATE_API_KEY: cli.lateApiKey || process.env.LATE_API_KEY || '',\n LATE_PROFILE_ID: cli.lateProfileId || process.env.LATE_PROFILE_ID || '',\n SKIP_SOCIAL_PUBLISH: cli.socialPublish === false,\n }\n\n return config\n}\n\nexport function getConfig(): AppEnvironment {\n if (config) {\n return config\n }\n\n // Fallback: init with no CLI options (pure env-var mode)\n return initConfig()\n}\n","export { watch, type FSWatcher } from 'chokidar'\nexport { EventEmitter } from 'events'\n","import winston from 'winston'\n\n/**\n * Sanitize user input for logging to prevent log injection attacks.\n * Removes or escapes newlines, carriage returns, and other control characters.\n */\nexport function sanitizeForLog(value: unknown): string {\n if (value === null || value === undefined) return String(value)\n const str = String(value)\n return str.replace(/[\\r\\n\\t]/g, (c) => {\n switch (c) {\n case '\\r': return '\\\\r'\n case '\\n': return '\\\\n'\n case '\\t': return '\\\\t'\n default: return c\n }\n })\n}\n\nconst logger = winston.createLogger({\n level: 'info',\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.printf(({ timestamp, level, message }) => {\n return `${timestamp} [${level.toUpperCase()}]: ${message}`\n })\n ),\n transports: [new winston.transports.Console()],\n})\n\nexport function setVerbose(): void {\n logger.level = 'debug'\n}\n\nexport default logger\n","import { watch, type FSWatcher } from '../core/watcher.js'\nimport { getConfig } from '../config/environment'\nimport { EventEmitter } from '../core/watcher.js'\nimport { join, extname } from '../core/paths.js'\nimport { fileExistsSync, ensureDirectorySync, getFileStatsSync, listDirectorySync } from '../core/fileSystem.js'\nimport logger from '../config/logger'\n\nexport interface FileWatcherOptions {\n processExisting?: boolean\n}\n\nexport class FileWatcher extends EventEmitter {\n private watchFolder: string\n private watcher: FSWatcher | null = null\n private processExisting: boolean\n\n constructor(options: FileWatcherOptions = {}) {\n super()\n const config = getConfig()\n this.watchFolder = config.WATCH_FOLDER\n this.processExisting = options.processExisting ?? false\n\n if (!fileExistsSync(this.watchFolder)) {\n ensureDirectorySync(this.watchFolder)\n logger.info(`Created watch folder: ${this.watchFolder}`)\n }\n }\n\n private static readonly MIN_FILE_SIZE = 1024 * 1024 // 1MB\n private static readonly EXTRA_STABILITY_DELAY = 3000\n\n /** Read file size, wait, read again — if it changed the file is still being written. */\n private async isFileStable(filePath: string): Promise<boolean> {\n try {\n const sizeBefore = getFileStatsSync(filePath).size\n await new Promise((resolve) => setTimeout(resolve, FileWatcher.EXTRA_STABILITY_DELAY))\n const sizeAfter = getFileStatsSync(filePath).size\n return sizeBefore === sizeAfter\n } catch {\n return false\n }\n }\n\n private async handleDetectedFile(filePath: string): Promise<void> {\n if (extname(filePath).toLowerCase() !== '.mp4') {\n logger.debug(`[watcher] Ignoring non-mp4 file: ${filePath}`)\n return\n }\n\n let fileSize: number\n try {\n fileSize = getFileStatsSync(filePath).size\n } catch (err) {\n logger.warn(`[watcher] Could not stat file (may have been removed): ${filePath}`)\n return\n }\n\n logger.debug(`[watcher] File size: ${(fileSize / 1024 / 1024).toFixed(1)} MB — ${filePath}`)\n if (fileSize < FileWatcher.MIN_FILE_SIZE) {\n logger.warn(`Skipping small file (${fileSize} bytes), likely a failed recording: ${filePath}`)\n return\n }\n\n const stable = await this.isFileStable(filePath)\n if (!stable) {\n logger.warn(`File is still being written, skipping for now: ${filePath}`)\n return\n }\n\n logger.info(`New video detected: ${filePath}`)\n this.emit('new-video', filePath)\n }\n\n private scanExistingFiles(): void {\n let files: string[]\n try {\n files = listDirectorySync(this.watchFolder)\n } catch (err: any) {\n if (err?.code === 'ENOENT') {\n logger.warn(`Watch folder does not exist, skipping scan: ${this.watchFolder}`)\n return\n }\n throw err\n }\n for (const file of files) {\n if (extname(file).toLowerCase() === '.mp4') {\n const filePath = join(this.watchFolder, file)\n this.handleDetectedFile(filePath).catch(err =>\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\n )\n }\n }\n }\n\n start(): void {\n this.watcher = watch(this.watchFolder, {\n persistent: true,\n ignoreInitial: true,\n depth: 0,\n atomic: 100,\n // Polling is more reliable on Windows for detecting renames (e.g. Bandicam temp→final)\n usePolling: true,\n interval: 500,\n awaitWriteFinish: {\n stabilityThreshold: 3000,\n pollInterval: 200,\n },\n })\n\n this.watcher.on('add', (filePath: string) => {\n logger.debug(`[watcher] 'add' event: ${filePath}`)\n this.handleDetectedFile(filePath).catch(err =>\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\n )\n })\n\n this.watcher.on('change', (filePath: string) => {\n logger.debug(`[watcher] 'change' event: ${filePath}`)\n if (extname(filePath).toLowerCase() !== '.mp4') return\n logger.info(`Change detected on video file: ${filePath}`)\n this.handleDetectedFile(filePath).catch(err =>\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\n )\n })\n\n this.watcher.on('unlink', (filePath: string) => {\n logger.debug(`[watcher] 'unlink' event: ${filePath}`)\n })\n\n this.watcher.on('raw', (event: string, rawPath: string, details: unknown) => {\n logger.debug(`[watcher] raw event=${event} path=${rawPath}`)\n })\n\n this.watcher.on('error', (error: unknown) => {\n logger.error(`File watcher error: ${error instanceof Error ? error.message : String(error)}`)\n })\n\n this.watcher.on('ready', () => {\n logger.info('File watcher is fully initialized and ready')\n if (this.processExisting) {\n this.scanExistingFiles()\n }\n })\n\n logger.info(`Watching for new .mp4 files in: ${this.watchFolder}`)\n }\n\n stop(): void {\n if (this.watcher) {\n this.watcher.close()\n this.watcher = null\n logger.info('File watcher stopped')\n }\n }\n}\n","import slugifyLib from 'slugify'\nimport { v4 as uuidv4 } from 'uuid'\n\n/** Slugify text for use in URLs and file names. */\nexport function slugify(text: string, opts?: { lower?: boolean; strict?: boolean; replacement?: string }): string {\n return slugifyLib(text, { lower: true, strict: true, ...opts })\n}\n\n/** Generate a UUID v4. */\nexport function generateId(): string {\n return uuidv4()\n}\n","import ffmpegLib from 'fluent-ffmpeg'\nimport { createRequire } from 'module'\nimport { existsSync } from 'fs'\nimport logger from './logger.js'\nimport { getConfig } from '../config/environment.js'\n\nconst require = createRequire(import.meta.url)\n\n/** Get the resolved path to the FFmpeg binary. */\nexport function getFFmpegPath(): string {\n const config = getConfig();\n if (config.FFMPEG_PATH && config.FFMPEG_PATH !== 'ffmpeg') {\n logger.debug(`FFmpeg: using FFMPEG_PATH config: ${config.FFMPEG_PATH}`);\n return config.FFMPEG_PATH;\n }\n try {\n const staticPath = require('ffmpeg-static') as string;\n if (staticPath && existsSync(staticPath)) {\n logger.debug(`FFmpeg: using ffmpeg-static: ${staticPath}`);\n return staticPath;\n }\n } catch { /* ffmpeg-static not available */ }\n logger.debug('FFmpeg: falling back to system PATH');\n return 'ffmpeg';\n}\n\n/** Get the resolved path to the FFprobe binary. */\nexport function getFFprobePath(): string {\n const config = getConfig();\n if (config.FFPROBE_PATH && config.FFPROBE_PATH !== 'ffprobe') {\n logger.debug(`FFprobe: using FFPROBE_PATH config: ${config.FFPROBE_PATH}`);\n return config.FFPROBE_PATH;\n }\n try {\n const { path: probePath } = require('@ffprobe-installer/ffprobe') as { path: string };\n if (probePath && existsSync(probePath)) {\n logger.debug(`FFprobe: using @ffprobe-installer/ffprobe: ${probePath}`);\n return probePath;\n }\n } catch { /* @ffprobe-installer/ffprobe not available */ }\n logger.debug('FFprobe: falling back to system PATH');\n return 'ffprobe';\n}\n\n/** Create a pre-configured fluent-ffmpeg instance. */\nexport function createFFmpeg(input?: string): ffmpegLib.FfmpegCommand {\n const cmd = input ? ffmpegLib(input) : ffmpegLib()\n cmd.setFfmpegPath(getFFmpegPath())\n cmd.setFfprobePath(getFFprobePath())\n return cmd\n}\n\n/** Promisified ffprobe — get media file metadata. */\nexport function ffprobe(filePath: string): Promise<ffmpegLib.FfprobeData> {\n return new Promise((resolve, reject) => {\n ffmpegLib.setFfprobePath(getFFprobePath())\n ffmpegLib.ffprobe(filePath, (err, data) => {\n if (err) reject(err)\n else resolve(data)\n })\n })\n}\n\n// Re-export fluent-ffmpeg for cases where direct access is needed\nexport { ffmpegLib as fluent }\nexport type { FfmpegCommand, FfprobeData } from 'fluent-ffmpeg'\n","import { join, basename, extname } from '../core/paths.js'\nimport { fileExistsSync, ensureDirectory, copyFile, getFileStats, listDirectory, removeDirectory, removeFile, openReadStream, openWriteStream } from '../core/fileSystem.js'\nimport { slugify } from '../core/text.js'\nimport { ffprobe } from '../core/ffmpeg.js'\nimport { VideoFile } from '../types'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\nasync function getVideoMetadata(filePath: string): Promise<{ duration: number }> {\n const metadata = await ffprobe(filePath)\n return { duration: metadata.format.duration ?? 0 }\n}\n\nexport async function ingestVideo(sourcePath: string): Promise<VideoFile> {\n const config = getConfig()\n const baseName = basename(sourcePath, extname(sourcePath))\n const slug = slugify(baseName, { lower: true })\n\n const recordingsDir = join(config.OUTPUT_DIR, slug)\n const thumbnailsDir = join(recordingsDir, 'thumbnails')\n const shortsDir = join(recordingsDir, 'shorts')\n const socialPostsDir = join(recordingsDir, 'social-posts')\n\n logger.info(`Ingesting video: ${sourcePath} → ${slug}`)\n\n // Clean stale artifacts if output folder already exists\n if (fileExistsSync(recordingsDir)) {\n logger.warn(`Output folder already exists, cleaning previous artifacts: ${recordingsDir}`)\n\n const subDirs = ['thumbnails', 'shorts', 'social-posts', 'chapters', 'medium-clips', 'captions']\n for (const sub of subDirs) {\n await removeDirectory(join(recordingsDir, sub), { recursive: true, force: true })\n }\n\n const stalePatterns = [\n 'transcript.json', 'transcript-edited.json',\n 'captions.srt', 'captions.vtt', 'captions.ass',\n 'summary.md', 'blog-post.md', 'README.md',\n ]\n for (const pattern of stalePatterns) {\n await removeFile(join(recordingsDir, pattern))\n }\n\n const files = await listDirectory(recordingsDir)\n for (const file of files) {\n if (file.endsWith('-edited.mp4') || file.endsWith('-captioned.mp4')) {\n await removeFile(join(recordingsDir, file))\n }\n }\n }\n\n await ensureDirectory(recordingsDir)\n await ensureDirectory(thumbnailsDir)\n await ensureDirectory(shortsDir)\n await ensureDirectory(socialPostsDir)\n\n const destFilename = `${slug}.mp4`\n const destPath = join(recordingsDir, destFilename)\n\n let needsCopy = true\n try {\n const destStats = await getFileStats(destPath)\n const srcStats = await getFileStats(sourcePath)\n if (destStats.size === srcStats.size) {\n logger.info(`Video already copied (same size), skipping copy`)\n needsCopy = false\n }\n } catch {\n // Dest doesn't exist, need to copy\n }\n\n if (needsCopy) {\n await new Promise<void>((resolve, reject) => {\n const readStream = openReadStream(sourcePath)\n const writeStream = openWriteStream(destPath)\n readStream.on('error', reject)\n writeStream.on('error', reject)\n writeStream.on('finish', resolve)\n readStream.pipe(writeStream)\n })\n logger.info(`Copied video to ${destPath}`)\n }\n\n let duration = 0\n try {\n const meta = await getVideoMetadata(destPath)\n duration = meta.duration\n } catch (err) {\n logger.warn(`ffprobe failed, continuing without duration metadata: ${err instanceof Error ? err.message : String(err)}`)\n }\n const stats = await getFileStats(destPath)\n\n logger.info(`Video metadata: duration=${duration}s, size=${stats.size} bytes`)\n\n return {\n originalPath: sourcePath,\n repoPath: destPath,\n videoDir: recordingsDir,\n slug,\n filename: destFilename,\n duration,\n size: stats.size,\n createdAt: new Date(),\n }\n}\n","import { createFFmpeg, ffprobe } from '../../core/ffmpeg.js'\nimport { ensureDirectory, getFileStats } from '../../core/fileSystem.js'\nimport { dirname, extname } from '../../core/paths.js'\nimport logger from '../../config/logger'\n\nexport interface ExtractAudioOptions {\n /** Output format: 'mp3' (default, smaller) or 'wav' */\n format?: 'mp3' | 'wav';\n}\n\n/**\n * Extract audio from a video file to mono MP3 at 64kbps (small enough for Whisper).\n * A 10-minute video produces ~5MB MP3 vs ~115MB WAV.\n */\nexport async function extractAudio(\n videoPath: string,\n outputPath: string,\n options: ExtractAudioOptions = {},\n): Promise<string> {\n const { format = 'mp3' } = options;\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n logger.info(`Extracting audio (${format}): ${videoPath} → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n const command = createFFmpeg(videoPath).noVideo().audioChannels(1);\n\n if (format === 'mp3') {\n command.audioCodec('libmp3lame').audioBitrate('64k').audioFrequency(16000);\n } else {\n command.audioCodec('pcm_s16le').audioFrequency(16000);\n }\n\n command\n .output(outputPath)\n .on('end', () => {\n logger.info(`Audio extraction complete: ${outputPath}`);\n resolve(outputPath);\n })\n .on('error', (err) => {\n logger.error(`Audio extraction failed: ${err.message}`);\n reject(new Error(`Audio extraction failed: ${err.message}`));\n })\n .run();\n });\n}\n\n/**\n * Split an audio file into chunks of approximately `maxChunkSizeMB` each.\n * Uses ffmpeg to split by duration calculated from the file size.\n * Returns an array of chunk file paths.\n */\nexport async function splitAudioIntoChunks(\n audioPath: string,\n maxChunkSizeMB: number = 24,\n): Promise<string[]> {\n const stats = await getFileStats(audioPath);\n const fileSizeMB = stats.size / (1024 * 1024);\n\n if (fileSizeMB <= maxChunkSizeMB) {\n return [audioPath];\n }\n\n const duration = await getAudioDuration(audioPath);\n const numChunks = Math.ceil(fileSizeMB / maxChunkSizeMB);\n const chunkDuration = duration / numChunks;\n\n const ext = extname(audioPath);\n const base = audioPath.slice(0, -ext.length);\n const chunkPaths: string[] = [];\n\n logger.info(\n `Splitting ${fileSizeMB.toFixed(1)}MB audio into ${numChunks} chunks ` +\n `(~${chunkDuration.toFixed(0)}s each)`\n );\n\n for (let i = 0; i < numChunks; i++) {\n const startTime = i * chunkDuration;\n const chunkPath = `${base}_chunk${i}${ext}`;\n chunkPaths.push(chunkPath);\n\n await new Promise<void>((resolve, reject) => {\n const cmd = createFFmpeg(audioPath)\n .setStartTime(startTime)\n .setDuration(chunkDuration)\n .audioCodec('copy')\n .output(chunkPath)\n .on('end', () => resolve())\n .on('error', (err) => reject(new Error(`Chunk split failed: ${err.message}`)));\n cmd.run();\n });\n\n logger.info(`Created chunk ${i + 1}/${numChunks}: ${chunkPath}`);\n }\n\n return chunkPaths;\n}\n\n/** Get the duration of an audio file in seconds using ffprobe. */\nasync function getAudioDuration(audioPath: string): Promise<number> {\n try {\n const metadata = await ffprobe(audioPath);\n return metadata.format.duration ?? 0;\n } catch (err: any) {\n throw new Error(`ffprobe failed: ${err.message}`);\n }\n}\n","export { default as OpenAI } from 'openai'\nexport type { ChatCompletionMessageParam, ChatCompletionTool, ChatCompletion } from 'openai/resources/chat/completions.js'\nexport { default as Anthropic } from '@anthropic-ai/sdk'\nexport { CopilotClient, CopilotSession } from '@github/copilot-sdk'\nexport type { SessionEvent } from '@github/copilot-sdk'\n","import { fileExistsSync, readTextFileSync } from '../core/fileSystem.js'\nimport { getConfig } from './environment'\nimport logger from './logger'\n\nexport interface BrandConfig {\n name: string\n handle: string\n tagline: string\n voice: {\n tone: string\n personality: string\n style: string\n }\n advocacy: {\n primary: string[]\n interests: string[]\n avoids: string[]\n }\n customVocabulary: string[]\n hashtags: {\n always: string[]\n preferred: string[]\n platforms: Record<string, string[]>\n }\n contentGuidelines: {\n shortsFocus: string\n blogFocus: string\n socialFocus: string\n }\n}\n\nconst defaultBrand: BrandConfig = {\n name: 'Creator',\n handle: '@creator',\n tagline: '',\n voice: {\n tone: 'professional, friendly',\n personality: 'A knowledgeable content creator.',\n style: 'Clear and concise.',\n },\n advocacy: {\n primary: [],\n interests: [],\n avoids: [],\n },\n customVocabulary: [],\n hashtags: {\n always: [],\n preferred: [],\n platforms: {},\n },\n contentGuidelines: {\n shortsFocus: 'Highlight key moments and insights.',\n blogFocus: 'Educational and informative content.',\n socialFocus: 'Engaging and authentic posts.',\n },\n}\n\nlet cachedBrand: BrandConfig | null = null\n\n/** Validate brand config and log warnings for missing or empty fields. */\nfunction validateBrandConfig(brand: Partial<BrandConfig>): void {\n const requiredStrings: (keyof BrandConfig)[] = ['name', 'handle', 'tagline']\n for (const field of requiredStrings) {\n if (!brand[field]) {\n logger.warn(`brand.json: missing or empty field \"${field}\"`)\n }\n }\n\n const requiredObjects: { key: keyof BrandConfig; subKeys: string[] }[] = [\n { key: 'voice', subKeys: ['tone', 'personality', 'style'] },\n { key: 'advocacy', subKeys: ['primary', 'interests'] },\n { key: 'hashtags', subKeys: ['always', 'preferred'] },\n { key: 'contentGuidelines', subKeys: ['shortsFocus', 'blogFocus', 'socialFocus'] },\n ]\n\n for (const { key, subKeys } of requiredObjects) {\n if (!brand[key]) {\n logger.warn(`brand.json: missing section \"${key}\"`)\n } else {\n const section = brand[key] as Record<string, unknown>\n for (const sub of subKeys) {\n if (!section[sub] || (Array.isArray(section[sub]) && (section[sub] as unknown[]).length === 0)) {\n logger.warn(`brand.json: missing or empty field \"${key}.${sub}\"`)\n }\n }\n }\n }\n\n if (!brand.customVocabulary || brand.customVocabulary.length === 0) {\n logger.warn('brand.json: \"customVocabulary\" is empty — Whisper prompt will be blank')\n }\n}\n\nexport function getBrandConfig(): BrandConfig {\n if (cachedBrand) return cachedBrand\n\n const config = getConfig()\n const brandPath = config.BRAND_PATH\n\n if (!fileExistsSync(brandPath)) {\n logger.warn('brand.json not found — using defaults')\n cachedBrand = { ...defaultBrand }\n return cachedBrand\n }\n\n const raw = readTextFileSync(brandPath)\n cachedBrand = JSON.parse(raw) as BrandConfig\n validateBrandConfig(cachedBrand)\n logger.info(`Brand config loaded: ${cachedBrand.name}`)\n return cachedBrand\n}\n\n// Helper to get Whisper prompt from brand vocabulary\nexport function getWhisperPrompt(): string {\n const brand = getBrandConfig()\n return brand.customVocabulary.join(', ')\n}\n","/**\n * LLM Model Pricing Configuration\n * \n * Per-model pricing for cost calculation. Updated Feb 2026.\n * Copilot uses Premium Request Units (PRUs), others use per-token pricing.\n */\n\nexport interface ModelPricing {\n /** Price per 1M input tokens (USD) — for OpenAI/Claude */\n inputPer1M?: number;\n /** Price per 1M output tokens (USD) — for OpenAI/Claude */\n outputPer1M?: number;\n /** Premium request multiplier — for Copilot */\n pruMultiplier?: number;\n /** Whether this model is included free on paid Copilot plans */\n copilotIncluded?: boolean;\n}\n\n/** Overage rate for Copilot premium requests: $0.04 per PRU */\nexport const COPILOT_PRU_OVERAGE_RATE = 0.04;\n\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\n // === OpenAI Models (from Copilot model picker) ===\n 'gpt-4o': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-4o-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-4.1': { inputPer1M: 2.00, outputPer1M: 8.00, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-4.1-mini': { inputPer1M: 0.40, outputPer1M: 1.60 },\n 'gpt-5-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-5': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1-codex-max': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1-codex-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0.33 },\n 'gpt-5.2': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.2-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'o3': { inputPer1M: 10.00, outputPer1M: 40.00, pruMultiplier: 5 },\n 'o4-mini-high': { inputPer1M: 1.10, outputPer1M: 4.40, pruMultiplier: 20 },\n\n // === Anthropic Models (from Copilot model picker) ===\n 'claude-haiku-4.5': { inputPer1M: 0.80, outputPer1M: 4.00, pruMultiplier: 0.33 },\n 'claude-sonnet-4': { inputPer1M: 3.00, outputPer1M: 15.00, pruMultiplier: 1 },\n 'claude-sonnet-4.5': { inputPer1M: 3.00, outputPer1M: 15.00, pruMultiplier: 1 },\n 'claude-opus-4.5': { inputPer1M: 15.00, outputPer1M: 75.00, pruMultiplier: 3 },\n 'claude-opus-4.6': { inputPer1M: 5.00, outputPer1M: 25.00, pruMultiplier: 3 },\n 'claude-opus-4.6-fast': { inputPer1M: 5.00, outputPer1M: 25.00, pruMultiplier: 9 },\n\n // === Google Models (from Copilot model picker) ===\n 'gemini-2.5-pro': { inputPer1M: 1.25, outputPer1M: 5.00, pruMultiplier: 1 },\n 'gemini-3-flash': { inputPer1M: 0.10, outputPer1M: 0.40, pruMultiplier: 0.33 },\n 'gemini-3-pro': { inputPer1M: 1.25, outputPer1M: 5.00, pruMultiplier: 1 },\n};\n\n/**\n * Calculate cost for a single LLM call using per-token pricing.\n * Returns USD amount.\n */\nexport function calculateTokenCost(\n model: string,\n inputTokens: number,\n outputTokens: number\n): number {\n const pricing = getModelPricing(model);\n if (!pricing || (!pricing.inputPer1M && !pricing.outputPer1M)) return 0;\n \n const inputCost = ((pricing.inputPer1M ?? 0) / 1_000_000) * inputTokens;\n const outputCost = ((pricing.outputPer1M ?? 0) / 1_000_000) * outputTokens;\n return inputCost + outputCost;\n}\n\n/**\n * Calculate PRU cost for a Copilot premium request.\n * Returns PRU count consumed (multiply by $0.04 for overage cost).\n */\nexport function calculatePRUCost(model: string): number {\n const pricing = getModelPricing(model);\n if (!pricing) return 1; // Default 1 PRU for unknown models\n if (pricing.copilotIncluded) return 0; // Free on paid plans\n return pricing.pruMultiplier ?? 1;\n}\n\n/**\n * Look up model pricing. Returns undefined if model is unknown.\n */\nexport function getModelPricing(model: string): ModelPricing | undefined {\n // Try exact match first, then case-insensitive\n return MODEL_PRICING[model] ?? \n MODEL_PRICING[model.toLowerCase()] ??\n Object.entries(MODEL_PRICING).find(([key]) => \n model.toLowerCase().includes(key.toLowerCase())\n )?.[1];\n}\n","import type { TokenUsage, CostInfo, QuotaSnapshot } from '../providers/types.js';\nimport { calculateTokenCost, calculatePRUCost, COPILOT_PRU_OVERAGE_RATE } from '../config/pricing.js';\nimport logger from '../config/logger.js';\n\n/** Record of a single LLM usage event */\nexport interface UsageRecord {\n timestamp: Date;\n provider: string;\n model: string;\n agent: string;\n stage: string;\n usage: TokenUsage;\n cost: CostInfo;\n durationMs?: number;\n}\n\n/** Record of a non-LLM service usage event */\nexport interface ServiceUsageRecord {\n timestamp: Date;\n service: string;\n stage: string;\n costUSD: number;\n metadata: Record<string, unknown>;\n}\n\n/** Aggregated cost report */\nexport interface CostReport {\n totalCostUSD: number;\n totalPRUs: number;\n totalTokens: { input: number; output: number; total: number };\n byProvider: Record<string, { costUSD: number; prus: number; calls: number }>;\n byAgent: Record<string, { costUSD: number; prus: number; calls: number }>;\n byModel: Record<string, { costUSD: number; prus: number; calls: number }>;\n byService: Record<string, { costUSD: number; calls: number }>;\n records: UsageRecord[];\n serviceRecords: ServiceUsageRecord[];\n totalServiceCostUSD: number;\n /** Copilot quota info (if available) */\n copilotQuota?: QuotaSnapshot;\n}\n\n/** Singleton cost tracker for a pipeline run */\nclass CostTracker {\n private records: UsageRecord[] = [];\n private serviceRecords: ServiceUsageRecord[] = [];\n private latestQuota?: QuotaSnapshot;\n private currentAgent = 'unknown';\n private currentStage = 'unknown';\n\n /** Set the current agent name (called by BaseAgent before LLM calls) */\n setAgent(agent: string): void {\n this.currentAgent = agent;\n }\n\n /** Set the current pipeline stage */\n setStage(stage: string): void {\n this.currentStage = stage;\n }\n\n /** Record a usage event from any provider */\n recordUsage(\n provider: string,\n model: string,\n usage: TokenUsage,\n cost?: CostInfo,\n durationMs?: number,\n quotaSnapshot?: QuotaSnapshot\n ): void {\n // Calculate cost if not provided\n const finalCost = cost ?? {\n amount: provider === 'copilot'\n ? calculatePRUCost(model)\n : calculateTokenCost(model, usage.inputTokens, usage.outputTokens),\n unit: provider === 'copilot' ? 'premium_requests' as const : 'usd' as const,\n model,\n };\n\n const record: UsageRecord = {\n timestamp: new Date(),\n provider,\n model,\n agent: this.currentAgent,\n stage: this.currentStage,\n usage,\n cost: finalCost,\n durationMs,\n };\n\n this.records.push(record);\n\n if (quotaSnapshot) {\n this.latestQuota = quotaSnapshot;\n }\n\n logger.debug(\n `[CostTracker] ${provider}/${model} | ${this.currentAgent} | ` +\n `in=${usage.inputTokens} out=${usage.outputTokens} | ` +\n `cost=${finalCost.amount.toFixed(4)} ${finalCost.unit}`\n );\n }\n\n /** Record a non-LLM service usage event */\n recordServiceUsage(service: string, costUSD: number, metadata?: Record<string, unknown>): void {\n const record: ServiceUsageRecord = {\n timestamp: new Date(),\n service,\n stage: this.currentStage,\n costUSD,\n metadata: metadata ?? {},\n };\n\n this.serviceRecords.push(record);\n\n logger.debug(\n `[CostTracker] service=${service} | stage=${this.currentStage} | cost=$${costUSD.toFixed(4)}`\n );\n }\n\n /** Get the full cost report */\n getReport(): CostReport {\n const report: CostReport = {\n totalCostUSD: 0,\n totalPRUs: 0,\n totalTokens: { input: 0, output: 0, total: 0 },\n byProvider: {},\n byAgent: {},\n byModel: {},\n byService: {},\n records: [...this.records],\n serviceRecords: [...this.serviceRecords],\n totalServiceCostUSD: 0,\n copilotQuota: this.latestQuota,\n };\n\n for (const record of this.records) {\n const { provider, model, agent, usage, cost } = record;\n\n // Accumulate tokens\n report.totalTokens.input += usage.inputTokens;\n report.totalTokens.output += usage.outputTokens;\n report.totalTokens.total += usage.totalTokens;\n\n // Accumulate costs\n const usdCost = cost.unit === 'usd' ? cost.amount : cost.amount * COPILOT_PRU_OVERAGE_RATE;\n const prus = cost.unit === 'premium_requests' ? cost.amount : 0;\n report.totalCostUSD += usdCost;\n report.totalPRUs += prus;\n\n // By provider\n if (!report.byProvider[provider]) report.byProvider[provider] = { costUSD: 0, prus: 0, calls: 0 };\n report.byProvider[provider].costUSD += usdCost;\n report.byProvider[provider].prus += prus;\n report.byProvider[provider].calls += 1;\n\n // By agent\n if (!report.byAgent[agent]) report.byAgent[agent] = { costUSD: 0, prus: 0, calls: 0 };\n report.byAgent[agent].costUSD += usdCost;\n report.byAgent[agent].prus += prus;\n report.byAgent[agent].calls += 1;\n\n // By model\n if (!report.byModel[model]) report.byModel[model] = { costUSD: 0, prus: 0, calls: 0 };\n report.byModel[model].costUSD += usdCost;\n report.byModel[model].prus += prus;\n report.byModel[model].calls += 1;\n }\n\n for (const record of this.serviceRecords) {\n const { service, costUSD } = record;\n report.totalServiceCostUSD += costUSD;\n\n if (!report.byService[service]) report.byService[service] = { costUSD: 0, calls: 0 };\n report.byService[service].costUSD += costUSD;\n report.byService[service].calls += 1;\n }\n\n report.totalCostUSD += report.totalServiceCostUSD;\n\n return report;\n }\n\n /** Format report as human-readable string for console output */\n formatReport(): string {\n const report = this.getReport();\n const lines: string[] = [\n '',\n '═══════════════════════════════════════════',\n ' 💰 Pipeline Cost Report',\n '═══════════════════════════════════════════',\n '',\n ` Total Cost: $${report.totalCostUSD.toFixed(4)} USD` +\n (report.totalServiceCostUSD > 0 ? ` (incl. $${report.totalServiceCostUSD.toFixed(4)} services)` : ''),\n ];\n\n if (report.totalPRUs > 0) {\n lines.push(` Total PRUs: ${report.totalPRUs} premium requests`);\n }\n\n lines.push(\n ` Total Tokens: ${report.totalTokens.total.toLocaleString()} (${report.totalTokens.input.toLocaleString()} in / ${report.totalTokens.output.toLocaleString()} out)`,\n ` LLM Calls: ${this.records.length}`,\n );\n\n if (report.copilotQuota) {\n lines.push(\n '',\n ` Copilot Quota: ${report.copilotQuota.remainingPercentage.toFixed(1)}% remaining`,\n ` Used/Total: ${report.copilotQuota.usedRequests}/${report.copilotQuota.entitlementRequests} PRUs`,\n );\n if (report.copilotQuota.resetDate) {\n lines.push(` Resets: ${report.copilotQuota.resetDate}`);\n }\n }\n\n // By agent breakdown\n if (Object.keys(report.byAgent).length > 1) {\n lines.push('', ' By Agent:');\n for (const [agent, data] of Object.entries(report.byAgent)) {\n lines.push(` ${agent}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\n }\n }\n\n // By model breakdown\n if (Object.keys(report.byModel).length > 1) {\n lines.push('', ' By Model:');\n for (const [model, data] of Object.entries(report.byModel)) {\n lines.push(` ${model}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\n }\n }\n\n // By service breakdown\n if (Object.keys(report.byService).length > 0) {\n lines.push('', ' By Service:');\n for (const [service, data] of Object.entries(report.byService)) {\n lines.push(` ${service}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\n }\n }\n\n lines.push('', '═══════════════════════════════════════════', '');\n return lines.join('\\n');\n }\n\n /** Reset all tracking (for new pipeline run) */\n reset(): void {\n this.records = [];\n this.serviceRecords = [];\n this.latestQuota = undefined;\n this.currentAgent = 'unknown';\n this.currentStage = 'unknown';\n }\n}\n\n/** Global singleton instance */\nexport const costTracker = new CostTracker();\n","import { OpenAI } from '../../core/ai.js'\nimport { fileExistsSync, getFileStatsSync, openReadStream } from '../../core/fileSystem.js'\nimport { getConfig } from '../../config/environment'\nimport logger from '../../config/logger'\nimport { getWhisperPrompt } from '../../config/brand'\nimport { Transcript, Segment, Word } from '../../types'\nimport { costTracker } from '../../services/costTracker.js'\n\nconst MAX_FILE_SIZE_MB = 25\nconst WHISPER_COST_PER_MINUTE = 0.006 // $0.006/minute for whisper-1\nconst WARN_FILE_SIZE_MB = 20\n\nexport async function transcribeAudio(audioPath: string): Promise<Transcript> {\n logger.info(`Starting Whisper transcription: ${audioPath}`)\n\n if (!fileExistsSync(audioPath)) {\n throw new Error(`Audio file not found: ${audioPath}`)\n }\n\n // Check file size against Whisper's 25MB limit\n const stats = getFileStatsSync(audioPath)\n const fileSizeMB = stats.size / (1024 * 1024)\n\n if (fileSizeMB > MAX_FILE_SIZE_MB) {\n throw new Error(\n `Audio file exceeds Whisper's 25MB limit (${fileSizeMB.toFixed(1)}MB). ` +\n 'The file should be split into smaller chunks before transcription.'\n )\n }\n if (fileSizeMB > WARN_FILE_SIZE_MB) {\n logger.warn(`Audio file is ${fileSizeMB.toFixed(1)}MB — approaching 25MB limit`)\n }\n\n const config = getConfig()\n const openai = new OpenAI({ apiKey: config.OPENAI_API_KEY })\n\n try {\n const prompt = getWhisperPrompt()\n const response = await openai.audio.transcriptions.create({\n model: 'whisper-1',\n file: openReadStream(audioPath),\n response_format: 'verbose_json',\n timestamp_granularities: ['word', 'segment'],\n ...(prompt && { prompt }),\n })\n\n // The verbose_json response includes segments and words at the top level,\n // but the OpenAI SDK types don't expose them — cast to access raw fields.\n const verboseResponse = response as unknown as Record<string, unknown>\n const rawSegments = (verboseResponse.segments ?? []) as Array<{\n id: number; text: string; start: number; end: number\n }>\n const rawWords = (verboseResponse.words ?? []) as Array<{\n word: string; start: number; end: number\n }>\n\n const words: Word[] = rawWords.map((w) => ({\n word: w.word,\n start: w.start,\n end: w.end,\n }))\n\n const segments: Segment[] = rawSegments.map((s) => ({\n id: s.id,\n text: s.text.trim(),\n start: s.start,\n end: s.end,\n words: rawWords\n .filter((w) => w.start >= s.start && w.end <= s.end)\n .map((w) => ({ word: w.word, start: w.start, end: w.end })),\n }))\n\n logger.info(\n `Transcription complete — ${segments.length} segments, ` +\n `${words.length} words, language=${response.language}`\n )\n\n // Track Whisper API cost\n const durationMinutes = (response.duration ?? 0) / 60\n costTracker.recordServiceUsage('whisper', durationMinutes * WHISPER_COST_PER_MINUTE, {\n model: 'whisper-1',\n durationSeconds: response.duration ?? 0,\n audioFile: audioPath,\n })\n\n return {\n text: response.text,\n segments,\n words,\n language: response.language ?? 'unknown',\n duration: response.duration ?? 0,\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error)\n logger.error(`Whisper transcription failed: ${message}`)\n\n // OpenAI SDK errors expose a `status` property for HTTP status codes\n const status = (error as { status?: number }).status\n if (status === 401) {\n throw new Error('OpenAI API authentication failed. Check your OPENAI_API_KEY.')\n }\n if (status === 429) {\n throw new Error('OpenAI API rate limit exceeded. Please try again later.')\n }\n throw new Error(`Whisper transcription failed: ${message}`)\n }\n}\n","import { join } from '../core/paths.js'\nimport { getFileStats, writeJsonFile, ensureDirectory, removeFile } from '../core/fileSystem.js'\nimport { extractAudio, splitAudioIntoChunks } from '../tools/ffmpeg/audioExtraction'\nimport { transcribeAudio } from '../tools/whisper/whisperClient'\nimport { VideoFile, Transcript, Segment, Word } from '../types'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\nconst MAX_WHISPER_SIZE_MB = 25\n\nexport async function transcribeVideo(video: VideoFile): Promise<Transcript> {\n const config = getConfig()\n\n // 1. Create cache directory for temp audio files\n const cacheDir = join(config.REPO_ROOT, 'cache')\n await ensureDirectory(cacheDir)\n logger.info(`Cache directory ready: ${cacheDir}`)\n\n // 2. Extract audio as compressed MP3 (much smaller than WAV)\n const mp3Path = join(cacheDir, `${video.slug}.mp3`)\n logger.info(`Extracting audio for \"${video.slug}\"`)\n await extractAudio(video.repoPath, mp3Path)\n\n // 3. Check file size and chunk if necessary\n const stats = await getFileStats(mp3Path)\n const fileSizeMB = stats.size / (1024 * 1024)\n logger.info(`Extracted audio: ${fileSizeMB.toFixed(1)}MB`)\n\n let transcript: Transcript\n\n if (fileSizeMB <= MAX_WHISPER_SIZE_MB) {\n // Single-file transcription\n logger.info(`Transcribing audio for \"${video.slug}\"`)\n transcript = await transcribeAudio(mp3Path)\n } else {\n // Chunk and transcribe (very long videos, 50+ min)\n logger.info(`Audio exceeds ${MAX_WHISPER_SIZE_MB}MB, splitting into chunks`)\n const chunkPaths = await splitAudioIntoChunks(mp3Path)\n transcript = await transcribeChunks(chunkPaths)\n\n // Clean up chunk files\n for (const chunkPath of chunkPaths) {\n if (chunkPath !== mp3Path) {\n await removeFile(chunkPath).catch(() => {})\n }\n }\n }\n\n // 4. Save transcript JSON\n const transcriptDir = join(config.OUTPUT_DIR, video.slug)\n await ensureDirectory(transcriptDir)\n const transcriptPath = join(transcriptDir, 'transcript.json')\n await writeJsonFile(transcriptPath, transcript)\n logger.info(`Transcript saved: ${transcriptPath}`)\n\n // 5. Clean up temp audio file\n await removeFile(mp3Path).catch(() => {})\n logger.info(`Cleaned up temp file: ${mp3Path}`)\n\n // 6. Return the transcript\n logger.info(\n `Transcription complete for \"${video.slug}\" — ` +\n `${transcript.segments.length} segments, ${transcript.words.length} words`\n )\n return transcript\n}\n\n/**\n * Transcribe multiple audio chunks and merge results with adjusted timestamps.\n */\nasync function transcribeChunks(chunkPaths: string[]): Promise<Transcript> {\n let allText = ''\n const allSegments: Segment[] = []\n const allWords: Word[] = []\n let cumulativeOffset = 0\n let totalDuration = 0\n let language = 'unknown'\n\n for (let i = 0; i < chunkPaths.length; i++) {\n logger.info(`Transcribing chunk ${i + 1}/${chunkPaths.length}: ${chunkPaths[i]}`)\n const result = await transcribeAudio(chunkPaths[i])\n\n if (i === 0) language = result.language\n\n // Adjust timestamps by cumulative offset\n const offsetSegments = result.segments.map((s) => ({\n ...s,\n id: allSegments.length + s.id,\n start: s.start + cumulativeOffset,\n end: s.end + cumulativeOffset,\n words: s.words.map((w) => ({\n ...w,\n start: w.start + cumulativeOffset,\n end: w.end + cumulativeOffset,\n })),\n }))\n\n const offsetWords = result.words.map((w) => ({\n ...w,\n start: w.start + cumulativeOffset,\n end: w.end + cumulativeOffset,\n }))\n\n allText += (allText ? ' ' : '') + result.text\n allSegments.push(...offsetSegments)\n allWords.push(...offsetWords)\n\n cumulativeOffset += result.duration\n totalDuration += result.duration\n }\n\n return {\n text: allText,\n segments: allSegments,\n words: allWords,\n language,\n duration: totalDuration,\n }\n}\n","/**\n * Caption generator for the Advanced SubStation Alpha (ASS) subtitle format.\n *\n * ### Why ASS instead of SRT/VTT?\n * ASS supports inline style overrides — font size, color, and animation per\n * character/word — which enables the \"active word pop\" karaoke effect used\n * in modern short-form video (TikTok, Reels). SRT and VTT only support\n * plain text or basic HTML tags with no per-word timing control.\n *\n * ### Karaoke word highlighting approach\n * Instead of ASS's native `\\k` karaoke tags (which highlight left-to-right\n * within a line), we generate **one Dialogue line per word-state**. Each line\n * renders the entire caption group but with the currently-spoken word in a\n * different color and size. Contiguous end/start timestamps between\n * word-states prevent flicker. This gives us full control over the visual\n * treatment (color, font-size, scale animations) without the limitations\n * of the `\\k` tag.\n *\n * @module captionGenerator\n */\n\nimport { Transcript, Segment, Word, CaptionStyle } from '../../types'\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Pad a number to a fixed width with leading zeros. */\nfunction pad(n: number, width: number): string {\n return String(n).padStart(width, '0')\n}\n\n/** Convert seconds → SRT timestamp \"HH:MM:SS,mmm\" */\nfunction toSRT(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n const ms = Math.round((seconds - Math.floor(seconds)) * 1000)\n return `${pad(h, 2)}:${pad(m, 2)}:${pad(s, 2)},${pad(ms, 3)}`\n}\n\n/** Convert seconds → VTT timestamp \"HH:MM:SS.mmm\" */\nfunction toVTT(seconds: number): string {\n return toSRT(seconds).replace(',', '.')\n}\n\n/** Convert seconds → ASS timestamp \"H:MM:SS.cc\" */\nfunction toASS(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n const cs = Math.round((seconds - Math.floor(seconds)) * 100)\n return `${h}:${pad(m, 2)}:${pad(s, 2)}.${pad(cs, 2)}`\n}\n\n// ---------------------------------------------------------------------------\n// Premium caption constants\n// ---------------------------------------------------------------------------\n\n/** Silence gap threshold in seconds – gaps longer than this split caption groups. */\nconst SILENCE_GAP_THRESHOLD = 0.8\n/** Maximum words displayed simultaneously in a caption group. */\nconst MAX_WORDS_PER_GROUP = 8\n/** Target words per display line within a group (splits into 2 lines above this). */\nconst WORDS_PER_LINE = 4\n/** ASS BGR color for the active (currently-spoken) word – yellow. */\nconst ACTIVE_COLOR = '\\\\c&H00FFFF&'\n/** ASS BGR color for inactive words – white. */\nconst BASE_COLOR = '\\\\c&HFFFFFF&'\n/** Font size for the active word. */\nconst ACTIVE_FONT_SIZE = 54\n/** Font size for inactive words (matches style default). */\nconst BASE_FONT_SIZE = 42\n\n// ---------------------------------------------------------------------------\n// Medium caption constants (smaller, bottom-positioned for longer content)\n// ---------------------------------------------------------------------------\n\n/** Font size for the active word in medium style. */\nconst MEDIUM_ACTIVE_FONT_SIZE = 40\n/** Font size for inactive words in medium style. */\nconst MEDIUM_BASE_FONT_SIZE = 32\n\n// ---------------------------------------------------------------------------\n// Portrait caption constants (Opus Clips style)\n// ---------------------------------------------------------------------------\n\n/** Font size for the active word in portrait style. */\nconst PORTRAIT_ACTIVE_FONT_SIZE = 78\n/** Font size for inactive words in portrait style. */\nconst PORTRAIT_BASE_FONT_SIZE = 66\n/** ASS BGR color for the active word in portrait style – green. */\nconst PORTRAIT_ACTIVE_COLOR = '\\\\c&H00FF00&'\n/** ASS BGR color for inactive words in portrait style – white. */\nconst PORTRAIT_BASE_COLOR = '\\\\c&HFFFFFF&'\n\n// ---------------------------------------------------------------------------\n// SRT (segment-level)\n// ---------------------------------------------------------------------------\n\nexport function generateSRT(transcript: Transcript): string {\n return transcript.segments\n .map((seg: Segment, i: number) => {\n const idx = i + 1\n const start = toSRT(seg.start)\n const end = toSRT(seg.end)\n const text = seg.text.trim()\n return `${idx}\\n${start} --> ${end}\\n${text}`\n })\n .join('\\n\\n')\n .concat('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// VTT (segment-level)\n// ---------------------------------------------------------------------------\n\nexport function generateVTT(transcript: Transcript): string {\n const cues = transcript.segments\n .map((seg: Segment) => {\n const start = toVTT(seg.start)\n const end = toVTT(seg.end)\n const text = seg.text.trim()\n return `${start} --> ${end}\\n${text}`\n })\n .join('\\n\\n')\n\n return `WEBVTT\\n\\n${cues}\\n`\n}\n\n// ---------------------------------------------------------------------------\n// ASS – Premium active-word-pop captions\n// ---------------------------------------------------------------------------\n\n/**\n * ASS header for landscape (16:9, 1920×1080) captions.\n *\n * ### Style fields explained (comma-separated in the Style line):\n * - `Fontname: Montserrat` — bundled with the project; FFmpeg's `ass` filter\n * uses `fontsdir=.` so libass finds the .ttf files next to the .ass file.\n * - `Fontsize: 42` — base size for inactive words\n * - `PrimaryColour: &H00FFFFFF` — white (ASS uses `&HAABBGGRR` — alpha, blue, green, red)\n * - `OutlineColour: &H00000000` — black outline for readability on any background\n * - `BackColour: &H80000000` — 50% transparent black shadow\n * - `Bold: 1` — bold for better readability at small sizes\n * - `BorderStyle: 1` — outline + drop shadow (not opaque box)\n * - `Outline: 3` — 3px outline thickness\n * - `Shadow: 1` — 1px drop shadow\n * - `Alignment: 2` — bottom-center (SSA alignment: 1=left, 2=center, 3=right;\n * add 4 for top, 8 for middle — so 2 = bottom-center)\n * - `MarginV: 40` — 40px above the bottom edge\n * - `WrapStyle: 0` — smart word wrap\n */\nconst ASS_HEADER = `[Script Info]\nTitle: Auto-generated captions\nScriptType: v4.00+\nPlayResX: 1920\nPlayResY: 1080\nWrapStyle: 0\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,Montserrat,42,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,20,20,40,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`\n\n/**\n * ASS header for portrait (9:16, 1080×1920) captions — used for shorts.\n *\n * Key differences from the landscape header:\n * - `PlayResX/Y: 1080×1920` — matches portrait video dimensions\n * - `Fontsize: 78` — larger base font for vertical video viewing (small screens)\n * - `MarginV: 700` — pushes captions to the center-bottom area, leaving room\n * for the hook overlay at the top\n * - Includes a `Hook` style: semi-transparent pill/badge background\n * (`BorderStyle: 3` = opaque box) for the opening hook text overlay\n */\nconst ASS_HEADER_PORTRAIT = `[Script Info]\nTitle: Auto-generated captions\nScriptType: v4.00+\nPlayResX: 1080\nPlayResY: 1920\nWrapStyle: 0\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,Montserrat,78,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,30,30,700,1\nStyle: Hook,Montserrat,56,&H00333333,&H00333333,&H60D0D0D0,&H60E0E0E0,1,0,0,0,100,100,2,0,3,18,2,8,80,80,60,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`\n\n/**\n * ASS header for medium-style captions (1920×1080 but smaller font).\n *\n * Used for longer clips where large captions would be distracting.\n * - `Fontsize: 32` — smaller than the shorts style\n * - `Alignment: 2` — bottom-center\n * - `MarginV: 60` — slightly higher from the bottom edge to avoid UI overlaps\n */\nconst ASS_HEADER_MEDIUM = `[Script Info]\nTitle: Auto-generated captions\nScriptType: v4.00+\nPlayResX: 1920\nPlayResY: 1080\nWrapStyle: 0\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,Montserrat,32,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,2,1,2,20,20,60,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`\n\n/**\n * Group words into caption groups split on silence gaps and max word count.\n * All words within a group are displayed simultaneously; captions disappear\n * entirely during gaps longer than SILENCE_GAP_THRESHOLD.\n */\nfunction groupWordsBySpeech(words: Word[]): Word[][] {\n if (words.length === 0) return []\n\n const groups: Word[][] = []\n let current: Word[] = []\n\n for (let i = 0; i < words.length; i++) {\n current.push(words[i])\n\n const isLast = i === words.length - 1\n const hasGap =\n !isLast && words[i + 1].start - words[i].end > SILENCE_GAP_THRESHOLD\n const atMax = current.length >= MAX_WORDS_PER_GROUP\n\n if (isLast || hasGap || atMax) {\n groups.push(current)\n current = []\n }\n }\n\n return groups\n}\n\n/**\n * Split a caption group into 1–2 display lines.\n * Groups with ≤ WORDS_PER_LINE words stay on one line; larger groups\n * are split at the midpoint into two lines joined with \\\\N.\n */\nfunction splitGroupIntoLines(group: Word[]): Word[][] {\n if (group.length <= WORDS_PER_LINE) return [group]\n const mid = Math.ceil(group.length / 2)\n return [group.slice(0, mid), group.slice(mid)]\n}\n\n/**\n * Build premium ASS dialogue lines with active-word highlighting.\n * Generates one Dialogue line per word-state: the full caption group is\n * rendered with the currently-spoken word in yellow at a larger size while\n * all other words stay white at the base size. Contiguous end/start times\n * between word-states prevent flicker.\n */\nfunction buildPremiumDialogueLines(words: Word[], style: CaptionStyle = 'shorts'): string[] {\n const activeFontSize = style === 'portrait' ? PORTRAIT_ACTIVE_FONT_SIZE\n : style === 'medium' ? MEDIUM_ACTIVE_FONT_SIZE : ACTIVE_FONT_SIZE\n const baseFontSize = style === 'portrait' ? PORTRAIT_BASE_FONT_SIZE\n : style === 'medium' ? MEDIUM_BASE_FONT_SIZE : BASE_FONT_SIZE\n const groups = groupWordsBySpeech(words)\n const dialogues: string[] = []\n\n for (const group of groups) {\n const displayLines = splitGroupIntoLines(group)\n\n for (let activeIdx = 0; activeIdx < group.length; activeIdx++) {\n const activeWord = group[activeIdx]\n\n // Contiguous timing: end = next word's start, or this word's own end\n const endTime =\n activeIdx < group.length - 1\n ? group[activeIdx + 1].start\n : activeWord.end\n\n // Render all words across display lines with the active word highlighted\n const renderedLines: string[] = []\n let globalIdx = 0\n\n for (const line of displayLines) {\n const rendered = line.map((w) => {\n const idx = globalIdx++\n const text = w.word.trim()\n if (idx === activeIdx) {\n if (style === 'portrait') {\n // Opus Clips style: green color + scale pop animation\n return `{${PORTRAIT_ACTIVE_COLOR}\\\\fs${activeFontSize}\\\\fscx130\\\\fscy130\\\\t(0,150,\\\\fscx100\\\\fscy100)}${text}`\n }\n return `{${ACTIVE_COLOR}\\\\fs${activeFontSize}}${text}`\n }\n if (style === 'portrait') {\n return `{${PORTRAIT_BASE_COLOR}\\\\fs${baseFontSize}}${text}`\n }\n return `{${BASE_COLOR}\\\\fs${baseFontSize}}${text}`\n })\n renderedLines.push(rendered.join(' '))\n }\n\n const text = renderedLines.join('\\\\N')\n dialogues.push(\n `Dialogue: 0,${toASS(activeWord.start)},${toASS(endTime)},Default,,0,0,0,,${text}`,\n )\n }\n }\n\n return dialogues\n}\n\n/**\n * Generate premium ASS captions with active-word-pop highlighting.\n *\n * ### How it works\n * Words are grouped by speech bursts (split on silence gaps > 0.8s or after\n * 8 words). Within each group, one Dialogue line is emitted per word — the\n * full group is shown each time, but the \"active\" word gets a different color\n * and larger font size. This creates a karaoke-style bounce effect.\n *\n * @param transcript - Full transcript with word-level timestamps\n * @param style - Visual style: 'shorts' (large centered), 'medium' (small bottom),\n * or 'portrait' (Opus Clips style with green highlight + scale animation)\n * @returns Complete ASS file content (header + dialogue lines)\n */\nexport function generateStyledASS(transcript: Transcript, style: CaptionStyle = 'shorts'): string {\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\n const allWords = transcript.words\n if (allWords.length === 0) return header\n\n return header + buildPremiumDialogueLines(allWords, style).join('\\n') + '\\n'\n}\n\n/**\n * Generate premium ASS captions for a single contiguous segment.\n * Filters words within [startTime, endTime] (plus buffer), adjusts timestamps\n * relative to the clip's buffered start so they align with the extracted video.\n */\nexport function generateStyledASSForSegment(\n transcript: Transcript,\n startTime: number,\n endTime: number,\n buffer: number = 1.0,\n style: CaptionStyle = 'shorts',\n): string {\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\n const bufferedStart = Math.max(0, startTime - buffer)\n const bufferedEnd = endTime + buffer\n\n const words = transcript.words.filter(\n (w) => w.start >= bufferedStart && w.end <= bufferedEnd,\n )\n if (words.length === 0) return header\n\n const adjusted: Word[] = words.map((w) => ({\n word: w.word,\n start: w.start - bufferedStart,\n end: w.end - bufferedStart,\n }))\n\n return header + buildPremiumDialogueLines(adjusted, style).join('\\n') + '\\n'\n}\n\n/**\n * Generate premium ASS captions for a composite clip made of multiple segments.\n * Each segment's words are extracted and remapped to the concatenated timeline,\n * accounting for the buffer added during clip extraction.\n */\nexport function generateStyledASSForComposite(\n transcript: Transcript,\n segments: { start: number; end: number }[],\n buffer: number = 1.0,\n style: CaptionStyle = 'shorts',\n): string {\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\n const allAdjusted: Word[] = []\n let runningOffset = 0\n\n for (const seg of segments) {\n const bufferedStart = Math.max(0, seg.start - buffer)\n const bufferedEnd = seg.end + buffer\n const segDuration = bufferedEnd - bufferedStart\n\n const words = transcript.words.filter(\n (w) => w.start >= bufferedStart && w.end <= bufferedEnd,\n )\n\n for (const w of words) {\n allAdjusted.push({\n word: w.word,\n start: w.start - bufferedStart + runningOffset,\n end: w.end - bufferedStart + runningOffset,\n })\n }\n\n runningOffset += segDuration\n }\n\n if (allAdjusted.length === 0) return header\n\n return header + buildPremiumDialogueLines(allAdjusted, style).join('\\n') + '\\n'\n}\n\n// ---------------------------------------------------------------------------\n// Hook text overlay for portrait shorts\n// ---------------------------------------------------------------------------\n\n/** Maximum characters for hook text before truncation. */\nconst HOOK_TEXT_MAX_LENGTH = 60\n\n/**\n * Generate ASS dialogue lines for a hook text overlay at the top of the video.\n *\n * The hook is a short attention-grabbing phrase (e.g. \"Here's why you should\n * learn TypeScript\") displayed as a translucent pill/badge at the top of a\n * portrait video for the first few seconds.\n *\n * Uses the `Hook` style defined in {@link ASS_HEADER_PORTRAIT} which has\n * `BorderStyle: 3` (opaque box background) and `Alignment: 8` (top-center).\n *\n * The `\\fad(300,500)` tag creates a 300ms fade-in and 500ms fade-out so the\n * hook doesn't appear/disappear abruptly.\n *\n * @param hookText - The attention-grabbing phrase (truncated to 60 chars)\n * @param displayDuration - How long to show the hook in seconds (default: 4s)\n * @param _style - Caption style (currently only 'portrait' uses hooks)\n * @returns A single ASS Dialogue line to append to the Events section\n */\nexport function generateHookOverlay(\n hookText: string,\n displayDuration: number = 4.0,\n _style: CaptionStyle = 'portrait',\n): string {\n const text =\n hookText.length > HOOK_TEXT_MAX_LENGTH\n ? hookText.slice(0, HOOK_TEXT_MAX_LENGTH - 3) + '...'\n : hookText\n\n return `Dialogue: 1,${toASS(0)},${toASS(displayDuration)},Hook,,0,0,0,,{\\\\fad(300,500)}${text}`\n}\n\n/**\n * Generate a complete portrait ASS file with captions AND hook text overlay.\n */\nexport function generatePortraitASSWithHook(\n transcript: Transcript,\n hookText: string,\n startTime: number,\n endTime: number,\n buffer?: number,\n): string {\n const baseASS = generateStyledASSForSegment(transcript, startTime, endTime, buffer, 'portrait')\n const hookLine = generateHookOverlay(hookText, 4.0, 'portrait')\n return baseASS + hookLine + '\\n'\n}\n\n/**\n * Generate a complete portrait ASS file for a composite clip with captions AND hook text overlay.\n */\nexport function generatePortraitASSWithHookComposite(\n transcript: Transcript,\n segments: { start: number; end: number }[],\n hookText: string,\n buffer?: number,\n): string {\n const baseASS = generateStyledASSForComposite(transcript, segments, buffer, 'portrait')\n const hookLine = generateHookOverlay(hookText, 4.0, 'portrait')\n return baseASS + hookLine + '\\n'\n}\n","import { join } from '../core/paths.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { VideoFile, Transcript } from '../types'\nimport { generateSRT, generateVTT, generateStyledASS } from '../tools/captions/captionGenerator'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\n/**\n * Generate SRT, VTT, and ASS caption files for a video and save them to disk.\n * Returns the list of written file paths.\n */\nexport async function generateCaptions(\n video: VideoFile,\n transcript: Transcript,\n): Promise<string[]> {\n const config = getConfig()\n const captionsDir = join(config.OUTPUT_DIR, video.slug, 'captions')\n await ensureDirectory(captionsDir)\n\n const srtPath = join(captionsDir, 'captions.srt')\n const vttPath = join(captionsDir, 'captions.vtt')\n const assPath = join(captionsDir, 'captions.ass')\n\n const srt = generateSRT(transcript)\n const vtt = generateVTT(transcript)\n const ass = generateStyledASS(transcript)\n\n await Promise.all([\n writeTextFile(srtPath, srt),\n writeTextFile(vttPath, vtt),\n writeTextFile(assPath, ass),\n ])\n\n const paths = [srtPath, vttPath, assPath]\n logger.info(`Captions saved: ${paths.join(', ')}`)\n return paths\n}\n","/**\n * CopilotProvider — wraps @github/copilot-sdk behind the LLMProvider interface.\n *\n * Extracts the Copilot-specific logic from BaseAgent into a reusable provider\n * that can be swapped with OpenAI or Claude providers via the abstraction layer.\n */\n\nimport { CopilotClient, CopilotSession } from '../core/ai.js'\nimport type { SessionEvent } from '../core/ai.js'\nimport logger from '../config/logger.js'\nimport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n TokenUsage,\n CostInfo,\n QuotaSnapshot,\n ToolCall,\n ProviderEvent,\n ProviderEventType,\n} from './types'\n\nconst DEFAULT_MODEL = 'claude-opus-4.5'\nconst DEFAULT_TIMEOUT_MS = 300_000 // 5 minutes\n\nexport class CopilotProvider implements LLMProvider {\n readonly name = 'copilot' as const\n private client: CopilotClient | null = null\n\n isAvailable(): boolean {\n // Copilot uses GitHub auth, not an API key\n return true\n }\n\n getDefaultModel(): string {\n return DEFAULT_MODEL\n }\n\n async createSession(config: SessionConfig): Promise<LLMSession> {\n if (!this.client) {\n this.client = new CopilotClient({ autoStart: true, logLevel: 'error' })\n }\n\n const copilotSession = await this.client.createSession({\n model: config.model,\n mcpServers: config.mcpServers,\n systemMessage: { mode: 'replace', content: config.systemPrompt },\n tools: config.tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n handler: t.handler,\n })),\n streaming: config.streaming ?? true,\n })\n\n return new CopilotSessionWrapper(\n copilotSession,\n config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n )\n }\n\n /** Tear down the underlying Copilot client. */\n async close(): Promise<void> {\n try {\n if (this.client) {\n await this.client.stop()\n this.client = null\n }\n } catch (err) {\n logger.error(`[CopilotProvider] Error during close: ${err}`)\n }\n }\n}\n\n/** Wraps a CopilotSession to satisfy the LLMSession interface. */\nclass CopilotSessionWrapper implements LLMSession {\n private eventHandlers = new Map<ProviderEventType, Array<(event: ProviderEvent) => void>>()\n\n // Latest usage data captured from assistant.usage events\n private lastUsage: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }\n private lastCost: CostInfo | undefined\n private lastQuotaSnapshots: Record<string, QuotaSnapshot> | undefined\n\n constructor(\n private readonly session: CopilotSession,\n private readonly timeoutMs: number,\n ) {\n this.setupEventForwarding()\n this.setupUsageTracking()\n }\n\n async sendAndWait(message: string): Promise<LLMResponse> {\n const start = Date.now()\n\n // Reset usage tracking for this call\n this.lastUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }\n this.lastCost = undefined\n this.lastQuotaSnapshots = undefined\n\n const response = await this.session.sendAndWait(\n { prompt: message },\n this.timeoutMs,\n )\n\n const content = response?.data?.content ?? ''\n const toolCalls: ToolCall[] = [] // Copilot SDK handles tool calls internally\n\n return {\n content,\n toolCalls,\n usage: this.lastUsage,\n cost: this.lastCost,\n quotaSnapshots: this.lastQuotaSnapshots,\n durationMs: Date.now() - start,\n }\n }\n\n on(event: ProviderEventType, handler: (event: ProviderEvent) => void): void {\n const handlers = this.eventHandlers.get(event) ?? []\n handlers.push(handler)\n this.eventHandlers.set(event, handlers)\n }\n\n async close(): Promise<void> {\n await this.session.destroy()\n this.eventHandlers.clear()\n }\n\n /** Capture assistant.usage events for token/cost tracking. */\n private setupUsageTracking(): void {\n this.session.on((event: SessionEvent) => {\n if (event.type === 'assistant.usage') {\n const d = event.data as Record<string, unknown>\n this.lastUsage = {\n inputTokens: (d.inputTokens as number) ?? 0,\n outputTokens: (d.outputTokens as number) ?? 0,\n totalTokens: ((d.inputTokens as number) ?? 0) + ((d.outputTokens as number) ?? 0),\n cacheReadTokens: d.cacheReadTokens as number | undefined,\n cacheWriteTokens: d.cacheWriteTokens as number | undefined,\n }\n if (d.cost != null) {\n this.lastCost = {\n amount: d.cost as number,\n unit: 'premium_requests',\n model: (d.model as string) ?? DEFAULT_MODEL,\n multiplier: d.multiplier as number | undefined,\n }\n }\n if (d.quotaSnapshots != null) {\n this.lastQuotaSnapshots = d.quotaSnapshots as Record<string, QuotaSnapshot>\n }\n }\n })\n }\n\n /** Forward CopilotSession events to ProviderEvent subscribers. */\n private setupEventForwarding(): void {\n this.session.on((event: SessionEvent) => {\n switch (event.type) {\n case 'assistant.message_delta':\n this.emit('delta', event.data)\n break\n case 'tool.execution_start':\n this.emit('tool_start', event.data)\n break\n case 'tool.execution_complete':\n this.emit('tool_end', event.data)\n break\n case 'assistant.usage':\n this.emit('usage', event.data)\n break\n case 'session.error':\n this.emit('error', event.data)\n break\n }\n })\n }\n\n private emit(type: ProviderEventType, data: unknown): void {\n const handlers = this.eventHandlers.get(type)\n if (handlers) {\n for (const handler of handlers) {\n handler({ type, data })\n }\n }\n }\n}\n","/**\n * OpenAI Provider — wraps the OpenAI SDK behind the LLMProvider interface.\n *\n * Implements chat completions with automatic tool-calling loop:\n * user message → LLM → (tool_calls? → execute → feed back → LLM)* → final text\n */\n\nimport { OpenAI } from '../core/ai.js';\nimport type {\n ChatCompletionMessageParam,\n ChatCompletionTool,\n} from 'openai/resources/chat/completions.js';\nimport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n ToolWithHandler,\n TokenUsage,\n ProviderEventType,\n ProviderEvent,\n} from './types.js';\nimport { calculateTokenCost } from '../config/pricing.js';\nimport logger from '../config/logger.js';\nimport { getConfig } from '../config/environment.js';\n\nconst MAX_TOOL_ROUNDS = 50;\n\n// ── helpers ────────────────────────────────────────────────────────────\n\n/** Convert our ToolWithHandler[] to the OpenAI SDK tool format. */\nfunction toOpenAITools(tools: ToolWithHandler[]): ChatCompletionTool[] {\n return tools.map((t) => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n}\n\n/** Build a handler lookup map keyed by tool name. */\nfunction buildHandlerMap(\n tools: ToolWithHandler[],\n): Map<string, ToolWithHandler['handler']> {\n return new Map(tools.map((t) => [t.name, t.handler]));\n}\n\n/** Sum two TokenUsage objects. */\nfunction addUsage(a: TokenUsage, b: TokenUsage): TokenUsage {\n return {\n inputTokens: a.inputTokens + b.inputTokens,\n outputTokens: a.outputTokens + b.outputTokens,\n totalTokens: a.totalTokens + b.totalTokens,\n };\n}\n\n// ── session ────────────────────────────────────────────────────────────\n\nclass OpenAISession implements LLMSession {\n private client: OpenAI;\n private model: string;\n private messages: ChatCompletionMessageParam[];\n private tools: ChatCompletionTool[];\n private handlers: Map<string, ToolWithHandler['handler']>;\n private listeners = new Map<ProviderEventType, ((e: ProviderEvent) => void)[]>();\n private timeoutMs?: number;\n\n constructor(client: OpenAI, config: SessionConfig, model: string) {\n this.client = client;\n this.model = model;\n this.messages = [{ role: 'system', content: config.systemPrompt }];\n this.tools = toOpenAITools(config.tools);\n this.handlers = buildHandlerMap(config.tools);\n this.timeoutMs = config.timeoutMs;\n }\n\n // ── public API ─────────────────────────────────────────────────────\n\n async sendAndWait(message: string): Promise<LLMResponse> {\n this.messages.push({ role: 'user', content: message });\n\n let cumulative: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\n const start = Date.now();\n\n // Agent loop: keep calling the LLM until no tool_calls remain\n let toolRound = 0;\n while (true) {\n if (++toolRound > MAX_TOOL_ROUNDS) {\n logger.warn(`OpenAI agent exceeded ${MAX_TOOL_ROUNDS} tool rounds — aborting to prevent runaway`);\n throw new Error(`Max tool rounds (${MAX_TOOL_ROUNDS}) exceeded — possible infinite loop`);\n }\n const controller = new AbortController();\n const timeoutId = this.timeoutMs\n ? setTimeout(() => controller.abort(), this.timeoutMs)\n : undefined;\n let response: OpenAI.Chat.Completions.ChatCompletion;\n try {\n response = await this.client.chat.completions.create(\n {\n model: this.model,\n messages: this.messages,\n ...(this.tools.length > 0 ? { tools: this.tools } : {}),\n },\n { signal: controller.signal },\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n\n const choice = response.choices[0];\n const assistantMsg = choice.message;\n\n // Accumulate token usage\n if (response.usage) {\n const iterUsage: TokenUsage = {\n inputTokens: response.usage.prompt_tokens,\n outputTokens: response.usage.completion_tokens,\n totalTokens: response.usage.total_tokens,\n };\n cumulative = addUsage(cumulative, iterUsage);\n this.emit('usage', iterUsage);\n }\n\n // Add assistant message to history\n this.messages.push(assistantMsg as ChatCompletionMessageParam);\n\n const toolCalls = assistantMsg.tool_calls;\n if (!toolCalls || toolCalls.length === 0) {\n // No more tool calls — return final response\n const cost = calculateTokenCost(this.model, cumulative.inputTokens, cumulative.outputTokens);\n return {\n content: assistantMsg.content ?? '',\n toolCalls: [],\n usage: cumulative,\n cost: { amount: cost, unit: 'usd', model: this.model },\n durationMs: Date.now() - start,\n };\n }\n\n // Execute each tool call and feed results back\n for (const tc of toolCalls) {\n if (tc.type !== 'function') continue;\n\n const fnName = tc.function.name;\n const handler = this.handlers.get(fnName);\n\n let result: unknown;\n if (!handler) {\n logger.warn(`OpenAI requested unknown tool: ${fnName}`);\n result = { error: `Unknown tool: ${fnName}` };\n } else {\n this.emit('tool_start', { name: fnName, arguments: tc.function.arguments });\n try {\n const args = JSON.parse(tc.function.arguments) as Record<string, unknown>;\n result = await handler(args);\n } catch (err) {\n logger.error(`Tool ${fnName} failed: ${err}`);\n result = { error: String(err) };\n }\n this.emit('tool_end', { name: fnName, result });\n }\n\n this.messages.push({\n role: 'tool',\n tool_call_id: tc.id,\n content: typeof result === 'string' ? result : JSON.stringify(result),\n });\n }\n // Loop back to call the LLM again with tool results\n }\n }\n\n on(event: ProviderEventType, handler: (e: ProviderEvent) => void): void {\n const list = this.listeners.get(event) ?? [];\n list.push(handler);\n this.listeners.set(event, list);\n }\n\n async close(): Promise<void> {\n this.messages = [];\n this.listeners.clear();\n }\n\n // ── internals ──────────────────────────────────────────────────────\n\n private emit(type: ProviderEventType, data: unknown): void {\n for (const handler of this.listeners.get(type) ?? []) {\n try {\n handler({ type, data });\n } catch {\n // Don't let listener errors break the agent loop\n }\n }\n }\n}\n\n// ── provider ───────────────────────────────────────────────────────────\n\nexport class OpenAIProvider implements LLMProvider {\n readonly name = 'openai' as const;\n\n isAvailable(): boolean {\n return !!getConfig().OPENAI_API_KEY;\n }\n\n getDefaultModel(): string {\n return 'gpt-4o';\n }\n\n async createSession(config: SessionConfig): Promise<LLMSession> {\n const client = new OpenAI(); // reads OPENAI_API_KEY from env\n const model = config.model ?? this.getDefaultModel();\n logger.info(`OpenAI session created (model=${model}, tools=${config.tools.length})`);\n return new OpenAISession(client, config, model);\n }\n}\n","/**\n * Claude (Anthropic) LLM Provider\n *\n * Wraps the Anthropic Messages API behind the LLMProvider interface.\n * Uses direct @anthropic-ai/sdk for tool-calling with our own agent loop.\n */\n\nimport { Anthropic } from '../core/ai.js'\nimport type {\n ContentBlock,\n MessageParam,\n TextBlock,\n Tool,\n ToolResultBlockParam,\n ToolUseBlock,\n} from '@anthropic-ai/sdk/resources/messages.js'\nimport { calculateTokenCost } from '../config/pricing.js'\nimport logger from '../config/logger.js'\nimport { getConfig } from '../config/environment.js'\nimport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n ToolWithHandler,\n TokenUsage,\n ProviderEventType,\n ProviderEvent,\n} from './types.js'\n\nconst DEFAULT_MODEL = 'claude-opus-4.6'\nconst DEFAULT_MAX_TOKENS = 8192\nconst MAX_TOOL_ROUNDS = 50\n\n/** Convert our ToolWithHandler[] to Anthropic tool format */\nfunction toAnthropicTools(tools: ToolWithHandler[]): Tool[] {\n return tools.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.parameters as Tool['input_schema'],\n }))\n}\n\n/** Extract text content from Anthropic response content blocks */\nfunction extractText(content: ContentBlock[]): string {\n return content\n .filter((b): b is TextBlock => b.type === 'text')\n .map((b) => b.text)\n .join('')\n}\n\n/** Extract tool_use blocks from Anthropic response */\nfunction extractToolUse(content: ContentBlock[]): ToolUseBlock[] {\n return content.filter((b): b is ToolUseBlock => b.type === 'tool_use')\n}\n\nclass ClaudeSession implements LLMSession {\n private client: Anthropic\n private systemPrompt: string\n private tools: ToolWithHandler[]\n private anthropicTools: Tool[]\n private messages: MessageParam[] = []\n private model: string\n private maxTokens: number\n private handlers = new Map<ProviderEventType, ((event: ProviderEvent) => void)[]>()\n private timeoutMs?: number\n\n constructor(client: Anthropic, config: SessionConfig) {\n this.client = client\n this.systemPrompt = config.systemPrompt\n this.tools = config.tools\n this.anthropicTools = toAnthropicTools(config.tools)\n this.model = config.model ?? DEFAULT_MODEL\n this.maxTokens = DEFAULT_MAX_TOKENS\n this.timeoutMs = config.timeoutMs\n }\n\n on(event: ProviderEventType, handler: (event: ProviderEvent) => void): void {\n const list = this.handlers.get(event) ?? []\n list.push(handler)\n this.handlers.set(event, list)\n }\n\n private emit(type: ProviderEventType, data: unknown): void {\n for (const handler of this.handlers.get(type) ?? []) {\n handler({ type, data })\n }\n }\n\n async sendAndWait(message: string): Promise<LLMResponse> {\n this.messages.push({ role: 'user', content: message })\n\n let cumulativeUsage: TokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n }\n\n const startMs = Date.now()\n\n // Agent loop: keep calling until no more tool_use\n let toolRound = 0\n while (true) {\n if (++toolRound > MAX_TOOL_ROUNDS) {\n logger.warn(`Claude agent exceeded ${MAX_TOOL_ROUNDS} tool rounds — aborting to prevent runaway`)\n throw new Error(`Max tool rounds (${MAX_TOOL_ROUNDS}) exceeded — possible infinite loop`)\n }\n const controller = new AbortController()\n const timeoutId = this.timeoutMs\n ? setTimeout(() => controller.abort(), this.timeoutMs)\n : undefined\n let response: Anthropic.Messages.Message\n try {\n response = await this.client.messages.create(\n {\n model: this.model,\n max_tokens: this.maxTokens,\n system: this.systemPrompt,\n messages: this.messages,\n ...(this.anthropicTools.length > 0 ? { tools: this.anthropicTools } : {}),\n },\n { signal: controller.signal },\n )\n } finally {\n if (timeoutId) clearTimeout(timeoutId)\n }\n\n // Accumulate usage\n cumulativeUsage.inputTokens += response.usage.input_tokens\n cumulativeUsage.outputTokens += response.usage.output_tokens\n cumulativeUsage.totalTokens =\n cumulativeUsage.inputTokens + cumulativeUsage.outputTokens\n\n if (response.usage.cache_read_input_tokens) {\n cumulativeUsage.cacheReadTokens =\n (cumulativeUsage.cacheReadTokens ?? 0) + response.usage.cache_read_input_tokens\n }\n if (response.usage.cache_creation_input_tokens) {\n cumulativeUsage.cacheWriteTokens =\n (cumulativeUsage.cacheWriteTokens ?? 0) + response.usage.cache_creation_input_tokens\n }\n\n this.emit('usage', cumulativeUsage)\n\n // Add assistant response to history\n this.messages.push({ role: 'assistant', content: response.content })\n\n const toolUseBlocks = extractToolUse(response.content)\n\n if (toolUseBlocks.length === 0 || response.stop_reason === 'end_turn') {\n // No tool calls — return final text\n const text = extractText(response.content)\n const cost = calculateTokenCost(\n this.model,\n cumulativeUsage.inputTokens,\n cumulativeUsage.outputTokens,\n )\n\n return {\n content: text,\n toolCalls: [],\n usage: cumulativeUsage,\n cost: cost > 0\n ? { amount: cost, unit: 'usd', model: this.model }\n : undefined,\n durationMs: Date.now() - startMs,\n }\n }\n\n // Execute tool calls and build result messages\n const toolResults: ToolResultBlockParam[] = []\n\n for (const block of toolUseBlocks) {\n const tool = this.tools.find((t) => t.name === block.name)\n if (!tool) {\n logger.warn(`Claude requested unknown tool: ${block.name}`)\n toolResults.push({\n type: 'tool_result',\n tool_use_id: block.id,\n content: JSON.stringify({ error: `Unknown tool: ${block.name}` }),\n })\n continue\n }\n\n this.emit('tool_start', { name: block.name, arguments: block.input })\n\n try {\n const result = await tool.handler(block.input as Record<string, unknown>)\n toolResults.push({\n type: 'tool_result',\n tool_use_id: block.id,\n content: JSON.stringify(result),\n })\n this.emit('tool_end', { name: block.name, result })\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err)\n logger.error(`Tool ${block.name} failed: ${errorMsg}`)\n toolResults.push({\n type: 'tool_result',\n tool_use_id: block.id,\n content: JSON.stringify({ error: errorMsg }),\n is_error: true,\n })\n this.emit('error', { name: block.name, error: errorMsg })\n }\n }\n\n // Add tool results as a user message and loop\n this.messages.push({ role: 'user', content: toolResults })\n }\n }\n\n async close(): Promise<void> {\n this.messages = []\n this.handlers.clear()\n }\n}\n\nexport class ClaudeProvider implements LLMProvider {\n readonly name = 'claude' as const\n\n isAvailable(): boolean {\n return !!getConfig().ANTHROPIC_API_KEY\n }\n\n getDefaultModel(): string {\n return DEFAULT_MODEL\n }\n\n async createSession(config: SessionConfig): Promise<LLMSession> {\n const client = new Anthropic()\n return new ClaudeSession(client, config)\n }\n}\n","import type { LLMProvider } from './types.js';\nimport type { ProviderName } from './types.js';\nimport { CopilotProvider } from './CopilotProvider.js';\nimport { OpenAIProvider } from './OpenAIProvider.js';\nimport { ClaudeProvider } from './ClaudeProvider.js';\nimport logger from '../config/logger.js';\nimport { getConfig } from '../config/environment.js';\n\nconst providers: Record<ProviderName, () => LLMProvider> = {\n copilot: () => new CopilotProvider(),\n openai: () => new OpenAIProvider(),\n claude: () => new ClaudeProvider(),\n};\n\n/** Cached singleton provider instance */\nlet currentProvider: LLMProvider | null = null;\nlet currentProviderName: ProviderName | null = null;\n\n/**\n * Get the configured LLM provider.\n * Reads from LLM_PROVIDER env var, defaults to 'copilot'.\n * Caches the instance for reuse.\n */\nexport function getProvider(name?: ProviderName): LLMProvider {\n const raw = name ?? getConfig().LLM_PROVIDER.trim().toLowerCase();\n const providerName = raw as ProviderName;\n \n if (currentProvider && currentProviderName === providerName) {\n return currentProvider;\n }\n\n // Close old provider if switching to a different one\n currentProvider?.close?.().catch(() => { /* ignore close errors */ });\n\n if (!providers[providerName]) {\n throw new Error(\n `Unknown LLM provider: \"${providerName}\". ` +\n `Valid options: ${Object.keys(providers).join(', ')}`\n );\n }\n\n const provider = providers[providerName]();\n \n if (!provider.isAvailable()) {\n logger.warn(\n `Provider \"${providerName}\" is not available (missing API key or config). ` +\n `Falling back to copilot provider.`\n );\n currentProvider = providers.copilot();\n currentProviderName = 'copilot';\n return currentProvider;\n }\n\n logger.info(`Using LLM provider: ${providerName} (model: ${provider.getDefaultModel()})`);\n currentProvider = provider;\n currentProviderName = providerName;\n return currentProvider;\n}\n\n/** Reset the cached provider (for testing) */\nexport async function resetProvider(): Promise<void> {\n try { await currentProvider?.close?.(); } catch { /* ignore close errors */ }\n currentProvider = null;\n currentProviderName = null;\n}\n\n/** Get the name of the current provider */\nexport function getProviderName(): ProviderName {\n const raw = getConfig().LLM_PROVIDER.trim().toLowerCase();\n const valid: ProviderName[] = ['copilot', 'openai', 'claude'];\n return currentProviderName ?? (valid.includes(raw as ProviderName) ? (raw as ProviderName) : 'copilot');\n}\n\n// Re-export types and providers\nexport type { LLMProvider, LLMSession, LLMResponse, SessionConfig, ToolWithHandler, TokenUsage, CostInfo, QuotaSnapshot, ProviderEvent, ProviderEventType } from './types.js';\nexport type { ProviderName } from './types.js';\nexport { CopilotProvider } from './CopilotProvider.js';\nexport { OpenAIProvider } from './OpenAIProvider.js';\nexport { ClaudeProvider } from './ClaudeProvider.js';\n","/**\n * Per-Agent Model Selection\n *\n * Central config for which LLM model each agent should use.\n * Override any agent via env var MODEL_<AGENT_NAME_UPPER> or globally via LLM_MODEL.\n */\n\nimport { getConfig } from './environment.js';\n\nexport const PREMIUM_MODEL = 'claude-opus-4.5';\nexport const STANDARD_MODEL = 'claude-sonnet-4.5';\nexport const FREE_MODEL = 'gpt-4.1';\n\nexport const AGENT_MODEL_MAP: Record<string, string> = {\n SilenceRemovalAgent: PREMIUM_MODEL,\n ShortsAgent: PREMIUM_MODEL,\n MediumVideoAgent: PREMIUM_MODEL,\n SocialMediaAgent: PREMIUM_MODEL,\n BlogAgent: PREMIUM_MODEL,\n SummaryAgent: PREMIUM_MODEL,\n ChapterAgent: PREMIUM_MODEL,\n ShortPostsAgent: PREMIUM_MODEL,\n MediumClipPostsAgent: PREMIUM_MODEL,\n};\n\n/**\n * Resolve model for an agent. Priority:\n * 1. MODEL_<AGENT_NAME_UPPER> env var\n * 2. AGENT_MODEL_MAP entry\n * 3. Global LLM_MODEL env var\n * 4. undefined (provider default)\n */\nexport function getModelForAgent(agentName: string): string | undefined {\n // Per-agent env override (dynamic keys like MODEL_SHORTS_AGENT)\n const envKey = `MODEL_${agentName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()}`;\n const envOverride = process.env[envKey];\n if (envOverride) return envOverride;\n\n const mapped = AGENT_MODEL_MAP[agentName];\n if (mapped) return mapped;\n\n const global = getConfig().LLM_MODEL;\n if (global) return global;\n\n return undefined;\n}\n","import type { LLMProvider, LLMSession, ToolWithHandler, MCPServerConfig } from '../providers/types.js'\nimport { getProvider } from '../providers/index.js'\nimport { getModelForAgent } from '../config/modelConfig.js'\nimport { costTracker } from '../services/costTracker.js'\nimport logger from '../config/logger.js'\n\n/**\n * BaseAgent — abstract foundation for all LLM-powered agents.\n *\n * ### Agent pattern\n * Each agent in the pipeline (SummaryAgent, ShortsAgent, BlogAgent, etc.)\n * extends BaseAgent and implements two methods:\n * - `getTools()` — declares the tools (functions) the LLM can call\n * - `handleToolCall()` — dispatches tool invocations to concrete implementations\n *\n * ### Tool registration\n * Tools are declared as JSON Schema objects and passed to the LLMSession\n * at creation time. When the LLM decides to call a tool, the provider routes\n * the call through the tool handler where the subclass executes the actual\n * logic (e.g. reading files, running FFmpeg, querying APIs).\n *\n * ### Message flow\n * 1. `run(userMessage)` lazily creates an LLMSession via the configured\n * provider (Copilot, OpenAI, or Claude).\n * 2. The user message is sent via `sendAndWait()`, which blocks until the\n * LLM produces a final response (with a 5-minute timeout).\n * 3. During processing, the LLM may invoke tools multiple times — each call\n * is logged via session event handlers.\n * 4. The final assistant message text is returned to the caller.\n *\n * Sessions are reusable: calling `run()` multiple times on the same agent\n * sends additional messages within the same conversation context.\n */\nexport abstract class BaseAgent {\n protected provider: LLMProvider\n protected session: LLMSession | null = null\n protected readonly model?: string\n\n constructor(\n protected readonly agentName: string,\n protected readonly systemPrompt: string,\n provider?: LLMProvider,\n model?: string,\n ) {\n this.provider = provider ?? getProvider()\n this.model = model\n }\n\n /** Tools this agent exposes to the LLM. Override in subclasses. */\n protected getTools(): ToolWithHandler[] {\n return []\n }\n\n /** MCP servers this agent needs. Override in subclasses that use MCP tools. */\n protected getMcpServers(): Record<string, MCPServerConfig> | undefined {\n return undefined\n }\n\n /** Dispatch a tool call to the concrete agent. Override in subclasses. */\n protected abstract handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown>\n\n /**\n * Send a user message to the agent and return the final response text.\n *\n * 1. Lazily creates an LLMSession via the provider\n * 2. Registers event listeners for logging\n * 3. Calls sendAndWait and records usage via CostTracker\n */\n async run(userMessage: string): Promise<string> {\n if (!this.session) {\n this.session = await this.provider.createSession({\n systemPrompt: this.systemPrompt,\n tools: this.getTools(),\n streaming: true,\n model: this.model ?? getModelForAgent(this.agentName),\n timeoutMs: 300_000, // 5 min timeout\n mcpServers: this.getMcpServers(),\n })\n this.setupEventHandlers(this.session)\n }\n\n logger.info(`[${this.agentName}] Sending message: ${userMessage.substring(0, 80)}…`)\n\n costTracker.setAgent(this.agentName)\n const response = await this.session.sendAndWait(userMessage)\n\n // Record usage via CostTracker\n costTracker.recordUsage(\n this.provider.name,\n response.cost?.model ?? this.provider.getDefaultModel(),\n response.usage,\n response.cost,\n response.durationMs,\n response.quotaSnapshots\n ? Object.values(response.quotaSnapshots)[0]\n : undefined,\n )\n\n const content = response.content\n logger.info(`[${this.agentName}] Response received (${content.length} chars)`)\n return content\n }\n\n /** Wire up session event listeners for logging. */\n private setupEventHandlers(session: LLMSession): void {\n session.on('delta', (event) => {\n logger.debug(`[${this.agentName}] delta: ${JSON.stringify(event.data)}`)\n })\n\n session.on('tool_start', (event) => {\n logger.info(`[${this.agentName}] tool start: ${JSON.stringify(event.data)}`)\n })\n\n session.on('tool_end', (event) => {\n logger.info(`[${this.agentName}] tool done: ${JSON.stringify(event.data)}`)\n })\n\n session.on('error', (event) => {\n logger.error(`[${this.agentName}] error: ${JSON.stringify(event.data)}`)\n })\n }\n\n /** Tear down the session. */\n async destroy(): Promise<void> {\n try {\n if (this.session) {\n await this.session.close()\n this.session = null\n }\n } catch (err) {\n logger.error(`[${this.agentName}] Error during destroy: ${err}`)\n }\n }\n}\n","import { createFFmpeg } from '../../core/ffmpeg.js'\nimport { ensureDirectory } from '../../core/fileSystem.js'\nimport { dirname, join } from '../../core/paths.js'\nimport logger from '../../config/logger'\n\n/**\n * Extract a single PNG frame at the given timestamp (seconds).\n */\nexport async function captureFrame(\n videoPath: string,\n timestamp: number,\n outputPath: string,\n): Promise<string> {\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n logger.info(`Capturing frame at ${timestamp}s → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n createFFmpeg(videoPath)\n .seekInput(timestamp)\n .frames(1)\n .output(outputPath)\n .on('end', () => {\n logger.info(`Frame captured: ${outputPath}`);\n resolve(outputPath);\n })\n .on('error', (err) => {\n logger.error(`Frame capture failed: ${err.message}`);\n reject(new Error(`Frame capture failed: ${err.message}`));\n })\n .run();\n });\n}\n\n/**\n * Extract multiple frames at the given timestamps.\n * Files are named snapshot-001.png, snapshot-002.png, etc.\n */\nexport async function captureFrames(\n videoPath: string,\n timestamps: number[],\n outputDir: string,\n): Promise<string[]> {\n await ensureDirectory(outputDir);\n\n const results: string[] = [];\n\n for (let i = 0; i < timestamps.length; i++) {\n const idx = String(i + 1).padStart(3, '0');\n const outputPath = join(outputDir, `snapshot-${idx}.png`);\n await captureFrame(videoPath, timestamps[i], outputPath);\n results.push(outputPath);\n }\n\n logger.info(`Captured ${results.length} frames in ${outputDir}`);\n return results;\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\n\nimport { BaseAgent } from './BaseAgent'\nimport { captureFrame } from '../tools/ffmpeg/frameCapture'\nimport logger from '../config/logger'\nimport { getBrandConfig } from '../config/brand'\nimport { getConfig } from '../config/environment'\nimport type { VideoFile, Transcript, VideoSummary, VideoSnapshot, ShortClip, Chapter } from '../types'\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Format seconds → \"MM:SS\" */\nfunction fmtTime(seconds: number): string {\n const m = Math.floor(seconds / 60)\n const s = Math.floor(seconds % 60)\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\n\n/** Build a compact transcript block with timestamps for the LLM prompt. */\nfunction buildTranscriptBlock(transcript: Transcript): string {\n return transcript.segments\n .map((seg) => `[${fmtTime(seg.start)} → ${fmtTime(seg.end)}] ${seg.text.trim()}`)\n .join('\\n')\n}\n\n// ── System prompt ────────────────────────────────────────────────────────────\n\nfunction buildSystemPrompt(shortsInfo: string, socialPostsInfo: string, captionsInfo: string, chaptersInfo: string): string {\n const brand = getBrandConfig()\n\n return `You are a Video Summary Agent writing from the perspective of ${brand.name} (${brand.handle}).\nBrand voice: ${brand.voice.tone}. ${brand.voice.personality} ${brand.voice.style}\n\nYour job is to analyse a video transcript and produce a beautiful, narrative-style Markdown README.\n\n**Workflow**\n1. Read the transcript carefully.\n2. Identify 3-8 key topics, decisions, highlights, or memorable moments.\n3. For each highlight, decide on a representative timestamp and call the \"capture_frame\" tool to grab a screenshot.\n4. Once all frames are captured, call the \"write_summary\" tool with the final Markdown.\n\n**Markdown structure — follow this layout exactly:**\n\n\\`\\`\\`\n# [Video Title]\n\n> [Compelling one-line hook/tagline that captures the video's value]\n\n[2-3 paragraph natural summary that reads like a blog post, NOT a timeline.\nWeave in key insights naturally. Write in the brand voice: ${brand.voice.tone}.\n${brand.contentGuidelines.blogFocus}]\n\n---\n\n## Key Moments\n\n[For each key topic: write a narrative paragraph (not bullet points).\nEmbed the timestamp as an inline badge like \\`[0:12]\\` within the text, NOT as a section header.\nEmbed the screenshot naturally within or after the paragraph.\nUse blockquotes (>) for standout quotes or insights.]\n\n\n\n[Continue with next topic paragraph...]\n\n---\n\n## 📊 Quick Reference\n\n| Topic | Timestamp |\n|-------|-----------|\n| Topic name | \\`M:SS\\` |\n| ... | ... |\n\n---\n${chaptersInfo}\n${shortsInfo}\n${socialPostsInfo}\n${captionsInfo}\n\n---\n\n*Generated on [DATE] • Duration: [DURATION] • Tags: [relevant tags]*\n\\`\\`\\`\n\n**Writing style rules**\n- Write in a narrative, blog-post style — NOT a timestamp-driven timeline.\n- Timestamps appear as subtle inline badges like \\`[0:12]\\` or \\`[1:30]\\` within sentences, never as section headers.\n- The summary paragraphs should flow naturally and be enjoyable to read.\n- Use the brand perspective: ${brand.voice.personality}\n- Topics to emphasize: ${brand.advocacy.interests.join(', ')}\n- Avoid: ${brand.advocacy.avoids.join(', ')}\n\n**Screenshot distribution rules — CRITICAL**\n- You MUST spread screenshots across the ENTIRE video duration, from beginning to end.\n- Divide the video into equal segments based on the number of screenshots you plan to capture, and pick one timestamp from each segment.\n- NO MORE than 2 screenshots should fall within the same 60-second window.\n- If the video is longer than 2 minutes, your first screenshot must NOT be in the first 10% and your last screenshot must be in the final 30% of the video.\n- Use the suggested timestamp ranges provided in the user message as guidance, but pick the exact moment within each range that best matches a key topic in the transcript.\n\n**Tool rules**\n- Always call \"capture_frame\" BEFORE \"write_summary\".\n- The snapshot index must be a 1-based integer; the filename will be snapshot-001.png, etc.\n- In the Markdown, reference screenshots as \\`thumbnails/snapshot-001.png\\` (relative path).\n- Call \"write_summary\" exactly once with the complete Markdown string.`\n}\n\n// ── Tool argument shapes ─────────────────────────────────────────────────────\n\ninterface CaptureFrameArgs {\n timestamp: number\n description: string\n index: number\n}\n\ninterface WriteSummaryArgs {\n markdown: string\n title: string\n overview: string\n keyTopics: string[]\n}\n\n// ── SummaryAgent ─────────────────────────────────────────────────────────────\n\nclass SummaryAgent extends BaseAgent {\n private videoPath: string\n private outputDir: string\n private snapshots: VideoSnapshot[] = []\n\n constructor(videoPath: string, outputDir: string, systemPrompt: string, model?: string) {\n super('SummaryAgent', systemPrompt, undefined, model)\n this.videoPath = videoPath\n this.outputDir = outputDir\n }\n\n // Resolved paths\n private get thumbnailDir(): string {\n return join(this.outputDir, 'thumbnails')\n }\n\n private get markdownPath(): string {\n return join(this.outputDir, 'README.md')\n }\n\n /* ── Tools exposed to the LLM ─────────────────────────────────────────── */\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'capture_frame',\n description:\n 'Capture a screenshot from the video at a specific timestamp. ' +\n 'Provide: timestamp (seconds), description (what is shown), index (1-based integer for filename).',\n parameters: {\n type: 'object',\n properties: {\n timestamp: { type: 'number', description: 'Timestamp in seconds to capture' },\n description: { type: 'string', description: 'Brief description of the visual moment' },\n index: { type: 'integer', description: '1-based snapshot index (used for filename)' },\n },\n required: ['timestamp', 'description', 'index'],\n },\n handler: async (rawArgs: unknown) => {\n const args = rawArgs as CaptureFrameArgs\n return this.handleCaptureFrame(args)\n },\n },\n {\n name: 'write_summary',\n description:\n 'Write the final Markdown summary to disk. ' +\n 'Provide: markdown (full README content), title, overview, and keyTopics array.',\n parameters: {\n type: 'object',\n properties: {\n markdown: { type: 'string', description: 'Complete Markdown content for README.md' },\n title: { type: 'string', description: 'Video title' },\n overview: { type: 'string', description: 'Short overview paragraph' },\n keyTopics: {\n type: 'array',\n items: { type: 'string' },\n description: 'List of key topic names',\n },\n },\n required: ['markdown', 'title', 'overview', 'keyTopics'],\n },\n handler: async (rawArgs: unknown) => {\n const args = rawArgs as WriteSummaryArgs\n return this.handleWriteSummary(args)\n },\n },\n ]\n }\n\n /* ── Tool dispatch (required by BaseAgent) ─────────────────────────────── */\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n switch (toolName) {\n case 'capture_frame':\n return this.handleCaptureFrame(args as unknown as CaptureFrameArgs)\n case 'write_summary':\n return this.handleWriteSummary(args as unknown as WriteSummaryArgs)\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n /* ── Tool implementations ──────────────────────────────────────────────── */\n\n private async handleCaptureFrame(args: CaptureFrameArgs): Promise<string> {\n const idx = String(args.index).padStart(3, '0')\n const filename = `snapshot-${idx}.png`\n const outputPath = join(this.thumbnailDir, filename)\n\n await captureFrame(this.videoPath, args.timestamp, outputPath)\n\n const snapshot: VideoSnapshot = {\n timestamp: args.timestamp,\n description: args.description,\n outputPath,\n }\n this.snapshots.push(snapshot)\n\n logger.info(`[SummaryAgent] Captured snapshot ${idx} at ${fmtTime(args.timestamp)}`)\n return `Frame captured: thumbnails/${filename}`\n }\n\n private async handleWriteSummary(args: WriteSummaryArgs): Promise<string> {\n await ensureDirectory(this.outputDir)\n await writeTextFile(this.markdownPath, args.markdown)\n\n logger.info(`[SummaryAgent] Wrote summary → ${this.markdownPath}`)\n return `Summary written to ${this.markdownPath}`\n }\n\n /** Expose collected data after the run. */\n getResult(args: WriteSummaryArgs): VideoSummary {\n return {\n title: args.title,\n overview: args.overview,\n keyTopics: args.keyTopics,\n snapshots: this.snapshots,\n markdownPath: this.markdownPath,\n }\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/** Build the Shorts section for the README template. */\nfunction buildShortsSection(shorts?: ShortClip[]): string {\n if (!shorts || shorts.length === 0) {\n return `\n## ✂️ Shorts\n\n| Short | Duration | Description |\n|-------|----------|-------------|\n| *Shorts will appear here once generated* | | |`\n }\n\n const rows = shorts\n .map((s) => `| [${s.title}](shorts/${s.slug}.mp4) | ${Math.round(s.totalDuration)}s | ${s.description} |`)\n .join('\\n')\n\n return `\n## ✂️ Shorts\n\n| Short | Duration | Description |\n|-------|----------|-------------|\n${rows}`\n}\n\n/** Build the Social Media Posts section for the README template. */\nfunction buildSocialPostsSection(): string {\n return `\n## 📱 Social Media Posts\n\n- [TikTok](social-posts/tiktok.md)\n- [YouTube](social-posts/youtube.md)\n- [Instagram](social-posts/instagram.md)\n- [LinkedIn](social-posts/linkedin.md)\n- [X / Twitter](social-posts/x.md)\n- [Dev.to Blog](social-posts/devto.md)`\n}\n\n/** Build the Captions section for the README template. */\nfunction buildCaptionsSection(): string {\n return `\n## 🎬 Captions\n\n- [SRT](captions/captions.srt) | [VTT](captions/captions.vtt) | [ASS (Styled)](captions/captions.ass)`\n}\n\n/** Format seconds → YouTube-style timestamp for chapters display */\nfunction toYouTubeTimestamp(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`\n}\n\n/** Build the Chapters section for the README template. */\nfunction buildChaptersSection(chapters?: Chapter[]): string {\n if (!chapters || chapters.length === 0) {\n return ''\n }\n\n const rows = chapters\n .map((ch) => `| \\`${toYouTubeTimestamp(ch.timestamp)}\\` | ${ch.title} | ${ch.description} |`)\n .join('\\n')\n\n return `\n## 📑 Chapters\n\n| Time | Chapter | Description |\n|------|---------|-------------|\n${rows}\n\n> 📋 [YouTube Timestamps](chapters/chapters-youtube.txt) • [Markdown](chapters/chapters.md) • [JSON](chapters/chapters.json)`\n}\n\n/**\n * Generate a beautiful Markdown summary for a video recording.\n *\n * 1. Creates a SummaryAgent with `capture_frame` and `write_summary` tools\n * 2. Builds a prompt containing the full transcript with timestamps\n * 3. Lets the agent analyse the transcript, capture key frames, and write Markdown\n * 4. Returns a {@link VideoSummary} with metadata and snapshot paths\n */\nexport async function generateSummary(\n video: VideoFile,\n transcript: Transcript,\n shorts?: ShortClip[],\n chapters?: Chapter[],\n model?: string,\n): Promise<VideoSummary> {\n const config = getConfig()\n const outputDir = join(config.OUTPUT_DIR, video.slug)\n\n // Build content-section snippets for the system prompt\n const shortsInfo = buildShortsSection(shorts)\n const socialPostsInfo = buildSocialPostsSection()\n const captionsInfo = buildCaptionsSection()\n const chaptersInfo = buildChaptersSection(chapters)\n\n const systemPrompt = buildSystemPrompt(shortsInfo, socialPostsInfo, captionsInfo, chaptersInfo)\n const agent = new SummaryAgent(video.repoPath, outputDir, systemPrompt, model)\n\n const transcriptBlock = buildTranscriptBlock(transcript)\n\n // Pre-calculate suggested screenshot time ranges spread across the full video\n const screenshotCount = Math.min(8, Math.max(3, Math.round(video.duration / 120)))\n const interval = video.duration / screenshotCount\n const suggestedRanges = Array.from({ length: screenshotCount }, (_, i) => {\n const center = Math.round(interval * (i + 0.5))\n const lo = Math.max(0, Math.round(center - interval / 2))\n const hi = Math.min(Math.round(video.duration), Math.round(center + interval / 2))\n return `${fmtTime(lo)}–${fmtTime(hi)} (${lo}s–${hi}s)`\n }).join(', ')\n\n const userPrompt = [\n `**Video:** ${video.filename}`,\n `**Duration:** ${fmtTime(video.duration)} (${Math.round(video.duration)} seconds)`,\n `**Date:** ${video.createdAt.toISOString().slice(0, 10)}`,\n '',\n `**Suggested screenshot time ranges (one screenshot per range):**`,\n suggestedRanges,\n '',\n '---',\n '',\n '**Transcript:**',\n '',\n transcriptBlock,\n ].join('\\n')\n\n let lastWriteArgs: WriteSummaryArgs | undefined\n\n // Intercept write_summary args so we can build the return value\n // Uses `as any` to access private method — required by the intercept-and-capture pattern\n const origHandleWrite = (agent as any).handleWriteSummary.bind(agent) as (\n a: WriteSummaryArgs,\n ) => Promise<string>\n ;(agent as any).handleWriteSummary = async (args: WriteSummaryArgs) => {\n lastWriteArgs = args\n return origHandleWrite(args)\n }\n\n try {\n await agent.run(userPrompt)\n\n if (!lastWriteArgs) {\n throw new Error('SummaryAgent did not call write_summary')\n }\n\n return agent.getResult(lastWriteArgs)\n } finally {\n await agent.destroy()\n }\n}\n","import { execFile as nodeExecFile, execSync as nodeExecSync, spawnSync as nodeSpawnSync } from 'child_process'\nimport type { ExecFileOptions, SpawnSyncReturns, SpawnSyncOptions } from 'child_process'\nimport { createRequire } from 'module'\n\nexport type { ExecFileOptions }\n\nexport interface ExecResult {\n stdout: string\n stderr: string\n}\n\n/**\n * Execute a command asynchronously via execFile.\n * Returns promise of { stdout, stderr }.\n */\nexport function execCommand(\n cmd: string,\n args: string[],\n opts?: ExecFileOptions & { maxBuffer?: number },\n): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n nodeExecFile(cmd, args, { ...opts, encoding: 'utf-8' } as any, (error, stdout, stderr) => {\n if (error) {\n reject(Object.assign(error, { stdout: String(stdout ?? ''), stderr: String(stderr ?? '') }))\n } else {\n resolve({ stdout: String(stdout ?? ''), stderr: String(stderr ?? '') })\n }\n })\n })\n}\n\n/**\n * Execute a command with a callback (for cases where consumers need the raw callback pattern).\n * This matches the execFile signature used by captionBurning, singlePassEdit, etc.\n */\nexport function execFileRaw(\n cmd: string,\n args: string[],\n opts: ExecFileOptions & { maxBuffer?: number },\n callback: (error: Error | null, stdout: string, stderr: string) => void,\n): void {\n nodeExecFile(cmd, args, { ...opts, encoding: 'utf-8' } as any, (error, stdout, stderr) => {\n callback(error, String(stdout ?? ''), String(stderr ?? ''))\n })\n}\n\n/**\n * Execute a command synchronously. Returns trimmed stdout.\n * Throws on failure.\n */\nexport function execCommandSync(cmd: string, opts?: { encoding?: BufferEncoding; stdio?: any; cwd?: string }): string {\n return nodeExecSync(cmd, { encoding: 'utf-8' as BufferEncoding, ...opts }).toString().trim()\n}\n\n/**\n * Spawn a command synchronously. Returns full result including status.\n */\nexport function spawnCommand(\n cmd: string,\n args: string[],\n opts?: SpawnSyncOptions,\n): SpawnSyncReturns<string> {\n return nodeSpawnSync(cmd, args, { encoding: 'utf-8', ...opts }) as SpawnSyncReturns<string>\n}\n\n/**\n * Create a require function for ESM modules to use CommonJS require().\n * Usage: const require = createModuleRequire(import.meta.url)\n */\nexport function createModuleRequire(metaUrl: string): NodeRequire {\n return createRequire(metaUrl)\n}\n","import { createFFmpeg, getFFmpegPath, getFFprobePath } from '../../core/ffmpeg.js'\nimport { execFileRaw } from '../../core/process.js'\nimport { ensureDirectory, writeTextFile, closeFileDescriptor, tmp } from '../../core/fileSystem.js'\nimport { dirname, join } from '../../core/paths.js'\n\nimport logger from '../../config/logger'\nimport { ShortSegment } from '../../types'\n\nconst ffmpegPath = getFFmpegPath()\nconst ffprobePath = getFFprobePath()\n\nconst DEFAULT_FPS = 25;\n\n/**\n * Probe the source video's frame rate using ffprobe.\n * Returns a rounded integer fps, or DEFAULT_FPS if probing fails.\n * Needed because FFmpeg 7.x xfade requires constant-framerate inputs.\n */\nasync function getVideoFps(videoPath: string): Promise<number> {\n return new Promise<number>((resolve) => {\n execFileRaw(\n ffprobePath,\n ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=r_frame_rate', '-of', 'csv=p=0', videoPath],\n { timeout: 5000 },\n (error, stdout) => {\n if (error || !stdout.trim()) {\n resolve(DEFAULT_FPS);\n return;\n }\n const parts = stdout.trim().split('/');\n const fps = parts.length === 2 ? parseInt(parts[0]) / parseInt(parts[1]) : parseFloat(stdout.trim());\n resolve(isFinite(fps) && fps > 0 ? Math.round(fps) : DEFAULT_FPS);\n },\n );\n });\n}\n\n/**\n * Extract a single clip segment using re-encode for frame-accurate timing.\n *\n * ### Why re-encode instead of `-c copy`?\n * Stream copy (`-c copy`) seeks to the nearest **keyframe** before the\n * requested start time, which creates a PTS offset between the clip's actual\n * start and the timestamp the caption generator assumes. This causes\n * captions to be out of sync with the audio — especially visible in\n * landscape-captioned shorts where there's no intermediate re-encode to\n * normalize PTS (the portrait path gets an extra re-encode via aspect-ratio\n * conversion which masks the issue).\n *\n * Re-encoding with `trim` + `setpts=PTS-STARTPTS` guarantees:\n * - The clip starts at **exactly** `bufferedStart` (not the nearest keyframe)\n * - Output PTS starts at 0 with no offset\n * - Caption timestamps align perfectly with both audio and video\n *\n * @param buffer Seconds of padding added before start and after end (default 1.0)\n */\nexport async function extractClip(\n videoPath: string,\n start: number,\n end: number,\n outputPath: string,\n buffer: number = 1.0,\n): Promise<string> {\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n const bufferedStart = Math.max(0, start - buffer);\n const bufferedEnd = end + buffer;\n const duration = bufferedEnd - bufferedStart;\n logger.info(`Extracting clip [${start}s–${end}s] (buffered: ${bufferedStart.toFixed(2)}s–${bufferedEnd.toFixed(2)}s) → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n createFFmpeg(videoPath)\n .setStartTime(bufferedStart)\n .setDuration(duration)\n .outputOptions(['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '23', '-threads', '4', '-c:a', 'aac', '-b:a', '128k'])\n .output(outputPath)\n .on('end', () => {\n logger.info(`Clip extraction complete: ${outputPath}`);\n resolve(outputPath);\n })\n .on('error', (err) => {\n logger.error(`Clip extraction failed: ${err.message}`);\n reject(new Error(`Clip extraction failed: ${err.message}`));\n })\n .run();\n });\n}\n\n/**\n * Extract multiple non-contiguous segments and concatenate them into one clip.\n * Each segment is padded by `buffer` seconds on both sides for smoother cuts.\n * Re-encodes and uses concat demuxer for clean joins.\n * @param buffer Seconds of padding added before start and after end of each segment (default 1.0)\n */\nexport async function extractCompositeClip(\n videoPath: string,\n segments: ShortSegment[],\n outputPath: string,\n buffer: number = 1.0,\n): Promise<string> {\n if (!segments || segments.length === 0) {\n throw new Error('At least one segment is required');\n }\n\n if (segments.length === 1) {\n return extractClip(videoPath, segments[0].start, segments[0].end, outputPath, buffer);\n }\n\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n const tempDirObj = tmp.dirSync({ unsafeCleanup: true, prefix: 'vidpipe-' });\n const tempDir = tempDirObj.name;\n\n const tempFiles: string[] = [];\n let concatListFile: tmp.FileResult | null = null;\n\n try {\n // Extract each segment to a temp file (re-encode for reliable concat)\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const tempPath = join(tempDir, `segment-${i}.mp4`);\n tempFiles.push(tempPath);\n\n const bufferedStart = Math.max(0, seg.start - buffer);\n const bufferedEnd = seg.end + buffer;\n logger.info(`Extracting segment ${i + 1}/${segments.length} [${seg.start}s–${seg.end}s] (buffered: ${bufferedStart.toFixed(2)}s–${bufferedEnd.toFixed(2)}s)`);\n\n await new Promise<void>((resolve, reject) => {\n createFFmpeg(videoPath)\n .setStartTime(bufferedStart)\n .setDuration(bufferedEnd - bufferedStart)\n .outputOptions(['-threads', '4', '-preset', 'ultrafast'])\n .output(tempPath)\n .on('end', () => resolve())\n .on('error', (err) => reject(new Error(`Segment ${i} extraction failed: ${err.message}`)))\n .run();\n });\n }\n\n // Build concat list file\n concatListFile = tmp.fileSync({ dir: tempDir, postfix: '.txt', prefix: 'concat-' });\n const concatListPath = concatListFile.name;\n const listContent = tempFiles.map((f) => `file '${f.replace(/'/g, \"'\\\\''\")}'`).join('\\n');\n await writeTextFile(concatListPath, listContent);\n // Close file descriptor to avoid leaks on Windows\n closeFileDescriptor(concatListFile.fd);\n\n // Concatenate segments (re-encode for clean joins across buffered segments)\n logger.info(`Concatenating ${segments.length} segments → ${outputPath}`);\n await new Promise<void>((resolve, reject) => {\n createFFmpeg()\n .input(concatListPath)\n .inputOptions(['-f', 'concat', '-safe', '0'])\n .outputOptions(['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '23', '-threads', '4', '-c:a', 'aac'])\n .output(outputPath)\n .on('end', () => resolve())\n .on('error', (err) => reject(new Error(`Concat failed: ${err.message}`)))\n .run();\n });\n\n logger.info(`Composite clip complete: ${outputPath}`);\n return outputPath;\n } finally {\n // Clean up temp files and remove callbacks\n if (concatListFile) {\n try {\n concatListFile.removeCallback();\n } catch {}\n }\n try {\n tempDirObj.removeCallback();\n } catch {}\n }\n}\n\n/**\n * Extract multiple non-contiguous segments and concatenate them with crossfade\n * transitions using FFmpeg xfade/acrossfade filters.\n * Falls back to extractCompositeClip if only one segment is provided.\n *\n * @param transitionDuration Crossfade duration in seconds (default 0.5)\n * @param buffer Seconds of padding added before/after each segment (default 1.0)\n */\nexport async function extractCompositeClipWithTransitions(\n videoPath: string,\n segments: ShortSegment[],\n outputPath: string,\n transitionDuration: number = 0.5,\n buffer: number = 1.0,\n): Promise<string> {\n if (!segments || segments.length === 0) {\n throw new Error('At least one segment is required');\n }\n\n // Single segment — no transitions needed\n if (segments.length === 1) {\n return extractClip(videoPath, segments[0].start, segments[0].end, outputPath, buffer);\n }\n\n // Two segments — no transitions needed, use regular composite\n if (segments.length === 2 && transitionDuration <= 0) {\n return extractCompositeClip(videoPath, segments, outputPath, buffer);\n }\n\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n // Detect source fps so we can force CFR after trim (FFmpeg 7.x xfade requires it)\n const fps = await getVideoFps(videoPath);\n\n // Build filter_complex for xfade transitions between segments\n const filterParts: string[] = [];\n const segDurations: number[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const bufferedStart = Math.max(0, seg.start - buffer);\n const bufferedEnd = seg.end + buffer;\n const duration = bufferedEnd - bufferedStart;\n segDurations.push(duration);\n\n filterParts.push(\n `[0:v]trim=start=${bufferedStart.toFixed(3)}:end=${bufferedEnd.toFixed(3)},setpts=PTS-STARTPTS,fps=${fps}[v${i}]`,\n );\n filterParts.push(\n `[0:a]atrim=start=${bufferedStart.toFixed(3)}:end=${bufferedEnd.toFixed(3)},asetpts=PTS-STARTPTS[a${i}]`,\n );\n }\n\n // Chain xfade transitions: [v0][v1]xfade → [xv0]; [xv0][v2]xfade → [xv1]; ...\n let prevVideo = 'v0';\n let prevAudio = 'a0';\n let cumulativeDuration = segDurations[0];\n\n for (let i = 1; i < segments.length; i++) {\n const offset = Math.max(0, cumulativeDuration - transitionDuration);\n const outVideo = i === segments.length - 1 ? 'vout' : `xv${i - 1}`;\n const outAudio = i === segments.length - 1 ? 'aout' : `xa${i - 1}`;\n\n filterParts.push(\n `[${prevVideo}][v${i}]xfade=transition=fade:duration=${transitionDuration.toFixed(3)}:offset=${offset.toFixed(3)}[${outVideo}]`,\n );\n filterParts.push(\n `[${prevAudio}][a${i}]acrossfade=d=${transitionDuration.toFixed(3)}[${outAudio}]`,\n );\n\n prevVideo = outVideo;\n prevAudio = outAudio;\n // After xfade, the combined duration shrinks by transitionDuration\n cumulativeDuration = cumulativeDuration - transitionDuration + segDurations[i];\n }\n\n const filterComplex = filterParts.join(';\\n');\n\n const args = [\n '-y',\n '-i', videoPath,\n '-filter_complex', filterComplex,\n '-map', '[vout]',\n '-map', '[aout]',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n '-c:a', 'aac',\n '-b:a', '128k',\n outputPath,\n ];\n\n logger.info(`[ClipExtraction] Compositing ${segments.length} segments with xfade transitions → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`[ClipExtraction] xfade composite failed: ${stderr}`);\n reject(new Error(`xfade composite clip failed: ${error.message}`));\n return;\n }\n logger.info(`[ClipExtraction] xfade composite complete: ${outputPath}`);\n resolve(outputPath);\n });\n });\n}\n","import { execFileRaw } from '../../core/process.js'\nimport { ensureDirectory, copyFile, listDirectory, removeFile, removeDirectory, makeTempDir, renameFile } from '../../core/fileSystem.js'\nimport { dirname, join, fontsDir } from '../../core/paths.js'\nimport { getFFmpegPath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nconst ffmpegPath = getFFmpegPath()\nconst FONTS_DIR = fontsDir()\n\n/**\n * Burn ASS subtitles into video (hard-coded subtitles).\n * Uses direct execFile instead of fluent-ffmpeg to avoid Windows path escaping issues.\n * Copies the ASS file to a temp dir and uses a relative path to dodge the Windows\n * drive-letter colon being parsed as an FFmpeg filter option separator.\n */\nexport async function burnCaptions(\n videoPath: string,\n assPath: string,\n outputPath: string,\n): Promise<string> {\n const outputDir = dirname(outputPath)\n await ensureDirectory(outputDir)\n\n logger.info(`Burning captions into video → ${outputPath}`)\n\n // Create a dedicated temp dir so we can use colon-free relative paths\n const workDir = await makeTempDir('caption-')\n const tempAss = join(workDir, 'captions.ass')\n const tempOutput = join(workDir, 'output.mp4')\n\n await copyFile(assPath, tempAss)\n\n // Copy bundled fonts so libass can find them via fontsdir=.\n let fontFiles: string[]\n try {\n fontFiles = await listDirectory(FONTS_DIR)\n } catch (err: any) {\n if (err?.code === 'ENOENT') {\n throw new Error(`Fonts directory not found at ${FONTS_DIR}. Ensure assets/fonts/ exists in the project root.`)\n }\n throw err\n }\n for (const f of fontFiles) {\n if (f.endsWith('.ttf') || f.endsWith('.otf')) {\n await copyFile(join(FONTS_DIR, f), join(workDir, f))\n }\n }\n\n // Use just the filename — no drive letter, no colons\n const args = [\n '-y',\n '-i', videoPath,\n '-vf', 'ass=captions.ass:fontsdir=.',\n '-c:a', 'copy',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n tempOutput,\n ]\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { cwd: workDir, maxBuffer: 10 * 1024 * 1024 }, async (error, _stdout, stderr) => {\n const cleanup = async () => {\n const files = await listDirectory(workDir).catch(() => [] as string[])\n for (const f of files) {\n await removeFile(join(workDir, f)).catch(() => {})\n }\n await removeDirectory(workDir).catch(() => {})\n }\n\n if (error) {\n await cleanup()\n logger.error(`Caption burning failed: ${stderr || error.message}`)\n reject(new Error(`Caption burning failed: ${stderr || error.message}`))\n return\n }\n\n try {\n await renameFile(tempOutput, outputPath)\n } catch {\n await copyFile(tempOutput, outputPath)\n }\n await cleanup()\n logger.info(`Captions burned: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n","export { default as sharp } from 'sharp'\nexport type { Sharp, Metadata as SharpMetadata } from 'sharp'\nexport * as ort from 'onnxruntime-node'\n","import { execFileRaw } from '../../core/process.js'\nimport { fileExistsSync, listDirectory, removeFile, removeDirectory, makeTempDir } from '../../core/fileSystem.js'\nimport { join, modelsDir } from '../../core/paths.js'\nimport { sharp, ort } from '../../core/media.js'\nimport { getFFmpegPath, getFFprobePath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nconst ffmpegPath = getFFmpegPath()\nconst ffprobePath = getFFprobePath()\n\nconst MODEL_PATH = join(modelsDir(), 'ultraface-320.onnx')\n\n/** Cached ONNX session — loaded once, reused across calls. */\nlet cachedSession: ort.InferenceSession | null = null\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Bounding box and metadata for a detected webcam overlay in a screen recording.\n *\n * @property x - Left edge in pixels (original video resolution)\n * @property y - Top edge in pixels (original video resolution)\n * @property width - Width of the webcam region in pixels\n * @property height - Height of the webcam region in pixels\n * @property position - Which corner of the frame the webcam occupies\n * @property confidence - Detection confidence 0–1 (combines skin-tone consistency\n * across frames with per-frame score strength)\n */\nexport interface WebcamRegion {\n x: number\n y: number\n width: number\n height: number\n position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n confidence: number\n}\n\n// ── Constants ────────────────────────────────────────────────────────────────\n\n/** Number of frames sampled evenly across the video for analysis. */\nconst SAMPLE_FRAMES = 5\n/** UltraFace model input dimensions. */\nconst MODEL_WIDTH = 320\nconst MODEL_HEIGHT = 240\n/** Minimum face detection confidence from the ONNX model. */\nconst MIN_FACE_CONFIDENCE = 0.5\n/** Minimum confidence across frames to accept a webcam detection. */\nconst MIN_DETECTION_CONFIDENCE = 0.3\n\n// ── Refinement constants ─────────────────────────────────────────────────────\n\n/**\n * Minimum inter-column/row mean difference to accept as a valid overlay edge.\n * The webcam overlay border creates a sharp intensity step between the\n * overlay and the screen content behind it. Values below this threshold\n * are treated as noise or soft gradients.\n */\nconst REFINE_MIN_EDGE_DIFF = 3.0\n/** Webcam must be at least 5% of the frame in each dimension. */\nconst REFINE_MIN_SIZE_FRAC = 0.05\n/** Webcam must be at most 55% of the frame in each dimension. */\nconst REFINE_MAX_SIZE_FRAC = 0.55\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function getVideoDuration(videoPath: string): Promise<number> {\n return new Promise((resolve, reject) => {\n execFileRaw(\n ffprobePath,\n ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', videoPath],\n {},\n (error, stdout) => {\n if (error) {\n reject(new Error(`ffprobe failed: ${error.message}`))\n return\n }\n resolve(parseFloat(stdout.trim()))\n },\n )\n })\n}\n\nexport async function getVideoResolution(videoPath: string): Promise<{ width: number; height: number }> {\n return new Promise((resolve, reject) => {\n execFileRaw(\n ffprobePath,\n [\n '-v', 'error',\n '-select_streams', 'v:0',\n '-show_entries', 'stream=width,height',\n '-of', 'csv=p=0',\n videoPath,\n ],\n {},\n (error, stdout) => {\n if (error) {\n reject(new Error(`ffprobe failed: ${error.message}`))\n return\n }\n const [w, h] = stdout.trim().split(',').map(Number)\n resolve({ width: w, height: h })\n },\n )\n })\n}\n\nasync function extractSampleFrames(videoPath: string, tempDir: string): Promise<string[]> {\n const duration = await getVideoDuration(videoPath)\n const interval = Math.max(1, Math.floor(duration / (SAMPLE_FRAMES + 1)))\n\n const timestamps: number[] = []\n for (let i = 1; i <= SAMPLE_FRAMES; i++) {\n timestamps.push(i * interval)\n }\n\n const framePaths: string[] = []\n for (let i = 0; i < timestamps.length; i++) {\n const framePath = join(tempDir, `frame_${i}.png`)\n framePaths.push(framePath)\n\n await new Promise<void>((resolve, reject) => {\n execFileRaw(\n ffmpegPath,\n [\n '-y',\n '-ss', timestamps[i].toFixed(2),\n '-i', videoPath,\n '-vf', `scale=${MODEL_WIDTH}:${MODEL_HEIGHT}`,\n '-frames:v', '1',\n '-q:v', '2',\n framePath,\n ],\n { maxBuffer: 10 * 1024 * 1024 },\n (error) => {\n if (error) {\n reject(new Error(`Frame extraction failed at ${timestamps[i]}s: ${error.message}`))\n return\n }\n resolve()\n },\n )\n })\n }\n\n return framePaths\n}\n\n// ── ONNX Face Detection ─────────────────────────────────────────────────────\n\ninterface FaceBox {\n x1: number\n y1: number\n x2: number\n y2: number\n confidence: number\n}\n\nasync function getSession(): Promise<ort.InferenceSession> {\n if (cachedSession) return cachedSession\n if (!fileExistsSync(MODEL_PATH)) {\n throw new Error(`Face detection model not found at ${MODEL_PATH}. Run 'vidpipe doctor' to check dependencies.`)\n }\n cachedSession = await ort.InferenceSession.create(MODEL_PATH, {\n executionProviders: ['cpu'],\n graphOptimizationLevel: 'all',\n })\n return cachedSession\n}\n\n/**\n * Run UltraFace ONNX model on a frame image. Returns face bounding boxes\n * in normalized coordinates (0-1).\n */\nasync function detectFacesInFrame(framePath: string): Promise<FaceBox[]> {\n const session = await getSession()\n\n // Load and preprocess: resize to 320×240, convert to float32 NCHW, normalize to [0,1]\n const { data, info } = await sharp(framePath)\n .resize(MODEL_WIDTH, MODEL_HEIGHT, { fit: 'fill' })\n .removeAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n\n const pixels = info.width * info.height\n const floatData = new Float32Array(3 * pixels)\n\n // HWC RGB → NCHW (channel-first), normalize 0-1 with ImageNet mean/std\n const mean = [127, 127, 127]\n const std = 128\n for (let i = 0; i < pixels; i++) {\n floatData[i] = (data[i * 3] - mean[0]) / std // R\n floatData[pixels + i] = (data[i * 3 + 1] - mean[1]) / std // G\n floatData[2 * pixels + i] = (data[i * 3 + 2] - mean[2]) / std // B\n }\n\n const inputTensor = new ort.Tensor('float32', floatData, [1, 3, MODEL_HEIGHT, MODEL_WIDTH])\n const results = await session.run({ input: inputTensor })\n\n const scores = results['scores'].data as Float32Array // [1, N, 2]\n const boxes = results['boxes'].data as Float32Array // [1, N, 4]\n const numDetections = scores.length / 2\n\n const faces: FaceBox[] = []\n for (let i = 0; i < numDetections; i++) {\n const faceScore = scores[i * 2 + 1] // index 1 = face class\n if (faceScore > MIN_FACE_CONFIDENCE) {\n faces.push({\n x1: boxes[i * 4],\n y1: boxes[i * 4 + 1],\n x2: boxes[i * 4 + 2],\n y2: boxes[i * 4 + 3],\n confidence: faceScore,\n })\n }\n }\n\n return faces\n}\n\n/**\n * Determine which corner a face box belongs to. Returns null if the face\n * is in the center of the frame (not a webcam overlay).\n */\nfunction classifyCorner(\n box: FaceBox,\n): WebcamRegion['position'] | null {\n const cx = (box.x1 + box.x2) / 2\n const cy = (box.y1 + box.y2) / 2\n\n // Face center must be in the outer 40% of the frame to be a corner webcam\n const isLeft = cx < 0.4\n const isRight = cx > 0.6\n const isTop = cy < 0.4\n const isBottom = cy > 0.6\n\n if (isTop && isLeft) return 'top-left'\n if (isTop && isRight) return 'top-right'\n if (isBottom && isLeft) return 'bottom-left'\n if (isBottom && isRight) return 'bottom-right'\n return null // center face — likely full-frame webcam, not an overlay\n}\n\n// ── Refinement helpers ───────────────────────────────────────────────────────\n\n/**\n * Compute per-column mean grayscale intensity over a horizontal band of rows.\n *\n * Used to find the **vertical edge** of the webcam overlay. Each column gets\n * a single mean brightness value averaged over `yFrom..yTo` rows. The\n * resulting 1-D signal has a sharp step at the overlay boundary, which\n * {@link findPeakDiff} locates.\n *\n * @param data - Raw pixel buffer (RGB or RGBA interleaved)\n * @param width - Image width in pixels\n * @param channels - Bytes per pixel (3 for RGB, 4 for RGBA)\n * @param yFrom - First row (inclusive)\n * @param yTo - Last row (exclusive)\n * @returns Float64Array of length `width` with per-column mean grayscale\n */\nfunction columnMeansForRows(\n data: Buffer, width: number, channels: number,\n yFrom: number, yTo: number,\n): Float64Array {\n const means = new Float64Array(width)\n const count = yTo - yFrom\n if (count <= 0) return means\n for (let x = 0; x < width; x++) {\n let sum = 0\n for (let y = yFrom; y < yTo; y++) {\n const idx = (y * width + x) * channels\n sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3\n }\n means[x] = sum / count\n }\n return means\n}\n\n/**\n * Compute per-row mean grayscale intensity over a vertical band of columns.\n *\n * Used to find the **horizontal edge** of the webcam overlay. Each row gets\n * a single mean brightness value averaged over `xFrom..xTo` columns. Works\n * the same way as {@link columnMeansForRows} but rotated 90°.\n *\n * @param data - Raw pixel buffer\n * @param width - Image width in pixels\n * @param channels - Bytes per pixel\n * @param height - Image height in pixels\n * @param xFrom - First column (inclusive)\n * @param xTo - Last column (exclusive)\n * @returns Float64Array of length `height` with per-row mean grayscale\n */\nfunction rowMeansForCols(\n data: Buffer, width: number, channels: number, height: number,\n xFrom: number, xTo: number,\n): Float64Array {\n const means = new Float64Array(height)\n const count = xTo - xFrom\n if (count <= 0) return means\n for (let y = 0; y < height; y++) {\n let sum = 0\n for (let x = xFrom; x < xTo; x++) {\n const idx = (y * width + x) * channels\n sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3\n }\n means[y] = sum / count\n }\n return means\n}\n\n/** Element-wise average of Float64Arrays. */\nfunction averageFloat64Arrays(arrays: Float64Array[]): Float64Array {\n if (arrays.length === 0) return new Float64Array(0)\n const len = arrays[0].length\n const result = new Float64Array(len)\n for (const arr of arrays) {\n for (let i = 0; i < len; i++) result[i] += arr[i]\n }\n for (let i = 0; i < len; i++) result[i] /= arrays.length\n return result\n}\n\n/**\n * Find the position with the largest intensity step between adjacent elements.\n *\n * \"Peak difference\" = the index where `|means[i+1] - means[i]|` is maximized\n * within the search range. This corresponds to the webcam overlay's edge,\n * because the overlay border creates a hard brightness transition that\n * persists across all frames, while content-based edges average out.\n *\n * @param means - 1-D array of averaged intensities (from column or row means)\n * @param searchFrom - Start of search range (inclusive)\n * @param searchTo - End of search range (inclusive)\n * @param minDiff - Minimum step magnitude to accept (rejects noise)\n * @returns `{index, magnitude}` — index of the edge, or -1 if no edge exceeds minDiff\n */\nexport function findPeakDiff(\n means: Float64Array, searchFrom: number, searchTo: number, minDiff: number,\n): { index: number; magnitude: number } {\n const lo = Math.max(0, Math.min(searchFrom, searchTo))\n const hi = Math.min(means.length - 1, Math.max(searchFrom, searchTo))\n let maxDiff = 0\n let maxIdx = -1\n for (let i = lo; i < hi; i++) {\n const d = Math.abs(means[i + 1] - means[i])\n if (d > maxDiff) { maxDiff = d; maxIdx = i }\n }\n return maxDiff >= minDiff ? { index: maxIdx, magnitude: maxDiff } : { index: -1, magnitude: maxDiff }\n}\n\n/**\n * Refine the webcam bounding box by detecting the overlay's spatial edges.\n *\n * ### Why refinement is needed\n * The coarse phase ({@link detectWebcamRegion}'s corner analysis) only identifies\n * which corner contains a webcam — it uses a fixed 25% region and doesn't know\n * the overlay's exact boundaries. Refinement finds pixel-accurate edges.\n *\n * ### Edge detection algorithm\n * 1. For each sample frame, compute **per-column** and **per-row** mean grayscale\n * intensities (restricted to the webcam's half of the frame for stronger signal).\n * 2. **Average across all frames** — the overlay border is spatially fixed and\n * produces a consistent intensity step, while changing video content (slides,\n * code, etc.) averages out to a smooth gradient. This is the key insight that\n * makes the approach work without traditional edge detection filters.\n * 3. Use {@link findPeakDiff} to locate the maximum inter-adjacent intensity\n * difference in the averaged signal — this is the overlay's vertical and\n * horizontal edge.\n * 4. Sanity-check: the resulting rectangle must be 5–55% of the frame in each\n * dimension (webcams are never tiny or most of the screen).\n *\n * @param framePaths - Paths to sample frames at analysis resolution (320×180)\n * @param position - Which corner contains the webcam (from coarse phase)\n * @returns Refined bounding box in analysis-resolution coordinates, or null\n * if no strong edges are found or the result is implausibly sized\n */\nexport async function refineBoundingBox(\n framePaths: string[],\n position: WebcamRegion['position'],\n): Promise<{ x: number; y: number; width: number; height: number } | null> {\n if (framePaths.length === 0) return null\n\n const isRight = position.includes('right')\n const isBottom = position.includes('bottom')\n let fw = 0, fh = 0\n\n const colMeansAll: Float64Array[] = []\n const rowMeansAll: Float64Array[] = []\n\n for (const fp of framePaths) {\n const { data, info } = await sharp(fp).raw().toBuffer({ resolveWithObject: true })\n fw = info.width; fh = info.height\n\n // Column means: restrict to rows near the webcam for stronger signal\n const yFrom = isBottom ? Math.floor(fh * 0.35) : 0\n const yTo = isBottom ? fh : Math.ceil(fh * 0.65)\n colMeansAll.push(columnMeansForRows(data, fw, info.channels, yFrom, yTo))\n\n // Row means: restrict to columns near the webcam\n const xFrom = isRight ? Math.floor(fw * 0.35) : 0\n const xTo = isRight ? fw : Math.ceil(fw * 0.65)\n rowMeansAll.push(rowMeansForCols(data, fw, info.channels, fh, xFrom, xTo))\n }\n\n const avgCols = averageFloat64Arrays(colMeansAll)\n const avgRows = averageFloat64Arrays(rowMeansAll)\n\n // Search for the inner edge in the relevant portion of the frame\n const xFrom = isRight ? Math.floor(fw * 0.35) : Math.floor(fw * 0.05)\n const xTo = isRight ? Math.floor(fw * 0.95) : Math.floor(fw * 0.65)\n const xEdge = findPeakDiff(avgCols, xFrom, xTo, REFINE_MIN_EDGE_DIFF)\n\n const yFrom = isBottom ? Math.floor(fh * 0.35) : Math.floor(fh * 0.05)\n const yTo = isBottom ? Math.floor(fh * 0.95) : Math.floor(fh * 0.65)\n const yEdge = findPeakDiff(avgRows, yFrom, yTo, REFINE_MIN_EDGE_DIFF)\n\n if (xEdge.index < 0 || yEdge.index < 0) {\n logger.info(\n `[FaceDetection] Edge refinement: no strong edges ` +\n `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`,\n )\n return null\n }\n\n // Build the refined rectangle\n let x: number, y: number, w: number, h: number\n if (isRight) { x = xEdge.index + 1; w = fw - x }\n else { x = 0; w = xEdge.index }\n if (isBottom) { y = yEdge.index + 1; h = fh - y }\n else { y = 0; h = yEdge.index }\n\n // Sanity: webcam should be 5-55% of frame in each dimension\n if (w < fw * REFINE_MIN_SIZE_FRAC || h < fh * REFINE_MIN_SIZE_FRAC ||\n w > fw * REFINE_MAX_SIZE_FRAC || h > fh * REFINE_MAX_SIZE_FRAC) {\n logger.info(\n `[FaceDetection] Refined bounds implausible ` +\n `(${w}x${h} in ${fw}x${fh}), using coarse bounds`,\n )\n return null\n }\n\n logger.info(\n `[FaceDetection] Refined webcam: (${x},${y}) ${w}x${h} at analysis scale ` +\n `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`,\n )\n\n return { x, y, width: w, height: h }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Calculate confidence that a corner contains a webcam overlay based on\n * per-frame face detections. Higher consistency across frames = more confident.\n */\nexport function calculateCornerConfidence(scores: number[]): number {\n if (scores.length === 0) return 0\n const nonZeroCount = scores.filter(s => s > 0).length\n const consistency = nonZeroCount / scores.length\n const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length\n return consistency * avgScore\n}\n\n/**\n * Detect a webcam overlay region in a screen recording using the UltraFace\n * ONNX model for face detection.\n *\n * ### Approach\n * 1. Sample 5 frames evenly across the video\n * 2. Run UltraFace face detection on each frame\n * 3. For each detected face, classify which corner it's in\n * 4. The corner with consistent face detections across frames is the webcam\n * 5. Refine the bounding box using edge detection for exact overlay boundaries\n *\n * @param videoPath - Path to the source video file\n * @returns The detected webcam region in original video resolution, or null\n */\nexport async function detectWebcamRegion(videoPath: string): Promise<WebcamRegion | null> {\n const tempDir = await makeTempDir('face-detect-')\n\n try {\n const resolution = await getVideoResolution(videoPath)\n const framePaths = await extractSampleFrames(videoPath, tempDir)\n\n // Track face detections per corner across all frames\n const cornerScores = new Map<WebcamRegion['position'], number[]>()\n const cornerBoxes = new Map<WebcamRegion['position'], FaceBox[]>()\n for (const pos of ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const) {\n cornerScores.set(pos, [])\n cornerBoxes.set(pos, [])\n }\n\n for (const framePath of framePaths) {\n const faces = await detectFacesInFrame(framePath)\n\n // Track which corners got a face this frame\n const foundCorners = new Set<WebcamRegion['position']>()\n\n for (const face of faces) {\n const corner = classifyCorner(face)\n if (corner) {\n foundCorners.add(corner)\n cornerScores.get(corner)!.push(face.confidence)\n cornerBoxes.get(corner)!.push(face)\n }\n }\n\n // Corners without a face this frame get a 0\n for (const pos of ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const) {\n if (!foundCorners.has(pos)) {\n cornerScores.get(pos)!.push(0)\n }\n }\n }\n\n // Find best corner\n let bestPosition: WebcamRegion['position'] | null = null\n let bestConfidence = 0\n\n for (const [pos, scores] of cornerScores) {\n const confidence = calculateCornerConfidence(scores)\n logger.debug(`[FaceDetection] Corner ${pos}: confidence=${confidence.toFixed(3)}, scores=[${scores.map(s => s.toFixed(2)).join(',')}]`)\n if (confidence > bestConfidence) {\n bestConfidence = confidence\n bestPosition = pos\n }\n }\n\n if (!bestPosition || bestConfidence < MIN_DETECTION_CONFIDENCE) {\n logger.info(`[FaceDetection] No webcam region detected (best: ${bestPosition} at ${bestConfidence.toFixed(3)})`)\n return null\n }\n\n // Compute average face bounding box from model detections\n const boxes = cornerBoxes.get(bestPosition)!\n const avgBox: FaceBox = {\n x1: boxes.reduce((s, b) => s + b.x1, 0) / boxes.length,\n y1: boxes.reduce((s, b) => s + b.y1, 0) / boxes.length,\n x2: boxes.reduce((s, b) => s + b.x2, 0) / boxes.length,\n y2: boxes.reduce((s, b) => s + b.y2, 0) / boxes.length,\n confidence: bestConfidence,\n }\n\n // Try edge refinement for pixel-accurate webcam overlay boundaries\n const refined = await refineBoundingBox(framePaths, bestPosition)\n const scaleX = resolution.width / MODEL_WIDTH\n const scaleY = resolution.height / MODEL_HEIGHT\n\n let origX: number, origY: number, origW: number, origH: number\n\n if (refined) {\n origX = Math.round(refined.x * scaleX)\n origY = Math.round(refined.y * scaleY)\n origW = Math.round(refined.width * scaleX)\n origH = Math.round(refined.height * scaleY)\n } else {\n // Use expanded face bounding box as webcam region estimate\n // Webcam overlay is typically larger than the face (includes some background)\n const expandFactor = 1.4\n const faceCx = (avgBox.x1 + avgBox.x2) / 2\n const faceCy = (avgBox.y1 + avgBox.y2) / 2\n const faceW = (avgBox.x2 - avgBox.x1) * expandFactor\n const faceH = (avgBox.y2 - avgBox.y1) * expandFactor\n\n origX = Math.max(0, Math.round((faceCx - faceW / 2) * resolution.width))\n origY = Math.max(0, Math.round((faceCy - faceH / 2) * resolution.height))\n origW = Math.min(resolution.width - origX, Math.round(faceW * resolution.width))\n origH = Math.min(resolution.height - origY, Math.round(faceH * resolution.height))\n }\n\n const region: WebcamRegion = {\n x: origX,\n y: origY,\n width: origW,\n height: origH,\n position: bestPosition,\n confidence: Math.round(bestConfidence * 100) / 100,\n }\n\n logger.info(\n `[FaceDetection] Webcam detected at ${region.position} ` +\n `(${region.x},${region.y} ${region.width}x${region.height}) ` +\n `confidence=${region.confidence} refined=${!!refined}`,\n )\n\n return region\n } finally {\n const files = await listDirectory(tempDir).catch(() => [] as string[])\n for (const f of files) {\n await removeFile(join(tempDir, f)).catch(() => {})\n }\n await removeDirectory(tempDir, { recursive: true, force: true }).catch(() => {})\n }\n}\n","import { execFileRaw } from '../../core/process.js'\nimport { ensureDirectory, copyFile } from '../../core/fileSystem.js'\nimport { dirname, join } from '../../core/paths.js'\nimport { getFFmpegPath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\nimport { detectWebcamRegion, getVideoResolution } from './faceDetection'\n\nconst ffmpegPath = getFFmpegPath()\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Supported output aspect ratios.\n * - `16:9` — standard landscape (YouTube, desktop)\n * - `9:16` — portrait / vertical (TikTok, Reels, Shorts)\n * - `1:1` — square (LinkedIn, Twitter)\n * - `4:5` — tall feed (Instagram feed)\n */\nexport type AspectRatio = '16:9' | '9:16' | '1:1' | '4:5'\n\n/** Social-media platforms we generate video variants for. */\nexport type Platform =\n | 'tiktok'\n | 'youtube-shorts'\n | 'instagram-reels'\n | 'instagram-feed'\n | 'linkedin'\n | 'youtube'\n | 'twitter'\n\n/**\n * Maps each platform to its preferred aspect ratio.\n * Multiple platforms may share a ratio (e.g. TikTok + Reels both use 9:16),\n * which lets {@link generatePlatformVariants} deduplicate encodes.\n */\nexport const PLATFORM_RATIOS: Record<Platform, AspectRatio> = {\n 'tiktok': '9:16',\n 'youtube-shorts': '9:16',\n 'instagram-reels': '9:16',\n 'instagram-feed': '4:5',\n 'linkedin': '1:1',\n 'youtube': '16:9',\n 'twitter': '1:1',\n}\n\n/**\n * Canonical pixel dimensions for each aspect ratio.\n * Width is always 1080 px for non-landscape ratios (the standard vertical\n * video width); landscape stays at 1920×1080 for full HD.\n */\nexport const DIMENSIONS: Record<AspectRatio, { width: number; height: number }> = {\n '16:9': { width: 1920, height: 1080 },\n '9:16': { width: 1080, height: 1920 },\n '1:1': { width: 1080, height: 1080 },\n '4:5': { width: 1080, height: 1350 },\n}\n\nexport interface ConvertOptions {\n /** Fallback to letterbox/pillarbox instead of cropping (default: false) */\n letterbox?: boolean\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Build the FFmpeg `-vf` filter string for a simple center-crop conversion.\n *\n * This is the **fallback** used when smart layout (webcam detection + split-screen)\n * is unavailable. It center-crops the source frame to the target aspect ratio,\n * discarding content on the sides (or top/bottom).\n *\n * **Letterbox mode**: instead of cropping, scales the video to fit inside the\n * target dimensions and pads the remaining space with black bars. Useful when\n * you don't want to lose any content (e.g. screen recordings with important\n * edges).\n *\n * **Crop formulas** assume a 16:9 landscape source. `ih` = input height,\n * `iw` = input width. We compute the crop width from the height to maintain\n * the target ratio, then center the crop horizontally.\n *\n * @param targetRatio - The desired output aspect ratio\n * @param letterbox - If true, pad with black bars instead of cropping\n * @returns An FFmpeg `-vf` filter string\n */\nfunction buildCropFilter(targetRatio: AspectRatio, letterbox: boolean): string {\n if (letterbox) {\n const { width, height } = DIMENSIONS[targetRatio]\n // Scale to fit within target dimensions, then pad with black bars\n return `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`\n }\n\n switch (targetRatio) {\n case '9:16':\n // Center-crop landscape to portrait: crop width = ih*9/16, keep full height\n return 'crop=ih*9/16:ih:(iw-ih*9/16)/2:0,scale=1080:1920'\n case '1:1':\n // Center-crop to square: use height as the dimension (smaller axis for 16:9)\n return 'crop=ih:ih:(iw-ih)/2:0,scale=1080:1080'\n case '4:5':\n // Center-crop landscape to 4:5: crop width = ih*4/5, keep full height\n return 'crop=ih*4/5:ih:(iw-ih*4/5)/2:0,scale=1080:1350'\n case '16:9':\n // Same ratio — just ensure standard dimensions\n return 'scale=1920:1080'\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Convert a video's aspect ratio using FFmpeg center-crop.\n *\n * - 16:9 → 9:16: crops the center column to portrait\n * - 16:9 → 1:1: crops to a center square\n * - Same ratio: stream-copies without re-encoding\n *\n * @returns The output path on success\n */\nexport async function convertAspectRatio(\n inputPath: string,\n outputPath: string,\n targetRatio: AspectRatio,\n options: ConvertOptions = {},\n): Promise<string> {\n const outputDir = dirname(outputPath)\n await ensureDirectory(outputDir)\n\n const sourceRatio: AspectRatio = '16:9' // our videos are always landscape\n\n // Same ratio — stream copy\n if (sourceRatio === targetRatio && !options.letterbox) {\n logger.info(`Aspect ratio already ${targetRatio}, copying → ${outputPath}`)\n await copyFile(inputPath, outputPath)\n return outputPath\n }\n\n const vf = buildCropFilter(targetRatio, options.letterbox ?? false)\n logger.info(`Converting aspect ratio to ${targetRatio} (filter: ${vf}) → ${outputPath}`)\n\n const args = [\n '-y',\n '-i', inputPath,\n '-vf', vf,\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-c:a', 'copy',\n '-threads', '4',\n outputPath,\n ]\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`Aspect ratio conversion failed: ${stderr || error.message}`)\n reject(new Error(`Aspect ratio conversion failed: ${stderr || error.message}`))\n return\n }\n logger.info(`Aspect ratio conversion complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n\n// ── Smart Layout ─────────────────────────────────────────────────────────────\n\n/**\n * Configuration for the smart split-screen layout.\n *\n * The split-screen stacks two regions vertically: the **screen content** on top\n * and the **webcam face** on the bottom. Each field controls the geometry of\n * the final composite:\n *\n * @property label - Human-readable name for logging (e.g. \"SmartPortrait\")\n * @property targetW - Output width in pixels. All smart layouts use 1080 px\n * (vertical video standard) so both the screen and cam panels share the\n * same width.\n * @property screenH - Height of the top panel (screen recording). Combined\n * with `camH`, this determines the total output height and the visual\n * ratio between screen content and webcam. Roughly ~65% of total height.\n * @property camH - Height of the bottom panel (webcam). Roughly ~35% of\n * total height. The webcam is AR-matched and center-cropped to fill this\n * panel edge-to-edge without black bars.\n * @property fallbackRatio - Aspect ratio to use with the simple center-crop\n * path ({@link buildCropFilter}) when webcam detection fails.\n */\ninterface SmartLayoutConfig {\n label: string\n targetW: number\n screenH: number\n camH: number\n fallbackRatio: AspectRatio\n}\n\n/**\n * Shared smart conversion: detects a webcam overlay in the source video and\n * builds a **split-screen** layout (screen on top, webcam on bottom).\n *\n * ### Why split-screen?\n * Screen recordings with a webcam overlay (e.g. top-right corner) waste space\n * when naively center-cropped to portrait/square. The split-screen approach\n * gives the screen content and webcam each their own dedicated panel, making\n * both fully visible in a narrow frame.\n *\n * ### Algorithm\n * 1. Run {@link detectWebcamRegion} to find the webcam bounding box.\n * 2. **Screen crop**: exclude the webcam columns so only the screen content\n * remains, then scale to `targetW × screenH` (letterboxing if needed).\n * 3. **Webcam crop**: aspect-ratio-match the webcam region to `targetW × camH`.\n * If the webcam is wider than the target, we keep full height and\n * center-crop width; if taller, we keep full width and center-crop height.\n * This ensures the webcam fills its panel edge-to-edge with **no black bars**.\n * 4. **vstack**: vertically stack `[screen][cam]` into the final frame.\n *\n * Falls back to simple center-crop ({@link buildCropFilter}) if no webcam is\n * detected.\n *\n * @param inputPath - Source video (assumed 16:9 landscape with optional webcam overlay)\n * @param outputPath - Destination path for the converted video\n * @param config - Layout geometry (see {@link SmartLayoutConfig})\n * @returns The output path on success\n */\nasync function convertWithSmartLayout(\n inputPath: string,\n outputPath: string,\n config: SmartLayoutConfig,\n): Promise<string> {\n const { label, targetW, screenH, camH, fallbackRatio } = config\n const outputDir = dirname(outputPath)\n await ensureDirectory(outputDir)\n\n const webcam = await detectWebcamRegion(inputPath)\n\n if (!webcam) {\n logger.info(`[${label}] No webcam found, falling back to center-crop`)\n return convertAspectRatio(inputPath, outputPath, fallbackRatio)\n }\n\n const resolution = await getVideoResolution(inputPath)\n\n // Determine screen crop region (exclude webcam area using detected bounds)\n let screenCropX: number\n let screenCropW: number\n if (webcam.position === 'top-right' || webcam.position === 'bottom-right') {\n screenCropX = 0\n screenCropW = webcam.x\n } else {\n screenCropX = webcam.x + webcam.width\n screenCropW = Math.max(0, resolution.width - screenCropX)\n }\n\n // Crop webcam to match target bottom-section aspect ratio, then scale to fill\n const targetAR = targetW / camH\n const webcamAR = webcam.width / webcam.height\n\n let faceX: number, faceY: number, faceW: number, faceH: number\n if (webcamAR > targetAR) {\n // Webcam wider than target: keep full height, center-crop width\n faceH = webcam.height\n faceW = Math.round(faceH * targetAR)\n faceX = webcam.x + Math.round((webcam.width - faceW) / 2)\n faceY = webcam.y\n } else {\n // Webcam taller than target: keep full width, center-crop height\n faceW = webcam.width\n faceH = Math.round(faceW / targetAR)\n faceX = webcam.x\n faceY = webcam.y + Math.round((webcam.height - faceH) / 2)\n }\n\n const filterComplex = [\n `[0:v]crop=${screenCropW}:ih:${screenCropX}:0,scale=${targetW}:${screenH}:force_original_aspect_ratio=decrease,` +\n `pad=${targetW}:${screenH}:(ow-iw)/2:(oh-ih)/2:black[screen]`,\n `[0:v]crop=${faceW}:${faceH}:${faceX}:${faceY},scale=${targetW}:${camH}[cam]`,\n '[screen][cam]vstack[out]',\n ].join(';')\n\n logger.info(`[${label}] Split-screen layout: webcam at ${webcam.position} → ${outputPath}`)\n\n const args = [\n '-y',\n '-i', inputPath,\n '-filter_complex', filterComplex,\n '-map', '[out]',\n '-map', '0:a',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-c:a', 'aac',\n '-b:a', '128k',\n '-threads', '4',\n outputPath,\n ]\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`[${label}] FFmpeg failed: ${stderr || error.message}`)\n reject(new Error(`${label} conversion failed: ${stderr || error.message}`))\n return\n }\n logger.info(`[${label}] Complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n\n/**\n * Smart portrait (9:16) conversion → 1080×1920.\n *\n * Screen panel: 1080×1248 (65%), Webcam panel: 1080×672 (35%).\n * Total: 1080×1920 — standard TikTok / Reels / Shorts dimensions.\n *\n * Falls back to center-crop 9:16 if no webcam is detected.\n *\n * @param inputPath - Source landscape video\n * @param outputPath - Destination path for the portrait video\n */\nexport async function convertToPortraitSmart(\n inputPath: string,\n outputPath: string,\n): Promise<string> {\n return convertWithSmartLayout(inputPath, outputPath, {\n label: 'SmartPortrait',\n targetW: 1080,\n screenH: 1248,\n camH: 672,\n fallbackRatio: '9:16',\n })\n}\n\n/**\n * Smart square (1:1) conversion → 1080×1080.\n *\n * Screen panel: 1080×700 (65%), Webcam panel: 1080×380 (35%).\n * Total: 1080×1080 — standard LinkedIn / Twitter square format.\n *\n * Falls back to center-crop 1:1 if no webcam is detected.\n *\n * @param inputPath - Source landscape video\n * @param outputPath - Destination path for the square video\n */\nexport async function convertToSquareSmart(\n inputPath: string,\n outputPath: string,\n): Promise<string> {\n return convertWithSmartLayout(inputPath, outputPath, {\n label: 'SmartSquare',\n targetW: 1080,\n screenH: 700,\n camH: 380,\n fallbackRatio: '1:1',\n })\n}\n\n/**\n * Smart feed (4:5) conversion → 1080×1350.\n *\n * Screen panel: 1080×878 (65%), Webcam panel: 1080×472 (35%).\n * Total: 1080×1350 — Instagram feed's preferred tall format.\n *\n * Falls back to center-crop 4:5 if no webcam is detected.\n *\n * @param inputPath - Source landscape video\n * @param outputPath - Destination path for the 4:5 video\n */\nexport async function convertToFeedSmart(\n inputPath: string,\n outputPath: string,\n): Promise<string> {\n return convertWithSmartLayout(inputPath, outputPath, {\n label: 'SmartFeed',\n targetW: 1080,\n screenH: 878,\n camH: 472,\n fallbackRatio: '4:5',\n })\n}\n\n/**\n * Generate platform-specific aspect-ratio variants of a short clip.\n *\n * ### Routing logic\n * 1. Maps each requested platform to its aspect ratio via {@link PLATFORM_RATIOS}.\n * 2. **Deduplicates by ratio** — if TikTok and Reels both need 9:16, only one\n * encode is performed and both platforms reference the same output file.\n * 3. Skips 16:9 entirely since the source is already landscape.\n * 4. Routes each ratio to its smart converter (portrait / square / feed) for\n * split-screen layout, falling back to {@link convertAspectRatio} for any\n * ratio without a smart converter.\n *\n * @param inputPath - Source video (16:9 landscape)\n * @param outputDir - Directory to write variant files into\n * @param slug - Base filename slug (e.g. \"my-video-short-1\")\n * @param platforms - Platforms to generate for (default: tiktok + linkedin)\n * @returns Array of variant metadata (one entry per platform, deduplicated files)\n */\nexport async function generatePlatformVariants(\n inputPath: string,\n outputDir: string,\n slug: string,\n platforms: Platform[] = ['tiktok', 'linkedin'],\n): Promise<{ platform: Platform; aspectRatio: AspectRatio; path: string; width: number; height: number }[]> {\n await ensureDirectory(outputDir)\n\n // Deduplicate by aspect ratio to avoid redundant encodes\n const ratioMap = new Map<AspectRatio, Platform[]>()\n for (const p of platforms) {\n const ratio = PLATFORM_RATIOS[p]\n if (ratio === '16:9') continue // skip — original is already 16:9\n const list = ratioMap.get(ratio) ?? []\n list.push(p)\n ratioMap.set(ratio, list)\n }\n\n const variants: { platform: Platform; aspectRatio: AspectRatio; path: string; width: number; height: number }[] = []\n\n for (const [ratio, associatedPlatforms] of ratioMap) {\n const suffix = ratio === '9:16' ? 'portrait' : ratio === '4:5' ? 'feed' : 'square'\n const outPath = join(outputDir, `${slug}-${suffix}.mp4`)\n\n try {\n if (ratio === '9:16') {\n await convertToPortraitSmart(inputPath, outPath)\n } else if (ratio === '1:1') {\n await convertToSquareSmart(inputPath, outPath)\n } else if (ratio === '4:5') {\n await convertToFeedSmart(inputPath, outPath)\n } else {\n await convertAspectRatio(inputPath, outPath, ratio)\n }\n const dims = DIMENSIONS[ratio]\n for (const p of associatedPlatforms) {\n variants.push({ platform: p, aspectRatio: ratio, path: outPath, width: dims.width, height: dims.height })\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`Skipping ${ratio} variant for ${slug}: ${message}`)\n }\n }\n\n return variants\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { BaseAgent } from './BaseAgent'\nimport { VideoFile, Transcript, ShortClip, ShortSegment, ShortClipVariant } from '../types'\nimport { extractClip, extractCompositeClip } from '../tools/ffmpeg/clipExtraction'\nimport { generateStyledASSForSegment, generateStyledASSForComposite, generatePortraitASSWithHook, generatePortraitASSWithHookComposite } from '../tools/captions/captionGenerator'\nimport { burnCaptions } from '../tools/ffmpeg/captionBurning'\nimport { generatePlatformVariants, type Platform } from '../tools/ffmpeg/aspectRatio'\nimport { generateId } from '../core/text.js'\nimport { slugify } from '../core/text.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport logger from '../config/logger'\n\n// ── Types for the LLM's plan_shorts tool call ──────────────────────────────\n\ninterface PlannedSegment {\n start: number\n end: number\n description: string\n}\n\ninterface PlannedShort {\n title: string\n description: string\n tags: string[]\n segments: PlannedSegment[]\n}\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a short-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and identify the most compelling moments to extract as shorts (15–60 seconds each).\n\n## What to look for\n- **Key insights** — concise, quotable takeaways\n- **Funny moments** — humor, wit, unexpected punchlines\n- **Controversial takes** — bold opinions that spark discussion\n- **Educational nuggets** — clear explanations of complex topics\n- **Emotional peaks** — passion, vulnerability, excitement\n- **Topic compilations** — multiple brief mentions of one theme that can be stitched together\n\n## Short types\n- **Single segment** — one contiguous section of the video\n- **Composite** — multiple non-contiguous segments combined into one short (great for topic compilations or building a narrative arc)\n\n## Rules\n1. Each short must be 15–60 seconds total duration.\n2. Timestamps must align to word boundaries from the transcript.\n3. Prefer natural sentence boundaries for clean cuts.\n4. Aim for 3–8 shorts per video, depending on length and richness.\n5. Every short needs a catchy, descriptive title (5–10 words).\n6. Tags should be lowercase, no hashes, 3–6 per short.\n7. A 1-second buffer is automatically added before and after each segment boundary during extraction, so plan segments based on content timestamps without worrying about clipping words at the edges.\n\nWhen you have identified the shorts, call the **plan_shorts** tool with your complete plan.`\n\n// ── JSON Schema for the plan_shorts tool ────────────────────────────────────\n\nconst PLAN_SHORTS_SCHEMA = {\n type: 'object',\n properties: {\n shorts: {\n type: 'array',\n description: 'Array of planned short clips',\n items: {\n type: 'object',\n properties: {\n title: { type: 'string', description: 'Catchy short title (5–10 words)' },\n description: { type: 'string', description: 'Brief description of the short content' },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Lowercase tags without hashes, 3–6 per short',\n },\n segments: {\n type: 'array',\n description: 'One or more time segments that compose this short',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds' },\n end: { type: 'number', description: 'End time in seconds' },\n description: { type: 'string', description: 'What happens in this segment' },\n },\n required: ['start', 'end', 'description'],\n },\n },\n },\n required: ['title', 'description', 'tags', 'segments'],\n },\n },\n },\n required: ['shorts'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass ShortsAgent extends BaseAgent {\n private plannedShorts: PlannedShort[] = []\n\n constructor(model?: string) {\n super('ShortsAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'plan_shorts',\n description:\n 'Submit the planned shorts as a structured JSON array. Call this once with all planned shorts.',\n parameters: PLAN_SHORTS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('plan_shorts', args as Record<string, unknown>)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (toolName === 'plan_shorts') {\n this.plannedShorts = args.shorts as PlannedShort[]\n logger.info(`[ShortsAgent] Planned ${this.plannedShorts.length} shorts`)\n return { success: true, count: this.plannedShorts.length }\n }\n throw new Error(`Unknown tool: ${toolName}`)\n }\n\n getPlannedShorts(): PlannedShort[] {\n return this.plannedShorts\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\nexport async function generateShorts(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n): Promise<ShortClip[]> {\n const agent = new ShortsAgent(model)\n\n // Build prompt with full transcript including word-level timestamps\n const transcriptLines = transcript.segments.map((seg) => {\n const words = seg.words\n .map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`)\n .join(' ')\n return `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}\\nWords: ${words}`\n })\n\n const prompt = [\n `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and plan shorts.\\n`,\n `Video: ${video.filename}`,\n `Duration: ${transcript.duration.toFixed(1)}s\\n`,\n '--- TRANSCRIPT ---\\n',\n transcriptLines.join('\\n\\n'),\n '\\n--- END TRANSCRIPT ---',\n ].join('\\n')\n\n try {\n await agent.run(prompt)\n const planned = agent.getPlannedShorts()\n\n if (planned.length === 0) {\n logger.warn('[ShortsAgent] No shorts were planned')\n return []\n }\n\n const shortsDir = join(dirname(video.repoPath), 'shorts')\n await ensureDirectory(shortsDir)\n\n const shorts: ShortClip[] = []\n\n for (const plan of planned) {\n const id = generateId()\n const shortSlug = slugify(plan.title)\n const totalDuration = plan.segments.reduce((sum, s) => sum + (s.end - s.start), 0)\n const outputPath = join(shortsDir, `${shortSlug}.mp4`)\n\n const segments: ShortSegment[] = plan.segments.map((s) => ({\n start: s.start,\n end: s.end,\n description: s.description,\n }))\n\n // Extract the clip (single or composite)\n if (segments.length === 1) {\n await extractClip(video.repoPath, segments[0].start, segments[0].end, outputPath)\n } else {\n await extractCompositeClip(video.repoPath, segments, outputPath)\n }\n\n // Generate platform-specific aspect ratio variants from UNCAPTIONED video\n // so portrait/square crops are clean before captions are burned per-variant\n let variants: ShortClipVariant[] | undefined\n try {\n const defaultPlatforms: Platform[] = ['tiktok', 'youtube-shorts', 'instagram-reels', 'instagram-feed', 'linkedin']\n const results = await generatePlatformVariants(outputPath, shortsDir, shortSlug, defaultPlatforms)\n if (results.length > 0) {\n variants = results.map((v) => ({\n path: v.path,\n aspectRatio: v.aspectRatio,\n platform: v.platform as ShortClipVariant['platform'],\n width: v.width,\n height: v.height,\n }))\n logger.info(`[ShortsAgent] Generated ${variants.length} platform variants for: ${plan.title}`)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[ShortsAgent] Platform variant generation failed for ${plan.title}: ${message}`)\n }\n\n // Generate ASS captions for the landscape short and burn them in\n let captionedPath: string | undefined\n try {\n const assContent = segments.length === 1\n ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end)\n : generateStyledASSForComposite(transcript, segments)\n\n const assPath = join(shortsDir, `${shortSlug}.ass`)\n await writeTextFile(assPath, assContent)\n\n captionedPath = join(shortsDir, `${shortSlug}-captioned.mp4`)\n await burnCaptions(outputPath, assPath, captionedPath)\n logger.info(`[ShortsAgent] Burned captions for short: ${plan.title}`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[ShortsAgent] Caption burning failed for ${plan.title}: ${message}`)\n captionedPath = undefined\n }\n\n // Burn portrait-style captions (green highlight, centered, hook overlay) onto portrait variant\n if (variants) {\n const portraitVariant = variants.find(v => v.aspectRatio === '9:16')\n if (portraitVariant) {\n try {\n const portraitAssContent = segments.length === 1\n ? generatePortraitASSWithHook(transcript, plan.title, segments[0].start, segments[0].end)\n : generatePortraitASSWithHookComposite(transcript, segments, plan.title)\n const portraitAssPath = join(shortsDir, `${shortSlug}-portrait.ass`)\n await writeTextFile(portraitAssPath, portraitAssContent)\n const portraitCaptionedPath = portraitVariant.path.replace('.mp4', '-captioned.mp4')\n await burnCaptions(portraitVariant.path, portraitAssPath, portraitCaptionedPath)\n // Update the variant path to point to the captioned version\n portraitVariant.path = portraitCaptionedPath\n logger.info(`[ShortsAgent] Burned portrait captions with hook for: ${plan.title}`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[ShortsAgent] Portrait caption burning failed for ${plan.title}: ${message}`)\n }\n }\n }\n\n // Generate description markdown\n const mdPath = join(shortsDir, `${shortSlug}.md`)\n const mdContent = [\n `# ${plan.title}\\n`,\n plan.description,\n '',\n '## Segments\\n',\n ...plan.segments.map(\n (s, i) => `${i + 1}. **${s.start.toFixed(2)}s – ${s.end.toFixed(2)}s** — ${s.description}`,\n ),\n '',\n '## Tags\\n',\n plan.tags.map((t) => `- ${t}`).join('\\n'),\n '',\n ].join('\\n')\n await writeTextFile(mdPath, mdContent)\n\n shorts.push({\n id,\n title: plan.title,\n slug: shortSlug,\n segments,\n totalDuration,\n outputPath,\n captionedPath,\n description: plan.description,\n tags: plan.tags,\n variants,\n })\n\n logger.info(`[ShortsAgent] Created short: ${plan.title} (${totalDuration.toFixed(1)}s)`)\n }\n\n logger.info(`[ShortsAgent] Generated ${shorts.length} shorts`)\n return shorts\n } finally {\n await agent.destroy()\n }\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { BaseAgent } from './BaseAgent'\nimport { VideoFile, Transcript, MediumClip, MediumSegment } from '../types'\nimport { extractClip, extractCompositeClipWithTransitions } from '../tools/ffmpeg/clipExtraction'\nimport { generateStyledASSForSegment, generateStyledASSForComposite } from '../tools/captions/captionGenerator'\nimport { burnCaptions } from '../tools/ffmpeg/captionBurning'\nimport { generateId } from '../core/text.js'\nimport { slugify } from '../core/text.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport logger from '../config/logger'\n\n// ── Types for the LLM's plan_medium_clips tool call ─────────────────────────\n\ninterface PlannedSegment {\n start: number\n end: number\n description: string\n}\n\ninterface PlannedMediumClip {\n title: string\n description: string\n tags: string[]\n segments: PlannedSegment[]\n totalDuration: number\n hook: string\n topic: string\n}\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a medium-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and identify the best 1–3 minute segments to extract as standalone medium-form clips.\n\n## What to look for\n\n- **Complete topics** — a subject is introduced, explored, and concluded\n- **Narrative arcs** — problem → solution → result; question → exploration → insight\n- **Educational deep dives** — clear, thorough explanations of complex topics\n- **Compelling stories** — anecdotes with setup, tension, and resolution\n- **Strong arguments** — claim → evidence → implication sequences\n- **Topic compilations** — multiple brief mentions of one theme across the video that can be compiled into a cohesive 1–3 minute segment\n\n## Clip types\n\n- **Deep Dive** — a single contiguous section (1–3 min) covering one topic in depth\n- **Compilation** — multiple non-contiguous segments stitched together around a single theme or narrative thread (1–3 min total)\n\n## Rules\n\n1. Each clip must be 60–180 seconds total duration.\n2. Timestamps must align to word boundaries from the transcript.\n3. Prefer natural sentence and paragraph boundaries for clean entry/exit points.\n4. Each clip must be self-contained — a viewer with no other context should understand and get value from the clip.\n5. Aim for 2–4 medium clips per video, depending on length and richness.\n6. Every clip needs a descriptive title (5–12 words) and a topic label.\n7. For compilations, specify segments in the order they should appear in the final clip (which may differ from chronological order).\n8. Tags should be lowercase, no hashes, 3–6 per clip.\n9. A 1-second buffer is automatically added around each segment boundary.\n10. Each clip needs a hook — the opening line or concept that draws viewers in.\n\n## Differences from shorts\n\n- Shorts capture *moments*; medium clips capture *complete ideas*.\n- Don't just find the most exciting 60 seconds — find where a topic starts and where it naturally concludes.\n- It's OK if a medium clip has slower pacing — depth and coherence matter more than constant high energy.\n- Look for segments that work as standalone mini-tutorials or explanations.\n- Avoid overlap with content that would work better as a short (punchy, viral, single-moment).\n\nWhen you have identified the clips, call the **plan_medium_clips** tool with your complete plan.`\n\n// ── JSON Schema for the plan_medium_clips tool ──────────────────────────────\n\nconst PLAN_MEDIUM_CLIPS_SCHEMA = {\n type: 'object',\n properties: {\n clips: {\n type: 'array',\n description: 'Array of planned medium-length clips',\n items: {\n type: 'object',\n properties: {\n title: { type: 'string', description: 'Descriptive clip title (5–12 words)' },\n description: { type: 'string', description: 'Brief description of the clip content' },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Lowercase tags without hashes, 3–6 per clip',\n },\n segments: {\n type: 'array',\n description: 'One or more time segments that compose this clip',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds' },\n end: { type: 'number', description: 'End time in seconds' },\n description: { type: 'string', description: 'What happens in this segment' },\n },\n required: ['start', 'end', 'description'],\n },\n },\n totalDuration: { type: 'number', description: 'Total clip duration in seconds (60–180)' },\n hook: { type: 'string', description: 'Opening hook for the clip' },\n topic: { type: 'string', description: 'Main topic covered in the clip' },\n },\n required: ['title', 'description', 'tags', 'segments', 'totalDuration', 'hook', 'topic'],\n },\n },\n },\n required: ['clips'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass MediumVideoAgent extends BaseAgent {\n private plannedClips: PlannedMediumClip[] = []\n\n constructor(model?: string) {\n super('MediumVideoAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'plan_medium_clips',\n description:\n 'Submit the planned medium-length clips as a structured JSON array. Call this once with all planned clips.',\n parameters: PLAN_MEDIUM_CLIPS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('plan_medium_clips', args as Record<string, unknown>)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (toolName === 'plan_medium_clips') {\n this.plannedClips = args.clips as PlannedMediumClip[]\n logger.info(`[MediumVideoAgent] Planned ${this.plannedClips.length} medium clips`)\n return { success: true, count: this.plannedClips.length }\n }\n throw new Error(`Unknown tool: ${toolName}`)\n }\n\n getPlannedClips(): PlannedMediumClip[] {\n return this.plannedClips\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\nexport async function generateMediumClips(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n): Promise<MediumClip[]> {\n const agent = new MediumVideoAgent(model)\n\n // Build prompt with full transcript including word-level timestamps\n const transcriptLines = transcript.segments.map((seg) => {\n const words = seg.words\n .map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`)\n .join(' ')\n return `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}\\nWords: ${words}`\n })\n\n const prompt = [\n `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and plan medium-length clips (1–3 minutes each).\\n`,\n `Video: ${video.filename}`,\n `Duration: ${transcript.duration.toFixed(1)}s\\n`,\n '--- TRANSCRIPT ---\\n',\n transcriptLines.join('\\n\\n'),\n '\\n--- END TRANSCRIPT ---',\n ].join('\\n')\n\n try {\n await agent.run(prompt)\n const planned = agent.getPlannedClips()\n\n if (planned.length === 0) {\n logger.warn('[MediumVideoAgent] No medium clips were planned')\n return []\n }\n\n const clipsDir = join(dirname(video.repoPath), 'medium-clips')\n await ensureDirectory(clipsDir)\n\n const clips: MediumClip[] = []\n\n for (const plan of planned) {\n const id = generateId()\n const clipSlug = slugify(plan.title)\n const totalDuration = plan.segments.reduce((sum, s) => sum + (s.end - s.start), 0)\n const outputPath = join(clipsDir, `${clipSlug}.mp4`)\n\n const segments: MediumSegment[] = plan.segments.map((s) => ({\n start: s.start,\n end: s.end,\n description: s.description,\n }))\n\n // Extract the clip — single segment or composite with crossfade transitions\n if (segments.length === 1) {\n await extractClip(video.repoPath, segments[0].start, segments[0].end, outputPath)\n } else {\n await extractCompositeClipWithTransitions(video.repoPath, segments, outputPath)\n }\n\n // Generate ASS captions with medium style (smaller font, bottom-positioned)\n let captionedPath: string | undefined\n try {\n const assContent = segments.length === 1\n ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end, 1.0, 'medium')\n : generateStyledASSForComposite(transcript, segments, 1.0, 'medium')\n\n const assPath = join(clipsDir, `${clipSlug}.ass`)\n await writeTextFile(assPath, assContent)\n\n captionedPath = join(clipsDir, `${clipSlug}-captioned.mp4`)\n await burnCaptions(outputPath, assPath, captionedPath)\n logger.info(`[MediumVideoAgent] Burned captions for clip: ${plan.title}`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[MediumVideoAgent] Caption burning failed for ${plan.title}: ${message}`)\n captionedPath = undefined\n }\n\n // Generate description markdown\n const mdPath = join(clipsDir, `${clipSlug}.md`)\n const mdContent = [\n `# ${plan.title}\\n`,\n `**Topic:** ${plan.topic}\\n`,\n `**Hook:** ${plan.hook}\\n`,\n plan.description,\n '',\n '## Segments\\n',\n ...plan.segments.map(\n (s, i) => `${i + 1}. **${s.start.toFixed(2)}s – ${s.end.toFixed(2)}s** — ${s.description}`,\n ),\n '',\n '## Tags\\n',\n plan.tags.map((t) => `- ${t}`).join('\\n'),\n '',\n ].join('\\n')\n await writeTextFile(mdPath, mdContent)\n\n clips.push({\n id,\n title: plan.title,\n slug: clipSlug,\n segments,\n totalDuration,\n outputPath,\n captionedPath,\n description: plan.description,\n tags: plan.tags,\n hook: plan.hook,\n topic: plan.topic,\n })\n\n logger.info(`[MediumVideoAgent] Created medium clip: ${plan.title} (${totalDuration.toFixed(1)}s)`)\n }\n\n logger.info(`[MediumVideoAgent] Generated ${clips.length} medium clips`)\n return clips\n } finally {\n await agent.destroy()\n }\n}\n","/**\n * Type definitions for vidpipe CLI pipeline.\n *\n * Domain types covering transcription, video metadata, short-clip planning,\n * social-media post generation, and end-to-end pipeline orchestration.\n *\n * ### Timestamp convention\n * All `start` and `end` fields are in **seconds from the beginning of the video**\n * (floating-point, e.g. 12.345). This matches Whisper's output format and\n * FFmpeg's `-ss` / `-to` parameters.\n */\n\n// ============================================================================\n// PLATFORM\n// ============================================================================\n\n/** Social-media platforms supported for post generation. */\nexport enum Platform {\n TikTok = 'tiktok',\n YouTube = 'youtube',\n Instagram = 'instagram',\n LinkedIn = 'linkedin',\n X = 'x',\n}\n\n// ============================================================================\n// TRANSCRIPTION (Whisper)\n// ============================================================================\n\n/**\n * A single word with precise start/end timestamps from Whisper.\n *\n * Word-level timestamps are the foundation of the karaoke caption system —\n * each word knows exactly when it's spoken, enabling per-word highlighting.\n * Whisper produces these via its `--word_timestamps` flag.\n *\n * @property word - The spoken word (may include leading/trailing whitespace)\n * @property start - When this word begins, in seconds from video start\n * @property end - When this word ends, in seconds from video start\n */\nexport interface Word {\n word: string;\n start: number;\n end: number;\n}\n\n/**\n * A sentence/phrase-level segment from Whisper transcription.\n *\n * Segments are Whisper's natural grouping of words into sentences or clauses.\n * They're used for SRT/VTT subtitle generation (one cue per segment) and for\n * silence removal (segments that fall entirely within a removed region are dropped).\n *\n * @property id - Sequential segment index (0-based)\n * @property text - Full text of the segment\n * @property start - Segment start time in seconds\n * @property end - Segment end time in seconds\n * @property words - The individual words with their own timestamps\n */\nexport interface Segment {\n id: number;\n text: string;\n start: number;\n end: number;\n words: Word[];\n}\n\n/**\n * Complete transcript result from Whisper.\n *\n * Contains both segment-level and word-level data. The top-level `words` array\n * is a flat list of all words across all segments — this is the primary input\n * for the ASS caption generator's karaoke highlighting.\n *\n * @property text - Full transcript as a single string\n * @property segments - Sentence/phrase-level segments\n * @property words - Flat array of all words with timestamps (used by ASS captions)\n * @property language - Detected language code (e.g. \"en\")\n * @property duration - Total video duration in seconds\n */\nexport interface Transcript {\n text: string;\n segments: Segment[];\n words: Word[];\n language: string;\n duration: number;\n}\n\n// ============================================================================\n// VIDEO FILE\n// ============================================================================\n\n/**\n * Metadata for a video file after ingestion into the repo structure.\n *\n * @property originalPath - Where the file was picked up from (e.g. recordings/ folder)\n * @property repoPath - Canonical path within the repo's asset directory\n * @property videoDir - Directory containing all generated assets for this video\n * @property slug - URL/filesystem-safe name derived from the filename (e.g. \"my-video-2024-01-15\")\n * @property filename - Original filename with extension\n * @property duration - Video duration in seconds (from ffprobe)\n * @property size - File size in bytes\n * @property createdAt - File creation timestamp\n */\nexport interface VideoFile {\n originalPath: string;\n repoPath: string;\n videoDir: string;\n slug: string;\n filename: string;\n duration: number;\n size: number;\n createdAt: Date;\n}\n\n// ============================================================================\n// ASPECT RATIO\n// ============================================================================\n\nexport type AspectRatio = '16:9' | '9:16' | '1:1' | '4:5';\n\nexport type VideoPlatform =\n | 'tiktok'\n | 'youtube-shorts'\n | 'instagram-reels'\n | 'instagram-feed'\n | 'linkedin'\n | 'youtube'\n | 'twitter';\n\n// ============================================================================\n// CAPTION STYLE\n// ============================================================================\n\n/**\n * Caption rendering style.\n * - `'shorts'` — large centered pop captions for short-form clips (landscape 16:9)\n * - `'medium'` — smaller bottom-positioned captions for longer content\n * - `'portrait'` — Opus Clips style for 9:16 vertical video (green highlight,\n * scale-pop animation, larger fonts for small-screen viewing)\n */\nexport type CaptionStyle = 'shorts' | 'medium' | 'portrait';\n\nexport interface ShortClipVariant {\n path: string;\n aspectRatio: AspectRatio;\n platform: VideoPlatform;\n width: number;\n height: number;\n}\n\n// ============================================================================\n// SHORT CLIPS\n// ============================================================================\n\n/**\n * A single time range within a short clip.\n *\n * Short clips can be **composite** — made of multiple non-contiguous segments\n * from the original video, concatenated together. Each segment describes one\n * contiguous range.\n *\n * @property start - Start time in the original video (seconds)\n * @property end - End time in the original video (seconds)\n * @property description - Human-readable description of what happens in this segment\n */\nexport interface ShortSegment {\n start: number;\n end: number;\n description: string;\n}\n\n/**\n * A planned short clip (15–60s) extracted from the full video.\n *\n * May be a single contiguous segment or a **composite** of multiple segments\n * concatenated together (e.g. an intro + punchline from different parts of\n * the video). The `segments` array defines the source time ranges; `totalDuration`\n * is the sum of all segment durations.\n *\n * @property id - Unique identifier (e.g. \"short-1\")\n * @property title - Human-readable title for the clip\n * @property slug - Filesystem-safe slug (e.g. \"typescript-tip-generics\")\n * @property segments - One or more time ranges from the original video\n * @property totalDuration - Sum of all segment durations in seconds\n * @property outputPath - Path to the extracted video file\n * @property captionedPath - Path to the captioned version (if generated)\n * @property description - Short description for social media\n * @property tags - Hashtags / topic tags\n * @property variants - Platform-specific aspect-ratio variants (portrait, square, etc.)\n */\nexport interface ShortClip {\n id: string;\n title: string;\n slug: string;\n segments: ShortSegment[];\n totalDuration: number;\n outputPath: string;\n captionedPath?: string;\n description: string;\n tags: string[];\n variants?: ShortClipVariant[];\n}\n\n// ============================================================================\n// MEDIUM CLIPS\n// ============================================================================\n\n/** A planned medium clip segment */\nexport interface MediumSegment {\n start: number;\n end: number;\n description: string;\n}\n\nexport interface MediumClip {\n id: string;\n title: string;\n slug: string;\n segments: MediumSegment[];\n totalDuration: number;\n outputPath: string;\n captionedPath?: string;\n description: string;\n tags: string[];\n hook: string;\n topic: string;\n}\n\n// ============================================================================\n// SOCIAL MEDIA\n// ============================================================================\n\nexport interface SocialPost {\n platform: Platform;\n content: string;\n hashtags: string[];\n links: string[];\n characterCount: number;\n outputPath: string;\n}\n\n// ============================================================================\n// CHAPTERS\n// ============================================================================\n\n/**\n * A chapter marker for YouTube's chapters feature.\n *\n * @property timestamp - Start time in seconds (YouTube shows these as clickable markers)\n * @property title - Short chapter title (shown in the progress bar)\n * @property description - Longer description for the README/summary\n */\nexport interface Chapter {\n timestamp: number;\n title: string;\n description: string;\n}\n\n// ============================================================================\n// SNAPSHOTS & SUMMARY\n// ============================================================================\n\nexport interface VideoSnapshot {\n timestamp: number;\n description: string;\n outputPath: string;\n}\n\nexport interface VideoSummary {\n title: string;\n overview: string;\n keyTopics: string[];\n snapshots: VideoSnapshot[];\n markdownPath: string;\n}\n\n// ============================================================================\n// PIPELINE\n// ============================================================================\n\nexport enum PipelineStage {\n Ingestion = 'ingestion',\n Transcription = 'transcription',\n SilenceRemoval = 'silence-removal',\n Chapters = 'chapters',\n Captions = 'captions',\n CaptionBurn = 'caption-burn',\n Summary = 'summary',\n Shorts = 'shorts',\n MediumClips = 'medium-clips',\n SocialMedia = 'social-media',\n ShortPosts = 'short-posts',\n MediumClipPosts = 'medium-clip-posts',\n Blog = 'blog',\n QueueBuild = 'queue-build',\n GitPush = 'git-push',\n}\n\n/**\n * Per-stage outcome record for pipeline observability.\n *\n * @property stage - Which pipeline stage this result is for\n * @property success - Whether the stage completed without throwing\n * @property error - Error message if the stage failed\n * @property duration - Wall-clock time in milliseconds\n */\nexport interface StageResult {\n stage: PipelineStage;\n success: boolean;\n error?: string;\n duration: number;\n}\n\n/**\n * Complete output of a pipeline run.\n *\n * Fields are optional because stages can fail independently — a failed\n * transcription means no summary, but the video metadata is still available.\n *\n * @property totalDuration - Total pipeline wall-clock time in milliseconds\n */\nexport interface PipelineResult {\n video: VideoFile;\n transcript?: Transcript;\n editedVideoPath?: string;\n captions?: string[];\n captionedVideoPath?: string;\n summary?: VideoSummary;\n chapters?: Chapter[];\n shorts: ShortClip[];\n mediumClips: MediumClip[];\n socialPosts: SocialPost[];\n blogPost?: string;\n stageResults: StageResult[];\n totalDuration: number;\n}\n\n// ============================================================================\n// SILENCE REMOVAL\n// ============================================================================\n\n/**\n * Result of the silence removal stage.\n *\n * @property editedPath - Path to the video with silence regions cut out\n * @property removals - Time ranges that were removed (in original video time).\n * Used by {@link adjustTranscript} to shift transcript timestamps.\n * @property keepSegments - Inverse of removals — the time ranges that were kept.\n * Used by the single-pass caption burn to re-create the edit from the original.\n * @property wasEdited - False if no silence was found and the video is unchanged\n */\nexport interface SilenceRemovalResult {\n editedPath: string;\n removals: { start: number; end: number }[];\n keepSegments: { start: number; end: number }[];\n wasEdited: boolean;\n}\n\n// ============================================================================\n// AGENT RESULT (Copilot SDK)\n// ============================================================================\n\n/**\n * Standard result wrapper for all Copilot SDK agent calls.\n *\n * @property success - Whether the agent completed its task\n * @property data - The parsed result (type varies by agent)\n * @property error - Error message if the agent failed\n * @property usage - Token counts for cost tracking\n */\nexport interface AgentResult<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n usage?: {\n promptTokens: number;\n completionTokens: number;\n };\n}\n\n// ============================================================================\n// SOCIAL PUBLISHING / QUEUE\n// ============================================================================\n\n/** Character limits per social media platform */\nexport const PLATFORM_CHAR_LIMITS: Record<string, number> = {\n tiktok: 2200,\n youtube: 5000,\n instagram: 2200,\n linkedin: 3000,\n twitter: 280,\n}\n\n/**\n * Maps vidpipe Platform enum values to Late API platform strings.\n * Platform.X = 'x' but Late API expects 'twitter'.\n */\nexport function toLatePlatform(platform: Platform): string {\n return platform === Platform.X ? 'twitter' : platform\n}\n\n/**\n * Maps a Late API platform string back to vidpipe Platform enum.\n * \n * Validates the input against known Platform values to avoid admitting\n * unknown/unsupported platforms via an unchecked cast.\n * \n * @throws {Error} If the platform is not supported\n */\nexport function fromLatePlatform(latePlatform: string): Platform {\n const normalized = normalizePlatformString(latePlatform)\n \n if (normalized === 'twitter') {\n return Platform.X\n }\n \n const platformValues = Object.values(Platform) as string[]\n if (platformValues.includes(normalized)) {\n return normalized as Platform\n }\n \n throw new Error(`Unsupported platform from Late API: ${latePlatform}`)\n}\n\n/**\n * Normalizes raw platform strings (e.g., from user input or API responses)\n * to Late API platform names. Handles X/Twitter variants and case-insensitivity.\n * \n * @example\n * normalizePlatformString('X') // 'twitter'\n * normalizePlatformString('x (twitter)') // 'twitter'\n * normalizePlatformString('YouTube') // 'youtube'\n */\nexport function normalizePlatformString(raw: string): string {\n const lower = raw.toLowerCase().trim()\n if (lower === 'x' || lower === 'x (twitter)' || lower === 'x/twitter') {\n return 'twitter'\n }\n return lower\n}\n\n/** Schedule time slot for a platform */\nexport interface ScheduleSlot {\n platform: string\n scheduledFor: string // ISO datetime\n postId?: string // Late post ID if already published\n itemId?: string // Local queue item ID\n label?: string\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { ensureDirectorySync, writeTextFileSync } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../config/logger'\nimport type { MCPServerConfig } from '../providers/types.js'\nimport { getConfig } from '../config/environment.js'\nimport {\n Platform,\n ShortClip,\n SocialPost,\n Transcript,\n VideoFile,\n VideoSummary,\n} from '../types'\n\n// ── JSON shape the LLM returns via the create_posts tool ────────────────────\n\ninterface PlatformPost {\n platform: string\n content: string\n hashtags: string[]\n links: string[]\n characterCount: number\n}\n\ninterface CreatePostsArgs {\n posts: PlatformPost[]\n}\n\n// ── System prompt───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a viral social-media content strategist.\nGiven a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.\nEach post must match the platform's tone, format, and constraints exactly.\n\nPlatform guidelines:\n1. **TikTok** – Casual, hook-driven, trending hashtags, 150 chars max, emoji-heavy.\n2. **YouTube** – Descriptive, SEO-optimized title + description, relevant tags.\n3. **Instagram** – Visual storytelling, emoji-rich, 30 hashtags max, engaging caption.\n4. **LinkedIn** – Professional, thought-leadership, industry insights, 1-3 hashtags.\n5. **X (Twitter)** – Concise, punchy, 280 chars max, 2-5 hashtags, thread-ready.\n\nIMPORTANT – Content format:\nThe \"content\" field you provide must be the FINAL, ready-to-post text that can be directly copied and pasted onto the platform. Do NOT use markdown headers, bullet points, or any formatting inside the content. Include hashtags inline at the end of the post text where appropriate. The content is saved as-is for direct posting.\n\nWorkflow:\n1. First use the \"web_search_exa\" tool to search for relevant URLs based on the key topics discussed in the video.\n2. Then call the \"create_posts\" tool with a JSON object that has a \"posts\" array.\n Each element must have: platform, content, hashtags (array), links (array), characterCount.\n\nInclude relevant links in posts when search results provide them.\nAlways call \"create_posts\" exactly once with all 5 platform posts.`\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass SocialMediaAgent extends BaseAgent {\n private collectedPosts: PlatformPost[] = []\n\n constructor(model?: string) {\n super('SocialMediaAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getMcpServers(): Record<string, MCPServerConfig> | undefined {\n const config = getConfig()\n if (!config.EXA_API_KEY) return undefined\n return {\n exa: {\n type: 'http' as const,\n url: `${config.EXA_MCP_URL}?exaApiKey=${config.EXA_API_KEY}&tools=web_search_exa`,\n headers: {},\n tools: ['*'],\n },\n }\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'create_posts',\n description:\n 'Submit the generated social media posts for all 5 platforms.',\n parameters: {\n type: 'object',\n properties: {\n posts: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n platform: { type: 'string' },\n content: { type: 'string' },\n hashtags: { type: 'array', items: { type: 'string' } },\n links: { type: 'array', items: { type: 'string' } },\n characterCount: { type: 'number' },\n },\n required: ['platform', 'content', 'hashtags', 'links', 'characterCount'],\n },\n description: 'Array of posts, one per platform',\n },\n },\n required: ['posts'],\n },\n handler: async (args: unknown) => {\n const { posts } = args as CreatePostsArgs\n this.collectedPosts = posts\n logger.info(`[SocialMediaAgent] create_posts received ${posts.length} posts`)\n return JSON.stringify({ success: true, count: posts.length })\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n // Tool dispatch is handled inline via tool handlers above.\n // This satisfies the abstract contract from BaseAgent.\n logger.warn(`[SocialMediaAgent] Unexpected handleToolCall for \"${toolName}\"`)\n return { error: `Unknown tool: ${toolName}` }\n }\n\n getCollectedPosts(): PlatformPost[] {\n return this.collectedPosts\n }\n}\n\n// ── Helper: map raw platform string → Platform enum ─────────────────────────\n\nfunction toPlatformEnum(raw: string): Platform {\n const normalised = raw.toLowerCase().trim()\n switch (normalised) {\n case 'tiktok':\n return Platform.TikTok\n case 'youtube':\n return Platform.YouTube\n case 'instagram':\n return Platform.Instagram\n case 'linkedin':\n return Platform.LinkedIn\n case 'x':\n case 'twitter':\n case 'x (twitter)':\n case 'x/twitter':\n return Platform.X\n default:\n return normalised as Platform\n }\n}\n\n// ── Helper: render a post file with YAML frontmatter ───────────────────────\n\ninterface RenderPostOpts {\n videoSlug: string\n shortSlug?: string | null\n}\n\nfunction renderPostFile(post: PlatformPost, opts: RenderPostOpts): string {\n const now = new Date().toISOString()\n const platform = toPlatformEnum(post.platform)\n const lines: string[] = ['---']\n\n lines.push(`platform: ${platform}`)\n lines.push(`status: draft`)\n lines.push(`scheduledDate: null`)\n\n if (post.hashtags.length > 0) {\n lines.push('hashtags:')\n for (const tag of post.hashtags) {\n lines.push(` - \"${tag}\"`)\n }\n } else {\n lines.push('hashtags: []')\n }\n\n if (post.links.length > 0) {\n lines.push('links:')\n for (const link of post.links) {\n lines.push(` - url: \"${link}\"`)\n lines.push(` title: null`)\n }\n } else {\n lines.push('links: []')\n }\n\n lines.push(`characterCount: ${post.characterCount}`)\n lines.push(`videoSlug: \"${opts.videoSlug}\"`)\n lines.push(`shortSlug: ${opts.shortSlug ? `\"${opts.shortSlug}\"` : 'null'}`)\n lines.push(`createdAt: \"${now}\"`)\n lines.push('---')\n lines.push('')\n lines.push(post.content)\n lines.push('')\n\n return lines.join('\\n')\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\nexport async function generateShortPosts(\n video: VideoFile,\n short: ShortClip,\n transcript: Transcript,\n model?: string,\n): Promise<SocialPost[]> {\n const agent = new SocialMediaAgent(model)\n\n try {\n // Extract transcript segments that overlap with the short's time ranges\n const relevantText = transcript.segments\n .filter((seg) =>\n short.segments.some((ss) => seg.start < ss.end && seg.end > ss.start),\n )\n .map((seg) => seg.text)\n .join(' ')\n\n const userMessage = [\n '## Short Clip Metadata',\n `- **Title:** ${short.title}`,\n `- **Description:** ${short.description}`,\n `- **Duration:** ${short.totalDuration.toFixed(1)}s`,\n `- **Tags:** ${short.tags.join(', ')}`,\n '',\n '## Relevant Transcript',\n relevantText.slice(0, 3000),\n ].join('\\n')\n\n await agent.run(userMessage)\n\n const collectedPosts = agent.getCollectedPosts()\n\n // Save posts to recordings/{slug}/shorts/{short-slug}/posts/\n const shortsDir = join(dirname(video.repoPath), 'shorts')\n const postsDir = join(shortsDir, short.slug, 'posts')\n ensureDirectorySync(postsDir)\n\n const socialPosts: SocialPost[] = collectedPosts.map((p) => {\n const platform = toPlatformEnum(p.platform)\n const outputPath = join(postsDir, `${platform}.md`)\n\n writeTextFileSync(\n outputPath,\n renderPostFile(p, { videoSlug: video.slug, shortSlug: short.slug }),\n )\n logger.info(`[SocialMediaAgent] Wrote short post ${outputPath}`)\n\n return {\n platform,\n content: p.content,\n hashtags: p.hashtags,\n links: p.links,\n characterCount: p.characterCount,\n outputPath,\n }\n })\n\n return socialPosts\n } finally {\n await agent.destroy()\n }\n}\n\nexport async function generateSocialPosts(\n video: VideoFile,\n transcript: Transcript,\n summary: VideoSummary,\n outputDir?: string,\n model?: string,\n): Promise<SocialPost[]> {\n const agent = new SocialMediaAgent(model)\n\n try {\n // Build the user prompt with transcript summary and metadata\n const userMessage = [\n '## Video Metadata',\n `- **Title:** ${summary.title}`,\n `- **Slug:** ${video.slug}`,\n `- **Duration:** ${video.duration}s`,\n '',\n '## Summary',\n summary.overview,\n '',\n '## Key Topics',\n summary.keyTopics.map((t) => `- ${t}`).join('\\n'),\n '',\n '## Transcript (first 3000 chars)',\n transcript.text.slice(0, 3000),\n ].join('\\n')\n\n await agent.run(userMessage)\n\n const collectedPosts = agent.getCollectedPosts()\n\n // Ensure the output directory exists\n const outDir = outputDir ?? join(video.videoDir, 'social-posts')\n ensureDirectorySync(outDir)\n\n const socialPosts: SocialPost[] = collectedPosts.map((p) => {\n const platform = toPlatformEnum(p.platform)\n const outputPath = join(outDir, `${platform}.md`)\n\n writeTextFileSync(\n outputPath,\n renderPostFile(p, { videoSlug: video.slug }),\n )\n logger.info(`[SocialMediaAgent] Wrote ${outputPath}`)\n\n return {\n platform,\n content: p.content,\n hashtags: p.hashtags,\n links: p.links,\n characterCount: p.characterCount,\n outputPath,\n }\n })\n\n return socialPosts\n } finally {\n await agent.destroy()\n }\n}\n","import type { ToolWithHandler, MCPServerConfig } from '../providers/types.js'\nimport { ensureDirectorySync, writeTextFileSync } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../config/logger'\nimport { getBrandConfig } from '../config/brand'\nimport { getConfig } from '../config/environment.js'\nimport type { Transcript, VideoFile, VideoSummary } from '../types'\n\n// ── Tool argument shapes ────────────────────────────────────────────────────\n\ninterface WriteBlogArgs {\n frontmatter: {\n title: string\n description: string\n tags: string[]\n cover_image?: string\n }\n body: string\n}\n\n// ── Build system prompt from brand config ───────────────────────────────────\n\nfunction buildSystemPrompt(): string {\n const brand = getBrandConfig()\n\n return `You are a technical blog writer for dev.to, writing from the perspective of ${brand.name} (${brand.handle}).\n\nVoice & style:\n- Tone: ${brand.voice.tone}\n- Personality: ${brand.voice.personality}\n- Style: ${brand.voice.style}\n\nContent guidelines: ${brand.contentGuidelines.blogFocus}\n\nYour task is to generate a full dev.to-style technical blog post (800-1500 words) based on a video transcript and summary.\n\nThe blog post MUST include:\n1. dev.to frontmatter (title, published: false, description, tags, cover_image placeholder)\n2. An engaging introduction with a hook\n3. Clear sections covering the main content (e.g. The Problem, The Solution, How It Works)\n4. Code snippets where the video content discusses code — use fenced code blocks with language tags\n5. Key Takeaways section\n6. A conclusion\n7. A footer referencing the original video\n\nWorkflow:\n1. First use the \"web_search_exa\" tool to search for relevant articles and resources to link to. Search for key topics from the video.\n2. Then call \"write_blog\" with the complete blog post including frontmatter and body.\n - Weave the search result links organically into the post text (don't dump them at the end).\n - Reference the video and any shorts naturally.\n\nAlways call \"write_blog\" exactly once with the complete post.`\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass BlogAgent extends BaseAgent {\n private blogContent: WriteBlogArgs | null = null\n\n constructor(model?: string) {\n super('BlogAgent', buildSystemPrompt(), undefined, model)\n }\n\n protected getMcpServers(): Record<string, MCPServerConfig> | undefined {\n const config = getConfig()\n if (!config.EXA_API_KEY) return undefined\n return {\n exa: {\n type: 'http' as const,\n url: `${config.EXA_MCP_URL}?exaApiKey=${config.EXA_API_KEY}&tools=web_search_exa`,\n headers: {},\n tools: ['*'],\n },\n }\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'write_blog',\n description:\n 'Submit the complete dev.to blog post with frontmatter and markdown body.',\n parameters: {\n type: 'object',\n properties: {\n frontmatter: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n description: { type: 'string' },\n tags: { type: 'array', items: { type: 'string' } },\n cover_image: { type: 'string' },\n },\n required: ['title', 'description', 'tags'],\n },\n body: {\n type: 'string',\n description: 'The full markdown body of the blog post (excluding frontmatter)',\n },\n },\n required: ['frontmatter', 'body'],\n },\n handler: async (args: unknown) => {\n const blogArgs = args as WriteBlogArgs\n this.blogContent = blogArgs\n logger.info(`[BlogAgent] write_blog received post: \"${blogArgs.frontmatter.title}\"`)\n return JSON.stringify({ success: true })\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n _args: Record<string, unknown>,\n ): Promise<unknown> {\n logger.warn(`[BlogAgent] Unexpected handleToolCall for \"${toolName}\"`)\n return { error: `Unknown tool: ${toolName}` }\n }\n\n getBlogContent(): WriteBlogArgs | null {\n return this.blogContent\n }\n}\n\n// ── Render the final markdown ───────────────────────────────────────────────\n\nfunction renderBlogMarkdown(blog: WriteBlogArgs): string {\n const fm = blog.frontmatter\n const tags = fm.tags.map((t) => t.toLowerCase().replace(/[^a-z0-9]/g, '')).join(', ')\n\n const lines: string[] = [\n '---',\n `title: \"${fm.title}\"`,\n 'published: false',\n `description: \"${fm.description}\"`,\n `tags: ${tags}`,\n `cover_image: ${fm.cover_image || ''}`,\n '---',\n '',\n blog.body,\n ]\n\n return lines.join('\\n')\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\nexport async function generateBlogPost(\n video: VideoFile,\n transcript: Transcript,\n summary: VideoSummary,\n model?: string,\n): Promise<string> {\n const agent = new BlogAgent(model)\n\n try {\n const userMessage = [\n '## Video Metadata',\n `- **Title:** ${summary.title}`,\n `- **Slug:** ${video.slug}`,\n `- **Duration:** ${video.duration}s`,\n `- **Recorded:** ${video.createdAt.toISOString().split('T')[0]}`,\n '',\n '## Summary',\n summary.overview,\n '',\n '## Key Topics',\n summary.keyTopics.map((t) => `- ${t}`).join('\\n'),\n '',\n '## Transcript (first 6000 chars)',\n transcript.text.slice(0, 6000),\n ].join('\\n')\n\n await agent.run(userMessage)\n\n const blogContent = agent.getBlogContent()\n if (!blogContent) {\n throw new Error('BlogAgent did not produce any blog content')\n }\n\n const outDir = join(video.videoDir, 'social-posts')\n ensureDirectorySync(outDir)\n\n const outputPath = join(outDir, 'devto.md')\n writeTextFileSync(outputPath, renderBlogMarkdown(blogContent))\n logger.info(`[BlogAgent] Wrote blog post to ${outputPath}`)\n\n return outputPath\n } finally {\n await agent.destroy()\n }\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { writeTextFile, writeJsonFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\n\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../config/logger'\nimport { getConfig } from '../config/environment'\nimport type { VideoFile, Transcript, Chapter } from '../types'\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Format seconds → \"M:SS\" or \"H:MM:SS\" for YouTube timestamps */\nfunction toYouTubeTimestamp(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`\n}\n\n/** Format seconds → \"MM:SS\" for table display */\nfunction fmtTime(seconds: number): string {\n const m = Math.floor(seconds / 60)\n const s = Math.floor(seconds % 60)\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\n\n/** Build a compact transcript block with timestamps for the LLM prompt. */\nfunction buildTranscriptBlock(transcript: Transcript): string {\n return transcript.segments\n .map((seg) => `[${fmtTime(seg.start)} → ${fmtTime(seg.end)}] ${seg.text.trim()}`)\n .join('\\n')\n}\n\n// ── Output format generators ─────────────────────────────────────────────────\n\nfunction generateChaptersJSON(chapters: Chapter[]): string {\n return JSON.stringify({ chapters }, null, 2)\n}\n\nfunction generateYouTubeTimestamps(chapters: Chapter[]): string {\n return chapters\n .map((ch) => `${toYouTubeTimestamp(ch.timestamp)} ${ch.title}`)\n .join('\\n')\n}\n\nfunction generateChaptersMarkdown(chapters: Chapter[]): string {\n const rows = chapters\n .map((ch) => `| ${toYouTubeTimestamp(ch.timestamp)} | ${ch.title} | ${ch.description} |`)\n .join('\\n')\n\n return `## Chapters\n\n| Time | Chapter | Description |\n|------|---------|-------------|\n${rows}\n`\n}\n\nfunction generateFFMetadata(chapters: Chapter[], totalDuration: number): string {\n let meta = ';FFMETADATA1\\n\\n'\n for (let i = 0; i < chapters.length; i++) {\n const ch = chapters[i]\n const startMs = Math.round(ch.timestamp * 1000)\n const endMs = i < chapters.length - 1\n ? Math.round(chapters[i + 1].timestamp * 1000)\n : Math.round(totalDuration * 1000)\n const escapedTitle = ch.title.replace(/[=;#\\\\]/g, '\\\\$&')\n meta += `[CHAPTER]\\nTIMEBASE=1/1000\\nSTART=${startMs}\\nEND=${endMs}\\ntitle=${escapedTitle}\\n\\n`\n }\n return meta\n}\n\n// ── System prompt ────────────────────────────────────────────────────────────\n\nfunction buildChapterSystemPrompt(): string {\n return `You are a video chapter generator. Analyze the transcript and identify distinct topic segments.\n\nRules:\n- First chapter MUST start at 0:00\n- Minimum 3 chapters, maximum 10\n- Each chapter should be 2-5 minutes long\n- Chapter titles should be concise (3-7 words)\n- Look for topic transitions, \"moving on\", \"next\", \"now let's\", etc.\n- Include a brief 1-sentence description per chapter\n\n**Output format:**\nCall the \"generate_chapters\" tool with an array of chapter objects.\nEach chapter: { timestamp (seconds from start), title (short, 3-7 words), description (1-sentence summary) }\n\n**Title style:**\n- Use title case: \"Setting Up the Database\"\n- Be specific: \"Configuring PostgreSQL\" not \"Database Stuff\"\n- Include the action when relevant: \"Building the API Routes\"\n- Keep under 50 characters`\n}\n\n// ── Tool argument shape ──────────────────────────────────────────────────────\n\ninterface GenerateChaptersArgs {\n chapters: Chapter[]\n}\n\n// ── ChapterAgent ─────────────────────────────────────────────────────────────\n\nclass ChapterAgent extends BaseAgent {\n private outputDir: string\n private totalDuration: number\n\n constructor(outputDir: string, totalDuration: number, model?: string) {\n super('ChapterAgent', buildChapterSystemPrompt(), undefined, model)\n this.outputDir = outputDir\n this.totalDuration = totalDuration\n }\n\n private get chaptersDir(): string {\n return join(this.outputDir, 'chapters')\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'generate_chapters',\n description:\n 'Write the identified chapters to disk in all formats. ' +\n 'Provide: chapters (array of { timestamp, title, description }).',\n parameters: {\n type: 'object',\n properties: {\n chapters: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n timestamp: { type: 'number', description: 'Seconds from video start' },\n title: { type: 'string', description: 'Short chapter title (3-7 words)' },\n description: { type: 'string', description: '1-sentence summary' },\n },\n required: ['timestamp', 'title', 'description'],\n },\n },\n },\n required: ['chapters'],\n },\n handler: async (rawArgs: unknown) => {\n const args = rawArgs as GenerateChaptersArgs\n return this.handleGenerateChapters(args)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n switch (toolName) {\n case 'generate_chapters':\n return this.handleGenerateChapters(args as unknown as GenerateChaptersArgs)\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n private async handleGenerateChapters(args: GenerateChaptersArgs): Promise<string> {\n const { chapters } = args\n await ensureDirectory(this.chaptersDir)\n\n // Write all 4 formats in parallel\n await Promise.all([\n writeTextFile(\n join(this.chaptersDir, 'chapters.json'),\n generateChaptersJSON(chapters),\n ),\n writeTextFile(\n join(this.chaptersDir, 'chapters-youtube.txt'),\n generateYouTubeTimestamps(chapters),\n ),\n writeTextFile(\n join(this.chaptersDir, 'chapters.md'),\n generateChaptersMarkdown(chapters),\n ),\n writeTextFile(\n join(this.chaptersDir, 'chapters.ffmetadata'),\n generateFFMetadata(chapters, this.totalDuration),\n ),\n ])\n\n logger.info(`[ChapterAgent] Wrote ${chapters.length} chapters in 4 formats → ${this.chaptersDir}`)\n return `Chapters written: ${chapters.length} chapters in 4 formats to ${this.chaptersDir}`\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Generate chapters for a video recording.\n *\n * 1. Creates a ChapterAgent with a `generate_chapters` tool\n * 2. Builds a prompt containing the full transcript with timestamps\n * 3. Lets the agent analyse the transcript and identify chapter boundaries\n * 4. Returns the array of {@link Chapter} objects\n */\nexport async function generateChapters(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n): Promise<Chapter[]> {\n const config = getConfig()\n const outputDir = join(config.OUTPUT_DIR, video.slug)\n\n const agent = new ChapterAgent(outputDir, video.duration, model)\n const transcriptBlock = buildTranscriptBlock(transcript)\n\n const userPrompt = [\n `**Video:** ${video.filename}`,\n `**Duration:** ${fmtTime(video.duration)} (${Math.round(video.duration)} seconds)`,\n '',\n '---',\n '',\n '**Transcript:**',\n '',\n transcriptBlock,\n ].join('\\n')\n\n let capturedChapters: Chapter[] | undefined\n\n // Intercept generate_chapters args to capture the result\n // Uses `as any` to access private method — required by the intercept-and-capture pattern\n const origHandler = (agent as any).handleGenerateChapters.bind(agent) as (\n a: GenerateChaptersArgs,\n ) => Promise<string>\n ;(agent as any).handleGenerateChapters = async (args: GenerateChaptersArgs) => {\n capturedChapters = args.chapters\n return origHandler(args)\n }\n\n try {\n await agent.run(userPrompt)\n\n if (!capturedChapters) {\n throw new Error('ChapterAgent did not call generate_chapters')\n }\n\n return capturedChapters\n } finally {\n await agent.destroy()\n }\n}\n","import { execCommandSync } from '../core/process.js'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\nexport async function commitAndPush(videoSlug: string, message?: string): Promise<void> {\n const { REPO_ROOT } = getConfig()\n const commitMessage = message || `Auto-processed video: ${videoSlug}`\n\n try {\n logger.info(`Staging all changes in ${REPO_ROOT}`)\n execCommandSync('git add -A', { cwd: REPO_ROOT, stdio: 'pipe' })\n\n logger.info(`Committing: ${commitMessage}`)\n execCommandSync(`git commit -m \"${commitMessage}\"`, { cwd: REPO_ROOT, stdio: 'pipe' })\n\n const branch = execCommandSync('git rev-parse --abbrev-ref HEAD', { cwd: REPO_ROOT, stdio: 'pipe' })\n logger.info(`Pushing to origin ${branch}`)\n execCommandSync(`git push origin ${branch}`, { cwd: REPO_ROOT, stdio: 'pipe' })\n\n logger.info('Git commit and push completed successfully')\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n if (msg.includes('nothing to commit')) {\n logger.info('Nothing to commit, working tree clean')\n return\n }\n logger.error(`Git operation failed: ${msg}`)\n throw error\n }\n}\n\nexport async function stageFiles(patterns: string[]): Promise<void> {\n const { REPO_ROOT } = getConfig()\n\n for (const pattern of patterns) {\n try {\n logger.info(`Staging files matching: ${pattern}`)\n execCommandSync(`git add ${pattern}`, { cwd: REPO_ROOT, stdio: 'pipe' })\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to stage pattern \"${pattern}\": ${msg}`)\n throw error\n }\n }\n}\n","import { Platform } from '../types'\nimport type { VideoPlatform } from '../types'\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport type ClipType = 'video' | 'short' | 'medium-clip'\n\n/** How to resolve media for a specific platform + clip type combination. */\nexport interface MediaRule {\n /** Use the captioned variant when available */\n captions: boolean\n /**\n * Variant key to look up in ShortClip.variants / MediumClip.variants.\n * null = use the captioned/original clip directly (no variant lookup).\n */\n variantKey: VideoPlatform | null\n}\n\n// ============================================================================\n// CONTENT MATRIX\n// ============================================================================\n\n/**\n * Central content matrix — defines what clip types each platform accepts\n * and how to resolve the media file for each.\n *\n * If a platform + clipType combination is NOT listed, that post type is\n * text-only (no media attached).\n *\n * | Platform | video (main) | short | medium-clip |\n * |-----------|---------------------|----------------------|---------------------|\n * | YouTube | original, captioned | 9:16 portrait | original, captioned |\n * | LinkedIn | — | — | original, captioned |\n * | TikTok | — | 9:16 portrait | — |\n * | Instagram | — | 9:16 reels + 4:5 feed| original, captioned |\n * | X/Twitter | — | original, captioned | — |\n *\n * Posts whose clip type has no entry here will still be created but without\n * media (the social-media agents decide which clip types get posts per platform).\n */\nconst CONTENT_MATRIX: Record<Platform, Partial<Record<ClipType, MediaRule>>> = {\n [Platform.YouTube]: {\n video: { captions: true, variantKey: null },\n short: { captions: true, variantKey: 'youtube-shorts' },\n 'medium-clip': { captions: true, variantKey: null },\n },\n [Platform.LinkedIn]: {\n 'medium-clip': { captions: true, variantKey: null },\n },\n [Platform.TikTok]: {\n short: { captions: true, variantKey: 'tiktok' },\n },\n [Platform.Instagram]: {\n short: { captions: true, variantKey: 'instagram-reels' },\n 'medium-clip': { captions: true, variantKey: null },\n },\n [Platform.X]: {\n short: { captions: true, variantKey: null },\n },\n}\n\n// ============================================================================\n// PUBLIC API\n// ============================================================================\n\n/**\n * Get the media rule for a platform + clip type.\n * Returns null if that combination should be text-only.\n */\nexport function getMediaRule(platform: Platform, clipType: ClipType): MediaRule | null {\n return CONTENT_MATRIX[platform]?.[clipType] ?? null\n}\n\n/**\n * Check whether a platform accepts a given clip type (i.e. should attach media).\n */\nexport function platformAcceptsMedia(platform: Platform, clipType: ClipType): boolean {\n return getMediaRule(platform, clipType) !== null\n}\n","import { getConfig } from '../config/environment'\nimport logger from '../config/logger'\nimport { readTextFile, writeTextFile, writeJsonFile, ensureDirectory, copyFile, fileExists, listDirectoryWithTypes, removeDirectory, renameFile, copyDirectory } from '../core/fileSystem.js'\nimport { join, basename, resolve, sep } from '../core/paths.js'\n\nexport interface QueueItemMetadata {\n id: string\n platform: string\n accountId: string\n sourceVideo: string\n sourceClip: string | null\n clipType: 'video' | 'short' | 'medium-clip'\n sourceMediaPath: string | null\n hashtags: string[]\n links: Array<{ url: string; title?: string }>\n characterCount: number\n platformCharLimit: number\n suggestedSlot: string | null\n scheduledFor: string | null\n status: 'pending_review' | 'published'\n latePostId: string | null\n publishedUrl: string | null\n createdAt: string\n reviewedAt: string | null\n publishedAt: string | null\n textOnly?: boolean\n platformSpecificData?: Record<string, unknown>\n}\n\nexport interface QueueItem {\n id: string\n metadata: QueueItemMetadata\n postContent: string\n hasMedia: boolean\n mediaPath: string | null\n folderPath: string\n}\n\nfunction getQueueDir(): string {\n const { OUTPUT_DIR } = getConfig()\n return join(OUTPUT_DIR, 'publish-queue')\n}\n\nfunction getPublishedDir(): string {\n const { OUTPUT_DIR } = getConfig()\n return join(OUTPUT_DIR, 'published')\n}\n\nasync function readQueueItem(folderPath: string, id: string): Promise<QueueItem | null> {\n const metadataPath = join(folderPath, 'metadata.json')\n const postPath = join(folderPath, 'post.md')\n const mediaPath = join(folderPath, 'media.mp4')\n\n try {\n // Read directly without prior existence check to avoid TOCTOU race\n const metadataRaw = await readTextFile(metadataPath)\n const metadata: QueueItemMetadata = JSON.parse(metadataRaw)\n\n let postContent = ''\n try {\n postContent = await readTextFile(postPath)\n } catch {\n logger.debug(`No post.md found for ${String(id).replace(/[\\r\\n]/g, '')}`)\n }\n\n let hasMedia = false\n const mediaFilePath = join(folderPath, 'media.mp4')\n hasMedia = await fileExists(mediaFilePath)\n\n return {\n id,\n metadata,\n postContent,\n hasMedia,\n mediaPath: hasMedia ? mediaFilePath : null,\n folderPath,\n }\n } catch (err) {\n logger.debug(`Failed to read queue item ${String(id).replace(/[\\r\\n]/g, '')}: ${String(err).replace(/[\\r\\n]/g, '')}`)\n return null\n }\n}\n\nexport async function getPendingItems(): Promise<QueueItem[]> {\n const queueDir = getQueueDir()\n await ensureDirectory(queueDir)\n\n let entries: string[]\n try {\n const dirents = await listDirectoryWithTypes(queueDir)\n entries = dirents.filter(d => d.isDirectory()).map(d => d.name)\n } catch {\n return []\n }\n\n const items: QueueItem[] = []\n for (const name of entries) {\n const item = await readQueueItem(join(queueDir, name), name)\n if (item) items.push(item)\n }\n\n // Sort: items with media first (shorts/clips), then text-only (video-level), then by date\n items.sort((a, b) => {\n if (a.hasMedia !== b.hasMedia) return a.hasMedia ? -1 : 1\n return a.metadata.createdAt.localeCompare(b.metadata.createdAt)\n })\n return items\n}\n\nexport async function getItem(id: string): Promise<QueueItem | null> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const folderPath = join(getQueueDir(), basename(id))\n return readQueueItem(folderPath, id)\n}\n\nexport async function createItem(\n id: string,\n metadata: QueueItemMetadata,\n postContent: string,\n mediaSourcePath?: string,\n): Promise<QueueItem> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const folderPath = join(getQueueDir(), basename(id))\n await ensureDirectory(folderPath)\n\n await writeJsonFile(join(folderPath, 'metadata.json'), metadata)\n await writeTextFile(join(folderPath, 'post.md'), postContent)\n\n let hasMedia = false\n const mediaPath = join(folderPath, 'media.mp4')\n\n if (mediaSourcePath) {\n await copyFile(mediaSourcePath, mediaPath)\n hasMedia = true\n }\n\n logger.debug(`Created queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n\n return {\n id,\n metadata,\n postContent,\n hasMedia,\n mediaPath: hasMedia ? mediaPath : null,\n folderPath,\n }\n}\n\nexport async function updateItem(\n id: string,\n updates: { postContent?: string; metadata?: Partial<QueueItemMetadata> },\n): Promise<QueueItem | null> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const existing = await getItem(id)\n if (!existing) return null\n\n if (updates.metadata) {\n // Sanitize metadata by re-constructing with only expected fields before writing\n const sanitized: QueueItemMetadata = {\n id: String(existing.metadata.id),\n platform: String(updates.metadata.platform ?? existing.metadata.platform),\n accountId: String(updates.metadata.accountId ?? existing.metadata.accountId),\n sourceVideo: String(existing.metadata.sourceVideo),\n sourceClip: existing.metadata.sourceClip !== null ? String(existing.metadata.sourceClip) : null,\n clipType: existing.metadata.clipType,\n sourceMediaPath: existing.metadata.sourceMediaPath !== null ? String(existing.metadata.sourceMediaPath) : null,\n hashtags: Array.isArray(updates.metadata.hashtags) ? updates.metadata.hashtags.map(String) : (Array.isArray(existing.metadata.hashtags) ? existing.metadata.hashtags.map(String) : []),\n links: Array.isArray(updates.metadata.links) ? updates.metadata.links : (Array.isArray(existing.metadata.links) ? existing.metadata.links : []),\n characterCount: updates.metadata.characterCount !== undefined ? Number(updates.metadata.characterCount) || 0 : (Number(existing.metadata.characterCount) || 0),\n platformCharLimit: updates.metadata.platformCharLimit !== undefined ? Number(updates.metadata.platformCharLimit) || 0 : (Number(existing.metadata.platformCharLimit) || 0),\n suggestedSlot: updates.metadata.suggestedSlot !== undefined ? (updates.metadata.suggestedSlot !== null ? String(updates.metadata.suggestedSlot) : null) : (existing.metadata.suggestedSlot !== null ? String(existing.metadata.suggestedSlot) : null),\n scheduledFor: updates.metadata.scheduledFor !== undefined ? (updates.metadata.scheduledFor !== null ? String(updates.metadata.scheduledFor) : null) : (existing.metadata.scheduledFor !== null ? String(existing.metadata.scheduledFor) : null),\n status: updates.metadata.status ?? existing.metadata.status,\n latePostId: updates.metadata.latePostId !== undefined ? (updates.metadata.latePostId !== null ? String(updates.metadata.latePostId) : null) : (existing.metadata.latePostId !== null ? String(existing.metadata.latePostId) : null),\n publishedUrl: updates.metadata.publishedUrl !== undefined ? (updates.metadata.publishedUrl !== null ? String(updates.metadata.publishedUrl) : null) : (existing.metadata.publishedUrl !== null ? String(existing.metadata.publishedUrl) : null),\n createdAt: String(existing.metadata.createdAt),\n reviewedAt: updates.metadata.reviewedAt !== undefined ? (updates.metadata.reviewedAt !== null ? String(updates.metadata.reviewedAt) : null) : (existing.metadata.reviewedAt !== null ? String(existing.metadata.reviewedAt) : null),\n publishedAt: updates.metadata.publishedAt !== undefined ? (updates.metadata.publishedAt !== null ? String(updates.metadata.publishedAt) : null) : (existing.metadata.publishedAt !== null ? String(existing.metadata.publishedAt) : null),\n textOnly: updates.metadata.textOnly ?? existing.metadata.textOnly,\n platformSpecificData: updates.metadata.platformSpecificData ?? existing.metadata.platformSpecificData,\n }\n // Use only the sanitized object — do not spread raw HTTP updates (CodeQL js/http-to-file-access)\n existing.metadata = sanitized\n // Validate write target is within the expected queue directory\n const metadataWritePath = resolve(join(existing.folderPath, 'metadata.json'))\n if (!metadataWritePath.startsWith(resolve(getQueueDir()) + sep)) {\n throw new Error('Write target outside queue directory')\n }\n await writeTextFile(\n metadataWritePath,\n JSON.stringify(existing.metadata, null, 2),\n )\n }\n\n if (updates.postContent !== undefined) {\n // Sanitize post content - ensure it's a string\n const sanitizedContent = String(updates.postContent)\n existing.postContent = sanitizedContent\n // Validate write target is within the expected queue directory (CodeQL js/http-to-file-access)\n const postWritePath = resolve(join(existing.folderPath, 'post.md'))\n if (!postWritePath.startsWith(resolve(getQueueDir()) + sep)) {\n throw new Error('Write target outside queue directory')\n }\n // lgtm[js/http-to-file-access] - Writing user-provided post content to queue is intended functionality with path validation\n await writeTextFile(postWritePath, sanitizedContent)\n }\n\n logger.debug(`Updated queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n return existing\n}\n\nexport async function approveItem(\n id: string,\n publishData: { latePostId: string; scheduledFor: string; publishedUrl?: string; accountId?: string },\n): Promise<void> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const item = await getItem(id)\n if (!item) return\n\n const now = new Date().toISOString()\n if (publishData.accountId) {\n item.metadata.accountId = String(publishData.accountId)\n }\n item.metadata.status = 'published'\n item.metadata.latePostId = String(publishData.latePostId)\n item.metadata.scheduledFor = String(publishData.scheduledFor)\n item.metadata.publishedUrl = publishData.publishedUrl ? String(publishData.publishedUrl) : null\n item.metadata.publishedAt = now\n item.metadata.reviewedAt = now\n\n // Sanitize metadata before writing - reconstruct with validated fields\n const sanitizedMetadata: QueueItemMetadata = {\n id: String(item.metadata.id),\n platform: String(item.metadata.platform),\n accountId: String(item.metadata.accountId),\n sourceVideo: String(item.metadata.sourceVideo),\n sourceClip: item.metadata.sourceClip !== null ? String(item.metadata.sourceClip) : null,\n clipType: item.metadata.clipType,\n sourceMediaPath: item.metadata.sourceMediaPath !== null ? String(item.metadata.sourceMediaPath) : null,\n hashtags: Array.isArray(item.metadata.hashtags) ? item.metadata.hashtags.map(String) : [],\n links: Array.isArray(item.metadata.links) ? item.metadata.links : [],\n characterCount: Number(item.metadata.characterCount) || 0,\n platformCharLimit: Number(item.metadata.platformCharLimit) || 0,\n suggestedSlot: item.metadata.suggestedSlot !== null ? String(item.metadata.suggestedSlot) : null,\n scheduledFor: item.metadata.scheduledFor !== null ? String(item.metadata.scheduledFor) : null,\n status: item.metadata.status,\n latePostId: item.metadata.latePostId !== null ? String(item.metadata.latePostId) : null,\n publishedUrl: item.metadata.publishedUrl !== null ? String(item.metadata.publishedUrl) : null,\n createdAt: String(item.metadata.createdAt),\n reviewedAt: item.metadata.reviewedAt !== null ? String(item.metadata.reviewedAt) : null,\n publishedAt: item.metadata.publishedAt !== null ? String(item.metadata.publishedAt) : null,\n textOnly: item.metadata.textOnly,\n platformSpecificData: item.metadata.platformSpecificData,\n }\n\n // Validate write target is within the expected queue directory (CodeQL js/http-to-file-access)\n const approveMetadataPath = resolve(join(item.folderPath, 'metadata.json'))\n if (!approveMetadataPath.startsWith(resolve(getQueueDir()) + sep)) {\n throw new Error('Write target outside queue directory')\n }\n // lgtm[js/http-to-file-access] - Writing sanitized metadata to queue is intended functionality with path validation\n await writeTextFile(\n approveMetadataPath,\n JSON.stringify(sanitizedMetadata, null, 2),\n )\n\n const publishedDir = getPublishedDir()\n await ensureDirectory(publishedDir)\n \n // Validate destination path to prevent path traversal - use basename inline\n const destPath = join(publishedDir, basename(id))\n const resolvedDest = resolve(destPath)\n const resolvedPublishedDir = resolve(publishedDir)\n if (!resolvedDest.startsWith(resolvedPublishedDir + sep) && resolvedDest !== resolvedPublishedDir) {\n throw new Error(`Invalid destination path for item ${id}`)\n }\n\n try {\n await renameFile(item.folderPath, destPath)\n } catch (renameErr: unknown) {\n // On Windows, rename can fail with EPERM if a file handle is still releasing.\n // Fall back to recursive copy + delete.\n const errCode = (renameErr as NodeJS.ErrnoException | null)?.code\n if (errCode === 'EPERM') {\n logger.warn(`rename failed (EPERM) for ${String(id).replace(/[\\r\\n]/g, '')}, falling back to copy+delete`)\n await copyDirectory(item.folderPath, destPath)\n await removeDirectory(item.folderPath, { recursive: true, force: true })\n } else {\n throw renameErr\n }\n }\n\n logger.debug(`Approved and moved queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n}\n\nexport async function rejectItem(id: string): Promise<void> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const folderPath = join(getQueueDir(), basename(id))\n try {\n await removeDirectory(folderPath, { recursive: true })\n logger.debug(`Rejected and deleted queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n } catch (err) {\n logger.debug(`Failed to reject queue item ${String(id).replace(/[\\r\\n]/g, '')}: ${String(err).replace(/[\\r\\n]/g, '')}`)\n }\n}\n\nexport async function getPublishedItems(): Promise<QueueItem[]> {\n const publishedDir = getPublishedDir()\n await ensureDirectory(publishedDir)\n\n let entries: string[]\n try {\n const dirents = await listDirectoryWithTypes(publishedDir)\n entries = dirents.filter(d => d.isDirectory()).map(d => d.name)\n } catch {\n return []\n }\n\n const items: QueueItem[] = []\n for (const name of entries) {\n const item = await readQueueItem(join(publishedDir, name), name)\n if (item) items.push(item)\n }\n\n items.sort((a, b) => a.metadata.createdAt.localeCompare(b.metadata.createdAt))\n return items\n}\n\nexport async function itemExists(id: string): Promise<'pending' | 'published' | null> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n if (await fileExists(join(getQueueDir(), basename(id)))) {\n return 'pending'\n }\n\n if (await fileExists(join(getPublishedDir(), basename(id)))) {\n return 'published'\n }\n\n return null\n}\n","import { readTextFile } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport logger from '../config/logger'\nimport { PLATFORM_CHAR_LIMITS, toLatePlatform } from '../types'\nimport { Platform } from '../types'\nimport type { VideoFile, ShortClip, MediumClip, SocialPost } from '../types'\nimport { getMediaRule, platformAcceptsMedia } from './platformContentStrategy'\nimport type { ClipType } from './platformContentStrategy'\nimport { createItem, itemExists, type QueueItemMetadata } from './postStore'\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface QueueBuildResult {\n itemsCreated: number\n itemsSkipped: number\n errors: string[]\n}\n\n// ============================================================================\n// MEDIA RESOLUTION (driven by platformContentStrategy)\n// ============================================================================\n\n/**\n * Resolve the media file path for a short clip on a given platform.\n * Uses the content strategy's variantKey to find the right variant,\n * then falls back to captionedPath → outputPath.\n */\nfunction resolveShortMedia(clip: ShortClip, platform: Platform): string | null {\n const rule = getMediaRule(platform, 'short')\n if (!rule) return null // platform doesn't accept short media\n\n // If the rule specifies a variant key, look it up\n if (rule.variantKey && clip.variants?.length) {\n const match = clip.variants.find(v => v.platform === rule.variantKey)\n if (match) return match.path\n\n // Instagram fallback: try instagram-feed when instagram-reels missing\n if (platform === Platform.Instagram) {\n const fallback = clip.variants.find(v => v.platform === 'instagram-feed')\n if (fallback) return fallback.path\n }\n }\n\n // Fallback: captioned landscape → original\n return rule.captions\n ? (clip.captionedPath ?? clip.outputPath)\n : clip.outputPath\n}\n\n/**\n * Resolve the media file path for a medium clip on a given platform.\n */\nfunction resolveMediumMedia(clip: MediumClip, platform: Platform): string | null {\n const rule = getMediaRule(platform, 'medium-clip')\n if (!rule) return null // platform doesn't accept medium-clip media\n\n return rule.captions\n ? (clip.captionedPath ?? clip.outputPath)\n : clip.outputPath\n}\n\n/**\n * Resolve the media file path for a video-level post on a given platform.\n */\nfunction resolveVideoMedia(\n video: VideoFile,\n platform: Platform,\n captionedVideoPath: string | undefined,\n): string | null {\n const rule = getMediaRule(platform, 'video')\n if (!rule) return null // platform doesn't accept main-video media\n\n return rule.captions\n ? (captionedVideoPath ?? join(video.videoDir, video.filename))\n : join(video.videoDir, video.filename)\n}\n\n// ============================================================================\n// FRONTMATTER PARSER\n// ============================================================================\n\n/**\n * Parse YAML frontmatter from a post markdown file.\n * Handles simple key: value patterns. Arrays are stored as raw strings (e.g., \"[foo, bar]\").\n * For complex YAML parsing, consider adding a yaml library.\n */\nasync function parsePostFrontmatter(postPath: string): Promise<Record<string, string>> {\n let content: string\n try {\n content = await readTextFile(postPath)\n } catch {\n return {}\n }\n\n const result: Record<string, string> = {}\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/)\n if (!match) return result\n\n const yamlBlock = match[1]\n for (const line of yamlBlock.split(/\\r?\\n/)) {\n const kvMatch = line.match(/^(\\w+):\\s*(.*)$/)\n if (!kvMatch) continue\n\n const key = kvMatch[1]\n let value = kvMatch[2].trim()\n\n // Strip surrounding quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1)\n }\n\n // Treat 'null' string as empty\n if (value === 'null') continue\n\n result[key] = value\n }\n\n return result\n}\n\n// ============================================================================\n// CONTENT EXTRACTOR\n// ============================================================================\n\n/** Strip YAML frontmatter from markdown, returning only the body content. */\nfunction stripFrontmatter(content: string): string {\n return content.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/, '').trim()\n}\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\nexport async function buildPublishQueue(\n video: VideoFile,\n shorts: ShortClip[],\n mediumClips: MediumClip[],\n socialPosts: SocialPost[],\n captionedVideoPath: string | undefined,\n): Promise<QueueBuildResult> {\n const result: QueueBuildResult = { itemsCreated: 0, itemsSkipped: 0, errors: [] }\n\n for (const post of socialPosts) {\n try {\n const latePlatform = toLatePlatform(post.platform)\n const frontmatter = await parsePostFrontmatter(post.outputPath)\n\n let clipSlug: string\n let clipType: ClipType\n let mediaPath: string | null = null\n let sourceClip: string | null = null\n\n if (frontmatter.shortSlug) {\n // Short or medium clip post\n const short = shorts.find(s => s.slug === frontmatter.shortSlug)\n const medium = mediumClips.find(m => m.slug === frontmatter.shortSlug)\n\n if (short) {\n clipSlug = short.slug\n clipType = 'short'\n sourceClip = dirname(short.outputPath)\n mediaPath = resolveShortMedia(short, post.platform)\n } else if (medium) {\n clipSlug = medium.slug\n clipType = 'medium-clip'\n sourceClip = dirname(medium.outputPath)\n mediaPath = resolveMediumMedia(medium, post.platform)\n } else {\n clipSlug = frontmatter.shortSlug\n clipType = 'short'\n logger.warn(`Clip not found for slug: ${frontmatter.shortSlug}`)\n }\n } else {\n // Video-level post (stage 10)\n clipSlug = video.slug\n clipType = 'video'\n mediaPath = resolveVideoMedia(video, post.platform, captionedVideoPath)\n }\n\n // Skip posts for platform+clipType combos not in the content matrix\n if (!platformAcceptsMedia(post.platform, clipType)) {\n logger.debug(`Skipping ${post.platform}/${clipType} — not in content matrix`)\n result.itemsSkipped++\n continue\n }\n\n const itemId = `${clipSlug}-${latePlatform}`\n\n // Idempotency: skip if already published\n const exists = await itemExists(itemId)\n if (exists === 'published') {\n result.itemsSkipped++\n continue\n }\n\n const metadata: QueueItemMetadata = {\n id: itemId,\n platform: latePlatform,\n accountId: '',\n sourceVideo: video.videoDir,\n sourceClip,\n clipType,\n sourceMediaPath: mediaPath,\n hashtags: post.hashtags,\n links: post.links.map(l => typeof l === 'string' ? { url: l } : l),\n characterCount: post.characterCount,\n platformCharLimit: PLATFORM_CHAR_LIMITS[latePlatform] ?? 2200,\n suggestedSlot: null,\n scheduledFor: null,\n status: 'pending_review',\n latePostId: null,\n publishedUrl: null,\n createdAt: new Date().toISOString(),\n reviewedAt: null,\n publishedAt: null,\n }\n\n // Use raw post content (strip frontmatter if the content includes it)\n const stripped = stripFrontmatter(post.content)\n const postContent = stripped.trim().length > 0 ? stripped : post.content\n \n // Validate content exists after stripping frontmatter\n if (postContent.trim().length === 0) {\n throw new Error('Post content is empty after stripping frontmatter')\n }\n\n await createItem(itemId, metadata, postContent, mediaPath ?? undefined)\n result.itemsCreated++\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n result.errors.push(`${post.platform}: ${msg}`)\n logger.error(`Queue builder error for ${post.platform}: ${msg}`)\n }\n }\n\n logger.info(\n `Queue builder: ${result.itemsCreated} created, ${result.itemsSkipped} skipped, ${result.errors.length} errors`,\n )\n return result\n}\n","import { createFFmpeg } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nexport interface SilenceRegion {\n start: number // seconds\n end: number // seconds\n duration: number // seconds\n}\n\n/**\n * Use FFmpeg silencedetect filter to find silence regions in an audio/video file.\n */\nexport async function detectSilence(\n audioPath: string,\n minDuration: number = 1.0,\n noiseThreshold: string = '-30dB',\n): Promise<SilenceRegion[]> {\n logger.info(`Detecting silence in: ${audioPath} (min=${minDuration}s, threshold=${noiseThreshold})`)\n\n return new Promise<SilenceRegion[]>((resolve, reject) => {\n const regions: SilenceRegion[] = []\n let stderr = ''\n\n createFFmpeg(audioPath)\n .audioFilters(`silencedetect=noise=${noiseThreshold}:d=${minDuration}`)\n .format('null')\n .output('-')\n .on('stderr', (line: string) => {\n stderr += line + '\\n'\n })\n .on('end', () => {\n let pendingStart: number | null = null\n\n for (const line of stderr.split('\\n')) {\n const startMatch = line.match(/silence_start:\\s*([\\d.]+)/)\n if (startMatch) {\n pendingStart = parseFloat(startMatch[1])\n }\n\n const endMatch = line.match(/silence_end:\\s*([\\d.]+)\\s*\\|\\s*silence_duration:\\s*([\\d.]+)/)\n if (endMatch) {\n const end = parseFloat(endMatch[1])\n const duration = parseFloat(endMatch[2])\n // When silence starts at t=0, FFmpeg emits silence_end before any silence_start\n const start = pendingStart ?? Math.max(0, end - duration)\n\n regions.push({ start, end, duration })\n pendingStart = null\n }\n }\n\n const badRegions = regions.filter(r => r.end <= r.start)\n if (badRegions.length > 0) {\n logger.warn(`[SilenceDetect] Found ${badRegions.length} invalid regions (end <= start) — filtering out`)\n }\n const validRegions = regions.filter(r => r.end > r.start)\n\n if (validRegions.length > 0) {\n logger.info(`Sample silence regions: ${validRegions.slice(0, 3).map(r => `${r.start.toFixed(1)}s-${r.end.toFixed(1)}s (${r.duration.toFixed(2)}s)`).join(', ')}`)\n }\n logger.info(`Detected ${validRegions.length} silence regions`)\n resolve(validRegions)\n })\n .on('error', (err) => {\n logger.error(`Silence detection failed: ${err.message}`)\n reject(new Error(`Silence detection failed: ${err.message}`))\n })\n .run()\n })\n}\n","import { execFileRaw } from '../../core/process.js'\nimport { copyFile, listDirectory, removeFile, removeDirectory, makeTempDir } from '../../core/fileSystem.js'\nimport { join, fontsDir } from '../../core/paths.js'\nimport { getFFmpegPath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nconst ffmpegPath = getFFmpegPath()\nconst FONTS_DIR = fontsDir()\n\nexport interface KeepSegment {\n start: number\n end: number\n}\n\n/**\n * Build FFmpeg filter_complex string for silence removal.\n * Pure function — no I/O, easy to test.\n */\nexport function buildFilterComplex(\n keepSegments: KeepSegment[],\n options?: { assFilename?: string; fontsdir?: string },\n): string {\n if (keepSegments.length === 0) {\n throw new Error('keepSegments must not be empty')\n }\n\n const filterParts: string[] = []\n const concatInputs: string[] = []\n const hasCaptions = options?.assFilename\n\n for (let i = 0; i < keepSegments.length; i++) {\n const seg = keepSegments[i]\n filterParts.push(\n `[0:v]trim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},setpts=PTS-STARTPTS[v${i}]`,\n )\n filterParts.push(\n `[0:a]atrim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},asetpts=PTS-STARTPTS[a${i}]`,\n )\n concatInputs.push(`[v${i}][a${i}]`)\n }\n\n const concatOutV = hasCaptions ? '[cv]' : '[outv]'\n const concatOutA = hasCaptions ? '[ca]' : '[outa]'\n\n filterParts.push(\n `${concatInputs.join('')}concat=n=${keepSegments.length}:v=1:a=1${concatOutV}${concatOutA}`,\n )\n\n if (hasCaptions) {\n const fontsdir = options?.fontsdir ?? '.'\n filterParts.push(`[cv]ass=${options!.assFilename}:fontsdir=${fontsdir}[outv]`)\n }\n\n return filterParts.join(';\\n')\n}\n\n/**\n * Single-pass silence removal using FFmpeg filter_complex.\n * Uses trim+setpts+concat for frame-accurate cuts instead of -c copy which\n * snaps to keyframes and causes cumulative timestamp drift.\n */\nexport async function singlePassEdit(\n inputPath: string,\n keepSegments: KeepSegment[],\n outputPath: string,\n): Promise<string> {\n const filterComplex = buildFilterComplex(keepSegments)\n\n const args = [\n '-y',\n '-i', inputPath,\n '-filter_complex', filterComplex,\n '-map', '[outv]',\n '-map', '[outa]',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n '-c:a', 'aac',\n '-b:a', '128k',\n outputPath,\n ]\n\n logger.info(`[SinglePassEdit] Editing ${keepSegments.length} segments → ${outputPath}`)\n\n return new Promise((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`)\n reject(new Error(`Single-pass edit failed: ${error.message}`))\n return\n }\n logger.info(`[SinglePassEdit] Complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n\n/**\n * Single-pass silence removal + caption burning using FFmpeg filter_complex.\n * Uses trim+setpts+concat for frame-accurate cuts, then chains ass filter for captions.\n * One re-encode, perfect timestamp alignment.\n */\nexport async function singlePassEditAndCaption(\n inputPath: string,\n keepSegments: KeepSegment[],\n assPath: string,\n outputPath: string,\n): Promise<string> {\n // Copy ASS + bundled fonts to temp dir to avoid Windows drive colon issue\n const tempDir = await makeTempDir('caption-')\n const tempAss = join(tempDir, 'captions.ass')\n await copyFile(assPath, tempAss)\n\n let fontFiles: string[]\n try {\n fontFiles = await listDirectory(FONTS_DIR)\n } catch (err: any) {\n if (err?.code === 'ENOENT') {\n throw new Error(`Fonts directory not found at ${FONTS_DIR}. Ensure assets/fonts/ exists in the project root.`)\n }\n throw err\n }\n for (const f of fontFiles) {\n if (f.endsWith('.ttf') || f.endsWith('.otf')) {\n await copyFile(join(FONTS_DIR, f), join(tempDir, f))\n }\n }\n\n const filterComplex = buildFilterComplex(keepSegments, {\n assFilename: 'captions.ass',\n fontsdir: '.',\n })\n\n const args = [\n '-y',\n '-i', inputPath,\n '-filter_complex', filterComplex,\n '-map', '[outv]',\n '-map', '[ca]',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n '-c:a', 'aac',\n '-b:a', '128k',\n outputPath,\n ]\n\n logger.info(`[SinglePassEdit] Processing ${keepSegments.length} segments with captions → ${outputPath}`)\n\n return new Promise((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { cwd: tempDir, maxBuffer: 50 * 1024 * 1024 }, async (error, _stdout, stderr) => {\n // Cleanup temp\n const files = await listDirectory(tempDir).catch(() => [] as string[])\n for (const f of files) {\n await removeFile(join(tempDir, f)).catch(() => {})\n }\n await removeDirectory(tempDir).catch(() => {})\n\n if (error) {\n logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`)\n reject(new Error(`Single-pass edit failed: ${error.message}`))\n return\n }\n logger.info(`[SinglePassEdit] Complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n","import { ffprobe } from '../core/ffmpeg.js'\nimport type { ToolWithHandler } from '../providers/types.js'\nimport { join } from '../core/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport { detectSilence, SilenceRegion } from '../tools/ffmpeg/silenceDetection'\nimport { singlePassEdit } from '../tools/ffmpeg/singlePassEdit'\nimport type { VideoFile, Transcript, SilenceRemovalResult } from '../types'\nimport logger from '../config/logger'\n\n// ── Types for the LLM's decide_removals tool call ──────────────────────────\n\ninterface RemovalDecision {\n start: number\n end: number\n reason: string\n}\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a video editor AI that decides which silent regions in a video should be removed.\nYou will receive a transcript with timestamps and a list of detected silence regions.\n\nBe CONSERVATIVE. Only remove silence that is CLEARLY dead air — no speech, no demonstration, no purpose.\nAim to remove no more than 10-15% of total video duration.\nWhen in doubt, KEEP the silence.\n\nKEEP silences that are:\n- Dramatic pauses after impactful statements\n- Brief thinking pauses (< 2 seconds) in natural speech\n- Pauses before important reveals or demonstrations\n- Pauses where the speaker is clearly showing something on screen\n- Silence during screen demonstrations or typing — the viewer is watching the screen\n\nREMOVE silences that are:\n- Dead air with no purpose (> 3 seconds of nothing)\n- Gaps between topics where the speaker was gathering thoughts\n- Silence at the very beginning or end of the video\n\nReturn a JSON array of silence regions to REMOVE (not keep).\nWhen you have decided, call the **decide_removals** tool with your removal list.`\n\n// ── JSON Schema for the decide_removals tool ────────────────────────────────\n\nconst DECIDE_REMOVALS_SCHEMA = {\n type: 'object',\n properties: {\n removals: {\n type: 'array',\n description: 'Array of silence regions to remove',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds' },\n end: { type: 'number', description: 'End time in seconds' },\n reason: { type: 'string', description: 'Why this silence should be removed' },\n },\n required: ['start', 'end', 'reason'],\n },\n },\n },\n required: ['removals'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass SilenceRemovalAgent extends BaseAgent {\n private removals: RemovalDecision[] = []\n\n constructor(model?: string) {\n super('SilenceRemovalAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'decide_removals',\n description:\n 'Submit the list of silence regions to remove. Call this once with all removal decisions.',\n parameters: DECIDE_REMOVALS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('decide_removals', args as Record<string, unknown>)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (toolName === 'decide_removals') {\n this.removals = args.removals as RemovalDecision[]\n logger.info(`[SilenceRemovalAgent] Decided to remove ${this.removals.length} silence regions`)\n return { success: true, count: this.removals.length }\n }\n throw new Error(`Unknown tool: ${toolName}`)\n }\n\n getRemovals(): RemovalDecision[] {\n return this.removals\n }\n}\n\n// ── FFmpeg helpers ───────────────────────────────────────────────────────────\n\nasync function getVideoDuration(videoPath: string): Promise<number> {\n try {\n const metadata = await ffprobe(videoPath)\n return metadata.format.duration ?? 0\n } catch (err) {\n throw new Error(`ffprobe failed: ${(err as Error).message}`)\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Detect silence, use the agent to decide context-aware removals, and produce\n * an edited video with dead silence removed.\n *\n * Returns the path to the edited video, or the original path if no edits were needed.\n */\nexport async function removeDeadSilence(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n): Promise<SilenceRemovalResult> {\n const noEdit: SilenceRemovalResult = { editedPath: video.repoPath, removals: [], keepSegments: [], wasEdited: false }\n\n // 1. Detect silence regions (FFmpeg already filters to >= 0.5s via d=0.5)\n const silenceRegions = await detectSilence(video.repoPath, 0.5)\n\n if (silenceRegions.length === 0) {\n logger.info('[SilenceRemoval] No silence regions detected — skipping')\n return noEdit\n }\n\n const totalSilence = silenceRegions.reduce((sum, r) => sum + r.duration, 0)\n logger.info(`[SilenceRemoval] ${silenceRegions.length} silence regions detected (${totalSilence.toFixed(1)}s total silence)`)\n\n // Only send silence regions >= 2s to the agent; short pauses are natural speech rhythm\n let regionsForAgent = silenceRegions.filter(r => r.duration >= 2)\n if (regionsForAgent.length === 0) {\n logger.info('[SilenceRemoval] No silence regions >= 2s — skipping')\n return noEdit\n }\n\n // Cap at 30 longest regions to fit in context window\n if (regionsForAgent.length > 30) {\n regionsForAgent = [...regionsForAgent].sort((a, b) => b.duration - a.duration).slice(0, 30)\n regionsForAgent.sort((a, b) => a.start - b.start) // restore chronological order\n logger.info(`[SilenceRemoval] Capped to top 30 longest regions for agent analysis`)\n }\n\n // 2. Run the agent to decide which silences to remove\n const agent = new SilenceRemovalAgent(model)\n\n const transcriptLines = transcript.segments.map(\n (seg) => `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}`,\n )\n\n const silenceLines = regionsForAgent.map(\n (r, i) => `${i + 1}. ${r.start.toFixed(2)}s – ${r.end.toFixed(2)}s (${r.duration.toFixed(2)}s)`,\n )\n\n const prompt = [\n `Video: ${video.filename} (${transcript.duration.toFixed(1)}s total)\\n`,\n '--- TRANSCRIPT ---\\n',\n transcriptLines.join('\\n'),\n '\\n--- END TRANSCRIPT ---\\n',\n '--- SILENCE REGIONS ---\\n',\n silenceLines.join('\\n'),\n '\\n--- END SILENCE REGIONS ---\\n',\n 'Analyze the context around each silence region and decide which to remove.',\n ].join('\\n')\n\n let removals: RemovalDecision[]\n try {\n await agent.run(prompt)\n removals = agent.getRemovals()\n } finally {\n await agent.destroy()\n }\n\n if (removals.length === 0) {\n logger.info('[SilenceRemoval] Agent decided to keep all silences — skipping edit')\n return noEdit\n }\n\n // Safety: cap removals at 20% of video duration\n const maxRemoval = transcript.duration * 0.20\n let totalRemoval = 0\n const cappedRemovals: RemovalDecision[] = []\n const byDuration = [...removals].sort((a, b) => (b.end - b.start) - (a.end - a.start))\n for (const r of byDuration) {\n const dur = r.end - r.start\n if (totalRemoval + dur <= maxRemoval) {\n cappedRemovals.push(r)\n totalRemoval += dur\n }\n }\n if (cappedRemovals.length < removals.length) {\n logger.warn(`[SilenceRemoval] Capped from ${removals.length} to ${cappedRemovals.length} regions (${totalRemoval.toFixed(1)}s) to stay under 20% threshold`)\n }\n removals = cappedRemovals\n\n if (removals.length === 0) {\n logger.info('[SilenceRemoval] All removals exceeded 20% cap — skipping edit')\n return noEdit\n }\n\n // 3. Build list of segments to KEEP (inverse of removal regions)\n const videoDuration = await getVideoDuration(video.repoPath)\n const sortedRemovals = [...removals].sort((a, b) => a.start - b.start)\n\n const keepSegments: { start: number; end: number }[] = []\n let cursor = 0\n\n for (const removal of sortedRemovals) {\n if (removal.start > cursor) {\n keepSegments.push({ start: cursor, end: removal.start })\n }\n cursor = Math.max(cursor, removal.end)\n }\n\n if (cursor < videoDuration) {\n keepSegments.push({ start: cursor, end: videoDuration })\n }\n\n if (keepSegments.length === 0) {\n logger.warn('[SilenceRemoval] No segments to keep — returning original')\n return noEdit\n }\n\n // 4. Single-pass re-encode with trim+setpts+concat for frame-accurate cuts\n const editedPath = join(video.videoDir, `${video.slug}-edited.mp4`)\n await singlePassEdit(video.repoPath, keepSegments, editedPath)\n\n // Compute effective removals (merged, non-overlapping) from keep-segments\n const effectiveRemovals: { start: number; end: number }[] = []\n let prevEnd = 0\n for (const seg of keepSegments) {\n if (seg.start > prevEnd) {\n effectiveRemovals.push({ start: prevEnd, end: seg.start })\n }\n prevEnd = seg.end\n }\n // Don't add trailing silence as a \"removal\" — it's just the end of the video\n\n const actualRemoved = effectiveRemovals.reduce((sum, r) => sum + (r.end - r.start), 0)\n logger.info(\n `[SilenceRemoval] Removed ${effectiveRemovals.length} silence regions (${actualRemoved.toFixed(1)}s). Output: ${editedPath}`,\n )\n\n return {\n editedPath,\n removals: effectiveRemovals,\n keepSegments,\n wasEdited: true,\n }\n}\n","import { join, dirname, basename } from './core/paths.js'\nimport { ensureDirectory, writeJsonFile, writeTextFile, copyFile, removeFile } from './core/fileSystem.js'\nimport logger from './config/logger'\nimport { getConfig } from './config/environment'\nimport { ingestVideo } from './services/videoIngestion'\nimport { transcribeVideo } from './services/transcription'\nimport { generateCaptions } from './services/captionGeneration'\nimport { generateSummary } from './agents/SummaryAgent'\nimport { generateShorts } from './agents/ShortsAgent'\nimport { generateMediumClips } from './agents/MediumVideoAgent'\nimport { generateSocialPosts, generateShortPosts } from './agents/SocialMediaAgent'\nimport { generateBlogPost } from './agents/BlogAgent'\nimport { generateChapters } from './agents/ChapterAgent'\nimport { commitAndPush } from './services/gitOperations'\nimport { buildPublishQueue } from './services/queueBuilder'\nimport type { QueueBuildResult } from './services/queueBuilder'\nimport { removeDeadSilence } from './agents/SilenceRemovalAgent'\nimport { burnCaptions } from './tools/ffmpeg/captionBurning'\nimport { singlePassEditAndCaption } from './tools/ffmpeg/singlePassEdit'\nimport { getModelForAgent } from './config/modelConfig.js'\nimport { costTracker } from './services/costTracker.js'\nimport type { CostReport } from './services/costTracker.js'\nimport type {\n VideoFile,\n Transcript,\n VideoSummary,\n ShortClip,\n MediumClip,\n SocialPost,\n StageResult,\n PipelineResult,\n PipelineStage,\n SilenceRemovalResult,\n Chapter,\n} from './types'\nimport { PipelineStage as Stage } from './types'\n\n/**\n * Execute a single pipeline stage with error isolation and timing.\n *\n * ### Stage contract\n * - Each stage is wrapped in a try/catch so a failure **does not abort** the\n * pipeline. Subsequent stages proceed with whatever data is available.\n * - Returns `undefined` on failure (callers must null-check before using the result).\n * - Records success/failure, error message, and wall-clock duration in `stageResults`\n * for the pipeline summary.\n *\n * This design lets the pipeline produce partial results — e.g. if shorts\n * generation fails, the summary and social posts can still be generated\n * from the transcript.\n *\n * @param stageName - Enum value identifying the stage (used in logs and results)\n * @param fn - Async function that performs the stage's work\n * @param stageResults - Mutable array that accumulates per-stage outcome records\n * @returns The stage result on success, or `undefined` on failure\n */\nexport async function runStage<T>(\n stageName: PipelineStage,\n fn: () => Promise<T>,\n stageResults: StageResult[],\n): Promise<T | undefined> {\n costTracker.setStage(stageName)\n const start = Date.now()\n try {\n const result = await fn()\n const duration = Date.now() - start\n stageResults.push({ stage: stageName, success: true, duration })\n logger.info(`Stage ${stageName} completed in ${duration}ms`)\n return result\n } catch (err: unknown) {\n const duration = Date.now() - start\n const message = err instanceof Error ? err.message : String(err)\n stageResults.push({ stage: stageName, success: false, error: message, duration })\n logger.error(`Stage ${stageName} failed after ${duration}ms: ${message}`)\n return undefined\n }\n}\n\n/**\n * Adjust transcript timestamps to account for removed silence segments.\n * Shifts all timestamps by subtracting the cumulative removed duration before each point.\n */\nexport function adjustTranscript(\n transcript: Transcript,\n removals: { start: number; end: number }[],\n): Transcript {\n const sorted = [...removals].sort((a, b) => a.start - b.start)\n\n function adjustTime(t: number): number {\n let offset = 0\n for (const r of sorted) {\n if (t <= r.start) break\n if (t >= r.end) {\n offset += r.end - r.start\n } else {\n // timestamp is inside a removed region — snap to removal start\n offset += t - r.start\n }\n }\n return t - offset\n }\n\n return {\n ...transcript,\n duration: adjustTime(transcript.duration),\n segments: transcript.segments\n .filter(seg => !sorted.some(r => seg.start >= r.start && seg.end <= r.end))\n .map(seg => ({\n ...seg,\n start: adjustTime(seg.start),\n end: adjustTime(seg.end),\n })),\n words: transcript.words\n .filter(w => !sorted.some(r => w.start >= r.start && w.end <= r.end))\n .map(w => ({\n ...w,\n start: adjustTime(w.start),\n end: adjustTime(w.end),\n })),\n }\n}\n\n/**\n * Run the full video processing pipeline.\n *\n * ### Stage ordering and data flow\n * 1. **Ingest** — extracts metadata (slug, duration, paths). Required; aborts if failed.\n * 2. **Transcribe** — Whisper transcription with word-level timestamps.\n * 3. **Silence removal** — trims dead air from the video and adjusts the transcript\n * timestamps accordingly. Produces an `adjustedTranscript` for captions.\n * 4. **Captions** — generates SRT/VTT/ASS files from the (adjusted) transcript.\n * 5. **Caption burn** — renders captions into the video using FFmpeg. Prefers a\n * single-pass approach (silence removal + captions in one encode) when possible.\n * 6. **Shorts** — AI-selected short clips. Uses the **original** transcript because\n * clips are cut from the original (unedited) video.\n * 7. **Medium clips** — longer AI-selected clips (same original-transcript reasoning).\n * 8. **Chapters** — topic-boundary detection for YouTube chapters.\n * 9. **Summary** — README generation (runs after shorts/chapters so it can reference them).\n * 10–12. **Social posts** — platform-specific posts for the full video and each clip.\n * 13. **Queue build** — populates publish-queue/ for review before publishing.\n * 14. **Blog** — long-form blog post from transcript + summary.\n * 15. **Git push** — commits all generated assets and pushes.\n *\n * ### Why failures don't abort\n * Each stage runs through {@link runStage} which catches errors. This means a\n * transcription failure still lets git-push run (committing whatever was produced),\n * and a shorts failure doesn't block summary generation.\n */\nexport async function processVideo(videoPath: string): Promise<PipelineResult> {\n const pipelineStart = Date.now()\n const stageResults: StageResult[] = []\n const cfg = getConfig()\n\n costTracker.reset()\n logger.info(`Pipeline starting for: ${videoPath}`)\n\n // 1. Ingestion — required for all subsequent stages\n const video = await runStage<VideoFile>(Stage.Ingestion, () => ingestVideo(videoPath), stageResults)\n if (!video) {\n const totalDuration = Date.now() - pipelineStart\n logger.error('Ingestion failed — cannot proceed without video metadata')\n return { video: { originalPath: videoPath, repoPath: '', videoDir: '', slug: '', filename: '', duration: 0, size: 0, createdAt: new Date() }, transcript: undefined, editedVideoPath: undefined, captions: undefined, captionedVideoPath: undefined, summary: undefined, shorts: [], mediumClips: [], socialPosts: [], blogPost: undefined, stageResults, totalDuration }\n }\n\n // 2. Transcription\n let transcript: Transcript | undefined\n transcript = await runStage<Transcript>(Stage.Transcription, () => transcribeVideo(video), stageResults)\n\n // 3. Silence Removal (context-aware)\n let editedVideoPath: string | undefined\n let adjustedTranscript: Transcript | undefined\n let silenceRemovals: { start: number; end: number }[] = []\n let silenceKeepSegments: { start: number; end: number }[] | undefined\n\n if (transcript && !cfg.SKIP_SILENCE_REMOVAL) {\n const result = await runStage<SilenceRemovalResult>(Stage.SilenceRemoval, () => removeDeadSilence(video, transcript!, getModelForAgent('SilenceRemovalAgent')), stageResults)\n if (result && result.wasEdited) {\n editedVideoPath = result.editedPath\n silenceRemovals = result.removals\n silenceKeepSegments = result.keepSegments\n adjustedTranscript = adjustTranscript(transcript, silenceRemovals)\n\n // Validate: check that adjusted transcript duration is close to edited video duration\n const totalRemoved = silenceRemovals.reduce((sum, r) => sum + (r.end - r.start), 0)\n const expectedDuration = transcript.duration - totalRemoved\n const adjustedDuration = adjustedTranscript.duration\n const drift = Math.abs(expectedDuration - adjustedDuration)\n logger.info(`[Pipeline] Silence removal: original=${transcript.duration.toFixed(1)}s, removed=${totalRemoved.toFixed(1)}s, expected=${expectedDuration.toFixed(1)}s, adjusted=${adjustedDuration.toFixed(1)}s, drift=${drift.toFixed(1)}s`)\n\n await writeJsonFile(\n join(video.videoDir, 'transcript-edited.json'),\n adjustedTranscript,\n )\n }\n }\n\n // Use adjusted transcript for captions (if silence was removed), original otherwise\n const captionTranscript = adjustedTranscript ?? transcript\n\n // 4. Captions (fast, no AI needed) — generate from the right transcript\n let captions: string[] | undefined\n if (captionTranscript && !cfg.SKIP_CAPTIONS) {\n captions = await runStage<string[]>(Stage.Captions, () => generateCaptions(video, captionTranscript), stageResults)\n }\n\n // 5. Caption Burn — use single-pass (silence removal + captions) when possible\n let captionedVideoPath: string | undefined\n if (captions && !cfg.SKIP_CAPTIONS) {\n const assFile = captions.find((p) => p.endsWith('.ass'))\n if (assFile && silenceKeepSegments) {\n // Single-pass: re-do silence removal + burn captions from ORIGINAL video in one encode\n // This guarantees frame-accurate cuts with perfectly aligned captions\n const captionedOutput = join(video.videoDir, `${video.slug}-captioned.mp4`)\n captionedVideoPath = await runStage<string>(\n Stage.CaptionBurn,\n () => singlePassEditAndCaption(video.repoPath, silenceKeepSegments!, assFile, captionedOutput),\n stageResults,\n )\n } else if (assFile) {\n // No silence removal — just burn captions into original video\n const videoToBurn = editedVideoPath ?? video.repoPath\n const captionedOutput = join(video.videoDir, `${video.slug}-captioned.mp4`)\n captionedVideoPath = await runStage<string>(\n Stage.CaptionBurn,\n () => burnCaptions(videoToBurn, assFile, captionedOutput),\n stageResults,\n )\n }\n }\n\n // 6. Shorts — use ORIGINAL transcript (shorts reference original video timestamps)\n let shorts: ShortClip[] = []\n if (transcript && !cfg.SKIP_SHORTS) {\n const result = await runStage<ShortClip[]>(Stage.Shorts, () => generateShorts(video, transcript, getModelForAgent('ShortsAgent')), stageResults)\n if (result) shorts = result\n }\n\n // 7. Medium Clips — use ORIGINAL transcript (medium clips reference original video timestamps)\n let mediumClips: MediumClip[] = []\n if (transcript && !cfg.SKIP_MEDIUM_CLIPS) {\n const result = await runStage<MediumClip[]>(Stage.MediumClips, () => generateMediumClips(video, transcript, getModelForAgent('MediumVideoAgent')), stageResults)\n if (result) mediumClips = result\n }\n\n // 8. Chapters — analyse transcript for topic boundaries\n let chapters: Chapter[] | undefined\n if (transcript) {\n chapters = await runStage<Chapter[]>(Stage.Chapters, () => generateChapters(video, transcript, getModelForAgent('ChapterAgent')), stageResults)\n }\n\n // 9. Summary (after shorts, medium clips, and chapters so the README can reference them)\n let summary: VideoSummary | undefined\n if (transcript) {\n summary = await runStage<VideoSummary>(Stage.Summary, () => generateSummary(video, transcript, shorts, chapters, getModelForAgent('SummaryAgent')), stageResults)\n }\n\n // 10. Social Media\n let socialPosts: SocialPost[] = []\n if (transcript && summary && !cfg.SKIP_SOCIAL) {\n const result = await runStage<SocialPost[]>(\n Stage.SocialMedia,\n () => generateSocialPosts(video, transcript, summary, join(video.videoDir, 'social-posts'), getModelForAgent('SocialMediaAgent')),\n stageResults,\n )\n if (result) socialPosts = result\n }\n\n // 11. Short Posts — generate social posts per short clip\n if (transcript && shorts.length > 0 && !cfg.SKIP_SOCIAL) {\n await runStage<void>(\n Stage.ShortPosts,\n async () => {\n for (const short of shorts) {\n const posts = await generateShortPosts(video, short, transcript, getModelForAgent('ShortPostsAgent'))\n socialPosts.push(...posts)\n }\n },\n stageResults,\n )\n }\n\n // 12. Medium Clip Posts — generate social posts per medium clip\n if (transcript && mediumClips.length > 0 && !cfg.SKIP_SOCIAL) {\n await runStage<void>(\n Stage.MediumClipPosts,\n async () => {\n for (const clip of mediumClips) {\n const asShortClip: ShortClip = {\n id: clip.id,\n title: clip.title,\n slug: clip.slug,\n segments: clip.segments,\n totalDuration: clip.totalDuration,\n outputPath: clip.outputPath,\n captionedPath: clip.captionedPath,\n description: clip.description,\n tags: clip.tags,\n }\n const posts = await generateShortPosts(video, asShortClip, transcript, getModelForAgent('MediumClipPostsAgent'))\n // Move posts to medium-clips/{slug}/posts/\n const clipsDir = join(dirname(video.repoPath), 'medium-clips')\n const postsDir = join(clipsDir, clip.slug, 'posts')\n await ensureDirectory(postsDir)\n for (const post of posts) {\n const destPath = join(postsDir, basename(post.outputPath))\n await copyFile(post.outputPath, destPath)\n await removeFile(post.outputPath)\n post.outputPath = destPath\n }\n socialPosts.push(...posts)\n }\n },\n stageResults,\n )\n }\n\n // 13. Queue Build — populate publish-queue/ for review\n if (socialPosts.length > 0 && !cfg.SKIP_SOCIAL_PUBLISH) {\n await runStage<QueueBuildResult>(\n Stage.QueueBuild,\n () => buildPublishQueue(video, shorts, mediumClips, socialPosts, captionedVideoPath),\n stageResults,\n )\n }\n\n // 14. Blog Post\n let blogPost: string | undefined\n if (transcript && summary) {\n blogPost = await runStage<string>(\n Stage.Blog,\n () => generateBlogPost(video, transcript, summary, getModelForAgent('BlogAgent')),\n stageResults,\n )\n }\n\n // 15. Git\n if (!cfg.SKIP_GIT) {\n await runStage<void>(Stage.GitPush, () => commitAndPush(video.slug), stageResults)\n }\n\n const totalDuration = Date.now() - pipelineStart\n\n // Cost tracking report\n const report = costTracker.getReport()\n if (report.records.length > 0) {\n logger.info(costTracker.formatReport())\n const costMd = generateCostMarkdown(report)\n const costPath = join(video.videoDir, 'cost-report.md')\n await writeTextFile(costPath, costMd)\n logger.info(`Cost report saved: ${costPath}`)\n }\n\n logger.info(`Pipeline completed in ${totalDuration}ms`)\n\n return {\n video,\n transcript,\n editedVideoPath,\n captions,\n captionedVideoPath,\n summary,\n chapters,\n shorts,\n mediumClips,\n socialPosts,\n blogPost,\n stageResults,\n totalDuration,\n }\n}\n\nfunction generateCostMarkdown(report: CostReport): string {\n let md = '# Pipeline Cost Report\\n\\n'\n md += `| Metric | Value |\\n|--------|-------|\\n`\n md += `| Total Cost | $${report.totalCostUSD.toFixed(4)} USD |\\n`\n if (report.totalPRUs > 0) md += `| Total PRUs | ${report.totalPRUs} |\\n`\n md += `| Input Tokens | ${report.totalTokens.input.toLocaleString()} |\\n`\n md += `| Output Tokens | ${report.totalTokens.output.toLocaleString()} |\\n`\n md += `| LLM Calls | ${report.records.length} |\\n`\n if (report.totalServiceCostUSD > 0) md += `| Service Costs | $${report.totalServiceCostUSD.toFixed(4)} USD |\\n`\n md += '\\n'\n\n if (Object.keys(report.byAgent).length > 0) {\n md += '## By Agent\\n\\n| Agent | Cost | PRUs | Calls |\\n|-------|------|------|-------|\\n'\n for (const [agent, data] of Object.entries(report.byAgent)) {\n md += `| ${agent} | $${data.costUSD.toFixed(4)} | ${data.prus} | ${data.calls} |\\n`\n }\n md += '\\n'\n }\n\n if (Object.keys(report.byModel).length > 1) {\n md += '## By Model\\n\\n| Model | Cost | PRUs | Calls |\\n|-------|------|------|-------|\\n'\n for (const [model, data] of Object.entries(report.byModel)) {\n md += `| ${model} | $${data.costUSD.toFixed(4)} | ${data.prus} | ${data.calls} |\\n`\n }\n md += '\\n'\n }\n\n if (Object.keys(report.byService).length > 0) {\n md += '## By Service\\n\\n| Service | Cost | Calls |\\n|---------|------|-------|\\n'\n for (const [service, data] of Object.entries(report.byService)) {\n md += `| ${service} | $${data.costUSD.toFixed(4)} | ${data.calls} |\\n`\n }\n md += '\\n'\n }\n\n return md\n}\n\nexport async function processVideoSafe(videoPath: string): Promise<PipelineResult | null> {\n try {\n return await processVideo(videoPath)\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`Pipeline failed with uncaught error: ${message}`)\n return null\n }\n}\n","export { Readable } from 'stream'\n","/**\n * Media Upload Flow (verified via live testing 2026-02-09):\n * - Step 1: POST /media/presign { filename, contentType } → { uploadUrl, publicUrl, key, expiresIn }\n * - Step 2: PUT file bytes to uploadUrl (presigned Cloudflare R2 URL) with Content-Type header\n * - Step 3: Use publicUrl (https://media.getlate.dev/temp/...) in createPost({ mediaItems: [{ type, url }] })\n *\n * Notes:\n * - The old POST /media/upload endpoint exists but requires an \"upload token\" (not an API key).\n * It is likely used internally by Late's web UI; the presign flow is the correct API approach.\n * - Presigned URLs expire in 3600s (1 hour).\n * - Public URLs are served from media.getlate.dev CDN and are immediately accessible after PUT.\n * - No confirmation step is needed after uploading to the presigned URL.\n */\nimport { getConfig } from '../config/environment.js'\nimport logger from '../config/logger.js'\nimport { getFileStats, openReadStream } from '../core/fileSystem.js'\nimport { Readable } from '../core/network.js'\nimport { basename, extname } from '../core/paths.js'\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface LateAccount {\n _id: string\n platform: string // 'tiktok' | 'youtube' | 'instagram' | 'linkedin' | 'twitter'\n displayName: string\n username: string\n isActive: boolean\n profileId: { _id: string; name: string }\n}\n\nexport interface LateProfile {\n _id: string\n name: string\n}\n\nexport interface LatePost {\n _id: string\n content: string\n status: string // 'draft' | 'scheduled' | 'published' | 'failed'\n platforms: Array<{ platform: string; accountId: string }>\n scheduledFor?: string\n mediaItems?: Array<{ type: string; url: string }>\n isDraft?: boolean\n createdAt: string\n updatedAt: string\n}\n\nexport interface LateMediaPresignResult {\n uploadUrl: string\n publicUrl: string\n key: string\n expiresIn: number\n}\n\nexport interface LateMediaUploadResult {\n url: string\n type: 'image' | 'video'\n}\n\nexport interface CreatePostParams {\n content: string\n platforms: Array<{ platform: string; accountId: string }>\n scheduledFor?: string\n timezone?: string\n isDraft?: boolean\n mediaItems?: Array<{ type: 'image' | 'video'; url: string; thumbnail?: { url: string } }>\n platformSpecificData?: Record<string, unknown>\n tiktokSettings?: {\n privacy_level: string\n allow_comment: boolean\n allow_duet?: boolean\n allow_stitch?: boolean\n content_preview_confirmed: boolean\n express_consent_given: boolean\n [key: string]: unknown\n }\n}\n\n// ── Client ─────────────────────────────────────────────────────────────\n\nexport class LateApiClient {\n private baseUrl = 'https://getlate.dev/api/v1'\n private apiKey: string\n\n constructor(apiKey?: string) {\n this.apiKey = apiKey ?? getConfig().LATE_API_KEY\n if (!this.apiKey) {\n throw new Error('LATE_API_KEY is required — set it in environment or pass to constructor')\n }\n }\n\n // ── Private request helper ───────────────────────────────────────────\n\n private async request<T>(\n endpoint: string,\n options: RequestInit = {},\n retries = 3,\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n ...(options.headers as Record<string, string> | undefined),\n }\n\n // Only set Content-Type for non-FormData bodies\n if (!(options.body instanceof FormData)) {\n headers['Content-Type'] = 'application/json'\n }\n\n logger.debug(`Late API ${options.method ?? 'GET'} ${endpoint}`)\n\n for (let attempt = 1; attempt <= retries; attempt++) {\n const response = await fetch(url, { ...options, headers })\n\n if (response.ok) {\n // 204 No Content\n if (response.status === 204) return undefined as T\n return (await response.json()) as T\n }\n\n // 429 — rate limited, retry\n if (response.status === 429 && attempt < retries) {\n const retryAfter = Number(response.headers.get('Retry-After')) || 2\n logger.warn(`Late API rate limited, retrying in ${retryAfter}s (attempt ${attempt}/${retries})`)\n await new Promise((r) => setTimeout(r, retryAfter * 1000))\n continue\n }\n\n // 401 — bad API key\n if (response.status === 401) {\n throw new Error(\n 'Late API authentication failed (401). Check that LATE_API_KEY is valid.',\n )\n }\n\n // Other errors\n const body = await response.text().catch(() => '<no body>')\n throw new Error(\n `Late API error ${response.status} ${options.method ?? 'GET'} ${endpoint}: ${body}`,\n )\n }\n\n // Should not reach here, but satisfy TS\n throw new Error(`Late API request failed after ${retries} retries`)\n }\n\n // ── Core methods ─────────────────────────────────────────────────────\n\n async listProfiles(): Promise<LateProfile[]> {\n const data = await this.request<{ profiles: LateProfile[] }>('/profiles')\n return data.profiles ?? []\n }\n\n async listAccounts(): Promise<LateAccount[]> {\n const data = await this.request<{ accounts: LateAccount[] }>('/accounts')\n return data.accounts ?? []\n }\n\n async getScheduledPosts(platform?: string): Promise<LatePost[]> {\n const params = new URLSearchParams({ status: 'scheduled' })\n if (platform) params.set('platform', platform)\n const data = await this.request<{ posts: LatePost[] }>(`/posts?${params}`)\n return data.posts ?? []\n }\n\n async getDraftPosts(platform?: string): Promise<LatePost[]> {\n const params = new URLSearchParams({ status: 'draft' })\n if (platform) params.set('platform', platform)\n const data = await this.request<{ posts: LatePost[] }>(`/posts?${params}`)\n return data.posts ?? []\n }\n\n async createPost(params: CreatePostParams): Promise<LatePost> {\n const data = await this.request<{ post: LatePost }>('/posts', {\n method: 'POST',\n body: JSON.stringify(params),\n })\n return data.post\n }\n\n async deletePost(postId: string): Promise<void> {\n await this.request<void>(`/posts/${encodeURIComponent(postId)}`, {\n method: 'DELETE',\n })\n }\n\n async updatePost(postId: string, updates: Record<string, unknown>): Promise<LatePost> {\n const data = await this.request<{ post: LatePost }>(`/posts/${encodeURIComponent(postId)}`, {\n method: 'PUT',\n body: JSON.stringify(updates),\n })\n return data.post\n }\n\n async uploadMedia(filePath: string): Promise<LateMediaUploadResult> {\n const fileStats = await getFileStats(filePath)\n const fileName = basename(filePath)\n const ext = extname(fileName).toLowerCase()\n const contentType =\n ext === '.mp4' ? 'video/mp4' : ext === '.webm' ? 'video/webm' : ext === '.mov' ? 'video/quicktime' : 'video/mp4'\n\n logger.info(`Late API uploading ${String(fileName).replace(/[\\r\\n]/g, '')} (${(fileStats.size / 1024 / 1024).toFixed(1)} MB)`)\n\n // Step 1: Get presigned upload URL\n const presign = await this.request<LateMediaPresignResult>('/media/presign', {\n method: 'POST',\n body: JSON.stringify({ filename: fileName, contentType }),\n })\n logger.debug(`Late API presigned URL obtained for ${String(fileName).replace(/[\\r\\n]/g, '')} (expires in ${presign.expiresIn}s)`)\n\n // Step 2: Stream file to presigned URL (avoids loading entire file into memory)\n const nodeStream = openReadStream(filePath)\n try {\n const webStream = Readable.toWeb(nodeStream) as ReadableStream\n const uploadResp = await fetch(presign.uploadUrl, {\n method: 'PUT',\n headers: {\n 'Content-Type': contentType,\n 'Content-Length': String(fileStats.size),\n },\n body: webStream,\n // Node.js-specific property for streaming request bodies (not in standard RequestInit type)\n duplex: 'half',\n } as RequestInit)\n if (!uploadResp.ok) {\n throw new Error(`Late media upload failed: ${uploadResp.status} ${uploadResp.statusText}`)\n }\n } finally {\n // Ensure file handle is released so the folder can be renamed/moved on Windows\n nodeStream.destroy()\n }\n logger.debug(`Late API media uploaded → ${presign.publicUrl}`)\n\n const type: 'image' | 'video' = contentType.startsWith('image/') ? 'image' : 'video'\n return { url: presign.publicUrl, type }\n }\n\n // ── Helper ───────────────────────────────────────────────────────────\n\n async validateConnection(): Promise<{ valid: boolean; profileName?: string; error?: string }> {\n try {\n const profiles = await this.listProfiles()\n const name = profiles[0]?.name\n logger.info(`Late API connection valid — profile: ${name ?? 'unknown'}`)\n return { valid: true, profileName: name }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`Late API connection failed: ${message}`)\n return { valid: false, error: message }\n }\n }\n}\n","import { readTextFile, writeFileRaw } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport logger from '../config/logger.js'\n\nexport type DayOfWeek = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun'\n\nexport interface TimeSlot {\n days: DayOfWeek[]\n time: string // HH:MM format\n label: string\n}\n\nexport interface PlatformSchedule {\n slots: TimeSlot[]\n avoidDays: DayOfWeek[]\n}\n\nexport interface ScheduleConfig {\n timezone: string\n platforms: Record<string, PlatformSchedule>\n}\n\nconst VALID_DAYS: DayOfWeek[] = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']\nconst TIME_REGEX = /^([01]\\d|2[0-3]):[0-5]\\d$/\n\nlet cachedConfig: ScheduleConfig | null = null\n\nexport function getDefaultScheduleConfig(): ScheduleConfig {\n return {\n timezone: 'America/Chicago',\n platforms: {\n linkedin: {\n slots: [\n { days: ['tue', 'wed'], time: '08:00', label: 'Morning thought leadership' },\n { days: ['tue', 'wed', 'thu'], time: '12:00', label: 'Lunch break engagement' },\n ],\n avoidDays: ['sat', 'sun'],\n },\n tiktok: {\n slots: [\n { days: ['tue', 'wed', 'thu'], time: '19:00', label: 'Prime entertainment hours' },\n { days: ['fri', 'sat'], time: '21:00', label: 'Weekend evening' },\n ],\n avoidDays: [],\n },\n instagram: {\n slots: [\n { days: ['tue', 'wed', 'thu'], time: '10:00', label: 'Morning scroll' },\n { days: ['wed', 'thu', 'fri'], time: '19:30', label: 'Evening couch time' },\n ],\n avoidDays: [],\n },\n youtube: {\n slots: [\n { days: ['fri'], time: '15:00', label: 'Afternoon pre-weekend' },\n { days: ['thu', 'fri'], time: '20:00', label: 'Prime evening viewing' },\n ],\n avoidDays: ['mon'],\n },\n twitter: {\n slots: [\n { days: ['mon', 'tue', 'wed', 'thu', 'fri'], time: '08:30', label: 'Morning news check' },\n { days: ['tue', 'wed', 'thu'], time: '12:00', label: 'Lunch scroll' },\n { days: ['mon', 'tue', 'wed', 'thu', 'fri'], time: '17:00', label: 'Commute home' },\n ],\n avoidDays: [],\n },\n },\n }\n}\n\nexport function validateScheduleConfig(config: unknown): ScheduleConfig {\n if (!config || typeof config !== 'object') {\n throw new Error('Schedule config must be a non-null object')\n }\n\n const cfg = config as Record<string, unknown>\n\n if (typeof cfg.timezone !== 'string' || cfg.timezone.trim() === '') {\n throw new Error('Schedule config \"timezone\" must be a non-empty string')\n }\n\n if (!cfg.platforms || typeof cfg.platforms !== 'object' || Array.isArray(cfg.platforms)) {\n throw new Error('Schedule config \"platforms\" must be an object')\n }\n\n const platforms = cfg.platforms as Record<string, unknown>\n const validated: ScheduleConfig = {\n timezone: cfg.timezone,\n platforms: {},\n }\n\n for (const [name, value] of Object.entries(platforms)) {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`Platform \"${name}\" must be an object`)\n }\n\n const plat = value as Record<string, unknown>\n\n if (!Array.isArray(plat.slots)) {\n throw new Error(`Platform \"${name}\" must have a \"slots\" array`)\n }\n\n if (!Array.isArray(plat.avoidDays)) {\n throw new Error(`Platform \"${name}\" must have an \"avoidDays\" array`)\n }\n\n for (const day of plat.avoidDays) {\n if (!VALID_DAYS.includes(day as DayOfWeek)) {\n throw new Error(`Platform \"${name}\" avoidDays contains invalid day \"${day}\". Valid: ${VALID_DAYS.join(', ')}`)\n }\n }\n\n const validatedSlots: TimeSlot[] = []\n for (let i = 0; i < plat.slots.length; i++) {\n const slot = plat.slots[i] as Record<string, unknown>\n\n if (!Array.isArray(slot.days) || slot.days.length === 0) {\n throw new Error(`Platform \"${name}\" slot ${i} must have a non-empty \"days\" array`)\n }\n\n for (const day of slot.days) {\n if (!VALID_DAYS.includes(day as DayOfWeek)) {\n throw new Error(`Platform \"${name}\" slot ${i} has invalid day \"${day}\". Valid: ${VALID_DAYS.join(', ')}`)\n }\n }\n\n if (typeof slot.time !== 'string' || !TIME_REGEX.test(slot.time)) {\n throw new Error(`Platform \"${name}\" slot ${i} \"time\" must match HH:MM format (00:00–23:59)`)\n }\n\n if (typeof slot.label !== 'string' || slot.label.trim() === '') {\n throw new Error(`Platform \"${name}\" slot ${i} must have a non-empty \"label\" string`)\n }\n\n validatedSlots.push({\n days: slot.days as DayOfWeek[],\n time: slot.time,\n label: slot.label,\n })\n }\n\n validated.platforms[name] = {\n slots: validatedSlots,\n avoidDays: plat.avoidDays as DayOfWeek[],\n }\n }\n\n return validated\n}\n\nexport async function loadScheduleConfig(configPath?: string): Promise<ScheduleConfig> {\n if (cachedConfig) return cachedConfig\n\n const filePath = configPath ?? join(process.cwd(), 'schedule.json')\n\n let raw: string\n try {\n raw = await readTextFile(filePath)\n } catch {\n logger.info(`No schedule.json found at ${filePath}, creating with defaults`)\n const defaults = getDefaultScheduleConfig()\n // Write directly with exclusive create flag for security\n try {\n await writeFileRaw(filePath, JSON.stringify(defaults, null, 2), { \n encoding: 'utf-8',\n flag: 'wx',\n mode: 0o600\n })\n } catch (err: any) {\n // If file was created by another process in a race, read it\n if (err.code === 'EEXIST') {\n const raw = await readTextFile(filePath)\n const parsed: unknown = JSON.parse(raw)\n cachedConfig = validateScheduleConfig(parsed)\n logger.info(`Loaded schedule config from ${filePath}`)\n return cachedConfig\n }\n throw err\n }\n cachedConfig = defaults\n return defaults\n }\n\n const parsed: unknown = JSON.parse(raw)\n cachedConfig = validateScheduleConfig(parsed)\n logger.info(`Loaded schedule config from ${filePath}`)\n return cachedConfig\n}\n\nexport function getPlatformSchedule(platform: string): PlatformSchedule | null {\n if (!cachedConfig) return null\n return cachedConfig.platforms[platform] ?? null\n}\n\nexport function clearScheduleCache(): void {\n cachedConfig = null\n}\n","import { spawnCommand, createModuleRequire } from '../core/process.js'\nimport { fileExistsSync } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport { getConfig } from '../config/environment.js'\nimport { LateApiClient } from '../services/lateApi.js'\nimport { loadScheduleConfig } from '../services/scheduleConfig.js'\nimport type { ProviderName } from '../providers/index.js'\n\nconst require = createModuleRequire(import.meta.url)\n\ninterface CheckResult {\n label: string\n ok: boolean\n required: boolean\n message: string\n}\n\n/** Normalize LLM_PROVIDER the same way the provider factory does. */\nexport function normalizeProviderName(raw: string | undefined): string {\n return (raw || 'copilot').trim().toLowerCase()\n}\n\nfunction resolveFFmpegPath(): { path: string; source: string } {\n const config = getConfig()\n if (config.FFMPEG_PATH && config.FFMPEG_PATH !== 'ffmpeg') {\n return { path: config.FFMPEG_PATH, source: 'FFMPEG_PATH config' }\n }\n try {\n const staticPath = require('ffmpeg-static') as string\n if (staticPath && fileExistsSync(staticPath)) {\n return { path: staticPath, source: 'ffmpeg-static' }\n }\n } catch { /* not available */ }\n return { path: 'ffmpeg', source: 'system PATH' }\n}\n\nfunction resolveFFprobePath(): { path: string; source: string } {\n const config = getConfig()\n if (config.FFPROBE_PATH && config.FFPROBE_PATH !== 'ffprobe') {\n return { path: config.FFPROBE_PATH, source: 'FFPROBE_PATH config' }\n }\n try {\n const { path: probePath } = require('@ffprobe-installer/ffprobe') as { path: string }\n if (probePath && fileExistsSync(probePath)) {\n return { path: probePath, source: '@ffprobe-installer/ffprobe' }\n }\n } catch { /* not available */ }\n return { path: 'ffprobe', source: 'system PATH' }\n}\n\nfunction parseVersionFromOutput(output: string): string {\n const match = output.match(/(\\d+\\.\\d+(?:\\.\\d+)?)/)\n return match ? match[1] : 'unknown'\n}\n\nfunction getFFmpegInstallHint(): string {\n const platform = process.platform\n const lines = ['Install FFmpeg:']\n if (platform === 'win32') {\n lines.push(' winget install Gyan.FFmpeg')\n lines.push(' choco install ffmpeg (alternative)')\n } else if (platform === 'darwin') {\n lines.push(' brew install ffmpeg')\n } else {\n lines.push(' sudo apt install ffmpeg (Debian/Ubuntu)')\n lines.push(' sudo dnf install ffmpeg (Fedora)')\n lines.push(' sudo pacman -S ffmpeg (Arch)')\n }\n lines.push(' Or set FFMPEG_PATH to a custom binary location')\n return lines.join('\\n ')\n}\n\nfunction checkNode(): CheckResult {\n const raw = process.version // e.g. \"v20.11.1\"\n const major = parseInt(raw.slice(1), 10)\n const ok = major >= 20\n return {\n label: 'Node.js',\n ok,\n required: true,\n message: ok\n ? `Node.js ${raw} (required: ≥20)`\n : `Node.js ${raw} — version ≥20 required`,\n }\n}\n\nfunction checkFFmpeg(): CheckResult {\n const { path: binPath, source } = resolveFFmpegPath()\n try {\n const result = spawnCommand(binPath, ['-version'], { timeout: 10_000 })\n if (result.status === 0 && result.stdout) {\n const ver = parseVersionFromOutput(result.stdout)\n return { label: 'FFmpeg', ok: true, required: true, message: `FFmpeg ${ver} (source: ${source})` }\n }\n } catch { /* spawn failed */ }\n return {\n label: 'FFmpeg',\n ok: false,\n required: true,\n message: `FFmpeg not found — ${getFFmpegInstallHint()}`,\n }\n}\n\nfunction checkFFprobe(): CheckResult {\n const { path: binPath, source } = resolveFFprobePath()\n try {\n const result = spawnCommand(binPath, ['-version'], { timeout: 10_000 })\n if (result.status === 0 && result.stdout) {\n const ver = parseVersionFromOutput(result.stdout)\n return { label: 'FFprobe', ok: true, required: true, message: `FFprobe ${ver} (source: ${source})` }\n }\n } catch { /* spawn failed */ }\n return {\n label: 'FFprobe',\n ok: false,\n required: true,\n message: `FFprobe not found — usually included with FFmpeg.\\n ${getFFmpegInstallHint()}`,\n }\n}\n\nfunction checkOpenAIKey(): CheckResult {\n const set = !!getConfig().OPENAI_API_KEY\n return {\n label: 'OPENAI_API_KEY',\n ok: set,\n required: true,\n message: set\n ? 'OPENAI_API_KEY is set'\n : 'OPENAI_API_KEY not set — get one at https://platform.openai.com/api-keys',\n }\n}\n\nfunction checkExaKey(): CheckResult {\n const set = !!getConfig().EXA_API_KEY\n return {\n label: 'EXA_API_KEY',\n ok: set,\n required: false,\n message: set\n ? 'EXA_API_KEY is set'\n : 'EXA_API_KEY not set (optional — web search in social posts)',\n }\n}\n\nfunction checkGit(): CheckResult {\n try {\n const result = spawnCommand('git', ['--version'], { timeout: 10_000 })\n if (result.status === 0 && result.stdout) {\n const ver = parseVersionFromOutput(result.stdout)\n return { label: 'Git', ok: true, required: false, message: `Git ${ver}` }\n }\n } catch { /* spawn failed */ }\n return {\n label: 'Git',\n ok: false,\n required: false,\n message: 'Git not found (optional — needed for auto-commit stage)',\n }\n}\n\nfunction checkWatchFolder(): CheckResult {\n const watchDir = getConfig().WATCH_FOLDER || join(process.cwd(), 'watch')\n const exists = fileExistsSync(watchDir)\n return {\n label: 'Watch folder',\n ok: exists,\n required: false,\n message: exists\n ? `Watch folder exists: ${watchDir}`\n : `Watch folder missing: ${watchDir}`,\n }\n}\n\nexport async function runDoctor(): Promise<void> {\n console.log('\\n🔍 VidPipe Doctor — Checking prerequisites...\\n')\n\n const results: CheckResult[] = [\n checkNode(),\n checkFFmpeg(),\n checkFFprobe(),\n checkOpenAIKey(),\n checkExaKey(),\n checkGit(),\n checkWatchFolder(),\n ]\n\n for (const r of results) {\n const icon = r.ok ? '✅' : r.required ? '❌' : '⬚'\n console.log(` ${icon} ${r.message}`)\n }\n\n // LLM Provider section — check config values to avoid silent fallback\n const config = getConfig()\n console.log('\\nLLM Provider')\n const providerName = normalizeProviderName(config.LLM_PROVIDER) as ProviderName\n const isDefault = !config.LLM_PROVIDER\n const providerLabel = isDefault ? `${providerName} (default)` : providerName\n const validProviders: ProviderName[] = ['copilot', 'openai', 'claude']\n\n if (!validProviders.includes(providerName)) {\n console.log(` ❌ Provider: ${providerLabel} — unknown provider`)\n results.push({ label: 'LLM Provider', ok: false, required: true, message: `Unknown provider: ${providerName}` })\n } else if (providerName === 'copilot') {\n console.log(` ✅ Provider: ${providerLabel}`)\n console.log(' ✅ Copilot — uses GitHub auth')\n } else if (providerName === 'openai') {\n console.log(` ✅ Provider: ${providerLabel}`)\n if (config.OPENAI_API_KEY) {\n console.log(' ✅ OPENAI_API_KEY is set (also used for Whisper)')\n } else {\n console.log(' ❌ OPENAI_API_KEY not set (required for openai provider)')\n results.push({ label: 'LLM Provider', ok: false, required: true, message: 'OPENAI_API_KEY not set for OpenAI LLM' })\n }\n } else if (providerName === 'claude') {\n console.log(` ✅ Provider: ${providerLabel}`)\n if (config.ANTHROPIC_API_KEY) {\n console.log(' ✅ ANTHROPIC_API_KEY is set')\n } else {\n console.log(' ❌ ANTHROPIC_API_KEY not set (required for claude provider)')\n results.push({ label: 'LLM Provider', ok: false, required: true, message: 'ANTHROPIC_API_KEY not set for Claude LLM' })\n }\n }\n\n const defaultModels: Record<ProviderName, string> = {\n copilot: 'Claude Opus 4.6',\n openai: 'gpt-4o',\n claude: 'claude-opus-4.6',\n }\n if (validProviders.includes(providerName)) {\n const defaultModel = defaultModels[providerName]\n const modelOverride = config.LLM_MODEL\n if (modelOverride) {\n console.log(` ℹ️ Model override: ${modelOverride} (default: ${defaultModel})`)\n } else {\n console.log(` ℹ️ Default model: ${defaultModel}`)\n }\n }\n\n // Late API (optional — social publishing)\n console.log('\\nSocial Publishing')\n await checkLateApi(config.LATE_API_KEY)\n\n // Schedule config\n await checkScheduleConfig()\n\n const failedRequired = results.filter(r => r.required && !r.ok)\n\n console.log()\n if (failedRequired.length === 0) {\n console.log(' All required checks passed! ✅\\n')\n process.exit(0)\n } else {\n console.log(` ${failedRequired.length} required check${failedRequired.length > 1 ? 's' : ''} failed ❌\\n`)\n process.exit(1)\n }\n}\n\nconst PLATFORM_LABELS: Record<string, string> = {\n tiktok: 'TikTok',\n youtube: 'YouTube',\n instagram: 'Instagram',\n linkedin: 'LinkedIn',\n twitter: 'X/Twitter',\n}\n\nasync function checkLateApi(apiKey: string): Promise<void> {\n if (!apiKey) {\n console.log(' ⬚ Late API key: not configured (optional — set LATE_API_KEY for social publishing)')\n return\n }\n\n try {\n const client = new LateApiClient(apiKey)\n const { valid, profileName, error } = await client.validateConnection()\n\n if (!valid) {\n console.log(` ❌ Late API key: invalid (${error ?? 'unknown error'})`)\n return\n }\n\n console.log(` ✅ Late API key: connected to profile \"${profileName ?? 'unknown'}\"`)\n\n // List connected accounts\n try {\n const accounts = await client.listAccounts()\n if (accounts.length === 0) {\n console.log(' ⚠️ No social accounts connected in Late dashboard')\n } else {\n for (const acct of accounts) {\n const label = PLATFORM_LABELS[acct.platform] ?? acct.platform\n const handle = acct.username ? `@${acct.username}` : acct.displayName\n console.log(` ✅ ${label} — ${handle}`)\n }\n }\n } catch {\n console.log(' ⚠️ Could not fetch connected accounts')\n }\n } catch {\n console.log(' ❌ Late API key: could not connect (network error)')\n }\n}\n\nasync function checkScheduleConfig(): Promise<void> {\n const schedulePath = join(process.cwd(), 'schedule.json')\n\n if (!fileExistsSync(schedulePath)) {\n console.log(' ⬚ Schedule config: schedule.json not found (will use defaults on first run)')\n return\n }\n\n try {\n const scheduleConfig = await loadScheduleConfig(schedulePath)\n const platformCount = Object.keys(scheduleConfig.platforms).length\n console.log(` ✅ Schedule config: schedule.json found (${platformCount} platform${platformCount !== 1 ? 's' : ''} configured)`)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.log(` ❌ Schedule config: schedule.json invalid — ${msg}`)\n }\n}\n","import { createReadlineInterface } from '../core/cli.js'\nimport { writeTextFile, readTextFile, fileExists } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport { getFFmpegPath, getFFprobePath } from '../config/ffmpegResolver'\nimport { LateApiClient } from '../services/lateApi'\nimport { getDefaultScheduleConfig } from '../services/scheduleConfig'\n\nconst rl = createReadlineInterface({ input: process.stdin, output: process.stdout })\n\nfunction ask(question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => resolve(answer))\n })\n}\n\nexport async function runInit(): Promise<void> {\n // Gracefully handle Ctrl+C\n rl.on('close', () => {\n console.log('\\n')\n process.exit(0)\n })\n\n console.log('\\n🎬 Welcome to vidpipe setup!\\n')\n\n const envPath = join(process.cwd(), '.env')\n const envVars: Record<string, string> = {}\n\n // Load existing .env if present\n let existingEnv = ''\n try {\n existingEnv = await readTextFile(envPath)\n } catch {\n // No existing .env\n }\n\n // Parse existing env values for hints\n const existingVars: Record<string, string> = {}\n for (const line of existingEnv.split('\\n')) {\n const match = line.match(/^([A-Z_]+)=(.*)$/)\n if (match) existingVars[match[1]] = match[2]\n }\n\n // Step 1: FFmpeg\n console.log('Step 1/5: FFmpeg')\n try {\n const ffmpeg = getFFmpegPath()\n console.log(` ✅ FFmpeg found at: ${ffmpeg}`)\n } catch {\n console.log(' ❌ FFmpeg not found — install from https://ffmpeg.org/')\n }\n try {\n const ffprobe = getFFprobePath()\n console.log(` ✅ FFprobe found at: ${ffprobe}`)\n } catch {\n console.log(' ❌ FFprobe not found')\n }\n\n // Step 2: OpenAI\n console.log('\\nStep 2/5: OpenAI (Required for transcription)')\n const currentOpenAI = existingVars.OPENAI_API_KEY || process.env.OPENAI_API_KEY\n const hint = currentOpenAI ? ` (current: ${currentOpenAI.slice(0, 8)}...)` : ''\n const openaiKey = await ask(` ? OpenAI API key${hint}: `)\n if (openaiKey.trim()) {\n envVars.OPENAI_API_KEY = openaiKey.trim()\n console.log(' ✅ API key saved')\n } else if (currentOpenAI) {\n console.log(' ✅ Keeping current key')\n } else {\n console.log(' ⚠️ No key set — transcription will not work')\n }\n\n // Step 3: LLM Provider\n console.log('\\nStep 3/5: LLM Provider')\n const provider = await ask(' ? Provider [copilot/openai/claude] (copilot): ')\n envVars.LLM_PROVIDER = provider.trim() || 'copilot'\n console.log(` ✅ Using ${envVars.LLM_PROVIDER}`)\n\n // If claude, ask for ANTHROPIC_API_KEY\n if (envVars.LLM_PROVIDER === 'claude') {\n const claudeKey = await ask(' ? Anthropic API key: ')\n if (claudeKey.trim()) envVars.ANTHROPIC_API_KEY = claudeKey.trim()\n }\n\n // Step 4: Exa (optional)\n console.log('\\nStep 4/5: Web Search (Optional — enriches social posts)')\n const exaKey = await ask(' ? Exa API key (press Enter to skip): ')\n if (exaKey.trim()) {\n envVars.EXA_API_KEY = exaKey.trim()\n console.log(' ✅ Exa configured')\n } else {\n console.log(' ⏭️ Skipped')\n }\n\n // Step 5: Late API (optional)\n console.log('\\nStep 5/5: Social Publishing (Optional)')\n const setupLate = await ask(' ? Set up social media publishing? [y/N]: ')\n\n if (setupLate.toLowerCase() === 'y') {\n const lateKey = await ask(' ? Late API key (get one at https://getlate.dev): ')\n if (lateKey.trim()) {\n envVars.LATE_API_KEY = lateKey.trim()\n // Validate connection\n try {\n const client = new LateApiClient(lateKey.trim())\n const validation = await client.validateConnection()\n if (validation.valid) {\n console.log(` ✅ Connected to profile \"${validation.profileName}\"`)\n const accounts = await client.listAccounts()\n if (accounts.length > 0) {\n console.log(' Connected accounts:')\n for (const acc of accounts) {\n console.log(` ✅ ${acc.platform} — ${acc.username || acc.displayName}`)\n }\n }\n } else {\n console.log(` ❌ Connection failed: ${validation.error}`)\n }\n } catch (err) {\n console.log(` ⚠️ Could not validate key: ${err instanceof Error ? err.message : String(err)}`)\n }\n\n // Schedule.json\n const createSchedule = await ask(' ? Create default schedule.json? [Y/n]: ')\n if (createSchedule.toLowerCase() !== 'n') {\n const schedulePath = join(process.cwd(), 'schedule.json')\n if (await fileExists(schedulePath)) {\n console.log(' ✅ schedule.json already exists')\n } else {\n await writeTextFile(schedulePath, JSON.stringify(getDefaultScheduleConfig(), null, 2))\n console.log(' ✅ schedule.json created with optimal posting times')\n }\n }\n }\n } else {\n console.log(' ⏭️ Skipped')\n }\n\n // Write .env — merge new values with existing\n for (const [key, value] of Object.entries(envVars)) {\n const regex = new RegExp(`^${key}=.*$`, 'm')\n if (regex.test(existingEnv)) {\n existingEnv = existingEnv.replace(regex, `${key}=${value}`)\n } else {\n existingEnv += `\\n${key}=${value}`\n }\n }\n await writeTextFile(envPath, existingEnv.trim() + '\\n')\n\n console.log('\\n✅ Setup complete! Configuration saved to .env')\n console.log(' Run `vidpipe doctor` to verify everything is working.')\n console.log(' Run `vidpipe <video.mp4>` to process your first video.\\n')\n\n rl.close()\n}\n","import { LateApiClient, type LatePost } from './lateApi'\nimport { loadScheduleConfig, type DayOfWeek } from './scheduleConfig'\nimport { getPublishedItems } from './postStore'\nimport logger from '../config/logger'\n\n/**\n * Normalize ISO datetime to milliseconds since epoch for collision detection.\n * Handles different ISO formats from Late API vs local queue.\n */\nfunction normalizeDateTime(isoString: string): number {\n return new Date(isoString).getTime()\n}\n\nconst CHUNK_DAYS = 14 // generate candidates in 14-day chunks\nconst MAX_LOOKAHEAD_DAYS = 730 // hard ceiling (~2 years)\n\ninterface BookedSlot {\n scheduledFor: string\n source: 'late' | 'local'\n postId?: string\n itemId?: string\n platform: string\n}\n\n/**\n * Get the UTC offset string (e.g. \"-06:00\") for a timezone on a given date.\n */\nfunction getTimezoneOffset(timezone: string, date: Date): string {\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n timeZoneName: 'longOffset',\n })\n const parts = formatter.formatToParts(date)\n const tzPart = parts.find(p => p.type === 'timeZoneName')\n // longOffset gives e.g. \"GMT-06:00\" or \"GMT+05:30\"\n const match = tzPart?.value?.match(/GMT([+-]\\d{2}:\\d{2})/)\n if (match) return match[1]\n // GMT with no offset means UTC\n if (tzPart?.value === 'GMT') return '+00:00'\n logger.warn(\n `Could not parse timezone offset for timezone \"${timezone}\" on date \"${date.toISOString()}\". ` +\n `Raw timeZoneName part: \"${tzPart?.value ?? 'undefined'}\". Falling back to UTC (+00:00).`,\n )\n return '+00:00'\n}\n\n/**\n * Build an ISO datetime string with timezone offset for a given date and time.\n */\nfunction buildSlotDatetime(date: Date, time: string, timezone: string): string {\n // Derive calendar date parts in the target timezone to avoid host-timezone skew\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n })\n const parts = formatter.formatToParts(date)\n const yearPart = parts.find(p => p.type === 'year')?.value\n const monthPart = parts.find(p => p.type === 'month')?.value\n const dayPart = parts.find(p => p.type === 'day')?.value\n\n const year = yearPart ?? String(date.getFullYear())\n const month = (monthPart ?? String(date.getMonth() + 1)).padStart(2, '0')\n const day = (dayPart ?? String(date.getDate())).padStart(2, '0')\n const offset = getTimezoneOffset(timezone, date)\n return `${year}-${month}-${day}T${time}:00${offset}`\n}\n\n/**\n * Get the day-of-week key for a Date in the given timezone.\n */\nfunction getDayOfWeekInTimezone(date: Date, timezone: string): DayOfWeek {\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n weekday: 'short',\n })\n const short = formatter.format(date).toLowerCase().slice(0, 3)\n const map: Record<string, DayOfWeek> = {\n sun: 'sun', mon: 'mon', tue: 'tue', wed: 'wed', thu: 'thu', fri: 'fri', sat: 'sat',\n }\n return map[short] ?? 'mon'\n}\n\n\n\n/**\n * Fetch scheduled posts from Late API, returning empty array on failure.\n */\nasync function fetchScheduledPostsSafe(platform?: string): Promise<LatePost[]> {\n try {\n const client = new LateApiClient()\n return await client.getScheduledPosts(platform)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.warn(`Late API unreachable, using local data only: ${msg}`)\n return []\n }\n}\n\n/**\n * Build the set of already-booked slots from Late API and local published items.\n */\nasync function buildBookedSlots(platform?: string): Promise<BookedSlot[]> {\n const [latePosts, publishedItems] = await Promise.all([\n fetchScheduledPostsSafe(platform),\n getPublishedItems(),\n ])\n\n const slots: BookedSlot[] = []\n\n for (const post of latePosts) {\n if (!post.scheduledFor) continue\n for (const p of post.platforms) {\n if (!platform || p.platform === platform) {\n slots.push({\n scheduledFor: post.scheduledFor,\n source: 'late',\n postId: post._id,\n platform: p.platform,\n })\n }\n }\n }\n\n for (const item of publishedItems) {\n if (platform && item.metadata.platform !== platform) continue\n if (!item.metadata.scheduledFor) continue\n slots.push({\n scheduledFor: item.metadata.scheduledFor,\n source: 'local',\n itemId: item.id,\n platform: item.metadata.platform,\n })\n }\n\n return slots\n}\n\n\n\n/**\n * Find the next available posting slot for a platform.\n *\n * Algorithm (generate-sort-filter):\n * 1. Load platform schedule config from schedule.json\n * 2. Build set of already-booked datetimes from Late API + local published items\n * 3. In 14-day chunks, generate candidate slot datetimes, sort, and check availability\n * 4. If no available slot in the current chunk, expand to the next chunk (up to ~2 years)\n * 5. Return the first candidate not already booked, or null if none found\n */\nexport async function findNextSlot(platform: string): Promise<string | null> {\n const config = await loadScheduleConfig()\n const platformConfig = config.platforms[platform]\n if (!platformConfig) {\n logger.warn(`No schedule config found for platform \"${String(platform).replace(/[\\r\\n]/g, '')}\"`)\n return null\n }\n\n const { timezone } = config\n const bookedSlots = await buildBookedSlots(platform)\n const bookedDatetimes = new Set(bookedSlots.map(s => normalizeDateTime(s.scheduledFor)))\n\n const now = new Date()\n let startOffset = 1\n\n while (startOffset <= MAX_LOOKAHEAD_DAYS) {\n const endOffset = Math.min(startOffset + CHUNK_DAYS - 1, MAX_LOOKAHEAD_DAYS)\n const candidates: string[] = []\n\n for (let dayOffset = startOffset; dayOffset <= endOffset; dayOffset++) {\n const candidateDate = new Date(now)\n candidateDate.setDate(candidateDate.getDate() + dayOffset)\n\n const dayOfWeek = getDayOfWeekInTimezone(candidateDate, timezone)\n if (platformConfig.avoidDays.includes(dayOfWeek)) continue\n\n for (const slot of platformConfig.slots) {\n if (!slot.days.includes(dayOfWeek)) continue\n candidates.push(buildSlotDatetime(candidateDate, slot.time, timezone))\n }\n }\n\n candidates.sort((a, b) => new Date(a).getTime() - new Date(b).getTime())\n\n const available = candidates.find(c => !bookedDatetimes.has(normalizeDateTime(c)))\n if (available) {\n logger.debug(`Found available slot for ${String(platform).replace(/[\\r\\n]/g, '')}: ${String(available).replace(/[\\r\\n]/g, '')}`)\n return available\n }\n\n startOffset = endOffset + 1\n }\n\n logger.warn(`No available slot found for \"${String(platform).replace(/[\\r\\n]/g, '')}\" within ${MAX_LOOKAHEAD_DAYS} days`)\n return null\n}\n\n/**\n * Get a calendar view of scheduled posts across all platforms.\n * Returns slots sorted by datetime.\n */\nexport async function getScheduleCalendar(\n startDate?: Date,\n endDate?: Date,\n): Promise<Array<{\n platform: string\n scheduledFor: string\n source: 'late' | 'local'\n postId?: string\n itemId?: string\n}>> {\n const slots = await buildBookedSlots()\n\n let filtered = slots.map(s => ({\n platform: s.platform,\n scheduledFor: s.scheduledFor,\n source: s.source,\n postId: s.postId,\n itemId: s.itemId,\n }))\n\n if (startDate) {\n const startMs = startDate.getTime()\n filtered = filtered.filter(s => new Date(s.scheduledFor).getTime() >= startMs)\n }\n if (endDate) {\n const endMs = endDate.getTime()\n filtered = filtered.filter(s => new Date(s.scheduledFor).getTime() <= endMs)\n }\n\n filtered.sort((a, b) => new Date(a.scheduledFor).getTime() - new Date(b.scheduledFor).getTime())\n return filtered\n}\n","import { getScheduleCalendar } from '../services/scheduler'\nimport { loadScheduleConfig } from '../services/scheduleConfig'\nimport { initConfig } from '../config/environment'\n\nexport interface ScheduleCommandOptions {\n platform?: string\n}\n\nexport async function runSchedule(options: ScheduleCommandOptions = {}): Promise<void> {\n initConfig()\n\n console.log('\\n📅 Posting Schedule\\n')\n\n // Load config to show configured time slots\n const config = await loadScheduleConfig()\n \n // Get upcoming scheduled posts\n const calendar = await getScheduleCalendar()\n\n // Filter by platform if specified\n const filtered = options.platform \n ? calendar.filter(s => s.platform === options.platform)\n : calendar\n\n if (filtered.length === 0) {\n console.log('No posts scheduled.')\n console.log('\\nRun `vidpipe review` to review and schedule pending posts.')\n return\n }\n\n // Group by date\n const byDate = new Map<string, typeof filtered>()\n for (const slot of filtered) {\n const date = new Date(slot.scheduledFor).toLocaleDateString('en-US', {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n })\n if (!byDate.has(date)) byDate.set(date, [])\n byDate.get(date)!.push(slot)\n }\n\n // Display\n for (const [date, slots] of byDate) {\n console.log(` ${date}`)\n for (const slot of slots) {\n const time = new Date(slot.scheduledFor).toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n })\n const source = slot.source === 'late' ? '🌐' : '📁'\n const icon = getPlatformIcon(slot.platform)\n console.log(` ${time} ${icon} ${slot.platform} ${source}`)\n }\n }\n\n console.log(`\\n 🌐 = scheduled in Late 📁 = published locally\\n`)\n}\n\nfunction getPlatformIcon(platform: string): string {\n const icons: Record<string, string> = {\n tiktok: '🎵',\n youtube: '▶️',\n instagram: '📸',\n linkedin: '💼',\n twitter: '🐦',\n }\n return icons[platform] || '📱'\n}\n","export { default as express } from 'express'\nexport type { Request, Response, NextFunction, Express, Router as ExpressRouter } from 'express'\nexport { Router } from 'express'\nexport { default as rateLimit } from 'express-rate-limit'\n","import { Platform } from '../types/index.js'\nimport { LateApiClient } from './lateApi.js'\nimport type { LateAccount } from './lateApi.js'\nimport logger from '../config/logger.js'\nimport { readTextFile, writeTextFile, removeFile } from '../core/fileSystem.js'\nimport { join, resolve, sep } from '../core/paths.js'\n\n// ── Cache ──────────────────────────────────────────────────────────────\n\nconst CACHE_FILE = '.vidpipe-cache.json'\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\n\ninterface AccountCache {\n accounts: Record<string, string> // platform -> accountId\n fetchedAt: string\n}\n\nlet memoryCache: AccountCache | null = null\n\n// ── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Map a vidpipe Platform to the Late API platform string.\n */\nfunction toLatePlatform(platform: Platform): string {\n return platform === Platform.X ? 'twitter' : platform\n}\n\nfunction cachePath(): string {\n return join(process.cwd(), CACHE_FILE)\n}\n\nfunction isCacheValid(cache: AccountCache): boolean {\n const fetchedAtTime = new Date(cache.fetchedAt).getTime()\n if (Number.isNaN(fetchedAtTime)) {\n logger.warn('Invalid fetchedAt in account cache; treating as stale', {\n fetchedAt: cache.fetchedAt,\n })\n return false\n }\n const age = Date.now() - fetchedAtTime\n return age < CACHE_TTL_MS\n}\n\nasync function readFileCache(): Promise<AccountCache | null> {\n try {\n const raw = await readTextFile(cachePath())\n const cache = JSON.parse(raw) as AccountCache\n if (cache.accounts && cache.fetchedAt && isCacheValid(cache)) {\n return cache\n }\n return null\n } catch {\n return null\n }\n}\n\nasync function writeFileCache(cache: AccountCache): Promise<void> {\n try {\n // Validate cache structure before writing to prevent malformed data\n if (!cache || typeof cache !== 'object' || !cache.accounts || !cache.fetchedAt) {\n logger.warn('Invalid cache structure, skipping write')\n return\n }\n // Sanitize by re-constructing with only expected fields\n const sanitized: AccountCache = {\n accounts: typeof cache.accounts === 'object' ? { ...cache.accounts } : {},\n fetchedAt: String(cache.fetchedAt),\n }\n // Validate HTTP-sourced account data before writing to cache (CodeQL js/http-to-file-access)\n for (const [platform, accountId] of Object.entries(sanitized.accounts)) {\n if (typeof platform !== 'string' || typeof accountId !== 'string' ||\n /[\\x00-\\x1f]/.test(platform) || /[\\x00-\\x1f]/.test(accountId)) {\n logger.warn('Invalid account mapping data from API, skipping cache write')\n return\n }\n }\n const resolvedCachePath = resolve(cachePath())\n if (!resolvedCachePath.startsWith(resolve(process.cwd()) + sep)) {\n throw new Error('Cache path outside working directory')\n }\n // lgtm[js/http-to-file-access] - Writing sanitized account cache is intended functionality with path validation\n await writeTextFile(resolvedCachePath, JSON.stringify(sanitized, null, 2))\n } catch (err) {\n logger.warn('Failed to write account cache file', { error: err })\n }\n}\n\nasync function fetchAndCache(): Promise<Record<string, string>> {\n const client = new LateApiClient()\n const accounts: LateAccount[] = await client.listAccounts()\n\n const mapping: Record<string, string> = {}\n for (const acct of accounts) {\n if (acct.isActive) {\n mapping[acct.platform] = acct._id\n }\n }\n\n const cache: AccountCache = {\n accounts: mapping,\n fetchedAt: new Date().toISOString(),\n }\n memoryCache = cache\n await writeFileCache(cache)\n\n logger.info('Refreshed Late account mappings', {\n platforms: Object.keys(mapping),\n })\n return mapping\n}\n\nasync function ensureMappings(): Promise<Record<string, string>> {\n // 1. In-memory cache\n if (memoryCache && isCacheValid(memoryCache)) {\n return memoryCache.accounts\n }\n\n // 2. File cache\n const fileCache = await readFileCache()\n if (fileCache) {\n memoryCache = fileCache\n return fileCache.accounts\n }\n\n // 3. Fetch from Late API\n try {\n return await fetchAndCache()\n } catch (err) {\n logger.error('Failed to fetch Late account mappings', { error: err })\n return {}\n }\n}\n\n// ── Public API ─────────────────────────────────────────────────────────\n\n/**\n * Get the Late account ID for a given platform.\n *\n * Resolution order:\n * 1. In-memory cache\n * 2. File cache (.vidpipe-cache.json)\n * 3. Fetch from Late API and cache\n *\n * Returns null if the platform is not connected.\n */\nexport async function getAccountId(\n platform: Platform,\n): Promise<string | null> {\n const mappings = await ensureMappings()\n const latePlatform = toLatePlatform(platform)\n return mappings[latePlatform] ?? null\n}\n\n/**\n * Get all account mappings (platform -> accountId).\n * Fetches from Late API if not cached.\n */\nexport async function getAllAccountMappings(): Promise<\n Record<string, string>\n> {\n return ensureMappings()\n}\n\n/**\n * Force refresh the account mappings from Late API.\n */\nexport async function refreshAccountMappings(): Promise<\n Record<string, string>\n> {\n memoryCache = null\n return fetchAndCache()\n}\n\n/**\n * Clear the account cache (both memory and file).\n */\nexport async function clearAccountCache(): Promise<void> {\n memoryCache = null\n try {\n await removeFile(cachePath())\n } catch {\n // File may not exist — that's fine\n }\n}\n","import { fileExists } from '../core/fileSystem.js'\nimport { Router } from '../core/http.js'\nimport { rateLimit } from '../core/http.js'\nimport { getPendingItems, getItem, updateItem, approveItem, rejectItem } from '../services/postStore'\nimport { findNextSlot, getScheduleCalendar } from '../services/scheduler'\nimport { getAccountId } from '../services/accountMapping'\nimport { LateApiClient, type LateAccount, type LateProfile } from '../services/lateApi'\nimport { loadScheduleConfig } from '../services/scheduleConfig'\nimport { fromLatePlatform, normalizePlatformString } from '../types'\nimport logger from '../config/logger'\n\n// ── Simple in-memory cache (avoids repeated Late API calls) ────────────\nconst CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes\nconst cache = new Map<string, { data: unknown; expiry: number }>()\n\nfunction getCached<T>(key: string): T | undefined {\n const entry = cache.get(key)\n if (entry && entry.expiry > Date.now()) return entry.data as T\n cache.delete(key)\n return undefined\n}\n\nfunction setCache(key: string, data: unknown, ttl = CACHE_TTL_MS): void {\n cache.set(key, { data, expiry: Date.now() + ttl })\n}\n\nexport function createRouter(): Router {\n const router = Router()\n\n router.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }))\n\n // GET /api/posts/pending — list all pending review items\n router.get('/api/posts/pending', async (req, res) => {\n const items = await getPendingItems()\n res.json({ items, total: items.length })\n })\n\n // GET /api/init — combined endpoint for initial page load (1 request instead of 3)\n router.get('/api/init', async (req, res) => {\n const [itemsResult, accountsResult, profileResult] = await Promise.allSettled([\n getPendingItems(),\n (async () => {\n const cached = getCached<LateAccount[]>('accounts')\n if (cached) return cached\n const client = new LateApiClient()\n const accounts = await client.listAccounts()\n setCache('accounts', accounts)\n return accounts\n })(),\n (async () => {\n const cached = getCached<LateProfile | null>('profile')\n if (cached !== undefined) return cached\n const client = new LateApiClient()\n const profiles = await client.listProfiles()\n const profile = profiles[0] || null\n setCache('profile', profile)\n return profile\n })(),\n ])\n\n const items = itemsResult.status === 'fulfilled' ? itemsResult.value : []\n const accounts = accountsResult.status === 'fulfilled' ? accountsResult.value : []\n const profile = profileResult.status === 'fulfilled' ? profileResult.value : null\n\n res.json({ items, total: items.length, accounts, profile })\n })\n\n // GET /api/posts/:id — get single post with full content\n router.get('/api/posts/:id', async (req, res) => {\n const item = await getItem(req.params.id)\n if (!item) return res.status(404).json({ error: 'Item not found' })\n res.json(item)\n })\n\n // POST /api/posts/:id/approve — smart-schedule + upload media + publish to Late\n router.post('/api/posts/:id/approve', async (req, res) => {\n try {\n const item = await getItem(req.params.id)\n if (!item) return res.status(404).json({ error: 'Item not found' })\n\n // Normalize platform — LLM may output \"x (twitter)\" but Late API and schedule use \"twitter\"\n const latePlatform = normalizePlatformString(item.metadata.platform)\n\n // 1. Find next available slot\n const slot = await findNextSlot(latePlatform)\n if (!slot) return res.status(409).json({ error: 'No available schedule slots in the current scheduling window' })\n\n // 2. Resolve account ID\n const platform = fromLatePlatform(latePlatform)\n const accountId = item.metadata.accountId || await getAccountId(platform)\n if (!accountId) return res.status(400).json({ error: `No Late account connected for ${latePlatform}` })\n\n // 3. Upload media if exists (fallback to source media when queue copy is missing)\n const client = new LateApiClient()\n let mediaItems: Array<{ type: 'image' | 'video'; url: string }> | undefined\n const effectiveMediaPath = item.mediaPath ?? item.metadata.sourceMediaPath\n if (effectiveMediaPath) {\n const mediaExists = await fileExists(effectiveMediaPath)\n if (mediaExists) {\n if (!item.mediaPath && item.metadata.sourceMediaPath) {\n logger.info(`Using source media fallback for ${String(item.id).replace(/[\\r\\n]/g, '')}: ${String(item.metadata.sourceMediaPath).replace(/[\\r\\n]/g, '')}`)\n }\n const upload = await client.uploadMedia(effectiveMediaPath)\n mediaItems = [{ type: upload.type, url: upload.url }]\n }\n }\n\n // 4. Create scheduled post in Late\n const isTikTok = latePlatform === 'tiktok'\n const tiktokSettings = isTikTok ? {\n privacy_level: 'PUBLIC_TO_EVERYONE',\n allow_comment: true,\n allow_duet: true,\n allow_stitch: true,\n content_preview_confirmed: true,\n express_consent_given: true,\n } : undefined\n\n const schedConfig = await loadScheduleConfig()\n const latePost = await client.createPost({\n content: item.postContent,\n platforms: [{ platform: latePlatform, accountId }],\n scheduledFor: slot,\n timezone: schedConfig.timezone,\n mediaItems,\n platformSpecificData: item.metadata.platformSpecificData,\n tiktokSettings,\n })\n\n // 5. Move to published (persist resolved accountId to metadata)\n await approveItem(req.params.id, {\n latePostId: latePost._id,\n scheduledFor: slot,\n publishedUrl: undefined,\n accountId,\n })\n\n res.json({ success: true, scheduledFor: slot, latePostId: latePost._id })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.error(`Approve failed for ${String(req.params.id).replace(/[\\r\\n]/g, '')}: ${String(msg).replace(/[\\r\\n]/g, '')}`)\n res.status(500).json({ error: msg })\n }\n })\n\n // POST /api/posts/:id/reject — delete from queue\n router.post('/api/posts/:id/reject', async (req, res) => {\n try {\n await rejectItem(req.params.id)\n res.json({ success: true })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // PUT /api/posts/:id — edit post content\n router.put('/api/posts/:id', async (req, res) => {\n try {\n const { postContent, metadata } = req.body\n const updated = await updateItem(req.params.id, { postContent, metadata })\n if (!updated) return res.status(404).json({ error: 'Item not found' })\n res.json(updated)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // GET /api/schedule — current schedule calendar\n router.get('/api/schedule', async (req, res) => {\n try {\n const calendar = await getScheduleCalendar()\n res.json({ slots: calendar })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // GET /api/schedule/next-slot/:platform — calculate next available slot\n router.get('/api/schedule/next-slot/:platform', async (req, res) => {\n try {\n const normalized = normalizePlatformString(req.params.platform)\n const slot = await findNextSlot(normalized)\n res.json({ platform: normalized, nextSlot: slot })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // GET /api/accounts — list connected Late accounts (cached)\n router.get('/api/accounts', async (req, res) => {\n try {\n const cached = getCached<LateAccount[]>('accounts')\n if (cached) return res.json({ accounts: cached })\n\n const client = new LateApiClient()\n const accounts = await client.listAccounts()\n setCache('accounts', accounts)\n res.json({ accounts })\n } catch (err) {\n res.status(500).json({ accounts: [], error: err instanceof Error ? err.message : 'Failed to fetch accounts' })\n }\n })\n\n // GET /api/profile — get Late profile info (cached)\n router.get('/api/profile', async (req, res) => {\n try {\n const cached = getCached<LateProfile | null>('profile')\n if (cached !== undefined) return res.json({ profile: cached })\n\n const client = new LateApiClient()\n const profiles = await client.listProfiles()\n const profile = profiles[0] || null\n setCache('profile', profile)\n res.json({ profile })\n } catch (err) {\n res.status(500).json({ profile: null, error: err instanceof Error ? err.message : 'Failed to fetch profile' })\n }\n })\n\n return router\n}\n","import { express } from '../core/http.js'\nimport { join, dirname, fileURLToPath } from '../core/paths.js'\nimport { createRouter } from './routes'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport interface ReviewServerOptions {\n port?: number\n}\n\nexport async function startReviewServer(options: ReviewServerOptions = {}): Promise<{ \n port: number\n close: () => Promise<void>\n}> {\n const app = express()\n const port = options.port || 3847\n\n // Middleware\n app.use(express.json())\n\n // API routes (has its own rate limiter)\n app.use(createRouter())\n\n // Serve media files from publish-queue and published directories\n const cfg = getConfig()\n const queueDir = join(cfg.OUTPUT_DIR, 'publish-queue')\n const publishedDir = join(cfg.OUTPUT_DIR, 'published')\n app.use('/media/queue', express.static(queueDir))\n app.use('/media/published', express.static(publishedDir))\n\n // Serve static frontend\n const publicDir = join(__dirname, 'public')\n app.use(express.static(publicDir))\n\n // SPA fallback — serve index.html for non-API routes\n app.get('/{*splat}', (req, res) => {\n if (!req.path.startsWith('/api/') && !req.path.startsWith('/media/')) {\n res.sendFile(join(publicDir, 'index.html'))\n }\n })\n\n // Start server with port retry logic\n return new Promise((resolve, reject) => {\n const tryPort = (p: number, attempts: number) => {\n const server = app.listen(p, '127.0.0.1', () => {\n logger.info(`Review server running at http://localhost:${p}`)\n\n // Track open connections so we can destroy them on shutdown\n const connections = new Set<import('net').Socket>()\n server.on('connection', (conn) => {\n connections.add(conn)\n conn.on('close', () => connections.delete(conn))\n })\n\n resolve({\n port: p,\n close: () => new Promise<void>((res) => {\n let done = false\n\n const finish = () => {\n if (done) return\n done = true\n res()\n }\n\n for (const conn of connections) conn.destroy()\n\n const timeout = setTimeout(() => {\n logger.warn('Timed out waiting for review server to close, forcing shutdown')\n finish()\n }, 2000)\n\n // Allow process to exit naturally even if timeout is pending\n timeout.unref()\n\n server.close(() => {\n clearTimeout(timeout)\n finish()\n })\n }),\n })\n })\n \n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && attempts < 5) {\n logger.warn(`Port ${p} in use, trying ${p + 1}...`)\n tryPort(p + 1, attempts + 1)\n } else {\n reject(err)\n }\n })\n }\n \n tryPort(port, 0)\n })\n}\n","import { Command } from './core/cli.js'\nimport { initConfig, validateRequiredKeys, getConfig } from './config/environment'\nimport type { CLIOptions } from './config/environment'\nimport { FileWatcher } from './services/fileWatcher'\nimport { processVideoSafe } from './pipeline'\nimport logger, { setVerbose } from './config/logger'\nimport { runDoctor } from './commands/doctor'\nimport { runInit } from './commands/init'\nimport { runSchedule } from './commands/schedule'\nimport { startReviewServer } from './review/server'\nimport { openUrl } from './core/cli.js'\nimport { readTextFileSync } from './core/fileSystem.js'\nimport { projectRoot, join, resolve } from './core/paths.js'\n\nconst pkg = JSON.parse(readTextFileSync(join(projectRoot(), 'package.json')))\n\nconst BANNER = `\n╔══════════════════════════════════════╗\n║ VidPipe v${pkg.version.padEnd(24)}║\n╚══════════════════════════════════════╝\n`\n\nconst program = new Command()\n\nprogram\n .name('vidpipe')\n .description('AI-powered video content pipeline: transcribe, summarize, generate shorts, captions, and social posts')\n .version(pkg.version, '-V, --version')\n\n// --- Subcommands ---\n\nprogram\n .command('init')\n .description('Interactive setup wizard — configure API keys, providers, and social publishing')\n .action(async () => {\n await runInit()\n process.exit(0)\n })\n\nprogram\n .command('review')\n .description('Open the social media post review app in your browser')\n .option('--port <number>', 'Server port (default: 3847)', '3847')\n .action(async (opts) => {\n initConfig()\n const parsedPort = Number.parseInt(opts.port, 10)\n if (Number.isNaN(parsedPort) || parsedPort < 1 || parsedPort > 65535) {\n console.error('Invalid --port value. Must be an integer between 1 and 65535.')\n process.exit(1)\n }\n const { port, close } = await startReviewServer({ port: parsedPort })\n await openUrl(`http://localhost:${port}`)\n console.log(`\\nReview app running at http://localhost:${port}`)\n console.log('Press Ctrl+C to stop.\\n')\n\n const shutdown = async () => {\n console.log('\\nShutting down...')\n // Restore terminal to normal mode on Windows\n if (process.platform === 'win32' && process.stdin.setRawMode) {\n process.stdin.setRawMode(false)\n }\n await close()\n process.exit(0)\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n // On Windows, listen for raw input since SIGINT is unreliable\n if (process.platform === 'win32') {\n process.stdin.resume()\n process.stdin.setRawMode?.(true)\n process.stdin.on('data', (data) => {\n // Ctrl-C is byte 0x03\n if (data[0] === 0x03) void shutdown()\n })\n }\n })\n\nprogram\n .command('schedule')\n .description('View the current posting schedule across platforms')\n .option('--platform <name>', 'Filter by platform (tiktok, youtube, instagram, linkedin, twitter)')\n .action(async (opts) => {\n await runSchedule({ platform: opts.platform })\n process.exit(0)\n })\n\nprogram\n .command('doctor')\n .description('Check all prerequisites and dependencies')\n .action(async () => {\n await runDoctor()\n })\n\n// --- Default command (process video or watch) ---\n// This must come after subcommands so they take priority\n\nconst defaultCmd = program\n .command('process', { isDefault: true })\n .argument('[video-path]', 'Path to a video file to process (implies --once)')\n .option('--watch-dir <path>', 'Folder to watch for new recordings (default: env WATCH_FOLDER)')\n .option('--output-dir <path>', 'Output directory for processed videos (default: ./recordings)')\n .option('--openai-key <key>', 'OpenAI API key (default: env OPENAI_API_KEY)')\n .option('--exa-key <key>', 'Exa AI API key for web search (default: env EXA_API_KEY)')\n .option('--once', 'Process a single video and exit (no watching)')\n .option('--brand <path>', 'Path to brand.json config (default: ./brand.json)')\n .option('--no-git', 'Skip git commit/push stage')\n .option('--no-silence-removal', 'Skip silence removal stage')\n .option('--no-shorts', 'Skip shorts generation')\n .option('--no-medium-clips', 'Skip medium clip generation')\n .option('--no-social', 'Skip social media post generation')\n .option('--no-captions', 'Skip caption generation/burning')\n .option('--no-social-publish', 'Skip social media publishing/queue-build stage')\n .option('--late-api-key <key>', 'Late API key (default: env LATE_API_KEY)')\n .option('--late-profile-id <id>', 'Late profile ID (default: env LATE_PROFILE_ID)')\n .option('-v, --verbose', 'Verbose logging')\n .option('--doctor', 'Check all prerequisites and exit')\n .action(async (videoPath: string | undefined) => {\n const opts = defaultCmd.opts()\n\n // Handle --doctor before anything else\n if (opts.doctor) {\n await runDoctor()\n process.exit(0)\n }\n\n const onceMode: boolean = opts.once || !!videoPath\n\n const cliOptions: CLIOptions = {\n watchDir: opts.watchDir,\n outputDir: opts.outputDir,\n openaiKey: opts.openaiKey,\n exaKey: opts.exaKey,\n brand: opts.brand,\n verbose: opts.verbose,\n git: opts.git,\n silenceRemoval: opts.silenceRemoval,\n shorts: opts.shorts,\n mediumClips: opts.mediumClips,\n social: opts.social,\n captions: opts.captions,\n socialPublish: opts.socialPublish,\n lateApiKey: opts.lateApiKey,\n lateProfileId: opts.lateProfileId,\n }\n\n logger.info(BANNER)\n initConfig(cliOptions)\n if (opts.verbose) setVerbose()\n validateRequiredKeys()\n\n const config = getConfig()\n logger.info(`Watch folder: ${config.WATCH_FOLDER}`)\n logger.info(`Output dir: ${config.OUTPUT_DIR}`)\n\n // Direct file mode\n if (videoPath) {\n const resolvedPath = resolve(videoPath)\n logger.info(`Processing single video: ${resolvedPath}`)\n await processVideoSafe(resolvedPath)\n logger.info('Done.')\n process.exit(0)\n }\n\n // Watch mode\n const watcher = new FileWatcher()\n let processing = false\n let shutdownRequested = false\n const queue: string[] = []\n\n async function processQueue(): Promise<void> {\n if (processing || queue.length === 0) return\n processing = true\n try {\n while (queue.length > 0) {\n const vp = queue.shift()!\n logger.info(`Processing video: ${vp}`)\n await processVideoSafe(vp)\n if (onceMode) {\n logger.info('--once flag set, exiting after first video.')\n await shutdown()\n return\n }\n if (shutdownRequested) break\n }\n } finally {\n processing = false\n }\n }\n\n async function shutdown(): Promise<void> {\n if (shutdownRequested) return\n shutdownRequested = true\n logger.info('Shutting down...')\n watcher.stop()\n while (processing) await new Promise(r => setTimeout(r, 500))\n logger.info('Goodbye.')\n process.exit(0)\n }\n\n process.on('SIGINT', () => shutdown())\n process.on('SIGTERM', () => shutdown())\n\n watcher.on('new-video', (filePath: string) => {\n queue.push(filePath)\n logger.info(`Queued video: ${filePath} (queue length: ${queue.length})`)\n processQueue().catch(err => logger.error('Queue processing error:', err))\n })\n watcher.start()\n\n if (onceMode) {\n logger.info('Running in --once mode. Will exit after processing the next video.')\n } else {\n logger.info('Watching for new videos. Press Ctrl+C to stop.')\n }\n })\n\nprogram.parse()\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,OAAO,cAAc;AACrB,OAAO,UAAU;AAMV,SAAS,wBAAwB,MAAqD;AAC3F,SAAO,SAAS,gBAAgB,QAAQ,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC1F;AAGA,eAAsB,QAAQ,KAA4B;AACxD,QAAM,KAAK,GAAG;AAChB;;;ACdA,SAAS,MAAM,SAAS,SAAS,UAAU,SAAS,OAAO,KAAK,UAAU,iBAAiB;AAC3F,SAAS,qBAAqB;AAG9B,OAAO,aAAa;AAGpB,SAAS,kBAAkB;AAC3B,SAAS,QAAAA,OAAM,WAAAC,UAAS,WAAAC,gBAAsB;AAC9C,SAAS,iBAAAC,sBAAqB;AAE9B,IAAM,YAAYC,SAAQC,eAAc,YAAY,GAAG,CAAC;AAMjD,SAAS,SAAS,UAA0B;AACjD,MAAI,MAAMC,SAAQ,QAAQ;AAC1B,SAAO,MAAM;AACX,QAAI,WAAWC,MAAK,KAAK,cAAc,CAAC,EAAG,QAAO;AAClD,UAAM,SAASH,SAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK,OAAM,IAAI,MAAM,oCAAoC,QAAQ,EAAE;AAClF,UAAM;AAAA,EACR;AACF;AAEA,IAAI;AAGG,SAAS,cAAsB;AACpC,MAAI,CAAC,YAAa,eAAc,SAAS,SAAS;AAClD,SAAO;AACT;AAGO,SAAS,aAAa,UAA4B;AACvD,SAAOG,MAAK,YAAY,GAAG,UAAU,GAAG,QAAQ;AAClD;AAMO,SAAS,WAAmB;AACjC,QAAM,UAAUD,SAAQ,YAAY,GAAG,QAAQ,OAAO;AACtD,SAAO,WAAW,OAAO,IAAI,UAAU,UAAU,OAAO;AAC1D;AAMO,SAAS,YAAoB;AAClC,QAAM,UAAUA,SAAQ,YAAY,GAAG,QAAQ,QAAQ;AACvD,SAAO,WAAW,OAAO,IAAI,UAAU,UAAU,QAAQ;AAC3D;;;ACzDA;AAAA,EACE,YAAY;AAAA,EACZ,cAAAE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,OAAO,QAAQ;AAsRf,SAAoB,WAAXC,gBAAsB;AA3P/B,eAAsB,aAAa,UAAmC;AACpE,MAAI;AACF,WAAO,MAAM,IAAI,SAAS,UAAU,OAAO;AAAA,EAC7C,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAAA,IAC/C;AACA,UAAM;AAAA,EACR;AACF;AAGO,SAAS,iBAAiB,UAA0B;AACzD,MAAI;AACF,WAAO,aAAa,UAAU,OAAO;AAAA,EACvC,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAAA,IAC/C;AACA,UAAM;AAAA,EACR;AACF;AAGA,eAAsB,cAAc,SAAoC;AACtE,MAAI;AACF,WAAO,MAAM,IAAI,QAAQ,OAAO;AAAA,EAClC,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,wBAAwB,OAAO,EAAE;AAAA,IACnD;AACA,UAAM;AAAA,EACR;AACF;AAGA,eAAsB,uBAAuB,SAAoC;AAC/E,MAAI;AACF,WAAO,MAAM,IAAI,QAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;AAAA,EAC3D,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,wBAAwB,OAAO,EAAE;AAAA,IACnD;AACA,UAAM;AAAA,EACR;AACF;AAGO,SAAS,kBAAkB,SAA2B;AAC3D,MAAI;AACF,WAAO,YAAY,OAAO;AAAA,EAC5B,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,wBAAwB,OAAO,EAAE;AAAA,IACnD;AACA,UAAM;AAAA,EACR;AACF;AAGA,eAAsB,WAAW,UAAoC;AACnE,MAAI;AACF,UAAM,IAAI,KAAK,QAAQ;AACvB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,eAAe,UAA2B;AACxD,SAAOC,YAAW,QAAQ;AAC5B;AAGA,eAAsB,aAAa,UAAkC;AACnE,MAAI;AACF,WAAO,MAAM,IAAI,KAAK,QAAQ;AAAA,EAChC,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAAA,IAC/C;AACA,UAAM;AAAA,EACR;AACF;AAGO,SAAS,iBAAiB,UAAyB;AACxD,MAAI;AACF,WAAO,SAAS,QAAQ;AAAA,EAC1B,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,UAAU;AACrD,YAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAAA,IAC/C;AACA,UAAM;AAAA,EACR;AACF;AAGO,SAAS,eAAe,UAA8B;AAC3D,SAAO,iBAAiB,QAAQ;AAClC;AAKA,eAAsB,cAAc,UAAkB,MAA8B;AAClF,QAAM,IAAI,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,QAAM,IAAI,UAAU,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AACtE;AAGA,eAAsB,cAAc,UAAkB,SAAgC;AACpF,MAAI,OAAO,YAAY,SAAU,OAAM,IAAI,UAAU,0BAA0B;AAC/E,QAAM,IAAI,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,QAAM,IAAI,UAAU,UAAU,SAAS,OAAO;AAChD;AAGO,SAAS,kBAAkB,UAAkB,SAAuB;AACzE,MAAI,OAAO,YAAY,SAAU,OAAM,IAAI,UAAU,0BAA0B;AAC/E,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,SAAS,OAAO;AAC1C;AAGA,eAAsB,gBAAgB,SAAgC;AACpE,QAAM,IAAI,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAC9C;AAGO,SAAS,oBAAoB,SAAuB;AACzD,YAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC;AAGA,eAAsB,SAAS,KAAa,MAA6B;AACvE,QAAM,IAAI,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,QAAM,IAAI,SAAS,KAAK,IAAI;AAC9B;AAkBA,eAAsB,WAAW,UAAiC;AAChE,MAAI;AACF,UAAM,IAAI,OAAO,QAAQ;AAAA,EAC3B,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,SAAU;AACvD,UAAM;AAAA,EACR;AACF;AAGA,eAAsB,gBACpB,SACA,MACe;AACf,MAAI;AACF,UAAM,IAAI,GAAG,SAAS,EAAE,WAAW,MAAM,aAAa,OAAO,OAAO,MAAM,SAAS,MAAM,CAAC;AAAA,EAC5F,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,SAAU;AACvD,UAAM;AAAA,EACR;AACF;AAGO,SAAS,gBAAgB,UAA+B;AAC7D,SAAO,kBAAkB,QAAQ;AACnC;AAGO,SAAS,oBAAoB,IAAkB;AACpD,YAAU,EAAE;AACd;AAKA,eAAsB,YAAY,QAAiC;AACjE,SAAO,IAAI,QAAQ,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AAC9C;AAaA,eAAsB,WAAW,SAAiB,SAAgC;AAChF,MAAI;AACF,UAAM,IAAI,OAAO,SAAS,OAAO;AAAA,EACnC,SAAS,KAAc;AACrB,QAAK,KAA+B,SAAS,SAAS;AACpD,YAAM,SAAS,SAAS,OAAO;AAC/B,YAAM,WAAW,OAAO;AAAA,IAC1B,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAGA,eAAsB,cAAc,KAAa,MAA6B;AAC5E,QAAM,IAAI,GAAG,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC;AAC7C;AAGA,eAAsB,aACpB,UACA,MACA,MACe;AACf,QAAM,IAAI,UAAU,UAAU,MAAM,IAAI;AAC1C;;;AChRA,OAAO,YAAY;AAGZ,SAAS,YAAYC,UAAwB;AAClD,SAAO,OAAOA,WAAU,EAAE,MAAMA,SAAQ,IAAI,MAAS;AACvD;;;ACAA,IAAM,UAAU,KAAK,QAAQ,IAAI,GAAG,MAAM;AAC1C,IAAI,eAAe,OAAO,GAAG;AAC3B,cAAY,OAAO;AACrB;AA6CA,IAAI,SAAgC;AAE7B,SAAS,uBAA6B;AAC3C,MAAI,CAAC,QAAQ,kBAAkB,CAAC,QAAQ,IAAI,gBAAgB;AAC1D,UAAM,IAAI,MAAM,oEAAoE;AAAA,EACtF;AACF;AAGO,SAAS,WAAW,MAAkB,CAAC,GAAmB;AAC/D,QAAM,WAAW,QAAQ,IAAI,aAAa,QAAQ,IAAI;AAEtD,WAAS;AAAA,IACP,gBAAgB,IAAI,aAAa,QAAQ,IAAI,kBAAkB;AAAA,IAC/D,cAAc,IAAI,YAAY,QAAQ,IAAI,gBAAgB,KAAK,UAAU,OAAO;AAAA,IAChF,WAAW;AAAA,IACX,aAAa,QAAQ,IAAI,eAAe;AAAA;AAAA,IACxC,cAAc,QAAQ,IAAI,gBAAgB;AAAA;AAAA,IAC1C,aAAa,IAAI,UAAU,QAAQ,IAAI,eAAe;AAAA,IACtD,aAAa,QAAQ,IAAI,eAAe;AAAA,IACxC,cAAc,QAAQ,IAAI,gBAAgB;AAAA,IAC1C,WAAW,QAAQ,IAAI,aAAa;AAAA,IACpC,mBAAmB,QAAQ,IAAI,qBAAqB;AAAA,IACpD,YAAW,IAAI,aAAa,QAAQ,IAAI,cAAc,KAAK,UAAU,YAAY;AAAA,IACjF,YAAY,IAAI,SAAS,QAAQ,IAAI,cAAc,KAAK,UAAU,YAAY;AAAA,IAC9E,SAAS,IAAI,WAAW;AAAA,IACxB,UAAU,IAAI,QAAQ;AAAA,IACtB,sBAAsB,IAAI,mBAAmB;AAAA,IAC7C,aAAa,IAAI,WAAW;AAAA,IAC5B,mBAAmB,IAAI,gBAAgB;AAAA,IACvC,aAAa,IAAI,WAAW;AAAA,IAC5B,eAAe,IAAI,aAAa;AAAA,IAChC,cAAc,IAAI,cAAc,QAAQ,IAAI,gBAAgB;AAAA,IAC5D,iBAAiB,IAAI,iBAAiB,QAAQ,IAAI,mBAAmB;AAAA,IACrE,qBAAqB,IAAI,kBAAkB;AAAA,EAC7C;AAEA,SAAO;AACT;AAEO,SAAS,YAA4B;AAC1C,MAAI,QAAQ;AACV,WAAO;AAAA,EACT;AAGA,SAAO,WAAW;AACpB;;;ACpGA,SAAS,aAA6B;AACtC,SAAS,oBAAoB;;;ACD7B,OAAO,aAAa;AAmBpB,IAAM,SAAS,QAAQ,aAAa;AAAA,EAClC,OAAO;AAAA,EACP,QAAQ,QAAQ,OAAO;AAAA,IACrB,QAAQ,OAAO,UAAU;AAAA,IACzB,QAAQ,OAAO,OAAO,CAAC,EAAE,WAAW,OAAO,QAAQ,MAAM;AACvD,aAAO,GAAG,SAAS,KAAK,MAAM,YAAY,CAAC,MAAM,OAAO;AAAA,IAC1D,CAAC;AAAA,EACH;AAAA,EACA,YAAY,CAAC,IAAI,QAAQ,WAAW,QAAQ,CAAC;AAC/C,CAAC;AAEM,SAAS,aAAmB;AACjC,SAAO,QAAQ;AACjB;AAEA,IAAO,iBAAQ;;;ACvBR,IAAM,cAAN,MAAM,qBAAoB,aAAa;AAAA,EACpC;AAAA,EACA,UAA4B;AAAA,EAC5B;AAAA,EAER,YAAY,UAA8B,CAAC,GAAG;AAC5C,UAAM;AACN,UAAMC,UAAS,UAAU;AACzB,SAAK,cAAcA,QAAO;AAC1B,SAAK,kBAAkB,QAAQ,mBAAmB;AAElD,QAAI,CAAC,eAAe,KAAK,WAAW,GAAG;AACrC,0BAAoB,KAAK,WAAW;AACpC,qBAAO,KAAK,yBAAyB,KAAK,WAAW,EAAE;AAAA,IACzD;AAAA,EACF;AAAA,EAEA,OAAwB,gBAAgB,OAAO;AAAA;AAAA,EAC/C,OAAwB,wBAAwB;AAAA;AAAA,EAGhD,MAAc,aAAa,UAAoC;AAC7D,QAAI;AACF,YAAM,aAAa,iBAAiB,QAAQ,EAAE;AAC9C,YAAM,IAAI,QAAQ,CAACC,aAAY,WAAWA,UAAS,aAAY,qBAAqB,CAAC;AACrF,YAAM,YAAY,iBAAiB,QAAQ,EAAE;AAC7C,aAAO,eAAe;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,mBAAmB,UAAiC;AAChE,QAAI,QAAQ,QAAQ,EAAE,YAAY,MAAM,QAAQ;AAC9C,qBAAO,MAAM,oCAAoC,QAAQ,EAAE;AAC3D;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,iBAAW,iBAAiB,QAAQ,EAAE;AAAA,IACxC,SAAS,KAAK;AACZ,qBAAO,KAAK,0DAA0D,QAAQ,EAAE;AAChF;AAAA,IACF;AAEA,mBAAO,MAAM,yBAAyB,WAAW,OAAO,MAAM,QAAQ,CAAC,CAAC,cAAS,QAAQ,EAAE;AAC3F,QAAI,WAAW,aAAY,eAAe;AACxC,qBAAO,KAAK,wBAAwB,QAAQ,uCAAuC,QAAQ,EAAE;AAC7F;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,KAAK,aAAa,QAAQ;AAC/C,QAAI,CAAC,QAAQ;AACX,qBAAO,KAAK,kDAAkD,QAAQ,EAAE;AACxE;AAAA,IACF;AAEA,mBAAO,KAAK,uBAAuB,QAAQ,EAAE;AAC7C,SAAK,KAAK,aAAa,QAAQ;AAAA,EACjC;AAAA,EAEQ,oBAA0B;AAChC,QAAI;AACJ,QAAI;AACF,cAAQ,kBAAkB,KAAK,WAAW;AAAA,IAC5C,SAAS,KAAU;AACjB,UAAI,KAAK,SAAS,UAAU;AAC1B,uBAAO,KAAK,+CAA+C,KAAK,WAAW,EAAE;AAC7E;AAAA,MACF;AACA,YAAM;AAAA,IACR;AACA,eAAW,QAAQ,OAAO;AACxB,UAAI,QAAQ,IAAI,EAAE,YAAY,MAAM,QAAQ;AAC1C,cAAM,WAAW,KAAK,KAAK,aAAa,IAAI;AAC5C,aAAK,mBAAmB,QAAQ,EAAE;AAAA,UAAM,SACtC,eAAO,MAAM,oBAAoB,QAAQ,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,QAClG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,UAAU,MAAM,KAAK,aAAa;AAAA,MACrC,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,OAAO;AAAA,MACP,QAAQ;AAAA;AAAA,MAER,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,kBAAkB;AAAA,QAChB,oBAAoB;AAAA,QACpB,cAAc;AAAA,MAChB;AAAA,IACF,CAAC;AAED,SAAK,QAAQ,GAAG,OAAO,CAAC,aAAqB;AAC3C,qBAAO,MAAM,0BAA0B,QAAQ,EAAE;AACjD,WAAK,mBAAmB,QAAQ,EAAE;AAAA,QAAM,SACtC,eAAO,MAAM,oBAAoB,QAAQ,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MAClG;AAAA,IACF,CAAC;AAED,SAAK,QAAQ,GAAG,UAAU,CAAC,aAAqB;AAC9C,qBAAO,MAAM,6BAA6B,QAAQ,EAAE;AACpD,UAAI,QAAQ,QAAQ,EAAE,YAAY,MAAM,OAAQ;AAChD,qBAAO,KAAK,kCAAkC,QAAQ,EAAE;AACxD,WAAK,mBAAmB,QAAQ,EAAE;AAAA,QAAM,SACtC,eAAO,MAAM,oBAAoB,QAAQ,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MAClG;AAAA,IACF,CAAC;AAED,SAAK,QAAQ,GAAG,UAAU,CAAC,aAAqB;AAC9C,qBAAO,MAAM,6BAA6B,QAAQ,EAAE;AAAA,IACtD,CAAC;AAED,SAAK,QAAQ,GAAG,OAAO,CAAC,OAAe,SAAiB,YAAqB;AAC3E,qBAAO,MAAM,uBAAuB,KAAK,SAAS,OAAO,EAAE;AAAA,IAC7D,CAAC;AAED,SAAK,QAAQ,GAAG,SAAS,CAAC,UAAmB;AAC3C,qBAAO,MAAM,uBAAuB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAAA,IAC9F,CAAC;AAED,SAAK,QAAQ,GAAG,SAAS,MAAM;AAC7B,qBAAO,KAAK,6CAA6C;AACzD,UAAI,KAAK,iBAAiB;AACxB,aAAK,kBAAkB;AAAA,MACzB;AAAA,IACF,CAAC;AAED,mBAAO,KAAK,mCAAmC,KAAK,WAAW,EAAE;AAAA,EACnE;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,MAAM;AACnB,WAAK,UAAU;AACf,qBAAO,KAAK,sBAAsB;AAAA,IACpC;AAAA,EACF;AACF;;;AC1JA,OAAO,gBAAgB;AACvB,SAAS,MAAM,cAAc;AAGtB,SAAS,QAAQ,MAAc,MAA4E;AAChH,SAAO,WAAW,MAAM,EAAE,OAAO,MAAM,QAAQ,MAAM,GAAG,KAAK,CAAC;AAChE;AAGO,SAAS,aAAqB;AACnC,SAAO,OAAO;AAChB;;;ACXA,OAAO,eAAe;AACtB,SAAS,qBAAqB;AAC9B,SAAS,cAAAC,mBAAkB;AAI3B,IAAMC,WAAU,cAAc,YAAY,GAAG;AAGtC,SAAS,gBAAwB;AACtC,QAAMC,UAAS,UAAU;AACzB,MAAIA,QAAO,eAAeA,QAAO,gBAAgB,UAAU;AACzD,mBAAO,MAAM,qCAAqCA,QAAO,WAAW,EAAE;AACtE,WAAOA,QAAO;AAAA,EAChB;AACA,MAAI;AACF,UAAM,aAAaD,SAAQ,eAAe;AAC1C,QAAI,cAAcE,YAAW,UAAU,GAAG;AACxC,qBAAO,MAAM,gCAAgC,UAAU,EAAE;AACzD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAAoC;AAC5C,iBAAO,MAAM,qCAAqC;AAClD,SAAO;AACT;AAGO,SAAS,iBAAyB;AACvC,QAAMD,UAAS,UAAU;AACzB,MAAIA,QAAO,gBAAgBA,QAAO,iBAAiB,WAAW;AAC5D,mBAAO,MAAM,uCAAuCA,QAAO,YAAY,EAAE;AACzE,WAAOA,QAAO;AAAA,EAChB;AACA,MAAI;AACF,UAAM,EAAE,MAAM,UAAU,IAAID,SAAQ,4BAA4B;AAChE,QAAI,aAAaE,YAAW,SAAS,GAAG;AACtC,qBAAO,MAAM,8CAA8C,SAAS,EAAE;AACtE,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAAiD;AACzD,iBAAO,MAAM,sCAAsC;AACnD,SAAO;AACT;AAGO,SAAS,aAAa,OAAyC;AACpE,QAAM,MAAM,QAAQ,UAAU,KAAK,IAAI,UAAU;AACjD,MAAI,cAAc,cAAc,CAAC;AACjC,MAAI,eAAe,eAAe,CAAC;AACnC,SAAO;AACT;AAGO,SAAS,QAAQ,UAAkD;AACxE,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,cAAU,eAAe,eAAe,CAAC;AACzC,cAAU,QAAQ,UAAU,CAAC,KAAK,SAAS;AACzC,UAAI,IAAK,QAAO,GAAG;AAAA,UACd,CAAAA,SAAQ,IAAI;AAAA,IACnB,CAAC;AAAA,EACH,CAAC;AACH;;;ACrDA,eAAe,iBAAiB,UAAiD;AAC/E,QAAM,WAAW,MAAM,QAAQ,QAAQ;AACvC,SAAO,EAAE,UAAU,SAAS,OAAO,YAAY,EAAE;AACnD;AAEA,eAAsB,YAAY,YAAwC;AACxE,QAAMC,UAAS,UAAU;AACzB,QAAM,WAAW,SAAS,YAAY,QAAQ,UAAU,CAAC;AACzD,QAAM,OAAO,QAAQ,UAAU,EAAE,OAAO,KAAK,CAAC;AAE9C,QAAM,gBAAgB,KAAKA,QAAO,YAAY,IAAI;AAClD,QAAM,gBAAgB,KAAK,eAAe,YAAY;AACtD,QAAM,YAAY,KAAK,eAAe,QAAQ;AAC9C,QAAM,iBAAiB,KAAK,eAAe,cAAc;AAEzD,iBAAO,KAAK,oBAAoB,UAAU,WAAM,IAAI,EAAE;AAGtD,MAAI,eAAe,aAAa,GAAG;AACjC,mBAAO,KAAK,8DAA8D,aAAa,EAAE;AAEzF,UAAM,UAAU,CAAC,cAAc,UAAU,gBAAgB,YAAY,gBAAgB,UAAU;AAC/F,eAAW,OAAO,SAAS;AACzB,YAAM,gBAAgB,KAAK,eAAe,GAAG,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAClF;AAEA,UAAM,gBAAgB;AAAA,MACpB;AAAA,MAAmB;AAAA,MACnB;AAAA,MAAgB;AAAA,MAAgB;AAAA,MAChC;AAAA,MAAc;AAAA,MAAgB;AAAA,IAChC;AACA,eAAW,WAAW,eAAe;AACnC,YAAM,WAAW,KAAK,eAAe,OAAO,CAAC;AAAA,IAC/C;AAEA,UAAM,QAAQ,MAAM,cAAc,aAAa;AAC/C,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,SAAS,aAAa,KAAK,KAAK,SAAS,gBAAgB,GAAG;AACnE,cAAM,WAAW,KAAK,eAAe,IAAI,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,aAAa;AACnC,QAAM,gBAAgB,aAAa;AACnC,QAAM,gBAAgB,SAAS;AAC/B,QAAM,gBAAgB,cAAc;AAEpC,QAAM,eAAe,GAAG,IAAI;AAC5B,QAAM,WAAW,KAAK,eAAe,YAAY;AAEjD,MAAI,YAAY;AAChB,MAAI;AACF,UAAM,YAAY,MAAM,aAAa,QAAQ;AAC7C,UAAM,WAAW,MAAM,aAAa,UAAU;AAC9C,QAAI,UAAU,SAAS,SAAS,MAAM;AACpC,qBAAO,KAAK,iDAAiD;AAC7D,kBAAY;AAAA,IACd;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI,WAAW;AACb,UAAM,IAAI,QAAc,CAACC,UAAS,WAAW;AAC3C,YAAM,aAAa,eAAe,UAAU;AAC5C,YAAM,cAAc,gBAAgB,QAAQ;AAC5C,iBAAW,GAAG,SAAS,MAAM;AAC7B,kBAAY,GAAG,SAAS,MAAM;AAC9B,kBAAY,GAAG,UAAUA,QAAO;AAChC,iBAAW,KAAK,WAAW;AAAA,IAC7B,CAAC;AACD,mBAAO,KAAK,mBAAmB,QAAQ,EAAE;AAAA,EAC3C;AAEA,MAAI,WAAW;AACf,MAAI;AACF,UAAM,OAAO,MAAM,iBAAiB,QAAQ;AAC5C,eAAW,KAAK;AAAA,EAClB,SAAS,KAAK;AACZ,mBAAO,KAAK,yDAAyD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EACzH;AACA,QAAM,QAAQ,MAAM,aAAa,QAAQ;AAEzC,iBAAO,KAAK,4BAA4B,QAAQ,WAAW,MAAM,IAAI,QAAQ;AAE7E,SAAO;AAAA,IACL,cAAc;AAAA,IACd,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,WAAW,oBAAI,KAAK;AAAA,EACtB;AACF;;;AC1FA,eAAsB,aACpB,WACA,YACA,UAA+B,CAAC,GACf;AACjB,QAAM,EAAE,SAAS,MAAM,IAAI;AAC3B,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAE/B,iBAAO,KAAK,qBAAqB,MAAM,MAAM,SAAS,WAAM,UAAU,EAAE;AAExE,SAAO,IAAI,QAAgB,CAACC,UAAS,WAAW;AAC9C,UAAM,UAAU,aAAa,SAAS,EAAE,QAAQ,EAAE,cAAc,CAAC;AAEjE,QAAI,WAAW,OAAO;AACpB,cAAQ,WAAW,YAAY,EAAE,aAAa,KAAK,EAAE,eAAe,IAAK;AAAA,IAC3E,OAAO;AACL,cAAQ,WAAW,WAAW,EAAE,eAAe,IAAK;AAAA,IACtD;AAEA,YACG,OAAO,UAAU,EACjB,GAAG,OAAO,MAAM;AACf,qBAAO,KAAK,8BAA8B,UAAU,EAAE;AACtD,MAAAA,SAAQ,UAAU;AAAA,IACpB,CAAC,EACA,GAAG,SAAS,CAAC,QAAQ;AACpB,qBAAO,MAAM,4BAA4B,IAAI,OAAO,EAAE;AACtD,aAAO,IAAI,MAAM,4BAA4B,IAAI,OAAO,EAAE,CAAC;AAAA,IAC7D,CAAC,EACA,IAAI;AAAA,EACT,CAAC;AACH;AAOA,eAAsB,qBACpB,WACA,iBAAyB,IACN;AACnB,QAAM,QAAQ,MAAM,aAAa,SAAS;AAC1C,QAAM,aAAa,MAAM,QAAQ,OAAO;AAExC,MAAI,cAAc,gBAAgB;AAChC,WAAO,CAAC,SAAS;AAAA,EACnB;AAEA,QAAM,WAAW,MAAM,iBAAiB,SAAS;AACjD,QAAM,YAAY,KAAK,KAAK,aAAa,cAAc;AACvD,QAAM,gBAAgB,WAAW;AAEjC,QAAM,MAAM,QAAQ,SAAS;AAC7B,QAAM,OAAO,UAAU,MAAM,GAAG,CAAC,IAAI,MAAM;AAC3C,QAAM,aAAuB,CAAC;AAE9B,iBAAO;AAAA,IACL,aAAa,WAAW,QAAQ,CAAC,CAAC,iBAAiB,SAAS,aACvD,cAAc,QAAQ,CAAC,CAAC;AAAA,EAC/B;AAEA,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,YAAY,IAAI;AACtB,UAAM,YAAY,GAAG,IAAI,SAAS,CAAC,GAAG,GAAG;AACzC,eAAW,KAAK,SAAS;AAEzB,UAAM,IAAI,QAAc,CAACA,UAAS,WAAW;AAC3C,YAAM,MAAM,aAAa,SAAS,EAC/B,aAAa,SAAS,EACtB,YAAY,aAAa,EACzB,WAAW,MAAM,EACjB,OAAO,SAAS,EAChB,GAAG,OAAO,MAAMA,SAAQ,CAAC,EACzB,GAAG,SAAS,CAAC,QAAQ,OAAO,IAAI,MAAM,uBAAuB,IAAI,OAAO,EAAE,CAAC,CAAC;AAC/E,UAAI,IAAI;AAAA,IACV,CAAC;AAED,mBAAO,KAAK,iBAAiB,IAAI,CAAC,IAAI,SAAS,KAAK,SAAS,EAAE;AAAA,EACjE;AAEA,SAAO;AACT;AAGA,eAAe,iBAAiB,WAAoC;AAClE,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,SAAS;AACxC,WAAO,SAAS,OAAO,YAAY;AAAA,EACrC,SAAS,KAAU;AACjB,UAAM,IAAI,MAAM,mBAAmB,IAAI,OAAO,EAAE;AAAA,EAClD;AACF;;;AC3GA,SAAoB,WAAXC,gBAAyB;AAElC,SAAoB,WAAXA,gBAA4B;AACrC,SAAS,eAAe,sBAAsB;;;AC4B9C,IAAM,eAA4B;AAAA,EAChC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR,SAAS,CAAC;AAAA,IACV,WAAW,CAAC;AAAA,IACZ,QAAQ,CAAC;AAAA,EACX;AAAA,EACA,kBAAkB,CAAC;AAAA,EACnB,UAAU;AAAA,IACR,QAAQ,CAAC;AAAA,IACT,WAAW,CAAC;AAAA,IACZ,WAAW,CAAC;AAAA,EACd;AAAA,EACA,mBAAmB;AAAA,IACjB,aAAa;AAAA,IACb,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AACF;AAEA,IAAI,cAAkC;AAGtC,SAAS,oBAAoB,OAAmC;AAC9D,QAAM,kBAAyC,CAAC,QAAQ,UAAU,SAAS;AAC3E,aAAW,SAAS,iBAAiB;AACnC,QAAI,CAAC,MAAM,KAAK,GAAG;AACjB,qBAAO,KAAK,uCAAuC,KAAK,GAAG;AAAA,IAC7D;AAAA,EACF;AAEA,QAAM,kBAAmE;AAAA,IACvE,EAAE,KAAK,SAAS,SAAS,CAAC,QAAQ,eAAe,OAAO,EAAE;AAAA,IAC1D,EAAE,KAAK,YAAY,SAAS,CAAC,WAAW,WAAW,EAAE;AAAA,IACrD,EAAE,KAAK,YAAY,SAAS,CAAC,UAAU,WAAW,EAAE;AAAA,IACpD,EAAE,KAAK,qBAAqB,SAAS,CAAC,eAAe,aAAa,aAAa,EAAE;AAAA,EACnF;AAEA,aAAW,EAAE,KAAK,QAAQ,KAAK,iBAAiB;AAC9C,QAAI,CAAC,MAAM,GAAG,GAAG;AACf,qBAAO,KAAK,gCAAgC,GAAG,GAAG;AAAA,IACpD,OAAO;AACL,YAAM,UAAU,MAAM,GAAG;AACzB,iBAAW,OAAO,SAAS;AACzB,YAAI,CAAC,QAAQ,GAAG,KAAM,MAAM,QAAQ,QAAQ,GAAG,CAAC,KAAM,QAAQ,GAAG,EAAgB,WAAW,GAAI;AAC9F,yBAAO,KAAK,uCAAuC,GAAG,IAAI,GAAG,GAAG;AAAA,QAClE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,MAAM,oBAAoB,MAAM,iBAAiB,WAAW,GAAG;AAClE,mBAAO,KAAK,6EAAwE;AAAA,EACtF;AACF;AAEO,SAAS,iBAA8B;AAC5C,MAAI,YAAa,QAAO;AAExB,QAAMC,UAAS,UAAU;AACzB,QAAM,YAAYA,QAAO;AAEzB,MAAI,CAAC,eAAe,SAAS,GAAG;AAC9B,mBAAO,KAAK,4CAAuC;AACnD,kBAAc,EAAE,GAAG,aAAa;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,iBAAiB,SAAS;AACtC,gBAAc,KAAK,MAAM,GAAG;AAC5B,sBAAoB,WAAW;AAC/B,iBAAO,KAAK,wBAAwB,YAAY,IAAI,EAAE;AACtD,SAAO;AACT;AAGO,SAAS,mBAA2B;AACzC,QAAM,QAAQ,eAAe;AAC7B,SAAO,MAAM,iBAAiB,KAAK,IAAI;AACzC;;;AClGO,IAAM,2BAA2B;AAEjC,IAAM,gBAA8C;AAAA;AAAA,EAEzD,UAAU,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,GAAG,iBAAiB,KAAK;AAAA,EAC1F,eAAe,EAAE,YAAY,MAAM,aAAa,KAAM,eAAe,GAAG,iBAAiB,KAAK;AAAA,EAC9F,WAAW,EAAE,YAAY,GAAM,aAAa,GAAM,eAAe,GAAG,iBAAiB,KAAK;AAAA,EAC1F,gBAAgB,EAAE,YAAY,KAAM,aAAa,IAAK;AAAA,EACtD,cAAc,EAAE,YAAY,MAAM,aAAa,KAAM,eAAe,GAAG,iBAAiB,KAAK;AAAA,EAC7F,SAAS,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EAClE,eAAe,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EACxE,WAAW,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EACpE,iBAAiB,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EAC1E,qBAAqB,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EAC9E,sBAAsB,EAAE,YAAY,MAAM,aAAa,KAAM,eAAe,KAAK;AAAA,EACjF,WAAW,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EACpE,iBAAiB,EAAE,YAAY,KAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EAC1E,MAAM,EAAE,YAAY,IAAO,aAAa,IAAO,eAAe,EAAE;AAAA,EAChE,gBAAgB,EAAE,YAAY,KAAM,aAAa,KAAM,eAAe,GAAG;AAAA;AAAA,EAGzE,oBAAoB,EAAE,YAAY,KAAM,aAAa,GAAM,eAAe,KAAK;AAAA,EAC/E,mBAAmB,EAAE,YAAY,GAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EAC5E,qBAAqB,EAAE,YAAY,GAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EAC9E,mBAAmB,EAAE,YAAY,IAAO,aAAa,IAAO,eAAe,EAAE;AAAA,EAC7E,mBAAmB,EAAE,YAAY,GAAM,aAAa,IAAO,eAAe,EAAE;AAAA,EAC5E,wBAAwB,EAAE,YAAY,GAAM,aAAa,IAAO,eAAe,EAAE;AAAA;AAAA,EAGjF,kBAAkB,EAAE,YAAY,MAAM,aAAa,GAAM,eAAe,EAAE;AAAA,EAC1E,kBAAkB,EAAE,YAAY,KAAM,aAAa,KAAM,eAAe,KAAK;AAAA,EAC7E,gBAAgB,EAAE,YAAY,MAAM,aAAa,GAAM,eAAe,EAAE;AAC1E;AAMO,SAAS,mBACd,OACA,aACA,cACQ;AACR,QAAM,UAAU,gBAAgB,KAAK;AACrC,MAAI,CAAC,WAAY,CAAC,QAAQ,cAAc,CAAC,QAAQ,YAAc,QAAO;AAEtE,QAAM,aAAc,QAAQ,cAAc,KAAK,MAAa;AAC5D,QAAM,cAAe,QAAQ,eAAe,KAAK,MAAa;AAC9D,SAAO,YAAY;AACrB;AAMO,SAAS,iBAAiB,OAAuB;AACtD,QAAM,UAAU,gBAAgB,KAAK;AACrC,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,QAAQ,gBAAiB,QAAO;AACpC,SAAO,QAAQ,iBAAiB;AAClC;AAKO,SAAS,gBAAgB,OAAyC;AAEvE,SAAO,cAAc,KAAK,KACxB,cAAc,MAAM,YAAY,CAAC,KACjC,OAAO,QAAQ,aAAa,EAAE;AAAA,IAAK,CAAC,CAAC,GAAG,MACtC,MAAM,YAAY,EAAE,SAAS,IAAI,YAAY,CAAC;AAAA,EAChD,IAAI,CAAC;AACT;;;ACjDA,IAAM,cAAN,MAAkB;AAAA,EACR,UAAyB,CAAC;AAAA,EAC1B,iBAAuC,CAAC;AAAA,EACxC;AAAA,EACA,eAAe;AAAA,EACf,eAAe;AAAA;AAAA,EAGvB,SAAS,OAAqB;AAC5B,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,SAAS,OAAqB;AAC5B,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,YACE,UACA,OACA,OACA,MACA,YACA,eACM;AAEN,UAAM,YAAY,QAAQ;AAAA,MACxB,QAAQ,aAAa,YACjB,iBAAiB,KAAK,IACtB,mBAAmB,OAAO,MAAM,aAAa,MAAM,YAAY;AAAA,MACnE,MAAM,aAAa,YAAY,qBAA8B;AAAA,MAC7D;AAAA,IACF;AAEA,UAAM,SAAsB;AAAA,MAC1B,WAAW,oBAAI,KAAK;AAAA,MACpB;AAAA,MACA;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,MAAM;AAAA,MACN;AAAA,IACF;AAEA,SAAK,QAAQ,KAAK,MAAM;AAExB,QAAI,eAAe;AACjB,WAAK,cAAc;AAAA,IACrB;AAEA,mBAAO;AAAA,MACL,iBAAiB,QAAQ,IAAI,KAAK,MAAM,KAAK,YAAY,SACnD,MAAM,WAAW,QAAQ,MAAM,YAAY,WACzC,UAAU,OAAO,QAAQ,CAAC,CAAC,IAAI,UAAU,IAAI;AAAA,IACvD;AAAA,EACF;AAAA;AAAA,EAGA,mBAAmB,SAAiB,SAAiB,UAA0C;AAC7F,UAAM,SAA6B;AAAA,MACjC,WAAW,oBAAI,KAAK;AAAA,MACpB;AAAA,MACA,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,UAAU,YAAY,CAAC;AAAA,IACzB;AAEA,SAAK,eAAe,KAAK,MAAM;AAE/B,mBAAO;AAAA,MACL,yBAAyB,OAAO,YAAY,KAAK,YAAY,YAAY,QAAQ,QAAQ,CAAC,CAAC;AAAA,IAC7F;AAAA,EACF;AAAA;AAAA,EAGA,YAAwB;AACtB,UAAM,SAAqB;AAAA,MACzB,cAAc;AAAA,MACd,WAAW;AAAA,MACX,aAAa,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,EAAE;AAAA,MAC7C,YAAY,CAAC;AAAA,MACb,SAAS,CAAC;AAAA,MACV,SAAS,CAAC;AAAA,MACV,WAAW,CAAC;AAAA,MACZ,SAAS,CAAC,GAAG,KAAK,OAAO;AAAA,MACzB,gBAAgB,CAAC,GAAG,KAAK,cAAc;AAAA,MACvC,qBAAqB;AAAA,MACrB,cAAc,KAAK;AAAA,IACrB;AAEA,eAAW,UAAU,KAAK,SAAS;AACjC,YAAM,EAAE,UAAU,OAAO,OAAO,OAAO,KAAK,IAAI;AAGhD,aAAO,YAAY,SAAS,MAAM;AAClC,aAAO,YAAY,UAAU,MAAM;AACnC,aAAO,YAAY,SAAS,MAAM;AAGlC,YAAM,UAAU,KAAK,SAAS,QAAQ,KAAK,SAAS,KAAK,SAAS;AAClE,YAAM,OAAO,KAAK,SAAS,qBAAqB,KAAK,SAAS;AAC9D,aAAO,gBAAgB;AACvB,aAAO,aAAa;AAGpB,UAAI,CAAC,OAAO,WAAW,QAAQ,EAAG,QAAO,WAAW,QAAQ,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,EAAE;AAChG,aAAO,WAAW,QAAQ,EAAE,WAAW;AACvC,aAAO,WAAW,QAAQ,EAAE,QAAQ;AACpC,aAAO,WAAW,QAAQ,EAAE,SAAS;AAGrC,UAAI,CAAC,OAAO,QAAQ,KAAK,EAAG,QAAO,QAAQ,KAAK,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,EAAE;AACpF,aAAO,QAAQ,KAAK,EAAE,WAAW;AACjC,aAAO,QAAQ,KAAK,EAAE,QAAQ;AAC9B,aAAO,QAAQ,KAAK,EAAE,SAAS;AAG/B,UAAI,CAAC,OAAO,QAAQ,KAAK,EAAG,QAAO,QAAQ,KAAK,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,EAAE;AACpF,aAAO,QAAQ,KAAK,EAAE,WAAW;AACjC,aAAO,QAAQ,KAAK,EAAE,QAAQ;AAC9B,aAAO,QAAQ,KAAK,EAAE,SAAS;AAAA,IACjC;AAEA,eAAW,UAAU,KAAK,gBAAgB;AACxC,YAAM,EAAE,SAAS,QAAQ,IAAI;AAC7B,aAAO,uBAAuB;AAE9B,UAAI,CAAC,OAAO,UAAU,OAAO,EAAG,QAAO,UAAU,OAAO,IAAI,EAAE,SAAS,GAAG,OAAO,EAAE;AACnF,aAAO,UAAU,OAAO,EAAE,WAAW;AACrC,aAAO,UAAU,OAAO,EAAE,SAAS;AAAA,IACrC;AAEA,WAAO,gBAAgB,OAAO;AAE9B,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,eAAuB;AACrB,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,QAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,qBAAqB,OAAO,aAAa,QAAQ,CAAC,CAAC,UAChD,OAAO,sBAAsB,IAAI,YAAY,OAAO,oBAAoB,QAAQ,CAAC,CAAC,eAAe;AAAA,IACtG;AAEA,QAAI,OAAO,YAAY,GAAG;AACxB,YAAM,KAAK,oBAAoB,OAAO,SAAS,mBAAmB;AAAA,IACpE;AAEA,UAAM;AAAA,MACJ,oBAAoB,OAAO,YAAY,MAAM,eAAe,CAAC,KAAK,OAAO,YAAY,MAAM,eAAe,CAAC,SAAS,OAAO,YAAY,OAAO,eAAe,CAAC;AAAA,MAC9J,oBAAoB,KAAK,QAAQ,MAAM;AAAA,IACzC;AAEA,QAAI,OAAO,cAAc;AACvB,YAAM;AAAA,QACJ;AAAA,QACA,oBAAoB,OAAO,aAAa,oBAAoB,QAAQ,CAAC,CAAC;AAAA,QACtE,oBAAoB,OAAO,aAAa,YAAY,IAAI,OAAO,aAAa,mBAAmB;AAAA,MACjG;AACA,UAAI,OAAO,aAAa,WAAW;AACjC,cAAM,KAAK,oBAAoB,OAAO,aAAa,SAAS,EAAE;AAAA,MAChE;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,OAAO,OAAO,EAAE,SAAS,GAAG;AAC1C,YAAM,KAAK,IAAI,aAAa;AAC5B,iBAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AAC1D,cAAM,KAAK,OAAO,KAAK,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC,KAAK,KAAK,KAAK,SAAS;AAAA,MAC9E;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,OAAO,OAAO,EAAE,SAAS,GAAG;AAC1C,YAAM,KAAK,IAAI,aAAa;AAC5B,iBAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AAC1D,cAAM,KAAK,OAAO,KAAK,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC,KAAK,KAAK,KAAK,SAAS;AAAA,MAC9E;AAAA,IACF;AAGA,QAAI,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG;AAC5C,YAAM,KAAK,IAAI,eAAe;AAC9B,iBAAW,CAAC,SAAS,IAAI,KAAK,OAAO,QAAQ,OAAO,SAAS,GAAG;AAC9D,cAAM,KAAK,OAAO,OAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC,KAAK,KAAK,KAAK,SAAS;AAAA,MAChF;AAAA,IACF;AAEA,UAAM,KAAK,IAAI,sQAA+C,EAAE;AAChE,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,UAAU,CAAC;AAChB,SAAK,iBAAiB,CAAC;AACvB,SAAK,cAAc;AACnB,SAAK,eAAe;AACpB,SAAK,eAAe;AAAA,EACtB;AACF;AAGO,IAAM,cAAc,IAAI,YAAY;;;ACrP3C,IAAM,mBAAmB;AACzB,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAE1B,eAAsB,gBAAgB,WAAwC;AAC5E,iBAAO,KAAK,mCAAmC,SAAS,EAAE;AAE1D,MAAI,CAAC,eAAe,SAAS,GAAG;AAC9B,UAAM,IAAI,MAAM,yBAAyB,SAAS,EAAE;AAAA,EACtD;AAGA,QAAM,QAAQ,iBAAiB,SAAS;AACxC,QAAM,aAAa,MAAM,QAAQ,OAAO;AAExC,MAAI,aAAa,kBAAkB;AACjC,UAAM,IAAI;AAAA,MACR,4CAA4C,WAAW,QAAQ,CAAC,CAAC;AAAA,IAEnE;AAAA,EACF;AACA,MAAI,aAAa,mBAAmB;AAClC,mBAAO,KAAK,iBAAiB,WAAW,QAAQ,CAAC,CAAC,kCAA6B;AAAA,EACjF;AAEA,QAAMC,UAAS,UAAU;AACzB,QAAM,SAAS,IAAIC,SAAO,EAAE,QAAQD,QAAO,eAAe,CAAC;AAE3D,MAAI;AACF,UAAM,SAAS,iBAAiB;AAChC,UAAM,WAAW,MAAM,OAAO,MAAM,eAAe,OAAO;AAAA,MACxD,OAAO;AAAA,MACP,MAAM,eAAe,SAAS;AAAA,MAC9B,iBAAiB;AAAA,MACjB,yBAAyB,CAAC,QAAQ,SAAS;AAAA,MAC3C,GAAI,UAAU,EAAE,OAAO;AAAA,IACzB,CAAC;AAID,UAAM,kBAAkB;AACxB,UAAM,cAAe,gBAAgB,YAAY,CAAC;AAGlD,UAAM,WAAY,gBAAgB,SAAS,CAAC;AAI5C,UAAM,QAAgB,SAAS,IAAI,CAAC,OAAO;AAAA,MACzC,MAAM,EAAE;AAAA,MACR,OAAO,EAAE;AAAA,MACT,KAAK,EAAE;AAAA,IACT,EAAE;AAEF,UAAM,WAAsB,YAAY,IAAI,CAAC,OAAO;AAAA,MAClD,IAAI,EAAE;AAAA,MACN,MAAM,EAAE,KAAK,KAAK;AAAA,MAClB,OAAO,EAAE;AAAA,MACT,KAAK,EAAE;AAAA,MACP,OAAO,SACJ,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAClD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,OAAO,KAAK,EAAE,IAAI,EAAE;AAAA,IAC9D,EAAE;AAEF,mBAAO;AAAA,MACL,iCAA4B,SAAS,MAAM,cACxC,MAAM,MAAM,oBAAoB,SAAS,QAAQ;AAAA,IACtD;AAGA,UAAM,mBAAmB,SAAS,YAAY,KAAK;AACnD,gBAAY,mBAAmB,WAAW,kBAAkB,yBAAyB;AAAA,MACnF,OAAO;AAAA,MACP,iBAAiB,SAAS,YAAY;AAAA,MACtC,WAAW;AAAA,IACb,CAAC;AAED,WAAO;AAAA,MACL,MAAM,SAAS;AAAA,MACf;AAAA,MACA;AAAA,MACA,UAAU,SAAS,YAAY;AAAA,MAC/B,UAAU,SAAS,YAAY;AAAA,IACjC;AAAA,EACF,SAAS,OAAgB;AACvB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,mBAAO,MAAM,iCAAiC,OAAO,EAAE;AAGvD,UAAM,SAAU,MAA8B;AAC9C,QAAI,WAAW,KAAK;AAClB,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAChF;AACA,QAAI,WAAW,KAAK;AAClB,YAAM,IAAI,MAAM,yDAAyD;AAAA,IAC3E;AACA,UAAM,IAAI,MAAM,iCAAiC,OAAO,EAAE;AAAA,EAC5D;AACF;;;AClGA,IAAM,sBAAsB;AAE5B,eAAsB,gBAAgB,OAAuC;AAC3E,QAAME,UAAS,UAAU;AAGzB,QAAM,WAAW,KAAKA,QAAO,WAAW,OAAO;AAC/C,QAAM,gBAAgB,QAAQ;AAC9B,iBAAO,KAAK,0BAA0B,QAAQ,EAAE;AAGhD,QAAM,UAAU,KAAK,UAAU,GAAG,MAAM,IAAI,MAAM;AAClD,iBAAO,KAAK,yBAAyB,MAAM,IAAI,GAAG;AAClD,QAAM,aAAa,MAAM,UAAU,OAAO;AAG1C,QAAM,QAAQ,MAAM,aAAa,OAAO;AACxC,QAAM,aAAa,MAAM,QAAQ,OAAO;AACxC,iBAAO,KAAK,oBAAoB,WAAW,QAAQ,CAAC,CAAC,IAAI;AAEzD,MAAI;AAEJ,MAAI,cAAc,qBAAqB;AAErC,mBAAO,KAAK,2BAA2B,MAAM,IAAI,GAAG;AACpD,iBAAa,MAAM,gBAAgB,OAAO;AAAA,EAC5C,OAAO;AAEL,mBAAO,KAAK,iBAAiB,mBAAmB,2BAA2B;AAC3E,UAAM,aAAa,MAAM,qBAAqB,OAAO;AACrD,iBAAa,MAAM,iBAAiB,UAAU;AAG9C,eAAW,aAAa,YAAY;AAClC,UAAI,cAAc,SAAS;AACzB,cAAM,WAAW,SAAS,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,KAAKA,QAAO,YAAY,MAAM,IAAI;AACxD,QAAM,gBAAgB,aAAa;AACnC,QAAM,iBAAiB,KAAK,eAAe,iBAAiB;AAC5D,QAAM,cAAc,gBAAgB,UAAU;AAC9C,iBAAO,KAAK,qBAAqB,cAAc,EAAE;AAGjD,QAAM,WAAW,OAAO,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACxC,iBAAO,KAAK,yBAAyB,OAAO,EAAE;AAG9C,iBAAO;AAAA,IACL,+BAA+B,MAAM,IAAI,YACtC,WAAW,SAAS,MAAM,cAAc,WAAW,MAAM,MAAM;AAAA,EACpE;AACA,SAAO;AACT;AAKA,eAAe,iBAAiB,YAA2C;AACzE,MAAI,UAAU;AACd,QAAM,cAAyB,CAAC;AAChC,QAAM,WAAmB,CAAC;AAC1B,MAAI,mBAAmB;AACvB,MAAI,gBAAgB;AACpB,MAAI,WAAW;AAEf,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,mBAAO,KAAK,sBAAsB,IAAI,CAAC,IAAI,WAAW,MAAM,KAAK,WAAW,CAAC,CAAC,EAAE;AAChF,UAAM,SAAS,MAAM,gBAAgB,WAAW,CAAC,CAAC;AAElD,QAAI,MAAM,EAAG,YAAW,OAAO;AAG/B,UAAM,iBAAiB,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,MACjD,GAAG;AAAA,MACH,IAAI,YAAY,SAAS,EAAE;AAAA,MAC3B,OAAO,EAAE,QAAQ;AAAA,MACjB,KAAK,EAAE,MAAM;AAAA,MACb,OAAO,EAAE,MAAM,IAAI,CAAC,OAAO;AAAA,QACzB,GAAG;AAAA,QACH,OAAO,EAAE,QAAQ;AAAA,QACjB,KAAK,EAAE,MAAM;AAAA,MACf,EAAE;AAAA,IACJ,EAAE;AAEF,UAAM,cAAc,OAAO,MAAM,IAAI,CAAC,OAAO;AAAA,MAC3C,GAAG;AAAA,MACH,OAAO,EAAE,QAAQ;AAAA,MACjB,KAAK,EAAE,MAAM;AAAA,IACf,EAAE;AAEF,gBAAY,UAAU,MAAM,MAAM,OAAO;AACzC,gBAAY,KAAK,GAAG,cAAc;AAClC,aAAS,KAAK,GAAG,WAAW;AAE5B,wBAAoB,OAAO;AAC3B,qBAAiB,OAAO;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,EACZ;AACF;;;AC1FA,SAAS,IAAI,GAAW,OAAuB;AAC7C,SAAO,OAAO,CAAC,EAAE,SAAS,OAAO,GAAG;AACtC;AAGA,SAAS,MAAM,SAAyB;AACtC,QAAM,IAAI,KAAK,MAAM,UAAU,IAAI;AACnC,QAAM,IAAI,KAAK,MAAO,UAAU,OAAQ,EAAE;AAC1C,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,QAAM,KAAK,KAAK,OAAO,UAAU,KAAK,MAAM,OAAO,KAAK,GAAI;AAC5D,SAAO,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC7D;AAGA,SAAS,MAAM,SAAyB;AACtC,SAAO,MAAM,OAAO,EAAE,QAAQ,KAAK,GAAG;AACxC;AAGA,SAAS,MAAM,SAAyB;AACtC,QAAM,IAAI,KAAK,MAAM,UAAU,IAAI;AACnC,QAAM,IAAI,KAAK,MAAO,UAAU,OAAQ,EAAE;AAC1C,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,QAAM,KAAK,KAAK,OAAO,UAAU,KAAK,MAAM,OAAO,KAAK,GAAG;AAC3D,SAAO,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AACrD;AAOA,IAAM,wBAAwB;AAE9B,IAAM,sBAAsB;AAE5B,IAAM,iBAAiB;AAEvB,IAAM,eAAe;AAErB,IAAM,aAAa;AAEnB,IAAM,mBAAmB;AAEzB,IAAM,iBAAiB;AAOvB,IAAM,0BAA0B;AAEhC,IAAM,wBAAwB;AAO9B,IAAM,4BAA4B;AAElC,IAAM,0BAA0B;AAEhC,IAAM,wBAAwB;AAE9B,IAAM,sBAAsB;AAMrB,SAAS,YAAY,YAAgC;AAC1D,SAAO,WAAW,SACf,IAAI,CAAC,KAAc,MAAc;AAChC,UAAM,MAAM,IAAI;AAChB,UAAM,QAAQ,MAAM,IAAI,KAAK;AAC7B,UAAM,MAAM,MAAM,IAAI,GAAG;AACzB,UAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,WAAO,GAAG,GAAG;AAAA,EAAK,KAAK,QAAQ,GAAG;AAAA,EAAK,IAAI;AAAA,EAC7C,CAAC,EACA,KAAK,MAAM,EACX,OAAO,IAAI;AAChB;AAMO,SAAS,YAAY,YAAgC;AAC1D,QAAM,OAAO,WAAW,SACrB,IAAI,CAAC,QAAiB;AACrB,UAAM,QAAQ,MAAM,IAAI,KAAK;AAC7B,UAAM,MAAM,MAAM,IAAI,GAAG;AACzB,UAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,WAAO,GAAG,KAAK,QAAQ,GAAG;AAAA,EAAK,IAAI;AAAA,EACrC,CAAC,EACA,KAAK,MAAM;AAEd,SAAO;AAAA;AAAA,EAAa,IAAI;AAAA;AAC1B;AAyBA,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BnB,IAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwB5B,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoB1B,SAAS,mBAAmB,OAAyB;AACnD,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAkB,CAAC;AAEvB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAQ,KAAK,MAAM,CAAC,CAAC;AAErB,UAAM,SAAS,MAAM,MAAM,SAAS;AACpC,UAAM,SACJ,CAAC,UAAU,MAAM,IAAI,CAAC,EAAE,QAAQ,MAAM,CAAC,EAAE,MAAM;AACjD,UAAM,QAAQ,QAAQ,UAAU;AAEhC,QAAI,UAAU,UAAU,OAAO;AAC7B,aAAO,KAAK,OAAO;AACnB,gBAAU,CAAC;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AACT;AAOA,SAAS,oBAAoB,OAAyB;AACpD,MAAI,MAAM,UAAU,eAAgB,QAAO,CAAC,KAAK;AACjD,QAAM,MAAM,KAAK,KAAK,MAAM,SAAS,CAAC;AACtC,SAAO,CAAC,MAAM,MAAM,GAAG,GAAG,GAAG,MAAM,MAAM,GAAG,CAAC;AAC/C;AASA,SAAS,0BAA0B,OAAe,QAAsB,UAAoB;AAC1F,QAAM,iBAAiB,UAAU,aAAa,4BAC1C,UAAU,WAAW,0BAA0B;AACnD,QAAM,eAAe,UAAU,aAAa,0BACxC,UAAU,WAAW,wBAAwB;AACjD,QAAM,SAAS,mBAAmB,KAAK;AACvC,QAAM,YAAsB,CAAC;AAE7B,aAAW,SAAS,QAAQ;AAC1B,UAAM,eAAe,oBAAoB,KAAK;AAE9C,aAAS,YAAY,GAAG,YAAY,MAAM,QAAQ,aAAa;AAC7D,YAAM,aAAa,MAAM,SAAS;AAGlC,YAAM,UACJ,YAAY,MAAM,SAAS,IACvB,MAAM,YAAY,CAAC,EAAE,QACrB,WAAW;AAGjB,YAAM,gBAA0B,CAAC;AACjC,UAAI,YAAY;AAEhB,iBAAW,QAAQ,cAAc;AAC/B,cAAM,WAAW,KAAK,IAAI,CAAC,MAAM;AAC/B,gBAAM,MAAM;AACZ,gBAAMC,QAAO,EAAE,KAAK,KAAK;AACzB,cAAI,QAAQ,WAAW;AACrB,gBAAI,UAAU,YAAY;AAExB,qBAAO,IAAI,qBAAqB,OAAO,cAAc,mDAAmDA,KAAI;AAAA,YAC9G;AACA,mBAAO,IAAI,YAAY,OAAO,cAAc,IAAIA,KAAI;AAAA,UACtD;AACA,cAAI,UAAU,YAAY;AACxB,mBAAO,IAAI,mBAAmB,OAAO,YAAY,IAAIA,KAAI;AAAA,UAC3D;AACA,iBAAO,IAAI,UAAU,OAAO,YAAY,IAAIA,KAAI;AAAA,QAClD,CAAC;AACD,sBAAc,KAAK,SAAS,KAAK,GAAG,CAAC;AAAA,MACvC;AAEA,YAAM,OAAO,cAAc,KAAK,KAAK;AACrC,gBAAU;AAAA,QACR,eAAe,MAAM,WAAW,KAAK,CAAC,IAAI,MAAM,OAAO,CAAC,oBAAoB,IAAI;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAgBO,SAAS,kBAAkB,YAAwB,QAAsB,UAAkB;AAChG,QAAM,SAAS,UAAU,aAAa,sBAAsB,UAAU,WAAW,oBAAoB;AACrG,QAAM,WAAW,WAAW;AAC5B,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,SAAO,SAAS,0BAA0B,UAAU,KAAK,EAAE,KAAK,IAAI,IAAI;AAC1E;AAOO,SAAS,4BACd,YACA,WACA,SACA,SAAiB,GACjB,QAAsB,UACd;AACR,QAAM,SAAS,UAAU,aAAa,sBAAsB,UAAU,WAAW,oBAAoB;AACrG,QAAM,gBAAgB,KAAK,IAAI,GAAG,YAAY,MAAM;AACpD,QAAM,cAAc,UAAU;AAE9B,QAAM,QAAQ,WAAW,MAAM;AAAA,IAC7B,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,OAAO;AAAA,EAC9C;AACA,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,WAAmB,MAAM,IAAI,CAAC,OAAO;AAAA,IACzC,MAAM,EAAE;AAAA,IACR,OAAO,EAAE,QAAQ;AAAA,IACjB,KAAK,EAAE,MAAM;AAAA,EACf,EAAE;AAEF,SAAO,SAAS,0BAA0B,UAAU,KAAK,EAAE,KAAK,IAAI,IAAI;AAC1E;AAOO,SAAS,8BACd,YACA,UACA,SAAiB,GACjB,QAAsB,UACd;AACR,QAAM,SAAS,UAAU,aAAa,sBAAsB,UAAU,WAAW,oBAAoB;AACrG,QAAM,cAAsB,CAAC;AAC7B,MAAI,gBAAgB;AAEpB,aAAW,OAAO,UAAU;AAC1B,UAAM,gBAAgB,KAAK,IAAI,GAAG,IAAI,QAAQ,MAAM;AACpD,UAAM,cAAc,IAAI,MAAM;AAC9B,UAAM,cAAc,cAAc;AAElC,UAAM,QAAQ,WAAW,MAAM;AAAA,MAC7B,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,OAAO;AAAA,IAC9C;AAEA,eAAW,KAAK,OAAO;AACrB,kBAAY,KAAK;AAAA,QACf,MAAM,EAAE;AAAA,QACR,OAAO,EAAE,QAAQ,gBAAgB;AAAA,QACjC,KAAK,EAAE,MAAM,gBAAgB;AAAA,MAC/B,CAAC;AAAA,IACH;AAEA,qBAAiB;AAAA,EACnB;AAEA,MAAI,YAAY,WAAW,EAAG,QAAO;AAErC,SAAO,SAAS,0BAA0B,aAAa,KAAK,EAAE,KAAK,IAAI,IAAI;AAC7E;AAOA,IAAM,uBAAuB;AAoBtB,SAAS,oBACd,UACA,kBAA0B,GAC1B,SAAuB,YACf;AACR,QAAM,OACJ,SAAS,SAAS,uBACd,SAAS,MAAM,GAAG,uBAAuB,CAAC,IAAI,QAC9C;AAEN,SAAO,eAAe,MAAM,CAAC,CAAC,IAAI,MAAM,eAAe,CAAC,iCAAiC,IAAI;AAC/F;AAKO,SAAS,4BACd,YACA,UACA,WACA,SACA,QACQ;AACR,QAAM,UAAU,4BAA4B,YAAY,WAAW,SAAS,QAAQ,UAAU;AAC9F,QAAM,WAAW,oBAAoB,UAAU,GAAK,UAAU;AAC9D,SAAO,UAAU,WAAW;AAC9B;AAKO,SAAS,qCACd,YACA,UACA,UACA,QACQ;AACR,QAAM,UAAU,8BAA8B,YAAY,UAAU,QAAQ,UAAU;AACtF,QAAM,WAAW,oBAAoB,UAAU,GAAK,UAAU;AAC9D,SAAO,UAAU,WAAW;AAC9B;;;AC/cA,eAAsB,iBACpB,OACA,YACmB;AACnB,QAAMC,UAAS,UAAU;AACzB,QAAM,cAAc,KAAKA,QAAO,YAAY,MAAM,MAAM,UAAU;AAClE,QAAM,gBAAgB,WAAW;AAEjC,QAAM,UAAU,KAAK,aAAa,cAAc;AAChD,QAAM,UAAU,KAAK,aAAa,cAAc;AAChD,QAAM,UAAU,KAAK,aAAa,cAAc;AAEhD,QAAM,MAAM,YAAY,UAAU;AAClC,QAAM,MAAM,YAAY,UAAU;AAClC,QAAM,MAAM,kBAAkB,UAAU;AAExC,QAAM,QAAQ,IAAI;AAAA,IAChB,cAAc,SAAS,GAAG;AAAA,IAC1B,cAAc,SAAS,GAAG;AAAA,IAC1B,cAAc,SAAS,GAAG;AAAA,EAC5B,CAAC;AAED,QAAM,QAAQ,CAAC,SAAS,SAAS,OAAO;AACxC,iBAAO,KAAK,mBAAmB,MAAM,KAAK,IAAI,CAAC,EAAE;AACjD,SAAO;AACT;;;ACbA,IAAM,gBAAgB;AACtB,IAAM,qBAAqB;AAEpB,IAAM,kBAAN,MAA6C;AAAA,EACzC,OAAO;AAAA,EACR,SAA+B;AAAA,EAEvC,cAAuB;AAErB,WAAO;AAAA,EACT;AAAA,EAEA,kBAA0B;AACxB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,cAAcC,SAA4C;AAC9D,QAAI,CAAC,KAAK,QAAQ;AAChB,WAAK,SAAS,IAAI,cAAc,EAAE,WAAW,MAAM,UAAU,QAAQ,CAAC;AAAA,IACxE;AAEA,UAAM,iBAAiB,MAAM,KAAK,OAAO,cAAc;AAAA,MACrD,OAAOA,QAAO;AAAA,MACd,YAAYA,QAAO;AAAA,MACnB,eAAe,EAAE,MAAM,WAAW,SAASA,QAAO,aAAa;AAAA,MAC/D,OAAOA,QAAO,MAAM,IAAI,CAAC,OAAO;AAAA,QAC9B,MAAM,EAAE;AAAA,QACR,aAAa,EAAE;AAAA,QACf,YAAY,EAAE;AAAA,QACd,SAAS,EAAE;AAAA,MACb,EAAE;AAAA,MACF,WAAWA,QAAO,aAAa;AAAA,IACjC,CAAC;AAED,WAAO,IAAI;AAAA,MACT;AAAA,MACAA,QAAO,aAAa;AAAA,IACtB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI;AACF,UAAI,KAAK,QAAQ;AACf,cAAM,KAAK,OAAO,KAAK;AACvB,aAAK,SAAS;AAAA,MAChB;AAAA,IACF,SAAS,KAAK;AACZ,qBAAO,MAAM,yCAAyC,GAAG,EAAE;AAAA,IAC7D;AAAA,EACF;AACF;AAGA,IAAM,wBAAN,MAAkD;AAAA,EAQhD,YACmB,SACA,WACjB;AAFiB;AACA;AAEjB,SAAK,qBAAqB;AAC1B,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAbQ,gBAAgB,oBAAI,IAA8D;AAAA;AAAA,EAGlF,YAAwB,EAAE,aAAa,GAAG,cAAc,GAAG,aAAa,EAAE;AAAA,EAC1E;AAAA,EACA;AAAA,EAUR,MAAM,YAAY,SAAuC;AACvD,UAAM,QAAQ,KAAK,IAAI;AAGvB,SAAK,YAAY,EAAE,aAAa,GAAG,cAAc,GAAG,aAAa,EAAE;AACnE,SAAK,WAAW;AAChB,SAAK,qBAAqB;AAE1B,UAAM,WAAW,MAAM,KAAK,QAAQ;AAAA,MAClC,EAAE,QAAQ,QAAQ;AAAA,MAClB,KAAK;AAAA,IACP;AAEA,UAAM,UAAU,UAAU,MAAM,WAAW;AAC3C,UAAM,YAAwB,CAAC;AAE/B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,gBAAgB,KAAK;AAAA,MACrB,YAAY,KAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,GAAG,OAA0B,SAA+C;AAC1E,UAAM,WAAW,KAAK,cAAc,IAAI,KAAK,KAAK,CAAC;AACnD,aAAS,KAAK,OAAO;AACrB,SAAK,cAAc,IAAI,OAAO,QAAQ;AAAA,EACxC;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,QAAQ,QAAQ;AAC3B,SAAK,cAAc,MAAM;AAAA,EAC3B;AAAA;AAAA,EAGQ,qBAA2B;AACjC,SAAK,QAAQ,GAAG,CAAC,UAAwB;AACvC,UAAI,MAAM,SAAS,mBAAmB;AACpC,cAAM,IAAI,MAAM;AAChB,aAAK,YAAY;AAAA,UACf,aAAc,EAAE,eAA0B;AAAA,UAC1C,cAAe,EAAE,gBAA2B;AAAA,UAC5C,cAAe,EAAE,eAA0B,MAAO,EAAE,gBAA2B;AAAA,UAC/E,iBAAiB,EAAE;AAAA,UACnB,kBAAkB,EAAE;AAAA,QACtB;AACA,YAAI,EAAE,QAAQ,MAAM;AAClB,eAAK,WAAW;AAAA,YACd,QAAQ,EAAE;AAAA,YACV,MAAM;AAAA,YACN,OAAQ,EAAE,SAAoB;AAAA,YAC9B,YAAY,EAAE;AAAA,UAChB;AAAA,QACF;AACA,YAAI,EAAE,kBAAkB,MAAM;AAC5B,eAAK,qBAAqB,EAAE;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGQ,uBAA6B;AACnC,SAAK,QAAQ,GAAG,CAAC,UAAwB;AACvC,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AACH,eAAK,KAAK,SAAS,MAAM,IAAI;AAC7B;AAAA,QACF,KAAK;AACH,eAAK,KAAK,cAAc,MAAM,IAAI;AAClC;AAAA,QACF,KAAK;AACH,eAAK,KAAK,YAAY,MAAM,IAAI;AAChC;AAAA,QACF,KAAK;AACH,eAAK,KAAK,SAAS,MAAM,IAAI;AAC7B;AAAA,QACF,KAAK;AACH,eAAK,KAAK,SAAS,MAAM,IAAI;AAC7B;AAAA,MACJ;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,KAAK,MAAyB,MAAqB;AACzD,UAAM,WAAW,KAAK,cAAc,IAAI,IAAI;AAC5C,QAAI,UAAU;AACZ,iBAAW,WAAW,UAAU;AAC9B,gBAAQ,EAAE,MAAM,KAAK,CAAC;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AACF;;;AClKA,IAAM,kBAAkB;AAKxB,SAAS,cAAc,OAAgD;AACrE,SAAO,MAAM,IAAI,CAAC,OAAO;AAAA,IACvB,MAAM;AAAA,IACN,UAAU;AAAA,MACR,MAAM,EAAE;AAAA,MACR,aAAa,EAAE;AAAA,MACf,YAAY,EAAE;AAAA,IAChB;AAAA,EACF,EAAE;AACJ;AAGA,SAAS,gBACP,OACyC;AACzC,SAAO,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACtD;AAGA,SAAS,SAAS,GAAe,GAA2B;AAC1D,SAAO;AAAA,IACL,aAAa,EAAE,cAAc,EAAE;AAAA,IAC/B,cAAc,EAAE,eAAe,EAAE;AAAA,IACjC,aAAa,EAAE,cAAc,EAAE;AAAA,EACjC;AACF;AAIA,IAAM,gBAAN,MAA0C;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY,oBAAI,IAAuD;AAAA,EACvE;AAAA,EAER,YAAY,QAAgBC,SAAuB,OAAe;AAChE,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,WAAW,CAAC,EAAE,MAAM,UAAU,SAASA,QAAO,aAAa,CAAC;AACjE,SAAK,QAAQ,cAAcA,QAAO,KAAK;AACvC,SAAK,WAAW,gBAAgBA,QAAO,KAAK;AAC5C,SAAK,YAAYA,QAAO;AAAA,EAC1B;AAAA;AAAA,EAIA,MAAM,YAAY,SAAuC;AACvD,SAAK,SAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,QAAQ,CAAC;AAErD,QAAI,aAAyB,EAAE,aAAa,GAAG,cAAc,GAAG,aAAa,EAAE;AAC/E,UAAM,QAAQ,KAAK,IAAI;AAGvB,QAAI,YAAY;AAChB,WAAO,MAAM;AACX,UAAI,EAAE,YAAY,iBAAiB;AACjC,uBAAO,KAAK,yBAAyB,eAAe,iDAA4C;AAChG,cAAM,IAAI,MAAM,oBAAoB,eAAe,0CAAqC;AAAA,MAC1F;AACA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,YAAY,KAAK,YACnB,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,SAAS,IACnD;AACJ,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,UAC5C;AAAA,YACE,OAAO,KAAK;AAAA,YACZ,UAAU,KAAK;AAAA,YACf,GAAI,KAAK,MAAM,SAAS,IAAI,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,UACvD;AAAA,UACA,EAAE,QAAQ,WAAW,OAAO;AAAA,QAC9B;AAAA,MACF,UAAE;AACA,YAAI,UAAW,cAAa,SAAS;AAAA,MACvC;AAEA,YAAM,SAAS,SAAS,QAAQ,CAAC;AACjC,YAAM,eAAe,OAAO;AAG5B,UAAI,SAAS,OAAO;AAClB,cAAM,YAAwB;AAAA,UAC5B,aAAa,SAAS,MAAM;AAAA,UAC5B,cAAc,SAAS,MAAM;AAAA,UAC7B,aAAa,SAAS,MAAM;AAAA,QAC9B;AACA,qBAAa,SAAS,YAAY,SAAS;AAC3C,aAAK,KAAK,SAAS,SAAS;AAAA,MAC9B;AAGA,WAAK,SAAS,KAAK,YAA0C;AAE7D,YAAM,YAAY,aAAa;AAC/B,UAAI,CAAC,aAAa,UAAU,WAAW,GAAG;AAExC,cAAM,OAAO,mBAAmB,KAAK,OAAO,WAAW,aAAa,WAAW,YAAY;AAC3F,eAAO;AAAA,UACL,SAAS,aAAa,WAAW;AAAA,UACjC,WAAW,CAAC;AAAA,UACZ,OAAO;AAAA,UACP,MAAM,EAAE,QAAQ,MAAM,MAAM,OAAO,OAAO,KAAK,MAAM;AAAA,UACrD,YAAY,KAAK,IAAI,IAAI;AAAA,QAC3B;AAAA,MACF;AAGA,iBAAW,MAAM,WAAW;AAC1B,YAAI,GAAG,SAAS,WAAY;AAE5B,cAAM,SAAS,GAAG,SAAS;AAC3B,cAAM,UAAU,KAAK,SAAS,IAAI,MAAM;AAExC,YAAI;AACJ,YAAI,CAAC,SAAS;AACZ,yBAAO,KAAK,kCAAkC,MAAM,EAAE;AACtD,mBAAS,EAAE,OAAO,iBAAiB,MAAM,GAAG;AAAA,QAC9C,OAAO;AACL,eAAK,KAAK,cAAc,EAAE,MAAM,QAAQ,WAAW,GAAG,SAAS,UAAU,CAAC;AAC1E,cAAI;AACF,kBAAM,OAAO,KAAK,MAAM,GAAG,SAAS,SAAS;AAC7C,qBAAS,MAAM,QAAQ,IAAI;AAAA,UAC7B,SAAS,KAAK;AACZ,2BAAO,MAAM,QAAQ,MAAM,YAAY,GAAG,EAAE;AAC5C,qBAAS,EAAE,OAAO,OAAO,GAAG,EAAE;AAAA,UAChC;AACA,eAAK,KAAK,YAAY,EAAE,MAAM,QAAQ,OAAO,CAAC;AAAA,QAChD;AAEA,aAAK,SAAS,KAAK;AAAA,UACjB,MAAM;AAAA,UACN,cAAc,GAAG;AAAA,UACjB,SAAS,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM;AAAA,QACtE,CAAC;AAAA,MACH;AAAA,IAEF;AAAA,EACF;AAAA,EAEA,GAAG,OAA0B,SAA2C;AACtE,UAAM,OAAO,KAAK,UAAU,IAAI,KAAK,KAAK,CAAC;AAC3C,SAAK,KAAK,OAAO;AACjB,SAAK,UAAU,IAAI,OAAO,IAAI;AAAA,EAChC;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,WAAW,CAAC;AACjB,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA;AAAA,EAIQ,KAAK,MAAyB,MAAqB;AACzD,eAAW,WAAW,KAAK,UAAU,IAAI,IAAI,KAAK,CAAC,GAAG;AACpD,UAAI;AACF,gBAAQ,EAAE,MAAM,KAAK,CAAC;AAAA,MACxB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;AAIO,IAAM,iBAAN,MAA4C;AAAA,EACxC,OAAO;AAAA,EAEhB,cAAuB;AACrB,WAAO,CAAC,CAAC,UAAU,EAAE;AAAA,EACvB;AAAA,EAEA,kBAA0B;AACxB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,cAAcA,SAA4C;AAC9D,UAAM,SAAS,IAAIC,SAAO;AAC1B,UAAM,QAAQD,QAAO,SAAS,KAAK,gBAAgB;AACnD,mBAAO,KAAK,iCAAiC,KAAK,WAAWA,QAAO,MAAM,MAAM,GAAG;AACnF,WAAO,IAAI,cAAc,QAAQA,SAAQ,KAAK;AAAA,EAChD;AACF;;;AC3LA,IAAME,iBAAgB;AACtB,IAAM,qBAAqB;AAC3B,IAAMC,mBAAkB;AAGxB,SAAS,iBAAiB,OAAkC;AAC1D,SAAO,MAAM,IAAI,CAAC,OAAO;AAAA,IACvB,MAAM,EAAE;AAAA,IACR,aAAa,EAAE;AAAA,IACf,cAAc,EAAE;AAAA,EAClB,EAAE;AACJ;AAGA,SAAS,YAAY,SAAiC;AACpD,SAAO,QACJ,OAAO,CAAC,MAAsB,EAAE,SAAS,MAAM,EAC/C,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,EAAE;AACZ;AAGA,SAAS,eAAe,SAAyC;AAC/D,SAAO,QAAQ,OAAO,CAAC,MAAyB,EAAE,SAAS,UAAU;AACvE;AAEA,IAAM,gBAAN,MAA0C;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAA2B,CAAC;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,WAAW,oBAAI,IAA2D;AAAA,EAC1E;AAAA,EAER,YAAY,QAAmBC,SAAuB;AACpD,SAAK,SAAS;AACd,SAAK,eAAeA,QAAO;AAC3B,SAAK,QAAQA,QAAO;AACpB,SAAK,iBAAiB,iBAAiBA,QAAO,KAAK;AACnD,SAAK,QAAQA,QAAO,SAASF;AAC7B,SAAK,YAAY;AACjB,SAAK,YAAYE,QAAO;AAAA,EAC1B;AAAA,EAEA,GAAG,OAA0B,SAA+C;AAC1E,UAAM,OAAO,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC;AAC1C,SAAK,KAAK,OAAO;AACjB,SAAK,SAAS,IAAI,OAAO,IAAI;AAAA,EAC/B;AAAA,EAEQ,KAAK,MAAyB,MAAqB;AACzD,eAAW,WAAW,KAAK,SAAS,IAAI,IAAI,KAAK,CAAC,GAAG;AACnD,cAAQ,EAAE,MAAM,KAAK,CAAC;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,SAAuC;AACvD,SAAK,SAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,QAAQ,CAAC;AAErD,QAAI,kBAA8B;AAAA,MAChC,aAAa;AAAA,MACb,cAAc;AAAA,MACd,aAAa;AAAA,IACf;AAEA,UAAM,UAAU,KAAK,IAAI;AAGzB,QAAI,YAAY;AAChB,WAAO,MAAM;AACX,UAAI,EAAE,YAAYD,kBAAiB;AACjC,uBAAO,KAAK,yBAAyBA,gBAAe,iDAA4C;AAChG,cAAM,IAAI,MAAM,oBAAoBA,gBAAe,0CAAqC;AAAA,MAC1F;AACA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,YAAY,KAAK,YACnB,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,SAAS,IACnD;AACJ,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,KAAK,OAAO,SAAS;AAAA,UACpC;AAAA,YACE,OAAO,KAAK;AAAA,YACZ,YAAY,KAAK;AAAA,YACjB,QAAQ,KAAK;AAAA,YACb,UAAU,KAAK;AAAA,YACf,GAAI,KAAK,eAAe,SAAS,IAAI,EAAE,OAAO,KAAK,eAAe,IAAI,CAAC;AAAA,UACzE;AAAA,UACA,EAAE,QAAQ,WAAW,OAAO;AAAA,QAC9B;AAAA,MACF,UAAE;AACA,YAAI,UAAW,cAAa,SAAS;AAAA,MACvC;AAGA,sBAAgB,eAAe,SAAS,MAAM;AAC9C,sBAAgB,gBAAgB,SAAS,MAAM;AAC/C,sBAAgB,cACd,gBAAgB,cAAc,gBAAgB;AAEhD,UAAI,SAAS,MAAM,yBAAyB;AAC1C,wBAAgB,mBACb,gBAAgB,mBAAmB,KAAK,SAAS,MAAM;AAAA,MAC5D;AACA,UAAI,SAAS,MAAM,6BAA6B;AAC9C,wBAAgB,oBACb,gBAAgB,oBAAoB,KAAK,SAAS,MAAM;AAAA,MAC7D;AAEA,WAAK,KAAK,SAAS,eAAe;AAGlC,WAAK,SAAS,KAAK,EAAE,MAAM,aAAa,SAAS,SAAS,QAAQ,CAAC;AAEnE,YAAM,gBAAgB,eAAe,SAAS,OAAO;AAErD,UAAI,cAAc,WAAW,KAAK,SAAS,gBAAgB,YAAY;AAErE,cAAM,OAAO,YAAY,SAAS,OAAO;AACzC,cAAM,OAAO;AAAA,UACX,KAAK;AAAA,UACL,gBAAgB;AAAA,UAChB,gBAAgB;AAAA,QAClB;AAEA,eAAO;AAAA,UACL,SAAS;AAAA,UACT,WAAW,CAAC;AAAA,UACZ,OAAO;AAAA,UACP,MAAM,OAAO,IACT,EAAE,QAAQ,MAAM,MAAM,OAAO,OAAO,KAAK,MAAM,IAC/C;AAAA,UACJ,YAAY,KAAK,IAAI,IAAI;AAAA,QAC3B;AAAA,MACF;AAGA,YAAM,cAAsC,CAAC;AAE7C,iBAAW,SAAS,eAAe;AACjC,cAAM,OAAO,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IAAI;AACzD,YAAI,CAAC,MAAM;AACT,yBAAO,KAAK,kCAAkC,MAAM,IAAI,EAAE;AAC1D,sBAAY,KAAK;AAAA,YACf,MAAM;AAAA,YACN,aAAa,MAAM;AAAA,YACnB,SAAS,KAAK,UAAU,EAAE,OAAO,iBAAiB,MAAM,IAAI,GAAG,CAAC;AAAA,UAClE,CAAC;AACD;AAAA,QACF;AAEA,aAAK,KAAK,cAAc,EAAE,MAAM,MAAM,MAAM,WAAW,MAAM,MAAM,CAAC;AAEpE,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,KAAgC;AACxE,sBAAY,KAAK;AAAA,YACf,MAAM;AAAA,YACN,aAAa,MAAM;AAAA,YACnB,SAAS,KAAK,UAAU,MAAM;AAAA,UAChC,CAAC;AACD,eAAK,KAAK,YAAY,EAAE,MAAM,MAAM,MAAM,OAAO,CAAC;AAAA,QACpD,SAAS,KAAK;AACZ,gBAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,yBAAO,MAAM,QAAQ,MAAM,IAAI,YAAY,QAAQ,EAAE;AACrD,sBAAY,KAAK;AAAA,YACf,MAAM;AAAA,YACN,aAAa,MAAM;AAAA,YACnB,SAAS,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAAA,YAC3C,UAAU;AAAA,UACZ,CAAC;AACD,eAAK,KAAK,SAAS,EAAE,MAAM,MAAM,MAAM,OAAO,SAAS,CAAC;AAAA,QAC1D;AAAA,MACF;AAGA,WAAK,SAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,IAC3D;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,WAAW,CAAC;AACjB,SAAK,SAAS,MAAM;AAAA,EACtB;AACF;AAEO,IAAM,iBAAN,MAA4C;AAAA,EACxC,OAAO;AAAA,EAEhB,cAAuB;AACrB,WAAO,CAAC,CAAC,UAAU,EAAE;AAAA,EACvB;AAAA,EAEA,kBAA0B;AACxB,WAAOD;AAAA,EACT;AAAA,EAEA,MAAM,cAAcE,SAA4C;AAC9D,UAAM,SAAS,IAAIC,SAAU;AAC7B,WAAO,IAAI,cAAc,QAAQD,OAAM;AAAA,EACzC;AACF;;;ACjOA,IAAM,YAAqD;AAAA,EACzD,SAAS,MAAM,IAAI,gBAAgB;AAAA,EACnC,QAAQ,MAAM,IAAI,eAAe;AAAA,EACjC,QAAQ,MAAM,IAAI,eAAe;AACnC;AAGA,IAAI,kBAAsC;AAC1C,IAAI,sBAA2C;AAOxC,SAAS,YAAY,MAAkC;AAC5D,QAAM,MAAM,QAAQ,UAAU,EAAE,aAAa,KAAK,EAAE,YAAY;AAChE,QAAM,eAAe;AAErB,MAAI,mBAAmB,wBAAwB,cAAc;AAC3D,WAAO;AAAA,EACT;AAGA,mBAAiB,QAAQ,EAAE,MAAM,MAAM;AAAA,EAA4B,CAAC;AAEpE,MAAI,CAAC,UAAU,YAAY,GAAG;AAC5B,UAAM,IAAI;AAAA,MACR,0BAA0B,YAAY,qBACpB,OAAO,KAAK,SAAS,EAAE,KAAK,IAAI,CAAC;AAAA,IACrD;AAAA,EACF;AAEA,QAAM,WAAW,UAAU,YAAY,EAAE;AAEzC,MAAI,CAAC,SAAS,YAAY,GAAG;AAC3B,mBAAO;AAAA,MACL,aAAa,YAAY;AAAA,IAE3B;AACA,sBAAkB,UAAU,QAAQ;AACpC,0BAAsB;AACtB,WAAO;AAAA,EACT;AAEA,iBAAO,KAAK,uBAAuB,YAAY,YAAY,SAAS,gBAAgB,CAAC,GAAG;AACxF,oBAAkB;AAClB,wBAAsB;AACtB,SAAO;AACT;;;AChDO,IAAM,gBAAgB;AAItB,IAAM,kBAA0C;AAAA,EACrD,qBAAqB;AAAA,EACrB,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,cAAc;AAAA,EACd,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,sBAAsB;AACxB;AASO,SAAS,iBAAiB,WAAuC;AAEtE,QAAM,SAAS,SAAS,UAAU,QAAQ,mBAAmB,OAAO,EAAE,YAAY,CAAC;AACnF,QAAM,cAAc,QAAQ,IAAI,MAAM;AACtC,MAAI,YAAa,QAAO;AAExB,QAAM,SAAS,gBAAgB,SAAS;AACxC,MAAI,OAAQ,QAAO;AAEnB,QAAM,SAAS,UAAU,EAAE;AAC3B,MAAI,OAAQ,QAAO;AAEnB,SAAO;AACT;;;ACZO,IAAe,YAAf,MAAyB;AAAA,EAK9B,YACqB,WACA,cACnB,UACA,OACA;AAJmB;AACA;AAInB,SAAK,WAAW,YAAY,YAAY;AACxC,SAAK,QAAQ;AAAA,EACf;AAAA,EAZU;AAAA,EACA,UAA6B;AAAA,EACpB;AAAA;AAAA,EAaT,WAA8B;AACtC,WAAO,CAAC;AAAA,EACV;AAAA;AAAA,EAGU,gBAA6D;AACrE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,IAAI,aAAsC;AAC9C,QAAI,CAAC,KAAK,SAAS;AACjB,WAAK,UAAU,MAAM,KAAK,SAAS,cAAc;AAAA,QAC/C,cAAc,KAAK;AAAA,QACnB,OAAO,KAAK,SAAS;AAAA,QACrB,WAAW;AAAA,QACX,OAAO,KAAK,SAAS,iBAAiB,KAAK,SAAS;AAAA,QACpD,WAAW;AAAA;AAAA,QACX,YAAY,KAAK,cAAc;AAAA,MACjC,CAAC;AACD,WAAK,mBAAmB,KAAK,OAAO;AAAA,IACtC;AAEA,mBAAO,KAAK,IAAI,KAAK,SAAS,sBAAsB,YAAY,UAAU,GAAG,EAAE,CAAC,QAAG;AAEnF,gBAAY,SAAS,KAAK,SAAS;AACnC,UAAM,WAAW,MAAM,KAAK,QAAQ,YAAY,WAAW;AAG3D,gBAAY;AAAA,MACV,KAAK,SAAS;AAAA,MACd,SAAS,MAAM,SAAS,KAAK,SAAS,gBAAgB;AAAA,MACtD,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS,iBACL,OAAO,OAAO,SAAS,cAAc,EAAE,CAAC,IACxC;AAAA,IACN;AAEA,UAAM,UAAU,SAAS;AACzB,mBAAO,KAAK,IAAI,KAAK,SAAS,wBAAwB,QAAQ,MAAM,SAAS;AAC7E,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,mBAAmB,SAA2B;AACpD,YAAQ,GAAG,SAAS,CAAC,UAAU;AAC7B,qBAAO,MAAM,IAAI,KAAK,SAAS,YAAY,KAAK,UAAU,MAAM,IAAI,CAAC,EAAE;AAAA,IACzE,CAAC;AAED,YAAQ,GAAG,cAAc,CAAC,UAAU;AAClC,qBAAO,KAAK,IAAI,KAAK,SAAS,iBAAiB,KAAK,UAAU,MAAM,IAAI,CAAC,EAAE;AAAA,IAC7E,CAAC;AAED,YAAQ,GAAG,YAAY,CAAC,UAAU;AAChC,qBAAO,KAAK,IAAI,KAAK,SAAS,gBAAgB,KAAK,UAAU,MAAM,IAAI,CAAC,EAAE;AAAA,IAC5E,CAAC;AAED,YAAQ,GAAG,SAAS,CAAC,UAAU;AAC7B,qBAAO,MAAM,IAAI,KAAK,SAAS,YAAY,KAAK,UAAU,MAAM,IAAI,CAAC,EAAE;AAAA,IACzE,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,UAAyB;AAC7B,QAAI;AACF,UAAI,KAAK,SAAS;AAChB,cAAM,KAAK,QAAQ,MAAM;AACzB,aAAK,UAAU;AAAA,MACjB;AAAA,IACF,SAAS,KAAK;AACZ,qBAAO,MAAM,IAAI,KAAK,SAAS,2BAA2B,GAAG,EAAE;AAAA,IACjE;AAAA,EACF;AACF;;;AChIA,eAAsB,aACpB,WACA,WACA,YACiB;AACjB,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAE/B,iBAAO,KAAK,sBAAsB,SAAS,YAAO,UAAU,EAAE;AAE9D,SAAO,IAAI,QAAgB,CAACE,UAAS,WAAW;AAC9C,iBAAa,SAAS,EACnB,UAAU,SAAS,EACnB,OAAO,CAAC,EACR,OAAO,UAAU,EACjB,GAAG,OAAO,MAAM;AACf,qBAAO,KAAK,mBAAmB,UAAU,EAAE;AAC3C,MAAAA,SAAQ,UAAU;AAAA,IACpB,CAAC,EACA,GAAG,SAAS,CAAC,QAAQ;AACpB,qBAAO,MAAM,yBAAyB,IAAI,OAAO,EAAE;AACnD,aAAO,IAAI,MAAM,yBAAyB,IAAI,OAAO,EAAE,CAAC;AAAA,IAC1D,CAAC,EACA,IAAI;AAAA,EACT,CAAC;AACH;;;ACnBA,SAAS,QAAQ,SAAyB;AACxC,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,SAAO,GAAG,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACpE;AAGA,SAAS,qBAAqB,YAAgC;AAC5D,SAAO,WAAW,SACf,IAAI,CAAC,QAAQ,IAAI,QAAQ,IAAI,KAAK,CAAC,WAAM,QAAQ,IAAI,GAAG,CAAC,KAAK,IAAI,KAAK,KAAK,CAAC,EAAE,EAC/E,KAAK,IAAI;AACd;AAIA,SAAS,kBAAkB,YAAoB,iBAAyB,cAAsB,cAA8B;AAC1H,QAAM,QAAQ,eAAe;AAE7B,SAAO,iEAAiE,MAAM,IAAI,KAAK,MAAM,MAAM;AAAA,eACtF,MAAM,MAAM,IAAI,KAAK,MAAM,MAAM,WAAW,IAAI,MAAM,MAAM,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6DAkBnB,MAAM,MAAM,IAAI;AAAA,EAC3E,MAAM,kBAAkB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBjC,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,eAAe;AAAA,EACf,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAWiB,MAAM,MAAM,WAAW;AAAA,yBAC7B,MAAM,SAAS,UAAU,KAAK,IAAI,CAAC;AAAA,WACjD,MAAM,SAAS,OAAO,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAc3C;AAmBA,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,YAA6B,CAAC;AAAA,EAEtC,YAAY,WAAmB,WAAmB,cAAsB,OAAgB;AACtF,UAAM,gBAAgB,cAAc,QAAW,KAAK;AACpD,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAGA,IAAY,eAAuB;AACjC,WAAO,KAAK,KAAK,WAAW,YAAY;AAAA,EAC1C;AAAA,EAEA,IAAY,eAAuB;AACjC,WAAO,KAAK,KAAK,WAAW,WAAW;AAAA,EACzC;AAAA;AAAA,EAIU,WAA8B;AACtC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QAEF,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,WAAW,EAAE,MAAM,UAAU,aAAa,kCAAkC;AAAA,YAC5E,aAAa,EAAE,MAAM,UAAU,aAAa,yCAAyC;AAAA,YACrF,OAAO,EAAE,MAAM,WAAW,aAAa,6CAA6C;AAAA,UACtF;AAAA,UACA,UAAU,CAAC,aAAa,eAAe,OAAO;AAAA,QAChD;AAAA,QACA,SAAS,OAAO,YAAqB;AACnC,gBAAM,OAAO;AACb,iBAAO,KAAK,mBAAmB,IAAI;AAAA,QACrC;AAAA,MACF;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QAEF,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,UAAU,EAAE,MAAM,UAAU,aAAa,0CAA0C;AAAA,YACnF,OAAO,EAAE,MAAM,UAAU,aAAa,cAAc;AAAA,YACpD,UAAU,EAAE,MAAM,UAAU,aAAa,2BAA2B;AAAA,YACpE,WAAW;AAAA,cACT,MAAM;AAAA,cACN,OAAO,EAAE,MAAM,SAAS;AAAA,cACxB,aAAa;AAAA,YACf;AAAA,UACF;AAAA,UACA,UAAU,CAAC,YAAY,SAAS,YAAY,WAAW;AAAA,QACzD;AAAA,QACA,SAAS,OAAO,YAAqB;AACnC,gBAAM,OAAO;AACb,iBAAO,KAAK,mBAAmB,IAAI;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,MAAgB,eACd,UACA,MACkB;AAClB,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO,KAAK,mBAAmB,IAAmC;AAAA,MACpE,KAAK;AACH,eAAO,KAAK,mBAAmB,IAAmC;AAAA,MACpE;AACE,cAAM,IAAI,MAAM,iBAAiB,QAAQ,EAAE;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,mBAAmB,MAAyC;AACxE,UAAM,MAAM,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG,GAAG;AAC9C,UAAM,WAAW,YAAY,GAAG;AAChC,UAAM,aAAa,KAAK,KAAK,cAAc,QAAQ;AAEnD,UAAM,aAAa,KAAK,WAAW,KAAK,WAAW,UAAU;AAE7D,UAAM,WAA0B;AAAA,MAC9B,WAAW,KAAK;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB;AAAA,IACF;AACA,SAAK,UAAU,KAAK,QAAQ;AAE5B,mBAAO,KAAK,oCAAoC,GAAG,OAAO,QAAQ,KAAK,SAAS,CAAC,EAAE;AACnF,WAAO,8BAA8B,QAAQ;AAAA,EAC/C;AAAA,EAEA,MAAc,mBAAmB,MAAyC;AACxE,UAAM,gBAAgB,KAAK,SAAS;AACpC,UAAM,cAAc,KAAK,cAAc,KAAK,QAAQ;AAEpD,mBAAO,KAAK,uCAAkC,KAAK,YAAY,EAAE;AACjE,WAAO,sBAAsB,KAAK,YAAY;AAAA,EAChD;AAAA;AAAA,EAGA,UAAU,MAAsC;AAC9C,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,MAChB,cAAc,KAAK;AAAA,IACrB;AAAA,EACF;AACF;AAKA,SAAS,mBAAmB,QAA8B;AACxD,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT;AAEA,QAAM,OAAO,OACV,IAAI,CAAC,MAAM,MAAM,EAAE,KAAK,YAAY,EAAE,IAAI,WAAW,KAAK,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,WAAW,IAAI,EACxG,KAAK,IAAI;AAEZ,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAKP,IAAI;AACN;AAGA,SAAS,0BAAkC;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAST;AAGA,SAAS,uBAA+B;AACtC,SAAO;AAAA;AAAA;AAAA;AAIT;AAGA,SAAS,mBAAmB,SAAyB;AACnD,QAAM,IAAI,KAAK,MAAM,UAAU,IAAI;AACnC,QAAM,IAAI,KAAK,MAAO,UAAU,OAAQ,EAAE;AAC1C,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,SAAO,IAAI,IACP,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,KAChE,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACxC;AAGA,SAAS,qBAAqB,UAA8B;AAC1D,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,SACV,IAAI,CAAC,OAAO,OAAO,mBAAmB,GAAG,SAAS,CAAC,QAAQ,GAAG,KAAK,MAAM,GAAG,WAAW,IAAI,EAC3F,KAAK,IAAI;AAEZ,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAKP,IAAI;AAAA;AAAA;AAGN;AAUA,eAAsB,gBACpB,OACA,YACA,QACA,UACA,OACuB;AACvB,QAAMC,UAAS,UAAU;AACzB,QAAM,YAAY,KAAKA,QAAO,YAAY,MAAM,IAAI;AAGpD,QAAM,aAAa,mBAAmB,MAAM;AAC5C,QAAM,kBAAkB,wBAAwB;AAChD,QAAM,eAAe,qBAAqB;AAC1C,QAAM,eAAe,qBAAqB,QAAQ;AAElD,QAAM,eAAe,kBAAkB,YAAY,iBAAiB,cAAc,YAAY;AAC9F,QAAM,QAAQ,IAAI,aAAa,MAAM,UAAU,WAAW,cAAc,KAAK;AAE7E,QAAM,kBAAkB,qBAAqB,UAAU;AAGvD,QAAM,kBAAkB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC;AACjF,QAAM,WAAW,MAAM,WAAW;AAClC,QAAM,kBAAkB,MAAM,KAAK,EAAE,QAAQ,gBAAgB,GAAG,CAAC,GAAG,MAAM;AACxE,UAAM,SAAS,KAAK,MAAM,YAAY,IAAI,IAAI;AAC9C,UAAM,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD,UAAM,KAAK,KAAK,IAAI,KAAK,MAAM,MAAM,QAAQ,GAAG,KAAK,MAAM,SAAS,WAAW,CAAC,CAAC;AACjF,WAAO,GAAG,QAAQ,EAAE,CAAC,SAAI,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAK,EAAE;AAAA,EACpD,CAAC,EAAE,KAAK,IAAI;AAEZ,QAAM,aAAa;AAAA,IACjB,cAAc,MAAM,QAAQ;AAAA,IAC5B,iBAAiB,QAAQ,MAAM,QAAQ,CAAC,KAAK,KAAK,MAAM,MAAM,QAAQ,CAAC;AAAA,IACvE,aAAa,MAAM,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IACvD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,MAAI;AAIJ,QAAM,kBAAmB,MAAc,mBAAmB,KAAK,KAAK;AAGnE,EAAC,MAAc,qBAAqB,OAAO,SAA2B;AACrE,oBAAgB;AAChB,WAAO,gBAAgB,IAAI;AAAA,EAC7B;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,UAAU;AAE1B,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,WAAO,MAAM,UAAU,aAAa;AAAA,EACtC,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AACF;;;ACrZA,SAAS,YAAY,cAAc,YAAY,cAAc,aAAa,qBAAqB;AAE/F,SAAS,iBAAAC,sBAAqB;AAiCvB,SAAS,YACd,KACA,MACA,MACA,UACM;AACN,eAAa,KAAK,MAAM,EAAE,GAAG,MAAM,UAAU,QAAQ,GAAU,CAAC,OAAO,QAAQ,WAAW;AACxF,aAAS,OAAO,OAAO,UAAU,EAAE,GAAG,OAAO,UAAU,EAAE,CAAC;AAAA,EAC5D,CAAC;AACH;AAMO,SAAS,gBAAgB,KAAa,MAAyE;AACpH,SAAO,aAAa,KAAK,EAAE,UAAU,SAA2B,GAAG,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK;AAC7F;AAKO,SAAS,aACd,KACA,MACA,MAC0B;AAC1B,SAAO,cAAc,KAAK,MAAM,EAAE,UAAU,SAAS,GAAG,KAAK,CAAC;AAChE;AAMO,SAAS,oBAAoB,SAA8B;AAChE,SAAOC,eAAc,OAAO;AAC9B;;;AC/DA,IAAM,aAAa,cAAc;AACjC,IAAM,cAAc,eAAe;AAEnC,IAAM,cAAc;AAOpB,eAAe,YAAY,WAAoC;AAC7D,SAAO,IAAI,QAAgB,CAACC,aAAY;AACtC;AAAA,MACE;AAAA,MACA,CAAC,MAAM,SAAS,mBAAmB,OAAO,iBAAiB,uBAAuB,OAAO,WAAW,SAAS;AAAA,MAC7G,EAAE,SAAS,IAAK;AAAA,MAChB,CAAC,OAAO,WAAW;AACjB,YAAI,SAAS,CAAC,OAAO,KAAK,GAAG;AAC3B,UAAAA,SAAQ,WAAW;AACnB;AAAA,QACF;AACA,cAAM,QAAQ,OAAO,KAAK,EAAE,MAAM,GAAG;AACrC,cAAM,MAAM,MAAM,WAAW,IAAI,SAAS,MAAM,CAAC,CAAC,IAAI,SAAS,MAAM,CAAC,CAAC,IAAI,WAAW,OAAO,KAAK,CAAC;AACnG,QAAAA,SAAQ,SAAS,GAAG,KAAK,MAAM,IAAI,KAAK,MAAM,GAAG,IAAI,WAAW;AAAA,MAClE;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAqBA,eAAsB,YACpB,WACA,OACA,KACA,YACA,SAAiB,GACA;AACjB,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAE/B,QAAM,gBAAgB,KAAK,IAAI,GAAG,QAAQ,MAAM;AAChD,QAAM,cAAc,MAAM;AAC1B,QAAM,WAAW,cAAc;AAC/B,iBAAO,KAAK,oBAAoB,KAAK,UAAK,GAAG,iBAAiB,cAAc,QAAQ,CAAC,CAAC,UAAK,YAAY,QAAQ,CAAC,CAAC,aAAQ,UAAU,EAAE;AAErI,SAAO,IAAI,QAAgB,CAACA,UAAS,WAAW;AAC9C,iBAAa,SAAS,EACnB,aAAa,aAAa,EAC1B,YAAY,QAAQ,EACpB,cAAc,CAAC,QAAQ,WAAW,WAAW,aAAa,QAAQ,MAAM,YAAY,KAAK,QAAQ,OAAO,QAAQ,MAAM,CAAC,EACvH,OAAO,UAAU,EACjB,GAAG,OAAO,MAAM;AACf,qBAAO,KAAK,6BAA6B,UAAU,EAAE;AACrD,MAAAA,SAAQ,UAAU;AAAA,IACpB,CAAC,EACA,GAAG,SAAS,CAAC,QAAQ;AACpB,qBAAO,MAAM,2BAA2B,IAAI,OAAO,EAAE;AACrD,aAAO,IAAI,MAAM,2BAA2B,IAAI,OAAO,EAAE,CAAC;AAAA,IAC5D,CAAC,EACA,IAAI;AAAA,EACT,CAAC;AACH;AAQA,eAAsB,qBACpB,WACA,UACA,YACA,SAAiB,GACA;AACjB,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAEA,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,YAAY,WAAW,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,KAAK,YAAY,MAAM;AAAA,EACtF;AAEA,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAE/B,QAAM,aAAaC,SAAI,QAAQ,EAAE,eAAe,MAAM,QAAQ,WAAW,CAAC;AAC1E,QAAM,UAAU,WAAW;AAE3B,QAAM,YAAsB,CAAC;AAC7B,MAAI,iBAAwC;AAE5C,MAAI;AAEF,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,MAAM,SAAS,CAAC;AACtB,YAAM,WAAW,KAAK,SAAS,WAAW,CAAC,MAAM;AACjD,gBAAU,KAAK,QAAQ;AAEvB,YAAM,gBAAgB,KAAK,IAAI,GAAG,IAAI,QAAQ,MAAM;AACpD,YAAM,cAAc,IAAI,MAAM;AAC9B,qBAAO,KAAK,sBAAsB,IAAI,CAAC,IAAI,SAAS,MAAM,KAAK,IAAI,KAAK,UAAK,IAAI,GAAG,iBAAiB,cAAc,QAAQ,CAAC,CAAC,UAAK,YAAY,QAAQ,CAAC,CAAC,IAAI;AAE5J,YAAM,IAAI,QAAc,CAACD,UAAS,WAAW;AAC3C,qBAAa,SAAS,EACnB,aAAa,aAAa,EAC1B,YAAY,cAAc,aAAa,EACvC,cAAc,CAAC,YAAY,KAAK,WAAW,WAAW,CAAC,EACvD,OAAO,QAAQ,EACf,GAAG,OAAO,MAAMA,SAAQ,CAAC,EACzB,GAAG,SAAS,CAAC,QAAQ,OAAO,IAAI,MAAM,WAAW,CAAC,uBAAuB,IAAI,OAAO,EAAE,CAAC,CAAC,EACxF,IAAI;AAAA,MACT,CAAC;AAAA,IACH;AAGA,qBAAiBC,SAAI,SAAS,EAAE,KAAK,SAAS,SAAS,QAAQ,QAAQ,UAAU,CAAC;AAClF,UAAM,iBAAiB,eAAe;AACtC,UAAM,cAAc,UAAU,IAAI,CAAC,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,CAAC,GAAG,EAAE,KAAK,IAAI;AACxF,UAAM,cAAc,gBAAgB,WAAW;AAE/C,wBAAoB,eAAe,EAAE;AAGrC,mBAAO,KAAK,iBAAiB,SAAS,MAAM,oBAAe,UAAU,EAAE;AACvE,UAAM,IAAI,QAAc,CAACD,UAAS,WAAW;AAC3C,mBAAa,EACV,MAAM,cAAc,EACpB,aAAa,CAAC,MAAM,UAAU,SAAS,GAAG,CAAC,EAC3C,cAAc,CAAC,QAAQ,WAAW,WAAW,aAAa,QAAQ,MAAM,YAAY,KAAK,QAAQ,KAAK,CAAC,EACvG,OAAO,UAAU,EACjB,GAAG,OAAO,MAAMA,SAAQ,CAAC,EACzB,GAAG,SAAS,CAAC,QAAQ,OAAO,IAAI,MAAM,kBAAkB,IAAI,OAAO,EAAE,CAAC,CAAC,EACvE,IAAI;AAAA,IACT,CAAC;AAED,mBAAO,KAAK,4BAA4B,UAAU,EAAE;AACpD,WAAO;AAAA,EACT,UAAE;AAEA,QAAI,gBAAgB;AAClB,UAAI;AACF,uBAAe,eAAe;AAAA,MAChC,QAAQ;AAAA,MAAC;AAAA,IACX;AACA,QAAI;AACF,iBAAW,eAAe;AAAA,IAC5B,QAAQ;AAAA,IAAC;AAAA,EACX;AACF;AAUA,eAAsB,oCACpB,WACA,UACA,YACA,qBAA6B,KAC7B,SAAiB,GACA;AACjB,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAGA,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,YAAY,WAAW,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,KAAK,YAAY,MAAM;AAAA,EACtF;AAGA,MAAI,SAAS,WAAW,KAAK,sBAAsB,GAAG;AACpD,WAAO,qBAAqB,WAAW,UAAU,YAAY,MAAM;AAAA,EACrE;AAEA,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAG/B,QAAM,MAAM,MAAM,YAAY,SAAS;AAGvC,QAAM,cAAwB,CAAC;AAC/B,QAAM,eAAyB,CAAC;AAEhC,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,MAAM,SAAS,CAAC;AACtB,UAAM,gBAAgB,KAAK,IAAI,GAAG,IAAI,QAAQ,MAAM;AACpD,UAAM,cAAc,IAAI,MAAM;AAC9B,UAAM,WAAW,cAAc;AAC/B,iBAAa,KAAK,QAAQ;AAE1B,gBAAY;AAAA,MACV,mBAAmB,cAAc,QAAQ,CAAC,CAAC,QAAQ,YAAY,QAAQ,CAAC,CAAC,4BAA4B,GAAG,KAAK,CAAC;AAAA,IAChH;AACA,gBAAY;AAAA,MACV,oBAAoB,cAAc,QAAQ,CAAC,CAAC,QAAQ,YAAY,QAAQ,CAAC,CAAC,0BAA0B,CAAC;AAAA,IACvG;AAAA,EACF;AAGA,MAAI,YAAY;AAChB,MAAI,YAAY;AAChB,MAAI,qBAAqB,aAAa,CAAC;AAEvC,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,SAAS,KAAK,IAAI,GAAG,qBAAqB,kBAAkB;AAClE,UAAM,WAAW,MAAM,SAAS,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAChE,UAAM,WAAW,MAAM,SAAS,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAEhE,gBAAY;AAAA,MACV,IAAI,SAAS,MAAM,CAAC,mCAAmC,mBAAmB,QAAQ,CAAC,CAAC,WAAW,OAAO,QAAQ,CAAC,CAAC,IAAI,QAAQ;AAAA,IAC9H;AACA,gBAAY;AAAA,MACV,IAAI,SAAS,MAAM,CAAC,iBAAiB,mBAAmB,QAAQ,CAAC,CAAC,IAAI,QAAQ;AAAA,IAChF;AAEA,gBAAY;AACZ,gBAAY;AAEZ,yBAAqB,qBAAqB,qBAAqB,aAAa,CAAC;AAAA,EAC/E;AAEA,QAAM,gBAAgB,YAAY,KAAK,KAAK;AAE5C,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IAAM;AAAA,IACN;AAAA,IAAmB;AAAA,IACnB;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAW;AAAA,IACX;AAAA,IAAQ;AAAA,IACR;AAAA,IAAY;AAAA,IACZ;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,EACF;AAEA,iBAAO,KAAK,gCAAgC,SAAS,MAAM,2CAAsC,UAAU,EAAE;AAE7G,SAAO,IAAI,QAAgB,CAACA,UAAS,WAAW;AAC9C,gBAAY,YAAY,MAAM,EAAE,WAAW,KAAK,OAAO,KAAK,GAAG,CAAC,OAAO,SAAS,WAAW;AACzF,UAAI,OAAO;AACT,uBAAO,MAAM,4CAA4C,MAAM,EAAE;AACjE,eAAO,IAAI,MAAM,gCAAgC,MAAM,OAAO,EAAE,CAAC;AACjE;AAAA,MACF;AACA,qBAAO,KAAK,8CAA8C,UAAU,EAAE;AACtE,MAAAA,SAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AACH;;;ACtRA,IAAME,cAAa,cAAc;AACjC,IAAM,YAAY,SAAS;AAQ3B,eAAsB,aACpB,WACA,SACA,YACiB;AACjB,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAE/B,iBAAO,KAAK,sCAAiC,UAAU,EAAE;AAGzD,QAAM,UAAU,MAAM,YAAY,UAAU;AAC5C,QAAM,UAAU,KAAK,SAAS,cAAc;AAC5C,QAAM,aAAa,KAAK,SAAS,YAAY;AAE7C,QAAM,SAAS,SAAS,OAAO;AAG/B,MAAI;AACJ,MAAI;AACF,gBAAY,MAAM,cAAc,SAAS;AAAA,EAC3C,SAAS,KAAU;AACjB,QAAI,KAAK,SAAS,UAAU;AAC1B,YAAM,IAAI,MAAM,gCAAgC,SAAS,oDAAoD;AAAA,IAC/G;AACA,UAAM;AAAA,EACR;AACA,aAAW,KAAK,WAAW;AACzB,QAAI,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,MAAM,GAAG;AAC5C,YAAM,SAAS,KAAK,WAAW,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAAA,IACrD;AAAA,EACF;AAGA,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IAAM;AAAA,IACN;AAAA,IAAO;AAAA,IACP;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAW;AAAA,IACX;AAAA,IAAQ;AAAA,IACR;AAAA,IAAY;AAAA,IACZ;AAAA,EACF;AAEA,SAAO,IAAI,QAAgB,CAACC,UAAS,WAAW;AAC9C,gBAAYD,aAAY,MAAM,EAAE,KAAK,SAAS,WAAW,KAAK,OAAO,KAAK,GAAG,OAAO,OAAO,SAAS,WAAW;AAC7G,YAAM,UAAU,YAAY;AAC1B,cAAM,QAAQ,MAAM,cAAc,OAAO,EAAE,MAAM,MAAM,CAAC,CAAa;AACrE,mBAAW,KAAK,OAAO;AACrB,gBAAM,WAAW,KAAK,SAAS,CAAC,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnD;AACA,cAAM,gBAAgB,OAAO,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC/C;AAEA,UAAI,OAAO;AACT,cAAM,QAAQ;AACd,uBAAO,MAAM,2BAA2B,UAAU,MAAM,OAAO,EAAE;AACjE,eAAO,IAAI,MAAM,2BAA2B,UAAU,MAAM,OAAO,EAAE,CAAC;AACtE;AAAA,MACF;AAEA,UAAI;AACF,cAAM,WAAW,YAAY,UAAU;AAAA,MACzC,QAAQ;AACN,cAAM,SAAS,YAAY,UAAU;AAAA,MACvC;AACA,YAAM,QAAQ;AACd,qBAAO,KAAK,oBAAoB,UAAU,EAAE;AAC5C,MAAAC,SAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AACH;;;ACxFA,SAAoB,WAAXC,gBAAwB;AAEjC,YAAY,SAAS;;;ACKrB,IAAMC,cAAa,cAAc;AACjC,IAAMC,eAAc,eAAe;AAEnC,IAAM,aAAa,KAAK,UAAU,GAAG,oBAAoB;AAGzD,IAAI,gBAA6C;AA2BjD,IAAM,gBAAgB;AAEtB,IAAM,cAAc;AACpB,IAAM,eAAe;AAErB,IAAM,sBAAsB;AAE5B,IAAM,2BAA2B;AAUjC,IAAM,uBAAuB;AAE7B,IAAM,uBAAuB;AAE7B,IAAM,uBAAuB;AAI7B,eAAe,iBAAiB,WAAoC;AAClE,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC;AAAA,MACED;AAAA,MACA,CAAC,MAAM,SAAS,iBAAiB,mBAAmB,OAAO,WAAW,SAAS;AAAA,MAC/E,CAAC;AAAA,MACD,CAAC,OAAO,WAAW;AACjB,YAAI,OAAO;AACT,iBAAO,IAAI,MAAM,mBAAmB,MAAM,OAAO,EAAE,CAAC;AACpD;AAAA,QACF;AACA,QAAAC,SAAQ,WAAW,OAAO,KAAK,CAAC,CAAC;AAAA,MACnC;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,mBAAmB,WAA+D;AACtG,SAAO,IAAI,QAAQ,CAACA,UAAS,WAAW;AACtC;AAAA,MACED;AAAA,MACA;AAAA,QACE;AAAA,QAAM;AAAA,QACN;AAAA,QAAmB;AAAA,QACnB;AAAA,QAAiB;AAAA,QACjB;AAAA,QAAO;AAAA,QACP;AAAA,MACF;AAAA,MACA,CAAC;AAAA,MACD,CAAC,OAAO,WAAW;AACjB,YAAI,OAAO;AACT,iBAAO,IAAI,MAAM,mBAAmB,MAAM,OAAO,EAAE,CAAC;AACpD;AAAA,QACF;AACA,cAAM,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAClD,QAAAC,SAAQ,EAAE,OAAO,GAAG,QAAQ,EAAE,CAAC;AAAA,MACjC;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,eAAe,oBAAoB,WAAmB,SAAoC;AACxF,QAAM,WAAW,MAAM,iBAAiB,SAAS;AACjD,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,gBAAgB,EAAE,CAAC;AAEvE,QAAM,aAAuB,CAAC;AAC9B,WAAS,IAAI,GAAG,KAAK,eAAe,KAAK;AACvC,eAAW,KAAK,IAAI,QAAQ;AAAA,EAC9B;AAEA,QAAM,aAAuB,CAAC;AAC9B,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,UAAM,YAAY,KAAK,SAAS,SAAS,CAAC,MAAM;AAChD,eAAW,KAAK,SAAS;AAEzB,UAAM,IAAI,QAAc,CAACA,UAAS,WAAW;AAC3C;AAAA,QACEF;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UAAO,WAAW,CAAC,EAAE,QAAQ,CAAC;AAAA,UAC9B;AAAA,UAAM;AAAA,UACN;AAAA,UAAO,SAAS,WAAW,IAAI,YAAY;AAAA,UAC3C;AAAA,UAAa;AAAA,UACb;AAAA,UAAQ;AAAA,UACR;AAAA,QACF;AAAA,QACA,EAAE,WAAW,KAAK,OAAO,KAAK;AAAA,QAC9B,CAAC,UAAU;AACT,cAAI,OAAO;AACT,mBAAO,IAAI,MAAM,8BAA8B,WAAW,CAAC,CAAC,MAAM,MAAM,OAAO,EAAE,CAAC;AAClF;AAAA,UACF;AACA,UAAAE,SAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAYA,eAAe,aAA4C;AACzD,MAAI,cAAe,QAAO;AAC1B,MAAI,CAAC,eAAe,UAAU,GAAG;AAC/B,UAAM,IAAI,MAAM,qCAAqC,UAAU,+CAA+C;AAAA,EAChH;AACA,kBAAgB,MAAM,IAAI,iBAAiB,OAAO,YAAY;AAAA,IAC5D,oBAAoB,CAAC,KAAK;AAAA,IAC1B,wBAAwB;AAAA,EAC1B,CAAC;AACD,SAAO;AACT;AAMA,eAAe,mBAAmB,WAAuC;AACvE,QAAM,UAAU,MAAM,WAAW;AAGjC,QAAM,EAAE,MAAM,KAAK,IAAI,MAAMC,SAAM,SAAS,EACzC,OAAO,aAAa,cAAc,EAAE,KAAK,OAAO,CAAC,EACjD,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,QAAM,SAAS,KAAK,QAAQ,KAAK;AACjC,QAAM,YAAY,IAAI,aAAa,IAAI,MAAM;AAG7C,QAAM,OAAO,CAAC,KAAK,KAAK,GAAG;AAC3B,QAAM,MAAM;AACZ,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,cAAU,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK;AACzC,cAAU,SAAS,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK;AACtD,cAAU,IAAI,SAAS,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK;AAAA,EAC5D;AAEA,QAAM,cAAc,IAAI,IAAI,OAAO,WAAW,WAAW,CAAC,GAAG,GAAG,cAAc,WAAW,CAAC;AAC1F,QAAM,UAAU,MAAM,QAAQ,IAAI,EAAE,OAAO,YAAY,CAAC;AAExD,QAAM,SAAS,QAAQ,QAAQ,EAAE;AACjC,QAAM,QAAQ,QAAQ,OAAO,EAAE;AAC/B,QAAM,gBAAgB,OAAO,SAAS;AAEtC,QAAM,QAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,eAAe,KAAK;AACtC,UAAM,YAAY,OAAO,IAAI,IAAI,CAAC;AAClC,QAAI,YAAY,qBAAqB;AACnC,YAAM,KAAK;AAAA,QACT,IAAI,MAAM,IAAI,CAAC;AAAA,QACf,IAAI,MAAM,IAAI,IAAI,CAAC;AAAA,QACnB,IAAI,MAAM,IAAI,IAAI,CAAC;AAAA,QACnB,IAAI,MAAM,IAAI,IAAI,CAAC;AAAA,QACnB,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,eACP,KACiC;AACjC,QAAM,MAAM,IAAI,KAAK,IAAI,MAAM;AAC/B,QAAM,MAAM,IAAI,KAAK,IAAI,MAAM;AAG/B,QAAM,SAAS,KAAK;AACpB,QAAM,UAAU,KAAK;AACrB,QAAM,QAAQ,KAAK;AACnB,QAAM,WAAW,KAAK;AAEtB,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,YAAY,OAAQ,QAAO;AAC/B,MAAI,YAAY,QAAS,QAAO;AAChC,SAAO;AACT;AAmBA,SAAS,mBACP,MAAc,OAAe,UAC7B,OAAe,KACD;AACd,QAAM,QAAQ,IAAI,aAAa,KAAK;AACpC,QAAM,QAAQ,MAAM;AACpB,MAAI,SAAS,EAAG,QAAO;AACvB,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,QAAI,MAAM;AACV,aAAS,IAAI,OAAO,IAAI,KAAK,KAAK;AAChC,YAAM,OAAO,IAAI,QAAQ,KAAK;AAC9B,cAAQ,KAAK,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK;AAAA,IACvD;AACA,UAAM,CAAC,IAAI,MAAM;AAAA,EACnB;AACA,SAAO;AACT;AAiBA,SAAS,gBACP,MAAc,OAAe,UAAkB,QAC/C,OAAe,KACD;AACd,QAAM,QAAQ,IAAI,aAAa,MAAM;AACrC,QAAM,QAAQ,MAAM;AACpB,MAAI,SAAS,EAAG,QAAO;AACvB,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,QAAI,MAAM;AACV,aAAS,IAAI,OAAO,IAAI,KAAK,KAAK;AAChC,YAAM,OAAO,IAAI,QAAQ,KAAK;AAC9B,cAAQ,KAAK,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK;AAAA,IACvD;AACA,UAAM,CAAC,IAAI,MAAM;AAAA,EACnB;AACA,SAAO;AACT;AAGA,SAAS,qBAAqB,QAAsC;AAClE,MAAI,OAAO,WAAW,EAAG,QAAO,IAAI,aAAa,CAAC;AAClD,QAAM,MAAM,OAAO,CAAC,EAAE;AACtB,QAAM,SAAS,IAAI,aAAa,GAAG;AACnC,aAAW,OAAO,QAAQ;AACxB,aAAS,IAAI,GAAG,IAAI,KAAK,IAAK,QAAO,CAAC,KAAK,IAAI,CAAC;AAAA,EAClD;AACA,WAAS,IAAI,GAAG,IAAI,KAAK,IAAK,QAAO,CAAC,KAAK,OAAO;AAClD,SAAO;AACT;AAgBO,SAAS,aACd,OAAqB,YAAoB,UAAkB,SACrB;AACtC,QAAM,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,YAAY,QAAQ,CAAC;AACrD,QAAM,KAAK,KAAK,IAAI,MAAM,SAAS,GAAG,KAAK,IAAI,YAAY,QAAQ,CAAC;AACpE,MAAI,UAAU;AACd,MAAI,SAAS;AACb,WAAS,IAAI,IAAI,IAAI,IAAI,KAAK;AAC5B,UAAM,IAAI,KAAK,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC;AAC1C,QAAI,IAAI,SAAS;AAAE,gBAAU;AAAG,eAAS;AAAA,IAAE;AAAA,EAC7C;AACA,SAAO,WAAW,UAAU,EAAE,OAAO,QAAQ,WAAW,QAAQ,IAAI,EAAE,OAAO,IAAI,WAAW,QAAQ;AACtG;AA4BA,eAAsB,kBACpB,YACA,UACyE;AACzE,MAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,QAAM,UAAU,SAAS,SAAS,OAAO;AACzC,QAAM,WAAW,SAAS,SAAS,QAAQ;AAC3C,MAAI,KAAK,GAAG,KAAK;AAEjB,QAAM,cAA8B,CAAC;AACrC,QAAM,cAA8B,CAAC;AAErC,aAAW,MAAM,YAAY;AAC3B,UAAM,EAAE,MAAM,KAAK,IAAI,MAAMA,SAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,mBAAmB,KAAK,CAAC;AACjF,SAAK,KAAK;AAAO,SAAK,KAAK;AAG3B,UAAMC,SAAQ,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI;AACjD,UAAMC,OAAQ,WAAW,KAAK,KAAK,KAAK,KAAK,IAAI;AACjD,gBAAY,KAAK,mBAAmB,MAAM,IAAI,KAAK,UAAUD,QAAOC,IAAG,CAAC;AAGxE,UAAMC,SAAQ,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI;AAChD,UAAMC,OAAQ,UAAU,KAAK,KAAK,KAAK,KAAK,IAAI;AAChD,gBAAY,KAAK,gBAAgB,MAAM,IAAI,KAAK,UAAU,IAAID,QAAOC,IAAG,CAAC;AAAA,EAC3E;AAEA,QAAM,UAAU,qBAAqB,WAAW;AAChD,QAAM,UAAU,qBAAqB,WAAW;AAGhD,QAAM,QAAQ,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI;AACpE,QAAM,MAAQ,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI;AACpE,QAAM,QAAQ,aAAa,SAAS,OAAO,KAAK,oBAAoB;AAEpE,QAAM,QAAQ,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI;AACrE,QAAM,MAAQ,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI;AACrE,QAAM,QAAQ,aAAa,SAAS,OAAO,KAAK,oBAAoB;AAEpE,MAAI,MAAM,QAAQ,KAAK,MAAM,QAAQ,GAAG;AACtC,mBAAO;AAAA,MACL,2DACU,MAAM,UAAU,QAAQ,CAAC,CAAC,WAAW,MAAM,UAAU,QAAQ,CAAC,CAAC;AAAA,IAC3E;AACA,WAAO;AAAA,EACT;AAGA,MAAI,GAAW,GAAW,GAAW;AACrC,MAAI,SAAS;AAAE,QAAI,MAAM,QAAQ;AAAG,QAAI,KAAK;AAAA,EAAE,OAClC;AAAE,QAAI;AAAG,QAAI,MAAM;AAAA,EAAM;AACtC,MAAI,UAAU;AAAE,QAAI,MAAM,QAAQ;AAAG,QAAI,KAAK;AAAA,EAAE,OAClC;AAAE,QAAI;AAAG,QAAI,MAAM;AAAA,EAAM;AAGvC,MAAI,IAAI,KAAK,wBAAwB,IAAI,KAAK,wBAC1C,IAAI,KAAK,wBAAwB,IAAI,KAAK,sBAAsB;AAClE,mBAAO;AAAA,MACL,+CACI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;AAAA,IAC3B;AACA,WAAO;AAAA,EACT;AAEA,iBAAO;AAAA,IACL,oCAAoC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,6BAC3C,MAAM,UAAU,QAAQ,CAAC,CAAC,WAAW,MAAM,UAAU,QAAQ,CAAC,CAAC;AAAA,EAC3E;AAEA,SAAO,EAAE,GAAG,GAAG,OAAO,GAAG,QAAQ,EAAE;AACrC;AAQO,SAAS,0BAA0B,QAA0B;AAClE,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,eAAe,OAAO,OAAO,OAAK,IAAI,CAAC,EAAE;AAC/C,QAAM,cAAc,eAAe,OAAO;AAC1C,QAAM,WAAW,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO;AAC5D,SAAO,cAAc;AACvB;AAgBA,eAAsB,mBAAmB,WAAiD;AACxF,QAAM,UAAU,MAAM,YAAY,cAAc;AAEhD,MAAI;AACF,UAAM,aAAa,MAAM,mBAAmB,SAAS;AACrD,UAAM,aAAa,MAAM,oBAAoB,WAAW,OAAO;AAG/D,UAAM,eAAe,oBAAI,IAAwC;AACjE,UAAM,cAAc,oBAAI,IAAyC;AACjE,eAAW,OAAO,CAAC,YAAY,aAAa,eAAe,cAAc,GAAY;AACnF,mBAAa,IAAI,KAAK,CAAC,CAAC;AACxB,kBAAY,IAAI,KAAK,CAAC,CAAC;AAAA,IACzB;AAEA,eAAW,aAAa,YAAY;AAClC,YAAM,QAAQ,MAAM,mBAAmB,SAAS;AAGhD,YAAM,eAAe,oBAAI,IAA8B;AAEvD,iBAAW,QAAQ,OAAO;AACxB,cAAM,SAAS,eAAe,IAAI;AAClC,YAAI,QAAQ;AACV,uBAAa,IAAI,MAAM;AACvB,uBAAa,IAAI,MAAM,EAAG,KAAK,KAAK,UAAU;AAC9C,sBAAY,IAAI,MAAM,EAAG,KAAK,IAAI;AAAA,QACpC;AAAA,MACF;AAGA,iBAAW,OAAO,CAAC,YAAY,aAAa,eAAe,cAAc,GAAY;AACnF,YAAI,CAAC,aAAa,IAAI,GAAG,GAAG;AAC1B,uBAAa,IAAI,GAAG,EAAG,KAAK,CAAC;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAGA,QAAI,eAAgD;AACpD,QAAI,iBAAiB;AAErB,eAAW,CAAC,KAAK,MAAM,KAAK,cAAc;AACxC,YAAM,aAAa,0BAA0B,MAAM;AACnD,qBAAO,MAAM,0BAA0B,GAAG,gBAAgB,WAAW,QAAQ,CAAC,CAAC,aAAa,OAAO,IAAI,OAAK,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG;AACtI,UAAI,aAAa,gBAAgB;AAC/B,yBAAiB;AACjB,uBAAe;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,CAAC,gBAAgB,iBAAiB,0BAA0B;AAC9D,qBAAO,KAAK,oDAAoD,YAAY,OAAO,eAAe,QAAQ,CAAC,CAAC,GAAG;AAC/G,aAAO;AAAA,IACT;AAGA,UAAM,QAAQ,YAAY,IAAI,YAAY;AAC1C,UAAM,SAAkB;AAAA,MACtB,IAAI,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM;AAAA,MAChD,IAAI,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM;AAAA,MAChD,IAAI,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM;AAAA,MAChD,IAAI,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM;AAAA,MAChD,YAAY;AAAA,IACd;AAGA,UAAM,UAAU,MAAM,kBAAkB,YAAY,YAAY;AAChE,UAAM,SAAS,WAAW,QAAQ;AAClC,UAAM,SAAS,WAAW,SAAS;AAEnC,QAAI,OAAe,OAAe,OAAe;AAEjD,QAAI,SAAS;AACX,cAAQ,KAAK,MAAM,QAAQ,IAAI,MAAM;AACrC,cAAQ,KAAK,MAAM,QAAQ,IAAI,MAAM;AACrC,cAAQ,KAAK,MAAM,QAAQ,QAAQ,MAAM;AACzC,cAAQ,KAAK,MAAM,QAAQ,SAAS,MAAM;AAAA,IAC5C,OAAO;AAGL,YAAM,eAAe;AACrB,YAAM,UAAU,OAAO,KAAK,OAAO,MAAM;AACzC,YAAM,UAAU,OAAO,KAAK,OAAO,MAAM;AACzC,YAAM,SAAS,OAAO,KAAK,OAAO,MAAM;AACxC,YAAM,SAAS,OAAO,KAAK,OAAO,MAAM;AAExC,cAAQ,KAAK,IAAI,GAAG,KAAK,OAAO,SAAS,QAAQ,KAAK,WAAW,KAAK,CAAC;AACvE,cAAQ,KAAK,IAAI,GAAG,KAAK,OAAO,SAAS,QAAQ,KAAK,WAAW,MAAM,CAAC;AACxE,cAAQ,KAAK,IAAI,WAAW,QAAQ,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,CAAC;AAC/E,cAAQ,KAAK,IAAI,WAAW,SAAS,OAAO,KAAK,MAAM,QAAQ,WAAW,MAAM,CAAC;AAAA,IACnF;AAEA,UAAM,SAAuB;AAAA,MAC3B,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,YAAY,KAAK,MAAM,iBAAiB,GAAG,IAAI;AAAA,IACjD;AAEA,mBAAO;AAAA,MACL,sCAAsC,OAAO,QAAQ,KACjD,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,OAAO,KAAK,IAAI,OAAO,MAAM,gBAC3C,OAAO,UAAU,YAAY,CAAC,CAAC,OAAO;AAAA,IACtD;AAEA,WAAO;AAAA,EACT,UAAE;AACA,UAAM,QAAQ,MAAM,cAAc,OAAO,EAAE,MAAM,MAAM,CAAC,CAAa;AACrE,eAAW,KAAK,OAAO;AACrB,YAAM,WAAW,KAAK,SAAS,CAAC,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACnD;AACA,UAAM,gBAAgB,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACjF;AACF;;;AC1kBA,IAAMC,cAAa,cAAc;AA4B1B,IAAM,kBAAiD;AAAA,EAC5D,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AACb;AAOO,IAAM,aAAqE;AAAA,EAChF,QAAQ,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,EACpC,QAAQ,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,EACpC,OAAO,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,EACnC,OAAO,EAAE,OAAO,MAAM,QAAQ,KAAK;AACrC;AA6BA,SAAS,gBAAgB,aAA0B,WAA4B;AAC7E,MAAI,WAAW;AACb,UAAM,EAAE,OAAO,OAAO,IAAI,WAAW,WAAW;AAEhD,WAAO,SAAS,KAAK,IAAI,MAAM,6CAA6C,KAAK,IAAI,MAAM;AAAA,EAC7F;AAEA,UAAQ,aAAa;AAAA,IACnB,KAAK;AAEH,aAAO;AAAA,IACT,KAAK;AAEH,aAAO;AAAA,IACT,KAAK;AAEH,aAAO;AAAA,IACT,KAAK;AAEH,aAAO;AAAA,EACX;AACF;AAaA,eAAsB,mBACpB,WACA,YACA,aACA,UAA0B,CAAC,GACV;AACjB,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAE/B,QAAM,cAA2B;AAGjC,MAAI,gBAAgB,eAAe,CAAC,QAAQ,WAAW;AACrD,mBAAO,KAAK,wBAAwB,WAAW,oBAAe,UAAU,EAAE;AAC1E,UAAM,SAAS,WAAW,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,QAAM,KAAK,gBAAgB,aAAa,QAAQ,aAAa,KAAK;AAClE,iBAAO,KAAK,8BAA8B,WAAW,aAAa,EAAE,YAAO,UAAU,EAAE;AAEvF,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IAAM;AAAA,IACN;AAAA,IAAO;AAAA,IACP;AAAA,IAAQ;AAAA,IACR;AAAA,IAAW;AAAA,IACX;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAY;AAAA,IACZ;AAAA,EACF;AAEA,SAAO,IAAI,QAAgB,CAACC,UAAS,WAAW;AAC9C,gBAAYD,aAAY,MAAM,EAAE,WAAW,KAAK,OAAO,KAAK,GAAG,CAAC,OAAO,SAAS,WAAW;AACzF,UAAI,OAAO;AACT,uBAAO,MAAM,mCAAmC,UAAU,MAAM,OAAO,EAAE;AACzE,eAAO,IAAI,MAAM,mCAAmC,UAAU,MAAM,OAAO,EAAE,CAAC;AAC9E;AAAA,MACF;AACA,qBAAO,KAAK,qCAAqC,UAAU,EAAE;AAC7D,MAAAC,SAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AACH;AA4DA,eAAe,uBACb,WACA,YACAC,SACiB;AACjB,QAAM,EAAE,OAAO,SAAS,SAAS,MAAM,cAAc,IAAIA;AACzD,QAAM,YAAY,QAAQ,UAAU;AACpC,QAAM,gBAAgB,SAAS;AAE/B,QAAM,SAAS,MAAM,mBAAmB,SAAS;AAEjD,MAAI,CAAC,QAAQ;AACX,mBAAO,KAAK,IAAI,KAAK,gDAAgD;AACrE,WAAO,mBAAmB,WAAW,YAAY,aAAa;AAAA,EAChE;AAEA,QAAM,aAAa,MAAM,mBAAmB,SAAS;AAGrD,MAAI;AACJ,MAAI;AACJ,MAAI,OAAO,aAAa,eAAe,OAAO,aAAa,gBAAgB;AACzE,kBAAc;AACd,kBAAc,OAAO;AAAA,EACvB,OAAO;AACL,kBAAc,OAAO,IAAI,OAAO;AAChC,kBAAc,KAAK,IAAI,GAAG,WAAW,QAAQ,WAAW;AAAA,EAC1D;AAGA,QAAM,WAAW,UAAU;AAC3B,QAAM,WAAW,OAAO,QAAQ,OAAO;AAEvC,MAAI,OAAe,OAAe,OAAe;AACjD,MAAI,WAAW,UAAU;AAEvB,YAAQ,OAAO;AACf,YAAQ,KAAK,MAAM,QAAQ,QAAQ;AACnC,YAAQ,OAAO,IAAI,KAAK,OAAO,OAAO,QAAQ,SAAS,CAAC;AACxD,YAAQ,OAAO;AAAA,EACjB,OAAO;AAEL,YAAQ,OAAO;AACf,YAAQ,KAAK,MAAM,QAAQ,QAAQ;AACnC,YAAQ,OAAO;AACf,YAAQ,OAAO,IAAI,KAAK,OAAO,OAAO,SAAS,SAAS,CAAC;AAAA,EAC3D;AAEA,QAAM,gBAAgB;AAAA,IACpB,aAAa,WAAW,OAAO,WAAW,YAAY,OAAO,IAAI,OAAO,6CAC/D,OAAO,IAAI,OAAO;AAAA,IAC3B,aAAa,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,UAAU,OAAO,IAAI,IAAI;AAAA,IACtE;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,iBAAO,KAAK,IAAI,KAAK,oCAAoC,OAAO,QAAQ,WAAM,UAAU,EAAE;AAE1F,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IAAM;AAAA,IACN;AAAA,IAAmB;AAAA,IACnB;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAW;AAAA,IACX;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAY;AAAA,IACZ;AAAA,EACF;AAEA,SAAO,IAAI,QAAgB,CAACD,UAAS,WAAW;AAC9C,gBAAYD,aAAY,MAAM,EAAE,WAAW,KAAK,OAAO,KAAK,GAAG,CAAC,OAAO,SAAS,WAAW;AACzF,UAAI,OAAO;AACT,uBAAO,MAAM,IAAI,KAAK,oBAAoB,UAAU,MAAM,OAAO,EAAE;AACnE,eAAO,IAAI,MAAM,GAAG,KAAK,uBAAuB,UAAU,MAAM,OAAO,EAAE,CAAC;AAC1E;AAAA,MACF;AACA,qBAAO,KAAK,IAAI,KAAK,eAAe,UAAU,EAAE;AAChD,MAAAC,SAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AACH;AAaA,eAAsB,uBACpB,WACA,YACiB;AACjB,SAAO,uBAAuB,WAAW,YAAY;AAAA,IACnD,OAAO;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,EACjB,CAAC;AACH;AAaA,eAAsB,qBACpB,WACA,YACiB;AACjB,SAAO,uBAAuB,WAAW,YAAY;AAAA,IACnD,OAAO;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,EACjB,CAAC;AACH;AAaA,eAAsB,mBACpB,WACA,YACiB;AACjB,SAAO,uBAAuB,WAAW,YAAY;AAAA,IACnD,OAAO;AAAA,IACP,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,EACjB,CAAC;AACH;AAoBA,eAAsB,yBACpB,WACA,WACA,MACA,YAAwB,CAAC,UAAU,UAAU,GAC6D;AAC1G,QAAM,gBAAgB,SAAS;AAG/B,QAAM,WAAW,oBAAI,IAA6B;AAClD,aAAW,KAAK,WAAW;AACzB,UAAM,QAAQ,gBAAgB,CAAC;AAC/B,QAAI,UAAU,OAAQ;AACtB,UAAM,OAAO,SAAS,IAAI,KAAK,KAAK,CAAC;AACrC,SAAK,KAAK,CAAC;AACX,aAAS,IAAI,OAAO,IAAI;AAAA,EAC1B;AAEA,QAAM,WAA4G,CAAC;AAEnH,aAAW,CAAC,OAAO,mBAAmB,KAAK,UAAU;AACnD,UAAM,SAAS,UAAU,SAAS,aAAa,UAAU,QAAQ,SAAS;AAC1E,UAAM,UAAU,KAAK,WAAW,GAAG,IAAI,IAAI,MAAM,MAAM;AAEvD,QAAI;AACF,UAAI,UAAU,QAAQ;AACpB,cAAM,uBAAuB,WAAW,OAAO;AAAA,MACjD,WAAW,UAAU,OAAO;AAC1B,cAAM,qBAAqB,WAAW,OAAO;AAAA,MAC/C,WAAW,UAAU,OAAO;AAC1B,cAAM,mBAAmB,WAAW,OAAO;AAAA,MAC7C,OAAO;AACL,cAAM,mBAAmB,WAAW,SAAS,KAAK;AAAA,MACpD;AACA,YAAM,OAAO,WAAW,KAAK;AAC7B,iBAAW,KAAK,qBAAqB;AACnC,iBAAS,KAAK,EAAE,UAAU,GAAG,aAAa,OAAO,MAAM,SAAS,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO,CAAC;AAAA,MAC1G;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,qBAAO,KAAK,YAAY,KAAK,gBAAgB,IAAI,KAAK,OAAO,EAAE;AAAA,IACjE;AAAA,EACF;AAEA,SAAO;AACT;;;AC5ZA,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BtB,IAAM,qBAAqB;AAAA,EACzB,MAAM;AAAA,EACN,YAAY;AAAA,IACV,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,UACV,OAAO,EAAE,MAAM,UAAU,aAAa,uCAAkC;AAAA,UACxE,aAAa,EAAE,MAAM,UAAU,aAAa,yCAAyC;AAAA,UACrF,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,MAAM,SAAS;AAAA,YACxB,aAAa;AAAA,UACf;AAAA,UACA,UAAU;AAAA,YACR,MAAM;AAAA,YACN,aAAa;AAAA,YACb,OAAO;AAAA,cACL,MAAM;AAAA,cACN,YAAY;AAAA,gBACV,OAAO,EAAE,MAAM,UAAU,aAAa,wBAAwB;AAAA,gBAC9D,KAAK,EAAE,MAAM,UAAU,aAAa,sBAAsB;AAAA,gBAC1D,aAAa,EAAE,MAAM,UAAU,aAAa,+BAA+B;AAAA,cAC7E;AAAA,cACA,UAAU,CAAC,SAAS,OAAO,aAAa;AAAA,YAC1C;AAAA,UACF;AAAA,QACF;AAAA,QACA,UAAU,CAAC,SAAS,eAAe,QAAQ,UAAU;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAAA,EACA,UAAU,CAAC,QAAQ;AACrB;AAIA,IAAM,cAAN,cAA0B,UAAU;AAAA,EAC1B,gBAAgC,CAAC;AAAA,EAEzC,YAAY,OAAgB;AAC1B,UAAM,eAAe,eAAe,QAAW,KAAK;AAAA,EACtD;AAAA,EAEU,WAA8B;AACtC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QACF,YAAY;AAAA,QACZ,SAAS,OAAO,SAAkB;AAChC,iBAAO,KAAK,eAAe,eAAe,IAA+B;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,eACd,UACA,MACkB;AAClB,QAAI,aAAa,eAAe;AAC9B,WAAK,gBAAgB,KAAK;AAC1B,qBAAO,KAAK,yBAAyB,KAAK,cAAc,MAAM,SAAS;AACvE,aAAO,EAAE,SAAS,MAAM,OAAO,KAAK,cAAc,OAAO;AAAA,IAC3D;AACA,UAAM,IAAI,MAAM,iBAAiB,QAAQ,EAAE;AAAA,EAC7C;AAAA,EAEA,mBAAmC;AACjC,WAAO,KAAK;AAAA,EACd;AACF;AAIA,eAAsB,eACpB,OACA,YACA,OACsB;AACtB,QAAM,QAAQ,IAAI,YAAY,KAAK;AAGnC,QAAM,kBAAkB,WAAW,SAAS,IAAI,CAAC,QAAQ;AACvD,UAAM,QAAQ,IAAI,MACf,IAAI,CAAC,MAAM,IAAI,EAAE,MAAM,QAAQ,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAClE,KAAK,GAAG;AACX,WAAO,IAAI,IAAI,MAAM,QAAQ,CAAC,CAAC,YAAO,IAAI,IAAI,QAAQ,CAAC,CAAC,MAAM,IAAI,IAAI;AAAA,SAAY,KAAK;AAAA,EACzF,CAAC;AAED,QAAM,SAAS;AAAA,IACb,qCAAqC,WAAW,SAAS,QAAQ,CAAC,CAAC;AAAA;AAAA,IACnE,UAAU,MAAM,QAAQ;AAAA,IACxB,aAAa,WAAW,SAAS,QAAQ,CAAC,CAAC;AAAA;AAAA,IAC3C;AAAA,IACA,gBAAgB,KAAK,MAAM;AAAA,IAC3B;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,MAAI;AACF,UAAM,MAAM,IAAI,MAAM;AACtB,UAAM,UAAU,MAAM,iBAAiB;AAEvC,QAAI,QAAQ,WAAW,GAAG;AACxB,qBAAO,KAAK,sCAAsC;AAClD,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,YAAY,KAAK,QAAQ,MAAM,QAAQ,GAAG,QAAQ;AACxD,UAAM,gBAAgB,SAAS;AAE/B,UAAM,SAAsB,CAAC;AAE7B,eAAW,QAAQ,SAAS;AAC1B,YAAM,KAAK,WAAW;AACtB,YAAM,YAAY,QAAQ,KAAK,KAAK;AACpC,YAAM,gBAAgB,KAAK,SAAS,OAAO,CAAC,KAAK,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;AACjF,YAAM,aAAa,KAAK,WAAW,GAAG,SAAS,MAAM;AAErD,YAAM,WAA2B,KAAK,SAAS,IAAI,CAAC,OAAO;AAAA,QACzD,OAAO,EAAE;AAAA,QACT,KAAK,EAAE;AAAA,QACP,aAAa,EAAE;AAAA,MACjB,EAAE;AAGF,UAAI,SAAS,WAAW,GAAG;AACzB,cAAM,YAAY,MAAM,UAAU,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,KAAK,UAAU;AAAA,MAClF,OAAO;AACL,cAAM,qBAAqB,MAAM,UAAU,UAAU,UAAU;AAAA,MACjE;AAIA,UAAI;AACJ,UAAI;AACF,cAAM,mBAA+B,CAAC,UAAU,kBAAkB,mBAAmB,kBAAkB,UAAU;AACjH,cAAM,UAAU,MAAM,yBAAyB,YAAY,WAAW,WAAW,gBAAgB;AACjG,YAAI,QAAQ,SAAS,GAAG;AACtB,qBAAW,QAAQ,IAAI,CAAC,OAAO;AAAA,YAC7B,MAAM,EAAE;AAAA,YACR,aAAa,EAAE;AAAA,YACf,UAAU,EAAE;AAAA,YACZ,OAAO,EAAE;AAAA,YACT,QAAQ,EAAE;AAAA,UACZ,EAAE;AACF,yBAAO,KAAK,2BAA2B,SAAS,MAAM,2BAA2B,KAAK,KAAK,EAAE;AAAA,QAC/F;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,uBAAO,KAAK,wDAAwD,KAAK,KAAK,KAAK,OAAO,EAAE;AAAA,MAC9F;AAGA,UAAI;AACJ,UAAI;AACF,cAAM,aAAa,SAAS,WAAW,IACnC,4BAA4B,YAAY,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,GAAG,IAC1E,8BAA8B,YAAY,QAAQ;AAEtD,cAAM,UAAU,KAAK,WAAW,GAAG,SAAS,MAAM;AAClD,cAAM,cAAc,SAAS,UAAU;AAEvC,wBAAgB,KAAK,WAAW,GAAG,SAAS,gBAAgB;AAC5D,cAAM,aAAa,YAAY,SAAS,aAAa;AACrD,uBAAO,KAAK,4CAA4C,KAAK,KAAK,EAAE;AAAA,MACtE,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,uBAAO,KAAK,4CAA4C,KAAK,KAAK,KAAK,OAAO,EAAE;AAChF,wBAAgB;AAAA,MAClB;AAGA,UAAI,UAAU;AACZ,cAAM,kBAAkB,SAAS,KAAK,OAAK,EAAE,gBAAgB,MAAM;AACnE,YAAI,iBAAiB;AACnB,cAAI;AACF,kBAAM,qBAAqB,SAAS,WAAW,IAC3C,4BAA4B,YAAY,KAAK,OAAO,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,GAAG,IACtF,qCAAqC,YAAY,UAAU,KAAK,KAAK;AACzE,kBAAM,kBAAkB,KAAK,WAAW,GAAG,SAAS,eAAe;AACnE,kBAAM,cAAc,iBAAiB,kBAAkB;AACvD,kBAAM,wBAAwB,gBAAgB,KAAK,QAAQ,QAAQ,gBAAgB;AACnF,kBAAM,aAAa,gBAAgB,MAAM,iBAAiB,qBAAqB;AAE/E,4BAAgB,OAAO;AACvB,2BAAO,KAAK,yDAAyD,KAAK,KAAK,EAAE;AAAA,UACnF,SAAS,KAAK;AACZ,kBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,2BAAO,KAAK,qDAAqD,KAAK,KAAK,KAAK,OAAO,EAAE;AAAA,UAC3F;AAAA,QACF;AAAA,MACF;AAGA,YAAM,SAAS,KAAK,WAAW,GAAG,SAAS,KAAK;AAChD,YAAM,YAAY;AAAA,QAChB,KAAK,KAAK,KAAK;AAAA;AAAA,QACf,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,GAAG,KAAK,SAAS;AAAA,UACf,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,QAAQ,CAAC,CAAC,YAAO,EAAE,IAAI,QAAQ,CAAC,CAAC,cAAS,EAAE,WAAW;AAAA,QAC1F;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,KAAK,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAAA,QACxC;AAAA,MACF,EAAE,KAAK,IAAI;AACX,YAAM,cAAc,QAAQ,SAAS;AAErC,aAAO,KAAK;AAAA,QACV;AAAA,QACA,OAAO,KAAK;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,MAAM,KAAK;AAAA,QACX;AAAA,MACF,CAAC;AAED,qBAAO,KAAK,gCAAgC,KAAK,KAAK,KAAK,cAAc,QAAQ,CAAC,CAAC,IAAI;AAAA,IACzF;AAEA,mBAAO,KAAK,2BAA2B,OAAO,MAAM,SAAS;AAC7D,WAAO;AAAA,EACT,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AACF;;;ACrQA,IAAME,iBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyCtB,IAAM,2BAA2B;AAAA,EAC/B,MAAM;AAAA,EACN,YAAY;AAAA,IACV,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,UACV,OAAO,EAAE,MAAM,UAAU,aAAa,2CAAsC;AAAA,UAC5E,aAAa,EAAE,MAAM,UAAU,aAAa,wCAAwC;AAAA,UACpF,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,MAAM,SAAS;AAAA,YACxB,aAAa;AAAA,UACf;AAAA,UACA,UAAU;AAAA,YACR,MAAM;AAAA,YACN,aAAa;AAAA,YACb,OAAO;AAAA,cACL,MAAM;AAAA,cACN,YAAY;AAAA,gBACV,OAAO,EAAE,MAAM,UAAU,aAAa,wBAAwB;AAAA,gBAC9D,KAAK,EAAE,MAAM,UAAU,aAAa,sBAAsB;AAAA,gBAC1D,aAAa,EAAE,MAAM,UAAU,aAAa,+BAA+B;AAAA,cAC7E;AAAA,cACA,UAAU,CAAC,SAAS,OAAO,aAAa;AAAA,YAC1C;AAAA,UACF;AAAA,UACA,eAAe,EAAE,MAAM,UAAU,aAAa,+CAA0C;AAAA,UACxF,MAAM,EAAE,MAAM,UAAU,aAAa,4BAA4B;AAAA,UACjE,OAAO,EAAE,MAAM,UAAU,aAAa,iCAAiC;AAAA,QACzE;AAAA,QACA,UAAU,CAAC,SAAS,eAAe,QAAQ,YAAY,iBAAiB,QAAQ,OAAO;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AAAA,EACA,UAAU,CAAC,OAAO;AACpB;AAIA,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC/B,eAAoC,CAAC;AAAA,EAE7C,YAAY,OAAgB;AAC1B,UAAM,oBAAoBA,gBAAe,QAAW,KAAK;AAAA,EAC3D;AAAA,EAEU,WAA8B;AACtC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QACF,YAAY;AAAA,QACZ,SAAS,OAAO,SAAkB;AAChC,iBAAO,KAAK,eAAe,qBAAqB,IAA+B;AAAA,QACjF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,eACd,UACA,MACkB;AAClB,QAAI,aAAa,qBAAqB;AACpC,WAAK,eAAe,KAAK;AACzB,qBAAO,KAAK,8BAA8B,KAAK,aAAa,MAAM,eAAe;AACjF,aAAO,EAAE,SAAS,MAAM,OAAO,KAAK,aAAa,OAAO;AAAA,IAC1D;AACA,UAAM,IAAI,MAAM,iBAAiB,QAAQ,EAAE;AAAA,EAC7C;AAAA,EAEA,kBAAuC;AACrC,WAAO,KAAK;AAAA,EACd;AACF;AAIA,eAAsB,oBACpB,OACA,YACA,OACuB;AACvB,QAAM,QAAQ,IAAI,iBAAiB,KAAK;AAGxC,QAAM,kBAAkB,WAAW,SAAS,IAAI,CAAC,QAAQ;AACvD,UAAM,QAAQ,IAAI,MACf,IAAI,CAAC,MAAM,IAAI,EAAE,MAAM,QAAQ,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAClE,KAAK,GAAG;AACX,WAAO,IAAI,IAAI,MAAM,QAAQ,CAAC,CAAC,YAAO,IAAI,IAAI,QAAQ,CAAC,CAAC,MAAM,IAAI,IAAI;AAAA,SAAY,KAAK;AAAA,EACzF,CAAC;AAED,QAAM,SAAS;AAAA,IACb,qCAAqC,WAAW,SAAS,QAAQ,CAAC,CAAC;AAAA;AAAA,IACnE,UAAU,MAAM,QAAQ;AAAA,IACxB,aAAa,WAAW,SAAS,QAAQ,CAAC,CAAC;AAAA;AAAA,IAC3C;AAAA,IACA,gBAAgB,KAAK,MAAM;AAAA,IAC3B;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,MAAI;AACF,UAAM,MAAM,IAAI,MAAM;AACtB,UAAM,UAAU,MAAM,gBAAgB;AAEtC,QAAI,QAAQ,WAAW,GAAG;AACxB,qBAAO,KAAK,iDAAiD;AAC7D,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,WAAW,KAAK,QAAQ,MAAM,QAAQ,GAAG,cAAc;AAC7D,UAAM,gBAAgB,QAAQ;AAE9B,UAAM,QAAsB,CAAC;AAE7B,eAAW,QAAQ,SAAS;AAC1B,YAAM,KAAK,WAAW;AACtB,YAAM,WAAW,QAAQ,KAAK,KAAK;AACnC,YAAM,gBAAgB,KAAK,SAAS,OAAO,CAAC,KAAK,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;AACjF,YAAM,aAAa,KAAK,UAAU,GAAG,QAAQ,MAAM;AAEnD,YAAM,WAA4B,KAAK,SAAS,IAAI,CAAC,OAAO;AAAA,QAC1D,OAAO,EAAE;AAAA,QACT,KAAK,EAAE;AAAA,QACP,aAAa,EAAE;AAAA,MACjB,EAAE;AAGF,UAAI,SAAS,WAAW,GAAG;AACzB,cAAM,YAAY,MAAM,UAAU,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,KAAK,UAAU;AAAA,MAClF,OAAO;AACL,cAAM,oCAAoC,MAAM,UAAU,UAAU,UAAU;AAAA,MAChF;AAGA,UAAI;AACJ,UAAI;AACF,cAAM,aAAa,SAAS,WAAW,IACnC,4BAA4B,YAAY,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,KAAK,GAAK,QAAQ,IACzF,8BAA8B,YAAY,UAAU,GAAK,QAAQ;AAErE,cAAM,UAAU,KAAK,UAAU,GAAG,QAAQ,MAAM;AAChD,cAAM,cAAc,SAAS,UAAU;AAEvC,wBAAgB,KAAK,UAAU,GAAG,QAAQ,gBAAgB;AAC1D,cAAM,aAAa,YAAY,SAAS,aAAa;AACrD,uBAAO,KAAK,gDAAgD,KAAK,KAAK,EAAE;AAAA,MAC1E,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,uBAAO,KAAK,iDAAiD,KAAK,KAAK,KAAK,OAAO,EAAE;AACrF,wBAAgB;AAAA,MAClB;AAGA,YAAM,SAAS,KAAK,UAAU,GAAG,QAAQ,KAAK;AAC9C,YAAM,YAAY;AAAA,QAChB,KAAK,KAAK,KAAK;AAAA;AAAA,QACf,cAAc,KAAK,KAAK;AAAA;AAAA,QACxB,aAAa,KAAK,IAAI;AAAA;AAAA,QACtB,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,GAAG,KAAK,SAAS;AAAA,UACf,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,QAAQ,CAAC,CAAC,YAAO,EAAE,IAAI,QAAQ,CAAC,CAAC,cAAS,EAAE,WAAW;AAAA,QAC1F;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,KAAK,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAAA,QACxC;AAAA,MACF,EAAE,KAAK,IAAI;AACX,YAAM,cAAc,QAAQ,SAAS;AAErC,YAAM,KAAK;AAAA,QACT;AAAA,QACA,OAAO,KAAK;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa,KAAK;AAAA,QAClB,MAAM,KAAK;AAAA,QACX,MAAM,KAAK;AAAA,QACX,OAAO,KAAK;AAAA,MACd,CAAC;AAED,qBAAO,KAAK,2CAA2C,KAAK,KAAK,KAAK,cAAc,QAAQ,CAAC,CAAC,IAAI;AAAA,IACpG;AAEA,mBAAO,KAAK,gCAAgC,MAAM,MAAM,eAAe;AACvE,WAAO;AAAA,EACT,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AACF;;;AC/PO,IAAK,WAAL,kBAAKC,cAAL;AACL,EAAAA,UAAA,YAAS;AACT,EAAAA,UAAA,aAAU;AACV,EAAAA,UAAA,eAAY;AACZ,EAAAA,UAAA,cAAW;AACX,EAAAA,UAAA,OAAI;AALM,SAAAA;AAAA,GAAA;AAiXL,IAAM,uBAA+C;AAAA,EAC1D,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AACX;AAMO,SAAS,eAAe,UAA4B;AACzD,SAAO,aAAa,cAAa,YAAY;AAC/C;AAUO,SAAS,iBAAiB,cAAgC;AAC/D,QAAM,aAAa,wBAAwB,YAAY;AAEvD,MAAI,eAAe,WAAW;AAC5B,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,OAAO,OAAO,QAAQ;AAC7C,MAAI,eAAe,SAAS,UAAU,GAAG;AACvC,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,MAAM,uCAAuC,YAAY,EAAE;AACvE;AAWO,SAAS,wBAAwB,KAAqB;AAC3D,QAAM,QAAQ,IAAI,YAAY,EAAE,KAAK;AACrC,MAAI,UAAU,OAAO,UAAU,iBAAiB,UAAU,aAAa;AACrE,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;ACxZA,IAAMC,iBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBtB,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC/B,iBAAiC,CAAC;AAAA,EAE1C,YAAY,OAAgB;AAC1B,UAAM,oBAAoBA,gBAAe,QAAW,KAAK;AAAA,EAC3D;AAAA,EAEU,gBAA6D;AACrE,UAAMC,UAAS,UAAU;AACzB,QAAI,CAACA,QAAO,YAAa,QAAO;AAChC,WAAO;AAAA,MACL,KAAK;AAAA,QACH,MAAM;AAAA,QACN,KAAK,GAAGA,QAAO,WAAW,cAAcA,QAAO,WAAW;AAAA,QAC1D,SAAS,CAAC;AAAA,QACV,OAAO,CAAC,GAAG;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA,EAEU,WAA8B;AACtC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QACF,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,OAAO;AAAA,cACL,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,YAAY;AAAA,kBACV,UAAU,EAAE,MAAM,SAAS;AAAA,kBAC3B,SAAS,EAAE,MAAM,SAAS;AAAA,kBAC1B,UAAU,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,SAAS,EAAE;AAAA,kBACrD,OAAO,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,SAAS,EAAE;AAAA,kBAClD,gBAAgB,EAAE,MAAM,SAAS;AAAA,gBACnC;AAAA,gBACA,UAAU,CAAC,YAAY,WAAW,YAAY,SAAS,gBAAgB;AAAA,cACzE;AAAA,cACA,aAAa;AAAA,YACf;AAAA,UACF;AAAA,UACA,UAAU,CAAC,OAAO;AAAA,QACpB;AAAA,QACA,SAAS,OAAO,SAAkB;AAChC,gBAAM,EAAE,MAAM,IAAI;AAClB,eAAK,iBAAiB;AACtB,yBAAO,KAAK,4CAA4C,MAAM,MAAM,QAAQ;AAC5E,iBAAO,KAAK,UAAU,EAAE,SAAS,MAAM,OAAO,MAAM,OAAO,CAAC;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,eACd,UACA,MACkB;AAGlB,mBAAO,KAAK,qDAAqD,QAAQ,GAAG;AAC5E,WAAO,EAAE,OAAO,iBAAiB,QAAQ,GAAG;AAAA,EAC9C;AAAA,EAEA,oBAAoC;AAClC,WAAO,KAAK;AAAA,EACd;AACF;AAIA,SAAS,eAAe,KAAuB;AAC7C,QAAM,aAAa,IAAI,YAAY,EAAE,KAAK;AAC1C,UAAQ,YAAY;AAAA,IAClB,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AACH;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH;AAAA,IACF;AACE,aAAO;AAAA,EACX;AACF;AASA,SAAS,eAAe,MAAoB,MAA8B;AACxE,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,WAAW,eAAe,KAAK,QAAQ;AAC7C,QAAM,QAAkB,CAAC,KAAK;AAE9B,QAAM,KAAK,aAAa,QAAQ,EAAE;AAClC,QAAM,KAAK,eAAe;AAC1B,QAAM,KAAK,qBAAqB;AAEhC,MAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,UAAM,KAAK,WAAW;AACtB,eAAW,OAAO,KAAK,UAAU;AAC/B,YAAM,KAAK,QAAQ,GAAG,GAAG;AAAA,IAC3B;AAAA,EACF,OAAO;AACL,UAAM,KAAK,cAAc;AAAA,EAC3B;AAEA,MAAI,KAAK,MAAM,SAAS,GAAG;AACzB,UAAM,KAAK,QAAQ;AACnB,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,KAAK,aAAa,IAAI,GAAG;AAC/B,YAAM,KAAK,iBAAiB;AAAA,IAC9B;AAAA,EACF,OAAO;AACL,UAAM,KAAK,WAAW;AAAA,EACxB;AAEA,QAAM,KAAK,mBAAmB,KAAK,cAAc,EAAE;AACnD,QAAM,KAAK,eAAe,KAAK,SAAS,GAAG;AAC3C,QAAM,KAAK,cAAc,KAAK,YAAY,IAAI,KAAK,SAAS,MAAM,MAAM,EAAE;AAC1E,QAAM,KAAK,eAAe,GAAG,GAAG;AAChC,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,KAAK,OAAO;AACvB,QAAM,KAAK,EAAE;AAEb,SAAO,MAAM,KAAK,IAAI;AACxB;AAIA,eAAsB,mBACpB,OACA,OACA,YACA,OACuB;AACvB,QAAM,QAAQ,IAAI,iBAAiB,KAAK;AAExC,MAAI;AAEF,UAAM,eAAe,WAAW,SAC7B;AAAA,MAAO,CAAC,QACP,MAAM,SAAS,KAAK,CAAC,OAAO,IAAI,QAAQ,GAAG,OAAO,IAAI,MAAM,GAAG,KAAK;AAAA,IACtE,EACC,IAAI,CAAC,QAAQ,IAAI,IAAI,EACrB,KAAK,GAAG;AAEX,UAAM,cAAc;AAAA,MAClB;AAAA,MACA,gBAAgB,MAAM,KAAK;AAAA,MAC3B,sBAAsB,MAAM,WAAW;AAAA,MACvC,mBAAmB,MAAM,cAAc,QAAQ,CAAC,CAAC;AAAA,MACjD,eAAe,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACpC;AAAA,MACA;AAAA,MACA,aAAa,MAAM,GAAG,GAAI;AAAA,IAC5B,EAAE,KAAK,IAAI;AAEX,UAAM,MAAM,IAAI,WAAW;AAE3B,UAAM,iBAAiB,MAAM,kBAAkB;AAG/C,UAAM,YAAY,KAAK,QAAQ,MAAM,QAAQ,GAAG,QAAQ;AACxD,UAAM,WAAW,KAAK,WAAW,MAAM,MAAM,OAAO;AACpD,wBAAoB,QAAQ;AAE5B,UAAM,cAA4B,eAAe,IAAI,CAAC,MAAM;AAC1D,YAAM,WAAW,eAAe,EAAE,QAAQ;AAC1C,YAAM,aAAa,KAAK,UAAU,GAAG,QAAQ,KAAK;AAElD;AAAA,QACE;AAAA,QACA,eAAe,GAAG,EAAE,WAAW,MAAM,MAAM,WAAW,MAAM,KAAK,CAAC;AAAA,MACpE;AACA,qBAAO,KAAK,uCAAuC,UAAU,EAAE;AAE/D,aAAO;AAAA,QACL;AAAA,QACA,SAAS,EAAE;AAAA,QACX,UAAU,EAAE;AAAA,QACZ,OAAO,EAAE;AAAA,QACT,gBAAgB,EAAE;AAAA,QAClB;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AACF;AAEA,eAAsB,oBACpB,OACA,YACA,SACA,WACA,OACuB;AACvB,QAAM,QAAQ,IAAI,iBAAiB,KAAK;AAExC,MAAI;AAEF,UAAM,cAAc;AAAA,MAClB;AAAA,MACA,gBAAgB,QAAQ,KAAK;AAAA,MAC7B,eAAe,MAAM,IAAI;AAAA,MACzB,mBAAmB,MAAM,QAAQ;AAAA,MACjC;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAAA,MAChD;AAAA,MACA;AAAA,MACA,WAAW,KAAK,MAAM,GAAG,GAAI;AAAA,IAC/B,EAAE,KAAK,IAAI;AAEX,UAAM,MAAM,IAAI,WAAW;AAE3B,UAAM,iBAAiB,MAAM,kBAAkB;AAG/C,UAAM,SAAS,aAAa,KAAK,MAAM,UAAU,cAAc;AAC/D,wBAAoB,MAAM;AAE1B,UAAM,cAA4B,eAAe,IAAI,CAAC,MAAM;AAC1D,YAAM,WAAW,eAAe,EAAE,QAAQ;AAC1C,YAAM,aAAa,KAAK,QAAQ,GAAG,QAAQ,KAAK;AAEhD;AAAA,QACE;AAAA,QACA,eAAe,GAAG,EAAE,WAAW,MAAM,KAAK,CAAC;AAAA,MAC7C;AACA,qBAAO,KAAK,4BAA4B,UAAU,EAAE;AAEpD,aAAO;AAAA,QACL;AAAA,QACA,SAAS,EAAE;AAAA,QACX,UAAU,EAAE;AAAA,QACZ,OAAO,EAAE;AAAA,QACT,gBAAgB,EAAE;AAAA,QAClB;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AACF;;;AC3SA,SAASC,qBAA4B;AACnC,QAAM,QAAQ,eAAe;AAE7B,SAAO,+EAA+E,MAAM,IAAI,KAAK,MAAM,MAAM;AAAA;AAAA;AAAA,UAGzG,MAAM,MAAM,IAAI;AAAA,iBACT,MAAM,MAAM,WAAW;AAAA,WAC7B,MAAM,MAAM,KAAK;AAAA;AAAA,sBAEN,MAAM,kBAAkB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBvD;AAIA,IAAM,YAAN,cAAwB,UAAU;AAAA,EACxB,cAAoC;AAAA,EAE5C,YAAY,OAAgB;AAC1B,UAAM,aAAaA,mBAAkB,GAAG,QAAW,KAAK;AAAA,EAC1D;AAAA,EAEU,gBAA6D;AACrE,UAAMC,UAAS,UAAU;AACzB,QAAI,CAACA,QAAO,YAAa,QAAO;AAChC,WAAO;AAAA,MACL,KAAK;AAAA,QACH,MAAM;AAAA,QACN,KAAK,GAAGA,QAAO,WAAW,cAAcA,QAAO,WAAW;AAAA,QAC1D,SAAS,CAAC;AAAA,QACV,OAAO,CAAC,GAAG;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA,EAEU,WAA8B;AACtC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QACF,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,aAAa;AAAA,cACX,MAAM;AAAA,cACN,YAAY;AAAA,gBACV,OAAO,EAAE,MAAM,SAAS;AAAA,gBACxB,aAAa,EAAE,MAAM,SAAS;AAAA,gBAC9B,MAAM,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,SAAS,EAAE;AAAA,gBACjD,aAAa,EAAE,MAAM,SAAS;AAAA,cAChC;AAAA,cACA,UAAU,CAAC,SAAS,eAAe,MAAM;AAAA,YAC3C;AAAA,YACA,MAAM;AAAA,cACJ,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,UACF;AAAA,UACA,UAAU,CAAC,eAAe,MAAM;AAAA,QAClC;AAAA,QACA,SAAS,OAAO,SAAkB;AAChC,gBAAM,WAAW;AACjB,eAAK,cAAc;AACnB,yBAAO,KAAK,0CAA0C,SAAS,YAAY,KAAK,GAAG;AACnF,iBAAO,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,eACd,UACA,OACkB;AAClB,mBAAO,KAAK,8CAA8C,QAAQ,GAAG;AACrE,WAAO,EAAE,OAAO,iBAAiB,QAAQ,GAAG;AAAA,EAC9C;AAAA,EAEA,iBAAuC;AACrC,WAAO,KAAK;AAAA,EACd;AACF;AAIA,SAAS,mBAAmB,MAA6B;AACvD,QAAM,KAAK,KAAK;AAChB,QAAM,OAAO,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,cAAc,EAAE,CAAC,EAAE,KAAK,IAAI;AAEpF,QAAM,QAAkB;AAAA,IACtB;AAAA,IACA,WAAW,GAAG,KAAK;AAAA,IACnB;AAAA,IACA,iBAAiB,GAAG,WAAW;AAAA,IAC/B,SAAS,IAAI;AAAA,IACb,gBAAgB,GAAG,eAAe,EAAE;AAAA,IACpC;AAAA,IACA;AAAA,IACA,KAAK;AAAA,EACP;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAIA,eAAsB,iBACpB,OACA,YACA,SACA,OACiB;AACjB,QAAM,QAAQ,IAAI,UAAU,KAAK;AAEjC,MAAI;AACF,UAAM,cAAc;AAAA,MAClB;AAAA,MACA,gBAAgB,QAAQ,KAAK;AAAA,MAC7B,eAAe,MAAM,IAAI;AAAA,MACzB,mBAAmB,MAAM,QAAQ;AAAA,MACjC,mBAAmB,MAAM,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,MAC9D;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAAA,MAChD;AAAA,MACA;AAAA,MACA,WAAW,KAAK,MAAM,GAAG,GAAI;AAAA,IAC/B,EAAE,KAAK,IAAI;AAEX,UAAM,MAAM,IAAI,WAAW;AAE3B,UAAM,cAAc,MAAM,eAAe;AACzC,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AAEA,UAAM,SAAS,KAAK,MAAM,UAAU,cAAc;AAClD,wBAAoB,MAAM;AAE1B,UAAM,aAAa,KAAK,QAAQ,UAAU;AAC1C,sBAAkB,YAAY,mBAAmB,WAAW,CAAC;AAC7D,mBAAO,KAAK,kCAAkC,UAAU,EAAE;AAE1D,WAAO;AAAA,EACT,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AACF;;;ACrLA,SAASC,oBAAmB,SAAyB;AACnD,QAAM,IAAI,KAAK,MAAM,UAAU,IAAI;AACnC,QAAM,IAAI,KAAK,MAAO,UAAU,OAAQ,EAAE;AAC1C,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,SAAO,IAAI,IACP,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,KAChE,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACxC;AAGA,SAASC,SAAQ,SAAyB;AACxC,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,QAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,SAAO,GAAG,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACpE;AAGA,SAASC,sBAAqB,YAAgC;AAC5D,SAAO,WAAW,SACf,IAAI,CAAC,QAAQ,IAAID,SAAQ,IAAI,KAAK,CAAC,WAAMA,SAAQ,IAAI,GAAG,CAAC,KAAK,IAAI,KAAK,KAAK,CAAC,EAAE,EAC/E,KAAK,IAAI;AACd;AAIA,SAAS,qBAAqB,UAA6B;AACzD,SAAO,KAAK,UAAU,EAAE,SAAS,GAAG,MAAM,CAAC;AAC7C;AAEA,SAAS,0BAA0B,UAA6B;AAC9D,SAAO,SACJ,IAAI,CAAC,OAAO,GAAGD,oBAAmB,GAAG,SAAS,CAAC,IAAI,GAAG,KAAK,EAAE,EAC7D,KAAK,IAAI;AACd;AAEA,SAAS,yBAAyB,UAA6B;AAC7D,QAAM,OAAO,SACV,IAAI,CAAC,OAAO,KAAKA,oBAAmB,GAAG,SAAS,CAAC,MAAM,GAAG,KAAK,MAAM,GAAG,WAAW,IAAI,EACvF,KAAK,IAAI;AAEZ,SAAO;AAAA;AAAA;AAAA;AAAA,EAIP,IAAI;AAAA;AAEN;AAEA,SAAS,mBAAmB,UAAqB,eAA+B;AAC9E,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,KAAK,SAAS,CAAC;AACrB,UAAM,UAAU,KAAK,MAAM,GAAG,YAAY,GAAI;AAC9C,UAAM,QAAQ,IAAI,SAAS,SAAS,IAChC,KAAK,MAAM,SAAS,IAAI,CAAC,EAAE,YAAY,GAAI,IAC3C,KAAK,MAAM,gBAAgB,GAAI;AACnC,UAAM,eAAe,GAAG,MAAM,QAAQ,YAAY,MAAM;AACxD,YAAQ;AAAA;AAAA,QAAqC,OAAO;AAAA,MAAS,KAAK;AAAA,QAAW,YAAY;AAAA;AAAA;AAAA,EAC3F;AACA,SAAO;AACT;AAIA,SAAS,2BAAmC;AAC1C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBT;AAUA,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC3B;AAAA,EACA;AAAA,EAER,YAAY,WAAmB,eAAuB,OAAgB;AACpE,UAAM,gBAAgB,yBAAyB,GAAG,QAAW,KAAK;AAClE,SAAK,YAAY;AACjB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,IAAY,cAAsB;AAChC,WAAO,KAAK,KAAK,WAAW,UAAU;AAAA,EACxC;AAAA,EAEU,WAA8B;AACtC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QAEF,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,UAAU;AAAA,cACR,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,YAAY;AAAA,kBACV,WAAW,EAAE,MAAM,UAAU,aAAa,2BAA2B;AAAA,kBACrE,OAAO,EAAE,MAAM,UAAU,aAAa,kCAAkC;AAAA,kBACxE,aAAa,EAAE,MAAM,UAAU,aAAa,qBAAqB;AAAA,gBACnE;AAAA,gBACA,UAAU,CAAC,aAAa,SAAS,aAAa;AAAA,cAChD;AAAA,YACF;AAAA,UACF;AAAA,UACA,UAAU,CAAC,UAAU;AAAA,QACvB;AAAA,QACA,SAAS,OAAO,YAAqB;AACnC,gBAAM,OAAO;AACb,iBAAO,KAAK,uBAAuB,IAAI;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,eACd,UACA,MACkB;AAClB,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO,KAAK,uBAAuB,IAAuC;AAAA,MAC5E;AACE,cAAM,IAAI,MAAM,iBAAiB,QAAQ,EAAE;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,MAAc,uBAAuB,MAA6C;AAChF,UAAM,EAAE,SAAS,IAAI;AACrB,UAAM,gBAAgB,KAAK,WAAW;AAGtC,UAAM,QAAQ,IAAI;AAAA,MAChB;AAAA,QACE,KAAK,KAAK,aAAa,eAAe;AAAA,QACtC,qBAAqB,QAAQ;AAAA,MAC/B;AAAA,MACA;AAAA,QACE,KAAK,KAAK,aAAa,sBAAsB;AAAA,QAC7C,0BAA0B,QAAQ;AAAA,MACpC;AAAA,MACA;AAAA,QACE,KAAK,KAAK,aAAa,aAAa;AAAA,QACpC,yBAAyB,QAAQ;AAAA,MACnC;AAAA,MACA;AAAA,QACE,KAAK,KAAK,aAAa,qBAAqB;AAAA,QAC5C,mBAAmB,UAAU,KAAK,aAAa;AAAA,MACjD;AAAA,IACF,CAAC;AAED,mBAAO,KAAK,wBAAwB,SAAS,MAAM,iCAA4B,KAAK,WAAW,EAAE;AACjG,WAAO,qBAAqB,SAAS,MAAM,6BAA6B,KAAK,WAAW;AAAA,EAC1F;AACF;AAYA,eAAsB,iBACpB,OACA,YACA,OACoB;AACpB,QAAMG,UAAS,UAAU;AACzB,QAAM,YAAY,KAAKA,QAAO,YAAY,MAAM,IAAI;AAEpD,QAAM,QAAQ,IAAI,aAAa,WAAW,MAAM,UAAU,KAAK;AAC/D,QAAM,kBAAkBD,sBAAqB,UAAU;AAEvD,QAAM,aAAa;AAAA,IACjB,cAAc,MAAM,QAAQ;AAAA,IAC5B,iBAAiBD,SAAQ,MAAM,QAAQ,CAAC,KAAK,KAAK,MAAM,MAAM,QAAQ,CAAC;AAAA,IACvE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,MAAI;AAIJ,QAAM,cAAe,MAAc,uBAAuB,KAAK,KAAK;AAGnE,EAAC,MAAc,yBAAyB,OAAO,SAA+B;AAC7E,uBAAmB,KAAK;AACxB,WAAO,YAAY,IAAI;AAAA,EACzB;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,UAAU;AAE1B,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,EACT,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AACF;;;ACrPA,eAAsB,cAAc,WAAmB,SAAiC;AACtF,QAAM,EAAE,UAAU,IAAI,UAAU;AAChC,QAAM,gBAAgB,WAAW,yBAAyB,SAAS;AAEnE,MAAI;AACF,mBAAO,KAAK,0BAA0B,SAAS,EAAE;AACjD,oBAAgB,cAAc,EAAE,KAAK,WAAW,OAAO,OAAO,CAAC;AAE/D,mBAAO,KAAK,eAAe,aAAa,EAAE;AAC1C,oBAAgB,kBAAkB,aAAa,KAAK,EAAE,KAAK,WAAW,OAAO,OAAO,CAAC;AAErF,UAAM,SAAS,gBAAgB,mCAAmC,EAAE,KAAK,WAAW,OAAO,OAAO,CAAC;AACnG,mBAAO,KAAK,qBAAqB,MAAM,EAAE;AACzC,oBAAgB,mBAAmB,MAAM,IAAI,EAAE,KAAK,WAAW,OAAO,OAAO,CAAC;AAE9E,mBAAO,KAAK,4CAA4C;AAAA,EAC1D,SAAS,OAAgB;AACvB,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,QAAI,IAAI,SAAS,mBAAmB,GAAG;AACrC,qBAAO,KAAK,uCAAuC;AACnD;AAAA,IACF;AACA,mBAAO,MAAM,yBAAyB,GAAG,EAAE;AAC3C,UAAM;AAAA,EACR;AACF;;;ACaA,IAAM,iBAAyE;AAAA,EAC7E,wBAAiB,GAAG;AAAA,IAClB,OAAgB,EAAE,UAAU,MAAM,YAAY,KAAK;AAAA,IACnD,OAAgB,EAAE,UAAU,MAAM,YAAY,iBAAiB;AAAA,IAC/D,eAAgB,EAAE,UAAU,MAAM,YAAY,KAAK;AAAA,EACrD;AAAA,EACA,0BAAkB,GAAG;AAAA,IACnB,eAAgB,EAAE,UAAU,MAAM,YAAY,KAAK;AAAA,EACrD;AAAA,EACA,sBAAgB,GAAG;AAAA,IACjB,OAAgB,EAAE,UAAU,MAAM,YAAY,SAAS;AAAA,EACzD;AAAA,EACA,4BAAmB,GAAG;AAAA,IACpB,OAAgB,EAAE,UAAU,MAAM,YAAY,kBAAkB;AAAA,IAChE,eAAgB,EAAE,UAAU,MAAM,YAAY,KAAK;AAAA,EACrD;AAAA,EACA,YAAW,GAAG;AAAA,IACZ,OAAgB,EAAE,UAAU,MAAM,YAAY,KAAK;AAAA,EACrD;AACF;AAUO,SAAS,aAAa,UAAoB,UAAsC;AACrF,SAAO,eAAe,QAAQ,IAAI,QAAQ,KAAK;AACjD;AAKO,SAAS,qBAAqB,UAAoB,UAA6B;AACpF,SAAO,aAAa,UAAU,QAAQ,MAAM;AAC9C;;;AC1CA,SAAS,cAAsB;AAC7B,QAAM,EAAE,WAAW,IAAI,UAAU;AACjC,SAAO,KAAK,YAAY,eAAe;AACzC;AAEA,SAAS,kBAA0B;AACjC,QAAM,EAAE,WAAW,IAAI,UAAU;AACjC,SAAO,KAAK,YAAY,WAAW;AACrC;AAEA,eAAe,cAAc,YAAoB,IAAuC;AACtF,QAAM,eAAe,KAAK,YAAY,eAAe;AACrD,QAAM,WAAW,KAAK,YAAY,SAAS;AAC3C,QAAM,YAAY,KAAK,YAAY,WAAW;AAE9C,MAAI;AAEF,UAAM,cAAc,MAAM,aAAa,YAAY;AACnD,UAAM,WAA8B,KAAK,MAAM,WAAW;AAE1D,QAAI,cAAc;AAClB,QAAI;AACF,oBAAc,MAAM,aAAa,QAAQ;AAAA,IAC3C,QAAQ;AACN,qBAAO,MAAM,wBAAwB,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AAAA,IAC1E;AAEA,QAAI,WAAW;AACf,UAAM,gBAAgB,KAAK,YAAY,WAAW;AAClD,eAAW,MAAM,WAAW,aAAa;AAEzC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,WAAW,gBAAgB;AAAA,MACtC;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,mBAAO,MAAM,6BAA6B,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,KAAK,OAAO,GAAG,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AACpH,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,kBAAwC;AAC5D,QAAM,WAAW,YAAY;AAC7B,QAAM,gBAAgB,QAAQ;AAE9B,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,MAAM,uBAAuB,QAAQ;AACrD,cAAU,QAAQ,OAAO,OAAK,EAAE,YAAY,CAAC,EAAE,IAAI,OAAK,EAAE,IAAI;AAAA,EAChE,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAqB,CAAC;AAC5B,aAAW,QAAQ,SAAS;AAC1B,UAAM,OAAO,MAAM,cAAc,KAAK,UAAU,IAAI,GAAG,IAAI;AAC3D,QAAI,KAAM,OAAM,KAAK,IAAI;AAAA,EAC3B;AAGA,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,QAAI,EAAE,aAAa,EAAE,SAAU,QAAO,EAAE,WAAW,KAAK;AACxD,WAAO,EAAE,SAAS,UAAU,cAAc,EAAE,SAAS,SAAS;AAAA,EAChE,CAAC;AACD,SAAO;AACT;AAEA,eAAsB,QAAQ,IAAuC;AAEnE,MAAI,CAAC,MAAM,CAAC,mBAAmB,KAAK,EAAE,GAAG;AACvC,UAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAAA,EAC5C;AACA,QAAM,aAAa,KAAK,YAAY,GAAG,SAAS,EAAE,CAAC;AACnD,SAAO,cAAc,YAAY,EAAE;AACrC;AAEA,eAAsB,WACpB,IACA,UACA,aACA,iBACoB;AAEpB,MAAI,CAAC,MAAM,CAAC,mBAAmB,KAAK,EAAE,GAAG;AACvC,UAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAAA,EAC5C;AACA,QAAM,aAAa,KAAK,YAAY,GAAG,SAAS,EAAE,CAAC;AACnD,QAAM,gBAAgB,UAAU;AAEhC,QAAM,cAAc,KAAK,YAAY,eAAe,GAAG,QAAQ;AAC/D,QAAM,cAAc,KAAK,YAAY,SAAS,GAAG,WAAW;AAE5D,MAAI,WAAW;AACf,QAAM,YAAY,KAAK,YAAY,WAAW;AAE9C,MAAI,iBAAiB;AACnB,UAAM,SAAS,iBAAiB,SAAS;AACzC,eAAW;AAAA,EACb;AAEA,iBAAO,MAAM,uBAAuB,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AAEvE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,WAAW,YAAY;AAAA,IAClC;AAAA,EACF;AACF;AAEA,eAAsB,WACpB,IACA,SAC2B;AAE3B,MAAI,CAAC,MAAM,CAAC,mBAAmB,KAAK,EAAE,GAAG;AACvC,UAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAAA,EAC5C;AACA,QAAM,WAAW,MAAM,QAAQ,EAAE;AACjC,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI,QAAQ,UAAU;AAEpB,UAAM,YAA+B;AAAA,MACnC,IAAI,OAAO,SAAS,SAAS,EAAE;AAAA,MAC/B,UAAU,OAAO,QAAQ,SAAS,YAAY,SAAS,SAAS,QAAQ;AAAA,MACxE,WAAW,OAAO,QAAQ,SAAS,aAAa,SAAS,SAAS,SAAS;AAAA,MAC3E,aAAa,OAAO,SAAS,SAAS,WAAW;AAAA,MACjD,YAAY,SAAS,SAAS,eAAe,OAAO,OAAO,SAAS,SAAS,UAAU,IAAI;AAAA,MAC3F,UAAU,SAAS,SAAS;AAAA,MAC5B,iBAAiB,SAAS,SAAS,oBAAoB,OAAO,OAAO,SAAS,SAAS,eAAe,IAAI;AAAA,MAC1G,UAAU,MAAM,QAAQ,QAAQ,SAAS,QAAQ,IAAI,QAAQ,SAAS,SAAS,IAAI,MAAM,IAAK,MAAM,QAAQ,SAAS,SAAS,QAAQ,IAAI,SAAS,SAAS,SAAS,IAAI,MAAM,IAAI,CAAC;AAAA,MACpL,OAAO,MAAM,QAAQ,QAAQ,SAAS,KAAK,IAAI,QAAQ,SAAS,QAAS,MAAM,QAAQ,SAAS,SAAS,KAAK,IAAI,SAAS,SAAS,QAAQ,CAAC;AAAA,MAC7I,gBAAgB,QAAQ,SAAS,mBAAmB,SAAY,OAAO,QAAQ,SAAS,cAAc,KAAK,IAAK,OAAO,SAAS,SAAS,cAAc,KAAK;AAAA,MAC5J,mBAAmB,QAAQ,SAAS,sBAAsB,SAAY,OAAO,QAAQ,SAAS,iBAAiB,KAAK,IAAK,OAAO,SAAS,SAAS,iBAAiB,KAAK;AAAA,MACxK,eAAe,QAAQ,SAAS,kBAAkB,SAAa,QAAQ,SAAS,kBAAkB,OAAO,OAAO,QAAQ,SAAS,aAAa,IAAI,OAAS,SAAS,SAAS,kBAAkB,OAAO,OAAO,SAAS,SAAS,aAAa,IAAI;AAAA,MAChP,cAAc,QAAQ,SAAS,iBAAiB,SAAa,QAAQ,SAAS,iBAAiB,OAAO,OAAO,QAAQ,SAAS,YAAY,IAAI,OAAS,SAAS,SAAS,iBAAiB,OAAO,OAAO,SAAS,SAAS,YAAY,IAAI;AAAA,MAC1O,QAAQ,QAAQ,SAAS,UAAU,SAAS,SAAS;AAAA,MACrD,YAAY,QAAQ,SAAS,eAAe,SAAa,QAAQ,SAAS,eAAe,OAAO,OAAO,QAAQ,SAAS,UAAU,IAAI,OAAS,SAAS,SAAS,eAAe,OAAO,OAAO,SAAS,SAAS,UAAU,IAAI;AAAA,MAC9N,cAAc,QAAQ,SAAS,iBAAiB,SAAa,QAAQ,SAAS,iBAAiB,OAAO,OAAO,QAAQ,SAAS,YAAY,IAAI,OAAS,SAAS,SAAS,iBAAiB,OAAO,OAAO,SAAS,SAAS,YAAY,IAAI;AAAA,MAC1O,WAAW,OAAO,SAAS,SAAS,SAAS;AAAA,MAC7C,YAAY,QAAQ,SAAS,eAAe,SAAa,QAAQ,SAAS,eAAe,OAAO,OAAO,QAAQ,SAAS,UAAU,IAAI,OAAS,SAAS,SAAS,eAAe,OAAO,OAAO,SAAS,SAAS,UAAU,IAAI;AAAA,MAC9N,aAAa,QAAQ,SAAS,gBAAgB,SAAa,QAAQ,SAAS,gBAAgB,OAAO,OAAO,QAAQ,SAAS,WAAW,IAAI,OAAS,SAAS,SAAS,gBAAgB,OAAO,OAAO,SAAS,SAAS,WAAW,IAAI;AAAA,MACpO,UAAU,QAAQ,SAAS,YAAY,SAAS,SAAS;AAAA,MACzD,sBAAsB,QAAQ,SAAS,wBAAwB,SAAS,SAAS;AAAA,IACnF;AAEA,aAAS,WAAW;AAEpB,UAAM,oBAAoB,QAAQ,KAAK,SAAS,YAAY,eAAe,CAAC;AAC5E,QAAI,CAAC,kBAAkB,WAAW,QAAQ,YAAY,CAAC,IAAI,GAAG,GAAG;AAC/D,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,UAAM;AAAA,MACJ;AAAA,MACA,KAAK,UAAU,SAAS,UAAU,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI,QAAQ,gBAAgB,QAAW;AAErC,UAAM,mBAAmB,OAAO,QAAQ,WAAW;AACnD,aAAS,cAAc;AAEvB,UAAM,gBAAgB,QAAQ,KAAK,SAAS,YAAY,SAAS,CAAC;AAClE,QAAI,CAAC,cAAc,WAAW,QAAQ,YAAY,CAAC,IAAI,GAAG,GAAG;AAC3D,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,UAAM,cAAc,eAAe,gBAAgB;AAAA,EACrD;AAEA,iBAAO,MAAM,uBAAuB,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AACvE,SAAO;AACT;AAEA,eAAsB,YACpB,IACA,aACe;AAEf,MAAI,CAAC,MAAM,CAAC,mBAAmB,KAAK,EAAE,GAAG;AACvC,UAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAAA,EAC5C;AACA,QAAM,OAAO,MAAM,QAAQ,EAAE;AAC7B,MAAI,CAAC,KAAM;AAEX,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,MAAI,YAAY,WAAW;AACzB,SAAK,SAAS,YAAY,OAAO,YAAY,SAAS;AAAA,EACxD;AACA,OAAK,SAAS,SAAS;AACvB,OAAK,SAAS,aAAa,OAAO,YAAY,UAAU;AACxD,OAAK,SAAS,eAAe,OAAO,YAAY,YAAY;AAC5D,OAAK,SAAS,eAAe,YAAY,eAAe,OAAO,YAAY,YAAY,IAAI;AAC3F,OAAK,SAAS,cAAc;AAC5B,OAAK,SAAS,aAAa;AAG3B,QAAM,oBAAuC;AAAA,IAC3C,IAAI,OAAO,KAAK,SAAS,EAAE;AAAA,IAC3B,UAAU,OAAO,KAAK,SAAS,QAAQ;AAAA,IACvC,WAAW,OAAO,KAAK,SAAS,SAAS;AAAA,IACzC,aAAa,OAAO,KAAK,SAAS,WAAW;AAAA,IAC7C,YAAY,KAAK,SAAS,eAAe,OAAO,OAAO,KAAK,SAAS,UAAU,IAAI;AAAA,IACnF,UAAU,KAAK,SAAS;AAAA,IACxB,iBAAiB,KAAK,SAAS,oBAAoB,OAAO,OAAO,KAAK,SAAS,eAAe,IAAI;AAAA,IAClG,UAAU,MAAM,QAAQ,KAAK,SAAS,QAAQ,IAAI,KAAK,SAAS,SAAS,IAAI,MAAM,IAAI,CAAC;AAAA,IACxF,OAAO,MAAM,QAAQ,KAAK,SAAS,KAAK,IAAI,KAAK,SAAS,QAAQ,CAAC;AAAA,IACnE,gBAAgB,OAAO,KAAK,SAAS,cAAc,KAAK;AAAA,IACxD,mBAAmB,OAAO,KAAK,SAAS,iBAAiB,KAAK;AAAA,IAC9D,eAAe,KAAK,SAAS,kBAAkB,OAAO,OAAO,KAAK,SAAS,aAAa,IAAI;AAAA,IAC5F,cAAc,KAAK,SAAS,iBAAiB,OAAO,OAAO,KAAK,SAAS,YAAY,IAAI;AAAA,IACzF,QAAQ,KAAK,SAAS;AAAA,IACtB,YAAY,KAAK,SAAS,eAAe,OAAO,OAAO,KAAK,SAAS,UAAU,IAAI;AAAA,IACnF,cAAc,KAAK,SAAS,iBAAiB,OAAO,OAAO,KAAK,SAAS,YAAY,IAAI;AAAA,IACzF,WAAW,OAAO,KAAK,SAAS,SAAS;AAAA,IACzC,YAAY,KAAK,SAAS,eAAe,OAAO,OAAO,KAAK,SAAS,UAAU,IAAI;AAAA,IACnF,aAAa,KAAK,SAAS,gBAAgB,OAAO,OAAO,KAAK,SAAS,WAAW,IAAI;AAAA,IACtF,UAAU,KAAK,SAAS;AAAA,IACxB,sBAAsB,KAAK,SAAS;AAAA,EACtC;AAGA,QAAM,sBAAsB,QAAQ,KAAK,KAAK,YAAY,eAAe,CAAC;AAC1E,MAAI,CAAC,oBAAoB,WAAW,QAAQ,YAAY,CAAC,IAAI,GAAG,GAAG;AACjE,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AAEA,QAAM;AAAA,IACJ;AAAA,IACA,KAAK,UAAU,mBAAmB,MAAM,CAAC;AAAA,EAC3C;AAEA,QAAM,eAAe,gBAAgB;AACrC,QAAM,gBAAgB,YAAY;AAGlC,QAAM,WAAW,KAAK,cAAc,SAAS,EAAE,CAAC;AAChD,QAAM,eAAe,QAAQ,QAAQ;AACrC,QAAM,uBAAuB,QAAQ,YAAY;AACjD,MAAI,CAAC,aAAa,WAAW,uBAAuB,GAAG,KAAK,iBAAiB,sBAAsB;AACjG,UAAM,IAAI,MAAM,qCAAqC,EAAE,EAAE;AAAA,EAC3D;AAEA,MAAI;AACF,UAAM,WAAW,KAAK,YAAY,QAAQ;AAAA,EAC5C,SAAS,WAAoB;AAG3B,UAAM,UAAW,WAA4C;AAC7D,QAAI,YAAY,SAAS;AACvB,qBAAO,KAAK,6BAA6B,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,+BAA+B;AACzG,YAAM,cAAc,KAAK,YAAY,QAAQ;AAC7C,YAAM,gBAAgB,KAAK,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACzE,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AAEA,iBAAO,MAAM,kCAAkC,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AACpF;AAEA,eAAsB,WAAW,IAA2B;AAE1D,MAAI,CAAC,MAAM,CAAC,mBAAmB,KAAK,EAAE,GAAG;AACvC,UAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAAA,EAC5C;AACA,QAAM,aAAa,KAAK,YAAY,GAAG,SAAS,EAAE,CAAC;AACnD,MAAI;AACF,UAAM,gBAAgB,YAAY,EAAE,WAAW,KAAK,CAAC;AACrD,mBAAO,MAAM,oCAAoC,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AAAA,EACtF,SAAS,KAAK;AACZ,mBAAO,MAAM,+BAA+B,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,KAAK,OAAO,GAAG,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AAAA,EACxH;AACF;AAEA,eAAsB,oBAA0C;AAC9D,QAAM,eAAe,gBAAgB;AACrC,QAAM,gBAAgB,YAAY;AAElC,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,MAAM,uBAAuB,YAAY;AACzD,cAAU,QAAQ,OAAO,OAAK,EAAE,YAAY,CAAC,EAAE,IAAI,OAAK,EAAE,IAAI;AAAA,EAChE,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAqB,CAAC;AAC5B,aAAW,QAAQ,SAAS;AAC1B,UAAM,OAAO,MAAM,cAAc,KAAK,cAAc,IAAI,GAAG,IAAI;AAC/D,QAAI,KAAM,OAAM,KAAK,IAAI;AAAA,EAC3B;AAEA,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,UAAU,cAAc,EAAE,SAAS,SAAS,CAAC;AAC7E,SAAO;AACT;AAEA,eAAsB,WAAW,IAAqD;AAEpF,MAAI,CAAC,MAAM,CAAC,mBAAmB,KAAK,EAAE,GAAG;AACvC,UAAM,IAAI,MAAM,sBAAsB,EAAE,EAAE;AAAA,EAC5C;AACA,MAAI,MAAM,WAAW,KAAK,YAAY,GAAG,SAAS,EAAE,CAAC,CAAC,GAAG;AACvD,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,WAAW,KAAK,gBAAgB,GAAG,SAAS,EAAE,CAAC,CAAC,GAAG;AAC3D,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;ACxUA,SAAS,kBAAkB,MAAiB,UAAmC;AAC7E,QAAM,OAAO,aAAa,UAAU,OAAO;AAC3C,MAAI,CAAC,KAAM,QAAO;AAGlB,MAAI,KAAK,cAAc,KAAK,UAAU,QAAQ;AAC5C,UAAM,QAAQ,KAAK,SAAS,KAAK,OAAK,EAAE,aAAa,KAAK,UAAU;AACpE,QAAI,MAAO,QAAO,MAAM;AAGxB,QAAI,0CAAiC;AACnC,YAAM,WAAW,KAAK,SAAS,KAAK,OAAK,EAAE,aAAa,gBAAgB;AACxE,UAAI,SAAU,QAAO,SAAS;AAAA,IAChC;AAAA,EACF;AAGA,SAAO,KAAK,WACP,KAAK,iBAAiB,KAAK,aAC5B,KAAK;AACX;AAKA,SAAS,mBAAmB,MAAkB,UAAmC;AAC/E,QAAM,OAAO,aAAa,UAAU,aAAa;AACjD,MAAI,CAAC,KAAM,QAAO;AAElB,SAAO,KAAK,WACP,KAAK,iBAAiB,KAAK,aAC5B,KAAK;AACX;AAKA,SAAS,kBACP,OACA,UACA,oBACe;AACf,QAAM,OAAO,aAAa,UAAU,OAAO;AAC3C,MAAI,CAAC,KAAM,QAAO;AAElB,SAAO,KAAK,WACP,sBAAsB,KAAK,MAAM,UAAU,MAAM,QAAQ,IAC1D,KAAK,MAAM,UAAU,MAAM,QAAQ;AACzC;AAWA,eAAe,qBAAqB,UAAmD;AACrF,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,aAAa,QAAQ;AAAA,EACvC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,SAAiC,CAAC;AACxC,QAAM,QAAQ,QAAQ,MAAM,6BAA6B;AACzD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,YAAY,MAAM,CAAC;AACzB,aAAW,QAAQ,UAAU,MAAM,OAAO,GAAG;AAC3C,UAAM,UAAU,KAAK,MAAM,iBAAiB;AAC5C,QAAI,CAAC,QAAS;AAEd,UAAM,MAAM,QAAQ,CAAC;AACrB,QAAI,QAAQ,QAAQ,CAAC,EAAE,KAAK;AAG5B,QAAK,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAAO,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAAI;AACpG,cAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,IAC3B;AAGA,QAAI,UAAU,OAAQ;AAEtB,WAAO,GAAG,IAAI;AAAA,EAChB;AAEA,SAAO;AACT;AAOA,SAAS,iBAAiB,SAAyB;AACjD,SAAO,QAAQ,QAAQ,mCAAmC,EAAE,EAAE,KAAK;AACrE;AAMA,eAAsB,kBACpB,OACA,QACA,aACA,aACA,oBAC2B;AAC3B,QAAM,SAA2B,EAAE,cAAc,GAAG,cAAc,GAAG,QAAQ,CAAC,EAAE;AAEhF,aAAW,QAAQ,aAAa;AAC9B,QAAI;AACF,YAAM,eAAe,eAAe,KAAK,QAAQ;AACjD,YAAM,cAAc,MAAM,qBAAqB,KAAK,UAAU;AAE9D,UAAI;AACJ,UAAI;AACJ,UAAI,YAA2B;AAC/B,UAAI,aAA4B;AAEhC,UAAI,YAAY,WAAW;AAEzB,cAAM,QAAQ,OAAO,KAAK,OAAK,EAAE,SAAS,YAAY,SAAS;AAC/D,cAAM,SAAS,YAAY,KAAK,OAAK,EAAE,SAAS,YAAY,SAAS;AAErE,YAAI,OAAO;AACT,qBAAW,MAAM;AACjB,qBAAW;AACX,uBAAa,QAAQ,MAAM,UAAU;AACrC,sBAAY,kBAAkB,OAAO,KAAK,QAAQ;AAAA,QACpD,WAAW,QAAQ;AACjB,qBAAW,OAAO;AAClB,qBAAW;AACX,uBAAa,QAAQ,OAAO,UAAU;AACtC,sBAAY,mBAAmB,QAAQ,KAAK,QAAQ;AAAA,QACtD,OAAO;AACL,qBAAW,YAAY;AACvB,qBAAW;AACX,yBAAO,KAAK,4BAA4B,YAAY,SAAS,EAAE;AAAA,QACjE;AAAA,MACF,OAAO;AAEL,mBAAW,MAAM;AACjB,mBAAW;AACX,oBAAY,kBAAkB,OAAO,KAAK,UAAU,kBAAkB;AAAA,MACxE;AAGA,UAAI,CAAC,qBAAqB,KAAK,UAAU,QAAQ,GAAG;AAClD,uBAAO,MAAM,YAAY,KAAK,QAAQ,IAAI,QAAQ,+BAA0B;AAC5E,eAAO;AACP;AAAA,MACF;AAEA,YAAM,SAAS,GAAG,QAAQ,IAAI,YAAY;AAG1C,YAAM,SAAS,MAAM,WAAW,MAAM;AACtC,UAAI,WAAW,aAAa;AAC1B,eAAO;AACP;AAAA,MACF;AAEA,YAAM,WAA8B;AAAA,QAClC,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,WAAW;AAAA,QACX,aAAa,MAAM;AAAA,QACnB;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,QACjB,UAAU,KAAK;AAAA,QACf,OAAO,KAAK,MAAM,IAAI,OAAK,OAAO,MAAM,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC;AAAA,QACjE,gBAAgB,KAAK;AAAA,QACrB,mBAAmB,qBAAqB,YAAY,KAAK;AAAA,QACzD,eAAe;AAAA,QACf,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,YAAY;AAAA,QACZ,aAAa;AAAA,MACf;AAGA,YAAM,WAAW,iBAAiB,KAAK,OAAO;AAC9C,YAAM,cAAc,SAAS,KAAK,EAAE,SAAS,IAAI,WAAW,KAAK;AAGjE,UAAI,YAAY,KAAK,EAAE,WAAW,GAAG;AACnC,cAAM,IAAI,MAAM,mDAAmD;AAAA,MACrE;AAEA,YAAM,WAAW,QAAQ,UAAU,aAAa,aAAa,MAAS;AACtE,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,aAAO,OAAO,KAAK,GAAG,KAAK,QAAQ,KAAK,GAAG,EAAE;AAC7C,qBAAO,MAAM,2BAA2B,KAAK,QAAQ,KAAK,GAAG,EAAE;AAAA,IACjE;AAAA,EACF;AAEA,iBAAO;AAAA,IACL,kBAAkB,OAAO,YAAY,aAAa,OAAO,YAAY,aAAa,OAAO,OAAO,MAAM;AAAA,EACxG;AACA,SAAO;AACT;;;ACrOA,eAAsB,cACpB,WACA,cAAsB,GACtB,iBAAyB,SACC;AAC1B,iBAAO,KAAK,yBAAyB,SAAS,SAAS,WAAW,gBAAgB,cAAc,GAAG;AAEnG,SAAO,IAAI,QAAyB,CAACG,UAAS,WAAW;AACvD,UAAM,UAA2B,CAAC;AAClC,QAAI,SAAS;AAEb,iBAAa,SAAS,EACnB,aAAa,uBAAuB,cAAc,MAAM,WAAW,EAAE,EACrE,OAAO,MAAM,EACb,OAAO,GAAG,EACV,GAAG,UAAU,CAAC,SAAiB;AAC9B,gBAAU,OAAO;AAAA,IACnB,CAAC,EACA,GAAG,OAAO,MAAM;AACf,UAAI,eAA8B;AAElC,iBAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,cAAM,aAAa,KAAK,MAAM,2BAA2B;AACzD,YAAI,YAAY;AACd,yBAAe,WAAW,WAAW,CAAC,CAAC;AAAA,QACzC;AAEA,cAAM,WAAW,KAAK,MAAM,6DAA6D;AACzF,YAAI,UAAU;AACZ,gBAAM,MAAM,WAAW,SAAS,CAAC,CAAC;AAClC,gBAAM,WAAW,WAAW,SAAS,CAAC,CAAC;AAEvC,gBAAM,QAAQ,gBAAgB,KAAK,IAAI,GAAG,MAAM,QAAQ;AAExD,kBAAQ,KAAK,EAAE,OAAO,KAAK,SAAS,CAAC;AACrC,yBAAe;AAAA,QACjB;AAAA,MACF;AAEA,YAAM,aAAa,QAAQ,OAAO,OAAK,EAAE,OAAO,EAAE,KAAK;AACvD,UAAI,WAAW,SAAS,GAAG;AACzB,uBAAO,KAAK,yBAAyB,WAAW,MAAM,sDAAiD;AAAA,MACzG;AACA,YAAM,eAAe,QAAQ,OAAO,OAAK,EAAE,MAAM,EAAE,KAAK;AAExD,UAAI,aAAa,SAAS,GAAG;AAC3B,uBAAO,KAAK,2BAA2B,aAAa,MAAM,GAAG,CAAC,EAAE,IAAI,OAAK,GAAG,EAAE,MAAM,QAAQ,CAAC,CAAC,KAAK,EAAE,IAAI,QAAQ,CAAC,CAAC,MAAM,EAAE,SAAS,QAAQ,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,MAClK;AACA,qBAAO,KAAK,YAAY,aAAa,MAAM,kBAAkB;AAC7D,MAAAA,SAAQ,YAAY;AAAA,IACtB,CAAC,EACA,GAAG,SAAS,CAAC,QAAQ;AACpB,qBAAO,MAAM,6BAA6B,IAAI,OAAO,EAAE;AACvD,aAAO,IAAI,MAAM,6BAA6B,IAAI,OAAO,EAAE,CAAC;AAAA,IAC9D,CAAC,EACA,IAAI;AAAA,EACT,CAAC;AACH;;;AC/DA,IAAMC,cAAa,cAAc;AACjC,IAAMC,aAAY,SAAS;AAWpB,SAAS,mBACd,cACA,SACQ;AACR,MAAI,aAAa,WAAW,GAAG;AAC7B,UAAM,IAAI,MAAM,gCAAgC;AAAA,EAClD;AAEA,QAAM,cAAwB,CAAC;AAC/B,QAAM,eAAyB,CAAC;AAChC,QAAM,cAAc,SAAS;AAE7B,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,UAAM,MAAM,aAAa,CAAC;AAC1B,gBAAY;AAAA,MACV,mBAAmB,IAAI,MAAM,QAAQ,CAAC,CAAC,QAAQ,IAAI,IAAI,QAAQ,CAAC,CAAC,yBAAyB,CAAC;AAAA,IAC7F;AACA,gBAAY;AAAA,MACV,oBAAoB,IAAI,MAAM,QAAQ,CAAC,CAAC,QAAQ,IAAI,IAAI,QAAQ,CAAC,CAAC,0BAA0B,CAAC;AAAA,IAC/F;AACA,iBAAa,KAAK,KAAK,CAAC,MAAM,CAAC,GAAG;AAAA,EACpC;AAEA,QAAM,aAAa,cAAc,SAAS;AAC1C,QAAM,aAAa,cAAc,SAAS;AAE1C,cAAY;AAAA,IACV,GAAG,aAAa,KAAK,EAAE,CAAC,YAAY,aAAa,MAAM,WAAW,UAAU,GAAG,UAAU;AAAA,EAC3F;AAEA,MAAI,aAAa;AACf,UAAM,WAAW,SAAS,YAAY;AACtC,gBAAY,KAAK,WAAW,QAAS,WAAW,aAAa,QAAQ,QAAQ;AAAA,EAC/E;AAEA,SAAO,YAAY,KAAK,KAAK;AAC/B;AAOA,eAAsB,eACpB,WACA,cACA,YACiB;AACjB,QAAM,gBAAgB,mBAAmB,YAAY;AAErD,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IAAM;AAAA,IACN;AAAA,IAAmB;AAAA,IACnB;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAW;AAAA,IACX;AAAA,IAAQ;AAAA,IACR;AAAA,IAAY;AAAA,IACZ;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,EACF;AAEA,iBAAO,KAAK,4BAA4B,aAAa,MAAM,oBAAe,UAAU,EAAE;AAEtF,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,gBAAYF,aAAY,MAAM,EAAE,WAAW,KAAK,OAAO,KAAK,GAAG,CAAC,OAAO,SAAS,WAAW;AACzF,UAAI,OAAO;AACT,uBAAO,MAAM,mCAAmC,MAAM,EAAE;AACxD,eAAO,IAAI,MAAM,4BAA4B,MAAM,OAAO,EAAE,CAAC;AAC7D;AAAA,MACF;AACA,qBAAO,KAAK,8BAA8B,UAAU,EAAE;AACtD,MAAAE,SAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AACH;AAOA,eAAsB,yBACpB,WACA,cACA,SACA,YACiB;AAEjB,QAAM,UAAU,MAAM,YAAY,UAAU;AAC5C,QAAM,UAAU,KAAK,SAAS,cAAc;AAC5C,QAAM,SAAS,SAAS,OAAO;AAE/B,MAAI;AACJ,MAAI;AACF,gBAAY,MAAM,cAAcD,UAAS;AAAA,EAC3C,SAAS,KAAU;AACjB,QAAI,KAAK,SAAS,UAAU;AAC1B,YAAM,IAAI,MAAM,gCAAgCA,UAAS,oDAAoD;AAAA,IAC/G;AACA,UAAM;AAAA,EACR;AACA,aAAW,KAAK,WAAW;AACzB,QAAI,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,MAAM,GAAG;AAC5C,YAAM,SAAS,KAAKA,YAAW,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAAA,IACrD;AAAA,EACF;AAEA,QAAM,gBAAgB,mBAAmB,cAAc;AAAA,IACrD,aAAa;AAAA,IACb,UAAU;AAAA,EACZ,CAAC;AAED,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IAAM;AAAA,IACN;AAAA,IAAmB;AAAA,IACnB;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,IAAW;AAAA,IACX;AAAA,IAAQ;AAAA,IACR;AAAA,IAAY;AAAA,IACZ;AAAA,IAAQ;AAAA,IACR;AAAA,IAAQ;AAAA,IACR;AAAA,EACF;AAEA,iBAAO,KAAK,+BAA+B,aAAa,MAAM,kCAA6B,UAAU,EAAE;AAEvG,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,gBAAYF,aAAY,MAAM,EAAE,KAAK,SAAS,WAAW,KAAK,OAAO,KAAK,GAAG,OAAO,OAAO,SAAS,WAAW;AAE7G,YAAM,QAAQ,MAAM,cAAc,OAAO,EAAE,MAAM,MAAM,CAAC,CAAa;AACrE,iBAAW,KAAK,OAAO;AACrB,cAAM,WAAW,KAAK,SAAS,CAAC,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACnD;AACA,YAAM,gBAAgB,OAAO,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAE7C,UAAI,OAAO;AACT,uBAAO,MAAM,mCAAmC,MAAM,EAAE;AACxD,eAAO,IAAI,MAAM,4BAA4B,MAAM,OAAO,EAAE,CAAC;AAC7D;AAAA,MACF;AACA,qBAAO,KAAK,8BAA8B,UAAU,EAAE;AACtD,MAAAE,SAAQ,UAAU;AAAA,IACpB,CAAC;AAAA,EACH,CAAC;AACH;;;ACtJA,IAAMC,iBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBtB,IAAM,yBAAyB;AAAA,EAC7B,MAAM;AAAA,EACN,YAAY;AAAA,IACV,UAAU;AAAA,MACR,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,UACV,OAAO,EAAE,MAAM,UAAU,aAAa,wBAAwB;AAAA,UAC9D,KAAK,EAAE,MAAM,UAAU,aAAa,sBAAsB;AAAA,UAC1D,QAAQ,EAAE,MAAM,UAAU,aAAa,qCAAqC;AAAA,QAC9E;AAAA,QACA,UAAU,CAAC,SAAS,OAAO,QAAQ;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAAA,EACA,UAAU,CAAC,UAAU;AACvB;AAIA,IAAM,sBAAN,cAAkC,UAAU;AAAA,EAClC,WAA8B,CAAC;AAAA,EAEvC,YAAY,OAAgB;AAC1B,UAAM,uBAAuBA,gBAAe,QAAW,KAAK;AAAA,EAC9D;AAAA,EAEU,WAA8B;AACtC,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,aACE;AAAA,QACF,YAAY;AAAA,QACZ,SAAS,OAAO,SAAkB;AAChC,iBAAO,KAAK,eAAe,mBAAmB,IAA+B;AAAA,QAC/E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAgB,eACd,UACA,MACkB;AAClB,QAAI,aAAa,mBAAmB;AAClC,WAAK,WAAW,KAAK;AACrB,qBAAO,KAAK,2CAA2C,KAAK,SAAS,MAAM,kBAAkB;AAC7F,aAAO,EAAE,SAAS,MAAM,OAAO,KAAK,SAAS,OAAO;AAAA,IACtD;AACA,UAAM,IAAI,MAAM,iBAAiB,QAAQ,EAAE;AAAA,EAC7C;AAAA,EAEA,cAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AACF;AAIA,eAAeC,kBAAiB,WAAoC;AAClE,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,SAAS;AACxC,WAAO,SAAS,OAAO,YAAY;AAAA,EACrC,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,mBAAoB,IAAc,OAAO,EAAE;AAAA,EAC7D;AACF;AAUA,eAAsB,kBACpB,OACA,YACA,OAC+B;AAC/B,QAAM,SAA+B,EAAE,YAAY,MAAM,UAAU,UAAU,CAAC,GAAG,cAAc,CAAC,GAAG,WAAW,MAAM;AAGpH,QAAM,iBAAiB,MAAM,cAAc,MAAM,UAAU,GAAG;AAE9D,MAAI,eAAe,WAAW,GAAG;AAC/B,mBAAO,KAAK,8DAAyD;AACrE,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,eAAe,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,UAAU,CAAC;AAC1E,iBAAO,KAAK,oBAAoB,eAAe,MAAM,8BAA8B,aAAa,QAAQ,CAAC,CAAC,kBAAkB;AAG5H,MAAI,kBAAkB,eAAe,OAAO,OAAK,EAAE,YAAY,CAAC;AAChE,MAAI,gBAAgB,WAAW,GAAG;AAChC,mBAAO,KAAK,2DAAsD;AAClE,WAAO;AAAA,EACT;AAGA,MAAI,gBAAgB,SAAS,IAAI;AAC/B,sBAAkB,CAAC,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE;AAC1F,oBAAgB,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAChD,mBAAO,KAAK,sEAAsE;AAAA,EACpF;AAGA,QAAM,QAAQ,IAAI,oBAAoB,KAAK;AAE3C,QAAM,kBAAkB,WAAW,SAAS;AAAA,IAC1C,CAAC,QAAQ,IAAI,IAAI,MAAM,QAAQ,CAAC,CAAC,YAAO,IAAI,IAAI,QAAQ,CAAC,CAAC,MAAM,IAAI,IAAI;AAAA,EAC1E;AAEA,QAAM,eAAe,gBAAgB;AAAA,IACnC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,QAAQ,CAAC,CAAC,YAAO,EAAE,IAAI,QAAQ,CAAC,CAAC,MAAM,EAAE,SAAS,QAAQ,CAAC,CAAC;AAAA,EAC7F;AAEA,QAAM,SAAS;AAAA,IACb,UAAU,MAAM,QAAQ,KAAK,WAAW,SAAS,QAAQ,CAAC,CAAC;AAAA;AAAA,IAC3D;AAAA,IACA,gBAAgB,KAAK,IAAI;AAAA,IACzB;AAAA,IACA;AAAA,IACA,aAAa,KAAK,IAAI;AAAA,IACtB;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,IAAI,MAAM;AACtB,eAAW,MAAM,YAAY;AAAA,EAC/B,UAAE;AACA,UAAM,MAAM,QAAQ;AAAA,EACtB;AAEA,MAAI,SAAS,WAAW,GAAG;AACzB,mBAAO,KAAK,0EAAqE;AACjF,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,WAAW,WAAW;AACzC,MAAI,eAAe;AACnB,QAAM,iBAAoC,CAAC;AAC3C,QAAM,aAAa,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAO,EAAE,MAAM,EAAE,SAAU,EAAE,MAAM,EAAE,MAAM;AACrF,aAAW,KAAK,YAAY;AAC1B,UAAM,MAAM,EAAE,MAAM,EAAE;AACtB,QAAI,eAAe,OAAO,YAAY;AACpC,qBAAe,KAAK,CAAC;AACrB,sBAAgB;AAAA,IAClB;AAAA,EACF;AACA,MAAI,eAAe,SAAS,SAAS,QAAQ;AAC3C,mBAAO,KAAK,gCAAgC,SAAS,MAAM,OAAO,eAAe,MAAM,aAAa,aAAa,QAAQ,CAAC,CAAC,gCAAgC;AAAA,EAC7J;AACA,aAAW;AAEX,MAAI,SAAS,WAAW,GAAG;AACzB,mBAAO,KAAK,qEAAgE;AAC5E,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB,MAAMA,kBAAiB,MAAM,QAAQ;AAC3D,QAAM,iBAAiB,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAErE,QAAM,eAAiD,CAAC;AACxD,MAAI,SAAS;AAEb,aAAW,WAAW,gBAAgB;AACpC,QAAI,QAAQ,QAAQ,QAAQ;AAC1B,mBAAa,KAAK,EAAE,OAAO,QAAQ,KAAK,QAAQ,MAAM,CAAC;AAAA,IACzD;AACA,aAAS,KAAK,IAAI,QAAQ,QAAQ,GAAG;AAAA,EACvC;AAEA,MAAI,SAAS,eAAe;AAC1B,iBAAa,KAAK,EAAE,OAAO,QAAQ,KAAK,cAAc,CAAC;AAAA,EACzD;AAEA,MAAI,aAAa,WAAW,GAAG;AAC7B,mBAAO,KAAK,gEAA2D;AACvE,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,KAAK,MAAM,UAAU,GAAG,MAAM,IAAI,aAAa;AAClE,QAAM,eAAe,MAAM,UAAU,cAAc,UAAU;AAG7D,QAAM,oBAAsD,CAAC;AAC7D,MAAI,UAAU;AACd,aAAW,OAAO,cAAc;AAC9B,QAAI,IAAI,QAAQ,SAAS;AACvB,wBAAkB,KAAK,EAAE,OAAO,SAAS,KAAK,IAAI,MAAM,CAAC;AAAA,IAC3D;AACA,cAAU,IAAI;AAAA,EAChB;AAGA,QAAM,gBAAgB,kBAAkB,OAAO,CAAC,KAAK,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;AACrF,iBAAO;AAAA,IACL,4BAA4B,kBAAkB,MAAM,qBAAqB,cAAc,QAAQ,CAAC,CAAC,eAAe,UAAU;AAAA,EAC5H;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,WAAW;AAAA,EACb;AACF;;;AC5MA,eAAsB,SACpB,WACA,IACA,cACwB;AACxB,cAAY,SAAS,SAAS;AAC9B,QAAM,QAAQ,KAAK,IAAI;AACvB,MAAI;AACF,UAAM,SAAS,MAAM,GAAG;AACxB,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,iBAAa,KAAK,EAAE,OAAO,WAAW,SAAS,MAAM,SAAS,CAAC;AAC/D,mBAAO,KAAK,SAAS,SAAS,iBAAiB,QAAQ,IAAI;AAC3D,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,iBAAa,KAAK,EAAE,OAAO,WAAW,SAAS,OAAO,OAAO,SAAS,SAAS,CAAC;AAChF,mBAAO,MAAM,SAAS,SAAS,iBAAiB,QAAQ,OAAO,OAAO,EAAE;AACxE,WAAO;AAAA,EACT;AACF;AAMO,SAAS,iBACd,YACA,UACY;AACZ,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAE7D,WAAS,WAAW,GAAmB;AACrC,QAAI,SAAS;AACb,eAAW,KAAK,QAAQ;AACtB,UAAI,KAAK,EAAE,MAAO;AAClB,UAAI,KAAK,EAAE,KAAK;AACd,kBAAU,EAAE,MAAM,EAAE;AAAA,MACtB,OAAO;AAEL,kBAAU,IAAI,EAAE;AAAA,MAClB;AAAA,IACF;AACA,WAAO,IAAI;AAAA,EACb;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,UAAU,WAAW,WAAW,QAAQ;AAAA,IACxC,UAAU,WAAW,SAClB,OAAO,SAAO,CAAC,OAAO,KAAK,OAAK,IAAI,SAAS,EAAE,SAAS,IAAI,OAAO,EAAE,GAAG,CAAC,EACzE,IAAI,UAAQ;AAAA,MACX,GAAG;AAAA,MACH,OAAO,WAAW,IAAI,KAAK;AAAA,MAC3B,KAAK,WAAW,IAAI,GAAG;AAAA,IACzB,EAAE;AAAA,IACJ,OAAO,WAAW,MACf,OAAO,OAAK,CAAC,OAAO,KAAK,OAAK,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,EACnE,IAAI,QAAM;AAAA,MACT,GAAG;AAAA,MACH,OAAO,WAAW,EAAE,KAAK;AAAA,MACzB,KAAK,WAAW,EAAE,GAAG;AAAA,IACvB,EAAE;AAAA,EACN;AACF;AA4BA,eAAsB,aAAa,WAA4C;AAC7E,QAAM,gBAAgB,KAAK,IAAI;AAC/B,QAAM,eAA8B,CAAC;AACrC,QAAM,MAAM,UAAU;AAEtB,cAAY,MAAM;AAClB,iBAAO,KAAK,0BAA0B,SAAS,EAAE;AAGjD,QAAM,QAAQ,MAAM,sCAAqC,MAAM,YAAY,SAAS,GAAG,YAAY;AACnG,MAAI,CAAC,OAAO;AACV,UAAMC,iBAAgB,KAAK,IAAI,IAAI;AACnC,mBAAO,MAAM,+DAA0D;AACvE,WAAO,EAAE,OAAO,EAAE,cAAc,WAAW,UAAU,IAAI,UAAU,IAAI,MAAM,IAAI,UAAU,IAAI,UAAU,GAAG,MAAM,GAAG,WAAW,oBAAI,KAAK,EAAE,GAAG,YAAY,QAAW,iBAAiB,QAAW,UAAU,QAAW,oBAAoB,QAAW,SAAS,QAAW,QAAQ,CAAC,GAAG,aAAa,CAAC,GAAG,aAAa,CAAC,GAAG,UAAU,QAAW,cAAc,eAAAA,eAAc;AAAA,EAC1W;AAGA,MAAI;AACJ,eAAa,MAAM,8CAA0C,MAAM,gBAAgB,KAAK,GAAG,YAAY;AAGvG,MAAI;AACJ,MAAI;AACJ,MAAI,kBAAoD,CAAC;AACzD,MAAI;AAEJ,MAAI,cAAc,CAAC,IAAI,sBAAsB;AAC3C,UAAM,SAAS,MAAM,iDAAqD,MAAM,kBAAkB,OAAO,YAAa,iBAAiB,qBAAqB,CAAC,GAAG,YAAY;AAC5K,QAAI,UAAU,OAAO,WAAW;AAC9B,wBAAkB,OAAO;AACzB,wBAAkB,OAAO;AACzB,4BAAsB,OAAO;AAC7B,2BAAqB,iBAAiB,YAAY,eAAe;AAGjE,YAAM,eAAe,gBAAgB,OAAO,CAAC,KAAK,MAAM,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;AAClF,YAAM,mBAAmB,WAAW,WAAW;AAC/C,YAAM,mBAAmB,mBAAmB;AAC5C,YAAM,QAAQ,KAAK,IAAI,mBAAmB,gBAAgB;AAC1D,qBAAO,KAAK,wCAAwC,WAAW,SAAS,QAAQ,CAAC,CAAC,cAAc,aAAa,QAAQ,CAAC,CAAC,eAAe,iBAAiB,QAAQ,CAAC,CAAC,eAAe,iBAAiB,QAAQ,CAAC,CAAC,YAAY,MAAM,QAAQ,CAAC,CAAC,GAAG;AAE1O,YAAM;AAAA,QACJ,KAAK,MAAM,UAAU,wBAAwB;AAAA,QAC7C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,oBAAoB,sBAAsB;AAGhD,MAAI;AACJ,MAAI,qBAAqB,CAAC,IAAI,eAAe;AAC3C,eAAW,MAAM,oCAAmC,MAAM,iBAAiB,OAAO,iBAAiB,GAAG,YAAY;AAAA,EACpH;AAGA,MAAI;AACJ,MAAI,YAAY,CAAC,IAAI,eAAe;AAClC,UAAM,UAAU,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,CAAC;AACvD,QAAI,WAAW,qBAAqB;AAGlC,YAAM,kBAAkB,KAAK,MAAM,UAAU,GAAG,MAAM,IAAI,gBAAgB;AAC1E,2BAAqB,MAAM;AAAA;AAAA,QAEzB,MAAM,yBAAyB,MAAM,UAAU,qBAAsB,SAAS,eAAe;AAAA,QAC7F;AAAA,MACF;AAAA,IACF,WAAW,SAAS;AAElB,YAAM,cAAc,mBAAmB,MAAM;AAC7C,YAAM,kBAAkB,KAAK,MAAM,UAAU,GAAG,MAAM,IAAI,gBAAgB;AAC1E,2BAAqB,MAAM;AAAA;AAAA,QAEzB,MAAM,aAAa,aAAa,SAAS,eAAe;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,SAAsB,CAAC;AAC3B,MAAI,cAAc,CAAC,IAAI,aAAa;AAClC,UAAM,SAAS,MAAM,gCAAoC,MAAM,eAAe,OAAO,YAAY,iBAAiB,aAAa,CAAC,GAAG,YAAY;AAC/I,QAAI,OAAQ,UAAS;AAAA,EACvB;AAGA,MAAI,cAA4B,CAAC;AACjC,MAAI,cAAc,CAAC,IAAI,mBAAmB;AACxC,UAAM,SAAS,MAAM,2CAA0C,MAAM,oBAAoB,OAAO,YAAY,iBAAiB,kBAAkB,CAAC,GAAG,YAAY;AAC/J,QAAI,OAAQ,eAAc;AAAA,EAC5B;AAGA,MAAI;AACJ,MAAI,YAAY;AACd,eAAW,MAAM,oCAAoC,MAAM,iBAAiB,OAAO,YAAY,iBAAiB,cAAc,CAAC,GAAG,YAAY;AAAA,EAChJ;AAGA,MAAI;AACJ,MAAI,YAAY;AACd,cAAU,MAAM,kCAAsC,MAAM,gBAAgB,OAAO,YAAY,QAAQ,UAAU,iBAAiB,cAAc,CAAC,GAAG,YAAY;AAAA,EAClK;AAGA,MAAI,cAA4B,CAAC;AACjC,MAAI,cAAc,WAAW,CAAC,IAAI,aAAa;AAC7C,UAAM,SAAS,MAAM;AAAA;AAAA,MAEnB,MAAM,oBAAoB,OAAO,YAAY,SAAS,KAAK,MAAM,UAAU,cAAc,GAAG,iBAAiB,kBAAkB,CAAC;AAAA,MAChI;AAAA,IACF;AACA,QAAI,OAAQ,eAAc;AAAA,EAC5B;AAGA,MAAI,cAAc,OAAO,SAAS,KAAK,CAAC,IAAI,aAAa;AACvD,UAAM;AAAA;AAAA,MAEJ,YAAY;AACV,mBAAW,SAAS,QAAQ;AAC1B,gBAAM,QAAQ,MAAM,mBAAmB,OAAO,OAAO,YAAY,iBAAiB,iBAAiB,CAAC;AACpG,sBAAY,KAAK,GAAG,KAAK;AAAA,QAC3B;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,cAAc,YAAY,SAAS,KAAK,CAAC,IAAI,aAAa;AAC5D,UAAM;AAAA;AAAA,MAEJ,YAAY;AACV,mBAAW,QAAQ,aAAa;AAC9B,gBAAM,cAAyB;AAAA,YAC7B,IAAI,KAAK;AAAA,YACT,OAAO,KAAK;AAAA,YACZ,MAAM,KAAK;AAAA,YACX,UAAU,KAAK;AAAA,YACf,eAAe,KAAK;AAAA,YACpB,YAAY,KAAK;AAAA,YACjB,eAAe,KAAK;AAAA,YACpB,aAAa,KAAK;AAAA,YAClB,MAAM,KAAK;AAAA,UACb;AACA,gBAAM,QAAQ,MAAM,mBAAmB,OAAO,aAAa,YAAY,iBAAiB,sBAAsB,CAAC;AAE/G,gBAAM,WAAW,KAAK,QAAQ,MAAM,QAAQ,GAAG,cAAc;AAC7D,gBAAM,WAAW,KAAK,UAAU,KAAK,MAAM,OAAO;AAClD,gBAAM,gBAAgB,QAAQ;AAC9B,qBAAW,QAAQ,OAAO;AACxB,kBAAM,WAAW,KAAK,UAAU,SAAS,KAAK,UAAU,CAAC;AACzD,kBAAM,SAAS,KAAK,YAAY,QAAQ;AACxC,kBAAM,WAAW,KAAK,UAAU;AAChC,iBAAK,aAAa;AAAA,UACpB;AACA,sBAAY,KAAK,GAAG,KAAK;AAAA,QAC3B;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,YAAY,SAAS,KAAK,CAAC,IAAI,qBAAqB;AACtD,UAAM;AAAA;AAAA,MAEJ,MAAM,kBAAkB,OAAO,QAAQ,aAAa,aAAa,kBAAkB;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,cAAc,SAAS;AACzB,eAAW,MAAM;AAAA;AAAA,MAEf,MAAM,iBAAiB,OAAO,YAAY,SAAS,iBAAiB,WAAW,CAAC;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,CAAC,IAAI,UAAU;AACjB,UAAM,mCAA8B,MAAM,cAAc,MAAM,IAAI,GAAG,YAAY;AAAA,EACnF;AAEA,QAAM,gBAAgB,KAAK,IAAI,IAAI;AAGnC,QAAM,SAAS,YAAY,UAAU;AACrC,MAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,mBAAO,KAAK,YAAY,aAAa,CAAC;AACtC,UAAM,SAAS,qBAAqB,MAAM;AAC1C,UAAM,WAAW,KAAK,MAAM,UAAU,gBAAgB;AACtD,UAAM,cAAc,UAAU,MAAM;AACpC,mBAAO,KAAK,sBAAsB,QAAQ,EAAE;AAAA,EAC9C;AAEA,iBAAO,KAAK,yBAAyB,aAAa,IAAI;AAEtD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,QAA4B;AACxD,MAAI,KAAK;AACT,QAAM;AAAA;AAAA;AACN,QAAM,mBAAmB,OAAO,aAAa,QAAQ,CAAC,CAAC;AAAA;AACvD,MAAI,OAAO,YAAY,EAAG,OAAM,kBAAkB,OAAO,SAAS;AAAA;AAClE,QAAM,oBAAoB,OAAO,YAAY,MAAM,eAAe,CAAC;AAAA;AACnE,QAAM,qBAAqB,OAAO,YAAY,OAAO,eAAe,CAAC;AAAA;AACrE,QAAM,iBAAiB,OAAO,QAAQ,MAAM;AAAA;AAC5C,MAAI,OAAO,sBAAsB,EAAG,OAAM,sBAAsB,OAAO,oBAAoB,QAAQ,CAAC,CAAC;AAAA;AACrG,QAAM;AAEN,MAAI,OAAO,KAAK,OAAO,OAAO,EAAE,SAAS,GAAG;AAC1C,UAAM;AACN,eAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AAC1D,YAAM,KAAK,KAAK,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,MAAM,KAAK,IAAI,MAAM,KAAK,KAAK;AAAA;AAAA,IAC/E;AACA,UAAM;AAAA,EACR;AAEA,MAAI,OAAO,KAAK,OAAO,OAAO,EAAE,SAAS,GAAG;AAC1C,UAAM;AACN,eAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AAC1D,YAAM,KAAK,KAAK,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,MAAM,KAAK,IAAI,MAAM,KAAK,KAAK;AAAA;AAAA,IAC/E;AACA,UAAM;AAAA,EACR;AAEA,MAAI,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG;AAC5C,UAAM;AACN,eAAW,CAAC,SAAS,IAAI,KAAK,OAAO,QAAQ,OAAO,SAAS,GAAG;AAC9D,YAAM,KAAK,OAAO,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,MAAM,KAAK,KAAK;AAAA;AAAA,IAClE;AACA,UAAM;AAAA,EACR;AAEA,SAAO;AACT;AAEA,eAAsB,iBAAiB,WAAmD;AACxF,MAAI;AACF,WAAO,MAAM,aAAa,SAAS;AAAA,EACrC,SAAS,KAAc;AACrB,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,mBAAO,MAAM,wCAAwC,OAAO,EAAE;AAC9D,WAAO;AAAA,EACT;AACF;;;ACjaA,SAAS,gBAAgB;;;ACgFlB,IAAM,gBAAN,MAAoB;AAAA,EACjB,UAAU;AAAA,EACV;AAAA,EAER,YAAY,QAAiB;AAC3B,SAAK,SAAS,UAAU,UAAU,EAAE;AACpC,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,8EAAyE;AAAA,IAC3F;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,QACZ,UACA,UAAuB,CAAC,GACxB,UAAU,GACE;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,QAAQ;AACtC,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,GAAI,QAAQ;AAAA,IACd;AAGA,QAAI,EAAE,QAAQ,gBAAgB,WAAW;AACvC,cAAQ,cAAc,IAAI;AAAA,IAC5B;AAEA,mBAAO,MAAM,YAAY,QAAQ,UAAU,KAAK,IAAI,QAAQ,EAAE;AAE9D,aAAS,UAAU,GAAG,WAAW,SAAS,WAAW;AACnD,YAAM,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,QAAQ,CAAC;AAEzD,UAAI,SAAS,IAAI;AAEf,YAAI,SAAS,WAAW,IAAK,QAAO;AACpC,eAAQ,MAAM,SAAS,KAAK;AAAA,MAC9B;AAGA,UAAI,SAAS,WAAW,OAAO,UAAU,SAAS;AAChD,cAAM,aAAa,OAAO,SAAS,QAAQ,IAAI,aAAa,CAAC,KAAK;AAClE,uBAAO,KAAK,sCAAsC,UAAU,cAAc,OAAO,IAAI,OAAO,GAAG;AAC/F,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,aAAa,GAAI,CAAC;AACzD;AAAA,MACF;AAGA,UAAI,SAAS,WAAW,KAAK;AAC3B,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAGA,YAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,WAAW;AAC1D,YAAM,IAAI;AAAA,QACR,kBAAkB,SAAS,MAAM,IAAI,QAAQ,UAAU,KAAK,IAAI,QAAQ,KAAK,IAAI;AAAA,MACnF;AAAA,IACF;AAGA,UAAM,IAAI,MAAM,iCAAiC,OAAO,UAAU;AAAA,EACpE;AAAA;AAAA,EAIA,MAAM,eAAuC;AAC3C,UAAM,OAAO,MAAM,KAAK,QAAqC,WAAW;AACxE,WAAO,KAAK,YAAY,CAAC;AAAA,EAC3B;AAAA,EAEA,MAAM,eAAuC;AAC3C,UAAM,OAAO,MAAM,KAAK,QAAqC,WAAW;AACxE,WAAO,KAAK,YAAY,CAAC;AAAA,EAC3B;AAAA,EAEA,MAAM,kBAAkB,UAAwC;AAC9D,UAAM,SAAS,IAAI,gBAAgB,EAAE,QAAQ,YAAY,CAAC;AAC1D,QAAI,SAAU,QAAO,IAAI,YAAY,QAAQ;AAC7C,UAAM,OAAO,MAAM,KAAK,QAA+B,UAAU,MAAM,EAAE;AACzE,WAAO,KAAK,SAAS,CAAC;AAAA,EACxB;AAAA,EAEA,MAAM,cAAc,UAAwC;AAC1D,UAAM,SAAS,IAAI,gBAAgB,EAAE,QAAQ,QAAQ,CAAC;AACtD,QAAI,SAAU,QAAO,IAAI,YAAY,QAAQ;AAC7C,UAAM,OAAO,MAAM,KAAK,QAA+B,UAAU,MAAM,EAAE;AACzE,WAAO,KAAK,SAAS,CAAC;AAAA,EACxB;AAAA,EAEA,MAAM,WAAW,QAA6C;AAC5D,UAAM,OAAO,MAAM,KAAK,QAA4B,UAAU;AAAA,MAC5D,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,MAAM;AAAA,IAC7B,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,WAAW,QAA+B;AAC9C,UAAM,KAAK,QAAc,UAAU,mBAAmB,MAAM,CAAC,IAAI;AAAA,MAC/D,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WAAW,QAAgB,SAAqD;AACpF,UAAM,OAAO,MAAM,KAAK,QAA4B,UAAU,mBAAmB,MAAM,CAAC,IAAI;AAAA,MAC1F,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,YAAY,UAAkD;AAClE,UAAM,YAAY,MAAM,aAAa,QAAQ;AAC7C,UAAM,WAAW,SAAS,QAAQ;AAClC,UAAM,MAAM,QAAQ,QAAQ,EAAE,YAAY;AAC1C,UAAM,cACJ,QAAQ,SAAS,cAAc,QAAQ,UAAU,eAAe,QAAQ,SAAS,oBAAoB;AAEvG,mBAAO,KAAK,sBAAsB,OAAO,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC,MAAM,UAAU,OAAO,OAAO,MAAM,QAAQ,CAAC,CAAC,MAAM;AAG7H,UAAM,UAAU,MAAM,KAAK,QAAgC,kBAAkB;AAAA,MAC3E,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,UAAU,UAAU,YAAY,CAAC;AAAA,IAC1D,CAAC;AACD,mBAAO,MAAM,uCAAuC,OAAO,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC,gBAAgB,QAAQ,SAAS,IAAI;AAGhI,UAAM,aAAa,eAAe,QAAQ;AAC1C,QAAI;AACF,YAAM,YAAY,SAAS,MAAM,UAAU;AAC3C,YAAM,aAAa,MAAM,MAAM,QAAQ,WAAW;AAAA,QAChD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,kBAAkB,OAAO,UAAU,IAAI;AAAA,QACzC;AAAA,QACA,MAAM;AAAA;AAAA,QAEN,QAAQ;AAAA,MACV,CAAgB;AAChB,UAAI,CAAC,WAAW,IAAI;AAClB,cAAM,IAAI,MAAM,6BAA6B,WAAW,MAAM,IAAI,WAAW,UAAU,EAAE;AAAA,MAC3F;AAAA,IACF,UAAE;AAEA,iBAAW,QAAQ;AAAA,IACrB;AACA,mBAAO,MAAM,kCAA6B,QAAQ,SAAS,EAAE;AAE7D,UAAM,OAA0B,YAAY,WAAW,QAAQ,IAAI,UAAU;AAC7E,WAAO,EAAE,KAAK,QAAQ,WAAW,KAAK;AAAA,EACxC;AAAA;AAAA,EAIA,MAAM,qBAAwF;AAC5F,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,aAAa;AACzC,YAAM,OAAO,SAAS,CAAC,GAAG;AAC1B,qBAAO,KAAK,6CAAwC,QAAQ,SAAS,EAAE;AACvE,aAAO,EAAE,OAAO,MAAM,aAAa,KAAK;AAAA,IAC1C,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,qBAAO,MAAM,+BAA+B,OAAO,EAAE;AACrD,aAAO,EAAE,OAAO,OAAO,OAAO,QAAQ;AAAA,IACxC;AAAA,EACF;AACF;;;ACrOA,IAAM,aAA0B,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAChF,IAAM,aAAa;AAEnB,IAAI,eAAsC;AAEnC,SAAS,2BAA2C;AACzD,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW;AAAA,MACT,UAAU;AAAA,QACR,OAAO;AAAA,UACL,EAAE,MAAM,CAAC,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,6BAA6B;AAAA,UAC3E,EAAE,MAAM,CAAC,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,yBAAyB;AAAA,QAChF;AAAA,QACA,WAAW,CAAC,OAAO,KAAK;AAAA,MAC1B;AAAA,MACA,QAAQ;AAAA,QACN,OAAO;AAAA,UACL,EAAE,MAAM,CAAC,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,4BAA4B;AAAA,UACjF,EAAE,MAAM,CAAC,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,kBAAkB;AAAA,QAClE;AAAA,QACA,WAAW,CAAC;AAAA,MACd;AAAA,MACA,WAAW;AAAA,QACT,OAAO;AAAA,UACL,EAAE,MAAM,CAAC,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,iBAAiB;AAAA,UACtE,EAAE,MAAM,CAAC,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,qBAAqB;AAAA,QAC5E;AAAA,QACA,WAAW,CAAC;AAAA,MACd;AAAA,MACA,SAAS;AAAA,QACP,OAAO;AAAA,UACL,EAAE,MAAM,CAAC,KAAK,GAAG,MAAM,SAAS,OAAO,wBAAwB;AAAA,UAC/D,EAAE,MAAM,CAAC,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,wBAAwB;AAAA,QACxE;AAAA,QACA,WAAW,CAAC,KAAK;AAAA,MACnB;AAAA,MACA,SAAS;AAAA,QACP,OAAO;AAAA,UACL,EAAE,MAAM,CAAC,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,qBAAqB;AAAA,UACxF,EAAE,MAAM,CAAC,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,eAAe;AAAA,UACpE,EAAE,MAAM,CAAC,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,MAAM,SAAS,OAAO,eAAe;AAAA,QACpF;AAAA,QACA,WAAW,CAAC;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,uBAAuBC,SAAiC;AACtE,MAAI,CAACA,WAAU,OAAOA,YAAW,UAAU;AACzC,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAEA,QAAM,MAAMA;AAEZ,MAAI,OAAO,IAAI,aAAa,YAAY,IAAI,SAAS,KAAK,MAAM,IAAI;AAClE,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AAEA,MAAI,CAAC,IAAI,aAAa,OAAO,IAAI,cAAc,YAAY,MAAM,QAAQ,IAAI,SAAS,GAAG;AACvF,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,QAAM,YAAY,IAAI;AACtB,QAAM,YAA4B;AAAA,IAChC,UAAU,IAAI;AAAA,IACd,WAAW,CAAC;AAAA,EACd;AAEA,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACrD,QAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,YAAM,IAAI,MAAM,aAAa,IAAI,qBAAqB;AAAA,IACxD;AAEA,UAAM,OAAO;AAEb,QAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,GAAG;AAC9B,YAAM,IAAI,MAAM,aAAa,IAAI,6BAA6B;AAAA,IAChE;AAEA,QAAI,CAAC,MAAM,QAAQ,KAAK,SAAS,GAAG;AAClC,YAAM,IAAI,MAAM,aAAa,IAAI,kCAAkC;AAAA,IACrE;AAEA,eAAW,OAAO,KAAK,WAAW;AAChC,UAAI,CAAC,WAAW,SAAS,GAAgB,GAAG;AAC1C,cAAM,IAAI,MAAM,aAAa,IAAI,qCAAqC,GAAG,aAAa,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,MAC/G;AAAA,IACF;AAEA,UAAM,iBAA6B,CAAC;AACpC,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,YAAM,OAAO,KAAK,MAAM,CAAC;AAEzB,UAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,KAAK,WAAW,GAAG;AACvD,cAAM,IAAI,MAAM,aAAa,IAAI,UAAU,CAAC,qCAAqC;AAAA,MACnF;AAEA,iBAAW,OAAO,KAAK,MAAM;AAC3B,YAAI,CAAC,WAAW,SAAS,GAAgB,GAAG;AAC1C,gBAAM,IAAI,MAAM,aAAa,IAAI,UAAU,CAAC,qBAAqB,GAAG,aAAa,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,QAC1G;AAAA,MACF;AAEA,UAAI,OAAO,KAAK,SAAS,YAAY,CAAC,WAAW,KAAK,KAAK,IAAI,GAAG;AAChE,cAAM,IAAI,MAAM,aAAa,IAAI,UAAU,CAAC,oDAA+C;AAAA,MAC7F;AAEA,UAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,MAAM,IAAI;AAC9D,cAAM,IAAI,MAAM,aAAa,IAAI,UAAU,CAAC,uCAAuC;AAAA,MACrF;AAEA,qBAAe,KAAK;AAAA,QAClB,MAAM,KAAK;AAAA,QACX,MAAM,KAAK;AAAA,QACX,OAAO,KAAK;AAAA,MACd,CAAC;AAAA,IACH;AAEA,cAAU,UAAU,IAAI,IAAI;AAAA,MAC1B,OAAO;AAAA,MACP,WAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,mBAAmB,YAA8C;AACrF,MAAI,aAAc,QAAO;AAEzB,QAAM,WAAW,cAAc,KAAK,QAAQ,IAAI,GAAG,eAAe;AAElE,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,aAAa,QAAQ;AAAA,EACnC,QAAQ;AACN,mBAAO,KAAK,6BAA6B,QAAQ,0BAA0B;AAC3E,UAAM,WAAW,yBAAyB;AAE1C,QAAI;AACF,YAAM,aAAa,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG;AAAA,QAC9D,UAAU;AAAA,QACV,MAAM;AAAA,QACN,MAAM;AAAA,MACR,CAAC;AAAA,IACH,SAAS,KAAU;AAEjB,UAAI,IAAI,SAAS,UAAU;AACzB,cAAMC,OAAM,MAAM,aAAa,QAAQ;AACvC,cAAMC,UAAkB,KAAK,MAAMD,IAAG;AACtC,uBAAe,uBAAuBC,OAAM;AAC5C,uBAAO,KAAK,+BAA+B,QAAQ,EAAE;AACrD,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AACA,mBAAe;AACf,WAAO;AAAA,EACT;AAEA,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,iBAAe,uBAAuB,MAAM;AAC5C,iBAAO,KAAK,+BAA+B,QAAQ,EAAE;AACrD,SAAO;AACT;;;ACpLA,IAAMC,WAAU,oBAAoB,YAAY,GAAG;AAU5C,SAAS,sBAAsB,KAAiC;AACrE,UAAQ,OAAO,WAAW,KAAK,EAAE,YAAY;AAC/C;AAEA,SAAS,oBAAsD;AAC7D,QAAMC,UAAS,UAAU;AACzB,MAAIA,QAAO,eAAeA,QAAO,gBAAgB,UAAU;AACzD,WAAO,EAAE,MAAMA,QAAO,aAAa,QAAQ,qBAAqB;AAAA,EAClE;AACA,MAAI;AACF,UAAM,aAAaD,SAAQ,eAAe;AAC1C,QAAI,cAAc,eAAe,UAAU,GAAG;AAC5C,aAAO,EAAE,MAAM,YAAY,QAAQ,gBAAgB;AAAA,IACrD;AAAA,EACF,QAAQ;AAAA,EAAsB;AAC9B,SAAO,EAAE,MAAM,UAAU,QAAQ,cAAc;AACjD;AAEA,SAAS,qBAAuD;AAC9D,QAAMC,UAAS,UAAU;AACzB,MAAIA,QAAO,gBAAgBA,QAAO,iBAAiB,WAAW;AAC5D,WAAO,EAAE,MAAMA,QAAO,cAAc,QAAQ,sBAAsB;AAAA,EACpE;AACA,MAAI;AACF,UAAM,EAAE,MAAM,UAAU,IAAID,SAAQ,4BAA4B;AAChE,QAAI,aAAa,eAAe,SAAS,GAAG;AAC1C,aAAO,EAAE,MAAM,WAAW,QAAQ,6BAA6B;AAAA,IACjE;AAAA,EACF,QAAQ;AAAA,EAAsB;AAC9B,SAAO,EAAE,MAAM,WAAW,QAAQ,cAAc;AAClD;AAEA,SAAS,uBAAuB,QAAwB;AACtD,QAAM,QAAQ,OAAO,MAAM,sBAAsB;AACjD,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAEA,SAAS,uBAA+B;AACtC,QAAM,WAAW,QAAQ;AACzB,QAAM,QAAQ,CAAC,iBAAiB;AAChC,MAAI,aAAa,SAAS;AACxB,UAAM,KAAK,8BAA8B;AACzC,UAAM,KAAK,6CAA6C;AAAA,EAC1D,WAAW,aAAa,UAAU;AAChC,UAAM,KAAK,uBAAuB;AAAA,EACpC,OAAO;AACL,UAAM,KAAK,+CAA+C;AAC1D,UAAM,KAAK,wCAAwC;AACnD,UAAM,KAAK,sCAAsC;AAAA,EACnD;AACA,QAAM,KAAK,kDAAkD;AAC7D,SAAO,MAAM,KAAK,cAAc;AAClC;AAEA,SAAS,YAAyB;AAChC,QAAM,MAAM,QAAQ;AACpB,QAAM,QAAQ,SAAS,IAAI,MAAM,CAAC,GAAG,EAAE;AACvC,QAAM,KAAK,SAAS;AACpB,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,IACV,SAAS,KACL,WAAW,GAAG,0BACd,WAAW,GAAG;AAAA,EACpB;AACF;AAEA,SAAS,cAA2B;AAClC,QAAM,EAAE,MAAM,SAAS,OAAO,IAAI,kBAAkB;AACpD,MAAI;AACF,UAAM,SAAS,aAAa,SAAS,CAAC,UAAU,GAAG,EAAE,SAAS,IAAO,CAAC;AACtE,QAAI,OAAO,WAAW,KAAK,OAAO,QAAQ;AACxC,YAAM,MAAM,uBAAuB,OAAO,MAAM;AAChD,aAAO,EAAE,OAAO,UAAU,IAAI,MAAM,UAAU,MAAM,SAAS,UAAU,GAAG,aAAa,MAAM,IAAI;AAAA,IACnG;AAAA,EACF,QAAQ;AAAA,EAAqB;AAC7B,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,SAAS,2BAAsB,qBAAqB,CAAC;AAAA,EACvD;AACF;AAEA,SAAS,eAA4B;AACnC,QAAM,EAAE,MAAM,SAAS,OAAO,IAAI,mBAAmB;AACrD,MAAI;AACF,UAAM,SAAS,aAAa,SAAS,CAAC,UAAU,GAAG,EAAE,SAAS,IAAO,CAAC;AACtE,QAAI,OAAO,WAAW,KAAK,OAAO,QAAQ;AACxC,YAAM,MAAM,uBAAuB,OAAO,MAAM;AAChD,aAAO,EAAE,OAAO,WAAW,IAAI,MAAM,UAAU,MAAM,SAAS,WAAW,GAAG,aAAa,MAAM,IAAI;AAAA,IACrG;AAAA,EACF,QAAQ;AAAA,EAAqB;AAC7B,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,SAAS;AAAA,YAAgE,qBAAqB,CAAC;AAAA,EACjG;AACF;AAEA,SAAS,iBAA8B;AACrC,QAAM,MAAM,CAAC,CAAC,UAAU,EAAE;AAC1B,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,SAAS,MACL,0BACA;AAAA,EACN;AACF;AAEA,SAAS,cAA2B;AAClC,QAAM,MAAM,CAAC,CAAC,UAAU,EAAE;AAC1B,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,SAAS,MACL,uBACA;AAAA,EACN;AACF;AAEA,SAAS,WAAwB;AAC/B,MAAI;AACF,UAAM,SAAS,aAAa,OAAO,CAAC,WAAW,GAAG,EAAE,SAAS,IAAO,CAAC;AACrE,QAAI,OAAO,WAAW,KAAK,OAAO,QAAQ;AACxC,YAAM,MAAM,uBAAuB,OAAO,MAAM;AAChD,aAAO,EAAE,OAAO,OAAO,IAAI,MAAM,UAAU,OAAO,SAAS,OAAO,GAAG,GAAG;AAAA,IAC1E;AAAA,EACF,QAAQ;AAAA,EAAqB;AAC7B,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AACF;AAEA,SAAS,mBAAgC;AACvC,QAAM,WAAW,UAAU,EAAE,gBAAgB,KAAK,QAAQ,IAAI,GAAG,OAAO;AACxE,QAAM,SAAS,eAAe,QAAQ;AACtC,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,SAAS,SACL,wBAAwB,QAAQ,KAChC,yBAAyB,QAAQ;AAAA,EACvC;AACF;AAEA,eAAsB,YAA2B;AAC/C,UAAQ,IAAI,+DAAmD;AAE/D,QAAM,UAAyB;AAAA,IAC7B,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,iBAAiB;AAAA,EACnB;AAEA,aAAW,KAAK,SAAS;AACvB,UAAM,OAAO,EAAE,KAAK,WAAM,EAAE,WAAW,WAAM;AAC7C,YAAQ,IAAI,KAAK,IAAI,IAAI,EAAE,OAAO,EAAE;AAAA,EACtC;AAGA,QAAMC,UAAS,UAAU;AACzB,UAAQ,IAAI,gBAAgB;AAC5B,QAAM,eAAe,sBAAsBA,QAAO,YAAY;AAC9D,QAAM,YAAY,CAACA,QAAO;AAC1B,QAAM,gBAAgB,YAAY,GAAG,YAAY,eAAe;AAChE,QAAM,iBAAiC,CAAC,WAAW,UAAU,QAAQ;AAErE,MAAI,CAAC,eAAe,SAAS,YAAY,GAAG;AAC1C,YAAQ,IAAI,sBAAiB,aAAa,0BAAqB;AAC/D,YAAQ,KAAK,EAAE,OAAO,gBAAgB,IAAI,OAAO,UAAU,MAAM,SAAS,qBAAqB,YAAY,GAAG,CAAC;AAAA,EACjH,WAAW,iBAAiB,WAAW;AACrC,YAAQ,IAAI,sBAAiB,aAAa,EAAE;AAC5C,YAAQ,IAAI,0CAAgC;AAAA,EAC9C,WAAW,iBAAiB,UAAU;AACpC,YAAQ,IAAI,sBAAiB,aAAa,EAAE;AAC5C,QAAIA,QAAO,gBAAgB;AACzB,cAAQ,IAAI,wDAAmD;AAAA,IACjE,OAAO;AACL,cAAQ,IAAI,gEAA2D;AACvE,cAAQ,KAAK,EAAE,OAAO,gBAAgB,IAAI,OAAO,UAAU,MAAM,SAAS,wCAAwC,CAAC;AAAA,IACrH;AAAA,EACF,WAAW,iBAAiB,UAAU;AACpC,YAAQ,IAAI,sBAAiB,aAAa,EAAE;AAC5C,QAAIA,QAAO,mBAAmB;AAC5B,cAAQ,IAAI,mCAA8B;AAAA,IAC5C,OAAO;AACL,cAAQ,IAAI,mEAA8D;AAC1E,cAAQ,KAAK,EAAE,OAAO,gBAAgB,IAAI,OAAO,UAAU,MAAM,SAAS,2CAA2C,CAAC;AAAA,IACxH;AAAA,EACF;AAEA,QAAM,gBAA8C;AAAA,IAClD,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACA,MAAI,eAAe,SAAS,YAAY,GAAG;AACzC,UAAM,eAAe,cAAc,YAAY;AAC/C,UAAM,gBAAgBA,QAAO;AAC7B,QAAI,eAAe;AACjB,cAAQ,IAAI,mCAAyB,aAAa,cAAc,YAAY,GAAG;AAAA,IACjF,OAAO;AACL,cAAQ,IAAI,kCAAwB,YAAY,EAAE;AAAA,IACpD;AAAA,EACF;AAGA,UAAQ,IAAI,qBAAqB;AACjC,QAAM,aAAaA,QAAO,YAAY;AAGtC,QAAM,oBAAoB;AAE1B,QAAM,iBAAiB,QAAQ,OAAO,OAAK,EAAE,YAAY,CAAC,EAAE,EAAE;AAE9D,UAAQ,IAAI;AACZ,MAAI,eAAe,WAAW,GAAG;AAC/B,YAAQ,IAAI,wCAAmC;AAC/C,YAAQ,KAAK,CAAC;AAAA,EAChB,OAAO;AACL,YAAQ,IAAI,KAAK,eAAe,MAAM,kBAAkB,eAAe,SAAS,IAAI,MAAM,EAAE;AAAA,CAAa;AACzG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,IAAM,kBAA0C;AAAA,EAC9C,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AACX;AAEA,eAAe,aAAa,QAA+B;AACzD,MAAI,CAAC,QAAQ;AACX,YAAQ,IAAI,gGAAsF;AAClG;AAAA,EACF;AAEA,MAAI;AACF,UAAM,SAAS,IAAI,cAAc,MAAM;AACvC,UAAM,EAAE,OAAO,aAAa,MAAM,IAAI,MAAM,OAAO,mBAAmB;AAEtE,QAAI,CAAC,OAAO;AACV,cAAQ,IAAI,mCAA8B,SAAS,eAAe,GAAG;AACrE;AAAA,IACF;AAEA,YAAQ,IAAI,gDAA2C,eAAe,SAAS,GAAG;AAGlF,QAAI;AACF,YAAM,WAAW,MAAM,OAAO,aAAa;AAC3C,UAAI,SAAS,WAAW,GAAG;AACzB,gBAAQ,IAAI,+DAAqD;AAAA,MACnE,OAAO;AACL,mBAAW,QAAQ,UAAU;AAC3B,gBAAM,QAAQ,gBAAgB,KAAK,QAAQ,KAAK,KAAK;AACrD,gBAAM,SAAS,KAAK,WAAW,IAAI,KAAK,QAAQ,KAAK,KAAK;AAC1D,kBAAQ,IAAI,YAAO,KAAK,WAAM,MAAM,EAAE;AAAA,QACxC;AAAA,MACF;AAAA,IACF,QAAQ;AACN,cAAQ,IAAI,mDAAyC;AAAA,IACvD;AAAA,EACF,QAAQ;AACN,YAAQ,IAAI,0DAAqD;AAAA,EACnE;AACF;AAEA,eAAe,sBAAqC;AAClD,QAAM,eAAe,KAAK,QAAQ,IAAI,GAAG,eAAe;AAExD,MAAI,CAAC,eAAe,YAAY,GAAG;AACjC,YAAQ,IAAI,oFAA+E;AAC3F;AAAA,EACF;AAEA,MAAI;AACF,UAAM,iBAAiB,MAAM,mBAAmB,YAAY;AAC5D,UAAM,gBAAgB,OAAO,KAAK,eAAe,SAAS,EAAE;AAC5D,YAAQ,IAAI,kDAA6C,aAAa,YAAY,kBAAkB,IAAI,MAAM,EAAE,cAAc;AAAA,EAChI,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAQ,IAAI,0DAAgD,GAAG,EAAE;AAAA,EACnE;AACF;;;ACvTA,IAAM,KAAK,wBAAwB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAEnF,SAAS,IAAI,UAAmC;AAC9C,SAAO,IAAI,QAAQ,CAACC,aAAY;AAC9B,OAAG,SAAS,UAAU,CAAC,WAAWA,SAAQ,MAAM,CAAC;AAAA,EACnD,CAAC;AACH;AAEA,eAAsB,UAAyB;AAE7C,KAAG,GAAG,SAAS,MAAM;AACnB,YAAQ,IAAI,IAAI;AAChB,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAED,UAAQ,IAAI,yCAAkC;AAE9C,QAAMC,WAAU,KAAK,QAAQ,IAAI,GAAG,MAAM;AAC1C,QAAM,UAAkC,CAAC;AAGzC,MAAI,cAAc;AAClB,MAAI;AACF,kBAAc,MAAM,aAAaA,QAAO;AAAA,EAC1C,QAAQ;AAAA,EAER;AAGA,QAAM,eAAuC,CAAC;AAC9C,aAAW,QAAQ,YAAY,MAAM,IAAI,GAAG;AAC1C,UAAM,QAAQ,KAAK,MAAM,kBAAkB;AAC3C,QAAI,MAAO,cAAa,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC;AAAA,EAC7C;AAGA,UAAQ,IAAI,kBAAkB;AAC9B,MAAI;AACF,UAAM,SAAS,cAAc;AAC7B,YAAQ,IAAI,6BAAwB,MAAM,EAAE;AAAA,EAC9C,QAAQ;AACN,YAAQ,IAAI,mEAAyD;AAAA,EACvE;AACA,MAAI;AACF,UAAMC,WAAU,eAAe;AAC/B,YAAQ,IAAI,8BAAyBA,QAAO,EAAE;AAAA,EAChD,QAAQ;AACN,YAAQ,IAAI,4BAAuB;AAAA,EACrC;AAGA,UAAQ,IAAI,iDAAiD;AAC7D,QAAM,gBAAgB,aAAa,kBAAkB,QAAQ,IAAI;AACjE,QAAM,OAAO,gBAAgB,cAAc,cAAc,MAAM,GAAG,CAAC,CAAC,SAAS;AAC7E,QAAM,YAAY,MAAM,IAAI,qBAAqB,IAAI,IAAI;AACzD,MAAI,UAAU,KAAK,GAAG;AACpB,YAAQ,iBAAiB,UAAU,KAAK;AACxC,YAAQ,IAAI,wBAAmB;AAAA,EACjC,WAAW,eAAe;AACxB,YAAQ,IAAI,8BAAyB;AAAA,EACvC,OAAO;AACL,YAAQ,IAAI,+DAAgD;AAAA,EAC9D;AAGA,UAAQ,IAAI,0BAA0B;AACtC,QAAM,WAAW,MAAM,IAAI,kDAAkD;AAC7E,UAAQ,eAAe,SAAS,KAAK,KAAK;AAC1C,UAAQ,IAAI,kBAAa,QAAQ,YAAY,EAAE;AAG/C,MAAI,QAAQ,iBAAiB,UAAU;AACrC,UAAM,YAAY,MAAM,IAAI,yBAAyB;AACrD,QAAI,UAAU,KAAK,EAAG,SAAQ,oBAAoB,UAAU,KAAK;AAAA,EACnE;AAGA,UAAQ,IAAI,gEAA2D;AACvE,QAAM,SAAS,MAAM,IAAI,yCAAyC;AAClE,MAAI,OAAO,KAAK,GAAG;AACjB,YAAQ,cAAc,OAAO,KAAK;AAClC,YAAQ,IAAI,yBAAoB;AAAA,EAClC,OAAO;AACL,YAAQ,IAAI,yBAAe;AAAA,EAC7B;AAGA,UAAQ,IAAI,0CAA0C;AACtD,QAAM,YAAY,MAAM,IAAI,6CAA6C;AAEzE,MAAI,UAAU,YAAY,MAAM,KAAK;AACnC,UAAM,UAAU,MAAM,IAAI,qDAAqD;AAC/E,QAAI,QAAQ,KAAK,GAAG;AAClB,cAAQ,eAAe,QAAQ,KAAK;AAEpC,UAAI;AACF,cAAM,SAAS,IAAI,cAAc,QAAQ,KAAK,CAAC;AAC/C,cAAM,aAAa,MAAM,OAAO,mBAAmB;AACnD,YAAI,WAAW,OAAO;AACpB,kBAAQ,IAAI,kCAA6B,WAAW,WAAW,GAAG;AAClE,gBAAM,WAAW,MAAM,OAAO,aAAa;AAC3C,cAAI,SAAS,SAAS,GAAG;AACvB,oBAAQ,IAAI,uBAAuB;AACnC,uBAAW,OAAO,UAAU;AAC1B,sBAAQ,IAAI,cAAS,IAAI,QAAQ,WAAM,IAAI,YAAY,IAAI,WAAW,EAAE;AAAA,YAC1E;AAAA,UACF;AAAA,QACF,OAAO;AACL,kBAAQ,IAAI,+BAA0B,WAAW,KAAK,EAAE;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,IAAI,2CAAiC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MACjG;AAGA,YAAM,iBAAiB,MAAM,IAAI,2CAA2C;AAC5E,UAAI,eAAe,YAAY,MAAM,KAAK;AACxC,cAAM,eAAe,KAAK,QAAQ,IAAI,GAAG,eAAe;AACxD,YAAI,MAAM,WAAW,YAAY,GAAG;AAClC,kBAAQ,IAAI,uCAAkC;AAAA,QAChD,OAAO;AACL,gBAAM,cAAc,cAAc,KAAK,UAAU,yBAAyB,GAAG,MAAM,CAAC,CAAC;AACrF,kBAAQ,IAAI,2DAAsD;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AACL,YAAQ,IAAI,yBAAe;AAAA,EAC7B;AAGA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAM,QAAQ,IAAI,OAAO,IAAI,GAAG,QAAQ,GAAG;AAC3C,QAAI,MAAM,KAAK,WAAW,GAAG;AAC3B,oBAAc,YAAY,QAAQ,OAAO,GAAG,GAAG,IAAI,KAAK,EAAE;AAAA,IAC5D,OAAO;AACL,qBAAe;AAAA,EAAK,GAAG,IAAI,KAAK;AAAA,IAClC;AAAA,EACF;AACA,QAAM,cAAcD,UAAS,YAAY,KAAK,IAAI,IAAI;AAEtD,UAAQ,IAAI,sDAAiD;AAC7D,UAAQ,IAAI,0DAA0D;AACtE,UAAQ,IAAI,6DAA6D;AAEzE,KAAG,MAAM;AACX;;;AChJA,SAAS,kBAAkB,WAA2B;AACpD,SAAO,IAAI,KAAK,SAAS,EAAE,QAAQ;AACrC;AAEA,IAAM,aAAa;AACnB,IAAM,qBAAqB;AAa3B,SAAS,kBAAkB,UAAkB,MAAoB;AAC/D,QAAM,YAAY,IAAI,KAAK,eAAe,SAAS;AAAA,IACjD,UAAU;AAAA,IACV,cAAc;AAAA,EAChB,CAAC;AACD,QAAM,QAAQ,UAAU,cAAc,IAAI;AAC1C,QAAM,SAAS,MAAM,KAAK,OAAK,EAAE,SAAS,cAAc;AAExD,QAAM,QAAQ,QAAQ,OAAO,MAAM,sBAAsB;AACzD,MAAI,MAAO,QAAO,MAAM,CAAC;AAEzB,MAAI,QAAQ,UAAU,MAAO,QAAO;AACpC,iBAAO;AAAA,IACL,iDAAiD,QAAQ,cAAc,KAAK,YAAY,CAAC,8BAC9D,QAAQ,SAAS,WAAW;AAAA,EACzD;AACA,SAAO;AACT;AAKA,SAAS,kBAAkB,MAAY,MAAc,UAA0B;AAE7E,QAAM,YAAY,IAAI,KAAK,eAAe,SAAS;AAAA,IACjD,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC;AACD,QAAM,QAAQ,UAAU,cAAc,IAAI;AAC1C,QAAM,WAAW,MAAM,KAAK,OAAK,EAAE,SAAS,MAAM,GAAG;AACrD,QAAM,YAAY,MAAM,KAAK,OAAK,EAAE,SAAS,OAAO,GAAG;AACvD,QAAM,UAAU,MAAM,KAAK,OAAK,EAAE,SAAS,KAAK,GAAG;AAEnD,QAAM,OAAO,YAAY,OAAO,KAAK,YAAY,CAAC;AAClD,QAAM,SAAS,aAAa,OAAO,KAAK,SAAS,IAAI,CAAC,GAAG,SAAS,GAAG,GAAG;AACxE,QAAM,OAAO,WAAW,OAAO,KAAK,QAAQ,CAAC,GAAG,SAAS,GAAG,GAAG;AAC/D,QAAM,SAAS,kBAAkB,UAAU,IAAI;AAC/C,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,MAAM,MAAM;AACpD;AAKA,SAAS,uBAAuB,MAAY,UAA6B;AACvE,QAAM,YAAY,IAAI,KAAK,eAAe,SAAS;AAAA,IACjD,UAAU;AAAA,IACV,SAAS;AAAA,EACX,CAAC;AACD,QAAM,QAAQ,UAAU,OAAO,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAC7D,QAAM,MAAiC;AAAA,IACrC,KAAK;AAAA,IAAO,KAAK;AAAA,IAAO,KAAK;AAAA,IAAO,KAAK;AAAA,IAAO,KAAK;AAAA,IAAO,KAAK;AAAA,IAAO,KAAK;AAAA,EAC/E;AACA,SAAO,IAAI,KAAK,KAAK;AACvB;AAOA,eAAe,wBAAwB,UAAwC;AAC7E,MAAI;AACF,UAAM,SAAS,IAAI,cAAc;AACjC,WAAO,MAAM,OAAO,kBAAkB,QAAQ;AAAA,EAChD,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,mBAAO,KAAK,gDAAgD,GAAG,EAAE;AACjE,WAAO,CAAC;AAAA,EACV;AACF;AAKA,eAAe,iBAAiB,UAA0C;AACxE,QAAM,CAAC,WAAW,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,IACpD,wBAAwB,QAAQ;AAAA,IAChC,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,QAAsB,CAAC;AAE7B,aAAW,QAAQ,WAAW;AAC5B,QAAI,CAAC,KAAK,aAAc;AACxB,eAAW,KAAK,KAAK,WAAW;AAC9B,UAAI,CAAC,YAAY,EAAE,aAAa,UAAU;AACxC,cAAM,KAAK;AAAA,UACT,cAAc,KAAK;AAAA,UACnB,QAAQ;AAAA,UACR,QAAQ,KAAK;AAAA,UACb,UAAU,EAAE;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,aAAW,QAAQ,gBAAgB;AACjC,QAAI,YAAY,KAAK,SAAS,aAAa,SAAU;AACrD,QAAI,CAAC,KAAK,SAAS,aAAc;AACjC,UAAM,KAAK;AAAA,MACT,cAAc,KAAK,SAAS;AAAA,MAC5B,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK,SAAS;AAAA,IAC1B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAcA,eAAsB,aAAa,UAA0C;AAC3E,QAAME,UAAS,MAAM,mBAAmB;AACxC,QAAM,iBAAiBA,QAAO,UAAU,QAAQ;AAChD,MAAI,CAAC,gBAAgB;AACnB,mBAAO,KAAK,0CAA0C,OAAO,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC,GAAG;AAChG,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,SAAS,IAAIA;AACrB,QAAM,cAAc,MAAM,iBAAiB,QAAQ;AACnD,QAAM,kBAAkB,IAAI,IAAI,YAAY,IAAI,OAAK,kBAAkB,EAAE,YAAY,CAAC,CAAC;AAEvF,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,cAAc;AAElB,SAAO,eAAe,oBAAoB;AACxC,UAAM,YAAY,KAAK,IAAI,cAAc,aAAa,GAAG,kBAAkB;AAC3E,UAAM,aAAuB,CAAC;AAE9B,aAAS,YAAY,aAAa,aAAa,WAAW,aAAa;AACrE,YAAM,gBAAgB,IAAI,KAAK,GAAG;AAClC,oBAAc,QAAQ,cAAc,QAAQ,IAAI,SAAS;AAEzD,YAAM,YAAY,uBAAuB,eAAe,QAAQ;AAChE,UAAI,eAAe,UAAU,SAAS,SAAS,EAAG;AAElD,iBAAW,QAAQ,eAAe,OAAO;AACvC,YAAI,CAAC,KAAK,KAAK,SAAS,SAAS,EAAG;AACpC,mBAAW,KAAK,kBAAkB,eAAe,KAAK,MAAM,QAAQ,CAAC;AAAA,MACvE;AAAA,IACF;AAEA,eAAW,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,EAAE,QAAQ,IAAI,IAAI,KAAK,CAAC,EAAE,QAAQ,CAAC;AAEvE,UAAM,YAAY,WAAW,KAAK,OAAK,CAAC,gBAAgB,IAAI,kBAAkB,CAAC,CAAC,CAAC;AACjF,QAAI,WAAW;AACb,qBAAO,MAAM,4BAA4B,OAAO,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC,KAAK,OAAO,SAAS,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AAC/H,aAAO;AAAA,IACT;AAEA,kBAAc,YAAY;AAAA,EAC5B;AAEA,iBAAO,KAAK,gCAAgC,OAAO,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC,YAAY,kBAAkB,OAAO;AACxH,SAAO;AACT;AAMA,eAAsB,oBACpB,WACA,SAOE;AACF,QAAM,QAAQ,MAAM,iBAAiB;AAErC,MAAI,WAAW,MAAM,IAAI,QAAM;AAAA,IAC7B,UAAU,EAAE;AAAA,IACZ,cAAc,EAAE;AAAA,IAChB,QAAQ,EAAE;AAAA,IACV,QAAQ,EAAE;AAAA,IACV,QAAQ,EAAE;AAAA,EACZ,EAAE;AAEF,MAAI,WAAW;AACb,UAAM,UAAU,UAAU,QAAQ;AAClC,eAAW,SAAS,OAAO,OAAK,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ,KAAK,OAAO;AAAA,EAC/E;AACA,MAAI,SAAS;AACX,UAAM,QAAQ,QAAQ,QAAQ;AAC9B,eAAW,SAAS,OAAO,OAAK,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ,KAAK,KAAK;AAAA,EAC7E;AAEA,WAAS,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC;AAC/F,SAAO;AACT;;;ACjOA,eAAsB,YAAY,UAAkC,CAAC,GAAkB;AACrF,aAAW;AAEX,UAAQ,IAAI,gCAAyB;AAGrC,QAAMC,UAAS,MAAM,mBAAmB;AAGxC,QAAM,WAAW,MAAM,oBAAoB;AAG3C,QAAM,WAAW,QAAQ,WACrB,SAAS,OAAO,OAAK,EAAE,aAAa,QAAQ,QAAQ,IACpD;AAEJ,MAAI,SAAS,WAAW,GAAG;AACzB,YAAQ,IAAI,qBAAqB;AACjC,YAAQ,IAAI,8DAA8D;AAC1E;AAAA,EACF;AAGA,QAAM,SAAS,oBAAI,IAA6B;AAChD,aAAW,QAAQ,UAAU;AAC3B,UAAM,OAAO,IAAI,KAAK,KAAK,YAAY,EAAE,mBAAmB,SAAS;AAAA,MACnE,SAAS;AAAA,MACT,OAAO;AAAA,MACP,KAAK;AAAA,IACP,CAAC;AACD,QAAI,CAAC,OAAO,IAAI,IAAI,EAAG,QAAO,IAAI,MAAM,CAAC,CAAC;AAC1C,WAAO,IAAI,IAAI,EAAG,KAAK,IAAI;AAAA,EAC7B;AAGA,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ;AAClC,YAAQ,IAAI,KAAK,IAAI,EAAE;AACvB,eAAW,QAAQ,OAAO;AACxB,YAAM,OAAO,IAAI,KAAK,KAAK,YAAY,EAAE,mBAAmB,SAAS;AAAA,QACnE,MAAM;AAAA,QACN,QAAQ;AAAA,MACV,CAAC;AACD,YAAM,SAAS,KAAK,WAAW,SAAS,cAAO;AAC/C,YAAM,OAAO,gBAAgB,KAAK,QAAQ;AAC1C,cAAQ,IAAI,OAAO,IAAI,KAAK,IAAI,IAAI,KAAK,QAAQ,KAAK,MAAM,EAAE;AAAA,IAChE;AAAA,EACF;AAEA,UAAQ,IAAI;AAAA;AAAA,CAAsD;AACpE;AAEA,SAAS,gBAAgB,UAA0B;AACjD,QAAM,QAAgC;AAAA,IACpC,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AACA,SAAO,MAAM,QAAQ,KAAK;AAC5B;;;ACpEA,SAAoB,WAAXC,gBAA0B;AAEnC,SAAS,cAAc;AACvB,SAAoB,WAAXA,gBAA4B;;;ACMrC,IAAM,aAAa;AACnB,IAAM,eAAe,KAAK,KAAK,KAAK;AAOpC,IAAI,cAAmC;AAOvC,SAASC,gBAAe,UAA4B;AAClD,SAAO,2BAA0B,YAAY;AAC/C;AAEA,SAAS,YAAoB;AAC3B,SAAO,KAAK,QAAQ,IAAI,GAAG,UAAU;AACvC;AAEA,SAAS,aAAaC,QAA8B;AAClD,QAAM,gBAAgB,IAAI,KAAKA,OAAM,SAAS,EAAE,QAAQ;AACxD,MAAI,OAAO,MAAM,aAAa,GAAG;AAC/B,mBAAO,KAAK,yDAAyD;AAAA,MACnE,WAAWA,OAAM;AAAA,IACnB,CAAC;AACD,WAAO;AAAA,EACT;AACA,QAAM,MAAM,KAAK,IAAI,IAAI;AACzB,SAAO,MAAM;AACf;AAEA,eAAe,gBAA8C;AAC3D,MAAI;AACF,UAAM,MAAM,MAAM,aAAa,UAAU,CAAC;AAC1C,UAAMA,SAAQ,KAAK,MAAM,GAAG;AAC5B,QAAIA,OAAM,YAAYA,OAAM,aAAa,aAAaA,MAAK,GAAG;AAC5D,aAAOA;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,eAAeA,QAAoC;AAChE,MAAI;AAEF,QAAI,CAACA,UAAS,OAAOA,WAAU,YAAY,CAACA,OAAM,YAAY,CAACA,OAAM,WAAW;AAC9E,qBAAO,KAAK,yCAAyC;AACrD;AAAA,IACF;AAEA,UAAM,YAA0B;AAAA,MAC9B,UAAU,OAAOA,OAAM,aAAa,WAAW,EAAE,GAAGA,OAAM,SAAS,IAAI,CAAC;AAAA,MACxE,WAAW,OAAOA,OAAM,SAAS;AAAA,IACnC;AAEA,eAAW,CAAC,UAAU,SAAS,KAAK,OAAO,QAAQ,UAAU,QAAQ,GAAG;AACtE,UAAI,OAAO,aAAa,YAAY,OAAO,cAAc,YACrD,cAAc,KAAK,QAAQ,KAAK,cAAc,KAAK,SAAS,GAAG;AACjE,uBAAO,KAAK,6DAA6D;AACzE;AAAA,MACF;AAAA,IACF;AACA,UAAM,oBAAoB,QAAQ,UAAU,CAAC;AAC7C,QAAI,CAAC,kBAAkB,WAAW,QAAQ,QAAQ,IAAI,CAAC,IAAI,GAAG,GAAG;AAC/D,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,UAAM,cAAc,mBAAmB,KAAK,UAAU,WAAW,MAAM,CAAC,CAAC;AAAA,EAC3E,SAAS,KAAK;AACZ,mBAAO,KAAK,sCAAsC,EAAE,OAAO,IAAI,CAAC;AAAA,EAClE;AACF;AAEA,eAAe,gBAAiD;AAC9D,QAAM,SAAS,IAAI,cAAc;AACjC,QAAM,WAA0B,MAAM,OAAO,aAAa;AAE1D,QAAM,UAAkC,CAAC;AACzC,aAAW,QAAQ,UAAU;AAC3B,QAAI,KAAK,UAAU;AACjB,cAAQ,KAAK,QAAQ,IAAI,KAAK;AAAA,IAChC;AAAA,EACF;AAEA,QAAMA,SAAsB;AAAA,IAC1B,UAAU;AAAA,IACV,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACA,gBAAcA;AACd,QAAM,eAAeA,MAAK;AAE1B,iBAAO,KAAK,mCAAmC;AAAA,IAC7C,WAAW,OAAO,KAAK,OAAO;AAAA,EAChC,CAAC;AACD,SAAO;AACT;AAEA,eAAe,iBAAkD;AAE/D,MAAI,eAAe,aAAa,WAAW,GAAG;AAC5C,WAAO,YAAY;AAAA,EACrB;AAGA,QAAM,YAAY,MAAM,cAAc;AACtC,MAAI,WAAW;AACb,kBAAc;AACd,WAAO,UAAU;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,MAAM,cAAc;AAAA,EAC7B,SAAS,KAAK;AACZ,mBAAO,MAAM,yCAAyC,EAAE,OAAO,IAAI,CAAC;AACpE,WAAO,CAAC;AAAA,EACV;AACF;AAcA,eAAsB,aACpB,UACwB;AACxB,QAAM,WAAW,MAAM,eAAe;AACtC,QAAM,eAAeD,gBAAe,QAAQ;AAC5C,SAAO,SAAS,YAAY,KAAK;AACnC;;;AC5IA,IAAME,gBAAe,IAAI,KAAK;AAC9B,IAAM,QAAQ,oBAAI,IAA+C;AAEjE,SAAS,UAAa,KAA4B;AAChD,QAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,MAAI,SAAS,MAAM,SAAS,KAAK,IAAI,EAAG,QAAO,MAAM;AACrD,QAAM,OAAO,GAAG;AAChB,SAAO;AACT;AAEA,SAAS,SAAS,KAAa,MAAe,MAAMA,eAAoB;AACtE,QAAM,IAAI,KAAK,EAAE,MAAM,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC;AACnD;AAEO,SAAS,eAAuB;AACrC,QAAM,SAAS,OAAO;AAEtB,SAAO,IAAIC,SAAU,EAAE,UAAU,KAAK,KAAK,KAAM,KAAK,IAAI,CAAC,CAAC;AAG5D,SAAO,IAAI,sBAAsB,OAAO,KAAK,QAAQ;AACnD,UAAM,QAAQ,MAAM,gBAAgB;AACpC,QAAI,KAAK,EAAE,OAAO,OAAO,MAAM,OAAO,CAAC;AAAA,EACzC,CAAC;AAGD,SAAO,IAAI,aAAa,OAAO,KAAK,QAAQ;AAC1C,UAAM,CAAC,aAAa,gBAAgB,aAAa,IAAI,MAAM,QAAQ,WAAW;AAAA,MAC5E,gBAAgB;AAAA,OACf,YAAY;AACX,cAAM,SAAS,UAAyB,UAAU;AAClD,YAAI,OAAQ,QAAO;AACnB,cAAM,SAAS,IAAI,cAAc;AACjC,cAAMC,YAAW,MAAM,OAAO,aAAa;AAC3C,iBAAS,YAAYA,SAAQ;AAC7B,eAAOA;AAAA,MACT,GAAG;AAAA,OACF,YAAY;AACX,cAAM,SAAS,UAA8B,SAAS;AACtD,YAAI,WAAW,OAAW,QAAO;AACjC,cAAM,SAAS,IAAI,cAAc;AACjC,cAAM,WAAW,MAAM,OAAO,aAAa;AAC3C,cAAMC,WAAU,SAAS,CAAC,KAAK;AAC/B,iBAAS,WAAWA,QAAO;AAC3B,eAAOA;AAAA,MACT,GAAG;AAAA,IACL,CAAC;AAED,UAAM,QAAQ,YAAY,WAAW,cAAc,YAAY,QAAQ,CAAC;AACxE,UAAM,WAAW,eAAe,WAAW,cAAc,eAAe,QAAQ,CAAC;AACjF,UAAM,UAAU,cAAc,WAAW,cAAc,cAAc,QAAQ;AAE7E,QAAI,KAAK,EAAE,OAAO,OAAO,MAAM,QAAQ,UAAU,QAAQ,CAAC;AAAA,EAC5D,CAAC;AAGD,SAAO,IAAI,kBAAkB,OAAO,KAAK,QAAQ;AAC/C,UAAM,OAAO,MAAM,QAAQ,IAAI,OAAO,EAAE;AACxC,QAAI,CAAC,KAAM,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAClE,QAAI,KAAK,IAAI;AAAA,EACf,CAAC;AAGD,SAAO,KAAK,0BAA0B,OAAO,KAAK,QAAQ;AACxD,QAAI;AACF,YAAM,OAAO,MAAM,QAAQ,IAAI,OAAO,EAAE;AACxC,UAAI,CAAC,KAAM,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAGlE,YAAM,eAAe,wBAAwB,KAAK,SAAS,QAAQ;AAGnE,YAAM,OAAO,MAAM,aAAa,YAAY;AAC5C,UAAI,CAAC,KAAM,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,+DAA+D,CAAC;AAGhH,YAAM,WAAW,iBAAiB,YAAY;AAC9C,YAAM,YAAY,KAAK,SAAS,aAAa,MAAM,aAAa,QAAQ;AACxE,UAAI,CAAC,UAAW,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iCAAiC,YAAY,GAAG,CAAC;AAGtG,YAAM,SAAS,IAAI,cAAc;AACjC,UAAI;AACJ,YAAM,qBAAqB,KAAK,aAAa,KAAK,SAAS;AAC3D,UAAI,oBAAoB;AACtB,cAAM,cAAc,MAAM,WAAW,kBAAkB;AACvD,YAAI,aAAa;AACf,cAAI,CAAC,KAAK,aAAa,KAAK,SAAS,iBAAiB;AACpD,2BAAO,KAAK,mCAAmC,OAAO,KAAK,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,KAAK,OAAO,KAAK,SAAS,eAAe,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AAAA,UAC1J;AACA,gBAAM,SAAS,MAAM,OAAO,YAAY,kBAAkB;AAC1D,uBAAa,CAAC,EAAE,MAAM,OAAO,MAAM,KAAK,OAAO,IAAI,CAAC;AAAA,QACtD;AAAA,MACF;AAGA,YAAM,WAAW,iBAAiB;AAClC,YAAM,iBAAiB,WAAW;AAAA,QAChC,eAAe;AAAA,QACf,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,2BAA2B;AAAA,QAC3B,uBAAuB;AAAA,MACzB,IAAI;AAEJ,YAAM,cAAc,MAAM,mBAAmB;AAC7C,YAAM,WAAW,MAAM,OAAO,WAAW;AAAA,QACvC,SAAS,KAAK;AAAA,QACd,WAAW,CAAC,EAAE,UAAU,cAAc,UAAU,CAAC;AAAA,QACjD,cAAc;AAAA,QACd,UAAU,YAAY;AAAA,QACtB;AAAA,QACA,sBAAsB,KAAK,SAAS;AAAA,QACpC;AAAA,MACF,CAAC;AAGD,YAAM,YAAY,IAAI,OAAO,IAAI;AAAA,QAC/B,YAAY,SAAS;AAAA,QACrB,cAAc;AAAA,QACd,cAAc;AAAA,QACd;AAAA,MACF,CAAC;AAED,UAAI,KAAK,EAAE,SAAS,MAAM,cAAc,MAAM,YAAY,SAAS,IAAI,CAAC;AAAA,IAC1E,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,qBAAO,MAAM,sBAAsB,OAAO,IAAI,OAAO,EAAE,EAAE,QAAQ,WAAW,EAAE,CAAC,KAAK,OAAO,GAAG,EAAE,QAAQ,WAAW,EAAE,CAAC,EAAE;AACxH,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,IACrC;AAAA,EACF,CAAC;AAGD,SAAO,KAAK,yBAAyB,OAAO,KAAK,QAAQ;AACvD,QAAI;AACF,YAAM,WAAW,IAAI,OAAO,EAAE;AAC9B,UAAI,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,IAC5B,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,IACrC;AAAA,EACF,CAAC;AAGD,SAAO,IAAI,kBAAkB,OAAO,KAAK,QAAQ;AAC/C,QAAI;AACF,YAAM,EAAE,aAAa,SAAS,IAAI,IAAI;AACtC,YAAM,UAAU,MAAM,WAAW,IAAI,OAAO,IAAI,EAAE,aAAa,SAAS,CAAC;AACzE,UAAI,CAAC,QAAS,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AACrE,UAAI,KAAK,OAAO;AAAA,IAClB,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,IACrC;AAAA,EACF,CAAC;AAGD,SAAO,IAAI,iBAAiB,OAAO,KAAK,QAAQ;AAC9C,QAAI;AACF,YAAM,WAAW,MAAM,oBAAoB;AAC3C,UAAI,KAAK,EAAE,OAAO,SAAS,CAAC;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,IACrC;AAAA,EACF,CAAC;AAGD,SAAO,IAAI,qCAAqC,OAAO,KAAK,QAAQ;AAClE,QAAI;AACF,YAAM,aAAa,wBAAwB,IAAI,OAAO,QAAQ;AAC9D,YAAM,OAAO,MAAM,aAAa,UAAU;AAC1C,UAAI,KAAK,EAAE,UAAU,YAAY,UAAU,KAAK,CAAC;AAAA,IACnD,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,IACrC;AAAA,EACF,CAAC;AAGD,SAAO,IAAI,iBAAiB,OAAO,KAAK,QAAQ;AAC9C,QAAI;AACF,YAAM,SAAS,UAAyB,UAAU;AAClD,UAAI,OAAQ,QAAO,IAAI,KAAK,EAAE,UAAU,OAAO,CAAC;AAEhD,YAAM,SAAS,IAAI,cAAc;AACjC,YAAM,WAAW,MAAM,OAAO,aAAa;AAC3C,eAAS,YAAY,QAAQ;AAC7B,UAAI,KAAK,EAAE,SAAS,CAAC;AAAA,IACvB,SAAS,KAAK;AACZ,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,OAAO,eAAe,QAAQ,IAAI,UAAU,2BAA2B,CAAC;AAAA,IAC/G;AAAA,EACF,CAAC;AAGD,SAAO,IAAI,gBAAgB,OAAO,KAAK,QAAQ;AAC7C,QAAI;AACF,YAAM,SAAS,UAA8B,SAAS;AACtD,UAAI,WAAW,OAAW,QAAO,IAAI,KAAK,EAAE,SAAS,OAAO,CAAC;AAE7D,YAAM,SAAS,IAAI,cAAc;AACjC,YAAM,WAAW,MAAM,OAAO,aAAa;AAC3C,YAAM,UAAU,SAAS,CAAC,KAAK;AAC/B,eAAS,WAAW,OAAO;AAC3B,UAAI,KAAK,EAAE,QAAQ,CAAC;AAAA,IACtB,SAAS,KAAK;AACZ,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,MAAM,OAAO,eAAe,QAAQ,IAAI,UAAU,0BAA0B,CAAC;AAAA,IAC/G;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AC1NA,IAAMC,aAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAMxD,eAAsB,kBAAkB,UAA+B,CAAC,GAGrE;AACD,QAAM,MAAMC,SAAQ;AACpB,QAAM,OAAO,QAAQ,QAAQ;AAG7B,MAAI,IAAIA,SAAQ,KAAK,CAAC;AAGtB,MAAI,IAAI,aAAa,CAAC;AAGtB,QAAM,MAAM,UAAU;AACtB,QAAM,WAAW,KAAK,IAAI,YAAY,eAAe;AACrD,QAAM,eAAe,KAAK,IAAI,YAAY,WAAW;AACrD,MAAI,IAAI,gBAAgBA,SAAQ,OAAO,QAAQ,CAAC;AAChD,MAAI,IAAI,oBAAoBA,SAAQ,OAAO,YAAY,CAAC;AAGxD,QAAM,YAAY,KAAKD,YAAW,QAAQ;AAC1C,MAAI,IAAIC,SAAQ,OAAO,SAAS,CAAC;AAGjC,MAAI,IAAI,aAAa,CAAC,KAAK,QAAQ;AACjC,QAAI,CAAC,IAAI,KAAK,WAAW,OAAO,KAAK,CAAC,IAAI,KAAK,WAAW,SAAS,GAAG;AACpE,UAAI,SAAS,KAAK,WAAW,YAAY,CAAC;AAAA,IAC5C;AAAA,EACF,CAAC;AAGD,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,UAAM,UAAU,CAAC,GAAW,aAAqB;AAC/C,YAAM,SAAS,IAAI,OAAO,GAAG,aAAa,MAAM;AAC9C,uBAAO,KAAK,6CAA6C,CAAC,EAAE;AAG5D,cAAM,cAAc,oBAAI,IAA0B;AAClD,eAAO,GAAG,cAAc,CAAC,SAAS;AAChC,sBAAY,IAAI,IAAI;AACpB,eAAK,GAAG,SAAS,MAAM,YAAY,OAAO,IAAI,CAAC;AAAA,QACjD,CAAC;AAED,QAAAA,SAAQ;AAAA,UACN,MAAM;AAAA,UACN,OAAO,MAAM,IAAI,QAAc,CAAC,QAAQ;AACtC,gBAAI,OAAO;AAEX,kBAAM,SAAS,MAAM;AACnB,kBAAI,KAAM;AACV,qBAAO;AACP,kBAAI;AAAA,YACN;AAEA,uBAAW,QAAQ,YAAa,MAAK,QAAQ;AAE7C,kBAAM,UAAU,WAAW,MAAM;AAC/B,6BAAO,KAAK,gEAAgE;AAC5E,qBAAO;AAAA,YACT,GAAG,GAAI;AAGP,oBAAQ,MAAM;AAEd,mBAAO,MAAM,MAAM;AACjB,2BAAa,OAAO;AACpB,qBAAO;AAAA,YACT,CAAC;AAAA,UACH,CAAC;AAAA,QACH,CAAC;AAAA,MACH,CAAC;AAED,aAAO,GAAG,SAAS,CAAC,QAA+B;AACjD,YAAI,IAAI,SAAS,gBAAgB,WAAW,GAAG;AAC7C,yBAAO,KAAK,QAAQ,CAAC,mBAAmB,IAAI,CAAC,KAAK;AAClD,kBAAQ,IAAI,GAAG,WAAW,CAAC;AAAA,QAC7B,OAAO;AACL,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH;AAEA,YAAQ,MAAM,CAAC;AAAA,EACjB,CAAC;AACH;;;ACnFA,IAAM,MAAM,KAAK,MAAM,iBAAiB,KAAK,YAAY,GAAG,cAAc,CAAC,CAAC;AAE5E,IAAM,SAAS;AAAA;AAAA,qBAEC,IAAI,QAAQ,OAAO,EAAE,CAAC;AAAA;AAAA;AAItC,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,SAAS,EACd,YAAY,uGAAuG,EACnH,QAAQ,IAAI,SAAS,eAAe;AAIvC,QACG,QAAQ,MAAM,EACd,YAAY,sFAAiF,EAC7F,OAAO,YAAY;AAClB,QAAM,QAAQ;AACd,UAAQ,KAAK,CAAC;AAChB,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,uDAAuD,EACnE,OAAO,mBAAmB,+BAA+B,MAAM,EAC/D,OAAO,OAAO,SAAS;AACtB,aAAW;AACX,QAAM,aAAa,OAAO,SAAS,KAAK,MAAM,EAAE;AAChD,MAAI,OAAO,MAAM,UAAU,KAAK,aAAa,KAAK,aAAa,OAAO;AACpE,YAAQ,MAAM,+DAA+D;AAC7E,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpE,QAAM,QAAQ,oBAAoB,IAAI,EAAE;AACxC,UAAQ,IAAI;AAAA,yCAA4C,IAAI,EAAE;AAC9D,UAAQ,IAAI,yBAAyB;AAErC,QAAM,WAAW,YAAY;AAC3B,YAAQ,IAAI,oBAAoB;AAEhC,QAAI,QAAQ,aAAa,WAAW,QAAQ,MAAM,YAAY;AAC5D,cAAQ,MAAM,WAAW,KAAK;AAAA,IAChC;AACA,UAAM,MAAM;AACZ,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAG9B,MAAI,QAAQ,aAAa,SAAS;AAChC,YAAQ,MAAM,OAAO;AACrB,YAAQ,MAAM,aAAa,IAAI;AAC/B,YAAQ,MAAM,GAAG,QAAQ,CAAC,SAAS;AAEjC,UAAI,KAAK,CAAC,MAAM,EAAM,MAAK,SAAS;AAAA,IACtC,CAAC;AAAA,EACH;AACF,CAAC;AAEH,QACG,QAAQ,UAAU,EAClB,YAAY,oDAAoD,EAChE,OAAO,qBAAqB,oEAAoE,EAChG,OAAO,OAAO,SAAS;AACtB,QAAM,YAAY,EAAE,UAAU,KAAK,SAAS,CAAC;AAC7C,UAAQ,KAAK,CAAC;AAChB,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,0CAA0C,EACtD,OAAO,YAAY;AAClB,QAAM,UAAU;AAClB,CAAC;AAKH,IAAM,aAAa,QAChB,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC,EACtC,SAAS,gBAAgB,kDAAkD,EAC3E,OAAO,sBAAsB,gEAAgE,EAC7F,OAAO,uBAAuB,+DAA+D,EAC7F,OAAO,sBAAsB,8CAA8C,EAC3E,OAAO,mBAAmB,0DAA0D,EACpF,OAAO,UAAU,+CAA+C,EAChE,OAAO,kBAAkB,mDAAmD,EAC5E,OAAO,YAAY,4BAA4B,EAC/C,OAAO,wBAAwB,4BAA4B,EAC3D,OAAO,eAAe,wBAAwB,EAC9C,OAAO,qBAAqB,6BAA6B,EACzD,OAAO,eAAe,mCAAmC,EACzD,OAAO,iBAAiB,iCAAiC,EACzD,OAAO,uBAAuB,gDAAgD,EAC9E,OAAO,wBAAwB,0CAA0C,EACzE,OAAO,0BAA0B,gDAAgD,EACjF,OAAO,iBAAiB,iBAAiB,EACzC,OAAO,YAAY,kCAAkC,EACrD,OAAO,OAAO,cAAkC;AAC/C,QAAM,OAAO,WAAW,KAAK;AAG7B,MAAI,KAAK,QAAQ;AACf,UAAM,UAAU;AAChB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAoB,KAAK,QAAQ,CAAC,CAAC;AAEzC,QAAM,aAAyB;AAAA,IAC7B,UAAU,KAAK;AAAA,IACf,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,KAAK,KAAK;AAAA,IACV,gBAAgB,KAAK;AAAA,IACrB,QAAQ,KAAK;AAAA,IACb,aAAa,KAAK;AAAA,IAClB,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,eAAe,KAAK;AAAA,IACpB,YAAY,KAAK;AAAA,IACjB,eAAe,KAAK;AAAA,EACtB;AAEA,iBAAO,KAAK,MAAM;AAClB,aAAW,UAAU;AACrB,MAAI,KAAK,QAAS,YAAW;AAC7B,uBAAqB;AAErB,QAAMC,UAAS,UAAU;AACzB,iBAAO,KAAK,iBAAiBA,QAAO,YAAY,EAAE;AAClD,iBAAO,KAAK,iBAAiBA,QAAO,UAAU,EAAE;AAGhD,MAAI,WAAW;AACb,UAAM,eAAe,QAAQ,SAAS;AACtC,mBAAO,KAAK,4BAA4B,YAAY,EAAE;AACtD,UAAM,iBAAiB,YAAY;AACnC,mBAAO,KAAK,OAAO;AACnB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,aAAa;AACjB,MAAI,oBAAoB;AACxB,QAAM,QAAkB,CAAC;AAEzB,iBAAe,eAA8B;AAC3C,QAAI,cAAc,MAAM,WAAW,EAAG;AACtC,iBAAa;AACb,QAAI;AACF,aAAO,MAAM,SAAS,GAAG;AACvB,cAAM,KAAK,MAAM,MAAM;AACvB,uBAAO,KAAK,qBAAqB,EAAE,EAAE;AACrC,cAAM,iBAAiB,EAAE;AACzB,YAAI,UAAU;AACZ,yBAAO,KAAK,6CAA6C;AACzD,gBAAM,SAAS;AACf;AAAA,QACF;AACA,YAAI,kBAAmB;AAAA,MACzB;AAAA,IACF,UAAE;AACA,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,iBAAe,WAA0B;AACvC,QAAI,kBAAmB;AACvB,wBAAoB;AACpB,mBAAO,KAAK,kBAAkB;AAC9B,YAAQ,KAAK;AACb,WAAO,WAAY,OAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAG,CAAC;AAC5D,mBAAO,KAAK,UAAU;AACtB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,UAAU,MAAM,SAAS,CAAC;AACrC,UAAQ,GAAG,WAAW,MAAM,SAAS,CAAC;AAEtC,UAAQ,GAAG,aAAa,CAAC,aAAqB;AAC5C,UAAM,KAAK,QAAQ;AACnB,mBAAO,KAAK,iBAAiB,QAAQ,mBAAmB,MAAM,MAAM,GAAG;AACvE,iBAAa,EAAE,MAAM,SAAO,eAAO,MAAM,2BAA2B,GAAG,CAAC;AAAA,EAC1E,CAAC;AACD,UAAQ,MAAM;AAEd,MAAI,UAAU;AACZ,mBAAO,KAAK,oEAAoE;AAAA,EAClF,OAAO;AACL,mBAAO,KAAK,gDAAgD;AAAA,EAC9D;AACF,CAAC;AAEH,QAAQ,MAAM;","names":["join","resolve","dirname","fileURLToPath","dirname","fileURLToPath","resolve","join","existsSync","default","existsSync","envPath","config","resolve","existsSync","require","config","existsSync","resolve","config","resolve","resolve","default","config","config","default","config","text","config","config","config","default","DEFAULT_MODEL","MAX_TOOL_ROUNDS","config","default","resolve","config","createRequire","createRequire","resolve","default","ffmpegPath","resolve","default","ffmpegPath","ffprobePath","resolve","default","yFrom","yTo","xFrom","xTo","ffmpegPath","resolve","config","SYSTEM_PROMPT","Platform","SYSTEM_PROMPT","config","buildSystemPrompt","config","toYouTubeTimestamp","fmtTime","buildTranscriptBlock","config","resolve","ffmpegPath","FONTS_DIR","resolve","SYSTEM_PROMPT","getVideoDuration","totalDuration","config","raw","parsed","require","config","resolve","envPath","ffprobe","config","config","default","toLatePlatform","cache","CACHE_TTL_MS","default","accounts","profile","__dirname","default","resolve","config"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/paths.ts","../src/core/fileSystem.ts","../src/core/env.ts","../src/config/environment.ts","../src/core/logger.ts","../src/config/logger.ts","../src/core/ffmpeg.ts","../src/tools/captions/captionGenerator.ts","../src/core/process.ts","../src/core/media.ts","../src/tools/ffmpeg/faceDetection.ts","../src/tools/ffmpeg/audioExtraction.ts","../src/core/ai.ts","../src/config/brand.ts","../src/config/pricing.ts","../src/services/costTracker.ts","../src/tools/whisper/whisperClient.ts","../src/services/transcription.ts","../src/providers/CopilotProvider.ts","../src/providers/imageUtils.ts","../src/providers/OpenAIProvider.ts","../src/providers/ClaudeProvider.ts","../src/providers/index.ts","../src/config/modelConfig.ts","../src/agents/BaseAgent.ts","../src/tools/ffmpeg/silenceDetection.ts","../src/tools/ffmpeg/singlePassEdit.ts","../src/agents/SilenceRemovalAgent.ts","../src/tools/ffmpeg/captionBurning.ts","../src/tools/ffmpeg/clipExtraction.ts","../src/tools/ffmpeg/aspectRatio.ts","../src/core/text.ts","../src/agents/ShortsAgent.ts","../src/agents/MediumVideoAgent.ts","../src/agents/ChapterAgent.ts","../src/tools/ffmpeg/frameCapture.ts","../src/agents/SummaryAgent.ts","../src/agents/ProducerAgent.ts","../src/tools/gemini/geminiClient.ts","../src/types/index.ts","../src/services/captionGeneration.ts","../src/agents/SocialMediaAgent.ts","../src/agents/BlogAgent.ts","../src/core/cli.ts","../src/index.ts","../src/core/watcher.ts","../src/services/fileWatcher.ts","../src/pipeline.ts","../src/assets/Asset.ts","../src/assets/VideoAsset.ts","../src/assets/loaders.ts","../src/assets/SocialPostAsset.ts","../src/assets/TextAsset.ts","../src/assets/ShortVideoAsset.ts","../src/assets/MediumClipAsset.ts","../src/assets/SummaryAsset.ts","../src/assets/BlogAsset.ts","../src/assets/MainVideoAsset.ts","../src/services/gitOperations.ts","../src/services/queueBuilder.ts","../src/services/platformContentStrategy.ts","../src/services/postStore.ts","../src/commands/doctor.ts","../src/services/lateApi.ts","../src/core/network.ts","../src/services/scheduleConfig.ts","../src/commands/init.ts","../src/config/ffmpegResolver.ts","../src/services/scheduler.ts","../src/commands/schedule.ts","../src/core/http.ts","../src/review/server.ts","../src/review/routes.ts","../src/services/accountMapping.ts"],"sourcesContent":["// Re-export all commonly used path functions\nexport { join, resolve, dirname, basename, extname, parse, sep, relative, normalize } from 'path'\nexport { fileURLToPath } from 'url'\n\n// Also re-export the path module itself for the rare cases where namespace import is needed\nimport pathMod from 'path'\nexport { pathMod }\n\nimport { existsSync } from 'fs'\nimport { join, resolve, dirname, parse } from 'path'\nimport { fileURLToPath } from 'url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\n/**\n * Walk up from `startDir` until a directory containing `package.json` is found.\n * Throws if the filesystem root is reached without finding one.\n */\nexport function findRoot(startDir: string): string {\n let dir = resolve(startDir)\n while (true) {\n if (existsSync(join(dir, 'package.json'))) return dir\n const parent = dirname(dir)\n if (parent === dir) throw new Error(`Could not find project root from ${startDir}`)\n dir = parent\n }\n}\n\nlet _cachedRoot: string | undefined\n\n/** Get the project root directory. */\nexport function projectRoot(): string {\n if (!_cachedRoot) _cachedRoot = findRoot(__dirname)\n return _cachedRoot\n}\n\n/** Get path within the assets directory. */\nexport function assetsDir(...segments: string[]): string {\n return join(projectRoot(), 'assets', ...segments)\n}\n\n/**\n * Resolve the fonts directory — checks bundled (dist/fonts/) first,\n * falls back to dev (assets/fonts/).\n */\nexport function fontsDir(): string {\n const bundled = resolve(projectRoot(), 'dist', 'fonts')\n return existsSync(bundled) ? bundled : assetsDir('fonts')\n}\n\n/**\n * Resolve the models directory — checks bundled (dist/models/) first,\n * falls back to dev (assets/models/).\n */\nexport function modelsDir(): string {\n const bundled = resolve(projectRoot(), 'dist', 'models')\n return existsSync(bundled) ? bundled : assetsDir('models')\n}\n\n/** Get the recordings directory, optionally for a specific slug. */\nexport function recordingsDir(slug?: string): string {\n return slug ? join(projectRoot(), 'recordings', slug) : join(projectRoot(), 'recordings')\n}\n","import {\n promises as fsp,\n existsSync,\n statSync,\n readdirSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n createReadStream,\n createWriteStream,\n closeSync,\n} from 'fs'\nimport type { Stats, Dirent, ReadStream, WriteStream } from 'fs'\nimport tmp from 'tmp'\nimport { join, dirname } from './paths.js'\n\n// Enable graceful cleanup of all tmp resources on process exit\ntmp.setGracefulCleanup()\n\nexport type { Stats, Dirent, ReadStream, WriteStream }\n\n// ── Reads ──────────────────────────────────────────────────────\n\n/** Read and parse a JSON file. Throws descriptive error on ENOENT or parse failure. */\nexport async function readJsonFile<T>(filePath: string, defaultValue?: T): Promise<T> {\n let raw: string\n try {\n raw = await fsp.readFile(filePath, 'utf-8')\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n if (arguments.length >= 2) return defaultValue as T\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n try {\n return JSON.parse(raw) as T\n } catch (err: unknown) {\n throw new Error(`Failed to parse JSON at ${filePath}: ${(err as Error).message}`)\n }\n}\n\n/** Read a text file as UTF-8 string. Throws \"File not found: <path>\" on ENOENT. */\nexport async function readTextFile(filePath: string): Promise<string> {\n try {\n return await fsp.readFile(filePath, 'utf-8')\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** Sync variant of readTextFile. */\nexport function readTextFileSync(filePath: string): string {\n try {\n return readFileSync(filePath, 'utf-8')\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** List directory contents. Throws \"Directory not found: <path>\" on ENOENT. */\nexport async function listDirectory(dirPath: string): Promise<string[]> {\n try {\n return await fsp.readdir(dirPath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dirPath}`)\n }\n throw err\n }\n}\n\n/** List directory with Dirent objects. Throws \"Directory not found: <path>\" on ENOENT. */\nexport async function listDirectoryWithTypes(dirPath: string): Promise<Dirent[]> {\n try {\n return await fsp.readdir(dirPath, { withFileTypes: true })\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dirPath}`)\n }\n throw err\n }\n}\n\n/** Sync variant of listDirectory. */\nexport function listDirectorySync(dirPath: string): string[] {\n try {\n return readdirSync(dirPath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`Directory not found: ${dirPath}`)\n }\n throw err\n }\n}\n\n/** Check if file/dir exists (async, using stat). */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await fsp.stat(filePath)\n return true\n } catch {\n return false\n }\n}\n\n/** Check if file/dir exists (sync). */\nexport function fileExistsSync(filePath: string): boolean {\n return existsSync(filePath)\n}\n\n/** Get file stats. Throws \"File not found: <path>\" on ENOENT. */\nexport async function getFileStats(filePath: string): Promise<Stats> {\n try {\n return await fsp.stat(filePath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** Sync variant. */\nexport function getFileStatsSync(filePath: string): Stats {\n try {\n return statSync(filePath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw err\n }\n}\n\n/** Create a read stream. */\nexport function openReadStream(filePath: string): ReadStream {\n return createReadStream(filePath)\n}\n\n// ── Writes ─────────────────────────────────────────────────────\n\n/** Write data as JSON. Creates parent dirs. */\nexport async function writeJsonFile(filePath: string, data: unknown): Promise<void> {\n await fsp.mkdir(dirname(filePath), { recursive: true })\n await fsp.writeFile(filePath, JSON.stringify(data, null, 2), { encoding: 'utf-8', mode: 0o600 })\n}\n\n/** Write text file. Creates parent dirs. */\nexport async function writeTextFile(filePath: string, content: string): Promise<void> {\n if (typeof content !== 'string') throw new TypeError('content must be a string')\n await fsp.mkdir(dirname(filePath), { recursive: true })\n await fsp.writeFile(filePath, content, { encoding: 'utf-8', mode: 0o600 })\n}\n\n/** Sync variant of writeTextFile. */\nexport function writeTextFileSync(filePath: string, content: string): void {\n if (typeof content !== 'string') throw new TypeError('content must be a string')\n mkdirSync(dirname(filePath), { recursive: true })\n writeFileSync(filePath, content, { encoding: 'utf-8', mode: 0o600 })\n}\n\n/** Ensure directory exists (recursive). */\nexport async function ensureDirectory(dirPath: string): Promise<void> {\n await fsp.mkdir(dirPath, { recursive: true })\n}\n\n/** Sync variant. */\nexport function ensureDirectorySync(dirPath: string): void {\n mkdirSync(dirPath, { recursive: true })\n}\n\n/** Copy file. Ensures destination parent dir exists. */\nexport async function copyFile(src: string, dest: string): Promise<void> {\n await fsp.mkdir(dirname(dest), { recursive: true })\n await fsp.copyFile(src, dest)\n}\n\n/** Move/rename file. Falls back to copy+delete on EXDEV. */\nexport async function moveFile(src: string, dest: string): Promise<void> {\n await fsp.mkdir(dirname(dest), { recursive: true })\n try {\n await fsp.rename(src, dest)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'EXDEV') {\n await copyFile(src, dest)\n await removeFile(src)\n return\n }\n throw err\n }\n}\n\n/** Remove file (ignores ENOENT). */\nexport async function removeFile(filePath: string): Promise<void> {\n try {\n await fsp.unlink(filePath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return\n throw err\n }\n}\n\n/** Remove directory. */\nexport async function removeDirectory(\n dirPath: string,\n opts?: { recursive?: boolean; force?: boolean },\n): Promise<void> {\n try {\n await fsp.rm(dirPath, { recursive: opts?.recursive ?? false, force: opts?.force ?? false })\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return\n throw err\n }\n}\n\n/** Create a write stream. */\nexport function openWriteStream(filePath: string): WriteStream {\n return createWriteStream(filePath)\n}\n\n/** Close a file descriptor (sync). */\nexport function closeFileDescriptor(fd: number): void {\n closeSync(fd)\n}\n\n// ── Temp Dir ───────────────────────────────────────────────────\n\n/** Create a temporary directory with the given prefix. Caller is responsible for cleanup. */\nexport async function makeTempDir(prefix: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // mode 0o700 ensures only the owner can access the directory (secure)\n tmp.dir({ prefix, mode: 0o700 }, (err, path) => {\n if (err) reject(err)\n else resolve(path)\n })\n })\n}\n\n/** Run fn inside a temp directory, auto-cleanup on completion or error. */\nexport async function withTempDir<T>(prefix: string, fn: (tempDir: string) => Promise<T>): Promise<T> {\n const tempDir = await makeTempDir(prefix)\n try {\n return await fn(tempDir)\n } finally {\n await removeDirectory(tempDir, { recursive: true, force: true })\n }\n}\n\n/** Rename/move a file or directory (fs.rename). Falls back to copy+delete on EXDEV. */\nexport async function renameFile(oldPath: string, newPath: string): Promise<void> {\n try {\n await fsp.rename(oldPath, newPath)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException)?.code === 'EXDEV') {\n await copyFile(oldPath, newPath)\n await removeFile(oldPath)\n } else {\n throw err\n }\n }\n}\n\n/** Copy directory recursively (fs.cp). */\nexport async function copyDirectory(src: string, dest: string): Promise<void> {\n await fsp.cp(src, dest, { recursive: true })\n}\n\n/** Write file with raw options (flag, mode, etc.) for security-sensitive writes. */\nexport async function writeFileRaw(\n filePath: string,\n data: string,\n opts: { encoding?: BufferEncoding; flag?: string; mode?: number },\n): Promise<void> {\n await fsp.writeFile(filePath, data, opts)\n}\n\n// ── Specialized ────────────────────────────────────────────────\n\n/** List .ttf and .otf font files in a directory. Throws if dir missing. */\nexport async function listFontFiles(fontsDir: string): Promise<string[]> {\n const entries = await listDirectory(fontsDir)\n return entries.filter((f) => /\\.(ttf|otf)$/i.test(f))\n}\n\n/** Copy all .ttf/.otf fonts from fontsDir to destDir. */\nexport async function copyFontsToDir(fontsDir: string, destDir: string): Promise<void> {\n const fonts = await listFontFiles(fontsDir)\n await ensureDirectory(destDir)\n await Promise.all(fonts.map((f) => copyFile(join(fontsDir, f), join(destDir, f))))\n}\n\n// ── Third-party re-exports ─────────────────────────────────────\n\nexport { default as tmp } from 'tmp'\n","import dotenv from 'dotenv'\n\n/** Load environment variables from a .env file. */\nexport function loadEnvFile(envPath?: string): void {\n dotenv.config(envPath ? { path: envPath } : undefined)\n}\n","import { join } from '../core/paths.js'\nimport { fileExistsSync } from '../core/fileSystem.js'\nimport { loadEnvFile } from '../core/env.js'\n\n// Load .env file from repo root\nconst envPath = join(process.cwd(), '.env')\nif (fileExistsSync(envPath)) {\n loadEnvFile(envPath)\n}\n\nexport interface AppEnvironment {\n OPENAI_API_KEY: string\n WATCH_FOLDER: string\n REPO_ROOT: string\n FFMPEG_PATH: string\n FFPROBE_PATH: string\n EXA_API_KEY: string\n EXA_MCP_URL: string\n LLM_PROVIDER: string\n LLM_MODEL: string\n ANTHROPIC_API_KEY: string\n OUTPUT_DIR: string\n BRAND_PATH: string\n VERBOSE: boolean\n SKIP_GIT: boolean\n SKIP_SILENCE_REMOVAL: boolean\n SKIP_SHORTS: boolean\n SKIP_MEDIUM_CLIPS: boolean\n SKIP_SOCIAL: boolean\n SKIP_CAPTIONS: boolean\n LATE_API_KEY: string\n LATE_PROFILE_ID: string\n SKIP_SOCIAL_PUBLISH: boolean\n GEMINI_API_KEY: string\n}\n\nexport interface CLIOptions {\n watchDir?: string\n outputDir?: string\n openaiKey?: string\n exaKey?: string\n brand?: string\n verbose?: boolean\n git?: boolean\n silenceRemoval?: boolean\n shorts?: boolean\n mediumClips?: boolean\n social?: boolean\n captions?: boolean\n socialPublish?: boolean\n lateApiKey?: string\n lateProfileId?: string\n}\n\nlet config: AppEnvironment | null = null\n\nexport function validateRequiredKeys(): void {\n if (!config?.OPENAI_API_KEY && !process.env.OPENAI_API_KEY) {\n throw new Error('Missing required: OPENAI_API_KEY (set via --openai-key or env var)')\n }\n}\n\n/** Merge CLI options → env vars → defaults. Call before getConfig(). */\nexport function initConfig(cli: CLIOptions = {}): AppEnvironment {\n const repoRoot = process.env.REPO_ROOT || process.cwd()\n\n config = {\n OPENAI_API_KEY: cli.openaiKey || process.env.OPENAI_API_KEY || '',\n WATCH_FOLDER: cli.watchDir || process.env.WATCH_FOLDER || join(repoRoot, 'watch'),\n REPO_ROOT: repoRoot,\n FFMPEG_PATH: process.env.FFMPEG_PATH || 'ffmpeg', // legacy; prefer ffmpegResolver\n FFPROBE_PATH: process.env.FFPROBE_PATH || 'ffprobe', // legacy; prefer ffmpegResolver\n EXA_API_KEY: cli.exaKey || process.env.EXA_API_KEY || '',\n EXA_MCP_URL: process.env.EXA_MCP_URL || 'https://mcp.exa.ai/mcp',\n LLM_PROVIDER: process.env.LLM_PROVIDER || 'copilot',\n LLM_MODEL: process.env.LLM_MODEL || '',\n ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || '',\n OUTPUT_DIR:cli.outputDir || process.env.OUTPUT_DIR || join(repoRoot, 'recordings'),\n BRAND_PATH: cli.brand || process.env.BRAND_PATH || join(repoRoot, 'brand.json'),\n VERBOSE: cli.verbose ?? false,\n SKIP_GIT: cli.git === false,\n SKIP_SILENCE_REMOVAL: cli.silenceRemoval === false,\n SKIP_SHORTS: cli.shorts === false,\n SKIP_MEDIUM_CLIPS: cli.mediumClips === false,\n SKIP_SOCIAL: cli.social === false,\n SKIP_CAPTIONS: cli.captions === false,\n LATE_API_KEY: cli.lateApiKey || process.env.LATE_API_KEY || '',\n LATE_PROFILE_ID: cli.lateProfileId || process.env.LATE_PROFILE_ID || '',\n SKIP_SOCIAL_PUBLISH: cli.socialPublish === false,\n GEMINI_API_KEY: process.env.GEMINI_API_KEY || '',\n }\n\n return config\n}\n\nexport function getConfig(): AppEnvironment {\n if (config) {\n return config\n }\n\n // Fallback: init with no CLI options (pure env-var mode)\n return initConfig()\n}\n","import winston from 'winston'\n\n/**\n * Sanitize user input for logging to prevent log injection attacks.\n * Removes or escapes newlines, carriage returns, and other control characters.\n */\nexport function sanitizeForLog(value: unknown): string {\n if (value === null || value === undefined) return String(value)\n const str = String(value)\n return str.replace(/[\\r\\n\\t]/g, (c) => {\n switch (c) {\n case '\\r': return '\\\\r'\n case '\\n': return '\\\\n'\n case '\\t': return '\\\\t'\n default: return c\n }\n })\n}\n\nconst logger = winston.createLogger({\n level: 'info',\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.printf(({ timestamp, level, message }) => {\n return `${timestamp} [${level.toUpperCase()}]: ${message}`\n })\n ),\n transports: [new winston.transports.Console()],\n})\n\nexport function setVerbose(): void {\n logger.level = 'debug'\n}\n\nexport default logger\n","// Re-export from core — this file exists for backward compatibility during migration\nexport { default, sanitizeForLog, setVerbose } from '../core/logger.js'\n","import ffmpegLib from 'fluent-ffmpeg'\nimport { createRequire } from 'module'\nimport { existsSync } from 'fs'\nimport logger from './logger.js'\nimport { getConfig } from '../config/environment.js'\n\nconst require = createRequire(import.meta.url)\n\n/** Get the resolved path to the FFmpeg binary. */\nexport function getFFmpegPath(): string {\n const config = getConfig();\n if (config.FFMPEG_PATH && config.FFMPEG_PATH !== 'ffmpeg') {\n logger.debug(`FFmpeg: using FFMPEG_PATH config: ${config.FFMPEG_PATH}`);\n return config.FFMPEG_PATH;\n }\n try {\n const staticPath = require('ffmpeg-static') as string;\n if (staticPath && existsSync(staticPath)) {\n logger.debug(`FFmpeg: using ffmpeg-static: ${staticPath}`);\n return staticPath;\n }\n } catch { /* ffmpeg-static not available */ }\n logger.debug('FFmpeg: falling back to system PATH');\n return 'ffmpeg';\n}\n\n/** Get the resolved path to the FFprobe binary. */\nexport function getFFprobePath(): string {\n const config = getConfig();\n if (config.FFPROBE_PATH && config.FFPROBE_PATH !== 'ffprobe') {\n logger.debug(`FFprobe: using FFPROBE_PATH config: ${config.FFPROBE_PATH}`);\n return config.FFPROBE_PATH;\n }\n try {\n const { path: probePath } = require('@ffprobe-installer/ffprobe') as { path: string };\n if (probePath && existsSync(probePath)) {\n logger.debug(`FFprobe: using @ffprobe-installer/ffprobe: ${probePath}`);\n return probePath;\n }\n } catch { /* @ffprobe-installer/ffprobe not available */ }\n logger.debug('FFprobe: falling back to system PATH');\n return 'ffprobe';\n}\n\n/** Create a pre-configured fluent-ffmpeg instance. */\nexport function createFFmpeg(input?: string): ffmpegLib.FfmpegCommand {\n const cmd = input ? ffmpegLib(input) : ffmpegLib()\n cmd.setFfmpegPath(getFFmpegPath())\n cmd.setFfprobePath(getFFprobePath())\n return cmd\n}\n\n/** Promisified ffprobe — get media file metadata. */\nexport function ffprobe(filePath: string): Promise<ffmpegLib.FfprobeData> {\n return new Promise((resolve, reject) => {\n ffmpegLib.setFfprobePath(getFFprobePath())\n ffmpegLib.ffprobe(filePath, (err, data) => {\n if (err) reject(err)\n else resolve(data)\n })\n })\n}\n\n// Re-export fluent-ffmpeg for cases where direct access is needed\nexport { ffmpegLib as fluent }\nexport type { FfmpegCommand, FfprobeData } from 'fluent-ffmpeg'\n","/**\n * Caption generator for the Advanced SubStation Alpha (ASS) subtitle format.\n *\n * ### Why ASS instead of SRT/VTT?\n * ASS supports inline style overrides — font size, color, and animation per\n * character/word — which enables the \"active word pop\" karaoke effect used\n * in modern short-form video (TikTok, Reels). SRT and VTT only support\n * plain text or basic HTML tags with no per-word timing control.\n *\n * ### Karaoke word highlighting approach\n * Instead of ASS's native `\\k` karaoke tags (which highlight left-to-right\n * within a line), we generate **one Dialogue line per word-state**. Each line\n * renders the entire caption group but with the currently-spoken word in a\n * different color and size. Contiguous end/start timestamps between\n * word-states prevent flicker. This gives us full control over the visual\n * treatment (color, font-size, scale animations) without the limitations\n * of the `\\k` tag.\n *\n * @module captionGenerator\n */\n\nimport { Transcript, Segment, Word, CaptionStyle } from '../../types'\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Pad a number to a fixed width with leading zeros. */\nfunction pad(n: number, width: number): string {\n return String(n).padStart(width, '0')\n}\n\n/** Convert seconds → SRT timestamp \"HH:MM:SS,mmm\" */\nfunction toSRT(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n const ms = Math.round((seconds - Math.floor(seconds)) * 1000)\n return `${pad(h, 2)}:${pad(m, 2)}:${pad(s, 2)},${pad(ms, 3)}`\n}\n\n/** Convert seconds → VTT timestamp \"HH:MM:SS.mmm\" */\nfunction toVTT(seconds: number): string {\n return toSRT(seconds).replace(',', '.')\n}\n\n/** Convert seconds → ASS timestamp \"H:MM:SS.cc\" */\nfunction toASS(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n const cs = Math.round((seconds - Math.floor(seconds)) * 100)\n return `${h}:${pad(m, 2)}:${pad(s, 2)}.${pad(cs, 2)}`\n}\n\n// ---------------------------------------------------------------------------\n// Premium caption constants\n// ---------------------------------------------------------------------------\n\n/** Silence gap threshold in seconds – gaps longer than this split caption groups. */\nconst SILENCE_GAP_THRESHOLD = 0.4\n/** Maximum words displayed simultaneously in a caption group. */\nconst MAX_WORDS_PER_GROUP = 5\n/** Target words per display line within a group (splits into 2 lines above this). */\nconst WORDS_PER_LINE = 3\n/** ASS BGR color for the active (currently-spoken) word – yellow. */\nconst ACTIVE_COLOR = '\\\\c&H00FFFF&'\n/** ASS BGR color for inactive words – white. */\nconst BASE_COLOR = '\\\\c&HFFFFFF&'\n/** Font size for the active word. */\nconst ACTIVE_FONT_SIZE = 72\n/** Font size for inactive words (matches style default). */\nconst BASE_FONT_SIZE = 58\n\n// ---------------------------------------------------------------------------\n// Medium caption constants (smaller, bottom-positioned for longer content)\n// ---------------------------------------------------------------------------\n\n/** Font size for the active word in medium style. */\nconst MEDIUM_ACTIVE_FONT_SIZE = 54\n/** Font size for inactive words in medium style. */\nconst MEDIUM_BASE_FONT_SIZE = 44\n\n// ---------------------------------------------------------------------------\n// Portrait caption constants (Opus Clips style)\n// ---------------------------------------------------------------------------\n\n/** Font size for the active word in portrait style. */\nconst PORTRAIT_ACTIVE_FONT_SIZE = 144\n/** Font size for inactive words in portrait style. */\nconst PORTRAIT_BASE_FONT_SIZE = 120\n/** ASS BGR color for the active word in portrait style – green. */\nconst PORTRAIT_ACTIVE_COLOR = '\\\\c&H00FF00&'\n/** ASS BGR color for inactive words in portrait style – white. */\nconst PORTRAIT_BASE_COLOR = '\\\\c&HFFFFFF&'\n\n// ---------------------------------------------------------------------------\n// SRT (segment-level)\n// ---------------------------------------------------------------------------\n\nexport function generateSRT(transcript: Transcript): string {\n return transcript.segments\n .map((seg: Segment, i: number) => {\n const idx = i + 1\n const start = toSRT(seg.start)\n const end = toSRT(seg.end)\n const text = seg.text.trim()\n return `${idx}\\n${start} --> ${end}\\n${text}`\n })\n .join('\\n\\n')\n .concat('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// VTT (segment-level)\n// ---------------------------------------------------------------------------\n\nexport function generateVTT(transcript: Transcript): string {\n const cues = transcript.segments\n .map((seg: Segment) => {\n const start = toVTT(seg.start)\n const end = toVTT(seg.end)\n const text = seg.text.trim()\n return `${start} --> ${end}\\n${text}`\n })\n .join('\\n\\n')\n\n return `WEBVTT\\n\\n${cues}\\n`\n}\n\n// ---------------------------------------------------------------------------\n// ASS – Premium active-word-pop captions\n// ---------------------------------------------------------------------------\n\n/**\n * ASS header for landscape (16:9, 1920×1080) captions.\n *\n * ### Style fields explained (comma-separated in the Style line):\n * - `Fontname: Montserrat` — bundled with the project; FFmpeg's `ass` filter\n * uses `fontsdir=.` so libass finds the .ttf files next to the .ass file.\n * - `Fontsize: 58` — base size for inactive words\n * - `PrimaryColour: &H00FFFFFF` — white (ASS uses `&HAABBGGRR` — alpha, blue, green, red)\n * - `OutlineColour: &H00000000` — black outline for readability on any background\n * - `BackColour: &H80000000` — 50% transparent black shadow\n * - `Bold: 1` — bold for better readability at small sizes\n * - `BorderStyle: 1` — outline + drop shadow (not opaque box)\n * - `Outline: 3` — 3px outline thickness\n * - `Shadow: 1` — 1px drop shadow\n * - `Alignment: 2` — bottom-center (SSA alignment: 1=left, 2=center, 3=right;\n * add 4 for top, 8 for middle — so 2 = bottom-center)\n * - `MarginV: 40` — 40px above the bottom edge\n * - `WrapStyle: 0` — smart word wrap\n */\nconst ASS_HEADER = `[Script Info]\nTitle: Auto-generated captions\nScriptType: v4.00+\nPlayResX: 1920\nPlayResY: 1080\nWrapStyle: 0\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,Montserrat,58,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,20,20,40,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`\n\n/**\n * ASS header for portrait (9:16, 1080×1920) captions — used for shorts.\n *\n * Key differences from the landscape header:\n * - `PlayResX/Y: 1080×1920` — matches portrait video dimensions\n * - `Fontsize: 120` — larger base font for vertical video viewing (small screens)\n * - `MarginV: 770` — pushes captions toward lower-center of the frame (above\n * bottom dead zones: TikTok=320px, Reels=310px, Shorts=300px)\n * - Hook `MarginV: 250` — below all platform top dead zones (TikTok=108px,\n * Instagram=210px, YouTube=120px)\n * - Includes a `Hook` style: semi-transparent pill/badge background\n * (`BorderStyle: 3` = opaque box) for the opening hook text overlay\n */\nconst ASS_HEADER_PORTRAIT = `[Script Info]\nTitle: Auto-generated captions\nScriptType: v4.00+\nPlayResX: 1080\nPlayResY: 1920\nWrapStyle: 0\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,Montserrat,120,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,1,2,30,30,770,1\nStyle: Hook,Montserrat,56,&H00333333,&H00333333,&H60D0D0D0,&H60E0E0E0,1,0,0,0,100,100,2,0,3,18,2,8,80,80,250,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`\n\n/**\n * ASS header for medium-style captions (1920×1080 but smaller font).\n *\n * Used for longer clips where large captions would be distracting.\n * - `Fontsize: 44` — smaller than the shorts style\n * - `Alignment: 2` — bottom-center\n * - `MarginV: 60` — slightly higher from the bottom edge to avoid UI overlaps\n */\nconst ASS_HEADER_MEDIUM = `[Script Info]\nTitle: Auto-generated captions\nScriptType: v4.00+\nPlayResX: 1920\nPlayResY: 1080\nWrapStyle: 0\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,Montserrat,44,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,2,1,2,20,20,60,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`\n\n/**\n * Group words into caption groups split on silence gaps and max word count.\n * All words within a group are displayed simultaneously; captions disappear\n * entirely during gaps longer than SILENCE_GAP_THRESHOLD.\n */\nfunction groupWordsBySpeech(words: Word[]): Word[][] {\n if (words.length === 0) return []\n\n const groups: Word[][] = []\n let current: Word[] = []\n\n for (let i = 0; i < words.length; i++) {\n current.push(words[i])\n\n const isLast = i === words.length - 1\n const hasGap =\n !isLast && words[i + 1].start - words[i].end > SILENCE_GAP_THRESHOLD\n const atMax = current.length >= MAX_WORDS_PER_GROUP\n\n if (isLast || hasGap || atMax) {\n groups.push(current)\n current = []\n }\n }\n\n return groups\n}\n\n/**\n * Split a caption group into 1–2 display lines.\n * Groups with ≤ WORDS_PER_LINE words stay on one line; larger groups\n * are split at the midpoint into two lines joined with \\\\N.\n */\nfunction splitGroupIntoLines(group: Word[]): Word[][] {\n if (group.length <= WORDS_PER_LINE) return [group]\n const mid = Math.ceil(group.length / 2)\n return [group.slice(0, mid), group.slice(mid)]\n}\n\n/**\n * Build premium ASS dialogue lines with active-word highlighting.\n * Generates one Dialogue line per word-state: the full caption group is\n * rendered with the currently-spoken word in yellow at a larger size while\n * all other words stay white at the base size. Contiguous end/start times\n * between word-states prevent flicker.\n */\nfunction buildPremiumDialogueLines(words: Word[], style: CaptionStyle = 'shorts'): string[] {\n const activeFontSize = style === 'portrait' ? PORTRAIT_ACTIVE_FONT_SIZE\n : style === 'medium' ? MEDIUM_ACTIVE_FONT_SIZE : ACTIVE_FONT_SIZE\n const baseFontSize = style === 'portrait' ? PORTRAIT_BASE_FONT_SIZE\n : style === 'medium' ? MEDIUM_BASE_FONT_SIZE : BASE_FONT_SIZE\n const groups = groupWordsBySpeech(words)\n const dialogues: string[] = []\n\n for (const group of groups) {\n const displayLines = splitGroupIntoLines(group)\n\n for (let activeIdx = 0; activeIdx < group.length; activeIdx++) {\n const activeWord = group[activeIdx]\n\n // Contiguous timing: end = next word's start, or this word's own end\n // BUT cap the gap — don't stretch across pauses > 0.3s\n const endTime =\n activeIdx < group.length - 1\n ? Math.min(group[activeIdx + 1].start, activeWord.end + 0.3)\n : activeWord.end\n\n // Render all words across display lines with the active word highlighted\n const renderedLines: string[] = []\n let globalIdx = 0\n\n for (const line of displayLines) {\n const rendered = line.map((w) => {\n const idx = globalIdx++\n const text = w.word.trim()\n if (idx === activeIdx) {\n if (style === 'portrait') {\n // Opus Clips style: green color + scale pop animation\n return `{${PORTRAIT_ACTIVE_COLOR}\\\\fs${activeFontSize}\\\\fscx130\\\\fscy130\\\\t(0,150,\\\\fscx100\\\\fscy100)}${text}`\n }\n return `{${ACTIVE_COLOR}\\\\fs${activeFontSize}}${text}`\n }\n if (style === 'portrait') {\n return `{${PORTRAIT_BASE_COLOR}\\\\fs${baseFontSize}}${text}`\n }\n return `{${BASE_COLOR}\\\\fs${baseFontSize}}${text}`\n })\n renderedLines.push(rendered.join(' '))\n }\n\n const text = renderedLines.join('\\\\N')\n dialogues.push(\n `Dialogue: 0,${toASS(activeWord.start)},${toASS(endTime)},Default,,0,0,0,,${text}`,\n )\n }\n }\n\n return dialogues\n}\n\n/**\n * Generate premium ASS captions with active-word-pop highlighting.\n *\n * ### How it works\n * Words are grouped by speech bursts (split on silence gaps > 0.8s or after\n * 5 words). Within each group, one Dialogue line is emitted per word — the\n * full group is shown each time, but the \"active\" word gets a different color\n * and larger font size. This creates a karaoke-style bounce effect.\n *\n * @param transcript - Full transcript with word-level timestamps\n * @param style - Visual style: 'shorts' (large centered), 'medium' (small bottom),\n * or 'portrait' (Opus Clips style with green highlight + scale animation)\n * @returns Complete ASS file content (header + dialogue lines)\n */\nexport function generateStyledASS(transcript: Transcript, style: CaptionStyle = 'shorts'): string {\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\n const allWords = transcript.words\n if (allWords.length === 0) return header\n\n return header + buildPremiumDialogueLines(allWords, style).join('\\n') + '\\n'\n}\n\n/**\n * Generate premium ASS captions for a single contiguous segment.\n * Filters words within [startTime, endTime] (plus buffer), adjusts timestamps\n * relative to the clip's buffered start so they align with the extracted video.\n */\nexport function generateStyledASSForSegment(\n transcript: Transcript,\n startTime: number,\n endTime: number,\n buffer: number = 1.0,\n style: CaptionStyle = 'shorts',\n): string {\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\n const bufferedStart = Math.max(0, startTime - buffer)\n const bufferedEnd = endTime + buffer\n\n const words = transcript.words.filter(\n (w) => w.start >= bufferedStart && w.end <= bufferedEnd,\n )\n if (words.length === 0) return header\n\n const adjusted: Word[] = words.map((w) => ({\n word: w.word,\n start: w.start - bufferedStart,\n end: w.end - bufferedStart,\n }))\n\n return header + buildPremiumDialogueLines(adjusted, style).join('\\n') + '\\n'\n}\n\n/**\n * Generate premium ASS captions for a composite clip made of multiple segments.\n * Each segment's words are extracted and remapped to the concatenated timeline,\n * accounting for the buffer added during clip extraction.\n */\nexport function generateStyledASSForComposite(\n transcript: Transcript,\n segments: { start: number; end: number }[],\n buffer: number = 1.0,\n style: CaptionStyle = 'shorts',\n): string {\n const header = style === 'portrait' ? ASS_HEADER_PORTRAIT : style === 'medium' ? ASS_HEADER_MEDIUM : ASS_HEADER\n const allAdjusted: Word[] = []\n let runningOffset = 0\n\n for (const seg of segments) {\n const bufferedStart = Math.max(0, seg.start - buffer)\n const bufferedEnd = seg.end + buffer\n const segDuration = bufferedEnd - bufferedStart\n\n const words = transcript.words.filter(\n (w) => w.start >= bufferedStart && w.end <= bufferedEnd,\n )\n\n for (const w of words) {\n allAdjusted.push({\n word: w.word,\n start: w.start - bufferedStart + runningOffset,\n end: w.end - bufferedStart + runningOffset,\n })\n }\n\n runningOffset += segDuration\n }\n\n if (allAdjusted.length === 0) return header\n\n return header + buildPremiumDialogueLines(allAdjusted, style).join('\\n') + '\\n'\n}\n\n// ---------------------------------------------------------------------------\n// Hook text overlay for portrait shorts\n// ---------------------------------------------------------------------------\n\n/** Maximum characters for hook text before truncation. */\nconst HOOK_TEXT_MAX_LENGTH = 60\n\n/**\n * Generate ASS dialogue lines for a hook text overlay at the top of the video.\n *\n * The hook is a short attention-grabbing phrase (e.g. \"Here's why you should\n * learn TypeScript\") displayed as a translucent pill/badge at the top of a\n * portrait video for the first few seconds.\n *\n * Uses the `Hook` style defined in {@link ASS_HEADER_PORTRAIT} which has\n * `BorderStyle: 3` (opaque box background) and `Alignment: 8` (top-center).\n *\n * The `\\fad(300,500)` tag creates a 300ms fade-in and 500ms fade-out so the\n * hook doesn't appear/disappear abruptly.\n *\n * @param hookText - The attention-grabbing phrase (truncated to 60 chars)\n * @param displayDuration - How long to show the hook in seconds (default: 4s)\n * @param _style - Caption style (currently only 'portrait' uses hooks)\n * @returns A single ASS Dialogue line to append to the Events section\n */\nexport function generateHookOverlay(\n hookText: string,\n displayDuration: number = 4.0,\n _style: CaptionStyle = 'portrait',\n): string {\n const text =\n hookText.length > HOOK_TEXT_MAX_LENGTH\n ? hookText.slice(0, HOOK_TEXT_MAX_LENGTH - 3) + '...'\n : hookText\n\n return `Dialogue: 1,${toASS(0)},${toASS(displayDuration)},Hook,,0,0,0,,{\\\\fad(300,500)}${text}`\n}\n\n/**\n * Generate a complete portrait ASS file with captions AND hook text overlay.\n */\nexport function generatePortraitASSWithHook(\n transcript: Transcript,\n hookText: string,\n startTime: number,\n endTime: number,\n buffer?: number,\n): string {\n const baseASS = generateStyledASSForSegment(transcript, startTime, endTime, buffer, 'portrait')\n const hookLine = generateHookOverlay(hookText, 4.0, 'portrait')\n return baseASS + hookLine + '\\n'\n}\n\n/**\n * Generate a complete portrait ASS file for a composite clip with captions AND hook text overlay.\n */\nexport function generatePortraitASSWithHookComposite(\n transcript: Transcript,\n segments: { start: number; end: number }[],\n hookText: string,\n buffer?: number,\n): string {\n const baseASS = generateStyledASSForComposite(transcript, segments, buffer, 'portrait')\n const hookLine = generateHookOverlay(hookText, 4.0, 'portrait')\n return baseASS + hookLine + '\\n'\n}\n","import { execFile as nodeExecFile, execSync as nodeExecSync, spawnSync as nodeSpawnSync } from 'child_process'\nimport type { ExecFileOptions, SpawnSyncReturns, SpawnSyncOptions } from 'child_process'\nimport { createRequire } from 'module'\n\nexport type { ExecFileOptions }\n\nexport interface ExecResult {\n stdout: string\n stderr: string\n}\n\n/**\n * Execute a command asynchronously via execFile.\n * Returns promise of { stdout, stderr }.\n */\nexport function execCommand(\n cmd: string,\n args: string[],\n opts?: ExecFileOptions & { maxBuffer?: number },\n): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n nodeExecFile(cmd, args, { ...opts, encoding: 'utf-8' } as any, (error, stdout, stderr) => {\n if (error) {\n reject(Object.assign(error, { stdout: String(stdout ?? ''), stderr: String(stderr ?? '') }))\n } else {\n resolve({ stdout: String(stdout ?? ''), stderr: String(stderr ?? '') })\n }\n })\n })\n}\n\n/**\n * Execute a command with a callback (for cases where consumers need the raw callback pattern).\n * This matches the execFile signature used by captionBurning, singlePassEdit, etc.\n */\nexport function execFileRaw(\n cmd: string,\n args: string[],\n opts: ExecFileOptions & { maxBuffer?: number },\n callback: (error: Error | null, stdout: string, stderr: string) => void,\n): void {\n nodeExecFile(cmd, args, { ...opts, encoding: 'utf-8' } as any, (error, stdout, stderr) => {\n callback(error, String(stdout ?? ''), String(stderr ?? ''))\n })\n}\n\n/**\n * Execute a command synchronously. Returns trimmed stdout.\n * Throws on failure.\n */\nexport function execCommandSync(cmd: string, opts?: { encoding?: BufferEncoding; stdio?: any; cwd?: string }): string {\n return nodeExecSync(cmd, { encoding: 'utf-8' as BufferEncoding, ...opts }).toString().trim()\n}\n\n/**\n * Spawn a command synchronously. Returns full result including status.\n */\nexport function spawnCommand(\n cmd: string,\n args: string[],\n opts?: SpawnSyncOptions,\n): SpawnSyncReturns<string> {\n return nodeSpawnSync(cmd, args, { encoding: 'utf-8', ...opts }) as SpawnSyncReturns<string>\n}\n\n/**\n * Create a require function for ESM modules to use CommonJS require().\n * Usage: const require = createModuleRequire(import.meta.url)\n */\nexport function createModuleRequire(metaUrl: string): NodeRequire {\n return createRequire(metaUrl)\n}\n","export { default as sharp } from 'sharp'\nexport type { Sharp, Metadata as SharpMetadata } from 'sharp'\nexport * as ort from 'onnxruntime-node'\n","import { execFileRaw } from '../../core/process.js'\nimport { fileExistsSync, listDirectory, removeFile, removeDirectory, makeTempDir } from '../../core/fileSystem.js'\nimport { join, modelsDir } from '../../core/paths.js'\nimport { sharp, ort } from '../../core/media.js'\nimport { getFFmpegPath, getFFprobePath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nconst ffmpegPath = getFFmpegPath()\nconst ffprobePath = getFFprobePath()\n\nconst MODEL_PATH = join(modelsDir(), 'ultraface-320.onnx')\n\n/** Cached ONNX session — loaded once, reused across calls. */\nlet cachedSession: ort.InferenceSession | null = null\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Bounding box and metadata for a detected webcam overlay in a screen recording.\n *\n * @property x - Left edge in pixels (original video resolution)\n * @property y - Top edge in pixels (original video resolution)\n * @property width - Width of the webcam region in pixels\n * @property height - Height of the webcam region in pixels\n * @property position - Which corner of the frame the webcam occupies\n * @property confidence - Detection confidence 0–1 (combines skin-tone consistency\n * across frames with per-frame score strength)\n */\nexport interface WebcamRegion {\n x: number\n y: number\n width: number\n height: number\n position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n confidence: number\n}\n\n// ── Constants ────────────────────────────────────────────────────────────────\n\n/** Number of frames sampled evenly across the video for analysis. */\nconst SAMPLE_FRAMES = 5\n/** UltraFace model input dimensions. */\nconst MODEL_WIDTH = 320\nconst MODEL_HEIGHT = 240\n/** Minimum face detection confidence from the ONNX model. */\nconst MIN_FACE_CONFIDENCE = 0.5\n/** Minimum confidence across frames to accept a webcam detection. */\nconst MIN_DETECTION_CONFIDENCE = 0.3\n\n// ── Refinement constants ─────────────────────────────────────────────────────\n\n/**\n * Minimum inter-column/row mean difference to accept as a valid overlay edge.\n * The webcam overlay border creates a sharp intensity step between the\n * overlay and the screen content behind it. Values below this threshold\n * are treated as noise or soft gradients.\n */\nconst REFINE_MIN_EDGE_DIFF = 3.0\n/** Webcam must be at least 5% of the frame in each dimension. */\nconst REFINE_MIN_SIZE_FRAC = 0.05\n/** Webcam must be at most 55% of the frame in each dimension. */\nconst REFINE_MAX_SIZE_FRAC = 0.55\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function getVideoDuration(videoPath: string): Promise<number> {\n return new Promise((resolve, reject) => {\n execFileRaw(\n ffprobePath,\n ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', videoPath],\n {},\n (error, stdout) => {\n if (error) {\n reject(new Error(`ffprobe failed: ${error.message}`))\n return\n }\n resolve(parseFloat(stdout.trim()))\n },\n )\n })\n}\n\nexport async function getVideoResolution(videoPath: string): Promise<{ width: number; height: number }> {\n return new Promise((resolve, reject) => {\n execFileRaw(\n ffprobePath,\n [\n '-v', 'error',\n '-select_streams', 'v:0',\n '-show_entries', 'stream=width,height',\n '-of', 'csv=p=0',\n videoPath,\n ],\n {},\n (error, stdout) => {\n if (error) {\n reject(new Error(`ffprobe failed: ${error.message}`))\n return\n }\n const [w, h] = stdout.trim().split(',').map(Number)\n resolve({ width: w, height: h })\n },\n )\n })\n}\n\nasync function extractSampleFrames(videoPath: string, tempDir: string): Promise<string[]> {\n const duration = await getVideoDuration(videoPath)\n const interval = Math.max(1, Math.floor(duration / (SAMPLE_FRAMES + 1)))\n\n const timestamps: number[] = []\n for (let i = 1; i <= SAMPLE_FRAMES; i++) {\n timestamps.push(i * interval)\n }\n\n const framePaths: string[] = []\n for (let i = 0; i < timestamps.length; i++) {\n const framePath = join(tempDir, `frame_${i}.png`)\n framePaths.push(framePath)\n\n await new Promise<void>((resolve, reject) => {\n execFileRaw(\n ffmpegPath,\n [\n '-y',\n '-ss', timestamps[i].toFixed(2),\n '-i', videoPath,\n '-vf', `scale=${MODEL_WIDTH}:${MODEL_HEIGHT}`,\n '-frames:v', '1',\n '-q:v', '2',\n framePath,\n ],\n { maxBuffer: 10 * 1024 * 1024 },\n (error) => {\n if (error) {\n reject(new Error(`Frame extraction failed at ${timestamps[i]}s: ${error.message}`))\n return\n }\n resolve()\n },\n )\n })\n }\n\n return framePaths\n}\n\n// ── ONNX Face Detection ─────────────────────────────────────────────────────\n\ninterface FaceBox {\n x1: number\n y1: number\n x2: number\n y2: number\n confidence: number\n}\n\nasync function getSession(): Promise<ort.InferenceSession> {\n if (cachedSession) return cachedSession\n if (!fileExistsSync(MODEL_PATH)) {\n throw new Error(`Face detection model not found at ${MODEL_PATH}. Run 'vidpipe doctor' to check dependencies.`)\n }\n cachedSession = await ort.InferenceSession.create(MODEL_PATH, {\n executionProviders: ['cpu'],\n graphOptimizationLevel: 'all',\n })\n return cachedSession\n}\n\n/**\n * Run UltraFace ONNX model on a frame image. Returns face bounding boxes\n * in normalized coordinates (0-1).\n */\nasync function detectFacesInFrame(framePath: string): Promise<FaceBox[]> {\n const session = await getSession()\n\n // Load and preprocess: resize to 320×240, convert to float32 NCHW, normalize to [0,1]\n const { data, info } = await sharp(framePath)\n .resize(MODEL_WIDTH, MODEL_HEIGHT, { fit: 'fill' })\n .removeAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true })\n\n const pixels = info.width * info.height\n const floatData = new Float32Array(3 * pixels)\n\n // HWC RGB → NCHW (channel-first), normalize 0-1 with ImageNet mean/std\n const mean = [127, 127, 127]\n const std = 128\n for (let i = 0; i < pixels; i++) {\n floatData[i] = (data[i * 3] - mean[0]) / std // R\n floatData[pixels + i] = (data[i * 3 + 1] - mean[1]) / std // G\n floatData[2 * pixels + i] = (data[i * 3 + 2] - mean[2]) / std // B\n }\n\n const inputTensor = new ort.Tensor('float32', floatData, [1, 3, MODEL_HEIGHT, MODEL_WIDTH])\n const results = await session.run({ input: inputTensor })\n\n const scores = results['scores'].data as Float32Array // [1, N, 2]\n const boxes = results['boxes'].data as Float32Array // [1, N, 4]\n const numDetections = scores.length / 2\n\n const faces: FaceBox[] = []\n for (let i = 0; i < numDetections; i++) {\n const faceScore = scores[i * 2 + 1] // index 1 = face class\n if (faceScore > MIN_FACE_CONFIDENCE) {\n faces.push({\n x1: boxes[i * 4],\n y1: boxes[i * 4 + 1],\n x2: boxes[i * 4 + 2],\n y2: boxes[i * 4 + 3],\n confidence: faceScore,\n })\n }\n }\n\n return faces\n}\n\n/**\n * Determine which corner a face box belongs to. Returns null if the face\n * is in the center of the frame (not a webcam overlay).\n */\nfunction classifyCorner(\n box: FaceBox,\n): WebcamRegion['position'] | null {\n const cx = (box.x1 + box.x2) / 2\n const cy = (box.y1 + box.y2) / 2\n\n // Face center must be in the outer 40% of the frame to be a corner webcam\n const isLeft = cx < 0.4\n const isRight = cx > 0.6\n const isTop = cy < 0.4\n const isBottom = cy > 0.6\n\n if (isTop && isLeft) return 'top-left'\n if (isTop && isRight) return 'top-right'\n if (isBottom && isLeft) return 'bottom-left'\n if (isBottom && isRight) return 'bottom-right'\n return null // center face — likely full-frame webcam, not an overlay\n}\n\n// ── Refinement helpers ───────────────────────────────────────────────────────\n\n/**\n * Compute per-column mean grayscale intensity over a horizontal band of rows.\n *\n * Used to find the **vertical edge** of the webcam overlay. Each column gets\n * a single mean brightness value averaged over `yFrom..yTo` rows. The\n * resulting 1-D signal has a sharp step at the overlay boundary, which\n * {@link findPeakDiff} locates.\n *\n * @param data - Raw pixel buffer (RGB or RGBA interleaved)\n * @param width - Image width in pixels\n * @param channels - Bytes per pixel (3 for RGB, 4 for RGBA)\n * @param yFrom - First row (inclusive)\n * @param yTo - Last row (exclusive)\n * @returns Float64Array of length `width` with per-column mean grayscale\n */\nfunction columnMeansForRows(\n data: Buffer, width: number, channels: number,\n yFrom: number, yTo: number,\n): Float64Array {\n const means = new Float64Array(width)\n const count = yTo - yFrom\n if (count <= 0) return means\n for (let x = 0; x < width; x++) {\n let sum = 0\n for (let y = yFrom; y < yTo; y++) {\n const idx = (y * width + x) * channels\n sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3\n }\n means[x] = sum / count\n }\n return means\n}\n\n/**\n * Compute per-row mean grayscale intensity over a vertical band of columns.\n *\n * Used to find the **horizontal edge** of the webcam overlay. Each row gets\n * a single mean brightness value averaged over `xFrom..xTo` columns. Works\n * the same way as {@link columnMeansForRows} but rotated 90°.\n *\n * @param data - Raw pixel buffer\n * @param width - Image width in pixels\n * @param channels - Bytes per pixel\n * @param height - Image height in pixels\n * @param xFrom - First column (inclusive)\n * @param xTo - Last column (exclusive)\n * @returns Float64Array of length `height` with per-row mean grayscale\n */\nfunction rowMeansForCols(\n data: Buffer, width: number, channels: number, height: number,\n xFrom: number, xTo: number,\n): Float64Array {\n const means = new Float64Array(height)\n const count = xTo - xFrom\n if (count <= 0) return means\n for (let y = 0; y < height; y++) {\n let sum = 0\n for (let x = xFrom; x < xTo; x++) {\n const idx = (y * width + x) * channels\n sum += (data[idx] + data[idx + 1] + data[idx + 2]) / 3\n }\n means[y] = sum / count\n }\n return means\n}\n\n/** Element-wise average of Float64Arrays. */\nfunction averageFloat64Arrays(arrays: Float64Array[]): Float64Array {\n if (arrays.length === 0) return new Float64Array(0)\n const len = arrays[0].length\n const result = new Float64Array(len)\n for (const arr of arrays) {\n for (let i = 0; i < len; i++) result[i] += arr[i]\n }\n for (let i = 0; i < len; i++) result[i] /= arrays.length\n return result\n}\n\n/**\n * Find the position with the largest intensity step between adjacent elements.\n *\n * \"Peak difference\" = the index where `|means[i+1] - means[i]|` is maximized\n * within the search range. This corresponds to the webcam overlay's edge,\n * because the overlay border creates a hard brightness transition that\n * persists across all frames, while content-based edges average out.\n *\n * @param means - 1-D array of averaged intensities (from column or row means)\n * @param searchFrom - Start of search range (inclusive)\n * @param searchTo - End of search range (inclusive)\n * @param minDiff - Minimum step magnitude to accept (rejects noise)\n * @returns `{index, magnitude}` — index of the edge, or -1 if no edge exceeds minDiff\n */\nexport function findPeakDiff(\n means: Float64Array, searchFrom: number, searchTo: number, minDiff: number,\n): { index: number; magnitude: number } {\n const lo = Math.max(0, Math.min(searchFrom, searchTo))\n const hi = Math.min(means.length - 1, Math.max(searchFrom, searchTo))\n let maxDiff = 0\n let maxIdx = -1\n for (let i = lo; i < hi; i++) {\n const d = Math.abs(means[i + 1] - means[i])\n if (d > maxDiff) { maxDiff = d; maxIdx = i }\n }\n return maxDiff >= minDiff ? { index: maxIdx, magnitude: maxDiff } : { index: -1, magnitude: maxDiff }\n}\n\n/**\n * Refine the webcam bounding box by detecting the overlay's spatial edges.\n *\n * ### Why refinement is needed\n * The coarse phase ({@link detectWebcamRegion}'s corner analysis) only identifies\n * which corner contains a webcam — it uses a fixed 25% region and doesn't know\n * the overlay's exact boundaries. Refinement finds pixel-accurate edges.\n *\n * ### Edge detection algorithm\n * 1. For each sample frame, compute **per-column** and **per-row** mean grayscale\n * intensities (restricted to the webcam's half of the frame for stronger signal).\n * 2. **Average across all frames** — the overlay border is spatially fixed and\n * produces a consistent intensity step, while changing video content (slides,\n * code, etc.) averages out to a smooth gradient. This is the key insight that\n * makes the approach work without traditional edge detection filters.\n * 3. Use {@link findPeakDiff} to locate the maximum inter-adjacent intensity\n * difference in the averaged signal — this is the overlay's vertical and\n * horizontal edge.\n * 4. Sanity-check: the resulting rectangle must be 5–55% of the frame in each\n * dimension (webcams are never tiny or most of the screen).\n *\n * @param framePaths - Paths to sample frames at analysis resolution (320×180)\n * @param position - Which corner contains the webcam (from coarse phase)\n * @returns Refined bounding box in analysis-resolution coordinates, or null\n * if no strong edges are found or the result is implausibly sized\n */\nexport async function refineBoundingBox(\n framePaths: string[],\n position: WebcamRegion['position'],\n): Promise<{ x: number; y: number; width: number; height: number } | null> {\n if (framePaths.length === 0) return null\n\n const isRight = position.includes('right')\n const isBottom = position.includes('bottom')\n let fw = 0, fh = 0\n\n const colMeansAll: Float64Array[] = []\n const rowMeansAll: Float64Array[] = []\n\n for (const fp of framePaths) {\n const { data, info } = await sharp(fp).raw().toBuffer({ resolveWithObject: true })\n fw = info.width; fh = info.height\n\n // Column means: restrict to rows near the webcam for stronger signal\n const yFrom = isBottom ? Math.floor(fh * 0.35) : 0\n const yTo = isBottom ? fh : Math.ceil(fh * 0.65)\n colMeansAll.push(columnMeansForRows(data, fw, info.channels, yFrom, yTo))\n\n // Row means: restrict to columns near the webcam\n const xFrom = isRight ? Math.floor(fw * 0.35) : 0\n const xTo = isRight ? fw : Math.ceil(fw * 0.65)\n rowMeansAll.push(rowMeansForCols(data, fw, info.channels, fh, xFrom, xTo))\n }\n\n const avgCols = averageFloat64Arrays(colMeansAll)\n const avgRows = averageFloat64Arrays(rowMeansAll)\n\n // Search for the inner edge in the relevant portion of the frame\n const xFrom = isRight ? Math.floor(fw * 0.35) : Math.floor(fw * 0.05)\n const xTo = isRight ? Math.floor(fw * 0.95) : Math.floor(fw * 0.65)\n const xEdge = findPeakDiff(avgCols, xFrom, xTo, REFINE_MIN_EDGE_DIFF)\n\n const yFrom = isBottom ? Math.floor(fh * 0.35) : Math.floor(fh * 0.05)\n const yTo = isBottom ? Math.floor(fh * 0.95) : Math.floor(fh * 0.65)\n const yEdge = findPeakDiff(avgRows, yFrom, yTo, REFINE_MIN_EDGE_DIFF)\n\n if (xEdge.index < 0 || yEdge.index < 0) {\n logger.info(\n `[FaceDetection] Edge refinement: no strong edges ` +\n `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`,\n )\n return null\n }\n\n // Build the refined rectangle\n let x: number, y: number, w: number, h: number\n if (isRight) { x = xEdge.index + 1; w = fw - x }\n else { x = 0; w = xEdge.index }\n if (isBottom) { y = yEdge.index + 1; h = fh - y }\n else { y = 0; h = yEdge.index }\n\n // Sanity: webcam should be 5-55% of frame in each dimension\n if (w < fw * REFINE_MIN_SIZE_FRAC || h < fh * REFINE_MIN_SIZE_FRAC ||\n w > fw * REFINE_MAX_SIZE_FRAC || h > fh * REFINE_MAX_SIZE_FRAC) {\n logger.info(\n `[FaceDetection] Refined bounds implausible ` +\n `(${w}x${h} in ${fw}x${fh}), using coarse bounds`,\n )\n return null\n }\n\n logger.info(\n `[FaceDetection] Refined webcam: (${x},${y}) ${w}x${h} at analysis scale ` +\n `(xDiff=${xEdge.magnitude.toFixed(1)}, yDiff=${yEdge.magnitude.toFixed(1)})`,\n )\n\n return { x, y, width: w, height: h }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Calculate confidence that a corner contains a webcam overlay based on\n * per-frame face detections. Higher consistency across frames = more confident.\n */\nexport function calculateCornerConfidence(scores: number[]): number {\n if (scores.length === 0) return 0\n const nonZeroCount = scores.filter(s => s > 0).length\n const consistency = nonZeroCount / scores.length\n const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length\n return consistency * avgScore\n}\n\n/**\n * Detect a webcam overlay region in a screen recording using the UltraFace\n * ONNX model for face detection.\n *\n * ### Approach\n * 1. Sample 5 frames evenly across the video\n * 2. Run UltraFace face detection on each frame\n * 3. For each detected face, classify which corner it's in\n * 4. The corner with consistent face detections across frames is the webcam\n * 5. Refine the bounding box using edge detection for exact overlay boundaries\n *\n * @param videoPath - Path to the source video file\n * @returns The detected webcam region in original video resolution, or null\n */\nexport async function detectWebcamRegion(videoPath: string): Promise<WebcamRegion | null> {\n const tempDir = await makeTempDir('face-detect-')\n\n try {\n const resolution = await getVideoResolution(videoPath)\n const framePaths = await extractSampleFrames(videoPath, tempDir)\n\n // Track face detections per corner across all frames\n const cornerScores = new Map<WebcamRegion['position'], number[]>()\n const cornerBoxes = new Map<WebcamRegion['position'], FaceBox[]>()\n for (const pos of ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const) {\n cornerScores.set(pos, [])\n cornerBoxes.set(pos, [])\n }\n\n for (const framePath of framePaths) {\n const faces = await detectFacesInFrame(framePath)\n\n // Track which corners got a face this frame\n const foundCorners = new Set<WebcamRegion['position']>()\n\n for (const face of faces) {\n const corner = classifyCorner(face)\n if (corner) {\n foundCorners.add(corner)\n cornerScores.get(corner)!.push(face.confidence)\n cornerBoxes.get(corner)!.push(face)\n }\n }\n\n // Corners without a face this frame get a 0\n for (const pos of ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const) {\n if (!foundCorners.has(pos)) {\n cornerScores.get(pos)!.push(0)\n }\n }\n }\n\n // Find best corner\n let bestPosition: WebcamRegion['position'] | null = null\n let bestConfidence = 0\n\n for (const [pos, scores] of cornerScores) {\n const confidence = calculateCornerConfidence(scores)\n logger.debug(`[FaceDetection] Corner ${pos}: confidence=${confidence.toFixed(3)}, scores=[${scores.map(s => s.toFixed(2)).join(',')}]`)\n if (confidence > bestConfidence) {\n bestConfidence = confidence\n bestPosition = pos\n }\n }\n\n if (!bestPosition || bestConfidence < MIN_DETECTION_CONFIDENCE) {\n logger.info(`[FaceDetection] No webcam region detected (best: ${bestPosition} at ${bestConfidence.toFixed(3)})`)\n return null\n }\n\n // Compute average face bounding box from model detections\n const boxes = cornerBoxes.get(bestPosition)!\n const avgBox: FaceBox = {\n x1: boxes.reduce((s, b) => s + b.x1, 0) / boxes.length,\n y1: boxes.reduce((s, b) => s + b.y1, 0) / boxes.length,\n x2: boxes.reduce((s, b) => s + b.x2, 0) / boxes.length,\n y2: boxes.reduce((s, b) => s + b.y2, 0) / boxes.length,\n confidence: bestConfidence,\n }\n\n // Try edge refinement for pixel-accurate webcam overlay boundaries\n const refined = await refineBoundingBox(framePaths, bestPosition)\n const scaleX = resolution.width / MODEL_WIDTH\n const scaleY = resolution.height / MODEL_HEIGHT\n\n let origX: number, origY: number, origW: number, origH: number\n\n if (refined) {\n origX = Math.round(refined.x * scaleX)\n origY = Math.round(refined.y * scaleY)\n origW = Math.round(refined.width * scaleX)\n origH = Math.round(refined.height * scaleY)\n } else {\n // Use expanded face bounding box as webcam region estimate\n // Webcam overlay is typically larger than the face (includes some background)\n const expandFactor = 1.4\n const faceCx = (avgBox.x1 + avgBox.x2) / 2\n const faceCy = (avgBox.y1 + avgBox.y2) / 2\n const faceW = (avgBox.x2 - avgBox.x1) * expandFactor\n const faceH = (avgBox.y2 - avgBox.y1) * expandFactor\n\n origX = Math.max(0, Math.round((faceCx - faceW / 2) * resolution.width))\n origY = Math.max(0, Math.round((faceCy - faceH / 2) * resolution.height))\n origW = Math.min(resolution.width - origX, Math.round(faceW * resolution.width))\n origH = Math.min(resolution.height - origY, Math.round(faceH * resolution.height))\n }\n\n const region: WebcamRegion = {\n x: origX,\n y: origY,\n width: origW,\n height: origH,\n position: bestPosition,\n confidence: Math.round(bestConfidence * 100) / 100,\n }\n\n logger.info(\n `[FaceDetection] Webcam detected at ${region.position} ` +\n `(${region.x},${region.y} ${region.width}x${region.height}) ` +\n `confidence=${region.confidence} refined=${!!refined}`,\n )\n\n return region\n } finally {\n const files = await listDirectory(tempDir).catch(() => [] as string[])\n for (const f of files) {\n await removeFile(join(tempDir, f)).catch(() => {})\n }\n await removeDirectory(tempDir, { recursive: true, force: true }).catch(() => {})\n }\n}\n","import { createFFmpeg, ffprobe } from '../../core/ffmpeg.js'\nimport { ensureDirectory, getFileStats } from '../../core/fileSystem.js'\nimport { dirname, extname } from '../../core/paths.js'\nimport logger from '../../config/logger'\n\nexport interface ExtractAudioOptions {\n /** Output format: 'mp3' (default, smaller) or 'wav' */\n format?: 'mp3' | 'wav';\n}\n\n/**\n * Extract audio from a video file to mono MP3 at 64kbps (small enough for Whisper).\n * A 10-minute video produces ~5MB MP3 vs ~115MB WAV.\n */\nexport async function extractAudio(\n videoPath: string,\n outputPath: string,\n options: ExtractAudioOptions = {},\n): Promise<string> {\n const { format = 'mp3' } = options;\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n logger.info(`Extracting audio (${format}): ${videoPath} → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n const command = createFFmpeg(videoPath).noVideo().audioChannels(1);\n\n if (format === 'mp3') {\n command.audioCodec('libmp3lame').audioBitrate('64k').audioFrequency(16000);\n } else {\n command.audioCodec('pcm_s16le').audioFrequency(16000);\n }\n\n command\n .output(outputPath)\n .on('end', () => {\n logger.info(`Audio extraction complete: ${outputPath}`);\n resolve(outputPath);\n })\n .on('error', (err) => {\n logger.error(`Audio extraction failed: ${err.message}`);\n reject(new Error(`Audio extraction failed: ${err.message}`));\n })\n .run();\n });\n}\n\n/**\n * Split an audio file into chunks of approximately `maxChunkSizeMB` each.\n * Uses ffmpeg to split by duration calculated from the file size.\n * Returns an array of chunk file paths.\n */\nexport async function splitAudioIntoChunks(\n audioPath: string,\n maxChunkSizeMB: number = 24,\n): Promise<string[]> {\n const stats = await getFileStats(audioPath);\n const fileSizeMB = stats.size / (1024 * 1024);\n\n if (fileSizeMB <= maxChunkSizeMB) {\n return [audioPath];\n }\n\n const duration = await getAudioDuration(audioPath);\n const numChunks = Math.ceil(fileSizeMB / maxChunkSizeMB);\n const chunkDuration = duration / numChunks;\n\n const ext = extname(audioPath);\n const base = audioPath.slice(0, -ext.length);\n const chunkPaths: string[] = [];\n\n logger.info(\n `Splitting ${fileSizeMB.toFixed(1)}MB audio into ${numChunks} chunks ` +\n `(~${chunkDuration.toFixed(0)}s each)`\n );\n\n for (let i = 0; i < numChunks; i++) {\n const startTime = i * chunkDuration;\n const chunkPath = `${base}_chunk${i}${ext}`;\n chunkPaths.push(chunkPath);\n\n await new Promise<void>((resolve, reject) => {\n const cmd = createFFmpeg(audioPath)\n .setStartTime(startTime)\n .setDuration(chunkDuration)\n .audioCodec('copy')\n .output(chunkPath)\n .on('end', () => resolve())\n .on('error', (err) => reject(new Error(`Chunk split failed: ${err.message}`)));\n cmd.run();\n });\n\n logger.info(`Created chunk ${i + 1}/${numChunks}: ${chunkPath}`);\n }\n\n return chunkPaths;\n}\n\n/** Get the duration of an audio file in seconds using ffprobe. */\nasync function getAudioDuration(audioPath: string): Promise<number> {\n try {\n const metadata = await ffprobe(audioPath);\n return metadata.format.duration ?? 0;\n } catch (err: any) {\n throw new Error(`ffprobe failed: ${err.message}`);\n }\n}\n","export { default as OpenAI } from 'openai'\nexport type { ChatCompletionMessageParam, ChatCompletionTool, ChatCompletion } from 'openai/resources/chat/completions.js'\nexport { default as Anthropic } from '@anthropic-ai/sdk'\nexport { CopilotClient, CopilotSession } from '@github/copilot-sdk'\nexport type { SessionEvent } from '@github/copilot-sdk'\n","import { fileExistsSync, readTextFileSync } from '../core/fileSystem.js'\nimport { getConfig } from './environment'\nimport logger from './logger'\n\nexport interface BrandConfig {\n name: string\n handle: string\n tagline: string\n voice: {\n tone: string\n personality: string\n style: string\n }\n advocacy: {\n primary: string[]\n interests: string[]\n avoids: string[]\n }\n customVocabulary: string[]\n hashtags: {\n always: string[]\n preferred: string[]\n platforms: Record<string, string[]>\n }\n contentGuidelines: {\n shortsFocus: string\n blogFocus: string\n socialFocus: string\n }\n}\n\nconst defaultBrand: BrandConfig = {\n name: 'Creator',\n handle: '@creator',\n tagline: '',\n voice: {\n tone: 'professional, friendly',\n personality: 'A knowledgeable content creator.',\n style: 'Clear and concise.',\n },\n advocacy: {\n primary: [],\n interests: [],\n avoids: [],\n },\n customVocabulary: [],\n hashtags: {\n always: [],\n preferred: [],\n platforms: {},\n },\n contentGuidelines: {\n shortsFocus: 'Highlight key moments and insights.',\n blogFocus: 'Educational and informative content.',\n socialFocus: 'Engaging and authentic posts.',\n },\n}\n\nlet cachedBrand: BrandConfig | null = null\n\n/** Validate brand config and log warnings for missing or empty fields. */\nfunction validateBrandConfig(brand: Partial<BrandConfig>): void {\n const requiredStrings: (keyof BrandConfig)[] = ['name', 'handle', 'tagline']\n for (const field of requiredStrings) {\n if (!brand[field]) {\n logger.warn(`brand.json: missing or empty field \"${field}\"`)\n }\n }\n\n const requiredObjects: { key: keyof BrandConfig; subKeys: string[] }[] = [\n { key: 'voice', subKeys: ['tone', 'personality', 'style'] },\n { key: 'advocacy', subKeys: ['primary', 'interests'] },\n { key: 'hashtags', subKeys: ['always', 'preferred'] },\n { key: 'contentGuidelines', subKeys: ['shortsFocus', 'blogFocus', 'socialFocus'] },\n ]\n\n for (const { key, subKeys } of requiredObjects) {\n if (!brand[key]) {\n logger.warn(`brand.json: missing section \"${key}\"`)\n } else {\n const section = brand[key] as Record<string, unknown>\n for (const sub of subKeys) {\n if (!section[sub] || (Array.isArray(section[sub]) && (section[sub] as unknown[]).length === 0)) {\n logger.warn(`brand.json: missing or empty field \"${key}.${sub}\"`)\n }\n }\n }\n }\n\n if (!brand.customVocabulary || brand.customVocabulary.length === 0) {\n logger.warn('brand.json: \"customVocabulary\" is empty — Whisper prompt will be blank')\n }\n}\n\nexport function getBrandConfig(): BrandConfig {\n if (cachedBrand) return cachedBrand\n\n const config = getConfig()\n const brandPath = config.BRAND_PATH\n\n if (!fileExistsSync(brandPath)) {\n logger.warn('brand.json not found — using defaults')\n cachedBrand = { ...defaultBrand }\n return cachedBrand\n }\n\n const raw = readTextFileSync(brandPath)\n cachedBrand = JSON.parse(raw) as BrandConfig\n validateBrandConfig(cachedBrand)\n logger.info(`Brand config loaded: ${cachedBrand.name}`)\n return cachedBrand\n}\n\n// Helper to get Whisper prompt from brand vocabulary\nexport function getWhisperPrompt(): string {\n const brand = getBrandConfig()\n return brand.customVocabulary.join(', ')\n}\n","/**\n * LLM Model Pricing Configuration\n * \n * Per-model pricing for cost calculation. Updated Feb 2026.\n * Copilot uses Premium Request Units (PRUs), others use per-token pricing.\n */\n\nexport interface ModelPricing {\n /** Price per 1M input tokens (USD) — for OpenAI/Claude */\n inputPer1M?: number;\n /** Price per 1M output tokens (USD) — for OpenAI/Claude */\n outputPer1M?: number;\n /** Premium request multiplier — for Copilot */\n pruMultiplier?: number;\n /** Whether this model is included free on paid Copilot plans */\n copilotIncluded?: boolean;\n}\n\n/** Overage rate for Copilot premium requests: $0.04 per PRU */\nexport const COPILOT_PRU_OVERAGE_RATE = 0.04;\n\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\n // === OpenAI Models (from Copilot model picker) ===\n 'gpt-4o': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-4o-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-4.1': { inputPer1M: 2.00, outputPer1M: 8.00, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-4.1-mini': { inputPer1M: 0.40, outputPer1M: 1.60 },\n 'gpt-5-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0, copilotIncluded: true },\n 'gpt-5': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1-codex-max': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.1-codex-mini': { inputPer1M: 0.15, outputPer1M: 0.60, pruMultiplier: 0.33 },\n 'gpt-5.2': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'gpt-5.2-codex': { inputPer1M: 2.50, outputPer1M: 10.00, pruMultiplier: 1 },\n 'o3': { inputPer1M: 10.00, outputPer1M: 40.00, pruMultiplier: 5 },\n 'o4-mini-high': { inputPer1M: 1.10, outputPer1M: 4.40, pruMultiplier: 20 },\n\n // === Anthropic Models (from Copilot model picker) ===\n 'claude-haiku-4.5': { inputPer1M: 0.80, outputPer1M: 4.00, pruMultiplier: 0.33 },\n 'claude-sonnet-4': { inputPer1M: 3.00, outputPer1M: 15.00, pruMultiplier: 1 },\n 'claude-sonnet-4.5': { inputPer1M: 3.00, outputPer1M: 15.00, pruMultiplier: 1 },\n 'claude-opus-4.5': { inputPer1M: 15.00, outputPer1M: 75.00, pruMultiplier: 3 },\n 'claude-opus-4.6': { inputPer1M: 5.00, outputPer1M: 25.00, pruMultiplier: 3 },\n 'claude-opus-4.6-fast': { inputPer1M: 5.00, outputPer1M: 25.00, pruMultiplier: 9 },\n\n // === Google Models (from Copilot model picker) ===\n 'gemini-2.5-pro': { inputPer1M: 1.25, outputPer1M: 5.00, pruMultiplier: 1 },\n 'gemini-2.5-flash': { inputPer1M: 0.15, outputPer1M: 0.60 },\n 'gemini-3-flash': { inputPer1M: 0.10, outputPer1M: 0.40, pruMultiplier: 0.33 },\n 'gemini-3-pro': { inputPer1M: 1.25, outputPer1M: 5.00, pruMultiplier: 1 },\n};\n\n/**\n * Calculate cost for a single LLM call using per-token pricing.\n * Returns USD amount.\n */\nexport function calculateTokenCost(\n model: string,\n inputTokens: number,\n outputTokens: number\n): number {\n const pricing = getModelPricing(model);\n if (!pricing || (!pricing.inputPer1M && !pricing.outputPer1M)) return 0;\n \n const inputCost = ((pricing.inputPer1M ?? 0) / 1_000_000) * inputTokens;\n const outputCost = ((pricing.outputPer1M ?? 0) / 1_000_000) * outputTokens;\n return inputCost + outputCost;\n}\n\n/**\n * Calculate PRU cost for a Copilot premium request.\n * Returns PRU count consumed (multiply by $0.04 for overage cost).\n */\nexport function calculatePRUCost(model: string): number {\n const pricing = getModelPricing(model);\n if (!pricing) return 1; // Default 1 PRU for unknown models\n if (pricing.copilotIncluded) return 0; // Free on paid plans\n return pricing.pruMultiplier ?? 1;\n}\n\n/**\n * Look up model pricing. Returns undefined if model is unknown.\n */\nexport function getModelPricing(model: string): ModelPricing | undefined {\n // Try exact match first, then case-insensitive\n return MODEL_PRICING[model] ?? \n MODEL_PRICING[model.toLowerCase()] ??\n Object.entries(MODEL_PRICING).find(([key]) => \n model.toLowerCase().includes(key.toLowerCase())\n )?.[1];\n}\n","import type { TokenUsage, CostInfo, QuotaSnapshot } from '../providers/types.js';\nimport { calculateTokenCost, calculatePRUCost, COPILOT_PRU_OVERAGE_RATE } from '../config/pricing.js';\nimport logger from '../config/logger.js';\n\n/** Record of a single LLM usage event */\nexport interface UsageRecord {\n timestamp: Date;\n provider: string;\n model: string;\n agent: string;\n stage: string;\n usage: TokenUsage;\n cost: CostInfo;\n durationMs?: number;\n}\n\n/** Record of a non-LLM service usage event */\nexport interface ServiceUsageRecord {\n timestamp: Date;\n service: string;\n stage: string;\n costUSD: number;\n metadata: Record<string, unknown>;\n}\n\n/** Aggregated cost report */\nexport interface CostReport {\n totalCostUSD: number;\n totalPRUs: number;\n totalTokens: { input: number; output: number; total: number };\n byProvider: Record<string, { costUSD: number; prus: number; calls: number }>;\n byAgent: Record<string, { costUSD: number; prus: number; calls: number }>;\n byModel: Record<string, { costUSD: number; prus: number; calls: number }>;\n byService: Record<string, { costUSD: number; calls: number }>;\n records: UsageRecord[];\n serviceRecords: ServiceUsageRecord[];\n totalServiceCostUSD: number;\n /** Copilot quota info (if available) */\n copilotQuota?: QuotaSnapshot;\n}\n\n/** Singleton cost tracker for a pipeline run */\nclass CostTracker {\n private records: UsageRecord[] = [];\n private serviceRecords: ServiceUsageRecord[] = [];\n private latestQuota?: QuotaSnapshot;\n private currentAgent = 'unknown';\n private currentStage = 'unknown';\n\n /** Set the current agent name (called by BaseAgent before LLM calls) */\n setAgent(agent: string): void {\n this.currentAgent = agent;\n }\n\n /** Set the current pipeline stage */\n setStage(stage: string): void {\n this.currentStage = stage;\n }\n\n /** Record a usage event from any provider */\n recordUsage(\n provider: string,\n model: string,\n usage: TokenUsage,\n cost?: CostInfo,\n durationMs?: number,\n quotaSnapshot?: QuotaSnapshot\n ): void {\n // Calculate cost if not provided\n const finalCost = cost ?? {\n amount: provider === 'copilot'\n ? calculatePRUCost(model)\n : calculateTokenCost(model, usage.inputTokens, usage.outputTokens),\n unit: provider === 'copilot' ? 'premium_requests' as const : 'usd' as const,\n model,\n };\n\n const record: UsageRecord = {\n timestamp: new Date(),\n provider,\n model,\n agent: this.currentAgent,\n stage: this.currentStage,\n usage,\n cost: finalCost,\n durationMs,\n };\n\n this.records.push(record);\n\n if (quotaSnapshot) {\n this.latestQuota = quotaSnapshot;\n }\n\n logger.debug(\n `[CostTracker] ${provider}/${model} | ${this.currentAgent} | ` +\n `in=${usage.inputTokens} out=${usage.outputTokens} | ` +\n `cost=${finalCost.amount.toFixed(4)} ${finalCost.unit}`\n );\n }\n\n /** Record a non-LLM service usage event */\n recordServiceUsage(service: string, costUSD: number, metadata?: Record<string, unknown>): void {\n const record: ServiceUsageRecord = {\n timestamp: new Date(),\n service,\n stage: this.currentStage,\n costUSD,\n metadata: metadata ?? {},\n };\n\n this.serviceRecords.push(record);\n\n logger.debug(\n `[CostTracker] service=${service} | stage=${this.currentStage} | cost=$${costUSD.toFixed(4)}`\n );\n }\n\n /** Get the full cost report */\n getReport(): CostReport {\n const report: CostReport = {\n totalCostUSD: 0,\n totalPRUs: 0,\n totalTokens: { input: 0, output: 0, total: 0 },\n byProvider: {},\n byAgent: {},\n byModel: {},\n byService: {},\n records: [...this.records],\n serviceRecords: [...this.serviceRecords],\n totalServiceCostUSD: 0,\n copilotQuota: this.latestQuota,\n };\n\n for (const record of this.records) {\n const { provider, model, agent, usage, cost } = record;\n\n // Accumulate tokens\n report.totalTokens.input += usage.inputTokens;\n report.totalTokens.output += usage.outputTokens;\n report.totalTokens.total += usage.totalTokens;\n\n // Accumulate costs\n const usdCost = cost.unit === 'usd' ? cost.amount : cost.amount * COPILOT_PRU_OVERAGE_RATE;\n const prus = cost.unit === 'premium_requests' ? cost.amount : 0;\n report.totalCostUSD += usdCost;\n report.totalPRUs += prus;\n\n // By provider\n if (!report.byProvider[provider]) report.byProvider[provider] = { costUSD: 0, prus: 0, calls: 0 };\n report.byProvider[provider].costUSD += usdCost;\n report.byProvider[provider].prus += prus;\n report.byProvider[provider].calls += 1;\n\n // By agent\n if (!report.byAgent[agent]) report.byAgent[agent] = { costUSD: 0, prus: 0, calls: 0 };\n report.byAgent[agent].costUSD += usdCost;\n report.byAgent[agent].prus += prus;\n report.byAgent[agent].calls += 1;\n\n // By model\n if (!report.byModel[model]) report.byModel[model] = { costUSD: 0, prus: 0, calls: 0 };\n report.byModel[model].costUSD += usdCost;\n report.byModel[model].prus += prus;\n report.byModel[model].calls += 1;\n }\n\n for (const record of this.serviceRecords) {\n const { service, costUSD } = record;\n report.totalServiceCostUSD += costUSD;\n\n if (!report.byService[service]) report.byService[service] = { costUSD: 0, calls: 0 };\n report.byService[service].costUSD += costUSD;\n report.byService[service].calls += 1;\n }\n\n report.totalCostUSD += report.totalServiceCostUSD;\n\n return report;\n }\n\n /** Format report as human-readable string for console output */\n formatReport(): string {\n const report = this.getReport();\n const lines: string[] = [\n '',\n '═══════════════════════════════════════════',\n ' 💰 Pipeline Cost Report',\n '═══════════════════════════════════════════',\n '',\n ` Total Cost: $${report.totalCostUSD.toFixed(4)} USD` +\n (report.totalServiceCostUSD > 0 ? ` (incl. $${report.totalServiceCostUSD.toFixed(4)} services)` : ''),\n ];\n\n if (report.totalPRUs > 0) {\n lines.push(` Total PRUs: ${report.totalPRUs} premium requests`);\n }\n\n lines.push(\n ` Total Tokens: ${report.totalTokens.total.toLocaleString()} (${report.totalTokens.input.toLocaleString()} in / ${report.totalTokens.output.toLocaleString()} out)`,\n ` LLM Calls: ${this.records.length}`,\n );\n\n if (report.copilotQuota) {\n lines.push(\n '',\n ` Copilot Quota: ${report.copilotQuota.remainingPercentage.toFixed(1)}% remaining`,\n ` Used/Total: ${report.copilotQuota.usedRequests}/${report.copilotQuota.entitlementRequests} PRUs`,\n );\n if (report.copilotQuota.resetDate) {\n lines.push(` Resets: ${report.copilotQuota.resetDate}`);\n }\n }\n\n // By agent breakdown\n if (Object.keys(report.byAgent).length > 1) {\n lines.push('', ' By Agent:');\n for (const [agent, data] of Object.entries(report.byAgent)) {\n lines.push(` ${agent}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\n }\n }\n\n // By model breakdown\n if (Object.keys(report.byModel).length > 1) {\n lines.push('', ' By Model:');\n for (const [model, data] of Object.entries(report.byModel)) {\n lines.push(` ${model}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\n }\n }\n\n // By service breakdown\n if (Object.keys(report.byService).length > 0) {\n lines.push('', ' By Service:');\n for (const [service, data] of Object.entries(report.byService)) {\n lines.push(` ${service}: $${data.costUSD.toFixed(4)} (${data.calls} calls)`);\n }\n }\n\n lines.push('', '═══════════════════════════════════════════', '');\n return lines.join('\\n');\n }\n\n /** Reset all tracking (for new pipeline run) */\n reset(): void {\n this.records = [];\n this.serviceRecords = [];\n this.latestQuota = undefined;\n this.currentAgent = 'unknown';\n this.currentStage = 'unknown';\n }\n}\n\n/** Global singleton instance */\nexport const costTracker = new CostTracker();\n","import { OpenAI } from '../../core/ai.js'\nimport { fileExistsSync, getFileStatsSync, openReadStream } from '../../core/fileSystem.js'\nimport { getConfig } from '../../config/environment'\nimport logger from '../../config/logger'\nimport { getWhisperPrompt } from '../../config/brand'\nimport { Transcript, Segment, Word } from '../../types'\nimport { costTracker } from '../../services/costTracker.js'\n\nconst MAX_FILE_SIZE_MB = 25\nconst WHISPER_COST_PER_MINUTE = 0.006 // $0.006/minute for whisper-1\nconst WARN_FILE_SIZE_MB = 20\n\nexport async function transcribeAudio(audioPath: string): Promise<Transcript> {\n logger.info(`Starting Whisper transcription: ${audioPath}`)\n\n if (!fileExistsSync(audioPath)) {\n throw new Error(`Audio file not found: ${audioPath}`)\n }\n\n // Check file size against Whisper's 25MB limit\n const stats = getFileStatsSync(audioPath)\n const fileSizeMB = stats.size / (1024 * 1024)\n\n if (fileSizeMB > MAX_FILE_SIZE_MB) {\n throw new Error(\n `Audio file exceeds Whisper's 25MB limit (${fileSizeMB.toFixed(1)}MB). ` +\n 'The file should be split into smaller chunks before transcription.'\n )\n }\n if (fileSizeMB > WARN_FILE_SIZE_MB) {\n logger.warn(`Audio file is ${fileSizeMB.toFixed(1)}MB — approaching 25MB limit`)\n }\n\n const config = getConfig()\n const openai = new OpenAI({ apiKey: config.OPENAI_API_KEY })\n\n try {\n const prompt = getWhisperPrompt()\n const response = await openai.audio.transcriptions.create({\n model: 'whisper-1',\n file: openReadStream(audioPath),\n response_format: 'verbose_json',\n timestamp_granularities: ['word', 'segment'],\n ...(prompt && { prompt }),\n })\n\n // The verbose_json response includes segments and words at the top level,\n // but the OpenAI SDK types don't expose them — cast to access raw fields.\n const verboseResponse = response as unknown as Record<string, unknown>\n const rawSegments = (verboseResponse.segments ?? []) as Array<{\n id: number; text: string; start: number; end: number\n }>\n const rawWords = (verboseResponse.words ?? []) as Array<{\n word: string; start: number; end: number\n }>\n\n const words: Word[] = rawWords.map((w) => ({\n word: w.word,\n start: w.start,\n end: w.end,\n }))\n\n const segments: Segment[] = rawSegments.map((s) => ({\n id: s.id,\n text: s.text.trim(),\n start: s.start,\n end: s.end,\n words: rawWords\n .filter((w) => w.start >= s.start && w.end <= s.end)\n .map((w) => ({ word: w.word, start: w.start, end: w.end })),\n }))\n\n logger.info(\n `Transcription complete — ${segments.length} segments, ` +\n `${words.length} words, language=${response.language}`\n )\n\n // Track Whisper API cost\n const durationMinutes = (response.duration ?? 0) / 60\n costTracker.recordServiceUsage('whisper', durationMinutes * WHISPER_COST_PER_MINUTE, {\n model: 'whisper-1',\n durationSeconds: response.duration ?? 0,\n audioFile: audioPath,\n })\n\n return {\n text: response.text,\n segments,\n words,\n language: response.language ?? 'unknown',\n duration: response.duration ?? 0,\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error)\n logger.error(`Whisper transcription failed: ${message}`)\n\n // OpenAI SDK errors expose a `status` property for HTTP status codes\n const status = (error as { status?: number }).status\n if (status === 401) {\n throw new Error('OpenAI API authentication failed. Check your OPENAI_API_KEY.')\n }\n if (status === 429) {\n throw new Error('OpenAI API rate limit exceeded. Please try again later.')\n }\n throw new Error(`Whisper transcription failed: ${message}`)\n }\n}\n","import { join } from '../core/paths.js'\nimport { getFileStats, writeJsonFile, ensureDirectory, removeFile } from '../core/fileSystem.js'\nimport { extractAudio, splitAudioIntoChunks } from '../tools/ffmpeg/audioExtraction'\nimport { transcribeAudio } from '../tools/whisper/whisperClient'\nimport { VideoFile, Transcript, Segment, Word } from '../types'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\nconst MAX_WHISPER_SIZE_MB = 25\n\nexport async function transcribeVideo(video: VideoFile): Promise<Transcript> {\n const config = getConfig()\n\n // 1. Create cache directory for temp audio files\n const cacheDir = join(config.REPO_ROOT, 'cache')\n await ensureDirectory(cacheDir)\n logger.info(`Cache directory ready: ${cacheDir}`)\n\n // 2. Extract audio as compressed MP3 (much smaller than WAV)\n const mp3Path = join(cacheDir, `${video.slug}.mp3`)\n logger.info(`Extracting audio for \"${video.slug}\"`)\n await extractAudio(video.repoPath, mp3Path)\n\n // 3. Check file size and chunk if necessary\n const stats = await getFileStats(mp3Path)\n const fileSizeMB = stats.size / (1024 * 1024)\n logger.info(`Extracted audio: ${fileSizeMB.toFixed(1)}MB`)\n\n let transcript: Transcript\n\n if (fileSizeMB <= MAX_WHISPER_SIZE_MB) {\n // Single-file transcription\n logger.info(`Transcribing audio for \"${video.slug}\"`)\n transcript = await transcribeAudio(mp3Path)\n } else {\n // Chunk and transcribe (very long videos, 50+ min)\n logger.info(`Audio exceeds ${MAX_WHISPER_SIZE_MB}MB, splitting into chunks`)\n const chunkPaths = await splitAudioIntoChunks(mp3Path)\n transcript = await transcribeChunks(chunkPaths)\n\n // Clean up chunk files\n for (const chunkPath of chunkPaths) {\n if (chunkPath !== mp3Path) {\n await removeFile(chunkPath).catch(() => {})\n }\n }\n }\n\n // 4. Save transcript JSON\n const transcriptDir = join(config.OUTPUT_DIR, video.slug)\n await ensureDirectory(transcriptDir)\n const transcriptPath = join(transcriptDir, 'transcript.json')\n await writeJsonFile(transcriptPath, transcript)\n logger.info(`Transcript saved: ${transcriptPath}`)\n\n // 5. Clean up temp audio file\n await removeFile(mp3Path).catch(() => {})\n logger.info(`Cleaned up temp file: ${mp3Path}`)\n\n // 6. Return the transcript\n logger.info(\n `Transcription complete for \"${video.slug}\" — ` +\n `${transcript.segments.length} segments, ${transcript.words.length} words`\n )\n return transcript\n}\n\n/**\n * Transcribe multiple audio chunks and merge results with adjusted timestamps.\n */\nasync function transcribeChunks(chunkPaths: string[]): Promise<Transcript> {\n let allText = ''\n const allSegments: Segment[] = []\n const allWords: Word[] = []\n let cumulativeOffset = 0\n let totalDuration = 0\n let language = 'unknown'\n\n for (let i = 0; i < chunkPaths.length; i++) {\n logger.info(`Transcribing chunk ${i + 1}/${chunkPaths.length}: ${chunkPaths[i]}`)\n const result = await transcribeAudio(chunkPaths[i])\n\n if (i === 0) language = result.language\n\n // Adjust timestamps by cumulative offset\n const offsetSegments = result.segments.map((s) => ({\n ...s,\n id: allSegments.length + s.id,\n start: s.start + cumulativeOffset,\n end: s.end + cumulativeOffset,\n words: s.words.map((w) => ({\n ...w,\n start: w.start + cumulativeOffset,\n end: w.end + cumulativeOffset,\n })),\n }))\n\n const offsetWords = result.words.map((w) => ({\n ...w,\n start: w.start + cumulativeOffset,\n end: w.end + cumulativeOffset,\n }))\n\n allText += (allText ? ' ' : '') + result.text\n allSegments.push(...offsetSegments)\n allWords.push(...offsetWords)\n\n cumulativeOffset += result.duration\n totalDuration += result.duration\n }\n\n return {\n text: allText,\n segments: allSegments,\n words: allWords,\n language,\n duration: totalDuration,\n }\n}\n","/**\n * CopilotProvider — wraps @github/copilot-sdk behind the LLMProvider interface.\n *\n * Extracts the Copilot-specific logic from BaseAgent into a reusable provider\n * that can be swapped with OpenAI or Claude providers via the abstraction layer.\n *\n * NOTE: Vision support for tool results is not available in the Copilot provider.\n * The @github/copilot-sdk handles tool calls internally, so we cannot inject\n * images into the conversation. Tools returning imagePath will have the path\n * included in the JSON result as text only.\n */\n\nimport { CopilotClient, CopilotSession } from '../core/ai.js'\nimport type { SessionEvent } from '../core/ai.js'\nimport logger from '../config/logger.js'\nimport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n TokenUsage,\n CostInfo,\n QuotaSnapshot,\n ToolCall,\n ProviderEvent,\n ProviderEventType,\n} from './types'\n\nconst DEFAULT_MODEL = 'claude-opus-4.5'\nconst DEFAULT_TIMEOUT_MS = 300_000 // 5 minutes\n\nexport class CopilotProvider implements LLMProvider {\n readonly name = 'copilot' as const\n private client: CopilotClient | null = null\n\n isAvailable(): boolean {\n // Copilot uses GitHub auth, not an API key\n return true\n }\n\n getDefaultModel(): string {\n return DEFAULT_MODEL\n }\n\n async createSession(config: SessionConfig): Promise<LLMSession> {\n if (!this.client) {\n this.client = new CopilotClient({ autoStart: true, logLevel: 'error' })\n }\n\n const copilotSession = await this.client.createSession({\n model: config.model,\n mcpServers: config.mcpServers,\n systemMessage: { mode: 'replace', content: config.systemPrompt },\n tools: config.tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n handler: t.handler,\n })),\n streaming: config.streaming ?? true,\n })\n\n return new CopilotSessionWrapper(\n copilotSession,\n config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n )\n }\n\n /** Tear down the underlying Copilot client. */\n async close(): Promise<void> {\n try {\n if (this.client) {\n await this.client.stop()\n this.client = null\n }\n } catch (err) {\n logger.error(`[CopilotProvider] Error during close: ${err}`)\n }\n }\n}\n\n/** Wraps a CopilotSession to satisfy the LLMSession interface. */\nclass CopilotSessionWrapper implements LLMSession {\n private eventHandlers = new Map<ProviderEventType, Array<(event: ProviderEvent) => void>>()\n\n // Latest usage data captured from assistant.usage events\n private lastUsage: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }\n private lastCost: CostInfo | undefined\n private lastQuotaSnapshots: Record<string, QuotaSnapshot> | undefined\n\n constructor(\n private readonly session: CopilotSession,\n private readonly timeoutMs: number,\n ) {\n this.setupEventForwarding()\n this.setupUsageTracking()\n }\n\n async sendAndWait(message: string): Promise<LLMResponse> {\n const start = Date.now()\n\n // Reset usage tracking for this call\n this.lastUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 }\n this.lastCost = undefined\n this.lastQuotaSnapshots = undefined\n\n const response = await this.session.sendAndWait(\n { prompt: message },\n this.timeoutMs,\n )\n\n const content = response?.data?.content ?? ''\n const toolCalls: ToolCall[] = [] // Copilot SDK handles tool calls internally\n\n return {\n content,\n toolCalls,\n usage: this.lastUsage,\n cost: this.lastCost,\n quotaSnapshots: this.lastQuotaSnapshots,\n durationMs: Date.now() - start,\n }\n }\n\n on(event: ProviderEventType, handler: (event: ProviderEvent) => void): void {\n const handlers = this.eventHandlers.get(event) ?? []\n handlers.push(handler)\n this.eventHandlers.set(event, handlers)\n }\n\n async close(): Promise<void> {\n await this.session.destroy()\n this.eventHandlers.clear()\n }\n\n /** Capture assistant.usage events for token/cost tracking. */\n private setupUsageTracking(): void {\n this.session.on((event: SessionEvent) => {\n if (event.type === 'assistant.usage') {\n const d = event.data as Record<string, unknown>\n this.lastUsage = {\n inputTokens: (d.inputTokens as number) ?? 0,\n outputTokens: (d.outputTokens as number) ?? 0,\n totalTokens: ((d.inputTokens as number) ?? 0) + ((d.outputTokens as number) ?? 0),\n cacheReadTokens: d.cacheReadTokens as number | undefined,\n cacheWriteTokens: d.cacheWriteTokens as number | undefined,\n }\n if (d.cost != null) {\n this.lastCost = {\n amount: d.cost as number,\n unit: 'premium_requests',\n model: (d.model as string) ?? DEFAULT_MODEL,\n multiplier: d.multiplier as number | undefined,\n }\n }\n if (d.quotaSnapshots != null) {\n this.lastQuotaSnapshots = d.quotaSnapshots as Record<string, QuotaSnapshot>\n }\n }\n })\n }\n\n /** Forward CopilotSession events to ProviderEvent subscribers. */\n private setupEventForwarding(): void {\n this.session.on((event: SessionEvent) => {\n switch (event.type) {\n case 'assistant.message_delta':\n this.emit('delta', event.data)\n break\n case 'tool.execution_start':\n this.emit('tool_start', event.data)\n break\n case 'tool.execution_complete':\n this.emit('tool_end', event.data)\n break\n case 'assistant.usage':\n this.emit('usage', event.data)\n break\n case 'session.error':\n this.emit('error', event.data)\n break\n }\n })\n }\n\n private emit(type: ProviderEventType, data: unknown): void {\n const handlers = this.eventHandlers.get(type)\n if (handlers) {\n for (const handler of handlers) {\n handler({ type, data })\n }\n }\n }\n}\n","/**\n * Image utilities for vision support in LLM providers.\n *\n * Handles detection of image paths in tool results and base64 encoding.\n */\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport type { ImageMimeType } from './types.js'\n\n/** Result of extracting an image from a tool result */\nexport interface ExtractedImage {\n base64: string\n mimeType: ImageMimeType\n path: string\n}\n\n/** Check if a tool result contains an imagePath field */\nexport function hasImagePath(result: unknown): result is { imagePath: string } {\n return (\n typeof result === 'object' &&\n result !== null &&\n 'imagePath' in result &&\n typeof (result as Record<string, unknown>).imagePath === 'string'\n )\n}\n\n/** Get MIME type from file extension */\nfunction getMimeType(filePath: string): ImageMimeType | null {\n const ext = path.extname(filePath).toLowerCase()\n switch (ext) {\n case '.png':\n return 'image/png'\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg'\n default:\n return null\n }\n}\n\n/**\n * Extract and encode an image from a tool result.\n * Returns null if the file doesn't exist or isn't a supported image type.\n */\nexport async function extractImage(result: { imagePath: string }): Promise<ExtractedImage | null> {\n const imagePath = result.imagePath\n\n // Check MIME type\n const mimeType = getMimeType(imagePath)\n if (!mimeType) {\n return null\n }\n\n // Read and encode file\n try {\n const buffer = await fs.readFile(imagePath)\n const base64 = buffer.toString('base64')\n return { base64, mimeType, path: imagePath }\n } catch {\n // File doesn't exist or can't be read\n return null\n }\n}\n","/**\n * OpenAI Provider — wraps the OpenAI SDK behind the LLMProvider interface.\n *\n * Implements chat completions with automatic tool-calling loop:\n * user message → LLM → (tool_calls? → execute → feed back → LLM)* → final text\n */\n\nimport { OpenAI } from '../core/ai.js';\nimport type {\n ChatCompletionMessageParam,\n ChatCompletionTool,\n ChatCompletionContentPart,\n} from 'openai/resources/chat/completions.js';\nimport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n ToolWithHandler,\n TokenUsage,\n ProviderEventType,\n ProviderEvent,\n} from './types.js';\nimport { calculateTokenCost } from '../config/pricing.js';\nimport logger from '../config/logger.js';\nimport { getConfig } from '../config/environment.js';\nimport { hasImagePath, extractImage } from './imageUtils.js';\n\nconst MAX_TOOL_ROUNDS = 50;\n\n// ── helpers ────────────────────────────────────────────────────────────\n\n/** Convert our ToolWithHandler[] to the OpenAI SDK tool format. */\nfunction toOpenAITools(tools: ToolWithHandler[]): ChatCompletionTool[] {\n return tools.map((t) => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n}\n\n/** Build a handler lookup map keyed by tool name. */\nfunction buildHandlerMap(\n tools: ToolWithHandler[],\n): Map<string, ToolWithHandler['handler']> {\n return new Map(tools.map((t) => [t.name, t.handler]));\n}\n\n/** Sum two TokenUsage objects. */\nfunction addUsage(a: TokenUsage, b: TokenUsage): TokenUsage {\n return {\n inputTokens: a.inputTokens + b.inputTokens,\n outputTokens: a.outputTokens + b.outputTokens,\n totalTokens: a.totalTokens + b.totalTokens,\n };\n}\n\n// ── session ────────────────────────────────────────────────────────────\n\nclass OpenAISession implements LLMSession {\n private client: OpenAI;\n private model: string;\n private messages: ChatCompletionMessageParam[];\n private tools: ChatCompletionTool[];\n private handlers: Map<string, ToolWithHandler['handler']>;\n private listeners = new Map<ProviderEventType, ((e: ProviderEvent) => void)[]>();\n private timeoutMs?: number;\n\n constructor(client: OpenAI, config: SessionConfig, model: string) {\n this.client = client;\n this.model = model;\n this.messages = [{ role: 'system', content: config.systemPrompt }];\n this.tools = toOpenAITools(config.tools);\n this.handlers = buildHandlerMap(config.tools);\n this.timeoutMs = config.timeoutMs;\n }\n\n // ── public API ─────────────────────────────────────────────────────\n\n async sendAndWait(message: string): Promise<LLMResponse> {\n this.messages.push({ role: 'user', content: message });\n\n let cumulative: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\n const start = Date.now();\n\n // Agent loop: keep calling the LLM until no tool_calls remain\n let toolRound = 0;\n while (true) {\n if (++toolRound > MAX_TOOL_ROUNDS) {\n logger.warn(`OpenAI agent exceeded ${MAX_TOOL_ROUNDS} tool rounds — aborting to prevent runaway`);\n throw new Error(`Max tool rounds (${MAX_TOOL_ROUNDS}) exceeded — possible infinite loop`);\n }\n const controller = new AbortController();\n const timeoutId = this.timeoutMs\n ? setTimeout(() => controller.abort(), this.timeoutMs)\n : undefined;\n let response: OpenAI.Chat.Completions.ChatCompletion;\n try {\n response = await this.client.chat.completions.create(\n {\n model: this.model,\n messages: this.messages,\n ...(this.tools.length > 0 ? { tools: this.tools } : {}),\n },\n { signal: controller.signal },\n );\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n\n const choice = response.choices[0];\n const assistantMsg = choice.message;\n\n // Accumulate token usage\n if (response.usage) {\n const iterUsage: TokenUsage = {\n inputTokens: response.usage.prompt_tokens,\n outputTokens: response.usage.completion_tokens,\n totalTokens: response.usage.total_tokens,\n };\n cumulative = addUsage(cumulative, iterUsage);\n this.emit('usage', iterUsage);\n }\n\n // Add assistant message to history\n this.messages.push(assistantMsg as ChatCompletionMessageParam);\n\n const toolCalls = assistantMsg.tool_calls;\n if (!toolCalls || toolCalls.length === 0) {\n // No more tool calls — return final response\n const cost = calculateTokenCost(this.model, cumulative.inputTokens, cumulative.outputTokens);\n return {\n content: assistantMsg.content ?? '',\n toolCalls: [],\n usage: cumulative,\n cost: { amount: cost, unit: 'usd', model: this.model },\n durationMs: Date.now() - start,\n };\n }\n\n // Execute each tool call and feed results back\n const pendingImageMessages: ChatCompletionMessageParam[] = [];\n\n for (const tc of toolCalls) {\n if (tc.type !== 'function') continue;\n\n const fnName = tc.function.name;\n const handler = this.handlers.get(fnName);\n\n let result: unknown;\n if (!handler) {\n logger.warn(`OpenAI requested unknown tool: ${fnName}`);\n result = { error: `Unknown tool: ${fnName}` };\n } else {\n this.emit('tool_start', { name: fnName, arguments: tc.function.arguments });\n try {\n const args = JSON.parse(tc.function.arguments) as Record<string, unknown>;\n result = await handler(args);\n } catch (err) {\n logger.error(`Tool ${fnName} failed: ${err}`);\n result = { error: String(err) };\n }\n this.emit('tool_end', { name: fnName, result });\n }\n\n // Add the tool result message\n this.messages.push({\n role: 'tool',\n tool_call_id: tc.id,\n content: typeof result === 'string' ? result : JSON.stringify(result),\n });\n\n // Check if result contains an image path and queue it for injection\n if (hasImagePath(result)) {\n const extracted = await extractImage(result);\n if (extracted) {\n const imageContent: ChatCompletionContentPart[] = [\n { type: 'text', text: `[Image from tool ${fnName}: ${extracted.path}]` },\n {\n type: 'image_url',\n image_url: { url: `data:${extracted.mimeType};base64,${extracted.base64}` },\n },\n ];\n pendingImageMessages.push({ role: 'user', content: imageContent });\n }\n }\n }\n\n // Inject any images as a follow-up user message\n if (pendingImageMessages.length > 0) {\n for (const imgMsg of pendingImageMessages) {\n this.messages.push(imgMsg);\n }\n }\n // Loop back to call the LLM again with tool results\n }\n }\n\n on(event: ProviderEventType, handler: (e: ProviderEvent) => void): void {\n const list = this.listeners.get(event) ?? [];\n list.push(handler);\n this.listeners.set(event, list);\n }\n\n async close(): Promise<void> {\n this.messages = [];\n this.listeners.clear();\n }\n\n // ── internals ──────────────────────────────────────────────────────\n\n private emit(type: ProviderEventType, data: unknown): void {\n for (const handler of this.listeners.get(type) ?? []) {\n try {\n handler({ type, data });\n } catch {\n // Don't let listener errors break the agent loop\n }\n }\n }\n}\n\n// ── provider ───────────────────────────────────────────────────────────\n\nexport class OpenAIProvider implements LLMProvider {\n readonly name = 'openai' as const;\n\n isAvailable(): boolean {\n return !!getConfig().OPENAI_API_KEY;\n }\n\n getDefaultModel(): string {\n return 'gpt-4o';\n }\n\n async createSession(config: SessionConfig): Promise<LLMSession> {\n const client = new OpenAI(); // reads OPENAI_API_KEY from env\n const model = config.model ?? this.getDefaultModel();\n logger.info(`OpenAI session created (model=${model}, tools=${config.tools.length})`);\n return new OpenAISession(client, config, model);\n }\n}\n","/**\n * Claude (Anthropic) LLM Provider\n *\n * Wraps the Anthropic Messages API behind the LLMProvider interface.\n * Uses direct @anthropic-ai/sdk for tool-calling with our own agent loop.\n */\n\nimport { Anthropic } from '../core/ai.js'\nimport type {\n ContentBlock,\n ContentBlockParam,\n ImageBlockParam,\n MessageParam,\n TextBlock,\n TextBlockParam,\n Tool,\n ToolResultBlockParam,\n ToolUseBlock,\n} from '@anthropic-ai/sdk/resources/messages.js'\nimport { calculateTokenCost } from '../config/pricing.js'\nimport logger from '../config/logger.js'\nimport { getConfig } from '../config/environment.js'\nimport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n ToolWithHandler,\n TokenUsage,\n ProviderEventType,\n ProviderEvent,\n} from './types.js'\nimport { hasImagePath, extractImage } from './imageUtils.js'\n\nconst DEFAULT_MODEL = 'claude-opus-4.6'\nconst DEFAULT_MAX_TOKENS = 8192\nconst MAX_TOOL_ROUNDS = 50\n\n/** Convert our ToolWithHandler[] to Anthropic tool format */\nfunction toAnthropicTools(tools: ToolWithHandler[]): Tool[] {\n return tools.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.parameters as Tool['input_schema'],\n }))\n}\n\n/** Extract text content from Anthropic response content blocks */\nfunction extractText(content: ContentBlock[]): string {\n return content\n .filter((b): b is TextBlock => b.type === 'text')\n .map((b) => b.text)\n .join('')\n}\n\n/** Extract tool_use blocks from Anthropic response */\nfunction extractToolUse(content: ContentBlock[]): ToolUseBlock[] {\n return content.filter((b): b is ToolUseBlock => b.type === 'tool_use')\n}\n\nclass ClaudeSession implements LLMSession {\n private client: Anthropic\n private systemPrompt: string\n private tools: ToolWithHandler[]\n private anthropicTools: Tool[]\n private messages: MessageParam[] = []\n private model: string\n private maxTokens: number\n private handlers = new Map<ProviderEventType, ((event: ProviderEvent) => void)[]>()\n private timeoutMs?: number\n\n constructor(client: Anthropic, config: SessionConfig) {\n this.client = client\n this.systemPrompt = config.systemPrompt\n this.tools = config.tools\n this.anthropicTools = toAnthropicTools(config.tools)\n this.model = config.model ?? DEFAULT_MODEL\n this.maxTokens = DEFAULT_MAX_TOKENS\n this.timeoutMs = config.timeoutMs\n }\n\n on(event: ProviderEventType, handler: (event: ProviderEvent) => void): void {\n const list = this.handlers.get(event) ?? []\n list.push(handler)\n this.handlers.set(event, list)\n }\n\n private emit(type: ProviderEventType, data: unknown): void {\n for (const handler of this.handlers.get(type) ?? []) {\n handler({ type, data })\n }\n }\n\n async sendAndWait(message: string): Promise<LLMResponse> {\n this.messages.push({ role: 'user', content: message })\n\n let cumulativeUsage: TokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n }\n\n const startMs = Date.now()\n\n // Agent loop: keep calling until no more tool_use\n let toolRound = 0\n while (true) {\n if (++toolRound > MAX_TOOL_ROUNDS) {\n logger.warn(`Claude agent exceeded ${MAX_TOOL_ROUNDS} tool rounds — aborting to prevent runaway`)\n throw new Error(`Max tool rounds (${MAX_TOOL_ROUNDS}) exceeded — possible infinite loop`)\n }\n const controller = new AbortController()\n const timeoutId = this.timeoutMs\n ? setTimeout(() => controller.abort(), this.timeoutMs)\n : undefined\n let response: Anthropic.Messages.Message\n try {\n response = await this.client.messages.create(\n {\n model: this.model,\n max_tokens: this.maxTokens,\n system: this.systemPrompt,\n messages: this.messages,\n ...(this.anthropicTools.length > 0 ? { tools: this.anthropicTools } : {}),\n },\n { signal: controller.signal },\n )\n } finally {\n if (timeoutId) clearTimeout(timeoutId)\n }\n\n // Accumulate usage\n cumulativeUsage.inputTokens += response.usage.input_tokens\n cumulativeUsage.outputTokens += response.usage.output_tokens\n cumulativeUsage.totalTokens =\n cumulativeUsage.inputTokens + cumulativeUsage.outputTokens\n\n if (response.usage.cache_read_input_tokens) {\n cumulativeUsage.cacheReadTokens =\n (cumulativeUsage.cacheReadTokens ?? 0) + response.usage.cache_read_input_tokens\n }\n if (response.usage.cache_creation_input_tokens) {\n cumulativeUsage.cacheWriteTokens =\n (cumulativeUsage.cacheWriteTokens ?? 0) + response.usage.cache_creation_input_tokens\n }\n\n this.emit('usage', cumulativeUsage)\n\n // Add assistant response to history\n this.messages.push({ role: 'assistant', content: response.content })\n\n const toolUseBlocks = extractToolUse(response.content)\n\n if (toolUseBlocks.length === 0 || response.stop_reason === 'end_turn') {\n // No tool calls — return final text\n const text = extractText(response.content)\n const cost = calculateTokenCost(\n this.model,\n cumulativeUsage.inputTokens,\n cumulativeUsage.outputTokens,\n )\n\n return {\n content: text,\n toolCalls: [],\n usage: cumulativeUsage,\n cost: cost > 0\n ? { amount: cost, unit: 'usd', model: this.model }\n : undefined,\n durationMs: Date.now() - startMs,\n }\n }\n\n // Execute tool calls and build result messages\n const toolResults: ToolResultBlockParam[] = []\n const pendingImageBlocks: ContentBlockParam[] = []\n\n for (const block of toolUseBlocks) {\n const tool = this.tools.find((t) => t.name === block.name)\n if (!tool) {\n logger.warn(`Claude requested unknown tool: ${block.name}`)\n toolResults.push({\n type: 'tool_result',\n tool_use_id: block.id,\n content: JSON.stringify({ error: `Unknown tool: ${block.name}` }),\n })\n continue\n }\n\n this.emit('tool_start', { name: block.name, arguments: block.input })\n\n try {\n const result = await tool.handler(block.input as Record<string, unknown>)\n toolResults.push({\n type: 'tool_result',\n tool_use_id: block.id,\n content: JSON.stringify(result),\n })\n this.emit('tool_end', { name: block.name, result })\n\n // Check if result contains an image path\n if (hasImagePath(result)) {\n const extracted = await extractImage(result)\n if (extracted) {\n const textBlock: TextBlockParam = {\n type: 'text',\n text: `[Image from tool ${block.name}: ${extracted.path}]`,\n }\n const imageBlock: ImageBlockParam = {\n type: 'image',\n source: {\n type: 'base64',\n media_type: extracted.mimeType,\n data: extracted.base64,\n },\n }\n pendingImageBlocks.push(textBlock, imageBlock)\n }\n }\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err)\n logger.error(`Tool ${block.name} failed: ${errorMsg}`)\n toolResults.push({\n type: 'tool_result',\n tool_use_id: block.id,\n content: JSON.stringify({ error: errorMsg }),\n is_error: true,\n })\n this.emit('error', { name: block.name, error: errorMsg })\n }\n }\n\n // Add tool results as a user message\n this.messages.push({ role: 'user', content: toolResults })\n\n // If we have images, add them as a follow-up user message\n if (pendingImageBlocks.length > 0) {\n this.messages.push({ role: 'user', content: pendingImageBlocks })\n }\n }\n }\n\n async close(): Promise<void> {\n this.messages = []\n this.handlers.clear()\n }\n}\n\nexport class ClaudeProvider implements LLMProvider {\n readonly name = 'claude' as const\n\n isAvailable(): boolean {\n return !!getConfig().ANTHROPIC_API_KEY\n }\n\n getDefaultModel(): string {\n return DEFAULT_MODEL\n }\n\n async createSession(config: SessionConfig): Promise<LLMSession> {\n const client = new Anthropic()\n return new ClaudeSession(client, config)\n }\n}\n","import type { LLMProvider } from './types.js';\nimport type { ProviderName } from './types.js';\nimport { CopilotProvider } from './CopilotProvider.js';\nimport { OpenAIProvider } from './OpenAIProvider.js';\nimport { ClaudeProvider } from './ClaudeProvider.js';\nimport logger from '../config/logger.js';\nimport { getConfig } from '../config/environment.js';\n\nconst providers: Record<ProviderName, () => LLMProvider> = {\n copilot: () => new CopilotProvider(),\n openai: () => new OpenAIProvider(),\n claude: () => new ClaudeProvider(),\n};\n\n/** Cached singleton provider instance */\nlet currentProvider: LLMProvider | null = null;\nlet currentProviderName: ProviderName | null = null;\n\n/**\n * Get the configured LLM provider.\n * Reads from LLM_PROVIDER env var, defaults to 'copilot'.\n * Caches the instance for reuse.\n */\nexport function getProvider(name?: ProviderName): LLMProvider {\n const raw = name ?? getConfig().LLM_PROVIDER.trim().toLowerCase();\n const providerName = raw as ProviderName;\n \n if (currentProvider && currentProviderName === providerName) {\n return currentProvider;\n }\n\n // Close old provider if switching to a different one\n currentProvider?.close?.().catch(() => { /* ignore close errors */ });\n\n if (!providers[providerName]) {\n throw new Error(\n `Unknown LLM provider: \"${providerName}\". ` +\n `Valid options: ${Object.keys(providers).join(', ')}`\n );\n }\n\n const provider = providers[providerName]();\n \n if (!provider.isAvailable()) {\n logger.warn(\n `Provider \"${providerName}\" is not available (missing API key or config). ` +\n `Falling back to copilot provider.`\n );\n currentProvider = providers.copilot();\n currentProviderName = 'copilot';\n return currentProvider;\n }\n\n logger.info(`Using LLM provider: ${providerName} (model: ${provider.getDefaultModel()})`);\n currentProvider = provider;\n currentProviderName = providerName;\n return currentProvider;\n}\n\n/** Reset the cached provider (for testing) */\nexport async function resetProvider(): Promise<void> {\n try { await currentProvider?.close?.(); } catch { /* ignore close errors */ }\n currentProvider = null;\n currentProviderName = null;\n}\n\n/** Get the name of the current provider */\nexport function getProviderName(): ProviderName {\n const raw = getConfig().LLM_PROVIDER.trim().toLowerCase();\n const valid: ProviderName[] = ['copilot', 'openai', 'claude'];\n return currentProviderName ?? (valid.includes(raw as ProviderName) ? (raw as ProviderName) : 'copilot');\n}\n\n// Re-export types and providers\nexport type { LLMProvider, LLMSession, LLMResponse, SessionConfig, ToolWithHandler, TokenUsage, CostInfo, QuotaSnapshot, ProviderEvent, ProviderEventType } from './types.js';\nexport type { ProviderName } from './types.js';\nexport { CopilotProvider } from './CopilotProvider.js';\nexport { OpenAIProvider } from './OpenAIProvider.js';\nexport { ClaudeProvider } from './ClaudeProvider.js';\n","/**\n * Per-Agent Model Selection\n *\n * Central config for which LLM model each agent should use.\n * Override any agent via env var MODEL_<AGENT_NAME_UPPER> or globally via LLM_MODEL.\n */\n\nimport { getConfig } from './environment.js';\n\nexport const PREMIUM_MODEL = 'claude-opus-4.5';\nexport const STANDARD_MODEL = 'claude-sonnet-4.5';\nexport const FREE_MODEL = 'gpt-4.1';\n\nexport const AGENT_MODEL_MAP: Record<string, string> = {\n SilenceRemovalAgent: PREMIUM_MODEL,\n ShortsAgent: PREMIUM_MODEL,\n MediumVideoAgent: PREMIUM_MODEL,\n SocialMediaAgent: PREMIUM_MODEL,\n BlogAgent: PREMIUM_MODEL,\n SummaryAgent: PREMIUM_MODEL,\n ChapterAgent: PREMIUM_MODEL,\n ShortPostsAgent: PREMIUM_MODEL,\n MediumClipPostsAgent: PREMIUM_MODEL,\n ProducerAgent: PREMIUM_MODEL,\n};\n\n/**\n * Resolve model for an agent. Priority:\n * 1. MODEL_<AGENT_NAME_UPPER> env var\n * 2. AGENT_MODEL_MAP entry\n * 3. Global LLM_MODEL env var\n * 4. undefined (provider default)\n */\nexport function getModelForAgent(agentName: string): string | undefined {\n // Per-agent env override (dynamic keys like MODEL_SHORTS_AGENT)\n const envKey = `MODEL_${agentName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()}`;\n const envOverride = process.env[envKey];\n if (envOverride) return envOverride;\n\n const mapped = AGENT_MODEL_MAP[agentName];\n if (mapped) return mapped;\n\n const global = getConfig().LLM_MODEL;\n if (global) return global;\n\n return undefined;\n}\n","import type { LLMProvider, LLMSession, ToolWithHandler, MCPServerConfig } from '../providers/types.js'\nimport { getProvider } from '../providers/index.js'\nimport { getModelForAgent } from '../config/modelConfig.js'\nimport { costTracker } from '../services/costTracker.js'\nimport logger from '../config/logger.js'\n\n/**\n * BaseAgent — abstract foundation for all LLM-powered agents.\n *\n * ### Agent pattern\n * Each agent in the pipeline (SummaryAgent, ShortsAgent, BlogAgent, etc.)\n * extends BaseAgent and implements two methods:\n * - `getTools()` — declares the tools (functions) the LLM can call\n * - `handleToolCall()` — dispatches tool invocations to concrete implementations\n *\n * ### Tool registration\n * Tools are declared as JSON Schema objects and passed to the LLMSession\n * at creation time. When the LLM decides to call a tool, the provider routes\n * the call through the tool handler where the subclass executes the actual\n * logic (e.g. reading files, running FFmpeg, querying APIs).\n *\n * ### Message flow\n * 1. `run(userMessage)` lazily creates an LLMSession via the configured\n * provider (Copilot, OpenAI, or Claude).\n * 2. The user message is sent via `sendAndWait()`, which blocks until the\n * LLM produces a final response (with a 5-minute timeout).\n * 3. During processing, the LLM may invoke tools multiple times — each call\n * is logged via session event handlers.\n * 4. The final assistant message text is returned to the caller.\n *\n * Sessions are reusable: calling `run()` multiple times on the same agent\n * sends additional messages within the same conversation context.\n */\nexport abstract class BaseAgent {\n protected provider: LLMProvider\n protected session: LLMSession | null = null\n protected readonly model?: string\n\n constructor(\n protected readonly agentName: string,\n protected readonly systemPrompt: string,\n provider?: LLMProvider,\n model?: string,\n ) {\n this.provider = provider ?? getProvider()\n this.model = model\n }\n\n /** Tools this agent exposes to the LLM. Override in subclasses. */\n protected getTools(): ToolWithHandler[] {\n return []\n }\n\n /** MCP servers this agent needs. Override in subclasses that use MCP tools. */\n protected getMcpServers(): Record<string, MCPServerConfig> | undefined {\n return undefined\n }\n\n /** Dispatch a tool call to the concrete agent. Override in subclasses. */\n protected abstract handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown>\n\n /**\n * Send a user message to the agent and return the final response text.\n *\n * 1. Lazily creates an LLMSession via the provider\n * 2. Registers event listeners for logging\n * 3. Calls sendAndWait and records usage via CostTracker\n */\n async run(userMessage: string): Promise<string> {\n if (!this.session) {\n this.session = await this.provider.createSession({\n systemPrompt: this.systemPrompt,\n tools: this.getTools(),\n streaming: true,\n model: this.model ?? getModelForAgent(this.agentName),\n timeoutMs: 300_000, // 5 min timeout\n mcpServers: this.getMcpServers(),\n })\n this.setupEventHandlers(this.session)\n }\n\n logger.info(`[${this.agentName}] Sending message: ${userMessage.substring(0, 80)}…`)\n\n costTracker.setAgent(this.agentName)\n const response = await this.session.sendAndWait(userMessage)\n\n // Record usage via CostTracker\n costTracker.recordUsage(\n this.provider.name,\n response.cost?.model ?? this.provider.getDefaultModel(),\n response.usage,\n response.cost,\n response.durationMs,\n response.quotaSnapshots\n ? Object.values(response.quotaSnapshots)[0]\n : undefined,\n )\n\n const content = response.content\n logger.info(`[${this.agentName}] Response received (${content.length} chars)`)\n return content\n }\n\n /** Wire up session event listeners for logging. */\n private setupEventHandlers(session: LLMSession): void {\n session.on('delta', (event) => {\n logger.debug(`[${this.agentName}] delta: ${JSON.stringify(event.data)}`)\n })\n\n session.on('tool_start', (event) => {\n logger.info(`[${this.agentName}] tool start: ${JSON.stringify(event.data)}`)\n })\n\n session.on('tool_end', (event) => {\n logger.info(`[${this.agentName}] tool done: ${JSON.stringify(event.data)}`)\n })\n\n session.on('error', (event) => {\n logger.error(`[${this.agentName}] error: ${JSON.stringify(event.data)}`)\n })\n }\n\n /** Tear down the session. */\n async destroy(): Promise<void> {\n try {\n if (this.session) {\n await this.session.close()\n this.session = null\n }\n } catch (err) {\n logger.error(`[${this.agentName}] Error during destroy: ${err}`)\n }\n }\n}\n","import { createFFmpeg } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nexport interface SilenceRegion {\n start: number // seconds\n end: number // seconds\n duration: number // seconds\n}\n\n/**\n * Use FFmpeg silencedetect filter to find silence regions in an audio/video file.\n */\nexport async function detectSilence(\n audioPath: string,\n minDuration: number = 1.0,\n noiseThreshold: string = '-30dB',\n): Promise<SilenceRegion[]> {\n logger.info(`Detecting silence in: ${audioPath} (min=${minDuration}s, threshold=${noiseThreshold})`)\n\n return new Promise<SilenceRegion[]>((resolve, reject) => {\n const regions: SilenceRegion[] = []\n let stderr = ''\n\n createFFmpeg(audioPath)\n .audioFilters(`silencedetect=noise=${noiseThreshold}:d=${minDuration}`)\n .format('null')\n .output('-')\n .on('stderr', (line: string) => {\n stderr += line + '\\n'\n })\n .on('end', () => {\n let pendingStart: number | null = null\n\n for (const line of stderr.split('\\n')) {\n const startMatch = line.match(/silence_start:\\s*([\\d.]+)/)\n if (startMatch) {\n pendingStart = parseFloat(startMatch[1])\n }\n\n const endMatch = line.match(/silence_end:\\s*([\\d.]+)\\s*\\|\\s*silence_duration:\\s*([\\d.]+)/)\n if (endMatch) {\n const end = parseFloat(endMatch[1])\n const duration = parseFloat(endMatch[2])\n // When silence starts at t=0, FFmpeg emits silence_end before any silence_start\n const start = pendingStart ?? Math.max(0, end - duration)\n\n regions.push({ start, end, duration })\n pendingStart = null\n }\n }\n\n const badRegions = regions.filter(r => r.end <= r.start)\n if (badRegions.length > 0) {\n logger.warn(`[SilenceDetect] Found ${badRegions.length} invalid regions (end <= start) — filtering out`)\n }\n const validRegions = regions.filter(r => r.end > r.start)\n\n if (validRegions.length > 0) {\n logger.info(`Sample silence regions: ${validRegions.slice(0, 3).map(r => `${r.start.toFixed(1)}s-${r.end.toFixed(1)}s (${r.duration.toFixed(2)}s)`).join(', ')}`)\n }\n logger.info(`Detected ${validRegions.length} silence regions`)\n resolve(validRegions)\n })\n .on('error', (err) => {\n logger.error(`Silence detection failed: ${err.message}`)\n reject(new Error(`Silence detection failed: ${err.message}`))\n })\n .run()\n })\n}\n","import { execFileRaw } from '../../core/process.js'\nimport { copyFile, listDirectory, removeFile, removeDirectory, makeTempDir } from '../../core/fileSystem.js'\nimport { join, fontsDir } from '../../core/paths.js'\nimport { getFFmpegPath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nconst ffmpegPath = getFFmpegPath()\nconst FONTS_DIR = fontsDir()\n\nexport interface KeepSegment {\n start: number\n end: number\n}\n\n/**\n * Build FFmpeg filter_complex string for silence removal.\n * Pure function — no I/O, easy to test.\n */\nexport function buildFilterComplex(\n keepSegments: KeepSegment[],\n options?: { assFilename?: string; fontsdir?: string },\n): string {\n if (keepSegments.length === 0) {\n throw new Error('keepSegments must not be empty')\n }\n\n const filterParts: string[] = []\n const concatInputs: string[] = []\n const hasCaptions = options?.assFilename\n\n for (let i = 0; i < keepSegments.length; i++) {\n const seg = keepSegments[i]\n filterParts.push(\n `[0:v]trim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},setpts=PTS-STARTPTS[v${i}]`,\n )\n filterParts.push(\n `[0:a]atrim=start=${seg.start.toFixed(3)}:end=${seg.end.toFixed(3)},asetpts=PTS-STARTPTS[a${i}]`,\n )\n concatInputs.push(`[v${i}][a${i}]`)\n }\n\n const concatOutV = hasCaptions ? '[cv]' : '[outv]'\n const concatOutA = hasCaptions ? '[ca]' : '[outa]'\n\n filterParts.push(\n `${concatInputs.join('')}concat=n=${keepSegments.length}:v=1:a=1${concatOutV}${concatOutA}`,\n )\n\n if (hasCaptions) {\n const fontsdir = options?.fontsdir ?? '.'\n filterParts.push(`[cv]ass=${options!.assFilename}:fontsdir=${fontsdir}[outv]`)\n }\n\n return filterParts.join(';\\n')\n}\n\n/**\n * Single-pass silence removal using FFmpeg filter_complex.\n * Uses trim+setpts+concat for frame-accurate cuts instead of -c copy which\n * snaps to keyframes and causes cumulative timestamp drift.\n */\nexport async function singlePassEdit(\n inputPath: string,\n keepSegments: KeepSegment[],\n outputPath: string,\n): Promise<string> {\n const filterComplex = buildFilterComplex(keepSegments)\n\n const args = [\n '-y',\n '-i', inputPath,\n '-filter_complex', filterComplex,\n '-map', '[outv]',\n '-map', '[outa]',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n '-c:a', 'aac',\n '-b:a', '128k',\n outputPath,\n ]\n\n logger.info(`[SinglePassEdit] Editing ${keepSegments.length} segments → ${outputPath}`)\n\n return new Promise((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`)\n reject(new Error(`Single-pass edit failed: ${error.message}`))\n return\n }\n logger.info(`[SinglePassEdit] Complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n\n/**\n * Single-pass silence removal + caption burning using FFmpeg filter_complex.\n * Uses trim+setpts+concat for frame-accurate cuts, then chains ass filter for captions.\n * One re-encode, perfect timestamp alignment.\n */\nexport async function singlePassEditAndCaption(\n inputPath: string,\n keepSegments: KeepSegment[],\n assPath: string,\n outputPath: string,\n): Promise<string> {\n // Copy ASS + bundled fonts to temp dir to avoid Windows drive colon issue\n const tempDir = await makeTempDir('caption-')\n const tempAss = join(tempDir, 'captions.ass')\n await copyFile(assPath, tempAss)\n\n let fontFiles: string[]\n try {\n fontFiles = await listDirectory(FONTS_DIR)\n } catch (err: any) {\n if (err?.code === 'ENOENT') {\n throw new Error(`Fonts directory not found at ${FONTS_DIR}. Ensure assets/fonts/ exists in the project root.`)\n }\n throw err\n }\n for (const f of fontFiles) {\n if (f.endsWith('.ttf') || f.endsWith('.otf')) {\n await copyFile(join(FONTS_DIR, f), join(tempDir, f))\n }\n }\n\n const filterComplex = buildFilterComplex(keepSegments, {\n assFilename: 'captions.ass',\n fontsdir: '.',\n })\n\n const args = [\n '-y',\n '-i', inputPath,\n '-filter_complex', filterComplex,\n '-map', '[outv]',\n '-map', '[ca]',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n '-c:a', 'aac',\n '-b:a', '128k',\n outputPath,\n ]\n\n logger.info(`[SinglePassEdit] Processing ${keepSegments.length} segments with captions → ${outputPath}`)\n\n return new Promise((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { cwd: tempDir, maxBuffer: 50 * 1024 * 1024 }, async (error, _stdout, stderr) => {\n // Cleanup temp\n const files = await listDirectory(tempDir).catch(() => [] as string[])\n for (const f of files) {\n await removeFile(join(tempDir, f)).catch(() => {})\n }\n await removeDirectory(tempDir).catch(() => {})\n\n if (error) {\n logger.error(`[SinglePassEdit] FFmpeg failed: ${stderr}`)\n reject(new Error(`Single-pass edit failed: ${error.message}`))\n return\n }\n logger.info(`[SinglePassEdit] Complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n","import { ffprobe } from '../core/ffmpeg.js'\nimport type { ToolWithHandler } from '../providers/types.js'\nimport { join } from '../core/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport { detectSilence, SilenceRegion } from '../tools/ffmpeg/silenceDetection'\nimport { singlePassEdit } from '../tools/ffmpeg/singlePassEdit'\nimport type { VideoFile, Transcript, SilenceRemovalResult } from '../types'\nimport logger from '../config/logger'\n\n// ── Types for the LLM's decide_removals tool call ──────────────────────────\n\ninterface RemovalDecision {\n start: number\n end: number\n reason: string\n}\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a video editor AI that decides which silent regions in a video should be removed.\nYou will receive a transcript with timestamps and a list of detected silence regions.\n\nBe CONSERVATIVE. Only remove silence that is CLEARLY dead air — no speech, no demonstration, no purpose.\nAim to remove no more than 10-15% of total video duration.\nWhen in doubt, KEEP the silence.\n\nKEEP silences that are:\n- Dramatic pauses after impactful statements\n- Brief thinking pauses (< 2 seconds) in natural speech\n- Pauses before important reveals or demonstrations\n- Pauses where the speaker is clearly showing something on screen\n- Silence during screen demonstrations or typing — the viewer is watching the screen\n\nREMOVE silences that are:\n- Dead air with no purpose (> 3 seconds of nothing)\n- Gaps between topics where the speaker was gathering thoughts\n- Silence at the very beginning or end of the video\n\nReturn a JSON array of silence regions to REMOVE (not keep).\nWhen you have decided, call the **decide_removals** tool with your removal list.`\n\n// ── JSON Schema for the decide_removals tool ────────────────────────────────\n\nconst DECIDE_REMOVALS_SCHEMA = {\n type: 'object',\n properties: {\n removals: {\n type: 'array',\n description: 'Array of silence regions to remove',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds' },\n end: { type: 'number', description: 'End time in seconds' },\n reason: { type: 'string', description: 'Why this silence should be removed' },\n },\n required: ['start', 'end', 'reason'],\n },\n },\n },\n required: ['removals'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass SilenceRemovalAgent extends BaseAgent {\n private removals: RemovalDecision[] = []\n\n constructor(model?: string) {\n super('SilenceRemovalAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'decide_removals',\n description:\n 'Submit the list of silence regions to remove. Call this once with all removal decisions.',\n parameters: DECIDE_REMOVALS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('decide_removals', args as Record<string, unknown>)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (toolName === 'decide_removals') {\n this.removals = args.removals as RemovalDecision[]\n logger.info(`[SilenceRemovalAgent] Decided to remove ${this.removals.length} silence regions`)\n return { success: true, count: this.removals.length }\n }\n throw new Error(`Unknown tool: ${toolName}`)\n }\n\n getRemovals(): RemovalDecision[] {\n return this.removals\n }\n}\n\n// ── FFmpeg helpers ───────────────────────────────────────────────────────────\n\nasync function getVideoDuration(videoPath: string): Promise<number> {\n try {\n const metadata = await ffprobe(videoPath)\n return metadata.format.duration ?? 0\n } catch (err) {\n throw new Error(`ffprobe failed: ${(err as Error).message}`)\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Detect silence, use the agent to decide context-aware removals, and produce\n * an edited video with dead silence removed.\n *\n * Returns the path to the edited video, or the original path if no edits were needed.\n */\nexport async function removeDeadSilence(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n): Promise<SilenceRemovalResult> {\n const noEdit: SilenceRemovalResult = { editedPath: video.repoPath, removals: [], keepSegments: [], wasEdited: false }\n\n // 1. Detect silence regions (FFmpeg already filters to >= 0.5s via d=0.5)\n const silenceRegions = await detectSilence(video.repoPath, 0.5)\n\n if (silenceRegions.length === 0) {\n logger.info('[SilenceRemoval] No silence regions detected — skipping')\n return noEdit\n }\n\n const totalSilence = silenceRegions.reduce((sum, r) => sum + r.duration, 0)\n logger.info(`[SilenceRemoval] ${silenceRegions.length} silence regions detected (${totalSilence.toFixed(1)}s total silence)`)\n\n // Only send silence regions >= 2s to the agent; short pauses are natural speech rhythm\n let regionsForAgent = silenceRegions.filter(r => r.duration >= 2)\n if (regionsForAgent.length === 0) {\n logger.info('[SilenceRemoval] No silence regions >= 2s — skipping')\n return noEdit\n }\n\n // Cap at 30 longest regions to fit in context window\n if (regionsForAgent.length > 30) {\n regionsForAgent = [...regionsForAgent].sort((a, b) => b.duration - a.duration).slice(0, 30)\n regionsForAgent.sort((a, b) => a.start - b.start) // restore chronological order\n logger.info(`[SilenceRemoval] Capped to top 30 longest regions for agent analysis`)\n }\n\n // 2. Run the agent to decide which silences to remove\n const agent = new SilenceRemovalAgent(model)\n\n const transcriptLines = transcript.segments.map(\n (seg) => `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}`,\n )\n\n const silenceLines = regionsForAgent.map(\n (r, i) => `${i + 1}. ${r.start.toFixed(2)}s – ${r.end.toFixed(2)}s (${r.duration.toFixed(2)}s)`,\n )\n\n const prompt = [\n `Video: ${video.filename} (${transcript.duration.toFixed(1)}s total)\\n`,\n '--- TRANSCRIPT ---\\n',\n transcriptLines.join('\\n'),\n '\\n--- END TRANSCRIPT ---\\n',\n '--- SILENCE REGIONS ---\\n',\n silenceLines.join('\\n'),\n '\\n--- END SILENCE REGIONS ---\\n',\n 'Analyze the context around each silence region and decide which to remove.',\n ].join('\\n')\n\n let removals: RemovalDecision[]\n try {\n await agent.run(prompt)\n removals = agent.getRemovals()\n } finally {\n await agent.destroy()\n }\n\n if (removals.length === 0) {\n logger.info('[SilenceRemoval] Agent decided to keep all silences — skipping edit')\n return noEdit\n }\n\n // Safety: cap removals at 20% of video duration\n const maxRemoval = transcript.duration * 0.20\n let totalRemoval = 0\n const cappedRemovals: RemovalDecision[] = []\n const byDuration = [...removals].sort((a, b) => (b.end - b.start) - (a.end - a.start))\n for (const r of byDuration) {\n const dur = r.end - r.start\n if (totalRemoval + dur <= maxRemoval) {\n cappedRemovals.push(r)\n totalRemoval += dur\n }\n }\n if (cappedRemovals.length < removals.length) {\n logger.warn(`[SilenceRemoval] Capped from ${removals.length} to ${cappedRemovals.length} regions (${totalRemoval.toFixed(1)}s) to stay under 20% threshold`)\n }\n removals = cappedRemovals\n\n if (removals.length === 0) {\n logger.info('[SilenceRemoval] All removals exceeded 20% cap — skipping edit')\n return noEdit\n }\n\n // 3. Build list of segments to KEEP (inverse of removal regions)\n const videoDuration = await getVideoDuration(video.repoPath)\n const sortedRemovals = [...removals].sort((a, b) => a.start - b.start)\n\n const keepSegments: { start: number; end: number }[] = []\n let cursor = 0\n\n for (const removal of sortedRemovals) {\n if (removal.start > cursor) {\n keepSegments.push({ start: cursor, end: removal.start })\n }\n cursor = Math.max(cursor, removal.end)\n }\n\n if (cursor < videoDuration) {\n keepSegments.push({ start: cursor, end: videoDuration })\n }\n\n if (keepSegments.length === 0) {\n logger.warn('[SilenceRemoval] No segments to keep — returning original')\n return noEdit\n }\n\n // 4. Single-pass re-encode with trim+setpts+concat for frame-accurate cuts\n const editedPath = join(video.videoDir, `${video.slug}-edited.mp4`)\n await singlePassEdit(video.repoPath, keepSegments, editedPath)\n\n // Compute effective removals (merged, non-overlapping) from keep-segments\n const effectiveRemovals: { start: number; end: number }[] = []\n let prevEnd = 0\n for (const seg of keepSegments) {\n if (seg.start > prevEnd) {\n effectiveRemovals.push({ start: prevEnd, end: seg.start })\n }\n prevEnd = seg.end\n }\n // Don't add trailing silence as a \"removal\" — it's just the end of the video\n\n const actualRemoved = effectiveRemovals.reduce((sum, r) => sum + (r.end - r.start), 0)\n logger.info(\n `[SilenceRemoval] Removed ${effectiveRemovals.length} silence regions (${actualRemoved.toFixed(1)}s). Output: ${editedPath}`,\n )\n\n return {\n editedPath,\n removals: effectiveRemovals,\n keepSegments,\n wasEdited: true,\n }\n}\n","import { execFileRaw } from '../../core/process.js'\nimport { ensureDirectory, copyFile, listDirectory, removeFile, removeDirectory, makeTempDir, renameFile } from '../../core/fileSystem.js'\nimport { dirname, join, fontsDir } from '../../core/paths.js'\nimport { getFFmpegPath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\n\nconst ffmpegPath = getFFmpegPath()\nconst FONTS_DIR = fontsDir()\n\n/**\n * Burn ASS subtitles into video (hard-coded subtitles).\n * Uses direct execFile instead of fluent-ffmpeg to avoid Windows path escaping issues.\n * Copies the ASS file to a temp dir and uses a relative path to dodge the Windows\n * drive-letter colon being parsed as an FFmpeg filter option separator.\n */\nexport async function burnCaptions(\n videoPath: string,\n assPath: string,\n outputPath: string,\n): Promise<string> {\n const outputDir = dirname(outputPath)\n await ensureDirectory(outputDir)\n\n logger.info(`Burning captions into video → ${outputPath}`)\n\n // Create a dedicated temp dir so we can use colon-free relative paths\n const workDir = await makeTempDir('caption-')\n const tempAss = join(workDir, 'captions.ass')\n const tempOutput = join(workDir, 'output.mp4')\n\n await copyFile(assPath, tempAss)\n\n // Copy bundled fonts so libass can find them via fontsdir=.\n let fontFiles: string[]\n try {\n fontFiles = await listDirectory(FONTS_DIR)\n } catch (err: any) {\n if (err?.code === 'ENOENT') {\n throw new Error(`Fonts directory not found at ${FONTS_DIR}. Ensure assets/fonts/ exists in the project root.`)\n }\n throw err\n }\n for (const f of fontFiles) {\n if (f.endsWith('.ttf') || f.endsWith('.otf')) {\n await copyFile(join(FONTS_DIR, f), join(workDir, f))\n }\n }\n\n // Use just the filename — no drive letter, no colons\n const args = [\n '-y',\n '-i', videoPath,\n '-vf', 'ass=captions.ass:fontsdir=.',\n '-c:a', 'copy',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n tempOutput,\n ]\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { cwd: workDir, maxBuffer: 10 * 1024 * 1024 }, async (error, _stdout, stderr) => {\n const cleanup = async () => {\n const files = await listDirectory(workDir).catch(() => [] as string[])\n for (const f of files) {\n await removeFile(join(workDir, f)).catch(() => {})\n }\n await removeDirectory(workDir).catch(() => {})\n }\n\n if (error) {\n await cleanup()\n logger.error(`Caption burning failed: ${stderr || error.message}`)\n reject(new Error(`Caption burning failed: ${stderr || error.message}`))\n return\n }\n\n try {\n await renameFile(tempOutput, outputPath)\n } catch {\n await copyFile(tempOutput, outputPath)\n }\n await cleanup()\n logger.info(`Captions burned: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n","import { createFFmpeg, getFFmpegPath, getFFprobePath } from '../../core/ffmpeg.js'\nimport { execFileRaw } from '../../core/process.js'\nimport { ensureDirectory, writeTextFile, closeFileDescriptor, tmp } from '../../core/fileSystem.js'\nimport { dirname, join } from '../../core/paths.js'\n\nimport logger from '../../config/logger'\nimport { ShortSegment } from '../../types'\n\nconst ffmpegPath = getFFmpegPath()\nconst ffprobePath = getFFprobePath()\n\nconst DEFAULT_FPS = 25;\n\n/**\n * Probe the source video's frame rate using ffprobe.\n * Returns a rounded integer fps, or DEFAULT_FPS if probing fails.\n * Needed because FFmpeg 7.x xfade requires constant-framerate inputs.\n */\nasync function getVideoFps(videoPath: string): Promise<number> {\n return new Promise<number>((resolve) => {\n execFileRaw(\n ffprobePath,\n ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=r_frame_rate', '-of', 'csv=p=0', videoPath],\n { timeout: 5000 },\n (error, stdout) => {\n if (error || !stdout.trim()) {\n resolve(DEFAULT_FPS);\n return;\n }\n const parts = stdout.trim().split('/');\n const fps = parts.length === 2 ? parseInt(parts[0]) / parseInt(parts[1]) : parseFloat(stdout.trim());\n resolve(isFinite(fps) && fps > 0 ? Math.round(fps) : DEFAULT_FPS);\n },\n );\n });\n}\n\n/**\n * Extract a single clip segment using re-encode for frame-accurate timing.\n *\n * ### Why re-encode instead of `-c copy`?\n * Stream copy (`-c copy`) seeks to the nearest **keyframe** before the\n * requested start time, which creates a PTS offset between the clip's actual\n * start and the timestamp the caption generator assumes. This causes\n * captions to be out of sync with the audio — especially visible in\n * landscape-captioned shorts where there's no intermediate re-encode to\n * normalize PTS (the portrait path gets an extra re-encode via aspect-ratio\n * conversion which masks the issue).\n *\n * Re-encoding with `trim` + `setpts=PTS-STARTPTS` guarantees:\n * - The clip starts at **exactly** `bufferedStart` (not the nearest keyframe)\n * - Output PTS starts at 0 with no offset\n * - Caption timestamps align perfectly with both audio and video\n *\n * @param buffer Seconds of padding added before start and after end (default 1.0)\n */\nexport async function extractClip(\n videoPath: string,\n start: number,\n end: number,\n outputPath: string,\n buffer: number = 1.0,\n): Promise<string> {\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n const bufferedStart = Math.max(0, start - buffer);\n const bufferedEnd = end + buffer;\n const duration = bufferedEnd - bufferedStart;\n logger.info(`Extracting clip [${start}s–${end}s] (buffered: ${bufferedStart.toFixed(2)}s–${bufferedEnd.toFixed(2)}s) → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n createFFmpeg(videoPath)\n .setStartTime(bufferedStart)\n .setDuration(duration)\n .outputOptions(['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '23', '-threads', '4', '-c:a', 'aac', '-b:a', '128k'])\n .output(outputPath)\n .on('end', () => {\n logger.info(`Clip extraction complete: ${outputPath}`);\n resolve(outputPath);\n })\n .on('error', (err) => {\n logger.error(`Clip extraction failed: ${err.message}`);\n reject(new Error(`Clip extraction failed: ${err.message}`));\n })\n .run();\n });\n}\n\n/**\n * Extract multiple non-contiguous segments and concatenate them into one clip.\n * Each segment is padded by `buffer` seconds on both sides for smoother cuts.\n * Re-encodes and uses concat demuxer for clean joins.\n * @param buffer Seconds of padding added before start and after end of each segment (default 1.0)\n */\nexport async function extractCompositeClip(\n videoPath: string,\n segments: ShortSegment[],\n outputPath: string,\n buffer: number = 1.0,\n): Promise<string> {\n if (!segments || segments.length === 0) {\n throw new Error('At least one segment is required');\n }\n\n if (segments.length === 1) {\n return extractClip(videoPath, segments[0].start, segments[0].end, outputPath, buffer);\n }\n\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n const tempDirObj = tmp.dirSync({ unsafeCleanup: true, prefix: 'vidpipe-' });\n const tempDir = tempDirObj.name;\n\n const tempFiles: string[] = [];\n let concatListFile: tmp.FileResult | null = null;\n\n try {\n // Extract each segment to a temp file (re-encode for reliable concat)\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const tempPath = join(tempDir, `segment-${i}.mp4`);\n tempFiles.push(tempPath);\n\n const bufferedStart = Math.max(0, seg.start - buffer);\n const bufferedEnd = seg.end + buffer;\n logger.info(`Extracting segment ${i + 1}/${segments.length} [${seg.start}s–${seg.end}s] (buffered: ${bufferedStart.toFixed(2)}s–${bufferedEnd.toFixed(2)}s)`);\n\n await new Promise<void>((resolve, reject) => {\n createFFmpeg(videoPath)\n .setStartTime(bufferedStart)\n .setDuration(bufferedEnd - bufferedStart)\n .outputOptions(['-threads', '4', '-preset', 'ultrafast'])\n .output(tempPath)\n .on('end', () => resolve())\n .on('error', (err) => reject(new Error(`Segment ${i} extraction failed: ${err.message}`)))\n .run();\n });\n }\n\n // Build concat list file\n concatListFile = tmp.fileSync({ dir: tempDir, postfix: '.txt', prefix: 'concat-' });\n const concatListPath = concatListFile.name;\n const listContent = tempFiles.map((f) => `file '${f.replace(/'/g, \"'\\\\''\")}'`).join('\\n');\n await writeTextFile(concatListPath, listContent);\n // Close file descriptor to avoid leaks on Windows\n closeFileDescriptor(concatListFile.fd);\n\n // Concatenate segments (re-encode for clean joins across buffered segments)\n logger.info(`Concatenating ${segments.length} segments → ${outputPath}`);\n await new Promise<void>((resolve, reject) => {\n createFFmpeg()\n .input(concatListPath)\n .inputOptions(['-f', 'concat', '-safe', '0'])\n .outputOptions(['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '23', '-threads', '4', '-c:a', 'aac'])\n .output(outputPath)\n .on('end', () => resolve())\n .on('error', (err) => reject(new Error(`Concat failed: ${err.message}`)))\n .run();\n });\n\n logger.info(`Composite clip complete: ${outputPath}`);\n return outputPath;\n } finally {\n // Clean up temp files and remove callbacks\n if (concatListFile) {\n try {\n concatListFile.removeCallback();\n } catch {}\n }\n try {\n tempDirObj.removeCallback();\n } catch {}\n }\n}\n\n/**\n * Extract multiple non-contiguous segments and concatenate them with crossfade\n * transitions using FFmpeg xfade/acrossfade filters.\n * Falls back to extractCompositeClip if only one segment is provided.\n *\n * @param transitionDuration Crossfade duration in seconds (default 0.5)\n * @param buffer Seconds of padding added before/after each segment (default 1.0)\n */\nexport async function extractCompositeClipWithTransitions(\n videoPath: string,\n segments: ShortSegment[],\n outputPath: string,\n transitionDuration: number = 0.5,\n buffer: number = 1.0,\n): Promise<string> {\n if (!segments || segments.length === 0) {\n throw new Error('At least one segment is required');\n }\n\n // Single segment — no transitions needed\n if (segments.length === 1) {\n return extractClip(videoPath, segments[0].start, segments[0].end, outputPath, buffer);\n }\n\n // Two segments — no transitions needed, use regular composite\n if (segments.length === 2 && transitionDuration <= 0) {\n return extractCompositeClip(videoPath, segments, outputPath, buffer);\n }\n\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n // Detect source fps so we can force CFR after trim (FFmpeg 7.x xfade requires it)\n const fps = await getVideoFps(videoPath);\n\n // Build filter_complex for xfade transitions between segments\n const filterParts: string[] = [];\n const segDurations: number[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n const bufferedStart = Math.max(0, seg.start - buffer);\n const bufferedEnd = seg.end + buffer;\n const duration = bufferedEnd - bufferedStart;\n segDurations.push(duration);\n\n filterParts.push(\n `[0:v]trim=start=${bufferedStart.toFixed(3)}:end=${bufferedEnd.toFixed(3)},setpts=PTS-STARTPTS,fps=${fps}[v${i}]`,\n );\n filterParts.push(\n `[0:a]atrim=start=${bufferedStart.toFixed(3)}:end=${bufferedEnd.toFixed(3)},asetpts=PTS-STARTPTS[a${i}]`,\n );\n }\n\n // Chain xfade transitions: [v0][v1]xfade → [xv0]; [xv0][v2]xfade → [xv1]; ...\n let prevVideo = 'v0';\n let prevAudio = 'a0';\n let cumulativeDuration = segDurations[0];\n\n for (let i = 1; i < segments.length; i++) {\n const offset = Math.max(0, cumulativeDuration - transitionDuration);\n const outVideo = i === segments.length - 1 ? 'vout' : `xv${i - 1}`;\n const outAudio = i === segments.length - 1 ? 'aout' : `xa${i - 1}`;\n\n filterParts.push(\n `[${prevVideo}][v${i}]xfade=transition=fade:duration=${transitionDuration.toFixed(3)}:offset=${offset.toFixed(3)}[${outVideo}]`,\n );\n filterParts.push(\n `[${prevAudio}][a${i}]acrossfade=d=${transitionDuration.toFixed(3)}[${outAudio}]`,\n );\n\n prevVideo = outVideo;\n prevAudio = outAudio;\n // After xfade, the combined duration shrinks by transitionDuration\n cumulativeDuration = cumulativeDuration - transitionDuration + segDurations[i];\n }\n\n const filterComplex = filterParts.join(';\\n');\n\n const args = [\n '-y',\n '-i', videoPath,\n '-filter_complex', filterComplex,\n '-map', '[vout]',\n '-map', '[aout]',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n '-c:a', 'aac',\n '-b:a', '128k',\n outputPath,\n ];\n\n logger.info(`[ClipExtraction] Compositing ${segments.length} segments with xfade transitions → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`[ClipExtraction] xfade composite failed: ${stderr}`);\n reject(new Error(`xfade composite clip failed: ${error.message}`));\n return;\n }\n logger.info(`[ClipExtraction] xfade composite complete: ${outputPath}`);\n resolve(outputPath);\n });\n });\n}\n","import { execFileRaw } from '../../core/process.js'\nimport { ensureDirectory, copyFile } from '../../core/fileSystem.js'\nimport { dirname, join } from '../../core/paths.js'\nimport { getFFmpegPath } from '../../core/ffmpeg.js'\nimport logger from '../../config/logger'\nimport { detectWebcamRegion, getVideoResolution } from './faceDetection'\n\nconst ffmpegPath = getFFmpegPath()\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Supported output aspect ratios.\n * - `16:9` — standard landscape (YouTube, desktop)\n * - `9:16` — portrait / vertical (TikTok, Reels, Shorts)\n * - `1:1` — square (LinkedIn, Twitter)\n * - `4:5` — tall feed (Instagram feed)\n */\nexport type AspectRatio = '16:9' | '9:16' | '1:1' | '4:5'\n\n/** Social-media platforms we generate video variants for. */\nexport type Platform =\n | 'tiktok'\n | 'youtube-shorts'\n | 'instagram-reels'\n | 'instagram-feed'\n | 'linkedin'\n | 'youtube'\n | 'twitter'\n\n/**\n * Maps each platform to its preferred aspect ratio.\n * Multiple platforms may share a ratio (e.g. TikTok + Reels both use 9:16),\n * which lets {@link generatePlatformVariants} deduplicate encodes.\n */\nexport const PLATFORM_RATIOS: Record<Platform, AspectRatio> = {\n 'tiktok': '9:16',\n 'youtube-shorts': '9:16',\n 'instagram-reels': '9:16',\n 'instagram-feed': '4:5',\n 'linkedin': '1:1',\n 'youtube': '16:9',\n 'twitter': '1:1',\n}\n\n/**\n * Canonical pixel dimensions for each aspect ratio.\n * Width is always 1080 px for non-landscape ratios (the standard vertical\n * video width); landscape stays at 1920×1080 for full HD.\n */\nexport const DIMENSIONS: Record<AspectRatio, { width: number; height: number }> = {\n '16:9': { width: 1920, height: 1080 },\n '9:16': { width: 1080, height: 1920 },\n '1:1': { width: 1080, height: 1080 },\n '4:5': { width: 1080, height: 1350 },\n}\n\nexport interface ConvertOptions {\n /** Fallback to letterbox/pillarbox instead of cropping (default: false) */\n letterbox?: boolean\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Build the FFmpeg `-vf` filter string for a simple center-crop conversion.\n *\n * This is the **fallback** used when smart layout (webcam detection + split-screen)\n * is unavailable. It center-crops the source frame to the target aspect ratio,\n * discarding content on the sides (or top/bottom).\n *\n * **Letterbox mode**: instead of cropping, scales the video to fit inside the\n * target dimensions and pads the remaining space with black bars. Useful when\n * you don't want to lose any content (e.g. screen recordings with important\n * edges).\n *\n * **Crop formulas** assume a 16:9 landscape source. `ih` = input height,\n * `iw` = input width. We compute the crop width from the height to maintain\n * the target ratio, then center the crop horizontally.\n *\n * @param targetRatio - The desired output aspect ratio\n * @param letterbox - If true, pad with black bars instead of cropping\n * @returns An FFmpeg `-vf` filter string\n */\nfunction buildCropFilter(targetRatio: AspectRatio, letterbox: boolean): string {\n if (letterbox) {\n const { width, height } = DIMENSIONS[targetRatio]\n // Scale to fit within target dimensions, then pad with black bars\n return `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`\n }\n\n switch (targetRatio) {\n case '9:16':\n // Center-crop landscape to portrait: crop width = ih*9/16, keep full height\n return 'crop=ih*9/16:ih:(iw-ih*9/16)/2:0,scale=1080:1920'\n case '1:1':\n // Center-crop to square: use height as the dimension (smaller axis for 16:9)\n return 'crop=ih:ih:(iw-ih)/2:0,scale=1080:1080'\n case '4:5':\n // Center-crop landscape to 4:5: crop width = ih*4/5, keep full height\n return 'crop=ih*4/5:ih:(iw-ih*4/5)/2:0,scale=1080:1350'\n case '16:9':\n // Same ratio — just ensure standard dimensions\n return 'scale=1920:1080'\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Convert a video's aspect ratio using FFmpeg center-crop.\n *\n * - 16:9 → 9:16: crops the center column to portrait\n * - 16:9 → 1:1: crops to a center square\n * - Same ratio: stream-copies without re-encoding\n *\n * @returns The output path on success\n */\nexport async function convertAspectRatio(\n inputPath: string,\n outputPath: string,\n targetRatio: AspectRatio,\n options: ConvertOptions = {},\n): Promise<string> {\n const outputDir = dirname(outputPath)\n await ensureDirectory(outputDir)\n\n const sourceRatio: AspectRatio = '16:9' // our videos are always landscape\n\n // Same ratio — stream copy\n if (sourceRatio === targetRatio && !options.letterbox) {\n logger.info(`Aspect ratio already ${targetRatio}, copying → ${outputPath}`)\n await copyFile(inputPath, outputPath)\n return outputPath\n }\n\n const vf = buildCropFilter(targetRatio, options.letterbox ?? false)\n logger.info(`Converting aspect ratio to ${targetRatio} (filter: ${vf}) → ${outputPath}`)\n\n const args = [\n '-y',\n '-i', inputPath,\n '-vf', vf,\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-c:a', 'copy',\n '-threads', '4',\n outputPath,\n ]\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`Aspect ratio conversion failed: ${stderr || error.message}`)\n reject(new Error(`Aspect ratio conversion failed: ${stderr || error.message}`))\n return\n }\n logger.info(`Aspect ratio conversion complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n\n// ── Smart Layout ─────────────────────────────────────────────────────────────\n\n/**\n * Configuration for the smart split-screen layout.\n *\n * The split-screen stacks two regions vertically: the **screen content** on top\n * and the **webcam face** on the bottom. Each field controls the geometry of\n * the final composite:\n *\n * @property label - Human-readable name for logging (e.g. \"SmartPortrait\")\n * @property targetW - Output width in pixels. All smart layouts use 1080 px\n * (vertical video standard) so both the screen and cam panels share the\n * same width.\n * @property screenH - Height of the top panel (screen recording). Combined\n * with `camH`, this determines the total output height and the visual\n * ratio between screen content and webcam. Roughly ~65% of total height.\n * @property camH - Height of the bottom panel (webcam). Roughly ~35% of\n * total height. The webcam is AR-matched and center-cropped to fill this\n * panel edge-to-edge without black bars.\n * @property fallbackRatio - Aspect ratio to use with the simple center-crop\n * path ({@link buildCropFilter}) when webcam detection fails.\n */\ninterface SmartLayoutConfig {\n label: string\n targetW: number\n screenH: number\n camH: number\n fallbackRatio: AspectRatio\n}\n\n/**\n * Shared smart conversion: detects a webcam overlay in the source video and\n * builds a **split-screen** layout (screen on top, webcam on bottom).\n *\n * ### Why split-screen?\n * Screen recordings with a webcam overlay (e.g. top-right corner) waste space\n * when naively center-cropped to portrait/square. The split-screen approach\n * gives the screen content and webcam each their own dedicated panel, making\n * both fully visible in a narrow frame.\n *\n * ### Algorithm\n * 1. Run {@link detectWebcamRegion} to find the webcam bounding box.\n * 2. **Screen crop**: exclude the webcam columns so only the screen content\n * remains, then scale to `targetW × screenH` (letterboxing if needed).\n * 3. **Webcam crop**: aspect-ratio-match the webcam region to `targetW × camH`.\n * If the webcam is wider than the target, we keep full height and\n * center-crop width; if taller, we keep full width and center-crop height.\n * This ensures the webcam fills its panel edge-to-edge with **no black bars**.\n * 4. **vstack**: vertically stack `[screen][cam]` into the final frame.\n *\n * Falls back to simple center-crop ({@link buildCropFilter}) if no webcam is\n * detected.\n *\n * @param inputPath - Source video (assumed 16:9 landscape with optional webcam overlay)\n * @param outputPath - Destination path for the converted video\n * @param config - Layout geometry (see {@link SmartLayoutConfig})\n * @returns The output path on success\n */\nasync function convertWithSmartLayout(\n inputPath: string,\n outputPath: string,\n config: SmartLayoutConfig,\n): Promise<string> {\n const { label, targetW, screenH, camH, fallbackRatio } = config\n const outputDir = dirname(outputPath)\n await ensureDirectory(outputDir)\n\n const webcam = await detectWebcamRegion(inputPath)\n\n if (!webcam) {\n logger.info(`[${label}] No webcam found, falling back to center-crop`)\n return convertAspectRatio(inputPath, outputPath, fallbackRatio)\n }\n\n const resolution = await getVideoResolution(inputPath)\n\n // Determine screen crop region (exclude webcam area using detected bounds)\n let screenCropX: number\n let screenCropW: number\n if (webcam.position === 'top-right' || webcam.position === 'bottom-right') {\n screenCropX = 0\n screenCropW = webcam.x\n } else {\n screenCropX = webcam.x + webcam.width\n screenCropW = Math.max(0, resolution.width - screenCropX)\n }\n\n // Crop webcam to match target bottom-section aspect ratio, then scale to fill\n const targetAR = targetW / camH\n const webcamAR = webcam.width / webcam.height\n\n let faceX: number, faceY: number, faceW: number, faceH: number\n if (webcamAR > targetAR) {\n // Webcam wider than target: keep full height, center-crop width\n faceH = webcam.height\n faceW = Math.round(faceH * targetAR)\n faceX = webcam.x + Math.round((webcam.width - faceW) / 2)\n faceY = webcam.y\n } else {\n // Webcam taller than target: keep full width, center-crop height\n faceW = webcam.width\n faceH = Math.round(faceW / targetAR)\n faceX = webcam.x\n faceY = webcam.y + Math.round((webcam.height - faceH) / 2)\n }\n\n const filterComplex = [\n `[0:v]crop=${screenCropW}:ih:${screenCropX}:0,scale=${targetW}:${screenH}:force_original_aspect_ratio=decrease,` +\n `pad=${targetW}:${screenH}:(ow-iw)/2:(oh-ih)/2:black[screen]`,\n `[0:v]crop=${faceW}:${faceH}:${faceX}:${faceY},scale=${targetW}:${camH}[cam]`,\n '[screen][cam]vstack[out]',\n ].join(';')\n\n logger.info(`[${label}] Split-screen layout: webcam at ${webcam.position} → ${outputPath}`)\n\n const args = [\n '-y',\n '-i', inputPath,\n '-filter_complex', filterComplex,\n '-map', '[out]',\n '-map', '0:a',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-c:a', 'aac',\n '-b:a', '128k',\n '-threads', '4',\n outputPath,\n ]\n\n return new Promise<string>((resolve, reject) => {\n execFileRaw(ffmpegPath, args, { maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {\n if (error) {\n logger.error(`[${label}] FFmpeg failed: ${stderr || error.message}`)\n reject(new Error(`${label} conversion failed: ${stderr || error.message}`))\n return\n }\n logger.info(`[${label}] Complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n\n/**\n * Smart portrait (9:16) conversion → 1080×1920.\n *\n * Screen panel: 1080×1248 (65%), Webcam panel: 1080×672 (35%).\n * Total: 1080×1920 — standard TikTok / Reels / Shorts dimensions.\n *\n * Falls back to center-crop 9:16 if no webcam is detected.\n *\n * @param inputPath - Source landscape video\n * @param outputPath - Destination path for the portrait video\n */\nexport async function convertToPortraitSmart(\n inputPath: string,\n outputPath: string,\n): Promise<string> {\n return convertWithSmartLayout(inputPath, outputPath, {\n label: 'SmartPortrait',\n targetW: 1080,\n screenH: 1248,\n camH: 672,\n fallbackRatio: '9:16',\n })\n}\n\n/**\n * Smart square (1:1) conversion → 1080×1080.\n *\n * Screen panel: 1080×700 (65%), Webcam panel: 1080×380 (35%).\n * Total: 1080×1080 — standard LinkedIn / Twitter square format.\n *\n * Falls back to center-crop 1:1 if no webcam is detected.\n *\n * @param inputPath - Source landscape video\n * @param outputPath - Destination path for the square video\n */\nexport async function convertToSquareSmart(\n inputPath: string,\n outputPath: string,\n): Promise<string> {\n return convertWithSmartLayout(inputPath, outputPath, {\n label: 'SmartSquare',\n targetW: 1080,\n screenH: 700,\n camH: 380,\n fallbackRatio: '1:1',\n })\n}\n\n/**\n * Smart feed (4:5) conversion → 1080×1350.\n *\n * Screen panel: 1080×878 (65%), Webcam panel: 1080×472 (35%).\n * Total: 1080×1350 — Instagram feed's preferred tall format.\n *\n * Falls back to center-crop 4:5 if no webcam is detected.\n *\n * @param inputPath - Source landscape video\n * @param outputPath - Destination path for the 4:5 video\n */\nexport async function convertToFeedSmart(\n inputPath: string,\n outputPath: string,\n): Promise<string> {\n return convertWithSmartLayout(inputPath, outputPath, {\n label: 'SmartFeed',\n targetW: 1080,\n screenH: 878,\n camH: 472,\n fallbackRatio: '4:5',\n })\n}\n\n/** Options for {@link generatePlatformVariants}. */\nexport interface GeneratePlatformVariantsOptions {\n /**\n * Use the vision-based LayoutAgent instead of ONNX face detection.\n * **EXPERIMENTAL/DISABLED**: The agent analyzes frame content and constructs FFmpeg commands dynamically.\n * The vision-based approach is not yet reliable; using the existing ONNX face detection pipeline instead.\n * Default: false (uses existing ONNX/heuristic pipeline).\n * @deprecated Set to false; the LayoutAgent feature is experimental and disabled.\n */\n useAgent?: boolean\n}\n\n/**\n * Generate platform-specific aspect-ratio variants of a short clip.\n *\n * ### Routing logic\n * 1. Maps each requested platform to its aspect ratio via {@link PLATFORM_RATIOS}.\n * 2. **Deduplicates by ratio** — if TikTok and Reels both need 9:16, only one\n * encode is performed and both platforms reference the same output file.\n * 3. Skips 16:9 entirely since the source is already landscape.\n * 4. Routes each ratio to its smart converter (portrait / square / feed) for\n * split-screen layout, falling back to {@link convertAspectRatio} for any\n * ratio without a smart converter.\n *\n * ### Agent mode (DISABLED)\n * **NOTE**: The vision-based {@link LayoutAgent} is experimental and has been disabled.\n * The `useAgent` option is kept for API compatibility but currently has no effect.\n * All conversions use the ONNX face detection pipeline ({@link convertToPortraitSmart}, etc.).\n *\n * @param inputPath - Source video (16:9 landscape)\n * @param outputDir - Directory to write variant files into\n * @param slug - Base filename slug (e.g. \"my-video-short-1\")\n * @param platforms - Platforms to generate for (default: tiktok + linkedin)\n * @param options - Additional options (useAgent is deprecated; all conversions use ONNX pipeline)\n * @returns Array of variant metadata (one entry per platform, deduplicated files)\n */\nexport async function generatePlatformVariants(\n inputPath: string,\n outputDir: string,\n slug: string,\n platforms: Platform[] = ['tiktok', 'linkedin'],\n options: GeneratePlatformVariantsOptions = {},\n): Promise<{ platform: Platform; aspectRatio: AspectRatio; path: string; width: number; height: number }[]> {\n await ensureDirectory(outputDir)\n\n // Deduplicate by aspect ratio to avoid redundant encodes\n const ratioMap = new Map<AspectRatio, Platform[]>()\n for (const p of platforms) {\n const ratio = PLATFORM_RATIOS[p]\n if (ratio === '16:9') continue // skip — original is already 16:9\n const list = ratioMap.get(ratio) ?? []\n list.push(p)\n ratioMap.set(ratio, list)\n }\n\n const variants: { platform: Platform; aspectRatio: AspectRatio; path: string; width: number; height: number }[] = []\n\n for (const [ratio, associatedPlatforms] of ratioMap) {\n const suffix = ratio === '9:16' ? 'portrait' : ratio === '4:5' ? 'feed' : 'square'\n const outPath = join(outputDir, `${slug}-${suffix}.mp4`)\n\n try {\n if (ratio === '9:16') {\n // NOTE: LayoutAgent support is DISABLED - vision-based approach not working well yet\n // The useAgent option is kept for backwards compatibility but is ignored.\n // All portrait conversions use the ONNX face detection pipeline.\n if (options.useAgent) {\n logger.warn(`[generatePlatformVariants] LayoutAgent is disabled, falling back to ONNX pipeline`)\n }\n await convertToPortraitSmart(inputPath, outPath)\n } else if (ratio === '1:1') {\n await convertToSquareSmart(inputPath, outPath)\n } else if (ratio === '4:5') {\n await convertToFeedSmart(inputPath, outPath)\n } else {\n await convertAspectRatio(inputPath, outPath, ratio)\n }\n const dims = DIMENSIONS[ratio]\n for (const p of associatedPlatforms) {\n variants.push({ platform: p, aspectRatio: ratio, path: outPath, width: dims.width, height: dims.height })\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`Skipping ${ratio} variant for ${slug}: ${message}`)\n }\n }\n\n return variants\n}\n","import slugifyLib from 'slugify'\nimport { v4 as uuidv4 } from 'uuid'\n\n/** Slugify text for use in URLs and file names. */\nexport function slugify(text: string, opts?: { lower?: boolean; strict?: boolean; replacement?: string }): string {\n return slugifyLib(text, { lower: true, strict: true, ...opts })\n}\n\n/** Generate a UUID v4. */\nexport function generateId(): string {\n return uuidv4()\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { BaseAgent } from './BaseAgent'\nimport { VideoFile, Transcript, ShortClip, ShortSegment, ShortClipVariant } from '../types'\nimport { extractClip, extractCompositeClip } from '../tools/ffmpeg/clipExtraction'\nimport { generateStyledASSForSegment, generateStyledASSForComposite, generatePortraitASSWithHook, generatePortraitASSWithHookComposite } from '../tools/captions/captionGenerator'\nimport { burnCaptions } from '../tools/ffmpeg/captionBurning'\nimport { generatePlatformVariants, type Platform } from '../tools/ffmpeg/aspectRatio'\nimport { generateId } from '../core/text.js'\nimport { slugify } from '../core/text.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport logger from '../config/logger'\n\n// ── Types for the LLM's plan_shorts tool call ──────────────────────────────\n\ninterface PlannedSegment {\n start: number\n end: number\n description: string\n}\n\ninterface PlannedShort {\n title: string\n description: string\n tags: string[]\n segments: PlannedSegment[]\n}\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a short-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and identify the most compelling moments to extract as shorts (15–60 seconds each).\n\n## What to look for\n- **Key insights** — concise, quotable takeaways\n- **Funny moments** — humor, wit, unexpected punchlines\n- **Controversial takes** — bold opinions that spark discussion\n- **Educational nuggets** — clear explanations of complex topics\n- **Emotional peaks** — passion, vulnerability, excitement\n- **Topic compilations** — multiple brief mentions of one theme that can be stitched together\n\n## Short types\n- **Single segment** — one contiguous section of the video\n- **Composite** — multiple non-contiguous segments combined into one short (great for topic compilations or building a narrative arc)\n\n## Rules\n1. Each short must be 15–60 seconds total duration.\n2. Timestamps must align to word boundaries from the transcript.\n3. Prefer natural sentence boundaries for clean cuts.\n4. Aim for 3–8 shorts per video, depending on length and richness.\n5. Every short needs a catchy, descriptive title (5–10 words).\n6. Tags should be lowercase, no hashes, 3–6 per short.\n7. A 1-second buffer is automatically added before and after each segment boundary during extraction, so plan segments based on content timestamps without worrying about clipping words at the edges.\n\nWhen you have identified the shorts, call the **plan_shorts** tool with your complete plan.\n\n## Using Clip Direction\nYou may receive AI-generated clip direction with suggested shorts. Use these as a starting point but make your own decisions:\n- The suggestions are based on visual + audio analysis and may identify moments you'd miss from transcript alone\n- Feel free to adjust timestamps, combine suggestions, or ignore ones that don't work\n- You may also find good shorts NOT in the suggestions — always analyze the full transcript`\n\n// ── JSON Schema for the plan_shorts tool ────────────────────────────────────\n\nconst PLAN_SHORTS_SCHEMA = {\n type: 'object',\n properties: {\n shorts: {\n type: 'array',\n description: 'Array of planned short clips',\n items: {\n type: 'object',\n properties: {\n title: { type: 'string', description: 'Catchy short title (5–10 words)' },\n description: { type: 'string', description: 'Brief description of the short content' },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Lowercase tags without hashes, 3–6 per short',\n },\n segments: {\n type: 'array',\n description: 'One or more time segments that compose this short',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds' },\n end: { type: 'number', description: 'End time in seconds' },\n description: { type: 'string', description: 'What happens in this segment' },\n },\n required: ['start', 'end', 'description'],\n },\n },\n },\n required: ['title', 'description', 'tags', 'segments'],\n },\n },\n },\n required: ['shorts'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass ShortsAgent extends BaseAgent {\n private plannedShorts: PlannedShort[] = []\n\n constructor(model?: string) {\n super('ShortsAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'plan_shorts',\n description:\n 'Submit the planned shorts as a structured JSON array. Call this once with all planned shorts.',\n parameters: PLAN_SHORTS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('plan_shorts', args as Record<string, unknown>)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (toolName === 'plan_shorts') {\n this.plannedShorts = args.shorts as PlannedShort[]\n logger.info(`[ShortsAgent] Planned ${this.plannedShorts.length} shorts`)\n return { success: true, count: this.plannedShorts.length }\n }\n throw new Error(`Unknown tool: ${toolName}`)\n }\n\n getPlannedShorts(): PlannedShort[] {\n return this.plannedShorts\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\nexport async function generateShorts(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n clipDirection?: string,\n): Promise<ShortClip[]> {\n const agent = new ShortsAgent(model)\n\n // Build prompt with full transcript including word-level timestamps\n const transcriptLines = transcript.segments.map((seg) => {\n const words = seg.words\n .map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`)\n .join(' ')\n return `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}\\nWords: ${words}`\n })\n\n const promptParts = [\n `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and plan shorts.\\n`,\n `Video: ${video.filename}`,\n `Duration: ${transcript.duration.toFixed(1)}s\\n`,\n '--- TRANSCRIPT ---\\n',\n transcriptLines.join('\\n\\n'),\n '\\n--- END TRANSCRIPT ---',\n ]\n\n if (clipDirection) {\n promptParts.push(\n '\\n--- CLIP DIRECTION (AI-generated suggestions — use as reference, make your own decisions) ---\\n',\n clipDirection,\n '\\n--- END CLIP DIRECTION ---',\n )\n }\n\n const prompt = promptParts.join('\\n')\n\n try {\n await agent.run(prompt)\n const planned = agent.getPlannedShorts()\n\n if (planned.length === 0) {\n logger.warn('[ShortsAgent] No shorts were planned')\n return []\n }\n\n const shortsDir = join(dirname(video.repoPath), 'shorts')\n await ensureDirectory(shortsDir)\n\n const shorts: ShortClip[] = []\n\n for (const plan of planned) {\n const id = generateId()\n const shortSlug = slugify(plan.title)\n const totalDuration = plan.segments.reduce((sum, s) => sum + (s.end - s.start), 0)\n const outputPath = join(shortsDir, `${shortSlug}.mp4`)\n\n const segments: ShortSegment[] = plan.segments.map((s) => ({\n start: s.start,\n end: s.end,\n description: s.description,\n }))\n\n // Extract the clip (single or composite)\n if (segments.length === 1) {\n await extractClip(video.repoPath, segments[0].start, segments[0].end, outputPath)\n } else {\n await extractCompositeClip(video.repoPath, segments, outputPath)\n }\n\n // Generate platform-specific aspect ratio variants from UNCAPTIONED video\n // so portrait/square crops are clean before captions are burned per-variant\n let variants: ShortClipVariant[] | undefined\n try {\n const defaultPlatforms: Platform[] = ['tiktok', 'youtube-shorts', 'instagram-reels', 'instagram-feed', 'linkedin']\n const results = await generatePlatformVariants(outputPath, shortsDir, shortSlug, defaultPlatforms)\n if (results.length > 0) {\n variants = results.map((v) => ({\n path: v.path,\n aspectRatio: v.aspectRatio,\n platform: v.platform as ShortClipVariant['platform'],\n width: v.width,\n height: v.height,\n }))\n logger.info(`[ShortsAgent] Generated ${variants.length} platform variants for: ${plan.title}`)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[ShortsAgent] Platform variant generation failed for ${plan.title}: ${message}`)\n }\n\n // Generate ASS captions for the landscape short and burn them in\n let captionedPath: string | undefined\n try {\n const assContent = segments.length === 1\n ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end)\n : generateStyledASSForComposite(transcript, segments)\n\n const assPath = join(shortsDir, `${shortSlug}.ass`)\n await writeTextFile(assPath, assContent)\n\n captionedPath = join(shortsDir, `${shortSlug}-captioned.mp4`)\n await burnCaptions(outputPath, assPath, captionedPath)\n logger.info(`[ShortsAgent] Burned captions for short: ${plan.title}`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[ShortsAgent] Caption burning failed for ${plan.title}: ${message}`)\n captionedPath = undefined\n }\n\n // Burn portrait-style captions (green highlight, centered, hook overlay) onto portrait variant\n if (variants) {\n // Burn captions for 9:16 portrait variants (tiktok, youtube-shorts, instagram-reels)\n const portraitVariants = variants.filter(v => v.aspectRatio === '9:16')\n if (portraitVariants.length > 0) {\n try {\n const portraitAssContent = segments.length === 1\n ? generatePortraitASSWithHook(transcript, plan.title, segments[0].start, segments[0].end)\n : generatePortraitASSWithHookComposite(transcript, segments, plan.title)\n const portraitAssPath = join(shortsDir, `${shortSlug}-portrait.ass`)\n await writeTextFile(portraitAssPath, portraitAssContent)\n // All 9:16 variants share the same source file — burn once, update all paths\n const portraitCaptionedPath = portraitVariants[0].path.replace('.mp4', '-captioned.mp4')\n await burnCaptions(portraitVariants[0].path, portraitAssPath, portraitCaptionedPath)\n for (const v of portraitVariants) {\n v.path = portraitCaptionedPath\n }\n logger.info(`[ShortsAgent] Burned portrait captions with hook for: ${plan.title}`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[ShortsAgent] Portrait caption burning failed for ${plan.title}: ${message}`)\n }\n }\n\n // Burn captions for non-portrait variants (4:5 feed, 1:1 square)\n const nonPortraitVariants = variants.filter(v => v.aspectRatio !== '9:16')\n for (const variant of nonPortraitVariants) {\n try {\n const variantAssContent = segments.length === 1\n ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end)\n : generateStyledASSForComposite(transcript, segments)\n const suffix = variant.aspectRatio === '4:5' ? 'feed' : 'square'\n const variantAssPath = join(shortsDir, `${shortSlug}-${suffix}.ass`)\n await writeTextFile(variantAssPath, variantAssContent)\n const variantCaptionedPath = variant.path.replace('.mp4', '-captioned.mp4')\n await burnCaptions(variant.path, variantAssPath, variantCaptionedPath)\n variant.path = variantCaptionedPath\n logger.info(`[ShortsAgent] Burned ${suffix} captions for: ${plan.title}`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[ShortsAgent] ${variant.aspectRatio} caption burning failed for ${plan.title}: ${message}`)\n }\n }\n }\n\n // Generate description markdown\n const mdPath = join(shortsDir, `${shortSlug}.md`)\n const mdContent = [\n `# ${plan.title}\\n`,\n plan.description,\n '',\n '## Segments\\n',\n ...plan.segments.map(\n (s, i) => `${i + 1}. **${s.start.toFixed(2)}s – ${s.end.toFixed(2)}s** — ${s.description}`,\n ),\n '',\n '## Tags\\n',\n plan.tags.map((t) => `- ${t}`).join('\\n'),\n '',\n ].join('\\n')\n await writeTextFile(mdPath, mdContent)\n\n shorts.push({\n id,\n title: plan.title,\n slug: shortSlug,\n segments,\n totalDuration,\n outputPath,\n captionedPath,\n description: plan.description,\n tags: plan.tags,\n variants,\n })\n\n logger.info(`[ShortsAgent] Created short: ${plan.title} (${totalDuration.toFixed(1)}s)`)\n }\n\n logger.info(`[ShortsAgent] Generated ${shorts.length} shorts`)\n return shorts\n } finally {\n await agent.destroy()\n }\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { BaseAgent } from './BaseAgent'\nimport { VideoFile, Transcript, MediumClip, MediumSegment } from '../types'\nimport { extractClip, extractCompositeClipWithTransitions } from '../tools/ffmpeg/clipExtraction'\nimport { generateStyledASSForSegment, generateStyledASSForComposite } from '../tools/captions/captionGenerator'\nimport { burnCaptions } from '../tools/ffmpeg/captionBurning'\nimport { generateId } from '../core/text.js'\nimport { slugify } from '../core/text.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport logger from '../config/logger'\n\n// ── Types for the LLM's plan_medium_clips tool call ─────────────────────────\n\ninterface PlannedSegment {\n start: number\n end: number\n description: string\n}\n\ninterface PlannedMediumClip {\n title: string\n description: string\n tags: string[]\n segments: PlannedSegment[]\n totalDuration: number\n hook: string\n topic: string\n}\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a medium-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and identify the best 1–3 minute segments to extract as standalone medium-form clips.\n\n## What to look for\n\n- **Complete topics** — a subject is introduced, explored, and concluded\n- **Narrative arcs** — problem → solution → result; question → exploration → insight\n- **Educational deep dives** — clear, thorough explanations of complex topics\n- **Compelling stories** — anecdotes with setup, tension, and resolution\n- **Strong arguments** — claim → evidence → implication sequences\n- **Topic compilations** — multiple brief mentions of one theme across the video that can be compiled into a cohesive 1–3 minute segment\n\n## Clip types\n\n- **Deep Dive** — a single contiguous section (1–3 min) covering one topic in depth\n- **Compilation** — multiple non-contiguous segments stitched together around a single theme or narrative thread (1–3 min total)\n\n## Rules\n\n1. Each clip must be 60–180 seconds total duration.\n2. Timestamps must align to word boundaries from the transcript.\n3. Prefer natural sentence and paragraph boundaries for clean entry/exit points.\n4. Each clip must be self-contained — a viewer with no other context should understand and get value from the clip.\n5. Aim for 2–4 medium clips per video, depending on length and richness.\n6. Every clip needs a descriptive title (5–12 words) and a topic label.\n7. For compilations, specify segments in the order they should appear in the final clip (which may differ from chronological order).\n8. Tags should be lowercase, no hashes, 3–6 per clip.\n9. A 1-second buffer is automatically added around each segment boundary.\n10. Each clip needs a hook — the opening line or concept that draws viewers in.\n\n## Differences from shorts\n\n- Shorts capture *moments*; medium clips capture *complete ideas*.\n- Don't just find the most exciting 60 seconds — find where a topic starts and where it naturally concludes.\n- It's OK if a medium clip has slower pacing — depth and coherence matter more than constant high energy.\n- Look for segments that work as standalone mini-tutorials or explanations.\n- Avoid overlap with content that would work better as a short (punchy, viral, single-moment).\n\nWhen you have identified the clips, call the **plan_medium_clips** tool with your complete plan.\n\n## Using Clip Direction\nYou may receive AI-generated clip direction with suggested medium clips. Use these as a starting point but make your own decisions:\n- The suggestions are based on visual + audio analysis and may identify narrative arcs you'd miss from transcript alone\n- Feel free to adjust timestamps, combine suggestions, or ignore ones that don't work\n- You may also find good clips NOT in the suggestions — always analyze the full transcript\n- Pay special attention to suggested hooks and topic arcs — they come from multimodal analysis`\n\n// ── JSON Schema for the plan_medium_clips tool ──────────────────────────────\n\nconst PLAN_MEDIUM_CLIPS_SCHEMA = {\n type: 'object',\n properties: {\n clips: {\n type: 'array',\n description: 'Array of planned medium-length clips',\n items: {\n type: 'object',\n properties: {\n title: { type: 'string', description: 'Descriptive clip title (5–12 words)' },\n description: { type: 'string', description: 'Brief description of the clip content' },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Lowercase tags without hashes, 3–6 per clip',\n },\n segments: {\n type: 'array',\n description: 'One or more time segments that compose this clip',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds' },\n end: { type: 'number', description: 'End time in seconds' },\n description: { type: 'string', description: 'What happens in this segment' },\n },\n required: ['start', 'end', 'description'],\n },\n },\n totalDuration: { type: 'number', description: 'Total clip duration in seconds (60–180)' },\n hook: { type: 'string', description: 'Opening hook for the clip' },\n topic: { type: 'string', description: 'Main topic covered in the clip' },\n },\n required: ['title', 'description', 'tags', 'segments', 'totalDuration', 'hook', 'topic'],\n },\n },\n },\n required: ['clips'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass MediumVideoAgent extends BaseAgent {\n private plannedClips: PlannedMediumClip[] = []\n\n constructor(model?: string) {\n super('MediumVideoAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'plan_medium_clips',\n description:\n 'Submit the planned medium-length clips as a structured JSON array. Call this once with all planned clips.',\n parameters: PLAN_MEDIUM_CLIPS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('plan_medium_clips', args as Record<string, unknown>)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (toolName === 'plan_medium_clips') {\n this.plannedClips = args.clips as PlannedMediumClip[]\n logger.info(`[MediumVideoAgent] Planned ${this.plannedClips.length} medium clips`)\n return { success: true, count: this.plannedClips.length }\n }\n throw new Error(`Unknown tool: ${toolName}`)\n }\n\n getPlannedClips(): PlannedMediumClip[] {\n return this.plannedClips\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\nexport async function generateMediumClips(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n clipDirection?: string,\n): Promise<MediumClip[]> {\n const agent = new MediumVideoAgent(model)\n\n // Build prompt with full transcript including word-level timestamps\n const transcriptLines = transcript.segments.map((seg) => {\n const words = seg.words\n .map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`)\n .join(' ')\n return `[${seg.start.toFixed(2)}s – ${seg.end.toFixed(2)}s] ${seg.text}\\nWords: ${words}`\n })\n\n const promptParts = [\n `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and plan medium-length clips (1–3 minutes each).\\n`,\n `Video: ${video.filename}`,\n `Duration: ${transcript.duration.toFixed(1)}s\\n`,\n '--- TRANSCRIPT ---\\n',\n transcriptLines.join('\\n\\n'),\n '\\n--- END TRANSCRIPT ---',\n ]\n\n if (clipDirection) {\n promptParts.push(\n '\\n--- CLIP DIRECTION (AI-generated suggestions — use as reference, make your own decisions) ---\\n',\n clipDirection,\n '\\n--- END CLIP DIRECTION ---',\n )\n }\n\n const prompt = promptParts.join('\\n')\n\n try {\n await agent.run(prompt)\n const planned = agent.getPlannedClips()\n\n if (planned.length === 0) {\n logger.warn('[MediumVideoAgent] No medium clips were planned')\n return []\n }\n\n const clipsDir = join(dirname(video.repoPath), 'medium-clips')\n await ensureDirectory(clipsDir)\n\n const clips: MediumClip[] = []\n\n for (const plan of planned) {\n const id = generateId()\n const clipSlug = slugify(plan.title)\n const totalDuration = plan.segments.reduce((sum, s) => sum + (s.end - s.start), 0)\n const outputPath = join(clipsDir, `${clipSlug}.mp4`)\n\n const segments: MediumSegment[] = plan.segments.map((s) => ({\n start: s.start,\n end: s.end,\n description: s.description,\n }))\n\n // Extract the clip — single segment or composite with crossfade transitions\n if (segments.length === 1) {\n await extractClip(video.repoPath, segments[0].start, segments[0].end, outputPath)\n } else {\n await extractCompositeClipWithTransitions(video.repoPath, segments, outputPath)\n }\n\n // Generate ASS captions with medium style (smaller font, bottom-positioned)\n let captionedPath: string | undefined\n try {\n const assContent = segments.length === 1\n ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end, 1.0, 'medium')\n : generateStyledASSForComposite(transcript, segments, 1.0, 'medium')\n\n const assPath = join(clipsDir, `${clipSlug}.ass`)\n await writeTextFile(assPath, assContent)\n\n captionedPath = join(clipsDir, `${clipSlug}-captioned.mp4`)\n await burnCaptions(outputPath, assPath, captionedPath)\n logger.info(`[MediumVideoAgent] Burned captions for clip: ${plan.title}`)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[MediumVideoAgent] Caption burning failed for ${plan.title}: ${message}`)\n captionedPath = undefined\n }\n\n // Generate description markdown\n const mdPath = join(clipsDir, `${clipSlug}.md`)\n const mdContent = [\n `# ${plan.title}\\n`,\n `**Topic:** ${plan.topic}\\n`,\n `**Hook:** ${plan.hook}\\n`,\n plan.description,\n '',\n '## Segments\\n',\n ...plan.segments.map(\n (s, i) => `${i + 1}. **${s.start.toFixed(2)}s – ${s.end.toFixed(2)}s** — ${s.description}`,\n ),\n '',\n '## Tags\\n',\n plan.tags.map((t) => `- ${t}`).join('\\n'),\n '',\n ].join('\\n')\n await writeTextFile(mdPath, mdContent)\n\n clips.push({\n id,\n title: plan.title,\n slug: clipSlug,\n segments,\n totalDuration,\n outputPath,\n captionedPath,\n description: plan.description,\n tags: plan.tags,\n hook: plan.hook,\n topic: plan.topic,\n })\n\n logger.info(`[MediumVideoAgent] Created medium clip: ${plan.title} (${totalDuration.toFixed(1)}s)`)\n }\n\n logger.info(`[MediumVideoAgent] Generated ${clips.length} medium clips`)\n return clips\n } finally {\n await agent.destroy()\n }\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { writeTextFile, writeJsonFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\n\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../config/logger'\nimport { getConfig } from '../config/environment'\nimport type { VideoFile, Transcript, Chapter } from '../types'\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Format seconds → \"M:SS\" or \"H:MM:SS\" for YouTube timestamps */\nfunction toYouTubeTimestamp(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`\n}\n\n/** Format seconds → \"MM:SS\" for table display */\nfunction fmtTime(seconds: number): string {\n const m = Math.floor(seconds / 60)\n const s = Math.floor(seconds % 60)\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\n\n/** Build a compact transcript block with timestamps for the LLM prompt. */\nfunction buildTranscriptBlock(transcript: Transcript): string {\n return transcript.segments\n .map((seg) => `[${fmtTime(seg.start)} → ${fmtTime(seg.end)}] ${seg.text.trim()}`)\n .join('\\n')\n}\n\n// ── Output format generators ─────────────────────────────────────────────────\n\nfunction generateChaptersJSON(chapters: Chapter[]): string {\n return JSON.stringify({ chapters }, null, 2)\n}\n\nfunction generateYouTubeTimestamps(chapters: Chapter[]): string {\n return chapters\n .map((ch) => `${toYouTubeTimestamp(ch.timestamp)} ${ch.title}`)\n .join('\\n')\n}\n\nfunction generateChaptersMarkdown(chapters: Chapter[]): string {\n const rows = chapters\n .map((ch) => `| ${toYouTubeTimestamp(ch.timestamp)} | ${ch.title} | ${ch.description} |`)\n .join('\\n')\n\n return `## Chapters\n\n| Time | Chapter | Description |\n|------|---------|-------------|\n${rows}\n`\n}\n\nfunction generateFFMetadata(chapters: Chapter[], totalDuration: number): string {\n let meta = ';FFMETADATA1\\n\\n'\n for (let i = 0; i < chapters.length; i++) {\n const ch = chapters[i]\n const startMs = Math.round(ch.timestamp * 1000)\n const endMs = i < chapters.length - 1\n ? Math.round(chapters[i + 1].timestamp * 1000)\n : Math.round(totalDuration * 1000)\n const escapedTitle = ch.title.replace(/[=;#\\\\]/g, '\\\\$&')\n meta += `[CHAPTER]\\nTIMEBASE=1/1000\\nSTART=${startMs}\\nEND=${endMs}\\ntitle=${escapedTitle}\\n\\n`\n }\n return meta\n}\n\n// ── System prompt ────────────────────────────────────────────────────────────\n\nfunction buildChapterSystemPrompt(): string {\n return `You are a video chapter generator. Analyze the transcript and identify distinct topic segments.\n\nRules:\n- First chapter MUST start at 0:00\n- Minimum 3 chapters, maximum 10\n- Each chapter should be 2-5 minutes long\n- Chapter titles should be concise (3-7 words)\n- Look for topic transitions, \"moving on\", \"next\", \"now let's\", etc.\n- Include a brief 1-sentence description per chapter\n\n**Output format:**\nCall the \"generate_chapters\" tool with an array of chapter objects.\nEach chapter: { timestamp (seconds from start), title (short, 3-7 words), description (1-sentence summary) }\n\n**Title style:**\n- Use title case: \"Setting Up the Database\"\n- Be specific: \"Configuring PostgreSQL\" not \"Database Stuff\"\n- Include the action when relevant: \"Building the API Routes\"\n- Keep under 50 characters`\n}\n\n// ── Tool argument shape ──────────────────────────────────────────────────────\n\ninterface GenerateChaptersArgs {\n chapters: Chapter[]\n}\n\n// ── ChapterAgent ─────────────────────────────────────────────────────────────\n\nclass ChapterAgent extends BaseAgent {\n private outputDir: string\n private totalDuration: number\n\n constructor(outputDir: string, totalDuration: number, model?: string) {\n super('ChapterAgent', buildChapterSystemPrompt(), undefined, model)\n this.outputDir = outputDir\n this.totalDuration = totalDuration\n }\n\n private get chaptersDir(): string {\n return join(this.outputDir, 'chapters')\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'generate_chapters',\n description:\n 'Write the identified chapters to disk in all formats. ' +\n 'Provide: chapters (array of { timestamp, title, description }).',\n parameters: {\n type: 'object',\n properties: {\n chapters: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n timestamp: { type: 'number', description: 'Seconds from video start' },\n title: { type: 'string', description: 'Short chapter title (3-7 words)' },\n description: { type: 'string', description: '1-sentence summary' },\n },\n required: ['timestamp', 'title', 'description'],\n },\n },\n },\n required: ['chapters'],\n },\n handler: async (rawArgs: unknown) => {\n const args = rawArgs as GenerateChaptersArgs\n return this.handleGenerateChapters(args)\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n switch (toolName) {\n case 'generate_chapters':\n return this.handleGenerateChapters(args as unknown as GenerateChaptersArgs)\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n private async handleGenerateChapters(args: GenerateChaptersArgs): Promise<string> {\n const { chapters } = args\n await ensureDirectory(this.chaptersDir)\n\n // Write all 4 formats in parallel\n await Promise.all([\n writeTextFile(\n join(this.chaptersDir, 'chapters.json'),\n generateChaptersJSON(chapters),\n ),\n writeTextFile(\n join(this.chaptersDir, 'chapters-youtube.txt'),\n generateYouTubeTimestamps(chapters),\n ),\n writeTextFile(\n join(this.chaptersDir, 'chapters.md'),\n generateChaptersMarkdown(chapters),\n ),\n writeTextFile(\n join(this.chaptersDir, 'chapters.ffmetadata'),\n generateFFMetadata(chapters, this.totalDuration),\n ),\n ])\n\n logger.info(`[ChapterAgent] Wrote ${chapters.length} chapters in 4 formats → ${this.chaptersDir}`)\n return `Chapters written: ${chapters.length} chapters in 4 formats to ${this.chaptersDir}`\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Generate chapters for a video recording.\n *\n * 1. Creates a ChapterAgent with a `generate_chapters` tool\n * 2. Builds a prompt containing the full transcript with timestamps\n * 3. Lets the agent analyse the transcript and identify chapter boundaries\n * 4. Returns the array of {@link Chapter} objects\n */\nexport async function generateChapters(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n): Promise<Chapter[]> {\n const config = getConfig()\n const outputDir = join(config.OUTPUT_DIR, video.slug)\n\n const agent = new ChapterAgent(outputDir, video.duration, model)\n const transcriptBlock = buildTranscriptBlock(transcript)\n\n const userPrompt = [\n `**Video:** ${video.filename}`,\n `**Duration:** ${fmtTime(video.duration)} (${Math.round(video.duration)} seconds)`,\n '',\n '---',\n '',\n '**Transcript:**',\n '',\n transcriptBlock,\n ].join('\\n')\n\n let capturedChapters: Chapter[] | undefined\n\n // Intercept generate_chapters args to capture the result\n // Uses `as any` to access private method — required by the intercept-and-capture pattern\n const origHandler = (agent as any).handleGenerateChapters.bind(agent) as (\n a: GenerateChaptersArgs,\n ) => Promise<string>\n ;(agent as any).handleGenerateChapters = async (args: GenerateChaptersArgs) => {\n capturedChapters = args.chapters\n return origHandler(args)\n }\n\n try {\n await agent.run(userPrompt)\n\n if (!capturedChapters) {\n throw new Error('ChapterAgent did not call generate_chapters')\n }\n\n return capturedChapters\n } finally {\n await agent.destroy()\n }\n}\n","import { createFFmpeg } from '../../core/ffmpeg.js'\nimport { ensureDirectory } from '../../core/fileSystem.js'\nimport { dirname, join } from '../../core/paths.js'\nimport logger from '../../config/logger'\n\n/**\n * Extract a single PNG frame at the given timestamp (seconds).\n */\nexport async function captureFrame(\n videoPath: string,\n timestamp: number,\n outputPath: string,\n): Promise<string> {\n const outputDir = dirname(outputPath);\n await ensureDirectory(outputDir);\n\n logger.info(`Capturing frame at ${timestamp}s → ${outputPath}`);\n\n return new Promise<string>((resolve, reject) => {\n createFFmpeg(videoPath)\n .seekInput(timestamp)\n .frames(1)\n .output(outputPath)\n .on('end', () => {\n logger.info(`Frame captured: ${outputPath}`);\n resolve(outputPath);\n })\n .on('error', (err) => {\n logger.error(`Frame capture failed: ${err.message}`);\n reject(new Error(`Frame capture failed: ${err.message}`));\n })\n .run();\n });\n}\n\n/**\n * Extract multiple frames at the given timestamps.\n * Files are named snapshot-001.png, snapshot-002.png, etc.\n */\nexport async function captureFrames(\n videoPath: string,\n timestamps: number[],\n outputDir: string,\n): Promise<string[]> {\n await ensureDirectory(outputDir);\n\n const results: string[] = [];\n\n for (let i = 0; i < timestamps.length; i++) {\n const idx = String(i + 1).padStart(3, '0');\n const outputPath = join(outputDir, `snapshot-${idx}.png`);\n await captureFrame(videoPath, timestamps[i], outputPath);\n results.push(outputPath);\n }\n\n logger.info(`Captured ${results.length} frames in ${outputDir}`);\n return results;\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\n\nimport { BaseAgent } from './BaseAgent'\nimport { captureFrame } from '../tools/ffmpeg/frameCapture'\nimport logger from '../config/logger'\nimport { getBrandConfig } from '../config/brand'\nimport { getConfig } from '../config/environment'\nimport type { VideoFile, Transcript, VideoSummary, VideoSnapshot, ShortClip, Chapter } from '../types'\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Format seconds → \"MM:SS\" */\nfunction fmtTime(seconds: number): string {\n const m = Math.floor(seconds / 60)\n const s = Math.floor(seconds % 60)\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\n\n/** Build a compact transcript block with timestamps for the LLM prompt. */\nfunction buildTranscriptBlock(transcript: Transcript): string {\n return transcript.segments\n .map((seg) => `[${fmtTime(seg.start)} → ${fmtTime(seg.end)}] ${seg.text.trim()}`)\n .join('\\n')\n}\n\n// ── System prompt ────────────────────────────────────────────────────────────\n\nfunction buildSystemPrompt(shortsInfo: string, socialPostsInfo: string, captionsInfo: string, chaptersInfo: string): string {\n const brand = getBrandConfig()\n\n return `You are a Video Summary Agent writing from the perspective of ${brand.name} (${brand.handle}).\nBrand voice: ${brand.voice.tone}. ${brand.voice.personality} ${brand.voice.style}\n\nYour job is to analyse a video transcript and produce a beautiful, narrative-style Markdown README.\n\n**Workflow**\n1. Read the transcript carefully.\n2. Identify 3-8 key topics, decisions, highlights, or memorable moments.\n3. For each highlight, decide on a representative timestamp and call the \"capture_frame\" tool to grab a screenshot.\n4. Once all frames are captured, call the \"write_summary\" tool with the final Markdown.\n\n**Markdown structure — follow this layout exactly:**\n\n\\`\\`\\`\n# [Video Title]\n\n> [Compelling one-line hook/tagline that captures the video's value]\n\n[2-3 paragraph natural summary that reads like a blog post, NOT a timeline.\nWeave in key insights naturally. Write in the brand voice: ${brand.voice.tone}.\n${brand.contentGuidelines.blogFocus}]\n\n---\n\n## Key Moments\n\n[For each key topic: write a narrative paragraph (not bullet points).\nEmbed the timestamp as an inline badge like \\`[0:12]\\` within the text, NOT as a section header.\nEmbed the screenshot naturally within or after the paragraph.\nUse blockquotes (>) for standout quotes or insights.]\n\n\n\n[Continue with next topic paragraph...]\n\n---\n\n## 📊 Quick Reference\n\n| Topic | Timestamp |\n|-------|-----------|\n| Topic name | \\`M:SS\\` |\n| ... | ... |\n\n---\n${chaptersInfo}\n${shortsInfo}\n${socialPostsInfo}\n${captionsInfo}\n\n---\n\n*Generated on [DATE] • Duration: [DURATION] • Tags: [relevant tags]*\n\\`\\`\\`\n\n**Writing style rules**\n- Write in a narrative, blog-post style — NOT a timestamp-driven timeline.\n- Timestamps appear as subtle inline badges like \\`[0:12]\\` or \\`[1:30]\\` within sentences, never as section headers.\n- The summary paragraphs should flow naturally and be enjoyable to read.\n- Use the brand perspective: ${brand.voice.personality}\n- Topics to emphasize: ${brand.advocacy.interests.join(', ')}\n- Avoid: ${brand.advocacy.avoids.join(', ')}\n\n**Screenshot distribution rules — CRITICAL**\n- You MUST spread screenshots across the ENTIRE video duration, from beginning to end.\n- Divide the video into equal segments based on the number of screenshots you plan to capture, and pick one timestamp from each segment.\n- NO MORE than 2 screenshots should fall within the same 60-second window.\n- If the video is longer than 2 minutes, your first screenshot must NOT be in the first 10% and your last screenshot must be in the final 30% of the video.\n- Use the suggested timestamp ranges provided in the user message as guidance, but pick the exact moment within each range that best matches a key topic in the transcript.\n\n**Tool rules**\n- Always call \"capture_frame\" BEFORE \"write_summary\".\n- The snapshot index must be a 1-based integer; the filename will be snapshot-001.png, etc.\n- In the Markdown, reference screenshots as \\`thumbnails/snapshot-001.png\\` (relative path).\n- Call \"write_summary\" exactly once with the complete Markdown string.`\n}\n\n// ── Tool argument shapes ─────────────────────────────────────────────────────\n\ninterface CaptureFrameArgs {\n timestamp: number\n description: string\n index: number\n}\n\ninterface WriteSummaryArgs {\n markdown: string\n title: string\n overview: string\n keyTopics: string[]\n}\n\n// ── SummaryAgent ─────────────────────────────────────────────────────────────\n\nclass SummaryAgent extends BaseAgent {\n private videoPath: string\n private outputDir: string\n private snapshots: VideoSnapshot[] = []\n\n constructor(videoPath: string, outputDir: string, systemPrompt: string, model?: string) {\n super('SummaryAgent', systemPrompt, undefined, model)\n this.videoPath = videoPath\n this.outputDir = outputDir\n }\n\n // Resolved paths\n private get thumbnailDir(): string {\n return join(this.outputDir, 'thumbnails')\n }\n\n private get markdownPath(): string {\n return join(this.outputDir, 'README.md')\n }\n\n /* ── Tools exposed to the LLM ─────────────────────────────────────────── */\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'capture_frame',\n description:\n 'Capture a screenshot from the video at a specific timestamp. ' +\n 'Provide: timestamp (seconds), description (what is shown), index (1-based integer for filename).',\n parameters: {\n type: 'object',\n properties: {\n timestamp: { type: 'number', description: 'Timestamp in seconds to capture' },\n description: { type: 'string', description: 'Brief description of the visual moment' },\n index: { type: 'integer', description: '1-based snapshot index (used for filename)' },\n },\n required: ['timestamp', 'description', 'index'],\n },\n handler: async (rawArgs: unknown) => {\n const args = rawArgs as CaptureFrameArgs\n return this.handleCaptureFrame(args)\n },\n },\n {\n name: 'write_summary',\n description:\n 'Write the final Markdown summary to disk. ' +\n 'Provide: markdown (full README content), title, overview, and keyTopics array.',\n parameters: {\n type: 'object',\n properties: {\n markdown: { type: 'string', description: 'Complete Markdown content for README.md' },\n title: { type: 'string', description: 'Video title' },\n overview: { type: 'string', description: 'Short overview paragraph' },\n keyTopics: {\n type: 'array',\n items: { type: 'string' },\n description: 'List of key topic names',\n },\n },\n required: ['markdown', 'title', 'overview', 'keyTopics'],\n },\n handler: async (rawArgs: unknown) => {\n const args = rawArgs as WriteSummaryArgs\n return this.handleWriteSummary(args)\n },\n },\n ]\n }\n\n /* ── Tool dispatch (required by BaseAgent) ─────────────────────────────── */\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n switch (toolName) {\n case 'capture_frame':\n return this.handleCaptureFrame(args as unknown as CaptureFrameArgs)\n case 'write_summary':\n return this.handleWriteSummary(args as unknown as WriteSummaryArgs)\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n /* ── Tool implementations ──────────────────────────────────────────────── */\n\n private async handleCaptureFrame(args: CaptureFrameArgs): Promise<string> {\n const idx = String(args.index).padStart(3, '0')\n const filename = `snapshot-${idx}.png`\n const outputPath = join(this.thumbnailDir, filename)\n\n await captureFrame(this.videoPath, args.timestamp, outputPath)\n\n const snapshot: VideoSnapshot = {\n timestamp: args.timestamp,\n description: args.description,\n outputPath,\n }\n this.snapshots.push(snapshot)\n\n logger.info(`[SummaryAgent] Captured snapshot ${idx} at ${fmtTime(args.timestamp)}`)\n return `Frame captured: thumbnails/${filename}`\n }\n\n private async handleWriteSummary(args: WriteSummaryArgs): Promise<string> {\n await ensureDirectory(this.outputDir)\n await writeTextFile(this.markdownPath, args.markdown)\n\n logger.info(`[SummaryAgent] Wrote summary → ${this.markdownPath}`)\n return `Summary written to ${this.markdownPath}`\n }\n\n /** Expose collected data after the run. */\n getResult(args: WriteSummaryArgs): VideoSummary {\n return {\n title: args.title,\n overview: args.overview,\n keyTopics: args.keyTopics,\n snapshots: this.snapshots,\n markdownPath: this.markdownPath,\n }\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/** Build the Shorts section for the README template. */\nfunction buildShortsSection(shorts?: ShortClip[]): string {\n if (!shorts || shorts.length === 0) {\n return `\n## ✂️ Shorts\n\n| Short | Duration | Description |\n|-------|----------|-------------|\n| *Shorts will appear here once generated* | | |`\n }\n\n const rows = shorts\n .map((s) => `| [${s.title}](shorts/${s.slug}.mp4) | ${Math.round(s.totalDuration)}s | ${s.description} |`)\n .join('\\n')\n\n return `\n## ✂️ Shorts\n\n| Short | Duration | Description |\n|-------|----------|-------------|\n${rows}`\n}\n\n/** Build the Social Media Posts section for the README template. */\nfunction buildSocialPostsSection(): string {\n return `\n## 📱 Social Media Posts\n\n- [TikTok](social-posts/tiktok.md)\n- [YouTube](social-posts/youtube.md)\n- [Instagram](social-posts/instagram.md)\n- [LinkedIn](social-posts/linkedin.md)\n- [X / Twitter](social-posts/x.md)\n- [Dev.to Blog](social-posts/devto.md)`\n}\n\n/** Build the Captions section for the README template. */\nfunction buildCaptionsSection(): string {\n return `\n## 🎬 Captions\n\n- [SRT](captions/captions.srt) | [VTT](captions/captions.vtt) | [ASS (Styled)](captions/captions.ass)`\n}\n\n/** Format seconds → YouTube-style timestamp for chapters display */\nfunction toYouTubeTimestamp(seconds: number): string {\n const h = Math.floor(seconds / 3600)\n const m = Math.floor((seconds % 3600) / 60)\n const s = Math.floor(seconds % 60)\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`\n}\n\n/** Build the Chapters section for the README template. */\nfunction buildChaptersSection(chapters?: Chapter[]): string {\n if (!chapters || chapters.length === 0) {\n return ''\n }\n\n const rows = chapters\n .map((ch) => `| \\`${toYouTubeTimestamp(ch.timestamp)}\\` | ${ch.title} | ${ch.description} |`)\n .join('\\n')\n\n return `\n## 📑 Chapters\n\n| Time | Chapter | Description |\n|------|---------|-------------|\n${rows}\n\n> 📋 [YouTube Timestamps](chapters/chapters-youtube.txt) • [Markdown](chapters/chapters.md) • [JSON](chapters/chapters.json)`\n}\n\n/**\n * Generate a beautiful Markdown summary for a video recording.\n *\n * 1. Creates a SummaryAgent with `capture_frame` and `write_summary` tools\n * 2. Builds a prompt containing the full transcript with timestamps\n * 3. Lets the agent analyse the transcript, capture key frames, and write Markdown\n * 4. Returns a {@link VideoSummary} with metadata and snapshot paths\n */\nexport async function generateSummary(\n video: VideoFile,\n transcript: Transcript,\n shorts?: ShortClip[],\n chapters?: Chapter[],\n model?: string,\n): Promise<VideoSummary> {\n const config = getConfig()\n const outputDir = join(config.OUTPUT_DIR, video.slug)\n\n // Build content-section snippets for the system prompt\n const shortsInfo = buildShortsSection(shorts)\n const socialPostsInfo = buildSocialPostsSection()\n const captionsInfo = buildCaptionsSection()\n const chaptersInfo = buildChaptersSection(chapters)\n\n const systemPrompt = buildSystemPrompt(shortsInfo, socialPostsInfo, captionsInfo, chaptersInfo)\n const agent = new SummaryAgent(video.repoPath, outputDir, systemPrompt, model)\n\n const transcriptBlock = buildTranscriptBlock(transcript)\n\n // Pre-calculate suggested screenshot time ranges spread across the full video\n const screenshotCount = Math.min(8, Math.max(3, Math.round(video.duration / 120)))\n const interval = video.duration / screenshotCount\n const suggestedRanges = Array.from({ length: screenshotCount }, (_, i) => {\n const center = Math.round(interval * (i + 0.5))\n const lo = Math.max(0, Math.round(center - interval / 2))\n const hi = Math.min(Math.round(video.duration), Math.round(center + interval / 2))\n return `${fmtTime(lo)}–${fmtTime(hi)} (${lo}s–${hi}s)`\n }).join(', ')\n\n const userPrompt = [\n `**Video:** ${video.filename}`,\n `**Duration:** ${fmtTime(video.duration)} (${Math.round(video.duration)} seconds)`,\n `**Date:** ${video.createdAt.toISOString().slice(0, 10)}`,\n '',\n `**Suggested screenshot time ranges (one screenshot per range):**`,\n suggestedRanges,\n '',\n '---',\n '',\n '**Transcript:**',\n '',\n transcriptBlock,\n ].join('\\n')\n\n let lastWriteArgs: WriteSummaryArgs | undefined\n\n // Intercept write_summary args so we can build the return value\n // Uses `as any` to access private method — required by the intercept-and-capture pattern\n const origHandleWrite = (agent as any).handleWriteSummary.bind(agent) as (\n a: WriteSummaryArgs,\n ) => Promise<string>\n ;(agent as any).handleWriteSummary = async (args: WriteSummaryArgs) => {\n lastWriteArgs = args\n return origHandleWrite(args)\n }\n\n try {\n await agent.run(userPrompt)\n\n if (!lastWriteArgs) {\n throw new Error('SummaryAgent did not call write_summary')\n }\n\n return agent.getResult(lastWriteArgs)\n } finally {\n await agent.destroy()\n }\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { BaseAgent } from './BaseAgent.js'\nimport type { VideoInfo } from '../tools/agentTools.js'\nimport { singlePassEdit, type KeepSegment } from '../tools/ffmpeg/singlePassEdit.js'\nimport type { VideoAsset } from '../assets/VideoAsset.js'\nimport logger from '../config/logger.js'\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a professional video cleaner. Your job is to analyze videos and identify regions that should be removed for a tighter, cleaner edit.\n\n## CONTEXT TOOLS (use these first to understand the video)\n- **get_video_info**: Get video dimensions, duration, and frame rate\n- **get_transcript**: Read what's being said (with optional time range filtering)\n- **get_editorial_direction**: Get AI-generated editorial guidance (cut points, pacing notes) from Gemini video analysis. Use this to inform your cleaning decisions.\n\n## WHAT TO REMOVE\n- **Dead air**: Long silences with no meaningful content\n- **Filler words**: Excessive \"um\", \"uh\", \"like\", \"you know\" clusters\n- **Bad takes**: False starts, stumbles, repeated sentences where the speaker restarts\n- **Long pauses**: Extended gaps between sentences (>3 seconds) that don't serve a purpose\n- **Redundant content**: Sections where the same point is repeated without adding value\n\n## WHAT TO PRESERVE\n- **Intentional pauses**: Dramatic pauses, thinking pauses before important points\n- **Demonstrations**: Silence during live coding, UI interaction, or waiting for results\n- **Meaningful silence**: Pauses that give the viewer time to absorb information\n- **All substantive content**: When in doubt, keep it\n\n## WORKFLOW\n\n1. Call get_video_info to know the video duration\n2. Call get_editorial_direction to get AI-powered editorial guidance (cut points, pacing issues)\n3. Call get_transcript (in sections if long) to understand what's being said and find removable regions\n4. When ready, call **plan_cuts** with your list of regions to remove\n\n## GUIDELINES\n- Be conservative: aim for 10-20% removal at most\n- Each removal should have a clear reason\n- Don't remove short pauses (<1 second) — they sound natural\n- Focus on making the video tighter, not shorter for its own sake\n- Use editorial direction from Gemini to identify problematic regions`\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\ninterface Removal {\n start: number\n end: number\n reason: string\n}\n\ninterface GetTranscriptArgs {\n start?: number\n end?: number\n}\n\n// ── JSON Schemas ─────────────────────────────────────────────────────────────\n\nconst PLAN_CUTS_SCHEMA = {\n type: 'object',\n properties: {\n removals: {\n type: 'array',\n description: 'Array of regions to remove from the video',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds' },\n end: { type: 'number', description: 'End time in seconds' },\n reason: { type: 'string', description: 'Why this region should be removed' },\n },\n required: ['start', 'end', 'reason'],\n },\n },\n },\n required: ['removals'],\n}\n\n/**\n * Result of the produce() method.\n */\nexport interface ProduceResult {\n /** The agent's summary of edits made */\n summary: string\n /** Path to the output video (if rendering succeeded) */\n outputPath?: string\n /** Whether FFmpeg rendering succeeded */\n success: boolean\n /** Error message if rendering failed */\n error?: string\n /** Number of edits planned */\n editCount?: number\n /** Regions removed from the video */\n removals: { start: number; end: number }[]\n /** Segments kept in the output video */\n keepSegments: { start: number; end: number }[]\n}\n\n// ── ProducerAgent ────────────────────────────────────────────────────────────\n\nexport class ProducerAgent extends BaseAgent {\n private readonly video: VideoAsset\n private videoDuration: number = 0\n private removals: Removal[] = []\n\n constructor(video: VideoAsset, model?: string) {\n super('ProducerAgent', SYSTEM_PROMPT, undefined, model)\n this.video = video\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'get_video_info',\n description: 'Get video metadata: dimensions, duration, and frame rate.',\n parameters: { type: 'object', properties: {} },\n handler: async () => this.handleToolCall('get_video_info', {}),\n },\n {\n name: 'get_transcript',\n description: 'Read the transcript with optional time range filtering.',\n parameters: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Optional start time in seconds' },\n end: { type: 'number', description: 'Optional end time in seconds' },\n },\n },\n handler: async (rawArgs: unknown) =>\n this.handleToolCall('get_transcript', rawArgs as Record<string, unknown>),\n },\n {\n name: 'get_editorial_direction',\n description:\n 'Get AI-generated editorial guidance from Gemini video analysis. ' +\n 'Returns timestamped cut points, pacing notes, and recommendations for cleaning.',\n parameters: { type: 'object', properties: {} },\n handler: async () => this.handleToolCall('get_editorial_direction', {}),\n },\n {\n name: 'plan_cuts',\n description:\n 'Submit your list of regions to remove from the video. Call this ONCE with ALL planned removals.',\n parameters: PLAN_CUTS_SCHEMA,\n handler: async (rawArgs: unknown) =>\n this.handleToolCall('plan_cuts', rawArgs as Record<string, unknown>),\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n switch (toolName) {\n case 'get_video_info': {\n logger.info(`[ProducerAgent] Getting video info`)\n const metadata = await this.video.getMetadata()\n this.videoDuration = metadata.duration\n return {\n width: metadata.width,\n height: metadata.height,\n duration: metadata.duration,\n fps: 30,\n } as VideoInfo\n }\n\n case 'get_transcript': {\n const { start, end } = args as GetTranscriptArgs\n logger.info(`[ProducerAgent] Reading transcript${start !== undefined ? ` (${start}s-${end}s)` : ''}`)\n\n const transcript = await this.video.getTranscript()\n\n let segments = transcript.segments\n if (start !== undefined || end !== undefined) {\n segments = segments.filter(s => {\n if (start !== undefined && s.end < start) return false\n if (end !== undefined && s.start > end) return false\n return true\n })\n }\n\n return {\n text: segments.map(s => s.text).join(' '),\n segments: segments.map(s => ({\n text: s.text,\n start: s.start,\n end: s.end,\n })),\n }\n }\n\n case 'get_editorial_direction': {\n logger.info(`[ProducerAgent] Getting editorial direction from Gemini`)\n\n const direction = await this.video.getEditorialDirection()\n\n if (!direction) {\n return {\n available: false,\n message: 'Editorial direction not available (GEMINI_API_KEY not configured). Plan cuts based on transcript analysis.',\n }\n }\n\n return {\n available: true,\n editorialDirection: direction,\n }\n }\n\n case 'plan_cuts': {\n const { removals } = args as { removals: Removal[] }\n logger.info(`[ProducerAgent] Received plan with ${removals.length} removals`)\n this.removals = removals\n return `Plan received with ${removals.length} removals. Video will be rendered automatically.`\n }\n\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n /**\n * Run the producer agent to clean the video by removing unwanted segments.\n *\n * @param outputPath - Path for the output video\n */\n async produce(outputPath: string): Promise<ProduceResult> {\n this.removals = []\n\n const prompt = `Analyze this video and decide which segments should be removed for a cleaner edit.\n\n**Video:** ${this.video.videoPath}\n\n## Instructions\n\n1. Call get_video_info to know the video duration.\n2. Call get_editorial_direction to get AI-powered editorial guidance (cut points, pacing issues).\n3. Call get_transcript to understand what's being said and identify removable regions.\n4. Call **plan_cuts** with your list of regions to remove.\n\nFocus on removing dead air, filler words, bad takes, and redundant content. Be conservative — aim for 10-20% removal at most.`\n\n try {\n const response = await this.run(prompt)\n logger.info(`[ProducerAgent] Agent planning complete for ${this.video.videoPath}`)\n\n if (this.removals.length === 0) {\n logger.info(`[ProducerAgent] No removals planned — video is clean`)\n return {\n summary: response,\n success: true,\n editCount: 0,\n removals: [],\n keepSegments: [{ start: 0, end: this.videoDuration }],\n }\n }\n\n // Safety cap: limit removals to 20% of video duration\n const maxRemoval = this.videoDuration * 0.20\n let totalRemoval = 0\n const sortedByDuration = [...this.removals].sort(\n (a, b) => (b.end - b.start) - (a.end - a.start),\n )\n const cappedRemovals: Removal[] = []\n for (const r of sortedByDuration) {\n const dur = r.end - r.start\n if (totalRemoval + dur <= maxRemoval) {\n cappedRemovals.push(r)\n totalRemoval += dur\n }\n }\n\n if (cappedRemovals.length < this.removals.length) {\n logger.warn(\n `[ProducerAgent] Safety cap: reduced ${this.removals.length} removals to ${cappedRemovals.length} (max 20% of ${this.videoDuration}s = ${maxRemoval.toFixed(1)}s)`,\n )\n }\n\n // Sort by start time for keepSegment construction\n const sortedRemovals = [...cappedRemovals].sort((a, b) => a.start - b.start)\n\n // Convert removals to keepSegments (inverse)\n const keepSegments: KeepSegment[] = []\n let cursor = 0\n for (const removal of sortedRemovals) {\n if (removal.start > cursor) {\n keepSegments.push({ start: cursor, end: removal.start })\n }\n cursor = Math.max(cursor, removal.end)\n }\n if (cursor < this.videoDuration) {\n keepSegments.push({ start: cursor, end: this.videoDuration })\n }\n\n logger.info(\n `[ProducerAgent] ${cappedRemovals.length} removals → ${keepSegments.length} keep segments, removing ${totalRemoval.toFixed(1)}s`,\n )\n\n // Render via singlePassEdit\n await singlePassEdit(this.video.videoPath, keepSegments, outputPath)\n\n logger.info(`[ProducerAgent] Render complete: ${outputPath}`)\n\n return {\n summary: response,\n outputPath,\n success: true,\n editCount: cappedRemovals.length,\n removals: sortedRemovals.map(r => ({ start: r.start, end: r.end })),\n keepSegments,\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`[ProducerAgent] Production failed: ${message}`)\n return {\n summary: `Production failed: ${message}`,\n success: false,\n error: message,\n removals: [],\n keepSegments: [],\n }\n } finally {\n await this.destroy()\n }\n }\n}\n","/**\n * Gemini Video Understanding Client\n *\n * Uses Google's Gemini API to analyze raw video files and return\n * timestamped editorial direction — cut points, pacing, transitions.\n *\n * Gemini is the only production-ready API that accepts raw video files\n * and returns timestamped analysis without frame extraction.\n */\nimport { GoogleGenAI, createUserContent, createPartFromUri } from '@google/genai'\nimport { getConfig } from '../../config/environment.js'\nimport logger from '../../config/logger.js'\nimport { costTracker } from '../../services/costTracker.js'\n\n\n/** Tokens per second of video footage (~263 tokens/s per Gemini docs) */\nconst VIDEO_TOKENS_PER_SECOND = 263\n\nconst EDITORIAL_PROMPT = `You are a professional video editor reviewing raw footage. Analyze this video and write detailed editorial direction in natural language.\n\nCover these areas with specific timestamps (use MM:SS format):\n\n## Cut Points & Transitions\nList every moment where a cut or transition should occur. For each, explain WHY this cut improves the edit and what transition type to use (hard cut, crossfade, dissolve, J-cut, L-cut, jump cut, fade to black).\n\n## Pacing Analysis\nFlag sections that are too slow, too fast, or have dead air. Give start/end timestamps and what to do about each issue.\n\n## B-Roll & Graphics Suggestions\nIdentify moments where text overlays, graphics, zoom-ins, or visual emphasis would improve engagement.\n\n## Hook & Retention\nRate the first 3 seconds (1-10) and suggest specific improvements for viewer retention.\n\n## Content Structure\nBreak the video into intro/body sections/outro with timestamps and topic for each section.\n\n## Key Moments\nHighlight the most engaging, surprising, or important moments that should be emphasized in the edit.\n\n## Cleaning Recommendations\nIdentify sections that should be trimmed or removed entirely to produce a tighter edit. For each:\n- Give start/end timestamps (MM:SS format)\n- Explain why it should be removed (dead air, filler words, false starts, repeated explanations, off-topic tangents, excessive pauses)\n- Rate the confidence (high/medium/low) — high means definitely remove, low means optional\n\n## Hook Snippets for Short Videos\nIdentify the 3-5 best moments (3-8 seconds each) that could serve as attention-grabbing hooks for the beginning of short-form videos. For each:\n- Give start/end timestamps\n- Transcribe the exact words spoken (or describe the visual action)\n- Explain why this would grab a viewer's attention in the first 3 seconds\n- Rate hook strength (1-10)\n\n## Short Video Suggestions\nIdentify 3-8 potential short clips (15-60 seconds each) that would work well as standalone short-form content (TikTok, YouTube Shorts, Instagram Reels). For each:\n- Give start/end timestamps\n- Suggest a title (5-10 words)\n- Describe the topic/moment and why it works as a standalone clip\n- Note if it could be a composite (multiple non-contiguous segments edited together)\n- Rate viral potential (1-10)\n\n## Medium Clip Suggestions\nIdentify 2-4 potential medium-length clips (60-180 seconds) that cover complete topics or narrative arcs. For each:\n- Give start/end timestamps (can be multiple segments for composites)\n- Suggest a title (5-12 words)\n- Describe the topic arc and why this stands alone as complete content\n- Suggest a hook line or concept for the opening\n- Note key moments within the clip that should be emphasized\n\nBe specific with timestamps. Be opinionated — say what works and what doesn't. Write as if briefing a human editor who will both clean the video AND extract clips from it.`\n\nconst CLIP_DIRECTION_PROMPT = `You are a social media content strategist analyzing an edited video to identify the best clips for short-form and medium-form content.\n\nThis video has already been cleaned (dead air and filler removed). Analyze it and provide detailed direction for clip extraction.\n\n## Short Video Direction (15-60 seconds each)\nFor each recommended short clip, provide:\n- **Timestamps**: Exact start and end (MM:SS format)\n- **Title**: Catchy title for the clip (5-10 words)\n- **Hook**: The opening line or visual that grabs attention — transcribe exact words if spoken\n- **Topic**: What this clip is about in one sentence\n- **Platform fit**: Which platforms this works best for (TikTok, YouTube Shorts, Instagram Reels) and why\n- **Engagement potential**: Rate 1-10 with brief justification\n- **Composite option**: If combining multiple segments would make a stronger clip, list all segment timestamps\n- **Tags**: 3-5 relevant hashtag suggestions\n\nIdentify 3-8 shorts. Prioritize: surprising insights, emotional peaks, controversial takes, practical tips, funny moments, and \"aha\" moments.\n\n## Medium Clip Direction (60-180 seconds each)\nFor each recommended medium clip, provide:\n- **Timestamps**: Exact start and end (MM:SS format) — can be multiple segments for composites\n- **Title**: Descriptive title (5-12 words)\n- **Hook**: Opening concept or line to grab attention\n- **Topic arc**: How the narrative flows from start to end\n- **Key moments**: Specific timestamps within the clip that should be emphasized (zoom, text overlay)\n- **Standalone score**: Rate 1-10 how well this works without watching the full video\n- **Tags**: 3-6 relevant hashtag suggestions\n\nIdentify 2-4 medium clips. Prioritize: complete explanations, tutorial segments, deep dives, and compelling narrative arcs.\n\nBe precise with timestamps. Be opinionated about what works and what doesn't. Think about what would make someone stop scrolling.`\n\n/**\n * Upload a video to Gemini and get timestamped editorial direction.\n *\n * @param videoPath - Path to the video file (mp4, webm, mov, etc.)\n * @param durationSeconds - Video duration in seconds (for cost estimation)\n * @param model - Gemini model to use (default: gemini-2.5-flash)\n * @returns Parsed editorial direction\n */\nexport async function analyzeVideoEditorial(\n videoPath: string,\n durationSeconds: number,\n model: string = 'gemini-2.5-flash',\n): Promise<string> {\n const config = getConfig()\n const apiKey = config.GEMINI_API_KEY\n\n if (!apiKey) {\n throw new Error(\n 'GEMINI_API_KEY is required for video editorial analysis. ' +\n 'Get a key at https://aistudio.google.com/apikey',\n )\n }\n\n const ai = new GoogleGenAI({ apiKey })\n\n logger.info(`[Gemini] Uploading video for editorial analysis: ${videoPath}`)\n\n // 1. Upload the video file\n const file = await ai.files.upload({\n file: videoPath,\n config: { mimeType: 'video/mp4' },\n })\n\n if (!file.uri || !file.mimeType || !file.name) {\n throw new Error('Gemini file upload failed — no URI returned')\n }\n\n // 2. Wait for file to become ACTIVE (Gemini processes uploads async)\n logger.info(`[Gemini] Waiting for file processing to complete...`)\n let fileState = file.state\n while (fileState === 'PROCESSING') {\n await new Promise(resolve => setTimeout(resolve, 2000))\n const updated = await ai.files.get({ name: file.name })\n fileState = updated.state\n logger.debug(`[Gemini] File state: ${fileState}`)\n }\n if (fileState !== 'ACTIVE') {\n throw new Error(`Gemini file processing failed — state: ${fileState}`)\n }\n\n logger.info(`[Gemini] Video ready, requesting editorial analysis (model: ${model})`)\n\n // 3. Request editorial analysis\n const response = await ai.models.generateContent({\n model,\n contents: createUserContent([\n createPartFromUri(file.uri, file.mimeType),\n EDITORIAL_PROMPT,\n ]),\n })\n\n const text = response.text ?? ''\n\n if (!text) {\n throw new Error('Gemini returned empty response')\n }\n\n // 3. Track cost\n const estimatedInputTokens = Math.ceil(durationSeconds * VIDEO_TOKENS_PER_SECOND)\n const estimatedOutputTokens = Math.ceil(text.length / 4) // rough token estimate\n costTracker.recordServiceUsage('gemini', 0, {\n model,\n durationSeconds,\n estimatedInputTokens,\n estimatedOutputTokens,\n videoFile: videoPath,\n })\n\n logger.info(`[Gemini] Editorial analysis complete (${text.length} chars)`)\n\n return text\n}\n\n/**\n * Upload a video to Gemini and get clip direction for shorts and medium clips.\n *\n * @param videoPath - Path to the cleaned video file (mp4, webm, mov, etc.)\n * @param durationSeconds - Video duration in seconds (for cost estimation)\n * @param model - Gemini model to use (default: gemini-2.5-flash)\n * @returns Clip direction as markdown text\n */\nexport async function analyzeVideoClipDirection(\n videoPath: string,\n durationSeconds: number,\n model: string = 'gemini-2.5-flash',\n): Promise<string> {\n const config = getConfig()\n const apiKey = config.GEMINI_API_KEY\n\n if (!apiKey) {\n throw new Error(\n 'GEMINI_API_KEY is required for video clip direction analysis. ' +\n 'Get a key at https://aistudio.google.com/apikey',\n )\n }\n\n const ai = new GoogleGenAI({ apiKey })\n\n logger.info(`[Gemini] Uploading video for clip direction analysis: ${videoPath}`)\n\n // 1. Upload the video file\n const file = await ai.files.upload({\n file: videoPath,\n config: { mimeType: 'video/mp4' },\n })\n\n if (!file.uri || !file.mimeType || !file.name) {\n throw new Error('Gemini file upload failed — no URI returned')\n }\n\n // 2. Wait for file to become ACTIVE (Gemini processes uploads async)\n logger.info(`[Gemini] Waiting for file processing to complete...`)\n let fileState = file.state\n while (fileState === 'PROCESSING') {\n await new Promise(resolve => setTimeout(resolve, 2000))\n const updated = await ai.files.get({ name: file.name })\n fileState = updated.state\n logger.debug(`[Gemini] File state: ${fileState}`)\n }\n if (fileState !== 'ACTIVE') {\n throw new Error(`Gemini file processing failed — state: ${fileState}`)\n }\n\n logger.info(`[Gemini] Video ready, requesting clip direction analysis (model: ${model})`)\n\n // 3. Request clip direction analysis\n const response = await ai.models.generateContent({\n model,\n contents: createUserContent([\n createPartFromUri(file.uri, file.mimeType),\n CLIP_DIRECTION_PROMPT,\n ]),\n })\n\n const text = response.text ?? ''\n\n if (!text) {\n throw new Error('Gemini returned empty response')\n }\n\n // 4. Track cost\n const estimatedInputTokens = Math.ceil(durationSeconds * VIDEO_TOKENS_PER_SECOND)\n const estimatedOutputTokens = Math.ceil(text.length / 4) // rough token estimate\n costTracker.recordServiceUsage('gemini', 0, {\n model,\n durationSeconds,\n estimatedInputTokens,\n estimatedOutputTokens,\n videoFile: videoPath,\n })\n\n logger.info(`[Gemini] Clip direction analysis complete (${text.length} chars)`)\n\n return text\n}\n\n\n","/**\n * Type definitions for vidpipe CLI pipeline.\n *\n * Domain types covering transcription, video metadata, short-clip planning,\n * social-media post generation, and end-to-end pipeline orchestration.\n *\n * ### Timestamp convention\n * All `start` and `end` fields are in **seconds from the beginning of the video**\n * (floating-point, e.g. 12.345). This matches Whisper's output format and\n * FFmpeg's `-ss` / `-to` parameters.\n */\n\n// ============================================================================\n// PLATFORM\n// ============================================================================\n\n/** Social-media platforms supported for post generation. */\nexport enum Platform {\n TikTok = 'tiktok',\n YouTube = 'youtube',\n Instagram = 'instagram',\n LinkedIn = 'linkedin',\n X = 'x',\n}\n\n// ============================================================================\n// TRANSCRIPTION (Whisper)\n// ============================================================================\n\n/**\n * A single word with precise start/end timestamps from Whisper.\n *\n * Word-level timestamps are the foundation of the karaoke caption system —\n * each word knows exactly when it's spoken, enabling per-word highlighting.\n * Whisper produces these via its `--word_timestamps` flag.\n *\n * @property word - The spoken word (may include leading/trailing whitespace)\n * @property start - When this word begins, in seconds from video start\n * @property end - When this word ends, in seconds from video start\n */\nexport interface Word {\n word: string;\n start: number;\n end: number;\n}\n\n/**\n * A sentence/phrase-level segment from Whisper transcription.\n *\n * Segments are Whisper's natural grouping of words into sentences or clauses.\n * They're used for SRT/VTT subtitle generation (one cue per segment) and for\n * silence removal (segments that fall entirely within a removed region are dropped).\n *\n * @property id - Sequential segment index (0-based)\n * @property text - Full text of the segment\n * @property start - Segment start time in seconds\n * @property end - Segment end time in seconds\n * @property words - The individual words with their own timestamps\n */\nexport interface Segment {\n id: number;\n text: string;\n start: number;\n end: number;\n words: Word[];\n}\n\n/**\n * Complete transcript result from Whisper.\n *\n * Contains both segment-level and word-level data. The top-level `words` array\n * is a flat list of all words across all segments — this is the primary input\n * for the ASS caption generator's karaoke highlighting.\n *\n * @property text - Full transcript as a single string\n * @property segments - Sentence/phrase-level segments\n * @property words - Flat array of all words with timestamps (used by ASS captions)\n * @property language - Detected language code (e.g. \"en\")\n * @property duration - Total video duration in seconds\n */\nexport interface Transcript {\n text: string;\n segments: Segment[];\n words: Word[];\n language: string;\n duration: number;\n}\n\n// ============================================================================\n// VIDEO FILE\n// ============================================================================\n\n/**\n * Metadata for a video file after ingestion into the repo structure.\n *\n * @property originalPath - Where the file was picked up from (e.g. recordings/ folder)\n * @property repoPath - Canonical path within the repo's asset directory\n * @property videoDir - Directory containing all generated assets for this video\n * @property slug - URL/filesystem-safe name derived from the filename (e.g. \"my-video-2024-01-15\")\n * @property filename - Original filename with extension\n * @property duration - Video duration in seconds (from ffprobe)\n * @property size - File size in bytes\n * @property createdAt - File creation timestamp\n * @property layout - Detected layout metadata (webcam region, etc.)\n */\nexport interface VideoFile {\n originalPath: string;\n repoPath: string;\n videoDir: string;\n slug: string;\n filename: string;\n duration: number;\n size: number;\n createdAt: Date;\n layout?: VideoLayout;\n}\n\n/**\n * Detected layout metadata for a video.\n * Captures webcam and screen region positions for use in aspect ratio conversion\n * and production effects.\n */\nexport interface VideoLayout {\n /** Video dimensions */\n width: number;\n height: number;\n /** Detected webcam overlay region (null if no webcam detected) */\n webcam: WebcamRegion | null;\n /** Main screen content region (computed as inverse of webcam) */\n screen: ScreenRegion | null;\n}\n\n/**\n * Webcam overlay region in a screen recording.\n */\nexport interface WebcamRegion {\n x: number;\n y: number;\n width: number;\n height: number;\n position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n confidence: number;\n}\n\n/**\n * Main screen content region (area not occupied by webcam).\n */\nexport interface ScreenRegion {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n// ============================================================================\n// ASPECT RATIO\n// ============================================================================\n\nexport type AspectRatio = '16:9' | '9:16' | '1:1' | '4:5';\n\nexport type VideoPlatform =\n | 'tiktok'\n | 'youtube-shorts'\n | 'instagram-reels'\n | 'instagram-feed'\n | 'linkedin'\n | 'youtube'\n | 'twitter';\n\n// ============================================================================\n// CAPTION STYLE\n// ============================================================================\n\n/**\n * Caption rendering style.\n * - `'shorts'` — large centered pop captions for short-form clips (landscape 16:9)\n * - `'medium'` — smaller bottom-positioned captions for longer content\n * - `'portrait'` — Opus Clips style for 9:16 vertical video (green highlight,\n * scale-pop animation, larger fonts for small-screen viewing)\n */\nexport type CaptionStyle = 'shorts' | 'medium' | 'portrait';\n\nexport interface ShortClipVariant {\n path: string;\n aspectRatio: AspectRatio;\n platform: VideoPlatform;\n width: number;\n height: number;\n}\n\n// ============================================================================\n// SHORT CLIPS\n// ============================================================================\n\n/**\n * A single time range within a short clip.\n *\n * Short clips can be **composite** — made of multiple non-contiguous segments\n * from the original video, concatenated together. Each segment describes one\n * contiguous range.\n *\n * @property start - Start time in the original video (seconds)\n * @property end - End time in the original video (seconds)\n * @property description - Human-readable description of what happens in this segment\n */\nexport interface ShortSegment {\n start: number;\n end: number;\n description: string;\n}\n\n/**\n * A planned short clip (15–60s) extracted from the full video.\n *\n * May be a single contiguous segment or a **composite** of multiple segments\n * concatenated together (e.g. an intro + punchline from different parts of\n * the video). The `segments` array defines the source time ranges; `totalDuration`\n * is the sum of all segment durations.\n *\n * @property id - Unique identifier (e.g. \"short-1\")\n * @property title - Human-readable title for the clip\n * @property slug - Filesystem-safe slug (e.g. \"typescript-tip-generics\")\n * @property segments - One or more time ranges from the original video\n * @property totalDuration - Sum of all segment durations in seconds\n * @property outputPath - Path to the extracted video file\n * @property captionedPath - Path to the captioned version (if generated)\n * @property description - Short description for social media\n * @property tags - Hashtags / topic tags\n * @property variants - Platform-specific aspect-ratio variants (portrait, square, etc.)\n */\nexport interface ShortClip {\n id: string;\n title: string;\n slug: string;\n segments: ShortSegment[];\n totalDuration: number;\n outputPath: string;\n captionedPath?: string;\n description: string;\n tags: string[];\n variants?: ShortClipVariant[];\n}\n\n// ============================================================================\n// MEDIUM CLIPS\n// ============================================================================\n\n/** A planned medium clip segment */\nexport interface MediumSegment {\n start: number;\n end: number;\n description: string;\n}\n\nexport interface MediumClip {\n id: string;\n title: string;\n slug: string;\n segments: MediumSegment[];\n totalDuration: number;\n outputPath: string;\n captionedPath?: string;\n description: string;\n tags: string[];\n hook: string;\n topic: string;\n}\n\n// ============================================================================\n// SOCIAL MEDIA\n// ============================================================================\n\nexport interface SocialPost {\n platform: Platform;\n content: string;\n hashtags: string[];\n links: string[];\n characterCount: number;\n outputPath: string;\n}\n\n// ============================================================================\n// CHAPTERS\n// ============================================================================\n\n/**\n * A chapter marker for YouTube's chapters feature.\n *\n * @property timestamp - Start time in seconds (YouTube shows these as clickable markers)\n * @property title - Short chapter title (shown in the progress bar)\n * @property description - Longer description for the README/summary\n */\nexport interface Chapter {\n timestamp: number;\n title: string;\n description: string;\n}\n\n// ============================================================================\n// SNAPSHOTS & SUMMARY\n// ============================================================================\n\nexport interface VideoSnapshot {\n timestamp: number;\n description: string;\n outputPath: string;\n}\n\nexport interface VideoSummary {\n title: string;\n overview: string;\n keyTopics: string[];\n snapshots: VideoSnapshot[];\n markdownPath: string;\n}\n\n// ============================================================================\n// PIPELINE\n// ============================================================================\n\nexport enum PipelineStage {\n Ingestion = 'ingestion',\n Transcription = 'transcription',\n SilenceRemoval = 'silence-removal',\n Chapters = 'chapters',\n Captions = 'captions',\n CaptionBurn = 'caption-burn',\n Summary = 'summary',\n Shorts = 'shorts',\n MediumClips = 'medium-clips',\n SocialMedia = 'social-media',\n ShortPosts = 'short-posts',\n MediumClipPosts = 'medium-clip-posts',\n Blog = 'blog',\n QueueBuild = 'queue-build',\n GitPush = 'git-push',\n}\n\n/**\n * Per-stage outcome record for pipeline observability.\n *\n * @property stage - Which pipeline stage this result is for\n * @property success - Whether the stage completed without throwing\n * @property error - Error message if the stage failed\n * @property duration - Wall-clock time in milliseconds\n */\nexport interface StageResult {\n stage: PipelineStage;\n success: boolean;\n error?: string;\n duration: number;\n}\n\n/**\n * Complete output of a pipeline run.\n *\n * Fields are optional because stages can fail independently — a failed\n * transcription means no summary, but the video metadata is still available.\n *\n * @property totalDuration - Total pipeline wall-clock time in milliseconds\n */\nexport interface PipelineResult {\n video: VideoFile;\n transcript?: Transcript;\n editedVideoPath?: string;\n captions?: string[];\n captionedVideoPath?: string;\n summary?: VideoSummary;\n chapters?: Chapter[];\n shorts: ShortClip[];\n mediumClips: MediumClip[];\n socialPosts: SocialPost[];\n blogPost?: string;\n stageResults: StageResult[];\n totalDuration: number;\n}\n\n// ============================================================================\n// SILENCE REMOVAL\n// ============================================================================\n\n/**\n * Result of the silence removal stage.\n *\n * @property editedPath - Path to the video with silence regions cut out\n * @property removals - Time ranges that were removed (in original video time).\n * Used by {@link adjustTranscript} to shift transcript timestamps.\n * @property keepSegments - Inverse of removals — the time ranges that were kept.\n * Used by the single-pass caption burn to re-create the edit from the original.\n * @property wasEdited - False if no silence was found and the video is unchanged\n */\nexport interface SilenceRemovalResult {\n editedPath: string;\n removals: { start: number; end: number }[];\n keepSegments: { start: number; end: number }[];\n wasEdited: boolean;\n}\n\n// ============================================================================\n// AGENT RESULT (Copilot SDK)\n// ============================================================================\n\n/**\n * Standard result wrapper for all Copilot SDK agent calls.\n *\n * @property success - Whether the agent completed its task\n * @property data - The parsed result (type varies by agent)\n * @property error - Error message if the agent failed\n * @property usage - Token counts for cost tracking\n */\nexport interface AgentResult<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n usage?: {\n promptTokens: number;\n completionTokens: number;\n };\n}\n\n// ============================================================================\n// SOCIAL PUBLISHING / QUEUE\n// ============================================================================\n\n/** Character limits per social media platform */\nexport const PLATFORM_CHAR_LIMITS: Record<string, number> = {\n tiktok: 2200,\n youtube: 5000,\n instagram: 2200,\n linkedin: 3000,\n twitter: 280,\n}\n\n/**\n * Maps vidpipe Platform enum values to Late API platform strings.\n * Platform.X = 'x' but Late API expects 'twitter'.\n */\nexport function toLatePlatform(platform: Platform): string {\n return platform === Platform.X ? 'twitter' : platform\n}\n\n/**\n * Maps a Late API platform string back to vidpipe Platform enum.\n * \n * Validates the input against known Platform values to avoid admitting\n * unknown/unsupported platforms via an unchecked cast.\n * \n * @throws {Error} If the platform is not supported\n */\nexport function fromLatePlatform(latePlatform: string): Platform {\n const normalized = normalizePlatformString(latePlatform)\n \n if (normalized === 'twitter') {\n return Platform.X\n }\n \n const platformValues = Object.values(Platform) as string[]\n if (platformValues.includes(normalized)) {\n return normalized as Platform\n }\n \n throw new Error(`Unsupported platform from Late API: ${latePlatform}`)\n}\n\n/**\n * Normalizes raw platform strings (e.g., from user input or API responses)\n * to Late API platform names. Handles X/Twitter variants and case-insensitivity.\n * \n * @example\n * normalizePlatformString('X') // 'twitter'\n * normalizePlatformString('x (twitter)') // 'twitter'\n * normalizePlatformString('YouTube') // 'youtube'\n */\nexport function normalizePlatformString(raw: string): string {\n const lower = raw.toLowerCase().trim()\n if (lower === 'x' || lower === 'x (twitter)' || lower === 'x/twitter') {\n return 'twitter'\n }\n return lower\n}\n\n/** Schedule time slot for a platform */\nexport interface ScheduleSlot {\n platform: string\n scheduledFor: string // ISO datetime\n postId?: string // Late post ID if already published\n itemId?: string // Local queue item ID\n label?: string\n}\n\n// ============================================================================\n// EDITORIAL DIRECTION (Gemini Video Understanding)\n// ============================================================================\n\n// Editorial direction is now stored as free-form markdown (editorial-direction.md)\n// generated by Gemini video analysis. No structured types needed.\n","import { join } from '../core/paths.js'\nimport { writeTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport { VideoFile, Transcript } from '../types'\nimport { generateSRT, generateVTT, generateStyledASS } from '../tools/captions/captionGenerator'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\n/**\n * Generate SRT, VTT, and ASS caption files for a video and save them to disk.\n * Returns the list of written file paths.\n */\nexport async function generateCaptions(\n video: VideoFile,\n transcript: Transcript,\n): Promise<string[]> {\n const config = getConfig()\n const captionsDir = join(config.OUTPUT_DIR, video.slug, 'captions')\n await ensureDirectory(captionsDir)\n\n const srtPath = join(captionsDir, 'captions.srt')\n const vttPath = join(captionsDir, 'captions.vtt')\n const assPath = join(captionsDir, 'captions.ass')\n\n const srt = generateSRT(transcript)\n const vtt = generateVTT(transcript)\n const ass = generateStyledASS(transcript)\n\n await Promise.all([\n writeTextFile(srtPath, srt),\n writeTextFile(vttPath, vtt),\n writeTextFile(assPath, ass),\n ])\n\n const paths = [srtPath, vttPath, assPath]\n logger.info(`Captions saved: ${paths.join(', ')}`)\n return paths\n}\n","import type { ToolWithHandler } from '../providers/types.js'\nimport { ensureDirectorySync, writeTextFileSync } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../config/logger'\nimport type { MCPServerConfig } from '../providers/types.js'\nimport { getConfig } from '../config/environment.js'\nimport {\n Platform,\n ShortClip,\n SocialPost,\n Transcript,\n VideoFile,\n VideoSummary,\n} from '../types'\n\n// ── JSON shape the LLM returns via the create_posts tool ────────────────────\n\ninterface PlatformPost {\n platform: string\n content: string\n hashtags: string[]\n links: string[]\n characterCount: number\n}\n\ninterface CreatePostsArgs {\n posts: PlatformPost[]\n}\n\n// ── System prompt───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a viral social-media content strategist.\nGiven a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.\nEach post must match the platform's tone, format, and constraints exactly.\n\nPlatform guidelines:\n1. **TikTok** – Casual, hook-driven, trending hashtags, 150 chars max, emoji-heavy.\n2. **YouTube** – Descriptive, SEO-optimized title + description, relevant tags.\n3. **Instagram** – Visual storytelling, emoji-rich, 30 hashtags max, engaging caption.\n4. **LinkedIn** – Professional, thought-leadership, industry insights, 1-3 hashtags.\n5. **X (Twitter)** – Concise, punchy, 280 chars max, 2-5 hashtags, thread-ready.\n\nIMPORTANT – Content format:\nThe \"content\" field you provide must be the FINAL, ready-to-post text that can be directly copied and pasted onto the platform. Do NOT use markdown headers, bullet points, or any formatting inside the content. Include hashtags inline at the end of the post text where appropriate. The content is saved as-is for direct posting.\n\nWorkflow:\n1. First use the \"web_search_exa\" tool to search for relevant URLs based on the key topics discussed in the video.\n2. Then call the \"create_posts\" tool with a JSON object that has a \"posts\" array.\n Each element must have: platform, content, hashtags (array), links (array), characterCount.\n\nInclude relevant links in posts when search results provide them.\nAlways call \"create_posts\" exactly once with all 5 platform posts.`\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass SocialMediaAgent extends BaseAgent {\n private collectedPosts: PlatformPost[] = []\n\n constructor(model?: string) {\n super('SocialMediaAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected getMcpServers(): Record<string, MCPServerConfig> | undefined {\n const config = getConfig()\n if (!config.EXA_API_KEY) return undefined\n return {\n exa: {\n type: 'http' as const,\n url: `${config.EXA_MCP_URL}?exaApiKey=${config.EXA_API_KEY}&tools=web_search_exa`,\n headers: {},\n tools: ['*'],\n },\n }\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'create_posts',\n description:\n 'Submit the generated social media posts for all 5 platforms.',\n parameters: {\n type: 'object',\n properties: {\n posts: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n platform: { type: 'string' },\n content: { type: 'string' },\n hashtags: { type: 'array', items: { type: 'string' } },\n links: { type: 'array', items: { type: 'string' } },\n characterCount: { type: 'number' },\n },\n required: ['platform', 'content', 'hashtags', 'links', 'characterCount'],\n },\n description: 'Array of posts, one per platform',\n },\n },\n required: ['posts'],\n },\n handler: async (args: unknown) => {\n const { posts } = args as CreatePostsArgs\n this.collectedPosts = posts\n logger.info(`[SocialMediaAgent] create_posts received ${posts.length} posts`)\n return JSON.stringify({ success: true, count: posts.length })\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n // Tool dispatch is handled inline via tool handlers above.\n // This satisfies the abstract contract from BaseAgent.\n logger.warn(`[SocialMediaAgent] Unexpected handleToolCall for \"${toolName}\"`)\n return { error: `Unknown tool: ${toolName}` }\n }\n\n getCollectedPosts(): PlatformPost[] {\n return this.collectedPosts\n }\n}\n\n// ── Helper: map raw platform string → Platform enum ─────────────────────────\n\nfunction toPlatformEnum(raw: string): Platform {\n const normalised = raw.toLowerCase().trim()\n switch (normalised) {\n case 'tiktok':\n return Platform.TikTok\n case 'youtube':\n return Platform.YouTube\n case 'instagram':\n return Platform.Instagram\n case 'linkedin':\n return Platform.LinkedIn\n case 'x':\n case 'twitter':\n case 'x (twitter)':\n case 'x/twitter':\n return Platform.X\n default:\n return normalised as Platform\n }\n}\n\n// ── Helper: render a post file with YAML frontmatter ───────────────────────\n\ninterface RenderPostOpts {\n videoSlug: string\n shortSlug?: string | null\n}\n\nfunction renderPostFile(post: PlatformPost, opts: RenderPostOpts): string {\n const now = new Date().toISOString()\n const platform = toPlatformEnum(post.platform)\n const lines: string[] = ['---']\n\n lines.push(`platform: ${platform}`)\n lines.push(`status: draft`)\n lines.push(`scheduledDate: null`)\n\n if (post.hashtags.length > 0) {\n lines.push('hashtags:')\n for (const tag of post.hashtags) {\n lines.push(` - \"${tag}\"`)\n }\n } else {\n lines.push('hashtags: []')\n }\n\n if (post.links.length > 0) {\n lines.push('links:')\n for (const link of post.links) {\n lines.push(` - url: \"${link}\"`)\n lines.push(` title: null`)\n }\n } else {\n lines.push('links: []')\n }\n\n lines.push(`characterCount: ${post.characterCount}`)\n lines.push(`videoSlug: \"${opts.videoSlug}\"`)\n lines.push(`shortSlug: ${opts.shortSlug ? `\"${opts.shortSlug}\"` : 'null'}`)\n lines.push(`createdAt: \"${now}\"`)\n lines.push('---')\n lines.push('')\n lines.push(post.content)\n lines.push('')\n\n return lines.join('\\n')\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\nexport async function generateShortPosts(\n video: VideoFile,\n short: ShortClip,\n transcript: Transcript,\n model?: string,\n): Promise<SocialPost[]> {\n const agent = new SocialMediaAgent(model)\n\n try {\n // Extract transcript segments that overlap with the short's time ranges\n const relevantText = transcript.segments\n .filter((seg) =>\n short.segments.some((ss) => seg.start < ss.end && seg.end > ss.start),\n )\n .map((seg) => seg.text)\n .join(' ')\n\n const userMessage = [\n '## Short Clip Metadata',\n `- **Title:** ${short.title}`,\n `- **Description:** ${short.description}`,\n `- **Duration:** ${short.totalDuration.toFixed(1)}s`,\n `- **Tags:** ${short.tags.join(', ')}`,\n '',\n '## Relevant Transcript',\n relevantText.slice(0, 3000),\n ].join('\\n')\n\n await agent.run(userMessage)\n\n const collectedPosts = agent.getCollectedPosts()\n\n // Save posts to recordings/{slug}/shorts/{short-slug}/posts/\n const shortsDir = join(dirname(video.repoPath), 'shorts')\n const postsDir = join(shortsDir, short.slug, 'posts')\n ensureDirectorySync(postsDir)\n\n const socialPosts: SocialPost[] = collectedPosts.map((p) => {\n const platform = toPlatformEnum(p.platform)\n const outputPath = join(postsDir, `${platform}.md`)\n\n writeTextFileSync(\n outputPath,\n renderPostFile(p, { videoSlug: video.slug, shortSlug: short.slug }),\n )\n logger.info(`[SocialMediaAgent] Wrote short post ${outputPath}`)\n\n return {\n platform,\n content: p.content,\n hashtags: p.hashtags,\n links: p.links,\n characterCount: p.characterCount,\n outputPath,\n }\n })\n\n return socialPosts\n } finally {\n await agent.destroy()\n }\n}\n\nexport async function generateSocialPosts(\n video: VideoFile,\n transcript: Transcript,\n summary: VideoSummary,\n outputDir?: string,\n model?: string,\n): Promise<SocialPost[]> {\n const agent = new SocialMediaAgent(model)\n\n try {\n // Build the user prompt with transcript summary and metadata\n const userMessage = [\n '## Video Metadata',\n `- **Title:** ${summary.title}`,\n `- **Slug:** ${video.slug}`,\n `- **Duration:** ${video.duration}s`,\n '',\n '## Summary',\n summary.overview,\n '',\n '## Key Topics',\n summary.keyTopics.map((t) => `- ${t}`).join('\\n'),\n '',\n '## Transcript (first 3000 chars)',\n transcript.text.slice(0, 3000),\n ].join('\\n')\n\n await agent.run(userMessage)\n\n const collectedPosts = agent.getCollectedPosts()\n\n // Ensure the output directory exists\n const outDir = outputDir ?? join(video.videoDir, 'social-posts')\n ensureDirectorySync(outDir)\n\n const socialPosts: SocialPost[] = collectedPosts.map((p) => {\n const platform = toPlatformEnum(p.platform)\n const outputPath = join(outDir, `${platform}.md`)\n\n writeTextFileSync(\n outputPath,\n renderPostFile(p, { videoSlug: video.slug }),\n )\n logger.info(`[SocialMediaAgent] Wrote ${outputPath}`)\n\n return {\n platform,\n content: p.content,\n hashtags: p.hashtags,\n links: p.links,\n characterCount: p.characterCount,\n outputPath,\n }\n })\n\n return socialPosts\n } finally {\n await agent.destroy()\n }\n}\n","import type { ToolWithHandler, MCPServerConfig } from '../providers/types.js'\nimport { ensureDirectorySync, writeTextFileSync } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../config/logger'\nimport { getBrandConfig } from '../config/brand'\nimport { getConfig } from '../config/environment.js'\nimport type { Transcript, VideoFile, VideoSummary } from '../types'\n\n// ── Tool argument shapes ────────────────────────────────────────────────────\n\ninterface WriteBlogArgs {\n frontmatter: {\n title: string\n description: string\n tags: string[]\n cover_image?: string\n }\n body: string\n}\n\n// ── Build system prompt from brand config ───────────────────────────────────\n\nfunction buildSystemPrompt(): string {\n const brand = getBrandConfig()\n\n return `You are a technical blog writer for dev.to, writing from the perspective of ${brand.name} (${brand.handle}).\n\nVoice & style:\n- Tone: ${brand.voice.tone}\n- Personality: ${brand.voice.personality}\n- Style: ${brand.voice.style}\n\nContent guidelines: ${brand.contentGuidelines.blogFocus}\n\nYour task is to generate a full dev.to-style technical blog post (800-1500 words) based on a video transcript and summary.\n\nThe blog post MUST include:\n1. dev.to frontmatter (title, published: false, description, tags, cover_image placeholder)\n2. An engaging introduction with a hook\n3. Clear sections covering the main content (e.g. The Problem, The Solution, How It Works)\n4. Code snippets where the video content discusses code — use fenced code blocks with language tags\n5. Key Takeaways section\n6. A conclusion\n7. A footer referencing the original video\n\nWorkflow:\n1. First use the \"web_search_exa\" tool to search for relevant articles and resources to link to. Search for key topics from the video.\n2. Then call \"write_blog\" with the complete blog post including frontmatter and body.\n - Weave the search result links organically into the post text (don't dump them at the end).\n - Reference the video and any shorts naturally.\n\nAlways call \"write_blog\" exactly once with the complete post.`\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass BlogAgent extends BaseAgent {\n private blogContent: WriteBlogArgs | null = null\n\n constructor(model?: string) {\n super('BlogAgent', buildSystemPrompt(), undefined, model)\n }\n\n protected getMcpServers(): Record<string, MCPServerConfig> | undefined {\n const config = getConfig()\n if (!config.EXA_API_KEY) return undefined\n return {\n exa: {\n type: 'http' as const,\n url: `${config.EXA_MCP_URL}?exaApiKey=${config.EXA_API_KEY}&tools=web_search_exa`,\n headers: {},\n tools: ['*'],\n },\n }\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'write_blog',\n description:\n 'Submit the complete dev.to blog post with frontmatter and markdown body.',\n parameters: {\n type: 'object',\n properties: {\n frontmatter: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n description: { type: 'string' },\n tags: { type: 'array', items: { type: 'string' } },\n cover_image: { type: 'string' },\n },\n required: ['title', 'description', 'tags'],\n },\n body: {\n type: 'string',\n description: 'The full markdown body of the blog post (excluding frontmatter)',\n },\n },\n required: ['frontmatter', 'body'],\n },\n handler: async (args: unknown) => {\n const blogArgs = args as WriteBlogArgs\n this.blogContent = blogArgs\n logger.info(`[BlogAgent] write_blog received post: \"${blogArgs.frontmatter.title}\"`)\n return JSON.stringify({ success: true })\n },\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n _args: Record<string, unknown>,\n ): Promise<unknown> {\n logger.warn(`[BlogAgent] Unexpected handleToolCall for \"${toolName}\"`)\n return { error: `Unknown tool: ${toolName}` }\n }\n\n getBlogContent(): WriteBlogArgs | null {\n return this.blogContent\n }\n}\n\n// ── Render the final markdown ───────────────────────────────────────────────\n\nfunction renderBlogMarkdown(blog: WriteBlogArgs): string {\n const fm = blog.frontmatter\n const tags = fm.tags.map((t) => t.toLowerCase().replace(/[^a-z0-9]/g, '')).join(', ')\n\n const lines: string[] = [\n '---',\n `title: \"${fm.title}\"`,\n 'published: false',\n `description: \"${fm.description}\"`,\n `tags: ${tags}`,\n `cover_image: ${fm.cover_image || ''}`,\n '---',\n '',\n blog.body,\n ]\n\n return lines.join('\\n')\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\nexport async function generateBlogPost(\n video: VideoFile,\n transcript: Transcript,\n summary: VideoSummary,\n model?: string,\n): Promise<string> {\n const agent = new BlogAgent(model)\n\n try {\n const userMessage = [\n '## Video Metadata',\n `- **Title:** ${summary.title}`,\n `- **Slug:** ${video.slug}`,\n `- **Duration:** ${video.duration}s`,\n `- **Recorded:** ${video.createdAt.toISOString().split('T')[0]}`,\n '',\n '## Summary',\n summary.overview,\n '',\n '## Key Topics',\n summary.keyTopics.map((t) => `- ${t}`).join('\\n'),\n '',\n '## Transcript (first 6000 chars)',\n transcript.text.slice(0, 6000),\n ].join('\\n')\n\n await agent.run(userMessage)\n\n const blogContent = agent.getBlogContent()\n if (!blogContent) {\n throw new Error('BlogAgent did not produce any blog content')\n }\n\n const outDir = join(video.videoDir, 'social-posts')\n ensureDirectorySync(outDir)\n\n const outputPath = join(outDir, 'devto.md')\n writeTextFileSync(outputPath, renderBlogMarkdown(blogContent))\n logger.info(`[BlogAgent] Wrote blog post to ${outputPath}`)\n\n return outputPath\n } finally {\n await agent.destroy()\n }\n}\n","import { Command } from 'commander'\nimport readline from 'readline'\nimport open from 'open'\n\nexport { Command }\nexport type { ReadLine } from 'readline'\n\n/** Create a readline interface for interactive prompts. */\nexport function createReadlineInterface(opts?: readline.ReadLineOptions): readline.Interface {\n return readline.createInterface(opts ?? { input: process.stdin, output: process.stdout })\n}\n\n/** Open a URL in the default browser. */\nexport async function openUrl(url: string): Promise<void> {\n await open(url)\n}\n","import { Command } from './core/cli.js'\nimport { initConfig, validateRequiredKeys, getConfig } from './config/environment'\nimport type { CLIOptions } from './config/environment'\nimport { FileWatcher } from './services/fileWatcher'\nimport { processVideoSafe } from './pipeline'\nimport logger, { setVerbose } from './config/logger'\nimport { runDoctor } from './commands/doctor'\nimport { runInit } from './commands/init'\nimport { runSchedule } from './commands/schedule'\nimport { startReviewServer } from './review/server'\nimport { openUrl } from './core/cli.js'\nimport { readTextFileSync } from './core/fileSystem.js'\nimport { projectRoot, join, resolve } from './core/paths.js'\n\nconst pkg = JSON.parse(readTextFileSync(join(projectRoot(), 'package.json')))\n\nconst BANNER = `\n╔══════════════════════════════════════╗\n║ VidPipe v${pkg.version.padEnd(24)}║\n╚══════════════════════════════════════╝\n`\n\nconst program = new Command()\n\nprogram\n .name('vidpipe')\n .description('AI-powered video content pipeline: transcribe, summarize, generate shorts, captions, and social posts')\n .version(pkg.version, '-V, --version')\n\n// --- Subcommands ---\n\nprogram\n .command('init')\n .description('Interactive setup wizard — configure API keys, providers, and social publishing')\n .action(async () => {\n await runInit()\n process.exit(0)\n })\n\nprogram\n .command('review')\n .description('Open the social media post review app in your browser')\n .option('--port <number>', 'Server port (default: 3847)', '3847')\n .action(async (opts) => {\n initConfig()\n const parsedPort = Number.parseInt(opts.port, 10)\n if (Number.isNaN(parsedPort) || parsedPort < 1 || parsedPort > 65535) {\n console.error('Invalid --port value. Must be an integer between 1 and 65535.')\n process.exit(1)\n }\n const { port, close } = await startReviewServer({ port: parsedPort })\n await openUrl(`http://localhost:${port}`)\n console.log(`\\nReview app running at http://localhost:${port}`)\n console.log('Press Ctrl+C to stop.\\n')\n\n const shutdown = async () => {\n console.log('\\nShutting down...')\n // Restore terminal to normal mode on Windows\n if (process.platform === 'win32' && process.stdin.setRawMode) {\n process.stdin.setRawMode(false)\n }\n await close()\n process.exit(0)\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n // On Windows, listen for raw input since SIGINT is unreliable\n if (process.platform === 'win32') {\n process.stdin.resume()\n process.stdin.setRawMode?.(true)\n process.stdin.on('data', (data) => {\n // Ctrl-C is byte 0x03\n if (data[0] === 0x03) void shutdown()\n })\n }\n })\n\nprogram\n .command('schedule')\n .description('View the current posting schedule across platforms')\n .option('--platform <name>', 'Filter by platform (tiktok, youtube, instagram, linkedin, twitter)')\n .action(async (opts) => {\n await runSchedule({ platform: opts.platform })\n process.exit(0)\n })\n\nprogram\n .command('doctor')\n .description('Check all prerequisites and dependencies')\n .action(async () => {\n await runDoctor()\n })\n\n// --- Default command (process video or watch) ---\n// This must come after subcommands so they take priority\n\nconst defaultCmd = program\n .command('process', { isDefault: true })\n .argument('[video-path]', 'Path to a video file to process (implies --once)')\n .option('--watch-dir <path>', 'Folder to watch for new recordings (default: env WATCH_FOLDER)')\n .option('--output-dir <path>', 'Output directory for processed videos (default: ./recordings)')\n .option('--openai-key <key>', 'OpenAI API key (default: env OPENAI_API_KEY)')\n .option('--exa-key <key>', 'Exa AI API key for web search (default: env EXA_API_KEY)')\n .option('--once', 'Process a single video and exit (no watching)')\n .option('--brand <path>', 'Path to brand.json config (default: ./brand.json)')\n .option('--no-git', 'Skip git commit/push stage')\n .option('--no-silence-removal', 'Skip silence removal stage')\n .option('--no-shorts', 'Skip shorts generation')\n .option('--no-medium-clips', 'Skip medium clip generation')\n .option('--no-social', 'Skip social media post generation')\n .option('--no-captions', 'Skip caption generation/burning')\n .option('--no-social-publish', 'Skip social media publishing/queue-build stage')\n .option('--late-api-key <key>', 'Late API key (default: env LATE_API_KEY)')\n .option('--late-profile-id <id>', 'Late profile ID (default: env LATE_PROFILE_ID)')\n .option('-v, --verbose', 'Verbose logging')\n .option('--doctor', 'Check all prerequisites and exit')\n .action(async (videoPath: string | undefined) => {\n const opts = defaultCmd.opts()\n\n // Handle --doctor before anything else\n if (opts.doctor) {\n await runDoctor()\n process.exit(0)\n }\n\n const onceMode: boolean = opts.once || !!videoPath\n\n const cliOptions: CLIOptions = {\n watchDir: opts.watchDir,\n outputDir: opts.outputDir,\n openaiKey: opts.openaiKey,\n exaKey: opts.exaKey,\n brand: opts.brand,\n verbose: opts.verbose,\n git: opts.git,\n silenceRemoval: opts.silenceRemoval,\n shorts: opts.shorts,\n mediumClips: opts.mediumClips,\n social: opts.social,\n captions: opts.captions,\n socialPublish: opts.socialPublish,\n lateApiKey: opts.lateApiKey,\n lateProfileId: opts.lateProfileId,\n }\n\n logger.info(BANNER)\n initConfig(cliOptions)\n if (opts.verbose) setVerbose()\n validateRequiredKeys()\n\n const config = getConfig()\n logger.info(`Watch folder: ${config.WATCH_FOLDER}`)\n logger.info(`Output dir: ${config.OUTPUT_DIR}`)\n\n // Direct file mode\n if (videoPath) {\n const resolvedPath = resolve(videoPath)\n logger.info(`Processing single video: ${resolvedPath}`)\n await processVideoSafe(resolvedPath)\n logger.info('Done.')\n process.exit(0)\n }\n\n // Watch mode\n const watcher = new FileWatcher()\n let processing = false\n let shutdownRequested = false\n const queue: string[] = []\n\n async function processQueue(): Promise<void> {\n if (processing || queue.length === 0) return\n processing = true\n try {\n while (queue.length > 0) {\n const vp = queue.shift()!\n logger.info(`Processing video: ${vp}`)\n await processVideoSafe(vp)\n if (onceMode) {\n logger.info('--once flag set, exiting after first video.')\n await shutdown()\n return\n }\n if (shutdownRequested) break\n }\n } finally {\n processing = false\n }\n }\n\n async function shutdown(): Promise<void> {\n if (shutdownRequested) return\n shutdownRequested = true\n logger.info('Shutting down...')\n watcher.stop()\n while (processing) await new Promise(r => setTimeout(r, 500))\n logger.info('Goodbye.')\n process.exit(0)\n }\n\n process.on('SIGINT', () => shutdown())\n process.on('SIGTERM', () => shutdown())\n\n watcher.on('new-video', (filePath: string) => {\n queue.push(filePath)\n logger.info(`Queued video: ${filePath} (queue length: ${queue.length})`)\n processQueue().catch(err => logger.error('Queue processing error:', err))\n })\n watcher.start()\n\n if (onceMode) {\n logger.info('Running in --once mode. Will exit after processing the next video.')\n } else {\n logger.info('Watching for new videos. Press Ctrl+C to stop.')\n }\n })\n\nprogram.parse()\n","export { watch, type FSWatcher } from 'chokidar'\nexport { EventEmitter } from 'events'\n","import { watch, type FSWatcher } from '../core/watcher.js'\nimport { getConfig } from '../config/environment'\nimport { EventEmitter } from '../core/watcher.js'\nimport { join, extname } from '../core/paths.js'\nimport { fileExistsSync, ensureDirectorySync, getFileStatsSync, listDirectorySync } from '../core/fileSystem.js'\nimport logger from '../config/logger'\n\nexport interface FileWatcherOptions {\n processExisting?: boolean\n}\n\nexport class FileWatcher extends EventEmitter {\n private watchFolder: string\n private watcher: FSWatcher | null = null\n private processExisting: boolean\n\n constructor(options: FileWatcherOptions = {}) {\n super()\n const config = getConfig()\n this.watchFolder = config.WATCH_FOLDER\n this.processExisting = options.processExisting ?? false\n\n if (!fileExistsSync(this.watchFolder)) {\n ensureDirectorySync(this.watchFolder)\n logger.info(`Created watch folder: ${this.watchFolder}`)\n }\n }\n\n private static readonly MIN_FILE_SIZE = 1024 * 1024 // 1MB\n private static readonly EXTRA_STABILITY_DELAY = 3000\n\n /** Read file size, wait, read again — if it changed the file is still being written. */\n private async isFileStable(filePath: string): Promise<boolean> {\n try {\n const sizeBefore = getFileStatsSync(filePath).size\n await new Promise((resolve) => setTimeout(resolve, FileWatcher.EXTRA_STABILITY_DELAY))\n const sizeAfter = getFileStatsSync(filePath).size\n return sizeBefore === sizeAfter\n } catch {\n return false\n }\n }\n\n private async handleDetectedFile(filePath: string): Promise<void> {\n if (extname(filePath).toLowerCase() !== '.mp4') {\n logger.debug(`[watcher] Ignoring non-mp4 file: ${filePath}`)\n return\n }\n\n let fileSize: number\n try {\n fileSize = getFileStatsSync(filePath).size\n } catch (err) {\n logger.warn(`[watcher] Could not stat file (may have been removed): ${filePath}`)\n return\n }\n\n logger.debug(`[watcher] File size: ${(fileSize / 1024 / 1024).toFixed(1)} MB — ${filePath}`)\n if (fileSize < FileWatcher.MIN_FILE_SIZE) {\n logger.warn(`Skipping small file (${fileSize} bytes), likely a failed recording: ${filePath}`)\n return\n }\n\n const stable = await this.isFileStable(filePath)\n if (!stable) {\n logger.warn(`File is still being written, skipping for now: ${filePath}`)\n return\n }\n\n logger.info(`New video detected: ${filePath}`)\n this.emit('new-video', filePath)\n }\n\n private scanExistingFiles(): void {\n let files: string[]\n try {\n files = listDirectorySync(this.watchFolder)\n } catch (err: any) {\n if (err?.code === 'ENOENT') {\n logger.warn(`Watch folder does not exist, skipping scan: ${this.watchFolder}`)\n return\n }\n throw err\n }\n for (const file of files) {\n if (extname(file).toLowerCase() === '.mp4') {\n const filePath = join(this.watchFolder, file)\n this.handleDetectedFile(filePath).catch(err =>\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\n )\n }\n }\n }\n\n start(): void {\n this.watcher = watch(this.watchFolder, {\n persistent: true,\n ignoreInitial: true,\n depth: 0,\n atomic: 100,\n // Polling is more reliable on Windows for detecting renames (e.g. Bandicam temp→final)\n usePolling: true,\n interval: 500,\n awaitWriteFinish: {\n stabilityThreshold: 3000,\n pollInterval: 200,\n },\n })\n\n this.watcher.on('add', (filePath: string) => {\n logger.debug(`[watcher] 'add' event: ${filePath}`)\n this.handleDetectedFile(filePath).catch(err =>\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\n )\n })\n\n this.watcher.on('change', (filePath: string) => {\n logger.debug(`[watcher] 'change' event: ${filePath}`)\n if (extname(filePath).toLowerCase() !== '.mp4') return\n logger.info(`Change detected on video file: ${filePath}`)\n this.handleDetectedFile(filePath).catch(err =>\n logger.error(`Error processing ${filePath}: ${err instanceof Error ? err.message : String(err)}`)\n )\n })\n\n this.watcher.on('unlink', (filePath: string) => {\n logger.debug(`[watcher] 'unlink' event: ${filePath}`)\n })\n\n this.watcher.on('raw', (event: string, rawPath: string, details: unknown) => {\n logger.debug(`[watcher] raw event=${event} path=${rawPath}`)\n })\n\n this.watcher.on('error', (error: unknown) => {\n logger.error(`File watcher error: ${error instanceof Error ? error.message : String(error)}`)\n })\n\n this.watcher.on('ready', () => {\n logger.info('File watcher is fully initialized and ready')\n if (this.processExisting) {\n this.scanExistingFiles()\n }\n })\n\n logger.info(`Watching for new .mp4 files in: ${this.watchFolder}`)\n }\n\n stop(): void {\n if (this.watcher) {\n this.watcher.close()\n this.watcher = null\n logger.info('File watcher stopped')\n }\n }\n}\n","import { join, dirname, basename } from './core/paths.js'\nimport { ensureDirectory, writeJsonFile, writeTextFile, copyFile, removeFile, fileExists, readTextFile } from './core/fileSystem.js'\nimport logger from './config/logger'\nimport { getConfig } from './config/environment'\nimport { MainVideoAsset } from './assets/MainVideoAsset.js'\nimport { transcribeVideo } from './services/transcription'\nimport { generateCaptions } from './services/captionGeneration'\nimport { generateSummary } from './agents/SummaryAgent'\nimport { generateShorts } from './agents/ShortsAgent'\nimport { generateMediumClips } from './agents/MediumVideoAgent'\nimport { generateSocialPosts, generateShortPosts } from './agents/SocialMediaAgent'\nimport { generateBlogPost } from './agents/BlogAgent'\nimport { generateChapters } from './agents/ChapterAgent'\nimport { commitAndPush } from './services/gitOperations'\nimport { buildPublishQueue } from './services/queueBuilder'\nimport type { QueueBuildResult } from './services/queueBuilder'\nimport { ProducerAgent, type ProduceResult } from './agents/ProducerAgent.js'\nimport { burnCaptions } from './tools/ffmpeg/captionBurning'\nimport { singlePassEditAndCaption } from './tools/ffmpeg/singlePassEdit'\nimport { getModelForAgent } from './config/modelConfig.js'\nimport { costTracker } from './services/costTracker.js'\nimport type { CostReport } from './services/costTracker.js'\nimport type {\n VideoFile,\n Transcript,\n VideoSummary,\n ShortClip,\n MediumClip,\n SocialPost,\n StageResult,\n PipelineResult,\n PipelineStage,\n Chapter,\n} from './types'\nimport { PipelineStage as Stage } from './types'\n\n/**\n * Execute a single pipeline stage with error isolation and timing.\n *\n * ### Stage contract\n * - Each stage is wrapped in a try/catch so a failure **does not abort** the\n * pipeline. Subsequent stages proceed with whatever data is available.\n * - Returns `undefined` on failure (callers must null-check before using the result).\n * - Records success/failure, error message, and wall-clock duration in `stageResults`\n * for the pipeline summary.\n *\n * This design lets the pipeline produce partial results — e.g. if shorts\n * generation fails, the summary and social posts can still be generated\n * from the transcript.\n *\n * @param stageName - Enum value identifying the stage (used in logs and results)\n * @param fn - Async function that performs the stage's work\n * @param stageResults - Mutable array that accumulates per-stage outcome records\n * @returns The stage result on success, or `undefined` on failure\n */\nexport async function runStage<T>(\n stageName: PipelineStage,\n fn: () => Promise<T>,\n stageResults: StageResult[],\n): Promise<T | undefined> {\n costTracker.setStage(stageName)\n const start = Date.now()\n try {\n const result = await fn()\n const duration = Date.now() - start\n stageResults.push({ stage: stageName, success: true, duration })\n logger.info(`Stage ${stageName} completed in ${duration}ms`)\n return result\n } catch (err: unknown) {\n const duration = Date.now() - start\n const message = err instanceof Error ? err.message : String(err)\n stageResults.push({ stage: stageName, success: false, error: message, duration })\n logger.error(`Stage ${stageName} failed after ${duration}ms: ${message}`)\n return undefined\n }\n}\n\n/**\n * Adjust transcript timestamps to account for removed silence segments.\n * Shifts all timestamps by subtracting the cumulative removed duration before each point.\n */\nexport function adjustTranscript(\n transcript: Transcript,\n removals: { start: number; end: number }[],\n): Transcript {\n const sorted = [...removals].sort((a, b) => a.start - b.start)\n\n function adjustTime(t: number): number {\n let offset = 0\n for (const r of sorted) {\n if (t <= r.start) break\n if (t >= r.end) {\n offset += r.end - r.start\n } else {\n // timestamp is inside a removed region — snap to removal start\n offset += t - r.start\n }\n }\n return t - offset\n }\n\n return {\n ...transcript,\n duration: adjustTime(transcript.duration),\n segments: transcript.segments\n .filter(seg => !sorted.some(r => seg.start >= r.start && seg.end <= r.end))\n .map(seg => ({\n ...seg,\n start: adjustTime(seg.start),\n end: adjustTime(seg.end),\n })),\n words: transcript.words\n .filter(w => !sorted.some(r => w.start >= r.start && w.end <= r.end))\n .map(w => ({\n ...w,\n start: adjustTime(w.start),\n end: adjustTime(w.end),\n })),\n }\n}\n\n/**\n * Run the full video processing pipeline.\n *\n * ### Stage ordering and data flow\n * 1. **Ingest** — extracts metadata (slug, duration, paths). Required; aborts if failed.\n * 2. **Transcribe** — Whisper transcription with word-level timestamps.\n * 3. **Video cleaning** — ProducerAgent trims dead air / bad segments and adjusts\n * the transcript timestamps accordingly. Produces an `adjustedTranscript` for captions.\n * 4. **Captions** — generates SRT/VTT/ASS files from the (adjusted) transcript.\n * 5. **Caption burn** — renders captions into the video using FFmpeg. Prefers a\n * single-pass approach (silence removal + captions in one encode) when possible.\n * 6. **Shorts** — AI-selected short clips. Uses the **original** transcript because\n * clips are cut from the original (unedited) video.\n * 7. **Medium clips** — longer AI-selected clips (same original-transcript reasoning).\n * 8. **Chapters** — topic-boundary detection for YouTube chapters.\n * 9. **Summary** — README generation (runs after shorts/chapters so it can reference them).\n * 10–12. **Social posts** — platform-specific posts for the full video and each clip.\n * 13. **Queue build** — populates publish-queue/ for review before publishing.\n * 14. **Blog** — long-form blog post from transcript + summary.\n * 15. **Git push** — commits all generated assets and pushes.\n *\n * ### Why failures don't abort\n * Each stage runs through {@link runStage} which catches errors. This means a\n * transcription failure still lets git-push run (committing whatever was produced),\n * and a shorts failure doesn't block summary generation.\n */\nexport async function processVideo(videoPath: string): Promise<PipelineResult> {\n const pipelineStart = Date.now()\n const stageResults: StageResult[] = []\n const cfg = getConfig()\n\n costTracker.reset()\n logger.info(`Pipeline starting for: ${videoPath}`)\n\n // 1. Ingestion — required for all subsequent stages\n // Use MainVideoAsset for ingestion, then convert to VideoFile for compatibility\n const videoAsset = await runStage<MainVideoAsset>(Stage.Ingestion, () => MainVideoAsset.ingest(videoPath), stageResults)\n if (!videoAsset) {\n const totalDuration = Date.now() - pipelineStart\n logger.error('Ingestion failed — cannot proceed without video metadata')\n return { video: { originalPath: videoPath, repoPath: '', videoDir: '', slug: '', filename: '', duration: 0, size: 0, createdAt: new Date() }, transcript: undefined, editedVideoPath: undefined, captions: undefined, captionedVideoPath: undefined, summary: undefined, shorts: [], mediumClips: [], socialPosts: [], blogPost: undefined, stageResults, totalDuration }\n }\n\n // Convert to VideoFile for backward compatibility with existing agents/services\n const video = await videoAsset.toVideoFile()\n\n // 2. Transcription\n let transcript: Transcript | undefined\n transcript = await runStage<Transcript>(Stage.Transcription, () => transcribeVideo(video), stageResults)\n\n // 3. Video Cleaning (ProducerAgent-based)\n let editedVideoPath: string | undefined\n let adjustedTranscript: Transcript | undefined\n let cleaningKeepSegments: { start: number; end: number }[] | undefined\n\n if (transcript && !cfg.SKIP_SILENCE_REMOVAL) {\n // Trigger Gemini editorial direction (cached for ProducerAgent use)\n await videoAsset.getEditorialDirection().catch(err => {\n logger.warn(`[Pipeline] Editorial direction unavailable: ${err instanceof Error ? err.message : String(err)}`)\n })\n\n const cleaningResult = await runStage<ProduceResult>(\n Stage.SilenceRemoval,\n async () => {\n const agent = new ProducerAgent(videoAsset, getModelForAgent('ProducerAgent'))\n const editedPath = join(video.videoDir, `${video.slug}-edited.mp4`)\n return agent.produce(editedPath)\n },\n stageResults,\n )\n\n if (cleaningResult && cleaningResult.success && cleaningResult.removals.length > 0) {\n editedVideoPath = cleaningResult.outputPath\n cleaningKeepSegments = cleaningResult.keepSegments\n adjustedTranscript = adjustTranscript(transcript, cleaningResult.removals)\n\n // Validate: check that adjusted transcript duration is close to edited video duration\n const totalRemoved = cleaningResult.removals.reduce((sum, r) => sum + (r.end - r.start), 0)\n const expectedDuration = transcript.duration - totalRemoved\n const adjustedDuration = adjustedTranscript.duration\n const drift = Math.abs(expectedDuration - adjustedDuration)\n logger.info(`[Pipeline] Video cleaning: original=${transcript.duration.toFixed(1)}s, removed=${totalRemoved.toFixed(1)}s, expected=${expectedDuration.toFixed(1)}s, adjusted=${adjustedDuration.toFixed(1)}s, drift=${drift.toFixed(1)}s`)\n\n await writeJsonFile(\n join(video.videoDir, 'transcript-edited.json'),\n adjustedTranscript,\n )\n }\n }\n\n // Gemini Pass 2: Analyze cleaned video for clip direction\n // This provides short/medium clip suggestions to downstream agents\n if (editedVideoPath && !cfg.SKIP_SHORTS) {\n try {\n if (cfg.GEMINI_API_KEY) {\n logger.info('[Pipeline] Running Gemini Pass 2: clip direction analysis on cleaned video')\n const { analyzeVideoClipDirection } = await import('./tools/gemini/geminiClient.js')\n const metadata = await videoAsset.getMetadata()\n const clipDirection = await analyzeVideoClipDirection(editedVideoPath, metadata.duration)\n await writeTextFile(join(video.videoDir, 'clip-direction.md'), clipDirection)\n logger.info(`[Pipeline] Clip direction saved (${clipDirection.length} chars)`)\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.warn(`[Pipeline] Gemini clip direction failed (non-fatal): ${msg}`)\n }\n }\n\n // Use adjusted transcript for captions (if silence was removed), original otherwise\n const captionTranscript = adjustedTranscript ?? transcript\n\n // 4. Captions (fast, no AI needed) — generate from the right transcript\n let captions: string[] | undefined\n if (captionTranscript && !cfg.SKIP_CAPTIONS) {\n captions = await runStage<string[]>(Stage.Captions, () => generateCaptions(video, captionTranscript), stageResults)\n }\n\n // 5. Caption Burn — use single-pass (silence removal + captions) when possible\n let captionedVideoPath: string | undefined\n if (captions && !cfg.SKIP_CAPTIONS) {\n const assFile = captions.find((p) => p.endsWith('.ass'))\n if (assFile && cleaningKeepSegments) {\n // Single-pass: re-do cleaning + burn captions from ORIGINAL video in one encode\n // This guarantees frame-accurate cuts with perfectly aligned captions\n const captionedOutput = join(video.videoDir, `${video.slug}-captioned.mp4`)\n captionedVideoPath = await runStage<string>(\n Stage.CaptionBurn,\n () => singlePassEditAndCaption(video.repoPath, cleaningKeepSegments!, assFile, captionedOutput),\n stageResults,\n )\n } else if (assFile) {\n // No cleaning — just burn captions into original video\n const videoToBurn = editedVideoPath ?? video.repoPath\n const captionedOutput = join(video.videoDir, `${video.slug}-captioned.mp4`)\n captionedVideoPath = await runStage<string>(\n Stage.CaptionBurn,\n () => burnCaptions(videoToBurn, assFile, captionedOutput),\n stageResults,\n )\n }\n }\n\n // 6. Shorts — use adjusted transcript + cleaned video (clips cut from cleaned video)\n let shorts: ShortClip[] = []\n if (transcript && !cfg.SKIP_SHORTS) {\n const shortsTranscript = adjustedTranscript ?? transcript\n const shortsVideo: VideoFile = editedVideoPath ? { ...video, repoPath: editedVideoPath } : video\n let clipDirection: string | undefined\n try {\n const clipDirPath = join(video.videoDir, 'clip-direction.md')\n if (await fileExists(clipDirPath)) {\n clipDirection = await readTextFile(clipDirPath)\n }\n } catch { /* clip direction is optional */ }\n const result = await runStage<ShortClip[]>(Stage.Shorts, () => generateShorts(shortsVideo, shortsTranscript, getModelForAgent('ShortsAgent'), clipDirection), stageResults)\n if (result) shorts = result\n }\n\n // 7. Medium Clips — use adjusted transcript + cleaned video (clips cut from cleaned video)\n let mediumClips: MediumClip[] = []\n if (transcript && !cfg.SKIP_MEDIUM_CLIPS) {\n const mediumTranscript = adjustedTranscript ?? transcript\n const mediumVideo: VideoFile = editedVideoPath ? { ...video, repoPath: editedVideoPath } : video\n let mediumClipDirection: string | undefined\n try {\n const clipDirPath = join(video.videoDir, 'clip-direction.md')\n if (await fileExists(clipDirPath)) {\n mediumClipDirection = await readTextFile(clipDirPath)\n }\n } catch { /* clip direction is optional */ }\n const result = await runStage<MediumClip[]>(Stage.MediumClips, () => generateMediumClips(mediumVideo, mediumTranscript, getModelForAgent('MediumVideoAgent'), mediumClipDirection), stageResults)\n if (result) mediumClips = result\n }\n\n // All downstream stages use the adjusted transcript (post-cleaning) when available\n const downstreamTranscript = adjustedTranscript ?? transcript\n\n // 8. Chapters — analyse transcript for topic boundaries\n let chapters: Chapter[] | undefined\n if (downstreamTranscript) {\n chapters = await runStage<Chapter[]>(Stage.Chapters, () => generateChapters(video, downstreamTranscript, getModelForAgent('ChapterAgent')), stageResults)\n }\n\n // 9. Summary (after shorts, medium clips, and chapters so the README can reference them)\n let summary: VideoSummary | undefined\n if (downstreamTranscript) {\n summary = await runStage<VideoSummary>(Stage.Summary, () => generateSummary(video, downstreamTranscript, shorts, chapters, getModelForAgent('SummaryAgent')), stageResults)\n }\n\n // 10. Social Media\n let socialPosts: SocialPost[] = []\n if (downstreamTranscript && summary && !cfg.SKIP_SOCIAL) {\n const result = await runStage<SocialPost[]>(\n Stage.SocialMedia,\n () => generateSocialPosts(video, downstreamTranscript, summary, join(video.videoDir, 'social-posts'), getModelForAgent('SocialMediaAgent')),\n stageResults,\n )\n if (result) socialPosts = result\n }\n\n // 11. Short Posts — generate social posts per short clip\n if (downstreamTranscript && shorts.length > 0 && !cfg.SKIP_SOCIAL) {\n await runStage<void>(\n Stage.ShortPosts,\n async () => {\n for (const short of shorts) {\n const posts = await generateShortPosts(video, short, downstreamTranscript, getModelForAgent('ShortPostsAgent'))\n socialPosts.push(...posts)\n }\n },\n stageResults,\n )\n }\n\n // 12. Medium Clip Posts — generate social posts per medium clip\n if (downstreamTranscript && mediumClips.length > 0 && !cfg.SKIP_SOCIAL) {\n await runStage<void>(\n Stage.MediumClipPosts,\n async () => {\n for (const clip of mediumClips) {\n const asShortClip: ShortClip = {\n id: clip.id,\n title: clip.title,\n slug: clip.slug,\n segments: clip.segments,\n totalDuration: clip.totalDuration,\n outputPath: clip.outputPath,\n captionedPath: clip.captionedPath,\n description: clip.description,\n tags: clip.tags,\n }\n const posts = await generateShortPosts(video, asShortClip, downstreamTranscript, getModelForAgent('MediumClipPostsAgent'))\n // Move posts to medium-clips/{slug}/posts/\n const clipsDir = join(dirname(video.repoPath), 'medium-clips')\n const postsDir = join(clipsDir, clip.slug, 'posts')\n await ensureDirectory(postsDir)\n for (const post of posts) {\n const destPath = join(postsDir, basename(post.outputPath))\n await copyFile(post.outputPath, destPath)\n await removeFile(post.outputPath)\n post.outputPath = destPath\n }\n socialPosts.push(...posts)\n }\n },\n stageResults,\n )\n }\n\n // 13. Queue Build — populate publish-queue/ for review\n if (socialPosts.length > 0 && !cfg.SKIP_SOCIAL_PUBLISH) {\n await runStage<QueueBuildResult>(\n Stage.QueueBuild,\n () => buildPublishQueue(video, shorts, mediumClips, socialPosts, captionedVideoPath),\n stageResults,\n )\n }\n\n // 14. Blog Post\n let blogPost: string | undefined\n if (downstreamTranscript && summary) {\n blogPost = await runStage<string>(\n Stage.Blog,\n () => generateBlogPost(video, downstreamTranscript, summary, getModelForAgent('BlogAgent')),\n stageResults,\n )\n }\n\n // 15. Git\n if (!cfg.SKIP_GIT) {\n await runStage<void>(Stage.GitPush, () => commitAndPush(video.slug), stageResults)\n }\n\n const totalDuration = Date.now() - pipelineStart\n\n // Cost tracking report\n const report = costTracker.getReport()\n if (report.records.length > 0) {\n logger.info(costTracker.formatReport())\n const costMd = generateCostMarkdown(report)\n const costPath = join(video.videoDir, 'cost-report.md')\n await writeTextFile(costPath, costMd)\n logger.info(`Cost report saved: ${costPath}`)\n }\n\n logger.info(`Pipeline completed in ${totalDuration}ms`)\n\n return {\n video,\n transcript,\n editedVideoPath,\n captions,\n captionedVideoPath,\n summary,\n chapters,\n shorts,\n mediumClips,\n socialPosts,\n blogPost,\n stageResults,\n totalDuration,\n }\n}\n\nfunction generateCostMarkdown(report: CostReport): string {\n let md = '# Pipeline Cost Report\\n\\n'\n md += `| Metric | Value |\\n|--------|-------|\\n`\n md += `| Total Cost | $${report.totalCostUSD.toFixed(4)} USD |\\n`\n if (report.totalPRUs > 0) md += `| Total PRUs | ${report.totalPRUs} |\\n`\n md += `| Input Tokens | ${report.totalTokens.input.toLocaleString()} |\\n`\n md += `| Output Tokens | ${report.totalTokens.output.toLocaleString()} |\\n`\n md += `| LLM Calls | ${report.records.length} |\\n`\n if (report.totalServiceCostUSD > 0) md += `| Service Costs | $${report.totalServiceCostUSD.toFixed(4)} USD |\\n`\n md += '\\n'\n\n if (Object.keys(report.byAgent).length > 0) {\n md += '## By Agent\\n\\n| Agent | Cost | PRUs | Calls |\\n|-------|------|------|-------|\\n'\n for (const [agent, data] of Object.entries(report.byAgent)) {\n md += `| ${agent} | $${data.costUSD.toFixed(4)} | ${data.prus} | ${data.calls} |\\n`\n }\n md += '\\n'\n }\n\n if (Object.keys(report.byModel).length > 1) {\n md += '## By Model\\n\\n| Model | Cost | PRUs | Calls |\\n|-------|------|------|-------|\\n'\n for (const [model, data] of Object.entries(report.byModel)) {\n md += `| ${model} | $${data.costUSD.toFixed(4)} | ${data.prus} | ${data.calls} |\\n`\n }\n md += '\\n'\n }\n\n if (Object.keys(report.byService).length > 0) {\n md += '## By Service\\n\\n| Service | Cost | Calls |\\n|---------|------|-------|\\n'\n for (const [service, data] of Object.entries(report.byService)) {\n md += `| ${service} | $${data.costUSD.toFixed(4)} | ${data.calls} |\\n`\n }\n md += '\\n'\n }\n\n return md\n}\n\nexport async function processVideoSafe(videoPath: string): Promise<PipelineResult | null> {\n try {\n return await processVideo(videoPath)\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`Pipeline failed with uncaught error: ${message}`)\n return null\n }\n}\n","/**\n * Asset Base Class\n *\n * Abstract base class for all pipeline assets (videos, transcripts, captions, etc.).\n * Provides a common interface for lazy loading, caching, and regeneration of assets.\n *\n * Assets follow the \"compute once, cache forever\" pattern:\n * - `exists()` checks if the asset is already on disk\n * - `getResult()` returns the asset, computing it only if needed\n * - `generate()` forces regeneration even if the asset exists\n *\n * @example\n * ```typescript\n * class TranscriptAsset extends Asset<Transcript> {\n * async exists(): Promise<boolean> {\n * return fs.existsSync(this.transcriptPath)\n * }\n *\n * async getResult(opts?: AssetOptions): Promise<Transcript> {\n * if (!opts?.force && await this.exists()) {\n * return this.loadFromDisk()\n * }\n * return this.transcribe()\n * }\n * }\n * ```\n */\n\n/**\n * Options for asset generation and retrieval.\n */\nexport interface AssetOptions {\n /** Regenerate the asset even if it already exists on disk */\n force?: boolean\n /** Custom prompt for AI-generated assets */\n prompt?: string\n /** Override the default model for AI generation */\n model?: string\n}\n\n/**\n * Abstract base class for pipeline assets.\n *\n * @typeParam T - The type of the asset's result (e.g., Transcript, Caption[], VideoMetadata)\n */\nexport abstract class Asset<T> {\n /** In-memory cache for computed values */\n protected cache: Map<string, unknown> = new Map()\n\n /** Cached result of the asset */\n protected _result: T | undefined\n\n /**\n * Get the asset result, computing it if necessary.\n *\n * Implementations should check `opts.force` and `exists()` to determine\n * whether to load from disk or regenerate.\n *\n * @param opts - Options controlling generation behavior\n * @returns The asset result\n */\n abstract getResult(opts?: AssetOptions): Promise<T>\n\n /**\n * Check if the asset already exists on disk.\n *\n * @returns true if the asset exists and doesn't need regeneration\n */\n abstract exists(): Promise<boolean>\n\n /**\n * Force regeneration of the asset, bypassing any cached or on-disk version.\n *\n * @param opts - Additional options (force is automatically set to true)\n * @returns The newly generated asset result\n */\n async generate(opts?: AssetOptions): Promise<T> {\n return this.getResult({ ...opts, force: true })\n }\n\n /**\n * Clear the in-memory cache.\n *\n * Useful when you need to force re-computation of cached helper values\n * without regenerating the entire asset.\n */\n clearCache(): void {\n this.cache.clear()\n this._result = undefined\n }\n\n /**\n * Cache helper for expensive computations.\n *\n * Stores the result of `fn` under `key` and returns it on subsequent calls.\n *\n * @param key - Unique cache key\n * @param fn - Function to compute the value if not cached\n * @returns The cached or newly computed value\n *\n * @example\n * ```typescript\n * const metadata = await this.cached('metadata', () => this.extractMetadata())\n * ```\n */\n protected async cached<V>(key: string, fn: () => Promise<V>): Promise<V> {\n if (this.cache.has(key)) {\n return this.cache.get(key) as V\n }\n const value = await fn()\n this.cache.set(key, value)\n return value\n }\n}\n","/**\n * VideoAsset Base Class\n *\n * Abstract base class for video assets (main video, shorts, medium clips).\n * Provides common functionality for transcripts, captions, layout detection.\n *\n * Implements the \"check cache → check disk → generate → save\" pattern for\n * all expensive operations like ffprobe metadata, layout detection, and\n * caption generation.\n */\nimport { Asset, AssetOptions } from './Asset.js'\nimport { join } from '../core/paths.js'\nimport { fileExists, readJsonFile, readTextFile, writeJsonFile, ensureDirectory, writeTextFile } from '../core/fileSystem.js'\nimport { ffprobe } from '../core/ffmpeg.js'\nimport { generateSRT, generateVTT, generateStyledASS } from '../tools/captions/captionGenerator.js'\nimport { loadFaceDetection, loadGeminiClient } from './loaders.js'\nimport type { Transcript, Chapter, VideoLayout, WebcamRegion, ScreenRegion } from '../types/index.js'\n\n/**\n * Video file metadata extracted via ffprobe.\n */\nexport interface VideoMetadata {\n /** Duration in seconds */\n duration: number\n /** File size in bytes */\n size: number\n /** Video width in pixels */\n width: number\n /** Video height in pixels */\n height: number\n}\n\n/**\n * Paths to generated caption files.\n */\nexport interface CaptionFiles {\n /** Path to SRT subtitle file */\n srt: string\n /** Path to WebVTT subtitle file */\n vtt: string\n /** Path to ASS subtitle file (with styling) */\n ass: string\n}\n\n/**\n * Base class for video assets (main video, shorts, medium clips).\n * Provides common functionality for transcripts, captions, layout detection.\n *\n * Subclasses must implement the abstract properties that define where\n * the video and its assets are stored.\n */\nexport abstract class VideoAsset extends Asset<string> {\n /** Directory containing this video's assets */\n abstract readonly videoDir: string\n\n /** Path to the video file */\n abstract readonly videoPath: string\n\n /** URL-safe identifier */\n abstract readonly slug: string\n\n // ── Computed paths ─────────────────────────────────────────────────────────\n\n /** Path to transcript JSON file */\n get transcriptPath(): string {\n return join(this.videoDir, 'transcript.json')\n }\n\n /** Path to layout JSON file */\n get layoutPath(): string {\n return join(this.videoDir, 'layout.json')\n }\n\n /** Path to editorial direction markdown file */\n get editorialDirectionPath(): string {\n return join(this.videoDir, 'editorial-direction.md')\n }\n\n /** Path to clip direction markdown file */\n get clipDirectionPath(): string {\n return join(this.videoDir, 'clip-direction.md')\n }\n\n /** Directory containing caption files */\n get captionsDir(): string {\n return join(this.videoDir, 'captions')\n }\n\n /** Directory containing chapter files */\n get chaptersDir(): string {\n return join(this.videoDir, 'chapters')\n }\n\n /** Path to chapters JSON file */\n get chaptersJsonPath(): string {\n return join(this.chaptersDir, 'chapters.json')\n }\n\n // ── Metadata ───────────────────────────────────────────────────────────────\n\n /**\n * Get video metadata (duration, size, resolution).\n * Lazy-loads from ffprobe, caches in memory.\n *\n * @param opts - Options controlling generation behavior\n * @returns Video metadata\n */\n async getMetadata(opts?: AssetOptions): Promise<VideoMetadata> {\n if (opts?.force) {\n this.cache.delete('metadata')\n }\n return this.cached('metadata', async () => {\n const probeData = await ffprobe(this.videoPath)\n const videoStream = probeData.streams.find((s) => s.codec_type === 'video')\n\n return {\n duration: probeData.format.duration ?? 0,\n size: probeData.format.size ?? 0,\n width: videoStream?.width ?? 0,\n height: videoStream?.height ?? 0,\n }\n })\n }\n\n // ── Layout Detection ───────────────────────────────────────────────────────\n\n /**\n * Get video layout (webcam region, screen region).\n * Lazy-loads from layout.json or detects via face detection.\n *\n * @param opts - Options controlling generation behavior\n * @returns Video layout with webcam and screen regions\n */\n async getLayout(opts?: AssetOptions): Promise<VideoLayout> {\n if (opts?.force) {\n this.cache.delete('layout')\n }\n return this.cached('layout', async () => {\n // Check disk first\n if (!opts?.force && (await fileExists(this.layoutPath))) {\n return readJsonFile<VideoLayout>(this.layoutPath)\n }\n\n // Detect layout (lazy import to avoid config issues at module load)\n const { getVideoResolution, detectWebcamRegion } = await loadFaceDetection()\n const { width, height } = await getVideoResolution(this.videoPath)\n const webcam = await detectWebcamRegion(this.videoPath)\n\n // Compute screen region as inverse of webcam\n let screen: ScreenRegion | null = null\n if (webcam) {\n screen = this.computeScreenRegion(width, height, webcam)\n }\n\n const layout: VideoLayout = { width, height, webcam, screen }\n\n // Save to disk\n await writeJsonFile(this.layoutPath, layout)\n\n return layout\n })\n }\n\n /**\n * Shortcut to get webcam region.\n *\n * @returns Webcam region if detected, null otherwise\n */\n async getWebcamRegion(): Promise<WebcamRegion | null> {\n const layout = await this.getLayout()\n return layout.webcam\n }\n\n /**\n * Shortcut to get screen region.\n *\n * @returns Screen region (area not occupied by webcam), null if no webcam\n */\n async getScreenRegion(): Promise<ScreenRegion | null> {\n const layout = await this.getLayout()\n return layout.screen\n }\n\n /**\n * Compute the screen region as the area not occupied by the webcam.\n * For corner webcams, this is the full frame minus the webcam overlay.\n */\n private computeScreenRegion(\n width: number,\n height: number,\n webcam: WebcamRegion,\n ): ScreenRegion {\n // For simplicity, treat the entire frame as the screen region\n // (the webcam is an overlay, not a separate region)\n // More sophisticated layouts could crop the webcam out\n return { x: 0, y: 0, width, height }\n }\n\n // ── Editorial Direction ────────────────────────────────────────────────────\n\n /**\n * Get AI-generated editorial direction from Gemini video analysis.\n * Lazy-loads from editorial-direction.json or calls Gemini API.\n *\n * Returns null if GEMINI_API_KEY is not configured (optional feature).\n *\n * @param opts - Options controlling generation behavior\n * @returns Editorial direction as markdown text\n */\n async getEditorialDirection(opts?: AssetOptions): Promise<string | null> {\n if (opts?.force) {\n this.cache.delete('editorialDirection')\n }\n return this.cached('editorialDirection', async () => {\n // Check disk first\n if (!opts?.force && (await fileExists(this.editorialDirectionPath))) {\n return readTextFile(this.editorialDirectionPath)\n }\n\n // Check if Gemini is configured\n const { getConfig } = await import('../config/environment.js')\n const config = getConfig()\n if (!config.GEMINI_API_KEY) {\n return null\n }\n\n // Analyze video via Gemini\n const { analyzeVideoEditorial } = await loadGeminiClient()\n const metadata = await this.getMetadata()\n const direction = await analyzeVideoEditorial(\n this.videoPath,\n metadata.duration,\n )\n\n // Save to disk as markdown\n await writeTextFile(this.editorialDirectionPath, direction)\n\n return direction\n })\n }\n\n // ── Clip Direction ──────────────────────────────────────────────────────────\n\n /**\n * Get AI-generated clip direction from Gemini video analysis (pass 2).\n * Runs on the cleaned video to provide detailed direction for shorts\n * and medium clip extraction.\n *\n * Returns null if GEMINI_API_KEY is not configured (optional feature).\n *\n * @param opts - Options controlling generation behavior\n * @returns Clip direction as markdown text\n */\n async getClipDirection(opts?: AssetOptions): Promise<string | null> {\n if (opts?.force) {\n this.cache.delete('clipDirection')\n }\n return this.cached('clipDirection', async () => {\n // Check disk first\n if (!opts?.force && (await fileExists(this.clipDirectionPath))) {\n return readTextFile(this.clipDirectionPath)\n }\n\n // Check if Gemini is configured\n const { getConfig } = await import('../config/environment.js')\n const config = getConfig()\n if (!config.GEMINI_API_KEY) {\n return null\n }\n\n // Analyze video via Gemini\n const { analyzeVideoClipDirection } = await loadGeminiClient()\n const metadata = await this.getMetadata()\n const direction = await analyzeVideoClipDirection(\n this.videoPath,\n metadata.duration,\n )\n\n // Save to disk as markdown\n await writeTextFile(this.clipDirectionPath, direction)\n\n return direction\n })\n }\n\n // ── Transcript ─────────────────────────────────────────────────────────────\n\n /**\n * Get transcript. Lazy-loads from disk.\n * Subclasses may override to return adjusted transcript (e.g., after silence removal).\n *\n * Note: Actual transcription via Whisper is handled by the pipeline's\n * transcription stage. This method loads the saved result.\n *\n * @param opts - Options controlling generation behavior\n * @returns Transcript with segments and words\n * @throws Error if transcript doesn't exist and force is not set\n */\n async getTranscript(opts?: AssetOptions): Promise<Transcript> {\n if (opts?.force) {\n this.cache.delete('transcript')\n }\n return this.cached('transcript', async () => {\n if (await fileExists(this.transcriptPath)) {\n return readJsonFile<Transcript>(this.transcriptPath)\n }\n\n // TODO: Consider integrating transcribeVideo() here for full lazy-load support\n // For now, expect the transcript to be pre-generated by the pipeline\n throw new Error(\n `Transcript not found at ${this.transcriptPath}. ` +\n `Run the transcription stage first.`,\n )\n })\n }\n\n // ── Chapters ───────────────────────────────────────────────────────────────\n\n /**\n * Get chapters. Lazy-loads from disk.\n * Subclasses may override to generate chapters if not found (e.g., via ChapterAgent).\n *\n * @param opts - Options controlling generation behavior\n * @returns Array of chapters, empty if none found on disk\n */\n async getChapters(opts?: AssetOptions): Promise<Chapter[]> {\n if (opts?.force) {\n this.cache.delete('chapters')\n }\n return this.cached('chapters', async () => {\n if (!opts?.force && (await fileExists(this.chaptersJsonPath))) {\n const data = await readJsonFile<{ chapters: Chapter[] }>(this.chaptersJsonPath)\n return data.chapters ?? []\n }\n return []\n })\n }\n\n // ── Captions ───────────────────────────────────────────────────────────────\n\n /**\n * Get caption files (SRT, VTT, ASS).\n * Lazy-generates from transcript if needed.\n *\n * @param opts - Options controlling generation behavior\n * @returns Paths to caption files\n */\n async getCaptions(opts?: AssetOptions): Promise<CaptionFiles> {\n if (opts?.force) {\n this.cache.delete('captions')\n }\n return this.cached('captions', async () => {\n const srtPath = join(this.captionsDir, 'captions.srt')\n const vttPath = join(this.captionsDir, 'captions.vtt')\n const assPath = join(this.captionsDir, 'captions.ass')\n\n // Check if all caption files exist\n const [srtExists, vttExists, assExists] = await Promise.all([\n fileExists(srtPath),\n fileExists(vttPath),\n fileExists(assPath),\n ])\n\n if (!opts?.force && srtExists && vttExists && assExists) {\n return { srt: srtPath, vtt: vttPath, ass: assPath }\n }\n\n // Generate captions from transcript\n const transcript = await this.getTranscript()\n\n await ensureDirectory(this.captionsDir)\n\n const srt = generateSRT(transcript)\n const vtt = generateVTT(transcript)\n const ass = generateStyledASS(transcript)\n\n await Promise.all([\n writeTextFile(srtPath, srt),\n writeTextFile(vttPath, vtt),\n writeTextFile(assPath, ass),\n ])\n\n return { srt: srtPath, vtt: vttPath, ass: assPath }\n })\n }\n\n // ── Asset Implementation ───────────────────────────────────────────────────\n\n /**\n * Check if the video file exists.\n */\n async exists(): Promise<boolean> {\n return fileExists(this.videoPath)\n }\n\n /**\n * Get the video file path (the primary \"result\" of this asset).\n */\n async getResult(opts?: AssetOptions): Promise<string> {\n if (!(await this.exists())) {\n throw new Error(`Video not found at ${this.videoPath}`)\n }\n return this.videoPath\n }\n}\n","/**\n * Lazy loaders for modules that have side effects at import time.\n * Use these instead of direct imports to avoid circular dependency issues.\n *\n * Many modules in the pipeline read config, resolve FFmpeg paths, or perform\n * other operations at module load time. This can cause issues when modules\n * are imported before the environment is fully initialized.\n *\n * These lazy loaders defer the actual import until the module is needed,\n * ensuring all dependencies are ready.\n */\n\n// Face detection (imports ffmpeg which reads config at load time)\nexport const loadFaceDetection = async () =>\n import('../tools/ffmpeg/faceDetection.js')\n\n// Transcription service\nexport const loadTranscription = async () =>\n import('../services/transcription.js')\n\n// Caption generation\nexport const loadCaptionGeneration = async () =>\n import('../services/captionGeneration.js')\n\n// Silence removal agent\nexport const loadSilenceRemovalAgent = async () =>\n import('../agents/SilenceRemovalAgent.js')\n\n// Caption burning\nexport const loadCaptionBurning = async () =>\n import('../tools/ffmpeg/captionBurning.js')\n\n// Shorts agent\nexport const loadShortsAgent = async () =>\n import('../agents/ShortsAgent.js')\n\n// Medium video agent\nexport const loadMediumVideoAgent = async () =>\n import('../agents/MediumVideoAgent.js')\n\n// Chapter agent\nexport const loadChapterAgent = async () =>\n import('../agents/ChapterAgent.js')\n\n// Summary agent\nexport const loadSummaryAgent = async () =>\n import('../agents/SummaryAgent.js')\n\n// Blog agent\nexport const loadBlogAgent = async () =>\n import('../agents/BlogAgent.js')\n\n// Social media agent\nexport const loadSocialMediaAgent = async () =>\n import('../agents/SocialMediaAgent.js')\n\n// Producer agent\nexport const loadProducerAgent = async () =>\n import('../agents/ProducerAgent.js')\n\n// Gemini video analysis\nexport const loadGeminiClient = async () =>\n import('../tools/gemini/geminiClient.js')\n","/**\n * SocialPostAsset Class\n *\n * Represents a social media post for a specific platform.\n * Each post is stored as a markdown file in the posts directory.\n */\nimport { join } from '../core/paths.js'\nimport { TextAsset } from './TextAsset.js'\nimport type { AssetOptions } from './Asset.js'\nimport type { VideoAsset } from './VideoAsset.js'\nimport type { Platform } from '../types/index.js'\n\n/**\n * A social media post asset for a specific platform.\n *\n * Posts are generated by AI agents and stored as markdown files.\n * This class provides the Asset interface for loading them.\n */\nexport class SocialPostAsset extends TextAsset {\n /** The video this post is for (main video, short, or medium clip) */\n readonly parent: VideoAsset\n\n /** Target platform (TikTok, YouTube, Instagram, LinkedIn, X) */\n readonly platform: Platform\n\n /** Path to the post markdown file */\n readonly filePath: string\n\n /**\n * Create a new social post asset.\n *\n * @param parent - The video this post is for\n * @param platform - Target social media platform\n * @param postsDir - Directory containing platform post files\n */\n constructor(parent: VideoAsset, platform: Platform, postsDir: string) {\n super()\n this.parent = parent\n this.platform = platform\n this.filePath = join(postsDir, `${platform.toLowerCase()}.md`)\n }\n\n /**\n * Get the post content for this platform.\n *\n * Loads from disk if exists, throws if not found.\n *\n * @param opts - Options controlling generation behavior\n * @returns The post content as markdown\n */\n async getResult(opts?: AssetOptions): Promise<string> {\n // Check memory cache first\n if (!opts?.force && this._result !== undefined) {\n return this._result\n }\n\n // Load from disk\n const content = await this.loadFromDisk()\n if (content === null) {\n throw new Error(\n `Social post not found for ${this.platform} at ${this.filePath}. ` +\n `Run the social media stage first.`,\n )\n }\n\n this._result = content\n return content\n }\n}\n","/**\n * TextAsset Base Class\n *\n * Abstract base class for text-based assets (blog posts, summaries, social posts).\n * Subclasses define where the content is stored and how it's generated.\n *\n * Provides common functionality for reading from and writing to disk,\n * with the Asset pattern of lazy loading and caching.\n */\nimport { Asset, AssetOptions } from './Asset.js'\nimport { fileExists, readTextFile, writeTextFile } from '../core/fileSystem.js'\n\n/**\n * Base class for text-based assets.\n *\n * Subclasses must implement:\n * - `filePath`: Where the text file lives on disk\n * - `getResult()`: How to generate/load the content\n */\nexport abstract class TextAsset extends Asset<string> {\n /** Path to the text file on disk */\n abstract readonly filePath: string\n\n /**\n * Get the text content (from disk or memory cache).\n *\n * @param opts - Options controlling generation behavior\n * @returns The text content\n */\n async getContent(opts?: AssetOptions): Promise<string> {\n return this.getResult(opts)\n }\n\n /**\n * Check if the text file exists on disk.\n *\n * @returns true if the file exists\n */\n async exists(): Promise<boolean> {\n return fileExists(this.filePath)\n }\n\n /**\n * Load content from disk.\n *\n * @returns File content if exists, null otherwise\n */\n protected async loadFromDisk(): Promise<string | null> {\n if (!(await this.exists())) {\n return null\n }\n return readTextFile(this.filePath)\n }\n\n /**\n * Save content to disk.\n *\n * Creates parent directories if they don't exist.\n *\n * @param content - The text content to write\n */\n protected async saveToDisk(content: string): Promise<void> {\n await writeTextFile(this.filePath, content)\n }\n}\n","/**\n * ShortVideoAsset Class\n *\n * Represents a short clip (15-60s) extracted from a main video.\n * Handles platform variants, social posts, and transcript filtering.\n */\nimport { VideoAsset } from './VideoAsset.js'\nimport { SocialPostAsset } from './SocialPostAsset.js'\nimport { join } from '../core/paths.js'\nimport { fileExists, listDirectory, readTextFile, ensureDirectory } from '../core/fileSystem.js'\nimport type { AssetOptions } from './Asset.js'\nimport type { ShortClip, Platform, Transcript, Segment, Word } from '../types/index.js'\nimport { Platform as PlatformEnum } from '../types/index.js'\nimport { extractCompositeClip } from '../tools/ffmpeg/clipExtraction.js'\n\n/**\n * A short video clip extracted from a parent video.\n *\n * Short clips are 15-60 second segments designed for social media platforms\n * like TikTok, YouTube Shorts, and Instagram Reels. Each short can have\n * multiple platform-specific variants with different aspect ratios.\n */\nexport class ShortVideoAsset extends VideoAsset {\n /** Reference to the source video this short was extracted from */\n readonly parent: VideoAsset\n\n /** Clip metadata including segments, title, and description */\n readonly clip: ShortClip\n\n /** Directory containing this short's assets (shorts/{clip-slug}/) */\n readonly videoDir: string\n\n /** URL-safe identifier for this short */\n readonly slug: string\n\n /**\n * Create a new ShortVideoAsset.\n *\n * @param parent - The source VideoAsset this short was extracted from\n * @param clip - Clip metadata (slug, title, segments, etc.)\n * @param shortsBaseDir - Base directory for all shorts (e.g., recordings/{slug}/shorts/)\n */\n constructor(parent: VideoAsset, clip: ShortClip, shortsBaseDir: string) {\n super()\n this.parent = parent\n this.clip = clip\n this.slug = clip.slug\n this.videoDir = join(shortsBaseDir, clip.slug)\n }\n\n // ── Paths ────────────────────────────────────────────────────────────────────\n\n /** Path to the main short video file */\n get videoPath(): string {\n return join(this.videoDir, 'media.mp4')\n }\n\n /** Directory containing social posts for this short */\n get postsDir(): string {\n return join(this.videoDir, 'posts')\n }\n\n // ── Platform Variants ────────────────────────────────────────────────────────\n\n /**\n * Get paths to platform-specific video variants.\n *\n * Each platform may have a different aspect ratio (9:16 for TikTok/Reels,\n * 1:1 for Instagram Feed, etc.). Variants are stored as media-{platform}.mp4.\n *\n * @returns Map of platform to variant file path (only existing files)\n */\n async getPlatformVariants(): Promise<Map<Platform, string>> {\n const variants = new Map<Platform, string>()\n const platforms = Object.values(PlatformEnum)\n\n await Promise.all(\n platforms.map(async (platform) => {\n const variantPath = join(this.videoDir, `media-${platform}.mp4`)\n if (await fileExists(variantPath)) {\n variants.set(platform, variantPath)\n }\n }),\n )\n\n return variants\n }\n\n // ── Social Posts ─────────────────────────────────────────────────────────────\n\n /**\n * Get social media posts for this short as SocialPostAsset objects.\n * Returns one asset per platform.\n *\n * @returns Array of SocialPostAsset objects (one per platform)\n */\n async getSocialPosts(): Promise<SocialPostAsset[]> {\n const platforms: Platform[] = [\n PlatformEnum.TikTok,\n PlatformEnum.YouTube,\n PlatformEnum.Instagram,\n PlatformEnum.LinkedIn,\n PlatformEnum.X,\n ]\n return platforms.map((platform) => new SocialPostAsset(this, platform, this.postsDir))\n }\n\n // ── Asset Implementation ─────────────────────────────────────────────────────\n\n /**\n * Check if the rendered short video exists.\n */\n async exists(): Promise<boolean> {\n return fileExists(this.videoPath)\n }\n\n /**\n * Get the rendered short video path, extracting from parent if needed.\n *\n * @param opts - Asset options (force regeneration, etc.)\n * @returns Path to the rendered short video\n */\n async getResult(opts?: AssetOptions): Promise<string> {\n if (!opts?.force && await this.exists()) {\n return this.videoPath\n }\n\n // Ensure output directory exists\n await ensureDirectory(this.videoDir)\n\n // Get parent video path\n const parentVideo = await this.parent.getResult()\n\n // Extract clip using FFmpeg (handles single and composite segments)\n await extractCompositeClip(parentVideo, this.clip.segments, this.videoPath)\n\n return this.videoPath\n }\n\n // ── Transcript ───────────────────────────────────────────────────────────────\n\n /**\n * Get transcript filtered to this short's time range.\n *\n * Uses the parent's ORIGINAL transcript (not adjusted) since short clips\n * reference timestamps in the original video.\n *\n * @param opts - Asset options\n * @returns Transcript containing only segments/words within this clip's time range\n */\n async getTranscript(opts?: AssetOptions): Promise<Transcript> {\n const parentTranscript = await this.parent.getTranscript(opts)\n\n // Get the overall time range for this short (may be composite)\n const startTime = Math.min(...this.clip.segments.map((s) => s.start))\n const endTime = Math.max(...this.clip.segments.map((s) => s.end))\n\n // Filter segments that overlap with any of our segments\n const filteredSegments: Segment[] = parentTranscript.segments.filter((seg) =>\n this.clip.segments.some(\n (clipSeg) => seg.start < clipSeg.end && seg.end > clipSeg.start,\n ),\n )\n\n // Filter words that fall within any of our segments\n const filteredWords: Word[] = parentTranscript.words.filter((word) =>\n this.clip.segments.some(\n (clipSeg) => word.start >= clipSeg.start && word.end <= clipSeg.end,\n ),\n )\n\n // Build filtered text from filtered segments\n const filteredText = filteredSegments.map((s) => s.text).join(' ')\n\n return {\n text: filteredText,\n segments: filteredSegments,\n words: filteredWords,\n language: parentTranscript.language,\n duration: this.clip.totalDuration,\n }\n }\n}\n","/**\n * MediumClipAsset - Represents a medium-length clip (60-180s)\n *\n * Medium clips are longer-form content extracted from the full video,\n * typically covering a complete topic or tutorial segment. Unlike shorts,\n * medium clips don't need platform variants (portrait/square) - they're\n * rendered in the original aspect ratio.\n */\nimport { VideoAsset } from './VideoAsset.js'\nimport { SocialPostAsset } from './SocialPostAsset.js'\nimport { join } from '../core/paths.js'\nimport { fileExists, listDirectory } from '../core/fileSystem.js'\nimport type { MediumClip, Platform } from '../types/index.js'\nimport { Platform as PlatformEnum } from '../types/index.js'\nimport type { AssetOptions } from './Asset.js'\n\n/**\n * Asset representing a medium-length clip extracted from a longer video.\n *\n * Medium clips are 60-180 second segments that cover complete topics.\n * They're stored in a dedicated directory with their own captions and\n * social media posts.\n */\nexport class MediumClipAsset extends VideoAsset {\n /** Parent video this clip was extracted from */\n readonly parent: VideoAsset\n\n /** Clip metadata (start/end times, title, segments) */\n readonly clip: MediumClip\n\n /** Directory containing this clip's assets */\n readonly videoDir: string\n\n /** URL-safe identifier for this clip */\n readonly slug: string\n\n /**\n * Create a medium clip asset.\n *\n * @param parent - The source video this clip was extracted from\n * @param clip - Clip metadata including time ranges and title\n * @param clipsBaseDir - Base directory for all medium clips (e.g., recordings/{slug}/medium-clips)\n */\n constructor(parent: VideoAsset, clip: MediumClip, clipsBaseDir: string) {\n super()\n this.parent = parent\n this.clip = clip\n this.slug = clip.slug\n this.videoDir = join(clipsBaseDir, clip.slug)\n }\n\n // ── Paths ──────────────────────────────────────────────────────────────────\n\n /**\n * Path to the rendered clip video file.\n */\n get videoPath(): string {\n return join(this.videoDir, 'media.mp4')\n }\n\n /**\n * Directory containing social media posts for this clip.\n */\n get postsDir(): string {\n return join(this.videoDir, 'posts')\n }\n\n // ── Social Posts ───────────────────────────────────────────────────────────\n\n /**\n * Get social media posts for this medium clip as SocialPostAsset objects.\n * Returns one asset per platform.\n *\n * @returns Array of SocialPostAsset objects (one per platform)\n */\n async getSocialPosts(): Promise<SocialPostAsset[]> {\n const platforms: Platform[] = [\n PlatformEnum.TikTok,\n PlatformEnum.YouTube,\n PlatformEnum.Instagram,\n PlatformEnum.LinkedIn,\n PlatformEnum.X,\n ]\n return platforms.map((platform) => new SocialPostAsset(this, platform, this.postsDir))\n }\n\n // ── Asset Implementation ───────────────────────────────────────────────────\n\n /**\n * Check if the rendered clip exists on disk.\n */\n async exists(): Promise<boolean> {\n return fileExists(this.videoPath)\n }\n\n /**\n * Get the rendered clip video path.\n *\n * @param opts - Asset options (force not used - clip must be pre-rendered)\n * @returns Path to the rendered video file\n * @throws Error if clip hasn't been rendered yet\n */\n async getResult(opts?: AssetOptions): Promise<string> {\n if (!(await this.exists())) {\n throw new Error(\n `Medium clip \"${this.slug}\" not found at ${this.videoPath}. ` +\n `Run the medium-clips stage first.`,\n )\n }\n return this.videoPath\n }\n}\n","/**\n * SummaryAsset Class\n *\n * Represents the README.md summary for a video. Wraps the summary file\n * with lazy loading and optional generation via SummaryAgent.\n */\nimport { TextAsset } from './TextAsset.js'\nimport { join } from '../core/paths.js'\nimport { loadSummaryAgent } from './loaders.js'\nimport type { AssetOptions } from './Asset.js'\nimport type { MainVideoAsset } from './MainVideoAsset.js'\n\n/**\n * Summary asset representing a video's README.md.\n *\n * Provides lazy loading from disk. Generation via SummaryAgent\n * requires transcript, shorts, and chapters data - handled by the pipeline.\n */\nexport class SummaryAsset extends TextAsset {\n /** Parent video this summary belongs to */\n readonly parent: MainVideoAsset\n\n /** Path to README.md file */\n readonly filePath: string\n\n /**\n * Create a summary asset for a video.\n *\n * @param parent - The video asset this summary belongs to\n */\n constructor(parent: MainVideoAsset) {\n super()\n this.parent = parent\n this.filePath = join(parent.videoDir, 'README.md')\n }\n\n /**\n * Get the summary content.\n *\n * Loads from disk if available. Otherwise generates via SummaryAgent\n * using transcript, shorts, and chapters data from parent.\n *\n * @param opts - Options controlling retrieval behavior\n * @returns The summary markdown content\n */\n async getResult(opts?: AssetOptions): Promise<string> {\n if (opts?.force) {\n this.clearCache()\n }\n\n return this.cached('content', async () => {\n // Check disk first\n const content = await this.loadFromDisk()\n if (!opts?.force && content !== null) {\n return content\n }\n\n // Generate via SummaryAgent\n const { generateSummary } = await loadSummaryAgent()\n const transcript = await this.parent.getTranscript()\n const shortAssets = await this.parent.getShorts()\n const shorts = shortAssets.map((s) => s.clip) // Get raw clip data\n const chapters = await this.parent.getChapters()\n const videoFile = await this.parent.toVideoFile()\n\n // Agent writes README.md to disk, just need to collect metadata and reload\n await generateSummary(videoFile, transcript, shorts, chapters)\n\n // The README was written by the agent, reload from disk\n const generated = await this.loadFromDisk()\n if (generated !== null) {\n return generated\n }\n\n throw new Error('SummaryAgent failed to generate README')\n })\n }\n}\n","/**\n * BlogAsset Class\n *\n * Represents a blog post generated from a video.\n * Extends TextAsset to provide lazy loading and caching of blog content.\n *\n * The blog post is stored as a Markdown file with YAML frontmatter\n * containing metadata like title, description, tags, etc.\n */\nimport { TextAsset } from './TextAsset.js'\nimport type { AssetOptions } from './Asset.js'\nimport { join } from '../core/paths.js'\nimport type { MainVideoAsset } from './MainVideoAsset.js'\n\n/**\n * YAML frontmatter for the blog post.\n */\nexport interface BlogFrontmatter {\n /** Blog post title */\n title: string\n /** SEO description / subtitle */\n description: string\n /** Tags for categorization */\n tags: string[]\n /** Whether the post is published */\n published: boolean\n /** Publication date (ISO format) */\n date: string\n}\n\n/**\n * Asset representing a blog post generated from a video.\n *\n * The blog post is stored as `blog-post.md` in the video's directory.\n * It includes YAML frontmatter with metadata and Markdown body content.\n *\n * Note: Generation requires VideoSummary object (with title, overview, keyTopics)\n * which is produced by the pipeline's summary stage. Use pipeline to generate.\n */\nexport class BlogAsset extends TextAsset {\n /** Reference to the video this blog is about */\n readonly parent: MainVideoAsset\n\n /** Path to the blog post file */\n readonly filePath: string\n\n constructor(parent: MainVideoAsset) {\n super()\n this.parent = parent\n this.filePath = join(parent.videoDir, 'blog-post.md')\n }\n\n /**\n * Parse frontmatter from the blog post.\n *\n * Extracts YAML frontmatter from between `---` delimiters.\n *\n * @returns Parsed frontmatter or null if file doesn't exist or has no frontmatter\n */\n async getFrontmatter(): Promise<BlogFrontmatter | null> {\n const content = await this.loadFromDisk()\n if (!content) {\n return null\n }\n\n return this.parseFrontmatter(content)\n }\n\n /**\n * Parse YAML frontmatter from markdown content.\n *\n * @param content - Markdown content with optional frontmatter\n * @returns Parsed frontmatter or null if not present\n */\n private parseFrontmatter(content: string): BlogFrontmatter | null {\n // Check for frontmatter delimiters\n if (!content.startsWith('---')) {\n return null\n }\n\n // Find closing delimiter\n const endIndex = content.indexOf('---', 3)\n if (endIndex === -1) {\n return null\n }\n\n const yamlContent = content.slice(3, endIndex).trim()\n if (!yamlContent) {\n return null\n }\n\n // Parse YAML manually (simple key: value format)\n const frontmatter: Partial<BlogFrontmatter> = {\n tags: [],\n published: false,\n }\n\n const lines = yamlContent.split('\\n')\n for (const line of lines) {\n const colonIndex = line.indexOf(':')\n if (colonIndex === -1) continue\n\n const key = line.slice(0, colonIndex).trim()\n const value = line.slice(colonIndex + 1).trim()\n\n switch (key) {\n case 'title':\n frontmatter.title = this.unquote(value)\n break\n case 'description':\n frontmatter.description = this.unquote(value)\n break\n case 'published':\n frontmatter.published = value === 'true'\n break\n case 'date':\n frontmatter.date = this.unquote(value)\n break\n case 'tags':\n // Handle both \"tag1, tag2\" and \"tag1,tag2\" formats\n frontmatter.tags = value\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean)\n break\n }\n }\n\n // Validate required fields\n if (!frontmatter.title || !frontmatter.description) {\n return null\n }\n\n return {\n title: frontmatter.title,\n description: frontmatter.description,\n tags: frontmatter.tags ?? [],\n published: frontmatter.published ?? false,\n date: frontmatter.date ?? new Date().toISOString().split('T')[0],\n }\n }\n\n /**\n * Remove surrounding quotes from a string.\n */\n private unquote(value: string): string {\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1)\n }\n return value\n }\n\n /**\n * Get the blog content.\n *\n * Loads from disk if exists. Generation requires VideoSummary object\n * which is produced by the pipeline's summary stage - use pipeline to generate.\n *\n * @param opts - Options controlling generation behavior\n * @returns The blog post content\n * @throws Error if blog post doesn't exist (generation happens via pipeline)\n */\n async getResult(opts?: AssetOptions): Promise<string> {\n // Return cached result if available and not forcing regeneration\n if (!opts?.force && this._result !== undefined) {\n return this._result\n }\n\n // Try to load from disk\n const content = await this.loadFromDisk()\n if (content !== null) {\n this._result = content\n return content\n }\n\n // Blog generation requires VideoSummary object (title, overview, keyTopics)\n // which is produced by the pipeline's summary stage\n throw new Error(\n `Blog post not found at ${this.filePath}. ` +\n `Run the pipeline to generate (requires VideoSummary from summary stage).`,\n )\n }\n}\n","/**\n * MainVideoAsset Class\n *\n * The primary video asset that the pipeline processes. Represents a source video\n * being processed through all pipeline stages.\n *\n * Provides lazy-loading access to:\n * - Video variants (original, edited, captioned, produced)\n * - Child assets (shorts, medium clips, chapters)\n * - Text assets (summary, blog)\n */\nimport { VideoAsset, VideoMetadata, CaptionFiles } from './VideoAsset.js'\nimport { AssetOptions } from './Asset.js'\nimport { ShortVideoAsset } from './ShortVideoAsset.js'\nimport { MediumClipAsset } from './MediumClipAsset.js'\nimport { SocialPostAsset } from './SocialPostAsset.js'\nimport { SummaryAsset } from './SummaryAsset.js'\nimport { BlogAsset } from './BlogAsset.js'\nimport { join, basename, extname, dirname } from '../core/paths.js'\nimport {\n fileExists,\n ensureDirectory,\n copyFile,\n getFileStats,\n listDirectory,\n removeDirectory,\n removeFile,\n openReadStream,\n openWriteStream,\n writeJsonFile,\n readJsonFile,\n readTextFile,\n} from '../core/fileSystem.js'\nimport { slugify } from '../core/text.js'\nimport { ffprobe } from '../core/ffmpeg.js'\nimport {\n loadTranscription,\n loadSilenceRemovalAgent,\n loadCaptionBurning,\n loadShortsAgent,\n loadMediumVideoAgent,\n loadChapterAgent,\n loadProducerAgent,\n} from './loaders.js'\nimport { getConfig } from '../config/environment.js'\nimport logger from '../config/logger.js'\nimport {\n Platform,\n} from '../types/index.js'\nimport type {\n ShortClip,\n MediumClip,\n Chapter,\n Transcript,\n VideoFile,\n VideoLayout,\n AspectRatio,\n} from '../types/index.js'\n\n/**\n * Main video asset - the entry point for pipeline processing.\n * Represents a source video that has been or will be ingested into the recordings folder.\n */\nexport class MainVideoAsset extends VideoAsset {\n readonly sourcePath: string\n readonly videoDir: string\n readonly slug: string\n\n private constructor(sourcePath: string, videoDir: string, slug: string) {\n super()\n this.sourcePath = sourcePath\n this.videoDir = videoDir\n this.slug = slug\n }\n\n // ── Computed Paths ─────────────────────────────────────────────────────────\n\n /** Path to the main video file: videoDir/{slug}.mp4 */\n get videoPath(): string {\n return join(this.videoDir, `${this.slug}.mp4`)\n }\n\n /** Path to the edited (silence-removed) video: videoDir/{slug}-edited.mp4 */\n get editedVideoPath(): string {\n return join(this.videoDir, `${this.slug}-edited.mp4`)\n }\n\n /** Path to the captioned video: videoDir/{slug}-captioned.mp4 */\n get captionedVideoPath(): string {\n return join(this.videoDir, `${this.slug}-captioned.mp4`)\n }\n\n /** Path to the fully produced video: videoDir/{slug}-produced.mp4 */\n get producedVideoPath(): string {\n return join(this.videoDir, `${this.slug}-produced.mp4`)\n }\n\n /** Path to a produced video for a specific aspect ratio: videoDir/{slug}-produced-{ar}.mp4 */\n producedVideoPathFor(aspectRatio: AspectRatio): string {\n const arSuffix = aspectRatio.replace(':', 'x') // '9:16' → '9x16'\n return join(this.videoDir, `${this.slug}-produced-${arSuffix}.mp4`)\n }\n\n /** Path to shorts metadata JSON */\n get shortsJsonPath(): string {\n return join(this.videoDir, 'shorts', 'shorts.json')\n }\n\n /** Path to medium clips metadata JSON */\n get mediumClipsJsonPath(): string {\n return join(this.videoDir, 'medium-clips', 'medium-clips.json')\n }\n\n // chaptersJsonPath is inherited from VideoAsset\n\n /** Path to summary README */\n get summaryPath(): string {\n return join(this.videoDir, 'README.md')\n }\n\n /** Path to blog post */\n get blogPath(): string {\n return join(this.videoDir, 'blog-post.md')\n }\n\n /** Path to adjusted transcript (post silence-removal) */\n get adjustedTranscriptPath(): string {\n return join(this.videoDir, 'transcript-edited.json')\n }\n\n // ── Static Factory Methods ─────────────────────────────────────────────────\n\n /**\n * Ingest a source video into the recordings folder.\n * Copies the video, creates directory structure, and extracts metadata.\n *\n * @param sourcePath - Path to the source video file\n * @returns A new MainVideoAsset instance\n */\n static async ingest(sourcePath: string): Promise<MainVideoAsset> {\n const config = getConfig()\n const baseName = basename(sourcePath, extname(sourcePath))\n const slug = slugify(baseName, { lower: true })\n\n const videoDir = join(config.OUTPUT_DIR, slug)\n const thumbnailsDir = join(videoDir, 'thumbnails')\n const shortsDir = join(videoDir, 'shorts')\n const socialPostsDir = join(videoDir, 'social-posts')\n\n logger.info(`Ingesting video: ${sourcePath} → ${slug}`)\n\n // Clean stale artifacts if output folder already exists\n if (await fileExists(videoDir)) {\n logger.warn(`Output folder already exists, cleaning previous artifacts: ${videoDir}`)\n\n const subDirs = ['thumbnails', 'shorts', 'social-posts', 'chapters', 'medium-clips', 'captions']\n for (const sub of subDirs) {\n await removeDirectory(join(videoDir, sub), { recursive: true, force: true })\n }\n\n const stalePatterns = [\n 'transcript.json',\n 'transcript-edited.json',\n 'captions.srt',\n 'captions.vtt',\n 'captions.ass',\n 'summary.md',\n 'blog-post.md',\n 'README.md',\n ]\n for (const pattern of stalePatterns) {\n await removeFile(join(videoDir, pattern))\n }\n\n const files = await listDirectory(videoDir)\n for (const file of files) {\n if (file.endsWith('-edited.mp4') || file.endsWith('-captioned.mp4') || file.endsWith('-produced.mp4')) {\n await removeFile(join(videoDir, file))\n }\n }\n }\n\n // Create directory structure\n await ensureDirectory(videoDir)\n await ensureDirectory(thumbnailsDir)\n await ensureDirectory(shortsDir)\n await ensureDirectory(socialPostsDir)\n\n const destFilename = `${slug}.mp4`\n const destPath = join(videoDir, destFilename)\n\n // Copy video if needed\n let needsCopy = true\n try {\n const destStats = await getFileStats(destPath)\n const srcStats = await getFileStats(sourcePath)\n if (destStats.size === srcStats.size) {\n logger.info(`Video already copied (same size), skipping copy`)\n needsCopy = false\n }\n } catch {\n // Dest doesn't exist, need to copy\n }\n\n if (needsCopy) {\n await new Promise<void>((resolve, reject) => {\n const readStream = openReadStream(sourcePath)\n const writeStream = openWriteStream(destPath)\n readStream.on('error', reject)\n writeStream.on('error', reject)\n writeStream.on('finish', resolve)\n readStream.pipe(writeStream)\n })\n logger.info(`Copied video to ${destPath}`)\n }\n\n // Create the asset instance\n const asset = new MainVideoAsset(sourcePath, videoDir, slug)\n\n // Detect and save layout\n try {\n const layout = await asset.getLayout()\n logger.info(\n `Layout detected: webcam=${layout.webcam ? `${layout.webcam.position} (${layout.webcam.confidence})` : 'none'}`,\n )\n } catch (err) {\n logger.warn(`Layout detection failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n\n // Log metadata\n try {\n const metadata = await asset.getMetadata()\n const stats = await getFileStats(destPath)\n logger.info(`Video metadata: duration=${metadata.duration}s, size=${stats.size} bytes`)\n } catch (err) {\n logger.warn(`Metadata extraction failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n\n return asset\n }\n\n /**\n * Load an existing video from a recordings folder.\n *\n * @param videoDir - Path to the recordings/{slug}/ directory\n * @returns A MainVideoAsset instance\n * @throws Error if the directory or video file doesn't exist\n */\n static async load(videoDir: string): Promise<MainVideoAsset> {\n if (!(await fileExists(videoDir))) {\n throw new Error(`Video directory not found: ${videoDir}`)\n }\n\n // Derive slug from directory name\n const slug = basename(videoDir)\n const videoPath = join(videoDir, `${slug}.mp4`)\n\n if (!(await fileExists(videoPath))) {\n throw new Error(`Video file not found: ${videoPath}`)\n }\n\n // Use the video path as the source path for loaded assets\n return new MainVideoAsset(videoPath, videoDir, slug)\n }\n\n // ── Transcript Override ────────────────────────────────────────────────────\n\n /**\n * Get transcript. Loads from disk if available, otherwise generates via transcription service.\n *\n * @param opts - Options controlling generation behavior\n * @returns Transcript with segments and words\n */\n async getTranscript(opts?: AssetOptions): Promise<Transcript> {\n if (opts?.force) {\n this.cache.delete('transcript')\n }\n return this.cached('transcript', async () => {\n if (!opts?.force && await fileExists(this.transcriptPath)) {\n return readJsonFile<Transcript>(this.transcriptPath)\n }\n\n // Generate via transcription service\n const { transcribeVideo } = await loadTranscription()\n const videoFile = await this.toVideoFile()\n const transcript = await transcribeVideo(videoFile)\n logger.info(`Generated transcript: ${transcript.segments.length} segments`)\n return transcript\n })\n }\n\n // ── Video Variants (Lazy-Load) ─────────────────────────────────────────────\n\n /**\n * Get the original video path. Always exists after ingestion.\n */\n async getOriginalVideo(): Promise<string> {\n if (!(await fileExists(this.videoPath))) {\n throw new Error(`Original video not found: ${this.videoPath}`)\n }\n return this.videoPath\n }\n\n /**\n * Get the edited (silence-removed) video.\n * If not already generated, runs silence removal.\n *\n * @param opts - Options controlling generation\n * @returns Path to the edited video\n */\n async getEditedVideo(opts?: AssetOptions): Promise<string> {\n // Check if edited video already exists\n if (!opts?.force && (await fileExists(this.editedVideoPath))) {\n return this.editedVideoPath\n }\n\n // Generate via silence removal agent\n const { removeDeadSilence } = await loadSilenceRemovalAgent()\n const transcript = await this.getTranscript()\n const videoFile = await this.toVideoFile()\n const result = await removeDeadSilence(videoFile, transcript)\n\n if (result.wasEdited) {\n logger.info(`Silence removal completed: ${result.removals.length} segments removed`)\n return result.editedPath\n }\n\n logger.info('No silence removed, using original video')\n return this.videoPath\n }\n\n /**\n * Get the captioned video.\n * If not already generated, burns captions into the edited video.\n *\n * @param opts - Options controlling generation\n * @returns Path to the captioned video\n */\n async getCaptionedVideo(opts?: AssetOptions): Promise<string> {\n // Check if captioned video already exists\n if (!opts?.force && (await fileExists(this.captionedVideoPath))) {\n return this.captionedVideoPath\n }\n\n // Get edited video and captions\n const editedPath = await this.getEditedVideo(opts)\n const captions = await this.getCaptions()\n\n // Burn captions into video\n const { burnCaptions } = await loadCaptionBurning()\n await burnCaptions(editedPath, captions.ass, this.captionedVideoPath)\n logger.info(`Captions burned into video: ${this.captionedVideoPath}`)\n return this.captionedVideoPath\n }\n\n /**\n * Get the fully produced video.\n * If not already generated, runs the ProducerAgent.\n *\n * @param opts - Options controlling generation\n * @param aspectRatio - Target aspect ratio (default: '16:9')\n * @returns Path to the produced video\n */\n async getProducedVideo(opts?: AssetOptions, aspectRatio: AspectRatio = '16:9'): Promise<string> {\n const outputPath = this.producedVideoPathFor(aspectRatio)\n\n // Check if produced video already exists\n if (!opts?.force && (await fileExists(outputPath))) {\n return outputPath\n }\n\n // Get required inputs - ensure captioned video exists first\n await this.getCaptionedVideo()\n\n // Load and run producer agent (video asset passed to constructor)\n const { ProducerAgent } = await loadProducerAgent()\n const agent = new ProducerAgent(this, aspectRatio)\n\n const result = await agent.produce(outputPath)\n\n if (!result.success) {\n logger.warn(`Production failed: ${result.error}, falling back to captioned`)\n return this.captionedVideoPath\n }\n\n return outputPath\n }\n\n // ── Asset Implementation ───────────────────────────────────────────────────\n\n /**\n * Get the final result - the produced video path.\n */\n async getResult(opts?: AssetOptions): Promise<string> {\n return this.getProducedVideo(opts)\n }\n\n // ── Child Assets ───────────────────────────────────────────────────────────\n\n /** Directory containing shorts */\n private get shortsDir(): string {\n return join(this.videoDir, 'shorts')\n }\n\n /** Directory containing medium clips */\n private get mediumClipsDir(): string {\n return join(this.videoDir, 'medium-clips')\n }\n\n /** Directory containing social posts */\n private get socialPostsDir(): string {\n return join(this.videoDir, 'social-posts')\n }\n\n /**\n * Get short clips for this video as ShortVideoAsset objects.\n * Loads clip data from disk if available, wraps each in ShortVideoAsset.\n *\n * @param opts - Options controlling generation\n * @returns Array of ShortVideoAsset objects\n */\n async getShorts(opts?: AssetOptions): Promise<ShortVideoAsset[]> {\n const clips = await this.loadOrGenerateShorts(opts)\n return clips.map((clip) => new ShortVideoAsset(this, clip, this.shortsDir))\n }\n\n /**\n * Load raw short clip data from disk or generate via ShortsAgent.\n *\n * @param opts - Options controlling generation\n * @returns Array of ShortClip objects\n */\n private async loadOrGenerateShorts(opts?: AssetOptions): Promise<ShortClip[]> {\n return this.cached('shortsData', async () => {\n // Check if shorts already exist on disk\n if (!opts?.force && await fileExists(this.shortsJsonPath)) {\n const data = await readJsonFile<{ shorts: ShortClip[] }>(this.shortsJsonPath)\n return data.shorts ?? []\n }\n\n // Check if individual short files exist in shorts directory\n if (!opts?.force && await fileExists(this.shortsDir)) {\n const files = await listDirectory(this.shortsDir)\n const mdFiles = files.filter((f) => f.endsWith('.md') && f !== 'README.md')\n if (mdFiles.length > 0) {\n // Parse shorts from individual markdown files\n // TODO: Implement parsing of individual short files\n logger.info(`Found ${mdFiles.length} short files, but parsing not yet implemented`)\n }\n }\n\n // Generate via ShortsAgent\n const { generateShorts } = await loadShortsAgent()\n const transcript = await this.getTranscript()\n const videoFile = await this.toVideoFile()\n const shorts = await generateShorts(videoFile, transcript)\n logger.info(`Generated ${shorts.length} short clips`)\n return shorts\n })\n }\n\n /**\n * Get medium clips for this video as MediumClipAsset objects.\n * Loads clip data from disk if available, wraps each in MediumClipAsset.\n *\n * @param opts - Options controlling generation\n * @returns Array of MediumClipAsset objects\n */\n async getMediumClips(opts?: AssetOptions): Promise<MediumClipAsset[]> {\n const clips = await this.loadOrGenerateMediumClips(opts)\n return clips.map((clip) => new MediumClipAsset(this, clip, this.mediumClipsDir))\n }\n\n /**\n * Load raw medium clip data from disk or generate via MediumVideoAgent.\n *\n * @param opts - Options controlling generation\n * @returns Array of MediumClip objects\n */\n private async loadOrGenerateMediumClips(opts?: AssetOptions): Promise<MediumClip[]> {\n return this.cached('mediumClipsData', async () => {\n // Check if medium clips already exist on disk\n if (await fileExists(this.mediumClipsJsonPath)) {\n const data = await readJsonFile<{ clips: MediumClip[] }>(this.mediumClipsJsonPath)\n return data.clips ?? []\n }\n\n // Check if individual clip files exist\n if (await fileExists(this.mediumClipsDir)) {\n const files = await listDirectory(this.mediumClipsDir)\n const mdFiles = files.filter((f) => f.endsWith('.md') && f !== 'README.md')\n if (mdFiles.length > 0) {\n logger.info(`Found ${mdFiles.length} medium clip files, but parsing not yet implemented`)\n }\n }\n\n // Generate via MediumVideoAgent\n const { generateMediumClips } = await loadMediumVideoAgent()\n const transcript = await this.getTranscript()\n const videoFile = await this.toVideoFile()\n const clips = await generateMediumClips(videoFile, transcript)\n logger.info(`Generated ${clips.length} medium clips for ${this.slug}`)\n return clips\n })\n }\n\n /**\n * Get social posts for this video as SocialPostAsset objects.\n * Returns one asset per platform.\n *\n * @returns Array of SocialPostAsset objects (one per platform)\n */\n async getSocialPosts(): Promise<SocialPostAsset[]> {\n const platforms: Platform[] = [\n Platform.TikTok,\n Platform.YouTube,\n Platform.Instagram,\n Platform.LinkedIn,\n Platform.X,\n ]\n return platforms.map((platform) => new SocialPostAsset(this, platform, this.socialPostsDir))\n }\n\n /**\n * Get the summary asset for this video.\n *\n * @returns SummaryAsset wrapping the README.md\n */\n async getSummary(): Promise<SummaryAsset> {\n return new SummaryAsset(this)\n }\n\n /**\n * Get the blog post asset for this video.\n *\n * @returns BlogAsset wrapping the blog-post.md\n */\n async getBlog(): Promise<BlogAsset> {\n return new BlogAsset(this)\n }\n\n /**\n * Get chapters for this video.\n * Loads from disk if available (via base class), otherwise generates via ChapterAgent.\n *\n * @param opts - Options controlling generation\n * @returns Array of Chapter objects\n */\n override async getChapters(opts?: AssetOptions): Promise<Chapter[]> {\n // Try loading from disk first (base class handles caching + disk read)\n const diskChapters = await super.getChapters(opts)\n if (diskChapters.length > 0) {\n return diskChapters\n }\n\n // Generate via ChapterAgent and cache the result\n return this.cached('chapters', async () => {\n const { generateChapters } = await loadChapterAgent()\n const transcript = await this.getTranscript()\n const videoFile = await this.toVideoFile()\n const chapters = await generateChapters(videoFile, transcript)\n logger.info(`Generated ${chapters.length} chapters`)\n return chapters\n })\n }\n\n // ── Text Assets ────────────────────────────────────────────────────────────\n\n /**\n * Get the summary README content.\n *\n * @returns Summary markdown content\n * @throws Error if summary doesn't exist\n */\n async getSummaryContent(): Promise<string> {\n if (!(await fileExists(this.summaryPath))) {\n throw new Error(`Summary not found at ${this.summaryPath}. Run the summary stage first.`)\n }\n return readTextFile(this.summaryPath)\n }\n\n /**\n * Get the blog post content.\n *\n * @returns Blog post markdown content\n * @throws Error if blog doesn't exist\n */\n async getBlogContent(): Promise<string> {\n if (!(await fileExists(this.blogPath))) {\n throw new Error(`Blog post not found at ${this.blogPath}. Run the blog stage first.`)\n }\n return readTextFile(this.blogPath)\n }\n\n // ── Transcript Access ──────────────────────────────────────────────────────\n\n /**\n * Get the adjusted transcript (post silence-removal).\n * Falls back to original transcript if adjusted version doesn't exist.\n */\n async getAdjustedTranscript(): Promise<Transcript> {\n if (await fileExists(this.adjustedTranscriptPath)) {\n return readJsonFile<Transcript>(this.adjustedTranscriptPath)\n }\n // Fall back to original transcript\n return this.getTranscript()\n }\n\n // ── VideoFile Conversion ───────────────────────────────────────────────────\n\n /**\n * Convert to VideoFile interface for compatibility with existing agents.\n */\n async toVideoFile(): Promise<VideoFile> {\n const metadata = await this.getMetadata()\n const stats = await getFileStats(this.videoPath)\n const layout = await this.getLayout().catch(() => undefined)\n\n return {\n originalPath: this.sourcePath,\n repoPath: this.videoPath,\n videoDir: this.videoDir,\n slug: this.slug,\n filename: `${this.slug}.mp4`,\n duration: metadata.duration,\n size: stats.size,\n createdAt: new Date(stats.mtime),\n layout,\n }\n }\n}\n","import { execCommandSync } from '../core/process.js'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\nexport async function commitAndPush(videoSlug: string, message?: string): Promise<void> {\n const { REPO_ROOT } = getConfig()\n const commitMessage = message || `Auto-processed video: ${videoSlug}`\n\n try {\n logger.info(`Staging all changes in ${REPO_ROOT}`)\n execCommandSync('git add -A', { cwd: REPO_ROOT, stdio: 'pipe' })\n\n logger.info(`Committing: ${commitMessage}`)\n execCommandSync(`git commit -m \"${commitMessage}\"`, { cwd: REPO_ROOT, stdio: 'pipe' })\n\n const branch = execCommandSync('git rev-parse --abbrev-ref HEAD', { cwd: REPO_ROOT, stdio: 'pipe' })\n logger.info(`Pushing to origin ${branch}`)\n execCommandSync(`git push origin ${branch}`, { cwd: REPO_ROOT, stdio: 'pipe' })\n\n logger.info('Git commit and push completed successfully')\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n if (msg.includes('nothing to commit')) {\n logger.info('Nothing to commit, working tree clean')\n return\n }\n logger.error(`Git operation failed: ${msg}`)\n throw error\n }\n}\n\nexport async function stageFiles(patterns: string[]): Promise<void> {\n const { REPO_ROOT } = getConfig()\n\n for (const pattern of patterns) {\n try {\n logger.info(`Staging files matching: ${pattern}`)\n execCommandSync(`git add ${pattern}`, { cwd: REPO_ROOT, stdio: 'pipe' })\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n logger.error(`Failed to stage pattern \"${pattern}\": ${msg}`)\n throw error\n }\n }\n}\n","import { readTextFile } from '../core/fileSystem.js'\nimport { join, dirname } from '../core/paths.js'\nimport logger from '../config/logger'\nimport { PLATFORM_CHAR_LIMITS, toLatePlatform } from '../types'\nimport { Platform } from '../types'\nimport type { VideoFile, ShortClip, MediumClip, SocialPost } from '../types'\nimport { getMediaRule, platformAcceptsMedia } from './platformContentStrategy'\nimport type { ClipType } from './platformContentStrategy'\nimport { createItem, itemExists, type QueueItemMetadata } from './postStore'\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface QueueBuildResult {\n itemsCreated: number\n itemsSkipped: number\n errors: string[]\n}\n\n// ============================================================================\n// MEDIA RESOLUTION (driven by platformContentStrategy)\n// ============================================================================\n\n/**\n * Resolve the media file path for a short clip on a given platform.\n * Uses the content strategy's variantKey to find the right variant,\n * then falls back to captionedPath → outputPath.\n */\nfunction resolveShortMedia(clip: ShortClip, platform: Platform): string | null {\n const rule = getMediaRule(platform, 'short')\n if (!rule) return null // platform doesn't accept short media\n\n // If the rule specifies a variant key, look it up\n if (rule.variantKey && clip.variants?.length) {\n const match = clip.variants.find(v => v.platform === rule.variantKey)\n if (match) return match.path\n\n // Instagram fallback: try instagram-feed when instagram-reels missing\n if (platform === Platform.Instagram) {\n const fallback = clip.variants.find(v => v.platform === 'instagram-feed')\n if (fallback) return fallback.path\n }\n }\n\n // Fallback: captioned landscape → original\n return rule.captions\n ? (clip.captionedPath ?? clip.outputPath)\n : clip.outputPath\n}\n\n/**\n * Resolve the media file path for a medium clip on a given platform.\n */\nfunction resolveMediumMedia(clip: MediumClip, platform: Platform): string | null {\n const rule = getMediaRule(platform, 'medium-clip')\n if (!rule) return null // platform doesn't accept medium-clip media\n\n return rule.captions\n ? (clip.captionedPath ?? clip.outputPath)\n : clip.outputPath\n}\n\n/**\n * Resolve the media file path for a video-level post on a given platform.\n */\nfunction resolveVideoMedia(\n video: VideoFile,\n platform: Platform,\n captionedVideoPath: string | undefined,\n): string | null {\n const rule = getMediaRule(platform, 'video')\n if (!rule) return null // platform doesn't accept main-video media\n\n return rule.captions\n ? (captionedVideoPath ?? join(video.videoDir, video.filename))\n : join(video.videoDir, video.filename)\n}\n\n// ============================================================================\n// FRONTMATTER PARSER\n// ============================================================================\n\n/**\n * Parse YAML frontmatter from a post markdown file.\n * Handles simple key: value patterns. Arrays are stored as raw strings (e.g., \"[foo, bar]\").\n * For complex YAML parsing, consider adding a yaml library.\n */\nasync function parsePostFrontmatter(postPath: string): Promise<Record<string, string>> {\n let content: string\n try {\n content = await readTextFile(postPath)\n } catch {\n return {}\n }\n\n const result: Record<string, string> = {}\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/)\n if (!match) return result\n\n const yamlBlock = match[1]\n for (const line of yamlBlock.split(/\\r?\\n/)) {\n const kvMatch = line.match(/^(\\w+):\\s*(.*)$/)\n if (!kvMatch) continue\n\n const key = kvMatch[1]\n let value = kvMatch[2].trim()\n\n // Strip surrounding quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1)\n }\n\n // Treat 'null' string as empty\n if (value === 'null') continue\n\n result[key] = value\n }\n\n return result\n}\n\n// ============================================================================\n// CONTENT EXTRACTOR\n// ============================================================================\n\n/** Strip YAML frontmatter from markdown, returning only the body content. */\nfunction stripFrontmatter(content: string): string {\n return content.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/, '').trim()\n}\n\n// ============================================================================\n// MAIN EXPORT\n// ============================================================================\n\nexport async function buildPublishQueue(\n video: VideoFile,\n shorts: ShortClip[],\n mediumClips: MediumClip[],\n socialPosts: SocialPost[],\n captionedVideoPath: string | undefined,\n): Promise<QueueBuildResult> {\n const result: QueueBuildResult = { itemsCreated: 0, itemsSkipped: 0, errors: [] }\n\n for (const post of socialPosts) {\n try {\n const latePlatform = toLatePlatform(post.platform)\n const frontmatter = await parsePostFrontmatter(post.outputPath)\n\n let clipSlug: string\n let clipType: ClipType\n let mediaPath: string | null = null\n let sourceClip: string | null = null\n\n if (frontmatter.shortSlug) {\n // Short or medium clip post\n const short = shorts.find(s => s.slug === frontmatter.shortSlug)\n const medium = mediumClips.find(m => m.slug === frontmatter.shortSlug)\n\n if (short) {\n clipSlug = short.slug\n clipType = 'short'\n sourceClip = dirname(short.outputPath)\n mediaPath = resolveShortMedia(short, post.platform)\n } else if (medium) {\n clipSlug = medium.slug\n clipType = 'medium-clip'\n sourceClip = dirname(medium.outputPath)\n mediaPath = resolveMediumMedia(medium, post.platform)\n } else {\n clipSlug = frontmatter.shortSlug\n clipType = 'short'\n logger.warn(`Clip not found for slug: ${frontmatter.shortSlug}`)\n }\n } else {\n // Video-level post (stage 10)\n clipSlug = video.slug\n clipType = 'video'\n mediaPath = resolveVideoMedia(video, post.platform, captionedVideoPath)\n }\n\n // Skip posts for platform+clipType combos not in the content matrix\n if (!platformAcceptsMedia(post.platform, clipType)) {\n logger.debug(`Skipping ${post.platform}/${clipType} — not in content matrix`)\n result.itemsSkipped++\n continue\n }\n\n const itemId = `${clipSlug}-${latePlatform}`\n\n // Idempotency: skip if already published\n const exists = await itemExists(itemId)\n if (exists === 'published') {\n result.itemsSkipped++\n continue\n }\n\n const metadata: QueueItemMetadata = {\n id: itemId,\n platform: latePlatform,\n accountId: '',\n sourceVideo: video.videoDir,\n sourceClip,\n clipType,\n sourceMediaPath: mediaPath,\n hashtags: post.hashtags,\n links: post.links.map(l => typeof l === 'string' ? { url: l } : l),\n characterCount: post.characterCount,\n platformCharLimit: PLATFORM_CHAR_LIMITS[latePlatform] ?? 2200,\n suggestedSlot: null,\n scheduledFor: null,\n status: 'pending_review',\n latePostId: null,\n publishedUrl: null,\n createdAt: new Date().toISOString(),\n reviewedAt: null,\n publishedAt: null,\n }\n\n // Use raw post content (strip frontmatter if the content includes it)\n const stripped = stripFrontmatter(post.content)\n const postContent = stripped.trim().length > 0 ? stripped : post.content\n \n // Validate content exists after stripping frontmatter\n if (postContent.trim().length === 0) {\n throw new Error('Post content is empty after stripping frontmatter')\n }\n\n await createItem(itemId, metadata, postContent, mediaPath ?? undefined)\n result.itemsCreated++\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n result.errors.push(`${post.platform}: ${msg}`)\n logger.error(`Queue builder error for ${post.platform}: ${msg}`)\n }\n }\n\n logger.info(\n `Queue builder: ${result.itemsCreated} created, ${result.itemsSkipped} skipped, ${result.errors.length} errors`,\n )\n return result\n}\n","import { Platform } from '../types'\nimport type { VideoPlatform } from '../types'\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport type ClipType = 'video' | 'short' | 'medium-clip'\n\n/** How to resolve media for a specific platform + clip type combination. */\nexport interface MediaRule {\n /** Use the captioned variant when available */\n captions: boolean\n /**\n * Variant key to look up in ShortClip.variants / MediumClip.variants.\n * null = use the captioned/original clip directly (no variant lookup).\n */\n variantKey: VideoPlatform | null\n}\n\n// ============================================================================\n// CONTENT MATRIX\n// ============================================================================\n\n/**\n * Central content matrix — defines what clip types each platform accepts\n * and how to resolve the media file for each.\n *\n * If a platform + clipType combination is NOT listed, that post type is\n * text-only (no media attached).\n *\n * | Platform | video (main) | short | medium-clip |\n * |-----------|---------------------|----------------------|---------------------|\n * | YouTube | original, captioned | 9:16 portrait | original, captioned |\n * | LinkedIn | — | — | original, captioned |\n * | TikTok | — | 9:16 portrait | — |\n * | Instagram | — | 9:16 reels + 4:5 feed| original, captioned |\n * | X/Twitter | — | original, captioned | — |\n *\n * Posts whose clip type has no entry here will still be created but without\n * media (the social-media agents decide which clip types get posts per platform).\n */\nconst CONTENT_MATRIX: Record<Platform, Partial<Record<ClipType, MediaRule>>> = {\n [Platform.YouTube]: {\n video: { captions: true, variantKey: null },\n short: { captions: true, variantKey: 'youtube-shorts' },\n 'medium-clip': { captions: true, variantKey: null },\n },\n [Platform.LinkedIn]: {\n 'medium-clip': { captions: true, variantKey: null },\n },\n [Platform.TikTok]: {\n short: { captions: true, variantKey: 'tiktok' },\n },\n [Platform.Instagram]: {\n short: { captions: true, variantKey: 'instagram-reels' },\n 'medium-clip': { captions: true, variantKey: null },\n },\n [Platform.X]: {\n short: { captions: true, variantKey: null },\n },\n}\n\n// ============================================================================\n// PUBLIC API\n// ============================================================================\n\n/**\n * Get the media rule for a platform + clip type.\n * Returns null if that combination should be text-only.\n */\nexport function getMediaRule(platform: Platform, clipType: ClipType): MediaRule | null {\n return CONTENT_MATRIX[platform]?.[clipType] ?? null\n}\n\n/**\n * Check whether a platform accepts a given clip type (i.e. should attach media).\n */\nexport function platformAcceptsMedia(platform: Platform, clipType: ClipType): boolean {\n return getMediaRule(platform, clipType) !== null\n}\n","import { getConfig } from '../config/environment'\nimport logger from '../config/logger'\nimport { readTextFile, writeTextFile, writeJsonFile, ensureDirectory, copyFile, fileExists, listDirectoryWithTypes, removeDirectory, renameFile, copyDirectory } from '../core/fileSystem.js'\nimport { join, basename, resolve, sep } from '../core/paths.js'\n\nexport interface QueueItemMetadata {\n id: string\n platform: string\n accountId: string\n sourceVideo: string\n sourceClip: string | null\n clipType: 'video' | 'short' | 'medium-clip'\n sourceMediaPath: string | null\n hashtags: string[]\n links: Array<{ url: string; title?: string }>\n characterCount: number\n platformCharLimit: number\n suggestedSlot: string | null\n scheduledFor: string | null\n status: 'pending_review' | 'published'\n latePostId: string | null\n publishedUrl: string | null\n createdAt: string\n reviewedAt: string | null\n publishedAt: string | null\n textOnly?: boolean\n platformSpecificData?: Record<string, unknown>\n}\n\nexport interface QueueItem {\n id: string\n metadata: QueueItemMetadata\n postContent: string\n hasMedia: boolean\n mediaPath: string | null\n folderPath: string\n}\n\nexport interface GroupedQueueItem {\n groupKey: string\n sourceVideo: string\n sourceClip: string | null\n clipType: 'video' | 'short' | 'medium-clip'\n hasMedia: boolean\n items: QueueItem[]\n}\n\nfunction getQueueDir(): string {\n const { OUTPUT_DIR } = getConfig()\n return join(OUTPUT_DIR, 'publish-queue')\n}\n\nfunction getPublishedDir(): string {\n const { OUTPUT_DIR } = getConfig()\n return join(OUTPUT_DIR, 'published')\n}\n\nasync function readQueueItem(folderPath: string, id: string): Promise<QueueItem | null> {\n const metadataPath = join(folderPath, 'metadata.json')\n const postPath = join(folderPath, 'post.md')\n const mediaPath = join(folderPath, 'media.mp4')\n\n try {\n // Read directly without prior existence check to avoid TOCTOU race\n const metadataRaw = await readTextFile(metadataPath)\n const metadata: QueueItemMetadata = JSON.parse(metadataRaw)\n\n let postContent = ''\n try {\n postContent = await readTextFile(postPath)\n } catch {\n logger.debug(`No post.md found for ${String(id).replace(/[\\r\\n]/g, '')}`)\n }\n\n let hasMedia = false\n const mediaFilePath = join(folderPath, 'media.mp4')\n hasMedia = await fileExists(mediaFilePath)\n\n return {\n id,\n metadata,\n postContent,\n hasMedia,\n mediaPath: hasMedia ? mediaFilePath : null,\n folderPath,\n }\n } catch (err) {\n logger.debug(`Failed to read queue item ${String(id).replace(/[\\r\\n]/g, '')}: ${String(err).replace(/[\\r\\n]/g, '')}`)\n return null\n }\n}\n\nexport async function getPendingItems(): Promise<QueueItem[]> {\n const queueDir = getQueueDir()\n await ensureDirectory(queueDir)\n\n let entries: string[]\n try {\n const dirents = await listDirectoryWithTypes(queueDir)\n entries = dirents.filter(d => d.isDirectory()).map(d => d.name)\n } catch {\n return []\n }\n\n const items: QueueItem[] = []\n for (const name of entries) {\n const item = await readQueueItem(join(queueDir, name), name)\n if (item) items.push(item)\n }\n\n // Sort: items with media first (shorts/clips), then text-only (video-level), then by date\n items.sort((a, b) => {\n if (a.hasMedia !== b.hasMedia) return a.hasMedia ? -1 : 1\n return a.metadata.createdAt.localeCompare(b.metadata.createdAt)\n })\n return items\n}\n\nexport async function getGroupedPendingItems(): Promise<GroupedQueueItem[]> {\n const items = await getPendingItems()\n \n // Group by clip slug — strip the platform suffix from item ID so platform\n // variants of the same clip (e.g. \"my-clip-youtube\", \"my-clip-instagram\")\n // land in the same group.\n const groups = new Map<string, QueueItem[]>()\n \n for (const item of items) {\n const platform = item.metadata.platform.toLowerCase()\n const clipSlug = item.id.endsWith(`-${platform}`)\n ? item.id.slice(0, -(platform.length + 1))\n : item.id\n const groupKey = `${item.metadata.sourceVideo}::${clipSlug}`\n if (!groups.has(groupKey)) {\n groups.set(groupKey, [])\n }\n groups.get(groupKey)!.push(item)\n }\n \n // Convert to GroupedQueueItem array\n const result: GroupedQueueItem[] = []\n for (const [groupKey, groupItems] of groups) {\n if (groupItems.length === 0) continue\n \n const first = groupItems[0]\n result.push({\n groupKey,\n sourceVideo: first.metadata.sourceVideo,\n sourceClip: first.metadata.sourceClip,\n clipType: first.metadata.clipType,\n hasMedia: first.hasMedia,\n items: groupItems,\n })\n }\n \n // Sort groups: media first, then by earliest createdAt in group\n result.sort((a, b) => {\n if (a.hasMedia !== b.hasMedia) return a.hasMedia ? -1 : 1\n const aDate = Math.min(...a.items.map(i => new Date(i.metadata.createdAt).getTime()))\n const bDate = Math.min(...b.items.map(i => new Date(i.metadata.createdAt).getTime()))\n return aDate - bDate\n })\n \n return result\n}\n\nexport async function getItem(id: string): Promise<QueueItem | null> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const folderPath = join(getQueueDir(), basename(id))\n return readQueueItem(folderPath, id)\n}\n\nexport async function createItem(\n id: string,\n metadata: QueueItemMetadata,\n postContent: string,\n mediaSourcePath?: string,\n): Promise<QueueItem> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const folderPath = join(getQueueDir(), basename(id))\n await ensureDirectory(folderPath)\n\n await writeJsonFile(join(folderPath, 'metadata.json'), metadata)\n await writeTextFile(join(folderPath, 'post.md'), postContent)\n\n let hasMedia = false\n const mediaPath = join(folderPath, 'media.mp4')\n\n if (mediaSourcePath) {\n await copyFile(mediaSourcePath, mediaPath)\n hasMedia = true\n }\n\n logger.debug(`Created queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n\n return {\n id,\n metadata,\n postContent,\n hasMedia,\n mediaPath: hasMedia ? mediaPath : null,\n folderPath,\n }\n}\n\nexport async function updateItem(\n id: string,\n updates: { postContent?: string; metadata?: Partial<QueueItemMetadata> },\n): Promise<QueueItem | null> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const existing = await getItem(id)\n if (!existing) return null\n\n if (updates.metadata) {\n // Sanitize metadata by re-constructing with only expected fields before writing\n const sanitized: QueueItemMetadata = {\n id: String(existing.metadata.id),\n platform: String(updates.metadata.platform ?? existing.metadata.platform),\n accountId: String(updates.metadata.accountId ?? existing.metadata.accountId),\n sourceVideo: String(existing.metadata.sourceVideo),\n sourceClip: existing.metadata.sourceClip !== null ? String(existing.metadata.sourceClip) : null,\n clipType: existing.metadata.clipType,\n sourceMediaPath: existing.metadata.sourceMediaPath !== null ? String(existing.metadata.sourceMediaPath) : null,\n hashtags: Array.isArray(updates.metadata.hashtags) ? updates.metadata.hashtags.map(String) : (Array.isArray(existing.metadata.hashtags) ? existing.metadata.hashtags.map(String) : []),\n links: Array.isArray(updates.metadata.links) ? updates.metadata.links : (Array.isArray(existing.metadata.links) ? existing.metadata.links : []),\n characterCount: updates.metadata.characterCount !== undefined ? Number(updates.metadata.characterCount) || 0 : (Number(existing.metadata.characterCount) || 0),\n platformCharLimit: updates.metadata.platformCharLimit !== undefined ? Number(updates.metadata.platformCharLimit) || 0 : (Number(existing.metadata.platformCharLimit) || 0),\n suggestedSlot: updates.metadata.suggestedSlot !== undefined ? (updates.metadata.suggestedSlot !== null ? String(updates.metadata.suggestedSlot) : null) : (existing.metadata.suggestedSlot !== null ? String(existing.metadata.suggestedSlot) : null),\n scheduledFor: updates.metadata.scheduledFor !== undefined ? (updates.metadata.scheduledFor !== null ? String(updates.metadata.scheduledFor) : null) : (existing.metadata.scheduledFor !== null ? String(existing.metadata.scheduledFor) : null),\n status: updates.metadata.status ?? existing.metadata.status,\n latePostId: updates.metadata.latePostId !== undefined ? (updates.metadata.latePostId !== null ? String(updates.metadata.latePostId) : null) : (existing.metadata.latePostId !== null ? String(existing.metadata.latePostId) : null),\n publishedUrl: updates.metadata.publishedUrl !== undefined ? (updates.metadata.publishedUrl !== null ? String(updates.metadata.publishedUrl) : null) : (existing.metadata.publishedUrl !== null ? String(existing.metadata.publishedUrl) : null),\n createdAt: String(existing.metadata.createdAt),\n reviewedAt: updates.metadata.reviewedAt !== undefined ? (updates.metadata.reviewedAt !== null ? String(updates.metadata.reviewedAt) : null) : (existing.metadata.reviewedAt !== null ? String(existing.metadata.reviewedAt) : null),\n publishedAt: updates.metadata.publishedAt !== undefined ? (updates.metadata.publishedAt !== null ? String(updates.metadata.publishedAt) : null) : (existing.metadata.publishedAt !== null ? String(existing.metadata.publishedAt) : null),\n textOnly: updates.metadata.textOnly ?? existing.metadata.textOnly,\n platformSpecificData: updates.metadata.platformSpecificData ?? existing.metadata.platformSpecificData,\n }\n // Use only the sanitized object — do not spread raw HTTP updates (CodeQL js/http-to-file-access)\n existing.metadata = sanitized\n // Validate write target is within the expected queue directory\n const metadataWritePath = resolve(join(existing.folderPath, 'metadata.json'))\n if (!metadataWritePath.startsWith(resolve(getQueueDir()) + sep)) {\n throw new Error('Write target outside queue directory')\n }\n await writeTextFile(\n metadataWritePath,\n JSON.stringify(existing.metadata, null, 2),\n )\n }\n\n if (updates.postContent !== undefined) {\n // Sanitize post content - ensure it's a string\n const sanitizedContent = String(updates.postContent)\n existing.postContent = sanitizedContent\n // Validate write target is within the expected queue directory (CodeQL js/http-to-file-access)\n const postWritePath = resolve(join(existing.folderPath, 'post.md'))\n if (!postWritePath.startsWith(resolve(getQueueDir()) + sep)) {\n throw new Error('Write target outside queue directory')\n }\n // lgtm[js/http-to-file-access] - Writing user-provided post content to queue is intended functionality with path validation\n await writeTextFile(postWritePath, sanitizedContent)\n }\n\n logger.debug(`Updated queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n return existing\n}\n\nexport async function approveItem(\n id: string,\n publishData: { latePostId: string; scheduledFor: string; publishedUrl?: string; accountId?: string },\n): Promise<void> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const item = await getItem(id)\n if (!item) return\n\n const now = new Date().toISOString()\n if (publishData.accountId) {\n item.metadata.accountId = String(publishData.accountId)\n }\n item.metadata.status = 'published'\n item.metadata.latePostId = String(publishData.latePostId)\n item.metadata.scheduledFor = String(publishData.scheduledFor)\n item.metadata.publishedUrl = publishData.publishedUrl ? String(publishData.publishedUrl) : null\n item.metadata.publishedAt = now\n item.metadata.reviewedAt = now\n\n // Sanitize metadata before writing - reconstruct with validated fields\n const sanitizedMetadata: QueueItemMetadata = {\n id: String(item.metadata.id),\n platform: String(item.metadata.platform),\n accountId: String(item.metadata.accountId),\n sourceVideo: String(item.metadata.sourceVideo),\n sourceClip: item.metadata.sourceClip !== null ? String(item.metadata.sourceClip) : null,\n clipType: item.metadata.clipType,\n sourceMediaPath: item.metadata.sourceMediaPath !== null ? String(item.metadata.sourceMediaPath) : null,\n hashtags: Array.isArray(item.metadata.hashtags) ? item.metadata.hashtags.map(String) : [],\n links: Array.isArray(item.metadata.links) ? item.metadata.links : [],\n characterCount: Number(item.metadata.characterCount) || 0,\n platformCharLimit: Number(item.metadata.platformCharLimit) || 0,\n suggestedSlot: item.metadata.suggestedSlot !== null ? String(item.metadata.suggestedSlot) : null,\n scheduledFor: item.metadata.scheduledFor !== null ? String(item.metadata.scheduledFor) : null,\n status: item.metadata.status,\n latePostId: item.metadata.latePostId !== null ? String(item.metadata.latePostId) : null,\n publishedUrl: item.metadata.publishedUrl !== null ? String(item.metadata.publishedUrl) : null,\n createdAt: String(item.metadata.createdAt),\n reviewedAt: item.metadata.reviewedAt !== null ? String(item.metadata.reviewedAt) : null,\n publishedAt: item.metadata.publishedAt !== null ? String(item.metadata.publishedAt) : null,\n textOnly: item.metadata.textOnly,\n platformSpecificData: item.metadata.platformSpecificData,\n }\n\n // Validate write target is within the expected queue directory (CodeQL js/http-to-file-access)\n const approveMetadataPath = resolve(join(item.folderPath, 'metadata.json'))\n if (!approveMetadataPath.startsWith(resolve(getQueueDir()) + sep)) {\n throw new Error('Write target outside queue directory')\n }\n // lgtm[js/http-to-file-access] - Writing sanitized metadata to queue is intended functionality with path validation\n await writeTextFile(\n approveMetadataPath,\n JSON.stringify(sanitizedMetadata, null, 2),\n )\n\n const publishedDir = getPublishedDir()\n await ensureDirectory(publishedDir)\n \n // Validate destination path to prevent path traversal - use basename inline\n const destPath = join(publishedDir, basename(id))\n const resolvedDest = resolve(destPath)\n const resolvedPublishedDir = resolve(publishedDir)\n if (!resolvedDest.startsWith(resolvedPublishedDir + sep) && resolvedDest !== resolvedPublishedDir) {\n throw new Error(`Invalid destination path for item ${id}`)\n }\n\n try {\n await renameFile(item.folderPath, destPath)\n } catch (renameErr: unknown) {\n // On Windows, rename can fail with EPERM if a file handle is still releasing.\n // Fall back to recursive copy + delete.\n const errCode = (renameErr as NodeJS.ErrnoException | null)?.code\n if (errCode === 'EPERM') {\n logger.warn(`rename failed (EPERM) for ${String(id).replace(/[\\r\\n]/g, '')}, falling back to copy+delete`)\n await copyDirectory(item.folderPath, destPath)\n await removeDirectory(item.folderPath, { recursive: true, force: true })\n } else {\n throw renameErr\n }\n }\n\n logger.debug(`Approved and moved queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n}\n\nexport interface BulkApprovalResult {\n itemId: string\n platform: string\n latePostId: string\n scheduledFor: string\n publishedUrl?: string\n}\n\nexport async function approveBulk(\n itemIds: string[],\n publishDataMap: Map<string, { latePostId: string; scheduledFor: string; publishedUrl?: string; accountId?: string }>,\n): Promise<BulkApprovalResult[]> {\n const results: BulkApprovalResult[] = []\n const errors: Array<{ itemId: string; error: string }> = []\n \n for (const id of itemIds) {\n try {\n const publishData = publishDataMap.get(id)\n if (!publishData) {\n errors.push({ itemId: id, error: 'No publish data provided' })\n continue\n }\n \n await approveItem(id, publishData)\n \n results.push({\n itemId: id,\n platform: id.split('-').pop() || 'unknown',\n latePostId: publishData.latePostId,\n scheduledFor: publishData.scheduledFor,\n publishedUrl: publishData.publishedUrl,\n })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n errors.push({ itemId: id, error: msg })\n logger.error(`Bulk approve failed for ${String(id).replace(/[\\r\\n]/g, '')}: ${msg}`)\n }\n }\n \n if (errors.length > 0) {\n logger.warn(`Bulk approval completed with ${errors.length} errors`)\n }\n \n return results\n}\n\nexport async function rejectItem(id: string): Promise<void> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n const folderPath = join(getQueueDir(), basename(id))\n try {\n await removeDirectory(folderPath, { recursive: true })\n logger.debug(`Rejected and deleted queue item: ${String(id).replace(/[\\r\\n]/g, '')}`)\n } catch (err) {\n logger.debug(`Failed to reject queue item ${String(id).replace(/[\\r\\n]/g, '')}: ${String(err).replace(/[\\r\\n]/g, '')}`)\n }\n}\n\nexport async function getPublishedItems(): Promise<QueueItem[]> {\n const publishedDir = getPublishedDir()\n await ensureDirectory(publishedDir)\n\n let entries: string[]\n try {\n const dirents = await listDirectoryWithTypes(publishedDir)\n entries = dirents.filter(d => d.isDirectory()).map(d => d.name)\n } catch {\n return []\n }\n\n const items: QueueItem[] = []\n for (const name of entries) {\n const item = await readQueueItem(join(publishedDir, name), name)\n if (item) items.push(item)\n }\n\n items.sort((a, b) => a.metadata.createdAt.localeCompare(b.metadata.createdAt))\n return items\n}\n\nexport async function itemExists(id: string): Promise<'pending' | 'published' | null> {\n // Inline validation to prevent path traversal - CodeQL recognizes this pattern\n if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {\n throw new Error(`Invalid ID format: ${id}`)\n }\n if (await fileExists(join(getQueueDir(), basename(id)))) {\n return 'pending'\n }\n\n if (await fileExists(join(getPublishedDir(), basename(id)))) {\n return 'published'\n }\n\n return null\n}\n","import { spawnCommand, createModuleRequire } from '../core/process.js'\nimport { fileExistsSync } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport { getConfig } from '../config/environment.js'\nimport { LateApiClient } from '../services/lateApi.js'\nimport { loadScheduleConfig } from '../services/scheduleConfig.js'\nimport type { ProviderName } from '../providers/index.js'\n\nconst require = createModuleRequire(import.meta.url)\n\ninterface CheckResult {\n label: string\n ok: boolean\n required: boolean\n message: string\n}\n\n/** Normalize LLM_PROVIDER the same way the provider factory does. */\nexport function normalizeProviderName(raw: string | undefined): string {\n return (raw || 'copilot').trim().toLowerCase()\n}\n\nfunction resolveFFmpegPath(): { path: string; source: string } {\n const config = getConfig()\n if (config.FFMPEG_PATH && config.FFMPEG_PATH !== 'ffmpeg') {\n return { path: config.FFMPEG_PATH, source: 'FFMPEG_PATH config' }\n }\n try {\n const staticPath = require('ffmpeg-static') as string\n if (staticPath && fileExistsSync(staticPath)) {\n return { path: staticPath, source: 'ffmpeg-static' }\n }\n } catch { /* not available */ }\n return { path: 'ffmpeg', source: 'system PATH' }\n}\n\nfunction resolveFFprobePath(): { path: string; source: string } {\n const config = getConfig()\n if (config.FFPROBE_PATH && config.FFPROBE_PATH !== 'ffprobe') {\n return { path: config.FFPROBE_PATH, source: 'FFPROBE_PATH config' }\n }\n try {\n const { path: probePath } = require('@ffprobe-installer/ffprobe') as { path: string }\n if (probePath && fileExistsSync(probePath)) {\n return { path: probePath, source: '@ffprobe-installer/ffprobe' }\n }\n } catch { /* not available */ }\n return { path: 'ffprobe', source: 'system PATH' }\n}\n\nfunction parseVersionFromOutput(output: string): string {\n const match = output.match(/(\\d+\\.\\d+(?:\\.\\d+)?)/)\n return match ? match[1] : 'unknown'\n}\n\nfunction getFFmpegInstallHint(): string {\n const platform = process.platform\n const lines = ['Install FFmpeg:']\n if (platform === 'win32') {\n lines.push(' winget install Gyan.FFmpeg')\n lines.push(' choco install ffmpeg (alternative)')\n } else if (platform === 'darwin') {\n lines.push(' brew install ffmpeg')\n } else {\n lines.push(' sudo apt install ffmpeg (Debian/Ubuntu)')\n lines.push(' sudo dnf install ffmpeg (Fedora)')\n lines.push(' sudo pacman -S ffmpeg (Arch)')\n }\n lines.push(' Or set FFMPEG_PATH to a custom binary location')\n return lines.join('\\n ')\n}\n\nfunction checkNode(): CheckResult {\n const raw = process.version // e.g. \"v20.11.1\"\n const major = parseInt(raw.slice(1), 10)\n const ok = major >= 20\n return {\n label: 'Node.js',\n ok,\n required: true,\n message: ok\n ? `Node.js ${raw} (required: ≥20)`\n : `Node.js ${raw} — version ≥20 required`,\n }\n}\n\nfunction checkFFmpeg(): CheckResult {\n const { path: binPath, source } = resolveFFmpegPath()\n try {\n const result = spawnCommand(binPath, ['-version'], { timeout: 10_000 })\n if (result.status === 0 && result.stdout) {\n const ver = parseVersionFromOutput(result.stdout)\n return { label: 'FFmpeg', ok: true, required: true, message: `FFmpeg ${ver} (source: ${source})` }\n }\n } catch { /* spawn failed */ }\n return {\n label: 'FFmpeg',\n ok: false,\n required: true,\n message: `FFmpeg not found — ${getFFmpegInstallHint()}`,\n }\n}\n\nfunction checkFFprobe(): CheckResult {\n const { path: binPath, source } = resolveFFprobePath()\n try {\n const result = spawnCommand(binPath, ['-version'], { timeout: 10_000 })\n if (result.status === 0 && result.stdout) {\n const ver = parseVersionFromOutput(result.stdout)\n return { label: 'FFprobe', ok: true, required: true, message: `FFprobe ${ver} (source: ${source})` }\n }\n } catch { /* spawn failed */ }\n return {\n label: 'FFprobe',\n ok: false,\n required: true,\n message: `FFprobe not found — usually included with FFmpeg.\\n ${getFFmpegInstallHint()}`,\n }\n}\n\nfunction checkOpenAIKey(): CheckResult {\n const set = !!getConfig().OPENAI_API_KEY\n return {\n label: 'OPENAI_API_KEY',\n ok: set,\n required: true,\n message: set\n ? 'OPENAI_API_KEY is set'\n : 'OPENAI_API_KEY not set — get one at https://platform.openai.com/api-keys',\n }\n}\n\nfunction checkExaKey(): CheckResult {\n const set = !!getConfig().EXA_API_KEY\n return {\n label: 'EXA_API_KEY',\n ok: set,\n required: false,\n message: set\n ? 'EXA_API_KEY is set'\n : 'EXA_API_KEY not set (optional — web search in social posts)',\n }\n}\n\nfunction checkGit(): CheckResult {\n try {\n const result = spawnCommand('git', ['--version'], { timeout: 10_000 })\n if (result.status === 0 && result.stdout) {\n const ver = parseVersionFromOutput(result.stdout)\n return { label: 'Git', ok: true, required: false, message: `Git ${ver}` }\n }\n } catch { /* spawn failed */ }\n return {\n label: 'Git',\n ok: false,\n required: false,\n message: 'Git not found (optional — needed for auto-commit stage)',\n }\n}\n\nfunction checkWatchFolder(): CheckResult {\n const watchDir = getConfig().WATCH_FOLDER || join(process.cwd(), 'watch')\n const exists = fileExistsSync(watchDir)\n return {\n label: 'Watch folder',\n ok: exists,\n required: false,\n message: exists\n ? `Watch folder exists: ${watchDir}`\n : `Watch folder missing: ${watchDir}`,\n }\n}\n\nexport async function runDoctor(): Promise<void> {\n console.log('\\n🔍 VidPipe Doctor — Checking prerequisites...\\n')\n\n const results: CheckResult[] = [\n checkNode(),\n checkFFmpeg(),\n checkFFprobe(),\n checkOpenAIKey(),\n checkExaKey(),\n checkGit(),\n checkWatchFolder(),\n ]\n\n for (const r of results) {\n const icon = r.ok ? '✅' : r.required ? '❌' : '⬚'\n console.log(` ${icon} ${r.message}`)\n }\n\n // LLM Provider section — check config values to avoid silent fallback\n const config = getConfig()\n console.log('\\nLLM Provider')\n const providerName = normalizeProviderName(config.LLM_PROVIDER) as ProviderName\n const isDefault = !config.LLM_PROVIDER\n const providerLabel = isDefault ? `${providerName} (default)` : providerName\n const validProviders: ProviderName[] = ['copilot', 'openai', 'claude']\n\n if (!validProviders.includes(providerName)) {\n console.log(` ❌ Provider: ${providerLabel} — unknown provider`)\n results.push({ label: 'LLM Provider', ok: false, required: true, message: `Unknown provider: ${providerName}` })\n } else if (providerName === 'copilot') {\n console.log(` ✅ Provider: ${providerLabel}`)\n console.log(' ✅ Copilot — uses GitHub auth')\n } else if (providerName === 'openai') {\n console.log(` ✅ Provider: ${providerLabel}`)\n if (config.OPENAI_API_KEY) {\n console.log(' ✅ OPENAI_API_KEY is set (also used for Whisper)')\n } else {\n console.log(' ❌ OPENAI_API_KEY not set (required for openai provider)')\n results.push({ label: 'LLM Provider', ok: false, required: true, message: 'OPENAI_API_KEY not set for OpenAI LLM' })\n }\n } else if (providerName === 'claude') {\n console.log(` ✅ Provider: ${providerLabel}`)\n if (config.ANTHROPIC_API_KEY) {\n console.log(' ✅ ANTHROPIC_API_KEY is set')\n } else {\n console.log(' ❌ ANTHROPIC_API_KEY not set (required for claude provider)')\n results.push({ label: 'LLM Provider', ok: false, required: true, message: 'ANTHROPIC_API_KEY not set for Claude LLM' })\n }\n }\n\n const defaultModels: Record<ProviderName, string> = {\n copilot: 'Claude Opus 4.6',\n openai: 'gpt-4o',\n claude: 'claude-opus-4.6',\n }\n if (validProviders.includes(providerName)) {\n const defaultModel = defaultModels[providerName]\n const modelOverride = config.LLM_MODEL\n if (modelOverride) {\n console.log(` ℹ️ Model override: ${modelOverride} (default: ${defaultModel})`)\n } else {\n console.log(` ℹ️ Default model: ${defaultModel}`)\n }\n }\n\n // Late API (optional — social publishing)\n console.log('\\nSocial Publishing')\n await checkLateApi(config.LATE_API_KEY)\n\n // Schedule config\n await checkScheduleConfig()\n\n const failedRequired = results.filter(r => r.required && !r.ok)\n\n console.log()\n if (failedRequired.length === 0) {\n console.log(' All required checks passed! ✅\\n')\n process.exit(0)\n } else {\n console.log(` ${failedRequired.length} required check${failedRequired.length > 1 ? 's' : ''} failed ❌\\n`)\n process.exit(1)\n }\n}\n\nconst PLATFORM_LABELS: Record<string, string> = {\n tiktok: 'TikTok',\n youtube: 'YouTube',\n instagram: 'Instagram',\n linkedin: 'LinkedIn',\n twitter: 'X/Twitter',\n}\n\nasync function checkLateApi(apiKey: string): Promise<void> {\n if (!apiKey) {\n console.log(' ⬚ Late API key: not configured (optional — set LATE_API_KEY for social publishing)')\n return\n }\n\n try {\n const client = new LateApiClient(apiKey)\n const { valid, profileName, error } = await client.validateConnection()\n\n if (!valid) {\n console.log(` ❌ Late API key: invalid (${error ?? 'unknown error'})`)\n return\n }\n\n console.log(` ✅ Late API key: connected to profile \"${profileName ?? 'unknown'}\"`)\n\n // List connected accounts\n try {\n const accounts = await client.listAccounts()\n if (accounts.length === 0) {\n console.log(' ⚠️ No social accounts connected in Late dashboard')\n } else {\n for (const acct of accounts) {\n const label = PLATFORM_LABELS[acct.platform] ?? acct.platform\n const handle = acct.username ? `@${acct.username}` : acct.displayName\n console.log(` ✅ ${label} — ${handle}`)\n }\n }\n } catch {\n console.log(' ⚠️ Could not fetch connected accounts')\n }\n } catch {\n console.log(' ❌ Late API key: could not connect (network error)')\n }\n}\n\nasync function checkScheduleConfig(): Promise<void> {\n const schedulePath = join(process.cwd(), 'schedule.json')\n\n if (!fileExistsSync(schedulePath)) {\n console.log(' ⬚ Schedule config: schedule.json not found (will use defaults on first run)')\n return\n }\n\n try {\n const scheduleConfig = await loadScheduleConfig(schedulePath)\n const platformCount = Object.keys(scheduleConfig.platforms).length\n console.log(` ✅ Schedule config: schedule.json found (${platformCount} platform${platformCount !== 1 ? 's' : ''} configured)`)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.log(` ❌ Schedule config: schedule.json invalid — ${msg}`)\n }\n}\n","/**\n * Media Upload Flow (verified via live testing 2026-02-09):\n * - Step 1: POST /media/presign { filename, contentType } → { uploadUrl, publicUrl, key, expiresIn }\n * - Step 2: PUT file bytes to uploadUrl (presigned Cloudflare R2 URL) with Content-Type header\n * - Step 3: Use publicUrl (https://media.getlate.dev/temp/...) in createPost({ mediaItems: [{ type, url }] })\n *\n * Notes:\n * - The old POST /media/upload endpoint exists but requires an \"upload token\" (not an API key).\n * It is likely used internally by Late's web UI; the presign flow is the correct API approach.\n * - Presigned URLs expire in 3600s (1 hour).\n * - Public URLs are served from media.getlate.dev CDN and are immediately accessible after PUT.\n * - No confirmation step is needed after uploading to the presigned URL.\n */\nimport { getConfig } from '../config/environment.js'\nimport logger from '../config/logger.js'\nimport { getFileStats, openReadStream } from '../core/fileSystem.js'\nimport { Readable } from '../core/network.js'\nimport { basename, extname } from '../core/paths.js'\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface LateAccount {\n _id: string\n platform: string // 'tiktok' | 'youtube' | 'instagram' | 'linkedin' | 'twitter'\n displayName: string\n username: string\n isActive: boolean\n profileId: { _id: string; name: string }\n}\n\nexport interface LateProfile {\n _id: string\n name: string\n}\n\nexport interface LatePost {\n _id: string\n content: string\n status: string // 'draft' | 'scheduled' | 'published' | 'failed'\n platforms: Array<{ platform: string; accountId: string }>\n scheduledFor?: string\n mediaItems?: Array<{ type: string; url: string }>\n isDraft?: boolean\n createdAt: string\n updatedAt: string\n}\n\nexport interface LateMediaPresignResult {\n uploadUrl: string\n publicUrl: string\n key: string\n expiresIn: number\n}\n\nexport interface LateMediaUploadResult {\n url: string\n type: 'image' | 'video'\n}\n\nexport interface CreatePostParams {\n content: string\n platforms: Array<{ platform: string; accountId: string }>\n scheduledFor?: string\n timezone?: string\n isDraft?: boolean\n mediaItems?: Array<{ type: 'image' | 'video'; url: string; thumbnail?: { url: string } }>\n platformSpecificData?: Record<string, unknown>\n tiktokSettings?: {\n privacy_level: string\n allow_comment: boolean\n allow_duet?: boolean\n allow_stitch?: boolean\n content_preview_confirmed: boolean\n express_consent_given: boolean\n [key: string]: unknown\n }\n}\n\n// ── Client ─────────────────────────────────────────────────────────────\n\nexport class LateApiClient {\n private baseUrl = 'https://getlate.dev/api/v1'\n private apiKey: string\n\n constructor(apiKey?: string) {\n this.apiKey = apiKey ?? getConfig().LATE_API_KEY\n if (!this.apiKey) {\n throw new Error('LATE_API_KEY is required — set it in environment or pass to constructor')\n }\n }\n\n // ── Private request helper ───────────────────────────────────────────\n\n private async request<T>(\n endpoint: string,\n options: RequestInit = {},\n retries = 3,\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n ...(options.headers as Record<string, string> | undefined),\n }\n\n // Only set Content-Type for non-FormData bodies\n if (!(options.body instanceof FormData)) {\n headers['Content-Type'] = 'application/json'\n }\n\n logger.debug(`Late API ${options.method ?? 'GET'} ${endpoint}`)\n\n for (let attempt = 1; attempt <= retries; attempt++) {\n const response = await fetch(url, { ...options, headers })\n\n if (response.ok) {\n // 204 No Content\n if (response.status === 204) return undefined as T\n return (await response.json()) as T\n }\n\n // 429 — rate limited, retry\n if (response.status === 429 && attempt < retries) {\n const retryAfter = Number(response.headers.get('Retry-After')) || 2\n logger.warn(`Late API rate limited, retrying in ${retryAfter}s (attempt ${attempt}/${retries})`)\n await new Promise((r) => setTimeout(r, retryAfter * 1000))\n continue\n }\n\n // 401 — bad API key\n if (response.status === 401) {\n throw new Error(\n 'Late API authentication failed (401). Check that LATE_API_KEY is valid.',\n )\n }\n\n // Other errors\n const body = await response.text().catch(() => '<no body>')\n throw new Error(\n `Late API error ${response.status} ${options.method ?? 'GET'} ${endpoint}: ${body}`,\n )\n }\n\n // Should not reach here, but satisfy TS\n throw new Error(`Late API request failed after ${retries} retries`)\n }\n\n // ── Core methods ─────────────────────────────────────────────────────\n\n async listProfiles(): Promise<LateProfile[]> {\n const data = await this.request<{ profiles: LateProfile[] }>('/profiles')\n return data.profiles ?? []\n }\n\n async listAccounts(): Promise<LateAccount[]> {\n const data = await this.request<{ accounts: LateAccount[] }>('/accounts')\n return data.accounts ?? []\n }\n\n async getScheduledPosts(platform?: string): Promise<LatePost[]> {\n const params = new URLSearchParams({ status: 'scheduled' })\n if (platform) params.set('platform', platform)\n const data = await this.request<{ posts: LatePost[] }>(`/posts?${params}`)\n return data.posts ?? []\n }\n\n async getDraftPosts(platform?: string): Promise<LatePost[]> {\n const params = new URLSearchParams({ status: 'draft' })\n if (platform) params.set('platform', platform)\n const data = await this.request<{ posts: LatePost[] }>(`/posts?${params}`)\n return data.posts ?? []\n }\n\n async createPost(params: CreatePostParams): Promise<LatePost> {\n const data = await this.request<{ post: LatePost }>('/posts', {\n method: 'POST',\n body: JSON.stringify(params),\n })\n return data.post\n }\n\n async deletePost(postId: string): Promise<void> {\n await this.request<void>(`/posts/${encodeURIComponent(postId)}`, {\n method: 'DELETE',\n })\n }\n\n async updatePost(postId: string, updates: Record<string, unknown>): Promise<LatePost> {\n const data = await this.request<{ post: LatePost }>(`/posts/${encodeURIComponent(postId)}`, {\n method: 'PUT',\n body: JSON.stringify(updates),\n })\n return data.post\n }\n\n async uploadMedia(filePath: string): Promise<LateMediaUploadResult> {\n const fileStats = await getFileStats(filePath)\n const fileName = basename(filePath)\n const ext = extname(fileName).toLowerCase()\n const contentType =\n ext === '.mp4' ? 'video/mp4' : ext === '.webm' ? 'video/webm' : ext === '.mov' ? 'video/quicktime' : 'video/mp4'\n\n logger.info(`Late API uploading ${String(fileName).replace(/[\\r\\n]/g, '')} (${(fileStats.size / 1024 / 1024).toFixed(1)} MB)`)\n\n // Step 1: Get presigned upload URL\n const presign = await this.request<LateMediaPresignResult>('/media/presign', {\n method: 'POST',\n body: JSON.stringify({ filename: fileName, contentType }),\n })\n logger.debug(`Late API presigned URL obtained for ${String(fileName).replace(/[\\r\\n]/g, '')} (expires in ${presign.expiresIn}s)`)\n\n // Step 2: Stream file to presigned URL (avoids loading entire file into memory)\n const nodeStream = openReadStream(filePath)\n try {\n const webStream = Readable.toWeb(nodeStream) as ReadableStream\n const uploadResp = await fetch(presign.uploadUrl, {\n method: 'PUT',\n headers: {\n 'Content-Type': contentType,\n 'Content-Length': String(fileStats.size),\n },\n body: webStream,\n // Node.js-specific property for streaming request bodies (not in standard RequestInit type)\n duplex: 'half',\n } as RequestInit)\n if (!uploadResp.ok) {\n throw new Error(`Late media upload failed: ${uploadResp.status} ${uploadResp.statusText}`)\n }\n } finally {\n // Ensure file handle is released so the folder can be renamed/moved on Windows\n nodeStream.destroy()\n }\n logger.debug(`Late API media uploaded → ${presign.publicUrl}`)\n\n const type: 'image' | 'video' = contentType.startsWith('image/') ? 'image' : 'video'\n return { url: presign.publicUrl, type }\n }\n\n // ── Helper ───────────────────────────────────────────────────────────\n\n async validateConnection(): Promise<{ valid: boolean; profileName?: string; error?: string }> {\n try {\n const profiles = await this.listProfiles()\n const name = profiles[0]?.name\n logger.info(`Late API connection valid — profile: ${name ?? 'unknown'}`)\n return { valid: true, profileName: name }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`Late API connection failed: ${message}`)\n return { valid: false, error: message }\n }\n }\n}\n","export { Readable } from 'stream'\n","import { readTextFile, writeFileRaw } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport logger from '../config/logger.js'\n\nexport type DayOfWeek = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun'\n\nexport interface TimeSlot {\n days: DayOfWeek[]\n time: string // HH:MM format\n label: string\n}\n\nexport interface PlatformSchedule {\n slots: TimeSlot[]\n avoidDays: DayOfWeek[]\n}\n\nexport interface ScheduleConfig {\n timezone: string\n platforms: Record<string, PlatformSchedule>\n}\n\nconst VALID_DAYS: DayOfWeek[] = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']\nconst TIME_REGEX = /^([01]\\d|2[0-3]):[0-5]\\d$/\n\nlet cachedConfig: ScheduleConfig | null = null\n\nexport function getDefaultScheduleConfig(): ScheduleConfig {\n return {\n timezone: 'America/Chicago',\n platforms: {\n linkedin: {\n slots: [\n { days: ['tue', 'wed'], time: '08:00', label: 'Morning thought leadership' },\n { days: ['tue', 'wed', 'thu'], time: '12:00', label: 'Lunch break engagement' },\n ],\n avoidDays: ['sat', 'sun'],\n },\n tiktok: {\n slots: [\n { days: ['tue', 'wed', 'thu'], time: '19:00', label: 'Prime entertainment hours' },\n { days: ['fri', 'sat'], time: '21:00', label: 'Weekend evening' },\n ],\n avoidDays: [],\n },\n instagram: {\n slots: [\n { days: ['tue', 'wed', 'thu'], time: '10:00', label: 'Morning scroll' },\n { days: ['wed', 'thu', 'fri'], time: '19:30', label: 'Evening couch time' },\n ],\n avoidDays: [],\n },\n youtube: {\n slots: [\n { days: ['fri'], time: '15:00', label: 'Afternoon pre-weekend' },\n { days: ['thu', 'fri'], time: '20:00', label: 'Prime evening viewing' },\n ],\n avoidDays: ['mon'],\n },\n twitter: {\n slots: [\n { days: ['mon', 'tue', 'wed', 'thu', 'fri'], time: '08:30', label: 'Morning news check' },\n { days: ['tue', 'wed', 'thu'], time: '12:00', label: 'Lunch scroll' },\n { days: ['mon', 'tue', 'wed', 'thu', 'fri'], time: '17:00', label: 'Commute home' },\n ],\n avoidDays: [],\n },\n },\n }\n}\n\nexport function validateScheduleConfig(config: unknown): ScheduleConfig {\n if (!config || typeof config !== 'object') {\n throw new Error('Schedule config must be a non-null object')\n }\n\n const cfg = config as Record<string, unknown>\n\n if (typeof cfg.timezone !== 'string' || cfg.timezone.trim() === '') {\n throw new Error('Schedule config \"timezone\" must be a non-empty string')\n }\n\n if (!cfg.platforms || typeof cfg.platforms !== 'object' || Array.isArray(cfg.platforms)) {\n throw new Error('Schedule config \"platforms\" must be an object')\n }\n\n const platforms = cfg.platforms as Record<string, unknown>\n const validated: ScheduleConfig = {\n timezone: cfg.timezone,\n platforms: {},\n }\n\n for (const [name, value] of Object.entries(platforms)) {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`Platform \"${name}\" must be an object`)\n }\n\n const plat = value as Record<string, unknown>\n\n if (!Array.isArray(plat.slots)) {\n throw new Error(`Platform \"${name}\" must have a \"slots\" array`)\n }\n\n if (!Array.isArray(plat.avoidDays)) {\n throw new Error(`Platform \"${name}\" must have an \"avoidDays\" array`)\n }\n\n for (const day of plat.avoidDays) {\n if (!VALID_DAYS.includes(day as DayOfWeek)) {\n throw new Error(`Platform \"${name}\" avoidDays contains invalid day \"${day}\". Valid: ${VALID_DAYS.join(', ')}`)\n }\n }\n\n const validatedSlots: TimeSlot[] = []\n for (let i = 0; i < plat.slots.length; i++) {\n const slot = plat.slots[i] as Record<string, unknown>\n\n if (!Array.isArray(slot.days) || slot.days.length === 0) {\n throw new Error(`Platform \"${name}\" slot ${i} must have a non-empty \"days\" array`)\n }\n\n for (const day of slot.days) {\n if (!VALID_DAYS.includes(day as DayOfWeek)) {\n throw new Error(`Platform \"${name}\" slot ${i} has invalid day \"${day}\". Valid: ${VALID_DAYS.join(', ')}`)\n }\n }\n\n if (typeof slot.time !== 'string' || !TIME_REGEX.test(slot.time)) {\n throw new Error(`Platform \"${name}\" slot ${i} \"time\" must match HH:MM format (00:00–23:59)`)\n }\n\n if (typeof slot.label !== 'string' || slot.label.trim() === '') {\n throw new Error(`Platform \"${name}\" slot ${i} must have a non-empty \"label\" string`)\n }\n\n validatedSlots.push({\n days: slot.days as DayOfWeek[],\n time: slot.time,\n label: slot.label,\n })\n }\n\n validated.platforms[name] = {\n slots: validatedSlots,\n avoidDays: plat.avoidDays as DayOfWeek[],\n }\n }\n\n return validated\n}\n\nexport async function loadScheduleConfig(configPath?: string): Promise<ScheduleConfig> {\n if (cachedConfig) return cachedConfig\n\n const filePath = configPath ?? join(process.cwd(), 'schedule.json')\n\n let raw: string\n try {\n raw = await readTextFile(filePath)\n } catch {\n logger.info(`No schedule.json found at ${filePath}, creating with defaults`)\n const defaults = getDefaultScheduleConfig()\n // Write directly with exclusive create flag for security\n try {\n await writeFileRaw(filePath, JSON.stringify(defaults, null, 2), { \n encoding: 'utf-8',\n flag: 'wx',\n mode: 0o600\n })\n } catch (err: any) {\n // If file was created by another process in a race, read it\n if (err.code === 'EEXIST') {\n const raw = await readTextFile(filePath)\n const parsed: unknown = JSON.parse(raw)\n cachedConfig = validateScheduleConfig(parsed)\n logger.info(`Loaded schedule config from ${filePath}`)\n return cachedConfig\n }\n throw err\n }\n cachedConfig = defaults\n return defaults\n }\n\n const parsed: unknown = JSON.parse(raw)\n cachedConfig = validateScheduleConfig(parsed)\n logger.info(`Loaded schedule config from ${filePath}`)\n return cachedConfig\n}\n\nexport function getPlatformSchedule(platform: string): PlatformSchedule | null {\n if (!cachedConfig) return null\n return cachedConfig.platforms[platform] ?? null\n}\n\nexport function clearScheduleCache(): void {\n cachedConfig = null\n}\n","import { createReadlineInterface } from '../core/cli.js'\nimport { writeTextFile, readTextFile, fileExists } from '../core/fileSystem.js'\nimport { join } from '../core/paths.js'\nimport { getFFmpegPath, getFFprobePath } from '../config/ffmpegResolver'\nimport { LateApiClient } from '../services/lateApi'\nimport { getDefaultScheduleConfig } from '../services/scheduleConfig'\n\nconst rl = createReadlineInterface({ input: process.stdin, output: process.stdout })\n\nfunction ask(question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => resolve(answer))\n })\n}\n\nexport async function runInit(): Promise<void> {\n // Gracefully handle Ctrl+C\n rl.on('close', () => {\n console.log('\\n')\n process.exit(0)\n })\n\n console.log('\\n🎬 Welcome to vidpipe setup!\\n')\n\n const envPath = join(process.cwd(), '.env')\n const envVars: Record<string, string> = {}\n\n // Load existing .env if present\n let existingEnv = ''\n try {\n existingEnv = await readTextFile(envPath)\n } catch {\n // No existing .env\n }\n\n // Parse existing env values for hints\n const existingVars: Record<string, string> = {}\n for (const line of existingEnv.split('\\n')) {\n const match = line.match(/^([A-Z_]+)=(.*)$/)\n if (match) existingVars[match[1]] = match[2]\n }\n\n // Step 1: FFmpeg\n console.log('Step 1/5: FFmpeg')\n try {\n const ffmpeg = getFFmpegPath()\n console.log(` ✅ FFmpeg found at: ${ffmpeg}`)\n } catch {\n console.log(' ❌ FFmpeg not found — install from https://ffmpeg.org/')\n }\n try {\n const ffprobe = getFFprobePath()\n console.log(` ✅ FFprobe found at: ${ffprobe}`)\n } catch {\n console.log(' ❌ FFprobe not found')\n }\n\n // Step 2: OpenAI\n console.log('\\nStep 2/5: OpenAI (Required for transcription)')\n const currentOpenAI = existingVars.OPENAI_API_KEY || process.env.OPENAI_API_KEY\n const hint = currentOpenAI ? ` (current: ${currentOpenAI.slice(0, 8)}...)` : ''\n const openaiKey = await ask(` ? OpenAI API key${hint}: `)\n if (openaiKey.trim()) {\n envVars.OPENAI_API_KEY = openaiKey.trim()\n console.log(' ✅ API key saved')\n } else if (currentOpenAI) {\n console.log(' ✅ Keeping current key')\n } else {\n console.log(' ⚠️ No key set — transcription will not work')\n }\n\n // Step 3: LLM Provider\n console.log('\\nStep 3/5: LLM Provider')\n const provider = await ask(' ? Provider [copilot/openai/claude] (copilot): ')\n envVars.LLM_PROVIDER = provider.trim() || 'copilot'\n console.log(` ✅ Using ${envVars.LLM_PROVIDER}`)\n\n // If claude, ask for ANTHROPIC_API_KEY\n if (envVars.LLM_PROVIDER === 'claude') {\n const claudeKey = await ask(' ? Anthropic API key: ')\n if (claudeKey.trim()) envVars.ANTHROPIC_API_KEY = claudeKey.trim()\n }\n\n // Step 4: Exa (optional)\n console.log('\\nStep 4/5: Web Search (Optional — enriches social posts)')\n const exaKey = await ask(' ? Exa API key (press Enter to skip): ')\n if (exaKey.trim()) {\n envVars.EXA_API_KEY = exaKey.trim()\n console.log(' ✅ Exa configured')\n } else {\n console.log(' ⏭️ Skipped')\n }\n\n // Step 5: Late API (optional)\n console.log('\\nStep 5/5: Social Publishing (Optional)')\n const setupLate = await ask(' ? Set up social media publishing? [y/N]: ')\n\n if (setupLate.toLowerCase() === 'y') {\n const lateKey = await ask(' ? Late API key (get one at https://getlate.dev): ')\n if (lateKey.trim()) {\n envVars.LATE_API_KEY = lateKey.trim()\n // Validate connection\n try {\n const client = new LateApiClient(lateKey.trim())\n const validation = await client.validateConnection()\n if (validation.valid) {\n console.log(` ✅ Connected to profile \"${validation.profileName}\"`)\n const accounts = await client.listAccounts()\n if (accounts.length > 0) {\n console.log(' Connected accounts:')\n for (const acc of accounts) {\n console.log(` ✅ ${acc.platform} — ${acc.username || acc.displayName}`)\n }\n }\n } else {\n console.log(` ❌ Connection failed: ${validation.error}`)\n }\n } catch (err) {\n console.log(` ⚠️ Could not validate key: ${err instanceof Error ? err.message : String(err)}`)\n }\n\n // Schedule.json\n const createSchedule = await ask(' ? Create default schedule.json? [Y/n]: ')\n if (createSchedule.toLowerCase() !== 'n') {\n const schedulePath = join(process.cwd(), 'schedule.json')\n if (await fileExists(schedulePath)) {\n console.log(' ✅ schedule.json already exists')\n } else {\n await writeTextFile(schedulePath, JSON.stringify(getDefaultScheduleConfig(), null, 2))\n console.log(' ✅ schedule.json created with optimal posting times')\n }\n }\n }\n } else {\n console.log(' ⏭️ Skipped')\n }\n\n // Write .env — merge new values with existing\n for (const [key, value] of Object.entries(envVars)) {\n const regex = new RegExp(`^${key}=.*$`, 'm')\n if (regex.test(existingEnv)) {\n existingEnv = existingEnv.replace(regex, `${key}=${value}`)\n } else {\n existingEnv += `\\n${key}=${value}`\n }\n }\n await writeTextFile(envPath, existingEnv.trim() + '\\n')\n\n console.log('\\n✅ Setup complete! Configuration saved to .env')\n console.log(' Run `vidpipe doctor` to verify everything is working.')\n console.log(' Run `vidpipe <video.mp4>` to process your first video.\\n')\n\n rl.close()\n}\n","// Re-export from core — this file exists for backward compatibility during migration\nexport { getFFmpegPath, getFFprobePath } from '../core/ffmpeg.js'\n","import { LateApiClient, type LatePost } from './lateApi'\nimport { loadScheduleConfig, type DayOfWeek } from './scheduleConfig'\nimport { getPublishedItems } from './postStore'\nimport logger from '../config/logger'\n\n/**\n * Normalize ISO datetime to milliseconds since epoch for collision detection.\n * Handles different ISO formats from Late API vs local queue.\n */\nfunction normalizeDateTime(isoString: string): number {\n return new Date(isoString).getTime()\n}\n\nconst CHUNK_DAYS = 14 // generate candidates in 14-day chunks\nconst MAX_LOOKAHEAD_DAYS = 730 // hard ceiling (~2 years)\n\ninterface BookedSlot {\n scheduledFor: string\n source: 'late' | 'local'\n postId?: string\n itemId?: string\n platform: string\n}\n\n/**\n * Get the UTC offset string (e.g. \"-06:00\") for a timezone on a given date.\n */\nfunction getTimezoneOffset(timezone: string, date: Date): string {\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n timeZoneName: 'longOffset',\n })\n const parts = formatter.formatToParts(date)\n const tzPart = parts.find(p => p.type === 'timeZoneName')\n // longOffset gives e.g. \"GMT-06:00\" or \"GMT+05:30\"\n const match = tzPart?.value?.match(/GMT([+-]\\d{2}:\\d{2})/)\n if (match) return match[1]\n // GMT with no offset means UTC\n if (tzPart?.value === 'GMT') return '+00:00'\n logger.warn(\n `Could not parse timezone offset for timezone \"${timezone}\" on date \"${date.toISOString()}\". ` +\n `Raw timeZoneName part: \"${tzPart?.value ?? 'undefined'}\". Falling back to UTC (+00:00).`,\n )\n return '+00:00'\n}\n\n/**\n * Build an ISO datetime string with timezone offset for a given date and time.\n */\nfunction buildSlotDatetime(date: Date, time: string, timezone: string): string {\n // Derive calendar date parts in the target timezone to avoid host-timezone skew\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n })\n const parts = formatter.formatToParts(date)\n const yearPart = parts.find(p => p.type === 'year')?.value\n const monthPart = parts.find(p => p.type === 'month')?.value\n const dayPart = parts.find(p => p.type === 'day')?.value\n\n const year = yearPart ?? String(date.getFullYear())\n const month = (monthPart ?? String(date.getMonth() + 1)).padStart(2, '0')\n const day = (dayPart ?? String(date.getDate())).padStart(2, '0')\n const offset = getTimezoneOffset(timezone, date)\n return `${year}-${month}-${day}T${time}:00${offset}`\n}\n\n/**\n * Get the day-of-week key for a Date in the given timezone.\n */\nfunction getDayOfWeekInTimezone(date: Date, timezone: string): DayOfWeek {\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n weekday: 'short',\n })\n const short = formatter.format(date).toLowerCase().slice(0, 3)\n const map: Record<string, DayOfWeek> = {\n sun: 'sun', mon: 'mon', tue: 'tue', wed: 'wed', thu: 'thu', fri: 'fri', sat: 'sat',\n }\n return map[short] ?? 'mon'\n}\n\n\n\n/**\n * Fetch scheduled posts from Late API, returning empty array on failure.\n */\nasync function fetchScheduledPostsSafe(platform?: string): Promise<LatePost[]> {\n try {\n const client = new LateApiClient()\n return await client.getScheduledPosts(platform)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.warn(`Late API unreachable, using local data only: ${msg}`)\n return []\n }\n}\n\n/**\n * Build the set of already-booked slots from Late API and local published items.\n */\nasync function buildBookedSlots(platform?: string): Promise<BookedSlot[]> {\n const [latePosts, publishedItems] = await Promise.all([\n fetchScheduledPostsSafe(platform),\n getPublishedItems(),\n ])\n\n const slots: BookedSlot[] = []\n\n for (const post of latePosts) {\n if (!post.scheduledFor) continue\n for (const p of post.platforms) {\n if (!platform || p.platform === platform) {\n slots.push({\n scheduledFor: post.scheduledFor,\n source: 'late',\n postId: post._id,\n platform: p.platform,\n })\n }\n }\n }\n\n for (const item of publishedItems) {\n if (platform && item.metadata.platform !== platform) continue\n if (!item.metadata.scheduledFor) continue\n slots.push({\n scheduledFor: item.metadata.scheduledFor,\n source: 'local',\n itemId: item.id,\n platform: item.metadata.platform,\n })\n }\n\n return slots\n}\n\n\n\n/**\n * Find the next available posting slot for a platform.\n *\n * Algorithm (generate-sort-filter):\n * 1. Load platform schedule config from schedule.json\n * 2. Build set of already-booked datetimes from Late API + local published items\n * 3. In 14-day chunks, generate candidate slot datetimes, sort, and check availability\n * 4. If no available slot in the current chunk, expand to the next chunk (up to ~2 years)\n * 5. Return the first candidate not already booked, or null if none found\n */\nexport async function findNextSlot(platform: string): Promise<string | null> {\n const config = await loadScheduleConfig()\n const platformConfig = config.platforms[platform]\n if (!platformConfig) {\n logger.warn(`No schedule config found for platform \"${String(platform).replace(/[\\r\\n]/g, '')}\"`)\n return null\n }\n\n const { timezone } = config\n const bookedSlots = await buildBookedSlots(platform)\n const bookedDatetimes = new Set(bookedSlots.map(s => normalizeDateTime(s.scheduledFor)))\n\n const now = new Date()\n let startOffset = 1\n\n while (startOffset <= MAX_LOOKAHEAD_DAYS) {\n const endOffset = Math.min(startOffset + CHUNK_DAYS - 1, MAX_LOOKAHEAD_DAYS)\n const candidates: string[] = []\n\n for (let dayOffset = startOffset; dayOffset <= endOffset; dayOffset++) {\n const candidateDate = new Date(now)\n candidateDate.setDate(candidateDate.getDate() + dayOffset)\n\n const dayOfWeek = getDayOfWeekInTimezone(candidateDate, timezone)\n if (platformConfig.avoidDays.includes(dayOfWeek)) continue\n\n for (const slot of platformConfig.slots) {\n if (!slot.days.includes(dayOfWeek)) continue\n candidates.push(buildSlotDatetime(candidateDate, slot.time, timezone))\n }\n }\n\n candidates.sort((a, b) => new Date(a).getTime() - new Date(b).getTime())\n\n const available = candidates.find(c => !bookedDatetimes.has(normalizeDateTime(c)))\n if (available) {\n logger.debug(`Found available slot for ${String(platform).replace(/[\\r\\n]/g, '')}: ${String(available).replace(/[\\r\\n]/g, '')}`)\n return available\n }\n\n startOffset = endOffset + 1\n }\n\n logger.warn(`No available slot found for \"${String(platform).replace(/[\\r\\n]/g, '')}\" within ${MAX_LOOKAHEAD_DAYS} days`)\n return null\n}\n\n/**\n * Get a calendar view of scheduled posts across all platforms.\n * Returns slots sorted by datetime.\n */\nexport async function getScheduleCalendar(\n startDate?: Date,\n endDate?: Date,\n): Promise<Array<{\n platform: string\n scheduledFor: string\n source: 'late' | 'local'\n postId?: string\n itemId?: string\n}>> {\n const slots = await buildBookedSlots()\n\n let filtered = slots.map(s => ({\n platform: s.platform,\n scheduledFor: s.scheduledFor,\n source: s.source,\n postId: s.postId,\n itemId: s.itemId,\n }))\n\n if (startDate) {\n const startMs = startDate.getTime()\n filtered = filtered.filter(s => new Date(s.scheduledFor).getTime() >= startMs)\n }\n if (endDate) {\n const endMs = endDate.getTime()\n filtered = filtered.filter(s => new Date(s.scheduledFor).getTime() <= endMs)\n }\n\n filtered.sort((a, b) => new Date(a.scheduledFor).getTime() - new Date(b.scheduledFor).getTime())\n return filtered\n}\n","import { getScheduleCalendar } from '../services/scheduler'\nimport { loadScheduleConfig } from '../services/scheduleConfig'\nimport { initConfig } from '../config/environment'\n\nexport interface ScheduleCommandOptions {\n platform?: string\n}\n\nexport async function runSchedule(options: ScheduleCommandOptions = {}): Promise<void> {\n initConfig()\n\n console.log('\\n📅 Posting Schedule\\n')\n\n // Load config to show configured time slots\n const config = await loadScheduleConfig()\n \n // Get upcoming scheduled posts\n const calendar = await getScheduleCalendar()\n\n // Filter by platform if specified\n const filtered = options.platform \n ? calendar.filter(s => s.platform === options.platform)\n : calendar\n\n if (filtered.length === 0) {\n console.log('No posts scheduled.')\n console.log('\\nRun `vidpipe review` to review and schedule pending posts.')\n return\n }\n\n // Group by date\n const byDate = new Map<string, typeof filtered>()\n for (const slot of filtered) {\n const date = new Date(slot.scheduledFor).toLocaleDateString('en-US', {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n })\n if (!byDate.has(date)) byDate.set(date, [])\n byDate.get(date)!.push(slot)\n }\n\n // Display\n for (const [date, slots] of byDate) {\n console.log(` ${date}`)\n for (const slot of slots) {\n const time = new Date(slot.scheduledFor).toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n })\n const source = slot.source === 'late' ? '🌐' : '📁'\n const icon = getPlatformIcon(slot.platform)\n console.log(` ${time} ${icon} ${slot.platform} ${source}`)\n }\n }\n\n console.log(`\\n 🌐 = scheduled in Late 📁 = published locally\\n`)\n}\n\nfunction getPlatformIcon(platform: string): string {\n const icons: Record<string, string> = {\n tiktok: '🎵',\n youtube: '▶️',\n instagram: '📸',\n linkedin: '💼',\n twitter: '🐦',\n }\n return icons[platform] || '📱'\n}\n","export { default as express } from 'express'\nexport type { Request, Response, NextFunction, Express, Router as ExpressRouter } from 'express'\nexport { Router } from 'express'\n","import { express } from '../core/http.js'\nimport { join, dirname, fileURLToPath } from '../core/paths.js'\nimport { createRouter } from './routes'\nimport { getConfig } from '../config/environment'\nimport logger from '../config/logger'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport interface ReviewServerOptions {\n port?: number\n}\n\nexport async function startReviewServer(options: ReviewServerOptions = {}): Promise<{ \n port: number\n close: () => Promise<void>\n}> {\n const app = express()\n const port = options.port || 3847\n\n // Middleware\n app.use(express.json())\n\n // API routes\n app.use(createRouter())\n\n // Serve media files from publish-queue and published directories\n const cfg = getConfig()\n const queueDir = join(cfg.OUTPUT_DIR, 'publish-queue')\n const publishedDir = join(cfg.OUTPUT_DIR, 'published')\n app.use('/media/queue', express.static(queueDir))\n app.use('/media/published', express.static(publishedDir))\n\n // Serve static frontend\n const publicDir = join(__dirname, 'public')\n app.use(express.static(publicDir))\n\n // SPA fallback — serve index.html for non-API routes\n app.get('/{*splat}', (req, res) => {\n if (!req.path.startsWith('/api/') && !req.path.startsWith('/media/')) {\n res.sendFile(join(publicDir, 'index.html'))\n }\n })\n\n // Start server with port retry logic\n return new Promise((resolve, reject) => {\n const tryPort = (p: number, attempts: number) => {\n const server = app.listen(p, '127.0.0.1', () => {\n logger.info(`Review server running at http://localhost:${p}`)\n\n // Track open connections so we can destroy them on shutdown\n const connections = new Set<import('net').Socket>()\n server.on('connection', (conn) => {\n connections.add(conn)\n conn.on('close', () => connections.delete(conn))\n })\n\n resolve({\n port: p,\n close: () => new Promise<void>((res) => {\n let done = false\n\n const finish = () => {\n if (done) return\n done = true\n res()\n }\n\n for (const conn of connections) conn.destroy()\n\n const timeout = setTimeout(() => {\n logger.warn('Timed out waiting for review server to close, forcing shutdown')\n finish()\n }, 2000)\n\n // Allow process to exit naturally even if timeout is pending\n timeout.unref()\n\n server.close(() => {\n clearTimeout(timeout)\n finish()\n })\n }),\n })\n })\n \n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && attempts < 5) {\n logger.warn(`Port ${p} in use, trying ${p + 1}...`)\n tryPort(p + 1, attempts + 1)\n } else {\n reject(err)\n }\n })\n }\n \n tryPort(port, 0)\n })\n}\n","import { fileExists } from '../core/fileSystem.js'\nimport { Router } from '../core/http.js'\nimport { getPendingItems, getGroupedPendingItems, getItem, updateItem, approveItem, rejectItem, approveBulk, type BulkApprovalResult } from '../services/postStore.js'\nimport { findNextSlot, getScheduleCalendar } from '../services/scheduler'\nimport { getAccountId } from '../services/accountMapping'\nimport { LateApiClient, type LateAccount, type LateProfile } from '../services/lateApi'\nimport { loadScheduleConfig } from '../services/scheduleConfig'\nimport { fromLatePlatform, normalizePlatformString } from '../types'\nimport logger from '../config/logger'\n\n// ── Simple in-memory cache (avoids repeated Late API calls) ────────────\nconst CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes\nconst cache = new Map<string, { data: unknown; expiry: number }>()\n\nfunction getCached<T>(key: string): T | undefined {\n const entry = cache.get(key)\n if (entry && entry.expiry > Date.now()) return entry.data as T\n cache.delete(key)\n return undefined\n}\n\nfunction setCache(key: string, data: unknown, ttl = CACHE_TTL_MS): void {\n cache.set(key, { data, expiry: Date.now() + ttl })\n}\n\nexport function createRouter(): Router {\n const router = Router()\n\n // GET /api/posts/pending — list all pending review items\n router.get('/api/posts/pending', async (req, res) => {\n const items = await getPendingItems()\n res.json({ items, total: items.length })\n })\n\n // GET /api/posts/grouped — list pending items grouped by video/clip\n router.get('/api/posts/grouped', async (req, res) => {\n const groups = await getGroupedPendingItems()\n res.json({ groups, total: groups.length })\n })\n\n // GET /api/init — combined endpoint for initial page load (1 request instead of 3)\n router.get('/api/init', async (req, res) => {\n const [groupsResult, accountsResult, profileResult] = await Promise.allSettled([\n getGroupedPendingItems(),\n (async () => {\n const cached = getCached<LateAccount[]>('accounts')\n if (cached) return cached\n const client = new LateApiClient()\n const accounts = await client.listAccounts()\n setCache('accounts', accounts)\n return accounts\n })(),\n (async () => {\n const cached = getCached<LateProfile | null>('profile')\n if (cached !== undefined) return cached\n const client = new LateApiClient()\n const profiles = await client.listProfiles()\n const profile = profiles[0] || null\n setCache('profile', profile)\n return profile\n })(),\n ])\n\n const groups = groupsResult.status === 'fulfilled' ? groupsResult.value : []\n const accounts = accountsResult.status === 'fulfilled' ? accountsResult.value : []\n const profile = profileResult.status === 'fulfilled' ? profileResult.value : null\n\n res.json({ groups, total: groups.length, accounts, profile })\n })\n\n // GET /api/posts/:id — get single post with full content\n router.get('/api/posts/:id', async (req, res) => {\n const item = await getItem(req.params.id)\n if (!item) return res.status(404).json({ error: 'Item not found' })\n res.json(item)\n })\n\n // POST /api/posts/:id/approve — smart-schedule + upload media + publish to Late\n router.post('/api/posts/:id/approve', async (req, res) => {\n try {\n const item = await getItem(req.params.id)\n if (!item) return res.status(404).json({ error: 'Item not found' })\n\n // Normalize platform — LLM may output \"x (twitter)\" but Late API and schedule use \"twitter\"\n const latePlatform = normalizePlatformString(item.metadata.platform)\n\n // 1. Find next available slot\n const slot = await findNextSlot(latePlatform)\n if (!slot) return res.status(409).json({ error: 'No available schedule slots in the current scheduling window' })\n\n // 2. Resolve account ID\n const platform = fromLatePlatform(latePlatform)\n const accountId = item.metadata.accountId || await getAccountId(platform)\n if (!accountId) return res.status(400).json({ error: `No Late account connected for ${latePlatform}` })\n\n // 3. Upload media if exists (fallback to source media when queue copy is missing)\n const client = new LateApiClient()\n let mediaItems: Array<{ type: 'image' | 'video'; url: string }> | undefined\n const effectiveMediaPath = item.mediaPath ?? item.metadata.sourceMediaPath\n if (effectiveMediaPath) {\n const mediaExists = await fileExists(effectiveMediaPath)\n if (mediaExists) {\n if (!item.mediaPath && item.metadata.sourceMediaPath) {\n logger.info(`Using source media fallback for ${String(item.id).replace(/[\\r\\n]/g, '')}: ${String(item.metadata.sourceMediaPath).replace(/[\\r\\n]/g, '')}`)\n }\n const upload = await client.uploadMedia(effectiveMediaPath)\n mediaItems = [{ type: upload.type, url: upload.url }]\n }\n }\n\n // 4. Create scheduled post in Late\n const isTikTok = latePlatform === 'tiktok'\n const tiktokSettings = isTikTok ? {\n privacy_level: 'PUBLIC_TO_EVERYONE',\n allow_comment: true,\n allow_duet: true,\n allow_stitch: true,\n content_preview_confirmed: true,\n express_consent_given: true,\n } : undefined\n\n const schedConfig = await loadScheduleConfig()\n const latePost = await client.createPost({\n content: item.postContent,\n platforms: [{ platform: latePlatform, accountId }],\n scheduledFor: slot,\n timezone: schedConfig.timezone,\n mediaItems,\n platformSpecificData: item.metadata.platformSpecificData,\n tiktokSettings,\n })\n\n // 5. Move to published (persist resolved accountId to metadata)\n await approveItem(req.params.id, {\n latePostId: latePost._id,\n scheduledFor: slot,\n publishedUrl: undefined,\n accountId,\n })\n\n res.json({ success: true, scheduledFor: slot, latePostId: latePost._id })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.error(`Approve failed for ${String(req.params.id).replace(/[\\r\\n]/g, '')}: ${String(msg).replace(/[\\r\\n]/g, '')}`)\n res.status(500).json({ error: msg })\n }\n })\n\n // POST /api/posts/bulk-approve — fire-and-forget: returns 202 immediately, processes in background\n router.post('/api/posts/bulk-approve', (req, res) => {\n const { itemIds } = req.body\n if (!Array.isArray(itemIds) || itemIds.length === 0) {\n return res.status(400).json({ error: 'itemIds must be a non-empty array' })\n }\n\n res.status(202).json({ accepted: true, count: itemIds.length })\n\n // Process in background — errors are logged, not sent to client\n ;(async () => {\n try {\n const client = new LateApiClient()\n const schedConfig = await loadScheduleConfig()\n const publishDataMap = new Map<string, { latePostId: string; scheduledFor: string; publishedUrl?: string; accountId?: string }>()\n\n for (const itemId of itemIds) {\n const item = await getItem(itemId)\n if (!item) {\n logger.warn(`Bulk approve: item ${String(itemId).replace(/[\\r\\n]/g, '')} not found`)\n continue\n }\n\n const latePlatform = normalizePlatformString(item.metadata.platform)\n\n const slot = await findNextSlot(latePlatform)\n if (!slot) {\n logger.warn(`Bulk approve: no slot available for ${latePlatform}`)\n continue\n }\n\n const platform = fromLatePlatform(latePlatform)\n const accountId = item.metadata.accountId || await getAccountId(platform)\n if (!accountId) {\n logger.warn(`Bulk approve: no account connected for ${latePlatform}`)\n continue\n }\n\n let mediaItems: Array<{ type: 'image' | 'video'; url: string }> | undefined\n const effectiveMediaPath = item.mediaPath ?? item.metadata.sourceMediaPath\n if (effectiveMediaPath) {\n const mediaExists = await fileExists(effectiveMediaPath)\n if (mediaExists) {\n const upload = await client.uploadMedia(effectiveMediaPath)\n mediaItems = [{ type: upload.type, url: upload.url }]\n }\n }\n\n const isTikTok = latePlatform === 'tiktok'\n const tiktokSettings = isTikTok ? {\n privacy_level: 'PUBLIC_TO_EVERYONE',\n allow_comment: true,\n allow_duet: true,\n allow_stitch: true,\n content_preview_confirmed: true,\n express_consent_given: true,\n } : undefined\n\n const latePost = await client.createPost({\n content: item.postContent,\n platforms: [{ platform: latePlatform, accountId }],\n scheduledFor: slot,\n timezone: schedConfig.timezone,\n mediaItems,\n platformSpecificData: item.metadata.platformSpecificData,\n tiktokSettings,\n })\n\n publishDataMap.set(itemId, {\n latePostId: latePost._id,\n scheduledFor: slot,\n publishedUrl: undefined,\n accountId,\n })\n }\n\n const results = await approveBulk(itemIds, publishDataMap)\n logger.info(`Bulk approve completed: ${results.length} of ${itemIds.length} scheduled`)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.error(`Bulk approve background failed: ${String(msg).replace(/[\\r\\n]/g, '')}`)\n }\n })()\n })\n\n // POST /api/posts/:id/reject — delete from queue\n router.post('/api/posts/:id/reject', async (req, res) => {\n try {\n await rejectItem(req.params.id)\n res.json({ success: true })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // POST /api/posts/bulk-reject — fire-and-forget: returns 202 immediately, deletes in background\n router.post('/api/posts/bulk-reject', (req, res) => {\n const { itemIds } = req.body\n if (!Array.isArray(itemIds) || itemIds.length === 0) {\n return res.status(400).json({ error: 'itemIds must be a non-empty array' })\n }\n\n res.status(202).json({ accepted: true, count: itemIds.length })\n\n // Process in background\n ;(async () => {\n let succeeded = 0\n for (const itemId of itemIds) {\n try {\n await rejectItem(itemId)\n succeeded++\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.error(`Bulk reject failed for ${String(itemId).replace(/[\\r\\n]/g, '')}: ${String(msg).replace(/[\\r\\n]/g, '')}`)\n }\n }\n logger.info(`Bulk reject completed: ${succeeded} of ${itemIds.length} removed`)\n })()\n })\n\n // PUT /api/posts/:id — edit post content\n router.put('/api/posts/:id', async (req, res) => {\n try {\n const { postContent, metadata } = req.body\n const updated = await updateItem(req.params.id, { postContent, metadata })\n if (!updated) return res.status(404).json({ error: 'Item not found' })\n res.json(updated)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // GET /api/schedule — current schedule calendar\n router.get('/api/schedule', async (req, res) => {\n try {\n const calendar = await getScheduleCalendar()\n res.json({ slots: calendar })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // GET /api/schedule/next-slot/:platform — calculate next available slot\n router.get('/api/schedule/next-slot/:platform', async (req, res) => {\n try {\n const normalized = normalizePlatformString(req.params.platform)\n const slot = await findNextSlot(normalized)\n res.json({ platform: normalized, nextSlot: slot })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n res.status(500).json({ error: msg })\n }\n })\n\n // GET /api/accounts — list connected Late accounts (cached)\n router.get('/api/accounts', async (req, res) => {\n try {\n const cached = getCached<LateAccount[]>('accounts')\n if (cached) return res.json({ accounts: cached })\n\n const client = new LateApiClient()\n const accounts = await client.listAccounts()\n setCache('accounts', accounts)\n res.json({ accounts })\n } catch (err) {\n res.status(500).json({ accounts: [], error: err instanceof Error ? err.message : 'Failed to fetch accounts' })\n }\n })\n\n // GET /api/profile — get Late profile info (cached)\n router.get('/api/profile', async (req, res) => {\n try {\n const cached = getCached<LateProfile | null>('profile')\n if (cached !== undefined) return res.json({ profile: cached })\n\n const client = new LateApiClient()\n const profiles = await client.listProfiles()\n const profile = profiles[0] || null\n setCache('profile', profile)\n res.json({ profile })\n } catch (err) {\n res.status(500).json({ profile: null, error: err instanceof Error ? err.message : 'Failed to fetch profile' })\n }\n })\n\n return router\n}\n","import { Platform } from '../types/index.js'\nimport { LateApiClient } from './lateApi.js'\nimport type { LateAccount } from './lateApi.js'\nimport logger from '../config/logger.js'\nimport { readTextFile, writeTextFile, removeFile } from '../core/fileSystem.js'\nimport { join, resolve, sep } from '../core/paths.js'\n\n// ── Cache ──────────────────────────────────────────────────────────────\n\nconst CACHE_FILE = '.vidpipe-cache.json'\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\n\ninterface AccountCache {\n accounts: Record<string, string> // platform -> accountId\n fetchedAt: string\n}\n\nlet memoryCache: AccountCache | null = null\n\n// ── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Map a vidpipe Platform to the Late API platform string.\n */\nfunction toLatePlatform(platform: Platform): string {\n return platform === Platform.X ? 'twitter' : platform\n}\n\nfunction cachePath(): string {\n return join(process.cwd(), CACHE_FILE)\n}\n\nfunction isCacheValid(cache: AccountCache): boolean {\n const fetchedAtTime = new Date(cache.fetchedAt).getTime()\n if (Number.isNaN(fetchedAtTime)) {\n logger.warn('Invalid fetchedAt in account cache; treating as stale', {\n fetchedAt: cache.fetchedAt,\n })\n return false\n }\n const age = Date.now() - fetchedAtTime\n return age < CACHE_TTL_MS\n}\n\nasync function readFileCache(): Promise<AccountCache | null> {\n try {\n const raw = await readTextFile(cachePath())\n const cache = JSON.parse(raw) as AccountCache\n if (cache.accounts && cache.fetchedAt && isCacheValid(cache)) {\n return cache\n }\n return null\n } catch {\n return null\n }\n}\n\nasync function writeFileCache(cache: AccountCache): Promise<void> {\n try {\n // Validate cache structure before writing to prevent malformed data\n if (!cache || typeof cache !== 'object' || !cache.accounts || !cache.fetchedAt) {\n logger.warn('Invalid cache structure, skipping write')\n return\n }\n // Sanitize by re-constructing with only expected fields\n const sanitized: AccountCache = {\n accounts: typeof cache.accounts === 'object' ? { ...cache.accounts } : {},\n fetchedAt: String(cache.fetchedAt),\n }\n // Validate HTTP-sourced account data before writing to cache (CodeQL js/http-to-file-access)\n for (const [platform, accountId] of Object.entries(sanitized.accounts)) {\n if (typeof platform !== 'string' || typeof accountId !== 'string' ||\n /[\\x00-\\x1f]/.test(platform) || /[\\x00-\\x1f]/.test(accountId)) {\n logger.warn('Invalid account mapping data from API, skipping cache write')\n return\n }\n }\n const resolvedCachePath = resolve(cachePath())\n if (!resolvedCachePath.startsWith(resolve(process.cwd()) + sep)) {\n throw new Error('Cache path outside working directory')\n }\n // lgtm[js/http-to-file-access] - Writing sanitized account cache is intended functionality with path validation\n await writeTextFile(resolvedCachePath, JSON.stringify(sanitized, null, 2))\n } catch (err) {\n logger.warn('Failed to write account cache file', { error: err })\n }\n}\n\nasync function fetchAndCache(): Promise<Record<string, string>> {\n const client = new LateApiClient()\n const accounts: LateAccount[] = await client.listAccounts()\n\n const mapping: Record<string, string> = {}\n for (const acct of accounts) {\n if (acct.isActive) {\n mapping[acct.platform] = acct._id\n }\n }\n\n const cache: AccountCache = {\n accounts: mapping,\n fetchedAt: new Date().toISOString(),\n }\n memoryCache = cache\n await writeFileCache(cache)\n\n logger.info('Refreshed Late account mappings', {\n platforms: Object.keys(mapping),\n })\n return mapping\n}\n\nasync function ensureMappings(): Promise<Record<string, string>> {\n // 1. In-memory cache\n if (memoryCache && isCacheValid(memoryCache)) {\n return memoryCache.accounts\n }\n\n // 2. File cache\n const fileCache = await readFileCache()\n if (fileCache) {\n memoryCache = fileCache\n return fileCache.accounts\n }\n\n // 3. Fetch from Late API\n try {\n return await fetchAndCache()\n } catch (err) {\n logger.error('Failed to fetch Late account mappings', { error: err })\n return {}\n }\n}\n\n// ── Public API ─────────────────────────────────────────────────────────\n\n/**\n * Get the Late account ID for a given platform.\n *\n * Resolution order:\n * 1. In-memory cache\n * 2. File cache (.vidpipe-cache.json)\n * 3. Fetch from Late API and cache\n *\n * Returns null if the platform is not connected.\n */\nexport async function getAccountId(\n platform: Platform,\n): Promise<string | null> {\n const mappings = await ensureMappings()\n const latePlatform = toLatePlatform(platform)\n return mappings[latePlatform] ?? null\n}\n\n/**\n * Get all account mappings (platform -> accountId).\n * Fetches from Late API if not cached.\n */\nexport async function getAllAccountMappings(): Promise<\n Record<string, string>\n> {\n return ensureMappings()\n}\n\n/**\n * Force refresh the account mappings from Late API.\n */\nexport async function refreshAccountMappings(): Promise<\n Record<string, string>\n> {\n memoryCache = null\n return fetchAndCache()\n}\n\n/**\n * Clear the account cache (both memory and file).\n */\nexport async function clearAccountCache(): Promise<void> {\n memoryCache = null\n try {\n await removeFile(cachePath())\n } catch {\n // File may not exist — that's fine\n }\n}\n"],"mappings":"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","names":["join","resolve","dirname","fileURLToPath","existsSync","default","resolve","path","envPath","init_logger","existsSync","config","require","resolve","text","createRequire","default","resolve","default","yFrom","yTo","xFrom","xTo","init_logger","resolve","init_logger","default","config","init_logger","init_logger","config","default","init_logger","config","init_logger","init_logger","config","init_logger","config","default","DEFAULT_MODEL","MAX_TOOL_ROUNDS","init_logger","config","default","init_logger","init_logger","resolve","init_logger","resolve","ffmpegPath","init_logger","getVideoDuration","init_logger","FONTS_DIR","resolve","ffmpegPath","init_logger","resolve","ffprobePath","default","ffmpegPath","init_logger","resolve","ffmpegPath","config","init_logger","SYSTEM_PROMPT","init_logger","SYSTEM_PROMPT","init_logger","config","init_logger","resolve","init_logger","fmtTime","buildTranscriptBlock","toYouTubeTimestamp","config","init_logger","SYSTEM_PROMPT","init_logger","config","resolve","init_logger","Platform","config","init_logger","SYSTEM_PROMPT","init_logger","config","buildSystemPrompt","init_logger","config","init_logger","config","resolve","init_logger","getVideoResolution","detectWebcamRegion","getConfig","config","analyzeVideoEditorial","analyzeVideoClipDirection","generateSummary","init_logger","config","resolve","transcribeVideo","removeDeadSilence","burnCaptions","ProducerAgent","generateShorts","generateMediumClips","generateChapters","init_logger","init_logger","init_logger","totalDuration","analyzeVideoClipDirection","init_logger","init_logger","init_logger","config","raw","parsed","require","config","resolve","envPath","ffprobe","init_logger","config","config","default","init_logger","toLatePlatform","cache","init_logger","CACHE_TTL_MS","accounts","profile","init_logger","__dirname","default","resolve","config"]}
|