vidpipe 1.3.4 → 1.3.5
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/README.md +4 -0
- package/dist/{index.js → cli.js} +10650 -7718
- package/dist/cli.js.map +1 -0
- package/dist/public/index.html +92 -4
- package/package.json +19 -8
- package/dist/index.js.map +0 -1
- /package/dist/{index.d.ts → cli.d.ts} +0 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/L1-infra/paths/paths.ts","../src/L1-infra/fileSystem/fileSystem.ts","../src/L1-infra/env/env.ts","../src/L1-infra/config/environment.ts","../src/L1-infra/logger/logger.ts","../src/L1-infra/logger/configLogger.ts","../src/L0-pure/pricing/pricing.ts","../src/L1-infra/ai/openai.ts","../src/L1-infra/ai/anthropic.ts","../src/L1-infra/ai/copilot.ts","../src/L2-clients/llm/ai.ts","../src/L2-clients/llm/CopilotProvider.ts","../src/L2-clients/llm/imageUtils.ts","../src/L2-clients/llm/OpenAIProvider.ts","../src/L2-clients/llm/ClaudeProvider.ts","../src/L2-clients/llm/index.ts","../src/L3-services/llm/providerFactory.ts","../src/L1-infra/config/modelConfig.ts","../src/L1-infra/ideaStore/ideaStore.ts","../src/L3-services/ideation/ideaService.ts","../src/L1-infra/cli/cli.ts","../src/L7-app/cli.ts","../src/L1-infra/watcher/watcher.ts","../src/L7-app/fileWatcher.ts","../src/L6-pipeline/pipeline.ts","../src/L5-assets/Asset.ts","../src/L5-assets/VideoAsset.ts","../src/L1-infra/ffmpeg/ffmpeg.ts","../src/L1-infra/process/process.ts","../src/L2-clients/ffmpeg/ffmpeg.ts","../src/L2-clients/ffmpeg/audioExtraction.ts","../src/L2-clients/ffmpeg/clipExtraction.ts","../src/L2-clients/ffmpeg/singlePassEdit.ts","../src/L2-clients/ffmpeg/captionBurning.ts","../src/L2-clients/ffmpeg/silenceDetection.ts","../src/L2-clients/ffmpeg/frameCapture.ts","../src/L2-clients/ffmpeg/aspectRatio.ts","../src/L2-clients/ffmpeg/faceDetection.ts","../src/L0-pure/media/media.ts","../src/L2-clients/ffmpeg/overlayCompositing.ts","../src/L3-services/videoOperations/videoOperations.ts","../src/L0-pure/captions/captionGenerator.ts","../src/L1-infra/ai/gemini.ts","../src/L2-clients/gemini/geminiClient.ts","../src/L3-services/costTracking/costTracker.ts","../src/L3-services/videoAnalysis/videoAnalysis.ts","../src/L3-services/transcription/transcription.ts","../src/L2-clients/whisper/whisperClient.ts","../src/L1-infra/config/brand.ts","../src/L3-services/captionGeneration/captionGeneration.ts","../src/L1-infra/image/image.ts","../src/L2-clients/openai/imageGeneration.ts","../src/L1-infra/http/httpClient.ts","../src/L3-services/imageGeneration/imageGeneration.ts","../src/L4-agents/analysisServiceBridge.ts","../src/L5-assets/SocialPostAsset.ts","../src/L5-assets/TextAsset.ts","../src/L5-assets/ShortVideoAsset.ts","../src/L0-pure/types/index.ts","../src/L5-assets/MediumClipAsset.ts","../src/L5-assets/MainVideoAsset.ts","../src/L0-pure/text/text.ts","../src/L4-agents/SilenceRemovalAgent.ts","../src/L3-services/llm/index.ts","../src/L4-agents/BaseAgent.ts","../src/L0-pure/ideaContext/ideaContext.ts","../src/L4-agents/ShortsAgent.ts","../src/L4-agents/MediumVideoAgent.ts","../src/L4-agents/ChapterAgent.ts","../src/L4-agents/ProducerAgent.ts","../src/L4-agents/SummaryAgent.ts","../src/L4-agents/SocialMediaAgent.ts","../src/L4-agents/BlogAgent.ts","../src/L3-services/processingState/processingState.ts","../src/L3-services/gitOperations/gitOperations.ts","../src/L3-services/queueBuilder/queueBuilder.ts","../src/L3-services/socialPosting/platformContentStrategy.ts","../src/L3-services/postStore/postStore.ts","../src/L4-agents/pipelineServiceBridge.ts","../src/L4-agents/GraphicsAgent.ts","../src/L5-assets/visualEnhancement.ts","../src/L2-clients/late/lateApi.ts","../src/L1-infra/http/network.ts","../src/L3-services/lateApi/lateApiService.ts","../src/L3-services/scheduler/scheduler.ts","../src/L2-clients/scheduleStore/scheduleStore.ts","../src/L3-services/scheduler/scheduleConfig.ts","../src/L3-services/scheduler/realign.ts","../src/L4-agents/ScheduleAgent.ts","../src/L4-agents/IdeationAgent.ts","../src/L5-assets/pipelineServices.ts","../src/L7-app/commands/doctor.ts","../src/L7-app/commands/init.ts","../src/L3-services/diagnostics/diagnostics.ts","../src/L7-app/commands/schedule.ts","../src/L7-app/commands/realign.ts","../src/L7-app/commands/chat.ts","../src/L1-infra/readline/readline.ts","../src/L6-pipeline/scheduleChat.ts","../src/L7-app/commands/ideate.ts","../src/L6-pipeline/ideation.ts","../src/L1-infra/http/http.ts","../src/L7-app/review/server.ts","../src/L7-app/review/routes.ts","../src/L7-app/review/approvalQueue.ts","../src/L3-services/socialPosting/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, resolve, normalize } from '../paths/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// ── Path Validation ────────────────────────────────────────────\n\n/**\n * Validate that a file path doesn't contain path traversal sequences.\n * Uses resolve() to normalize the path and ensure it's safe.\n */\nfunction validateFilePath(filePath: string): string {\n // Resolve to absolute path to detect and prevent path traversal\n const resolvedPath = resolve(filePath)\n // normalize() has already been called by resolve()\n return resolvedPath\n}\n\n/**\n * Sanitize text content before writing to prevent null-byte injection\n * and other potential security issues.\n */\nfunction sanitizeTextContent(content: string): string {\n // Remove null bytes which can cause security issues in some contexts\n return content.replace(/\\0/g, '')\n}\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 const safePath = validateFilePath(filePath)\n await fsp.mkdir(dirname(safePath), { recursive: true })\n await fsp.writeFile(safePath, 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 const safePath = validateFilePath(filePath)\n const safeContent = sanitizeTextContent(content)\n await fsp.mkdir(dirname(safePath), { recursive: true })\n await fsp.writeFile(safePath, safeContent, { 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 const safePath = validateFilePath(filePath)\n const safeContent = sanitizeTextContent(content)\n mkdirSync(dirname(safePath), { recursive: true })\n writeFileSync(safePath, safeContent, { 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// ── Binary I/O ─────────────────────────────────────────────────\n\n/** Read a file as a raw Buffer (no encoding). */\nexport async function readFileBuffer(filePath: string): Promise<Buffer> {\n return fsp.readFile(filePath)\n}\n\n/** Write a Buffer to a file (binary-safe). */\nexport async function writeFileBuffer(filePath: string, data: Buffer): Promise<void> {\n await fsp.writeFile(filePath, data)\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 '../paths/paths.js'\nimport { fileExistsSync } from '../fileSystem/fileSystem.js'\nimport { loadEnvFile } from '../env/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 YOUTUBE_API_KEY: string\n PERPLEXITY_API_KEY: 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 SKIP_VISUAL_ENHANCEMENT: boolean\n LATE_API_KEY: string\n LATE_PROFILE_ID: string\n SKIP_SOCIAL_PUBLISH: boolean\n GEMINI_API_KEY: string\n GEMINI_MODEL: string\n}\n\nexport interface CLIOptions {\n watchDir?: string\n outputDir?: string\n openaiKey?: string\n exaKey?: string\n youtubeKey?: string\n perplexityKey?: 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 visualEnhancement?: 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 YOUTUBE_API_KEY: cli.youtubeKey || process.env.YOUTUBE_API_KEY || '',\n PERPLEXITY_API_KEY: cli.perplexityKey || process.env.PERPLEXITY_API_KEY || '',\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 SKIP_VISUAL_ENHANCEMENT: cli.visualEnhancement === 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 GEMINI_MODEL: process.env.GEMINI_MODEL || 'gemini-2.5-pro',\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'\nimport { join } from '../paths/paths.js'\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 LOG_FORMAT = winston.format.combine(\n winston.format.timestamp(),\n winston.format.printf(({ timestamp, level, message }) => {\n return `${timestamp} [${level.toUpperCase()}]: ${message}`\n })\n)\n\nconst logger = winston.createLogger({\n level: 'info',\n format: LOG_FORMAT,\n transports: [new winston.transports.Console()],\n})\n\nexport function setVerbose(): void {\n logger.level = 'debug'\n}\n\n/** Suppress console output for interactive modes (chat). Restores with setChatMode(false). */\nconst consoleTransport = logger.transports[0] as winston.transports.ConsoleTransportInstance\nlet savedLevel: string | undefined\n\nexport function setChatMode(enabled: boolean): void {\n if (enabled) {\n savedLevel = consoleTransport.level\n consoleTransport.silent = true\n } else {\n consoleTransport.silent = false\n if (savedLevel !== undefined) {\n consoleTransport.level = savedLevel\n savedLevel = undefined\n }\n }\n}\n\n// ── Pipe stack ───────────────────────────────────────────────────────────────\n\nconst pipeStack: winston.transports.FileTransportInstance[] = []\n\n/**\n * Push a file transport that pipes all log output to `{folder}/pipeline.log`.\n * Supports nesting — each pushPipe adds a new file, popPipe removes the most recent.\n */\nexport function pushPipe(folder: string): void {\n const transport = new winston.transports.File({\n filename: join(folder, 'pipeline.log'),\n format: LOG_FORMAT,\n })\n pipeStack.push(transport)\n logger.add(transport)\n}\n\n/** Remove the most recently pushed file transport. */\nexport function popPipe(): void {\n const transport = pipeStack.pop()\n if (transport) {\n logger.remove(transport)\n }\n}\n\nexport default logger\n","// Re-export from core — this file exists for backward compatibility during migration\nexport { default, sanitizeForLog, setVerbose, setChatMode, pushPipe, popPipe } from './logger.js'\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","export { default as OpenAI } from 'openai'\nexport type {\n ChatCompletion,\n ChatCompletionContentPart,\n ChatCompletionMessageParam,\n ChatCompletionTool,\n} from 'openai/resources/chat/completions.js'\n","export { default as Anthropic } from '@anthropic-ai/sdk'\nexport 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'\n","export { CopilotClient, CopilotSession } from '@github/copilot-sdk'\nexport type { SessionEvent } from '@github/copilot-sdk'\n","import { OpenAI as _OpenAI } from '../../L1-infra/ai/openai.js'\nimport { Anthropic as _Anthropic } from '../../L1-infra/ai/anthropic.js'\nimport { CopilotClient as _CopilotClient, CopilotSession as _CopilotSession } from '../../L1-infra/ai/copilot.js'\n\nexport type { ChatCompletionMessageParam, ChatCompletionTool, ChatCompletion } from '../../L1-infra/ai/openai.js'\nexport type { SessionEvent } from '../../L1-infra/ai/copilot.js'\n\nexport function createOpenAI(\n ...args: ConstructorParameters<typeof _OpenAI>\n): InstanceType<typeof _OpenAI> {\n return new _OpenAI(...args)\n}\n\nexport function createAnthropic(\n ...args: ConstructorParameters<typeof _Anthropic>\n): InstanceType<typeof _Anthropic> {\n return new _Anthropic(...args)\n}\n\nexport function createCopilotClient(\n ...args: ConstructorParameters<typeof _CopilotClient>\n): InstanceType<typeof _CopilotClient> {\n return new _CopilotClient(...args)\n}\n\nexport function createCopilotSession(\n ...args: ConstructorParameters<typeof _CopilotSession>\n): InstanceType<typeof _CopilotSession> {\n return new _CopilotSession(...args)\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 { createCopilotClient } from './ai.js'\nimport type { SessionEvent } from './ai.js'\nimport type { CopilotClient, CopilotSession } from '../../L1-infra/ai/copilot.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n TokenUsage,\n CostInfo,\n QuotaSnapshot,\n ToolCall,\n ProviderEvent,\n ProviderEventType,\n UserInputRequest,\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 = createCopilotClient({ 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 onUserInputRequest: config.onUserInputRequest\n ? (request: UserInputRequest) => config.onUserInputRequest!(request)\n : undefined,\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 // Track tool completions to handle partial success on SDK errors\n private toolsCompleted = 0\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 this.toolsCompleted = 0\n\n let response: { data?: { content?: string } } | undefined\n let sdkError: Error | undefined\n\n try {\n response = await this.session.sendAndWait(\n { prompt: message },\n this.timeoutMs,\n )\n } catch (err) {\n sdkError = err instanceof Error ? err : new Error(String(err))\n \n // Handle the known \"missing finish_reason\" bug in @github/copilot SDK\n // This happens when the streaming response ends without proper termination\n // but tools may have already completed successfully\n if (sdkError.message.includes('missing finish_reason')) {\n if (this.toolsCompleted > 0) {\n logger.warn(`[CopilotProvider] SDK error after ${this.toolsCompleted} tool calls completed - treating as success`)\n // Return partial success - tools ran, just the final message was lost\n } else {\n // No tools completed, this is a real failure - rethrow\n throw sdkError\n }\n } else {\n throw sdkError\n }\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 // Add timeout to session.destroy() - it can hang on the same SDK bug\n const DESTROY_TIMEOUT_MS = 5000\n try {\n await Promise.race([\n this.session.destroy(),\n new Promise<void>((_, reject) => \n setTimeout(() => reject(new Error('session.destroy() timed out')), DESTROY_TIMEOUT_MS)\n ),\n ])\n } catch (err) {\n // Log but don't rethrow - the session may be in a bad state but we still want to clean up\n logger.warn(`[CopilotProvider] Session destroy failed: ${err instanceof Error ? err.message : String(err)}`)\n }\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.toolsCompleted++\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 { readFileBuffer } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { extname } from '../../L1-infra/paths/paths.js'\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 = 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 readFileBuffer(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 { createOpenAI } from './ai.js';\nimport type OpenAI from 'openai';\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 '../../L0-pure/pricing/pricing.js';\nimport logger from '../../L1-infra/logger/configLogger.js';\nimport { getConfig } from '../../L1-infra/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 = createOpenAI(); // 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 { createAnthropic } from './ai.js'\nimport type Anthropic from '@anthropic-ai/sdk'\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 '../../L0-pure/pricing/pricing.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { getConfig } from '../../L1-infra/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 = createAnthropic()\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 '../../L1-infra/logger/configLogger.js';\nimport { getConfig } from '../../L1-infra/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 * L3 wrapper around the L2 LLM provider factory.\n *\n * Wraps getProvider(), resetProvider(), and getProviderName() so that\n * L4 agents import from L3 (allowed) instead of L2 (blocked by layer rules).\n */\nimport {\n getProvider as _getProvider,\n resetProvider as _resetProvider,\n getProviderName as _getProviderName,\n} from '../../L2-clients/llm/index.js'\nimport type { ProviderName } from '../../L2-clients/llm/types.js'\nimport type { LLMProvider } from '../../L2-clients/llm/types.js'\n\nexport function getProvider(name?: ProviderName): LLMProvider {\n return _getProvider(name)\n}\n\nexport async function resetProvider(): Promise<void> {\n return _resetProvider()\n}\n\nexport function getProviderName(): ProviderName {\n return _getProviderName()\n}\n\n// Re-export types that L4 agents need\nexport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n ToolWithHandler,\n TokenUsage,\n CostInfo,\n QuotaSnapshot,\n ProviderEvent,\n ProviderEventType,\n ProviderName,\n ToolDefinition,\n ToolCall,\n ToolHandler,\n ImageContent,\n ImageMimeType,\n MCPServerConfig,\n MCPLocalServerConfig,\n MCPRemoteServerConfig,\n UserInputRequest,\n UserInputResponse,\n UserInputHandler,\n} from '../../L2-clients/llm/types.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 IdeationAgent: 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 { Idea, IdeaPublishRecord, IdeaStatus, Platform } from '../../L0-pure/types/index.js'\nimport {\n ensureDirectory,\n fileExists,\n listDirectory,\n readJsonFile,\n removeFile,\n writeJsonFile,\n} from '../fileSystem/fileSystem.js'\nimport logger from '../logger/configLogger.js'\nimport { join, resolve } from '../paths/paths.js'\n\nconst DEFAULT_IDEAS_DIR = join(resolve('.'), 'ideas')\nconst IDEA_FILE_EXTENSION = '.json'\nconst ideaStatuses = new Set(['draft', 'ready', 'recorded', 'published'])\nconst ideaClipTypes = new Set(['video', 'short', 'medium-clip'])\nconst ideaPlatforms = new Set(['tiktok', 'youtube', 'instagram', 'linkedin', 'x'])\n\nfunction resolveIdeasDir(dir?: string): string {\n return dir ? resolve(dir) : DEFAULT_IDEAS_DIR\n}\n\nfunction getErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction validateIdeaId(id: string): string {\n if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(id)) {\n throw new Error(`Invalid idea ID: ${id}`)\n }\n return id\n}\n\nfunction getIdeaFilePath(id: string, dir?: string): string {\n return join(resolveIdeasDir(dir), `${validateIdeaId(id)}${IDEA_FILE_EXTENSION}`)\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\nfunction isIdeaStatus(value: unknown): value is IdeaStatus {\n return typeof value === 'string' && ideaStatuses.has(value)\n}\n\nfunction isPlatform(value: unknown): value is Platform {\n return typeof value === 'string' && ideaPlatforms.has(value)\n}\n\nfunction isPlatformArray(value: unknown): value is Platform[] {\n return Array.isArray(value) && value.every((item) => isPlatform(item))\n}\n\nfunction isValidIsoDateString(value: unknown): value is string {\n return typeof value === 'string' && !Number.isNaN(new Date(value).getTime())\n}\n\nfunction isIdeaPublishRecord(value: unknown): value is IdeaPublishRecord {\n return isRecord(value)\n && typeof value.queueItemId === 'string'\n && typeof value.publishedAt === 'string'\n && typeof value.clipType === 'string'\n && ideaClipTypes.has(value.clipType)\n && isPlatform(value.platform)\n && (value.publishedUrl === undefined || typeof value.publishedUrl === 'string')\n}\n\nfunction isIdea(value: unknown): value is Idea {\n return isRecord(value)\n && typeof value.id === 'string'\n && typeof value.topic === 'string'\n && typeof value.hook === 'string'\n && typeof value.audience === 'string'\n && typeof value.keyTakeaway === 'string'\n && isStringArray(value.talkingPoints)\n && isPlatformArray(value.platforms)\n && isIdeaStatus(value.status)\n && isStringArray(value.tags)\n && typeof value.createdAt === 'string'\n && typeof value.updatedAt === 'string'\n && isValidIsoDateString(value.publishBy)\n && (value.sourceVideoSlug === undefined || typeof value.sourceVideoSlug === 'string')\n && (value.trendContext === undefined || typeof value.trendContext === 'string')\n && (value.publishedContent === undefined\n || (Array.isArray(value.publishedContent) && value.publishedContent.every((item) => isIdeaPublishRecord(item))))\n}\n\n/**\n * Read all ideas from the ideas directory.\n * Each idea is a separate `{id}.json` file.\n * Returns empty array if directory doesn't exist.\n */\nexport async function readIdeaBank(dir?: string): Promise<Idea[]> {\n const ideasDir = resolveIdeasDir(dir)\n const ideaIds = await listIdeaIds(ideasDir)\n const ideas = await Promise.all(\n ideaIds.map(async (id) => {\n try {\n return await readIdea(id, ideasDir)\n } catch (error: unknown) {\n logger.warn(`Skipping invalid idea file ${id}${IDEA_FILE_EXTENSION}: ${getErrorMessage(error)}`)\n return null\n }\n }),\n )\n\n return ideas.filter((idea): idea is Idea => idea !== null)\n}\n\n/**\n * Write a single idea to the ideas directory as `{idea.id}.json`.\n * Creates the directory if it doesn't exist.\n * Updates the `updatedAt` timestamp.\n */\nexport async function writeIdea(idea: Idea, dir?: string): Promise<void> {\n const ideasDir = resolveIdeasDir(dir)\n const ideaPath = getIdeaFilePath(idea.id, ideasDir)\n const now = new Date().toISOString()\n\n if (!isValidIsoDateString(idea.publishBy)) {\n throw new Error(`Invalid publishBy date: ${idea.publishBy}`)\n }\n\n idea.updatedAt = now\n\n await ensureDirectory(ideasDir)\n await writeJsonFile(ideaPath, idea)\n}\n\n/**\n * Read a single idea by ID.\n * Returns null if the idea file doesn't exist.\n */\nexport async function readIdea(id: string, dir?: string): Promise<Idea | null> {\n const ideaPath = getIdeaFilePath(id, dir)\n\n if (!(await fileExists(ideaPath))) {\n return null\n }\n\n const idea = await readJsonFile<unknown>(ideaPath)\n if (!isIdea(idea)) {\n throw new Error(`File does not contain a valid idea: ${ideaPath}`)\n }\n\n return idea\n}\n\n/**\n * List all idea IDs in the directory (without reading file contents).\n * Returns empty array if directory doesn't exist.\n */\nexport async function listIdeaIds(dir?: string): Promise<string[]> {\n const ideasDir = resolveIdeasDir(dir)\n\n if (!(await fileExists(ideasDir))) {\n return []\n }\n\n const entries = await listDirectory(ideasDir)\n return entries\n .filter((entry) => entry.toLowerCase().endsWith(IDEA_FILE_EXTENSION))\n .map((entry) => entry.slice(0, -IDEA_FILE_EXTENSION.length))\n}\n\n/**\n * Delete an idea file by ID.\n * No-op if the file doesn't exist.\n */\nexport async function deleteIdea(id: string, dir?: string): Promise<void> {\n const ideaPath = getIdeaFilePath(id, dir)\n\n try {\n await removeFile(ideaPath)\n } catch (error: unknown) {\n logger.error(`Failed to delete idea ${id}: ${getErrorMessage(error)}`)\n throw new Error(`Failed to delete idea ${id}: ${getErrorMessage(error)}`)\n }\n}\n","import type { Idea, IdeaPublishRecord, Transcript } from '../../L0-pure/types/index.js'\nimport { getModelForAgent } from '../../L1-infra/config/modelConfig.js'\nimport { readIdeaBank, writeIdea, readIdea, listIdeaIds } from '../../L1-infra/ideaStore/ideaStore.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { getProvider } from '../llm/providerFactory.js'\n\nconst IDEA_MATCH_AGENT_NAME = 'IdeaService'\nconst IDEA_MATCH_LIMIT = 3\nconst TRANSCRIPT_SUMMARY_LIMIT = 500\nconst MATCH_IDEAS_SYSTEM_PROMPT = 'You are a content matching assistant. Given a video transcript summary and a list of content ideas, identify which ideas (if any) the video covers. Return a JSON array of matching idea IDs, ordered by relevance. Return empty array if no ideas match. Only return ideas where the video clearly covers the topic.'\n\ninterface IdeaSummary {\n id: string\n topic: string\n hook: string\n keyTakeaway: string\n}\n\n/**\n * Resolve idea IDs to full Idea objects.\n * Throws if any ID is not found.\n */\nexport async function getIdeasByIds(ids: string[], dir?: string): Promise<Idea[]> {\n return Promise.all(\n ids.map(async (id) => {\n const idea = await readIdea(id, dir)\n if (!idea) {\n throw new Error(`Idea not found: ${id}`)\n }\n return idea\n }),\n )\n}\n\n/**\n * Return all ideas with status 'ready'.\n */\nexport async function getReadyIdeas(dir?: string): Promise<Idea[]> {\n const ideas = await readIdeaBank(dir)\n return ideas.filter((idea) => idea.status === 'ready')\n}\n\n/**\n * Update idea status to 'recorded' and link to video slug.\n * Sets sourceVideoSlug and updates status.\n */\nexport async function markRecorded(id: string, videoSlug: string, dir?: string): Promise<void> {\n const idea = await readIdea(id, dir)\n if (!idea) {\n throw new Error(`Idea not found: ${id}`)\n }\n\n idea.status = 'recorded'\n idea.sourceVideoSlug = videoSlug\n await writeIdea(idea, dir)\n}\n\n/**\n * Append a publish record to the idea and transition status to 'published'.\n * The idea transitions to 'published' on first publish record.\n */\nexport async function markPublished(id: string, record: IdeaPublishRecord, dir?: string): Promise<void> {\n const idea = await readIdea(id, dir)\n if (!idea) {\n throw new Error(`Idea not found: ${id}`)\n }\n\n idea.publishedContent = [...(idea.publishedContent ?? []), record]\n idea.status = 'published'\n await writeIdea(idea, dir)\n}\n\n/**\n * Auto-match ideas to a transcript using LLM.\n * Sends transcript summary + idea bank to LLM, returns top 1-3 matching ideas.\n * Returns empty array if no ideas match or if matching fails.\n * Only considers ideas with status 'ready'.\n */\nexport async function matchIdeasToTranscript(\n transcript: Transcript,\n ideas?: Idea[],\n dir?: string,\n): Promise<Idea[]> {\n try {\n const readyIdeas = (ideas ?? await readIdeaBank(dir)).filter((idea) => idea.status === 'ready')\n if (readyIdeas.length === 0) {\n return []\n }\n\n const provider = getProvider()\n if (!provider.isAvailable()) {\n logger.warn('[IdeaService] LLM provider unavailable for idea matching')\n return []\n }\n\n const transcriptSummary = transcript.text.slice(0, TRANSCRIPT_SUMMARY_LIMIT).trim()\n const readyIdeaIds = new Set(readyIdeas.map((idea) => idea.id))\n const readyIdeasById = new Map(readyIdeas.map((idea) => [idea.id, idea]))\n const ideaSummaries = readyIdeas.map<IdeaSummary>((idea) => ({\n id: idea.id,\n topic: idea.topic,\n hook: idea.hook,\n keyTakeaway: idea.keyTakeaway,\n }))\n const knownIdeaIds = new Set(\n ideas ? readyIdeaIds : await listIdeaIds(dir),\n )\n\n const session = await provider.createSession({\n systemPrompt: MATCH_IDEAS_SYSTEM_PROMPT,\n tools: [],\n streaming: false,\n model: getModelForAgent(IDEA_MATCH_AGENT_NAME),\n })\n\n try {\n const response = await session.sendAndWait(buildIdeaMatchPrompt(transcriptSummary, ideaSummaries))\n const matchedIds = parseMatchedIdeaIds(response.content, knownIdeaIds)\n .filter((id) => readyIdeaIds.has(id))\n .slice(0, IDEA_MATCH_LIMIT)\n\n if (matchedIds.length === 0) {\n return []\n }\n\n if (ideas) {\n return matchedIds.flatMap((id) => {\n const matchedIdea = readyIdeasById.get(id)\n return matchedIdea ? [matchedIdea] : []\n })\n }\n\n return await getIdeasByIds(matchedIds, dir)\n } finally {\n await session.close().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`[IdeaService] Failed to close idea matching session: ${message}`)\n })\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn(`[IdeaService] Failed to match ideas to transcript: ${message}`)\n return []\n }\n}\n\nfunction buildIdeaMatchPrompt(transcriptSummary: string, ideas: IdeaSummary[]): string {\n return [\n 'Transcript summary:',\n transcriptSummary || '(empty transcript)',\n '',\n 'Ideas:',\n JSON.stringify(ideas, null, 2),\n '',\n `Return up to ${IDEA_MATCH_LIMIT} idea IDs as a JSON array.`,\n ].join('\\n')\n}\n\nfunction parseMatchedIdeaIds(rawContent: string, knownIdeaIds: ReadonlySet<string>): string[] {\n const parsed = JSON.parse(rawContent) as unknown\n if (!Array.isArray(parsed)) {\n throw new Error('Idea match response was not a JSON array')\n }\n\n const matchedIds = parsed.filter((value): value is string => typeof value === 'string')\n return Array.from(new Set(matchedIds.filter((id) => knownIdeaIds.has(id))))\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 '../L1-infra/cli/cli.js'\nimport { initConfig, validateRequiredKeys, getConfig } from '../L1-infra/config/environment'\nimport type { CLIOptions } from '../L1-infra/config/environment'\nimport { FileWatcher } from './fileWatcher'\nimport { processVideoSafe } from '../L6-pipeline/pipeline'\nimport logger, { setVerbose } from '../L1-infra/logger/configLogger'\nimport { runDoctor } from './commands/doctor'\nimport { runInit } from './commands/init'\nimport { runSchedule } from './commands/schedule'\nimport { runRealign } from './commands/realign'\nimport { runChat } from './commands/chat'\nimport { runIdeate } from './commands/ideate'\nimport { startReviewServer } from './review/server'\nimport { openUrl } from '../L1-infra/cli/cli.js'\nimport { readTextFileSync, listDirectorySync } from '../L1-infra/fileSystem/fileSystem.js'\nimport { projectRoot, join, resolve, extname } from '../L1-infra/paths/paths.js'\nimport { isCompleted, getUnprocessed, getVideoStatus } from '../L3-services/processingState/processingState.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('realign')\n .description('Realign all Late scheduled, cancelled, and failed posts to match schedule.json slots')\n .option('--platform <name>', 'Filter by platform (tiktok, youtube, instagram, linkedin, twitter)')\n .option('--dry-run', 'Preview changes without updating posts')\n .action(async (opts) => {\n await runRealign({ platform: opts.platform, dryRun: opts.dryRun })\n process.exit(0)\n })\n\nprogram\n .command('chat')\n .description('Interactive chat session with the schedule management agent')\n .action(async () => {\n await runChat()\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\nprogram\n .command('ideate')\n .description('Generate AI-powered content ideas using trend research')\n .option('--topics <topics>', 'Comma-separated seed topics')\n .option('--count <n>', 'Number of ideas to generate (default: 5)', '5')\n .option('--output <dir>', 'Ideas directory (default: ./ideas)')\n .option('--brand <path>', 'Brand config path (default: ./brand.json)')\n .option('--list', 'List existing ideas instead of generating')\n .option('--status <status>', 'Filter by status when listing (draft|ready|recorded|published)')\n .action(async (opts) => {\n initConfig()\n await runIdeate(opts)\n process.exit(0)\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('--youtube-key <key>', 'YouTube API key (default: env YOUTUBE_API_KEY)')\n .option('--perplexity-key <key>', 'Perplexity API key (default: env PERPLEXITY_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-visual-enhancement', 'Skip visual enhancement (AI image overlays)')\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('--ideas <ids>', 'Comma-separated idea IDs to link to this video')\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 youtubeKey: opts.youtubeKey,\n perplexityKey: opts.perplexityKey,\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 visualEnhancement: opts.visualEnhancement,\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 // Resolve ideas if --ideas flag is provided\n let ideas: import('../L0-pure/types/index.js').Idea[] | undefined\n if (opts.ideas) {\n const { getIdeasByIds } = await import('../L3-services/ideation/ideaService.js')\n const ideaIds = (opts.ideas as string).split(',').map((id: string) => id.trim()).filter(Boolean)\n try {\n ideas = await getIdeasByIds(ideaIds)\n logger.info(`Linked ${ideas.length} idea(s): ${ideas.map(i => i.topic).join(', ')}`)\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.error(`Failed to resolve ideas: ${msg}`)\n process.exit(1)\n }\n }\n\n // Direct file mode\n if (videoPath) {\n const resolvedPath = resolve(videoPath)\n logger.info(`Processing single video: ${resolvedPath}`)\n await processVideoSafe(resolvedPath, ideas)\n\n // Mark ideas as recorded\n if (ideas && ideas.length > 0) {\n try {\n const { markRecorded } = await import('../L3-services/ideation/ideaService.js')\n const slug = resolvedPath.replace(/\\\\/g, '/').split('/').pop()?.replace(/\\.(mp4|mov|webm|avi|mkv)$/i, '') || ''\n for (const idea of ideas) {\n await markRecorded(idea.id, slug)\n }\n logger.info(`Marked ${ideas.length} idea(s) as recorded`)\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.warn(`Failed to mark ideas as recorded: ${msg}`)\n }\n }\n\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, ideas)\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', async (filePath: string) => {\n // Dedup: skip videos already completed\n const filename = filePath.replace(/\\\\/g, '/').split('/').pop() ?? ''\n const slug = filename.replace(/\\.(mp4|mov|webm|avi|mkv)$/i, '')\n if (slug && await isCompleted(slug)) {\n logger.info(`Skipping already-processed video: ${filePath}`)\n return\n }\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 // Startup reconciliation: scan watch folder for videos not yet tracked\n try {\n const watchFiles = listDirectorySync(config.WATCH_FOLDER)\n for (const file of watchFiles) {\n const ext = extname(file).toLowerCase()\n if (!['.mp4', '.mov', '.webm', '.avi', '.mkv'].includes(ext)) continue\n const filePath = join(config.WATCH_FOLDER, file)\n const slug = file.replace(/\\.(mp4|mov|webm|avi|mkv)$/i, '')\n const status = await getVideoStatus(slug)\n if (!status || status.status === 'failed' || status.status === 'pending') {\n if (!queue.includes(filePath)) {\n queue.push(filePath)\n logger.info(`Startup scan: queued ${slug}${status ? ` (was ${status.status})` : ' (new)'}`)\n }\n }\n }\n } catch (err) {\n logger.warn(`Could not scan watch folder on startup: ${err instanceof Error ? err.message : String(err)}`)\n }\n\n // Also re-queue any videos tracked as unprocessed (pending/failed) from previous runs\n const unprocessed = await getUnprocessed()\n for (const [slug, state] of Object.entries(unprocessed)) {\n if (!queue.includes(state.sourcePath)) {\n queue.push(state.sourcePath)\n logger.info(`Re-queued from state: ${slug} (${state.status})`)\n }\n }\n\n if (queue.length > 0) {\n logger.info(`Startup: ${queue.length} video(s) queued for processing`)\n processQueue().catch(err => logger.error('Queue processing error:', err))\n }\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 '../L1-infra/watcher/watcher.js'\nimport { getConfig } from '../L1-infra/config/environment'\nimport { EventEmitter } from '../L1-infra/watcher/watcher.js'\nimport { join, extname } from '../L1-infra/paths/paths.js'\nimport { fileExistsSync, ensureDirectorySync, getFileStatsSync, listDirectorySync } from '../L1-infra/fileSystem/fileSystem.js'\nimport logger from '../L1-infra/logger/configLogger'\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, basename } from '../L1-infra/paths/paths.js'\nimport { writeTextFile } from '../L1-infra/fileSystem/fileSystem.js'\nimport logger, { pushPipe, popPipe } from '../L1-infra/logger/configLogger'\nimport { getConfig } from '../L1-infra/config/environment'\nimport { MainVideoAsset } from '../L5-assets/MainVideoAsset.js'\nimport { costTracker, markPending, markProcessing, markCompleted, markFailed } from '../L5-assets/pipelineServices.js'\nimport type { CostReport } from '../L5-assets/pipelineServices.js'\nimport type {\n Transcript,\n VideoSummary,\n ShortClip,\n MediumClip,\n SocialPost,\n StageResult,\n PipelineResult,\n PipelineStage,\n Chapter,\n Idea,\n} from '../L0-pure/types/index'\nimport { PipelineStage as Stage } from '../L0-pure/types/index'\nimport type { ShortVideoAsset } from '../L5-assets/ShortVideoAsset.js'\nimport type { MediumClipAsset } from '../L5-assets/MediumClipAsset.js'\nimport type { CaptionFiles } from '../L5-assets/VideoAsset.js'\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 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 words: seg.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 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 flow\n * Each asset method (getTranscript, getEditedVideo, etc.) handles its own:\n * - Disk cache check (load from file if exists)\n * - Generation (call agent/service if needed)\n * - File writing (save result to disk)\n *\n * The pipeline orchestrates the order and provides timing/error isolation via runStage().\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, ideas?: Idea[]): Promise<PipelineResult> {\n const pipelineStart = Date.now()\n const stageResults: StageResult[] = []\n const cfg = getConfig()\n\n costTracker.reset()\n\n // Helper: set cost-tracking stage before running\n function trackStage<T>(stage: PipelineStage, fn: () => Promise<T>): Promise<T | undefined> {\n costTracker.setStage(stage)\n return runStage(stage, fn, stageResults)\n }\n\n logger.info(`Pipeline starting for: ${videoPath}`)\n\n // 1. Ingestion — required for all subsequent stages\n const asset = await trackStage<MainVideoAsset>(Stage.Ingestion, () => MainVideoAsset.ingest(videoPath))\n if (!asset) {\n const totalDuration = Date.now() - pipelineStart\n logger.error('Ingestion failed — cannot proceed without video metadata')\n return {\n video: { originalPath: videoPath, repoPath: '', videoDir: '', slug: '', filename: '', duration: 0, size: 0, createdAt: new Date() },\n transcript: undefined,\n editedVideoPath: undefined,\n captions: undefined,\n captionedVideoPath: undefined,\n summary: undefined,\n shorts: [],\n mediumClips: [],\n socialPosts: [],\n blogPost: undefined,\n stageResults,\n totalDuration,\n }\n }\n\n const video = await asset.toVideoFile()\n pushPipe(video.videoDir)\n\n // Set editorial direction from ideas (if provided)\n if (ideas && ideas.length > 0) {\n asset.setIdeas(ideas)\n logger.info(`Pipeline using ${ideas.length} idea(s) for editorial direction`)\n }\n\n try {\n // 2. Transcription — asset handles disk check + Whisper call + file write\n const transcript = await trackStage<Transcript>(Stage.Transcription, () => asset.getTranscript())\n\n // 3. Silence Removal — asset handles edited video generation\n let editedVideoPath: string | undefined\n if (!cfg.SKIP_SILENCE_REMOVAL) {\n editedVideoPath = await trackStage<string>(Stage.SilenceRemoval, () => asset.getEditedVideo())\n }\n\n // 3.5. Visual Enhancement — asset handles overlay generation + compositing\n let enhancedVideoPath: string | undefined\n if (!cfg.SKIP_VISUAL_ENHANCEMENT) {\n enhancedVideoPath = await trackStage<string>(Stage.VisualEnhancement, () => asset.getEnhancedVideo())\n }\n\n // 4. Captions — asset handles transcript → SRT/VTT/ASS generation\n let captions: CaptionFiles | undefined\n if (!cfg.SKIP_CAPTIONS) {\n captions = await trackStage<CaptionFiles>(Stage.Captions, () => asset.getCaptions())\n }\n\n // 5. Caption Burn — asset handles burning captions into video\n let captionedVideoPath: string | undefined\n if (!cfg.SKIP_CAPTIONS) {\n captionedVideoPath = await trackStage<string>(Stage.CaptionBurn, () => asset.getCaptionedVideo())\n }\n\n // 6. Shorts — asset handles clip planning, extraction, caption burning\n let shorts: ShortClip[] = []\n if (!cfg.SKIP_SHORTS) {\n const shortAssets = await trackStage<ShortVideoAsset[]>(Stage.Shorts, () => asset.getShorts()) ?? []\n shorts = shortAssets.map(s => s.clip)\n }\n\n // 7. Medium Clips — asset handles clip planning, extraction, transitions\n let mediumClips: MediumClip[] = []\n if (!cfg.SKIP_MEDIUM_CLIPS) {\n const mediumAssets = await trackStage<MediumClipAsset[]>(Stage.MediumClips, () => asset.getMediumClips()) ?? []\n mediumClips = mediumAssets.map(m => m.clip)\n }\n\n // 8. Chapters — asset handles topic boundary detection\n const chapters = await trackStage<Chapter[]>(Stage.Chapters, () => asset.getChapters())\n\n // 9. Summary — asset handles README generation (after shorts/chapters for references)\n const summary = await trackStage<VideoSummary>(Stage.Summary, () => asset.getSummary())\n\n // 10. Social Media — asset handles platform-specific post generation\n let socialPosts: SocialPost[] = []\n if (!cfg.SKIP_SOCIAL) {\n const mainPosts = await trackStage<SocialPost[]>(Stage.SocialMedia, () => asset.getSocialPosts()) ?? []\n socialPosts.push(...mainPosts)\n\n // 11. Short Posts — generate social posts for each short clip\n if (shorts.length > 0) {\n await trackStage<void>(Stage.ShortPosts, async () => {\n for (const short of shorts) {\n const posts = await asset.generateShortPostsData(short, await asset.getTranscript(), undefined, summary ?? undefined)\n socialPosts.push(...posts)\n }\n })\n }\n\n // 12. Medium Clip Posts — generate social posts for each medium clip\n if (mediumClips.length > 0) {\n await trackStage<void>(Stage.MediumClipPosts, async () => {\n for (const clip of mediumClips) {\n const posts = await asset.generateMediumClipPostsData(clip, undefined, summary ?? undefined)\n socialPosts.push(...posts)\n }\n })\n }\n }\n\n // 13. Queue Build — asset handles publish-queue/ population\n if (!cfg.SKIP_SOCIAL_PUBLISH && socialPosts.length > 0) {\n await trackStage<void>(Stage.QueueBuild, () => asset.buildQueue(shorts, mediumClips, socialPosts, captionedVideoPath))\n }\n\n // 14. Blog — asset handles blog post generation\n const blogPost = await trackStage<string>(Stage.Blog, () => asset.getBlog())\n\n // 15. Git — asset handles commit and push\n if (!cfg.SKIP_GIT) {\n await trackStage<void>(Stage.GitPush, () => asset.commitAndPushChanges())\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 enhancedVideoPath,\n captions: captions ? [captions.srt, captions.vtt, captions.ass] : undefined,\n captionedVideoPath,\n summary,\n chapters,\n shorts,\n mediumClips,\n socialPosts,\n blogPost,\n stageResults,\n totalDuration,\n }\n } finally {\n popPipe()\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, ideas?: Idea[]): Promise<PipelineResult | null> {\n // Derive slug from filename for state tracking (same logic as MainVideoAsset.ingest)\n const filename = basename(videoPath)\n const slug = filename.replace(/\\.(mp4|mov|webm|avi|mkv)$/i, '')\n await markPending(slug, videoPath)\n await markProcessing(slug)\n\n try {\n const result = await processVideo(videoPath, ideas)\n await markCompleted(slug)\n return result\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`Pipeline failed with uncaught error: ${message}`)\n await markFailed(slug, 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 * - `isComplete()` checks if the completion marker exists (generation finished successfully)\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 * getCompletionMarkerPath(): string {\n * return `${this.transcriptPath}.complete`\n * }\n *\n * async exists(): Promise<boolean> {\n * return fs.existsSync(this.transcriptPath)\n * }\n *\n * async getResult(opts?: AssetOptions): Promise<Transcript> {\n * if (opts?.force) {\n * await this.clearCompletion()\n * }\n * if (await this.isComplete()) {\n * return this.loadFromDisk()\n * }\n * const result = await this.transcribe()\n * await this.markComplete()\n * return result\n * }\n * }\n * ```\n */\n\nimport { fileExists, writeTextFile, removeFile } from '../L1-infra/fileSystem/fileSystem.js'\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 path where the completion marker file should be written.\n * Each asset defines its own marker location (typically `${primaryOutputPath}.complete`).\n *\n * @returns Absolute path to the .complete marker file\n */\n abstract getCompletionMarkerPath(): string\n\n /**\n * Get the asset result, computing it if necessary.\n *\n * Implementations should check `opts.force` and `isComplete()` 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 * Check if the completion marker file exists.\n * This confirms the full generation pipeline finished successfully.\n *\n * Note: `exists()` checks if the primary output file is present,\n * while `isComplete()` checks if generation finished successfully.\n * Both may be needed for full validation.\n *\n * @returns true if the completion marker exists\n */\n async isComplete(): Promise<boolean> {\n return fileExists(this.getCompletionMarkerPath())\n }\n\n /**\n * Write the completion marker file with the current ISO timestamp.\n * Call this after successful generation to mark the asset as complete.\n */\n protected async markComplete(): Promise<void> {\n await writeTextFile(this.getCompletionMarkerPath(), new Date().toISOString())\n }\n\n /**\n * Delete the completion marker file.\n * Silently ignores if the file doesn't exist.\n * Call this when forcing regeneration.\n */\n protected async clearCompletion(): Promise<void> {\n await removeFile(this.getCompletionMarkerPath())\n }\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 '../L1-infra/paths/paths.js'\nimport { fileExists, readJsonFile, readTextFile, writeJsonFile, ensureDirectory, writeTextFile } from '../L1-infra/fileSystem/fileSystem.js'\nimport { ffprobe, getVideoResolution, detectWebcamRegion } from '../L4-agents/videoServiceBridge.js'\nimport { generateSRT, generateVTT, generateStyledASS } from '../L0-pure/captions/captionGenerator.js'\nimport { analyzeVideoEditorial, analyzeVideoClipDirection, generateImage } from '../L4-agents/analysisServiceBridge.js'\nimport type { Transcript, Chapter, VideoLayout, WebcamRegion, ScreenRegion } from '../L0-pure/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 /**\n * Get the path to the completion marker file.\n * For video assets, this is {videoDir}/.complete\n */\n getCompletionMarkerPath(): string {\n return join(this.videoDir, '.complete')\n }\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 /** Path to generated cover image */\n get coverImagePath(): string {\n return join(this.videoDir, 'cover.png')\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 { 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('../L1-infra/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 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('../L1-infra/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 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 // ── Cover Image ─────────────────────────────────────────────────────────────\n\n /**\n * Generate an eye-catching cover image for this video using DALL-E.\n * The image captures the essence of the given post content.\n *\n * @param postContent - The social media post text to base the image on\n * @returns Path to the generated PNG image\n */\n async generateCoverImage(postContent: string): Promise<string> {\n // Check disk cache first\n if (await fileExists(this.coverImagePath)) {\n return this.coverImagePath\n }\n\n const prompt = buildCoverImagePrompt(postContent)\n\n await generateImage(prompt, this.coverImagePath, {\n size: '1024x1024',\n quality: 'high',\n })\n\n return this.coverImagePath\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\nfunction buildCoverImagePrompt(postContent: string): string {\n const essence = postContent.substring(0, 500)\n\n return `Create a professional, eye-catching social media cover image for a tech content post. The image should visually represent the following topic:\n\n\"${essence}\"\n\nStyle requirements:\n- Modern, clean design with bold visual elements\n- Tech-focused aesthetic (code elements, circuit patterns, or abstract tech visuals)\n- Vibrant colors that stand out in a social media feed\n- No text or words in the image — purely visual\n- Professional quality suitable for LinkedIn, YouTube, or blog headers\n- 1:1 square aspect ratio composition`\n}\n","export { default as fluentFfmpeg } from 'fluent-ffmpeg'\nexport type { FfmpegCommand, FfprobeData } from 'fluent-ffmpeg'\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 { fluentFfmpeg as ffmpegLib } from '../../L1-infra/ffmpeg/ffmpeg.js'\nimport { createModuleRequire } from '../../L1-infra/process/process.js'\nimport { fileExistsSync } from '../../L1-infra/fileSystem/fileSystem.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { getConfig } from '../../L1-infra/config/environment.js'\n\nconst require = createModuleRequire(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 && fileExistsSync(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 && fileExistsSync(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 '../../L1-infra/ffmpeg/ffmpeg.js'\n","import { createFFmpeg, ffprobe } from './ffmpeg.js'\nimport { ensureDirectory, getFileStats } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { dirname, extname } from '../../L1-infra/paths/paths.js'\nimport logger from '../../L1-infra/logger/configLogger'\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","import { createFFmpeg, getFFmpegPath, getFFprobePath } from './ffmpeg.js'\nimport { execFileRaw } from '../../L1-infra/process/process.js'\nimport { ensureDirectory, writeTextFile, closeFileDescriptor, tmp } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { dirname, join } from '../../L1-infra/paths/paths.js'\n\nimport logger from '../../L1-infra/logger/configLogger'\nimport { ShortSegment } from '../../L0-pure/types/index'\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 '-pix_fmt', 'yuv420p',\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 '../../L1-infra/process/process.js'\nimport { copyFile, listDirectory, removeFile, removeDirectory, makeTempDir } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join, fontsDir } from '../../L1-infra/paths/paths.js'\nimport { getFFmpegPath } from './ffmpeg.js'\nimport logger from '../../L1-infra/logger/configLogger'\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 { execFileRaw } from '../../L1-infra/process/process.js'\nimport { ensureDirectory, copyFile, listDirectory, removeFile, removeDirectory, makeTempDir, renameFile } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { dirname, join, fontsDir } from '../../L1-infra/paths/paths.js'\nimport { getFFmpegPath } from './ffmpeg.js'\nimport logger from '../../L1-infra/logger/configLogger'\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 } from './ffmpeg.js'\nimport logger from '../../L1-infra/logger/configLogger'\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 { createFFmpeg } from './ffmpeg.js'\nimport { ensureDirectory } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { dirname, join } from '../../L1-infra/paths/paths.js'\nimport logger from '../../L1-infra/logger/configLogger'\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 { execFileRaw } from '../../L1-infra/process/process.js'\nimport { ensureDirectory, copyFile } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { dirname, join } from '../../L1-infra/paths/paths.js'\nimport { getFFmpegPath } from './ffmpeg.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { detectWebcamRegion, getVideoResolution, type WebcamRegion } from './faceDetection.js'\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 webcamOverride?: WebcamRegion | null,\n): Promise<string> {\n const { label, targetW, screenH, camH, fallbackRatio } = config\n const outputDir = dirname(outputPath)\n await ensureDirectory(outputDir)\n\n const webcam = webcamOverride !== undefined ? webcamOverride : 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 // Add a small margin (2% of width) to ensure the webcam overlay is fully excluded\n // even when face detection bounding boxes aren't pixel-perfect\n const margin = Math.round(resolution.width * 0.02)\n let screenCropX: number\n let screenCropW: number\n if (webcam.position === 'top-right' || webcam.position === 'bottom-right') {\n screenCropX = 0\n screenCropW = Math.max(0, webcam.x - margin)\n } else {\n screenCropX = webcam.x + webcam.width + margin\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 webcamOverride?: WebcamRegion | null,\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 }, webcamOverride)\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 webcamOverride?: WebcamRegion | null,\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 }, webcamOverride)\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 webcamOverride?: WebcamRegion | null,\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 }, webcamOverride)\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 /** Pre-detected webcam region from the main video's layout.json.\n * When provided, smart converters skip per-clip webcam detection. */\n webcamOverride?: WebcamRegion | null\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, options.webcamOverride)\n } else if (ratio === '1:1') {\n await convertToSquareSmart(inputPath, outPath, options.webcamOverride)\n } else if (ratio === '4:5') {\n await convertToFeedSmart(inputPath, outPath, options.webcamOverride)\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 { execFileRaw } from '../../L1-infra/process/process.js'\nimport { fileExistsSync, listDirectory, removeFile, removeDirectory, makeTempDir } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join, modelsDir } from '../../L1-infra/paths/paths.js'\nimport { sharp, ort } from '../../L0-pure/media/media.js'\nimport { getFFmpegPath, getFFprobePath } from './ffmpeg.js'\nimport logger from '../../L1-infra/logger/configLogger'\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 = 15\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/**\n * Adaptive retry thresholds — when the primary threshold fails (e.g. dark\n * room + dark IDE → low contrast), retry with progressively lower thresholds\n * to catch subtler edges.\n */\nconst REFINE_RETRY_THRESHOLDS = [2.0, 1.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 * Minimum webcam width in original-resolution pixels to accept.\n * Anything smaller (e.g. 208px) is clearly a face bounding box, not an overlay.\n */\nconst MIN_WEBCAM_WIDTH_PX = 300\n/**\n * Minimum webcam height in original-resolution pixels to accept.\n * Catches edge-refinement failures that find a spurious horizontal edge near the\n * frame bottom, producing impossibly flat regions (e.g. 814×77).\n */\nconst MIN_WEBCAM_HEIGHT_PX = 200\n/**\n * Maximum width:height ratio for a plausible webcam overlay.\n * Real webcam overlays range from ~1:2 portrait to ~16:9 landscape (≈1.78).\n * Anything wider than 3:1 is a refinement artifact (e.g. a taskbar edge).\n */\nconst MAX_WEBCAM_ASPECT_RATIO = 3.0\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 // Cap sample count for short videos — need at least 1s between samples\n const effectiveSamples = Math.min(SAMPLE_FRAMES, Math.max(1, Math.floor(duration) - 1))\n const interval = Math.max(1, Math.floor(duration / (effectiveSamples + 1)))\n\n const timestamps: number[] = []\n for (let i = 1; i <= effectiveSamples; i++) {\n const ts = i * interval\n if (ts < duration) timestamps.push(ts)\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 minEdgeDiff: number = REFINE_MIN_EDGE_DIFF,\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, minEdgeDiff)\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, minEdgeDiff)\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 with adaptive thresholds for pixel-accurate webcam overlay boundaries\n // Primary threshold catches sharp borders; retries with lower thresholds catch\n // subtle edges (e.g. dark room webcam against dark IDE — similar brightness on both sides)\n let refined: { x: number; y: number; width: number; height: number } | null = null\n refined = await refineBoundingBox(framePaths, bestPosition, REFINE_MIN_EDGE_DIFF)\n if (!refined) {\n for (const threshold of REFINE_RETRY_THRESHOLDS) {\n logger.info(`[FaceDetection] Retrying edge refinement with threshold=${threshold}`)\n refined = await refineBoundingBox(framePaths, bestPosition, threshold)\n if (refined) break\n }\n }\n\n const scaleX = resolution.width / MODEL_WIDTH\n const scaleY = resolution.height / MODEL_HEIGHT\n\n let origX = 0, origY = 0, origW = 0, origH = 0\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\n // Sanity check: reject if the refined result is implausibly sized\n const refinedAR = origW / origH\n if (origW < MIN_WEBCAM_WIDTH_PX || origH < MIN_WEBCAM_HEIGHT_PX || refinedAR > MAX_WEBCAM_ASPECT_RATIO) {\n logger.info(\n `[FaceDetection] Refined region implausible (${origW}x${origH}px, AR=${refinedAR.toFixed(1)}), using proportional fallback`,\n )\n refined = null\n }\n }\n\n if (!refined) {\n // Proportional fallback: estimate webcam as ~33% of frame width, positioned\n // in the detected corner. This is more reliable than the old 1.4x face\n // expansion which produced face-sized boxes (208px) in low-contrast scenarios.\n const webcamWidthFrac = 0.33\n const webcamHeightFrac = 0.28\n origW = Math.round(resolution.width * webcamWidthFrac)\n origH = Math.round(resolution.height * webcamHeightFrac)\n\n const isRight = bestPosition.includes('right')\n const isBottom = bestPosition.includes('bottom')\n origX = isRight ? resolution.width - origW : 0\n origY = isBottom ? resolution.height - origH : 0\n\n logger.info(\n `[FaceDetection] Using proportional fallback: (${origX},${origY}) ${origW}x${origH}`,\n )\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","export { default as sharp } from 'sharp'\nexport type { Sharp, Metadata as SharpMetadata } from 'sharp'\nexport * as ort from 'onnxruntime-node'\n","import { execFileRaw } from '../../L1-infra/process/process.js'\nimport { getFFmpegPath } from './ffmpeg.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport type { GeneratedOverlay, OverlayRegion } from '../../L0-pure/types/index.js'\n\n/**\n * Get FFmpeg overlay position expressions for a given region.\n * Returns expressions using FFmpeg's built-in variables (main_w, main_h, overlay_w, overlay_h).\n */\nexport function getOverlayPosition(\n region: OverlayRegion,\n margin: number,\n): { x: string; y: string } {\n const m = String(margin)\n\n switch (region) {\n case 'top-left':\n return { x: m, y: m }\n case 'top-right':\n return { x: `(main_w-overlay_w-${m})`, y: m }\n case 'bottom-left':\n return { x: m, y: `(main_h-overlay_h-${m})` }\n case 'bottom-right':\n return { x: `(main_w-overlay_w-${m})`, y: `(main_h-overlay_h-${m})` }\n case 'center-right':\n return { x: `(main_w-overlay_w-${m})`, y: `((main_h-overlay_h)/2)` }\n case 'center-left':\n return { x: m, y: `((main_h-overlay_h)/2)` }\n }\n}\n\n/**\n * Build FFmpeg filter_complex for image overlay compositing.\n * Pure function — no I/O, easy to test.\n *\n * @param overlays - Generated overlays with position and timing info\n * @param videoWidth - Source video width in pixels\n * @param videoHeight - Source video height in pixels\n * @returns FFmpeg filter_complex string\n */\nexport function buildOverlayFilterComplex(\n overlays: readonly GeneratedOverlay[],\n videoWidth: number,\n videoHeight: number,\n): string {\n const margin = Math.round(videoWidth * 0.05)\n const filters: string[] = []\n\n for (let i = 0; i < overlays.length; i++) {\n const overlay = overlays[i]\n const inputIdx = i + 1 // 0 is the video\n const overlayWidth = Math.round(videoWidth * overlay.opportunity.placement.sizePercent / 100)\n const start = overlay.opportunity.timestampStart\n const end = overlay.opportunity.timestampEnd\n\n // Scale the image (input is looped via -loop 1 args, so it has a timeline)\n filters.push(`[${inputIdx}:v]scale=${overlayWidth}:-1,format=rgba[img_${i}]`)\n\n // Overlay with enable window — image appears/disappears at the specified timestamps\n const prev = i === 0 ? '[0:v]' : `[out_${i - 1}]`\n const isLast = i === overlays.length - 1\n const out = isLast ? '[overlaid]' : `[out_${i}]`\n const pos = getOverlayPosition(overlay.opportunity.placement.region, margin)\n filters.push(\n `${prev}[img_${i}]overlay=x=${pos.x}:y=${pos.y}:enable='between(t,${start},${end})':format=auto${out}`,\n )\n }\n\n // Convert back to yuv420p — overlay with RGBA images produces yuv444p which most players can't decode\n filters.push('[overlaid]format=yuv420p[outv]')\n\n return filters.join(';')\n}\n\n/**\n * Composite image overlays onto a video using FFmpeg.\n *\n * @param videoPath - Source video path\n * @param overlays - Overlays to composite\n * @param outputPath - Output video path\n * @param videoWidth - Source video width\n * @param videoHeight - Source video height\n * @returns Path to the composited video\n */\nexport async function compositeOverlays(\n videoPath: string,\n overlays: readonly GeneratedOverlay[],\n outputPath: string,\n videoWidth: number,\n videoHeight: number,\n): Promise<string> {\n if (overlays.length === 0) {\n throw new Error('[OverlayCompositing] No overlays provided')\n }\n\n const ffmpegPath = getFFmpegPath()\n const filterComplex = buildOverlayFilterComplex(overlays, videoWidth, videoHeight)\n\n const args = ['-y', '-i', videoPath]\n for (const overlay of overlays) {\n args.push('-loop', '1', '-i', overlay.imagePath)\n }\n args.push(\n '-filter_complex', filterComplex,\n '-map', '[outv]',\n '-map', '0:a',\n '-c:v', 'libx264',\n '-preset', 'ultrafast',\n '-crf', '23',\n '-threads', '4',\n '-c:a', 'copy',\n '-shortest',\n outputPath,\n )\n\n logger.info(`[OverlayCompositing] Compositing ${overlays.length} overlays → ${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(`[OverlayCompositing] FFmpeg failed: ${stderr}`)\n reject(new Error(`[OverlayCompositing] FFmpeg overlay compositing failed: ${error.message}`))\n return\n }\n logger.info(`[OverlayCompositing] Complete: ${outputPath}`)\n resolve(outputPath)\n })\n })\n}\n","import { ffprobe as _ffprobe, getFFmpegPath as _getFFmpegPath, getFFprobePath as _getFFprobePath } from '../../L2-clients/ffmpeg/ffmpeg.js'\nimport { extractAudio as _extractAudio, splitAudioIntoChunks as _splitAudioIntoChunks } from '../../L2-clients/ffmpeg/audioExtraction.js'\nimport { extractClip as _extractClip, extractCompositeClip as _extractCompositeClip, extractCompositeClipWithTransitions as _extractCompositeClipWithTransitions } from '../../L2-clients/ffmpeg/clipExtraction.js'\nimport { singlePassEdit as _singlePassEdit, singlePassEditAndCaption as _singlePassEditAndCaption } from '../../L2-clients/ffmpeg/singlePassEdit.js'\nimport { burnCaptions as _burnCaptions } from '../../L2-clients/ffmpeg/captionBurning.js'\nimport { detectSilence as _detectSilence } from '../../L2-clients/ffmpeg/silenceDetection.js'\nimport { captureFrame as _captureFrame } from '../../L2-clients/ffmpeg/frameCapture.js'\nimport { generatePlatformVariants as _generatePlatformVariants } from '../../L2-clients/ffmpeg/aspectRatio.js'\nimport { detectWebcamRegion as _detectWebcamRegion, getVideoResolution as _getVideoResolution } from '../../L2-clients/ffmpeg/faceDetection.js'\nimport { compositeOverlays as _compositeOverlays, buildOverlayFilterComplex as _buildOverlayFilterComplex, getOverlayPosition as _getOverlayPosition } from '../../L2-clients/ffmpeg/overlayCompositing.js'\n\n// Re-export types (exempt from layer rules)\nexport type { KeepSegment } from '../../L2-clients/ffmpeg/singlePassEdit.js'\nexport type { SilenceRegion } from '../../L2-clients/ffmpeg/silenceDetection.js'\nexport type { Platform } from '../../L2-clients/ffmpeg/aspectRatio.js'\n\n// Video information\nexport function ffprobe(...args: Parameters<typeof _ffprobe>): ReturnType<typeof _ffprobe> {\n return _ffprobe(...args)\n}\n\nexport function getFFmpegPath(...args: Parameters<typeof _getFFmpegPath>): ReturnType<typeof _getFFmpegPath> {\n return _getFFmpegPath(...args)\n}\n\nexport function getFFprobePath(...args: Parameters<typeof _getFFprobePath>): ReturnType<typeof _getFFprobePath> {\n return _getFFprobePath(...args)\n}\n\n// Audio extraction\nexport function extractAudio(...args: Parameters<typeof _extractAudio>): ReturnType<typeof _extractAudio> {\n return _extractAudio(...args)\n}\n\nexport function splitAudioIntoChunks(...args: Parameters<typeof _splitAudioIntoChunks>): ReturnType<typeof _splitAudioIntoChunks> {\n return _splitAudioIntoChunks(...args)\n}\n\n// Clip extraction\nexport function extractClip(...args: Parameters<typeof _extractClip>): ReturnType<typeof _extractClip> {\n return _extractClip(...args)\n}\n\nexport function extractCompositeClip(...args: Parameters<typeof _extractCompositeClip>): ReturnType<typeof _extractCompositeClip> {\n return _extractCompositeClip(...args)\n}\n\nexport function extractCompositeClipWithTransitions(...args: Parameters<typeof _extractCompositeClipWithTransitions>): ReturnType<typeof _extractCompositeClipWithTransitions> {\n return _extractCompositeClipWithTransitions(...args)\n}\n\n// Editing\nexport function singlePassEdit(...args: Parameters<typeof _singlePassEdit>): ReturnType<typeof _singlePassEdit> {\n return _singlePassEdit(...args)\n}\n\nexport function singlePassEditAndCaption(...args: Parameters<typeof _singlePassEditAndCaption>): ReturnType<typeof _singlePassEditAndCaption> {\n return _singlePassEditAndCaption(...args)\n}\n\n// Captions\nexport function burnCaptions(...args: Parameters<typeof _burnCaptions>): ReturnType<typeof _burnCaptions> {\n return _burnCaptions(...args)\n}\n\n// Detection\nexport function detectSilence(...args: Parameters<typeof _detectSilence>): ReturnType<typeof _detectSilence> {\n return _detectSilence(...args)\n}\n\n// Frame capture\nexport function captureFrame(...args: Parameters<typeof _captureFrame>): ReturnType<typeof _captureFrame> {\n return _captureFrame(...args)\n}\n\n// Aspect ratio / platform variants\nexport function generatePlatformVariants(...args: Parameters<typeof _generatePlatformVariants>): ReturnType<typeof _generatePlatformVariants> {\n return _generatePlatformVariants(...args)\n}\n\n// Webcam region detection\nexport function detectWebcamRegion(...args: Parameters<typeof _detectWebcamRegion>): ReturnType<typeof _detectWebcamRegion> {\n return _detectWebcamRegion(...args)\n}\n\nexport function getVideoResolution(...args: Parameters<typeof _getVideoResolution>): ReturnType<typeof _getVideoResolution> {\n return _getVideoResolution(...args)\n}\n\n// Overlay compositing\nexport function compositeOverlays(...args: Parameters<typeof _compositeOverlays>): ReturnType<typeof _compositeOverlays> {\n return _compositeOverlays(...args)\n}\n\nexport function buildOverlayFilterComplex(...args: Parameters<typeof _buildOverlayFilterComplex>): ReturnType<typeof _buildOverlayFilterComplex> {\n return _buildOverlayFilterComplex(...args)\n}\n\nexport function getOverlayPosition(...args: Parameters<typeof _getOverlayPosition>): ReturnType<typeof _getOverlayPosition> {\n return _getOverlayPosition(...args)\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/index'\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 — ~7% larger than base for subtle emphasis without layout shift. */\nconst ACTIVE_FONT_SIZE = 62\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 — ~7% larger than base. */\nconst MEDIUM_ACTIVE_FONT_SIZE = 47\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 — ~7% larger than base. */\nconst PORTRAIT_ACTIVE_FONT_SIZE = 128\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\nStyle: Hook,Montserrat,42,&H00333333,&H00333333,&H60D0D0D0,&H60E0E0E0,1,0,0,0,100,100,2,0,3,14,2,8,60,60,70,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 // Portrait style: green color + subtle size change (no scale pop)\n return `{${PORTRAIT_ACTIVE_COLOR}\\\\fs${activeFontSize}}${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\n/**\n * Generate a complete medium ASS file with captions AND hook text overlay.\n */\nexport function generateMediumASSWithHook(\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, 'medium')\n const hookLine = generateHookOverlay(hookText, 4.0, 'medium')\n return baseASS + hookLine + '\\n'\n}\n\n/**\n * Generate a complete medium ASS file for a composite clip with captions AND hook text overlay.\n */\nexport function generateMediumASSWithHookComposite(\n transcript: Transcript,\n segments: { start: number; end: number }[],\n hookText: string,\n buffer?: number,\n): string {\n const baseASS = generateStyledASSForComposite(transcript, segments, buffer, 'medium')\n const hookLine = generateHookOverlay(hookText, 4.0, 'medium')\n return baseASS + hookLine + '\\n'\n}\n","export { GoogleGenAI, createUserContent, createPartFromUri } from '@google/genai'\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 '../../L1-infra/ai/gemini.js'\nimport { getConfig } from '../../L1-infra/config/environment.js'\nimport logger from '../../L1-infra/logger/configLogger.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. If the video has a weak opening (meta-commentary, dead air, false starts), recommend where the actual content begins so an editor can start the video there.\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.s format with decimal precision, e.g. 00:14.3 - 00:37.0)\n- Explain why it should be removed (dead air, filler words, false starts, repeated explanations, excessive pauses)\n- Rate the confidence (high/medium/low) — high means definitely remove, low means optional\n\nIMPORTANT: Do NOT recommend removing rants, passionate opinions, tangential discussions, or opinionated monologues. These are intentional content — they are engaging, authentic, and serve as excellent source material for short-form clips. Only cut truly dead content (silence, dead air, repeated false starts, filler). When in doubt, keep it in.\n\nAfter listing the recommendations in markdown, also provide a machine-readable JSON block summarizing all suggested cuts:\n\n\\`\\`\\`json:cuts\n[\n { \"start\": 0.0, \"end\": 15.2, \"reason\": \"Opening too slow - dead air and filler\", \"confidence\": \"high\" },\n { \"start\": 26.5, \"end\": 37.0, \"reason\": \"Meta-commentary for editor\", \"confidence\": \"high\" }\n]\n\\`\\`\\`\n\nTimes in the JSON block should be in seconds with decimal precision. Place cut boundaries at word boundaries.\n\nBe specific with timestamps. Be opinionated — say what works and what doesn't. Write as if briefing a human editor who will clean and tighten the video.`\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: from GEMINI_MODEL env or gemini-2.5-pro)\n * @returns Parsed editorial direction\n */\nexport async function analyzeVideoEditorial(\n videoPath: string,\n durationSeconds: number,\n model?: string,\n): Promise<string> {\n const config = getConfig()\n const apiKey = config.GEMINI_API_KEY\n const resolvedModel = model ?? config.GEMINI_MODEL\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: ${resolvedModel})`)\n\n // 3. Request editorial analysis\n const response = await ai.models.generateContent({\n model: resolvedModel,\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 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: from GEMINI_MODEL env or gemini-2.5-pro)\n * @returns Clip direction as markdown text\n */\nexport async function analyzeVideoClipDirection(\n videoPath: string,\n durationSeconds: number,\n model?: string,\n): Promise<string> {\n const config = getConfig()\n const apiKey = config.GEMINI_API_KEY\n const resolvedModel = model ?? config.GEMINI_MODEL\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: ${resolvedModel})`)\n\n // 3. Request clip direction analysis\n const response = await ai.models.generateContent({\n model: resolvedModel,\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 logger.info(`[Gemini] Clip direction analysis complete (${text.length} chars)`)\n\n return text\n}\n\nconst ENHANCEMENT_ANALYSIS_PROMPT = `You are a visual content strategist reviewing raw video footage. Write an editorial report identifying moments where an AI-generated image overlay would genuinely enhance viewer comprehension.\n\nWatch the video carefully and read the transcript below. Write a natural editorial report covering:\n\n1. **Video layout observations** — What is on screen? Is there a webcam overlay? Where is the main content area (code editor, terminal, browser)? What areas of the screen have less visual activity and could safely hold an overlay without hiding important content?\n\n2. **Enhancement opportunities** — For each moment you identify, describe:\n - The approximate timestamp range (in seconds) where the speaker is discussing the topic\n - What the speaker is explaining and what is currently visible on screen\n - The dominant background colors and brightness level at that moment (e.g., dark IDE, white browser, terminal with dark background). This helps the image designer choose contrasting colors so the overlay stands out\n - What kind of image would help (diagram, flowchart, illustration, infographic, etc.)\n - A detailed description of the image to generate\n - Why showing this image at this moment helps the viewer understand\n - Where on screen the image should go to avoid blocking important content\n\n3. **Timing guidance** — For each opportunity, note the natural start and end of the speaker's explanation. The image should appear when the topic begins and disappear when the speaker moves on. Typically 5-12 seconds is ideal — long enough to register, short enough to not overstay.\n\nImportant guidelines:\n- Do NOT force opportunities — if the video doesn't need visual aids, say so\n- Do NOT suggest images when the screen already shows relevant visuals (diagrams, UI demos, live coding that needs to be seen)\n- Do NOT suggest images for trivial topics that don't need visual explanation\n- Do NOT suggest images during live demonstrations where the viewer needs to see the screen clearly\n- Moments shorter than 5 seconds are too brief for an overlay to register\n- It's perfectly fine to identify 0 opportunities, 1, or several — quality over quantity\n\nWrite your report in natural language with clear section headers. This report will be read by a graphics agent that will make final decisions about what to generate.\n\nTRANSCRIPT:\n`\n\n/**\n * Upload a video to Gemini and get an editorial report on moments where\n * AI-generated image overlays would enhance viewer comprehension.\n *\n * Returns a raw natural-language report (not structured JSON) that the\n * GraphicsAgent will use to make final editorial decisions.\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 transcript - Full transcript text for context\n * @param model - Gemini model to use (default: from GEMINI_MODEL env or gemini-2.5-pro)\n * @returns Raw editorial report text\n */\nexport async function analyzeVideoForEnhancements(\n videoPath: string,\n durationSeconds: number,\n transcript: string,\n model?: string,\n): Promise<string> {\n const config = getConfig()\n const apiKey = config.GEMINI_API_KEY\n const resolvedModel = model ?? config.GEMINI_MODEL\n\n if (!apiKey) {\n throw new Error(\n 'GEMINI_API_KEY is required for video enhancement 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 enhancement 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 enhancement analysis (model: ${resolvedModel})`)\n\n // 3. Request enhancement analysis with video + transcript\n const response = await ai.models.generateContent({\n model: resolvedModel,\n contents: createUserContent([\n createPartFromUri(file.uri, file.mimeType),\n ENHANCEMENT_ANALYSIS_PROMPT + transcript,\n ]),\n })\n\n const text = response.text ?? ''\n\n if (!text) {\n throw new Error('Gemini returned empty response')\n }\n\n logger.info(`[Gemini] Enhancement analysis complete (${text.length} chars)`)\n\n return text\n}\n","import type { TokenUsage, CostInfo, QuotaSnapshot } from '../../L2-clients/llm/types.js';\nimport { calculateTokenCost, calculatePRUCost, COPILOT_PRU_OVERAGE_RATE } from '../../L0-pure/pricing/pricing.js';\nimport logger from '../../L1-infra/logger/configLogger.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 {\n analyzeVideoEditorial as l2AnalyzeVideoEditorial,\n analyzeVideoClipDirection as l2AnalyzeVideoClipDirection,\n analyzeVideoForEnhancements as l2AnalyzeVideoForEnhancements,\n} from '../../L2-clients/gemini/geminiClient.js'\nimport { costTracker } from '../costTracking/costTracker.js'\n\nexport async function analyzeVideoEditorial(\n videoPath: string,\n durationSeconds: number,\n model?: string,\n): Promise<string> {\n const result = await l2AnalyzeVideoEditorial(videoPath, durationSeconds, model)\n costTracker.recordServiceUsage('gemini', 0, {\n model: model ?? 'gemini-2.5-pro',\n durationSeconds,\n estimatedInputTokens: Math.ceil(durationSeconds * 263),\n estimatedOutputTokens: Math.ceil(result.length / 4),\n videoFile: videoPath,\n })\n return result\n}\n\nexport async function analyzeVideoClipDirection(\n videoPath: string,\n durationSeconds: number,\n model?: string,\n): Promise<string> {\n const result = await l2AnalyzeVideoClipDirection(videoPath, durationSeconds, model)\n costTracker.recordServiceUsage('gemini', 0, {\n model: model ?? 'gemini-2.5-pro',\n durationSeconds,\n estimatedInputTokens: Math.ceil(durationSeconds * 263),\n estimatedOutputTokens: Math.ceil(result.length / 4),\n videoFile: videoPath,\n })\n return result\n}\n\nexport async function analyzeVideoForEnhancements(\n videoPath: string,\n durationSeconds: number,\n transcript: string,\n model?: string,\n): Promise<string> {\n const result = await l2AnalyzeVideoForEnhancements(videoPath, durationSeconds, transcript, model)\n costTracker.recordServiceUsage('gemini', 0, {\n model: model ?? 'gemini-2.5-pro',\n durationSeconds,\n estimatedInputTokens: Math.ceil(durationSeconds * 263),\n estimatedOutputTokens: Math.ceil(result.length / 4),\n videoFile: videoPath,\n })\n return result\n}\n","import { join } from '../../L1-infra/paths/paths.js'\nimport { getFileStats, ensureDirectory, removeFile } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { extractAudio, splitAudioIntoChunks } from '../../L2-clients/ffmpeg/audioExtraction.js'\nimport { transcribeAudio } from '../../L2-clients/whisper/whisperClient.js'\nimport type { VideoFile, Transcript, Segment, Word } from '../../L0-pure/types/index'\nimport { getConfig } from '../../L1-infra/config/environment.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { costTracker } from '../costTracking/costTracker.js'\n\nconst MAX_WHISPER_SIZE_MB = 25\nconst WHISPER_COST_PER_MINUTE = 0.006 // $0.006/minute for whisper-1\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 trackWhisperCost(transcript, 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. Clean up temp audio file\n await removeFile(mp3Path).catch(() => {})\n logger.info(`Cleaned up temp file: ${mp3Path}`)\n\n // 5. 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 trackWhisperCost(result, 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\nfunction trackWhisperCost(transcript: Transcript, audioPath: string): void {\n const durationMinutes = transcript.duration / 60\n costTracker.recordServiceUsage('whisper', durationMinutes * WHISPER_COST_PER_MINUTE, {\n model: 'whisper-1',\n durationSeconds: transcript.duration,\n audioFile: audioPath,\n })\n}\n","import { createOpenAI } from '../llm/ai.js'\nimport { fileExistsSync, getFileStatsSync, openReadStream } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { getConfig } from '../../L1-infra/config/environment'\nimport logger from '../../L1-infra/logger/configLogger'\nimport { getWhisperPrompt } from '../../L1-infra/config/brand'\nimport { Transcript, Segment, Word } from '../../L0-pure/types/index'\n\nconst MAX_FILE_SIZE_MB = 25\nconst WARN_FILE_SIZE_MB = 20\nconst MAX_RETRIES = 3\nconst RETRY_DELAY_MS = 5000\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 = createOpenAI({ apiKey: config.OPENAI_API_KEY })\n\n try {\n const prompt = getWhisperPrompt()\n\n let response: Awaited<ReturnType<typeof openai.audio.transcriptions.create>> | undefined\n for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {\n try {\n 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 break\n } catch (retryError: unknown) {\n // Safely extract status - network errors may not have this property\n const status = typeof retryError === 'object' && retryError !== null && 'status' in retryError\n ? (retryError as { status?: number }).status\n : undefined\n if (status === 401 || status === 400 || status === 429) throw retryError\n if (attempt === MAX_RETRIES) throw retryError\n const msg = retryError instanceof Error ? retryError.message : String(retryError)\n logger.warn(`Whisper attempt ${attempt}/${MAX_RETRIES} failed: ${msg} — retrying in ${RETRY_DELAY_MS / 1000}s`)\n await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS))\n }\n }\n\n if (!response) throw new Error('Whisper transcription failed after all retries')\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 // Cast to access typed fields — the verbose_json format always returns an object, not a string\n const typedResponse = response as unknown as { text: string; language?: string; duration?: number }\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=${typedResponse.language}`\n )\n\n return {\n text: typedResponse.text,\n segments,\n words,\n language: typedResponse.language ?? 'unknown',\n duration: typedResponse.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 { fileExistsSync, readTextFileSync } from '../fileSystem/fileSystem.js'\nimport { getConfig } from './environment'\nimport logger from '../logger/configLogger'\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","import { join } from '../../L1-infra/paths/paths.js'\nimport { writeTextFile, ensureDirectory } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { VideoFile, Transcript } from '../../L0-pure/types/index'\nimport { generateSRT, generateVTT, generateStyledASS } from '../../L0-pure/captions/captionGenerator'\nimport { getConfig } from '../../L1-infra/config/environment'\nimport logger from '../../L1-infra/logger/configLogger'\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","export { default as sharp } from 'sharp'\nexport type { Sharp, Metadata as SharpMetadata } from 'sharp'\n","import { sharp } from '../../L1-infra/image/image.js'\nimport { dirname } from '../../L1-infra/paths/paths.js'\nimport { fetchRaw } from '../../L1-infra/http/httpClient.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { getConfig } from '../../L1-infra/config/environment.js'\nimport { ensureDirectory, writeFileBuffer } from '../../L1-infra/fileSystem/fileSystem.js'\n\ntype ImageSize = '1024x1024' | '1536x1024' | '1024x1536' | 'auto'\ntype ImageQuality = 'low' | 'medium' | 'high'\n\ninterface ImageGenerationOptions {\n size?: ImageSize\n quality?: ImageQuality\n style?: string\n}\n\ninterface ImageApiResponse {\n data?: Array<{ b64_json?: string }>\n error?: { message?: string }\n}\n\nexport const COST_BY_QUALITY: Record<ImageQuality, number> = {\n low: 0.04,\n medium: 0.07,\n high: 0.07,\n}\n\n/** Base styling appended to every image prompt to ensure overlays stand out on video */\nconst IMAGE_BASE_PROMPT = `\\n\\nRendering requirements: The image MUST have a solid opaque background (not transparent). Include a thin border or subtle drop shadow around the entire image. Use a clean, flat design style suitable for overlaying on top of video content. The image should look like a polished infographic card that clearly separates from whatever is behind it.`\n\n/**\n * Generate an image using OpenAI's DALL-E 3 model.\n *\n * @param prompt - Detailed description of the image to generate\n * @param outputPath - Where to save the generated PNG\n * @param options - Optional configuration\n * @returns Path to the saved image file\n */\nexport async function generateImage(\n prompt: string,\n outputPath: string,\n options?: ImageGenerationOptions,\n): Promise<string> {\n const config = getConfig()\n if (!config.OPENAI_API_KEY) {\n throw new Error('[ImageGen] OPENAI_API_KEY is required for image generation')\n }\n\n const size = options?.size ?? 'auto'\n const quality = options?.quality ?? 'high'\n const fullPrompt = (options?.style ? `${prompt}\\n\\nStyle: ${options.style}` : prompt) + IMAGE_BASE_PROMPT\n\n logger.info(`[ImageGen] Generating image: ${prompt.substring(0, 100)}...`)\n logger.debug(`[ImageGen] Size: ${size}, Quality: ${quality}`)\n\n const response = await fetchRaw('https://api.openai.com/v1/images/generations', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.OPENAI_API_KEY}`,\n },\n body: JSON.stringify({\n model: 'gpt-image-1.5',\n prompt: fullPrompt,\n n: 1,\n size,\n quality,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n logger.error(`[ImageGen] API error (${response.status}): ${errorText}`)\n throw new Error(`[ImageGen] OpenAI API returned ${response.status}: ${errorText}`)\n }\n\n const result = (await response.json()) as ImageApiResponse\n const b64 = result.data?.[0]?.b64_json\n\n if (!b64) {\n logger.error('[ImageGen] No b64_json in API response')\n throw new Error('[ImageGen] API response missing b64_json image data')\n }\n\n const rawBuffer = Buffer.from(b64, 'base64')\n\n // Validate and sanitize the image data using Sharp\n // This ensures the data is a valid image and breaks the taint chain for CodeQL\n // Sharp will throw if the data is not a valid image format\n let validatedBuffer: Buffer\n try {\n validatedBuffer = await sharp(rawBuffer)\n .png() // Re-encode as PNG to ensure format consistency\n .toBuffer()\n } catch (error) {\n logger.error('[ImageGen] Failed to validate image data from API', { error })\n throw new Error('[ImageGen] Invalid image data received from API - not a valid image format')\n }\n\n await ensureDirectory(dirname(outputPath))\n await writeFileBuffer(outputPath, validatedBuffer)\n\n logger.info(`[ImageGen] Image saved to ${outputPath} (${validatedBuffer.length} bytes)`)\n\n return outputPath\n}\n","/** Thin fetch wrapper for mockability at the L1 boundary. */\nexport async function fetchJson<T>(url: string, options?: RequestInit): Promise<T> {\n const response = await fetch(url, options)\n if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n return response.json() as Promise<T>\n}\n\n/** Raw fetch wrapper — returns the full Response for callers that need headers, status, streaming, etc. */\nexport async function fetchRaw(url: string, options?: RequestInit): Promise<Response> {\n return fetch(url, options)\n}\n","import {\n generateImage as l2GenerateImage,\n COST_BY_QUALITY,\n} from '../../L2-clients/openai/imageGeneration.js'\nimport { costTracker } from '../costTracking/costTracker.js'\n\nexport { COST_BY_QUALITY }\n\nexport interface ImageGenerationOptions {\n size?: '1024x1024' | '1536x1024' | '1024x1536' | 'auto'\n quality?: 'low' | 'medium' | 'high'\n style?: string\n}\n\nexport async function generateImage(\n prompt: string,\n outputPath: string,\n options?: ImageGenerationOptions,\n): Promise<string> {\n const result = await l2GenerateImage(prompt, outputPath, options)\n const quality = options?.quality ?? 'high'\n costTracker.recordServiceUsage('openai-image', COST_BY_QUALITY[quality], {\n model: 'gpt-image-1.5',\n size: options?.size ?? 'auto',\n quality,\n prompt: prompt.substring(0, 200),\n })\n return result\n}\n","/**\n * L4 bridge for L3 analysis, transcription, and caption services.\n *\n * Separated from videoServiceBridge to avoid eagerly loading Gemini/Whisper\n * modules when only FFmpeg operations are needed.\n *\n * Wraps L3 service functions so L5-assets can access them\n * without directly importing from L3, maintaining strict layer hierarchy:\n * L5 → L4 → L3 → L2.\n */\n\nimport { analyzeVideoEditorial as _analyzeVideoEditorial, analyzeVideoClipDirection as _analyzeVideoClipDirection, analyzeVideoForEnhancements as _analyzeVideoForEnhancements } from '../L3-services/videoAnalysis/videoAnalysis.js'\nimport { transcribeVideo as _transcribeVideo } from '../L3-services/transcription/transcription.js'\nimport { generateCaptions as _generateCaptions } from '../L3-services/captionGeneration/captionGeneration.js'\nimport { generateImage as _generateImage } from '../L3-services/imageGeneration/imageGeneration.js'\n\n// Re-export types (exempt from layer rules)\nexport type { ImageGenerationOptions } from '../L3-services/imageGeneration/imageGeneration.js'\n\n// Video analysis (Gemini wrappers with cost tracking)\nexport function analyzeVideoEditorial(...args: Parameters<typeof _analyzeVideoEditorial>): ReturnType<typeof _analyzeVideoEditorial> {\n return _analyzeVideoEditorial(...args)\n}\n\nexport function analyzeVideoClipDirection(...args: Parameters<typeof _analyzeVideoClipDirection>): ReturnType<typeof _analyzeVideoClipDirection> {\n return _analyzeVideoClipDirection(...args)\n}\n\nexport function analyzeVideoForEnhancements(...args: Parameters<typeof _analyzeVideoForEnhancements>): ReturnType<typeof _analyzeVideoForEnhancements> {\n return _analyzeVideoForEnhancements(...args)\n}\n\n// Transcription (Whisper wrapper)\nexport function transcribeVideo(...args: Parameters<typeof _transcribeVideo>): ReturnType<typeof _transcribeVideo> {\n return _transcribeVideo(...args)\n}\n\n// Caption generation\nexport function generateCaptions(...args: Parameters<typeof _generateCaptions>): ReturnType<typeof _generateCaptions> {\n return _generateCaptions(...args)\n}\n\n// Image generation (DALL-E wrapper with cost tracking)\nexport function generateImage(...args: Parameters<typeof _generateImage>): ReturnType<typeof _generateImage> {\n return _generateImage(...args)\n}\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 '../L1-infra/paths/paths.js'\nimport { TextAsset } from './TextAsset.js'\nimport type { AssetOptions } from './Asset.js'\nimport type { VideoAsset } from './VideoAsset.js'\nimport type { Platform } from '../L0-pure/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 '../L1-infra/fileSystem/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 path to the completion marker file.\n * For text assets, this is the filePath with .complete appended.\n */\n getCompletionMarkerPath(): string {\n return `${this.filePath}.complete`\n }\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 '../L1-infra/paths/paths.js'\nimport { fileExists, listDirectory, readTextFile, ensureDirectory } from '../L1-infra/fileSystem/fileSystem.js'\nimport type { AssetOptions } from './Asset.js'\nimport type { ShortClip, Platform, Transcript, Segment, Word } from '../L0-pure/types/index.js'\nimport { Platform as PlatformEnum } from '../L0-pure/types/index.js'\nimport { extractCompositeClip } from '../L4-agents/videoServiceBridge.js'\nimport type { MainVideoAsset } from './MainVideoAsset.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 edited video (no overlays, no captions — shorts get their own processing)\n const mainParent = this.parent as MainVideoAsset\n const parentVideo = await mainParent.getEditedVideo()\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 * 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 */\n/** Hook pattern used to capture viewer attention in the first 1-3 seconds */\nexport type HookType = 'cold-open' | 'curiosity-gap' | 'contradiction' | 'result-first' | 'bold-claim' | 'question';\n\n/** Primary emotional trigger that drives engagement (shares, saves, comments) */\nexport type EmotionalTrigger = 'awe' | 'humor' | 'surprise' | 'empathy' | 'outrage' | 'practical-value';\n\n/** Narrative arc structure used in short clips */\nexport type ShortNarrativeStructure = 'result-method-proof' | 'doing-x-wrong' | 'expectation-vs-reality' | 'mini-list' | 'tension-release' | 'loop';\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 hook?: string;\n variants?: ShortClipVariant[];\n /** Hook pattern classification — how the opening captures attention */\n hookType?: HookType;\n /** Primary emotional driver that makes this clip engaging */\n emotionalTrigger?: EmotionalTrigger;\n /** Viral potential score (1-20) based on hook strength, emotion, shareability, completion, replay */\n viralScore?: number;\n /** Narrative arc pattern used in this clip */\n narrativeStructure?: ShortNarrativeStructure;\n /** Why would someone share this with a friend? */\n shareReason?: string;\n /** Whether the content naturally loops back to the beginning */\n isLoopCandidate?: boolean;\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\n/** Narrative arc structure used in medium clips */\nexport type MediumNarrativeStructure = 'open-loop-steps-payoff' | 'problem-deepdive-solution' | 'story-arc' | 'debate-comparison' | 'tutorial-micropayoffs';\n\n/** Classification of medium clip content type */\nexport type MediumClipType = 'deep-dive' | 'tutorial' | 'story-arc' | 'debate' | 'problem-solution';\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 /** Hook pattern classification — how the opening captures attention */\n hookType?: HookType;\n /** Primary emotional driver that makes this clip engaging */\n emotionalTrigger?: EmotionalTrigger;\n /** Viral potential score (1-20) based on hook strength, emotion, shareability, completion, replay */\n viralScore?: number;\n /** Narrative arc pattern used in this clip */\n narrativeStructure?: MediumNarrativeStructure;\n /** Content type classification */\n clipType?: MediumClipType;\n /** Why would someone save this to reference later? */\n saveReason?: string;\n /** Retention hooks planned at ~15-20 second intervals within the clip */\n microHooks?: 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// VISUAL ENHANCEMENT\n// ============================================================================\n\n/** Placement region for an image overlay on video */\nexport type OverlayRegion = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center-right' | 'center-left'\n\n/** Where on screen to place an overlay image */\nexport interface OverlayPlacement {\n region: OverlayRegion;\n avoidAreas: string[];\n sizePercent: number;\n}\n\n/** A moment in the video identified by Gemini as needing a visual aid */\nexport interface EnhancementOpportunity {\n timestampStart: number;\n timestampEnd: number;\n topic: string;\n imagePrompt: string;\n reason: string;\n placement: OverlayPlacement;\n confidence: number;\n}\n\n/** A generated image overlay ready for FFmpeg compositing */\nexport interface GeneratedOverlay {\n opportunity: EnhancementOpportunity;\n imagePath: string;\n width: number;\n height: number;\n}\n\n/** Result of the visual enhancement stage */\nexport interface VisualEnhancementResult {\n enhancedVideoPath: string;\n overlays: GeneratedOverlay[];\n analysisTokens: number;\n imageGenCost: number;\n}\n\n// ============================================================================\n// PIPELINE\n// ============================================================================\n\nexport enum PipelineStage {\n Ingestion = 'ingestion',\n Transcription = 'transcription',\n SilenceRemoval = 'silence-removal',\n VisualEnhancement = 'visual-enhancement',\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 enhancedVideoPath?: 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// ============================================================================\n// IDEATION\n// ============================================================================\n\n/** Status lifecycle for content ideas */\nexport type IdeaStatus = 'draft' | 'ready' | 'recorded' | 'published'\n\n/**\n * Record of a piece of content published from an idea.\n * Appended to `Idea.publishedContent` when queue items are approved/published.\n */\nexport interface IdeaPublishRecord {\n /** Content type that was published */\n clipType: 'video' | 'short' | 'medium-clip'\n /** Platform where content was published */\n platform: Platform\n /** Links back to QueueItemMetadata.id */\n queueItemId: string\n /** When the content was published (ISO 8601) */\n publishedAt: string\n /** Final published URL if available */\n publishedUrl?: string\n}\n\n/**\n * A content idea generated by the IdeationAgent or created manually.\n *\n * Ideas flow through the pipeline: they are created during ideation,\n * linked to recordings during processing, and tracked through publishing.\n * The `status` field tracks the lifecycle: draft → ready → recorded → published.\n */\nexport interface Idea {\n /** Unique identifier, e.g. \"idea-copilot-debugging\" */\n id: string\n /** Main topic/title of the idea */\n topic: string\n /** The attention-grabbing angle (≤80 chars) */\n hook: string\n /** Who this content is for */\n audience: string\n /** The one thing the viewer should remember */\n keyTakeaway: string\n /** Bullet points to cover in the recording */\n talkingPoints: string[]\n /** Target platforms for this content */\n platforms: Platform[]\n /** Lifecycle status */\n status: IdeaStatus\n /** Tags for categorization and matching */\n tags: string[]\n /** When the idea was created (ISO 8601) */\n createdAt: string\n /** When the idea was last updated (ISO 8601) */\n updatedAt: string\n /** Deadline for publishing this idea's content (ISO 8601 date). Agent sets based on timeliness:\n * - Hot trend: 3-5 days out\n * - Timely event: 1-2 weeks out\n * - Evergreen: 3-6 months out */\n publishBy: string\n /** Video slug linked after recording (back-reference) */\n sourceVideoSlug?: string\n /** Why this is timely — context from trend research */\n trendContext?: string\n /** Tracks every piece of content published for this idea */\n publishedContent?: IdeaPublishRecord[]\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","/**\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 '../L1-infra/paths/paths.js'\nimport { fileExists, ensureDirectory } from '../L1-infra/fileSystem/fileSystem.js'\nimport type { MediumClip, Platform } from '../L0-pure/types/index.js'\nimport { Platform as PlatformEnum } from '../L0-pure/types/index.js'\nimport type { AssetOptions } from './Asset.js'\nimport { extractCompositeClip } from '../L4-agents/videoServiceBridge.js'\nimport type { MainVideoAsset } from './MainVideoAsset.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, extracting from parent if needed.\n * Extracts from the enhanced video so AI-generated overlays carry through.\n *\n * @param opts - Asset options (force regeneration, etc.)\n * @returns Path to the rendered video file\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 enhanced video (with overlays, no captions — medium clips get their own captioning)\n const mainParent = this.parent as MainVideoAsset\n const parentVideo = await mainParent.getEnhancedVideo()\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","/**\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, enhanced, 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 { BlogAsset } from './BlogAsset.js'\nimport { join, basename, extname, dirname } from '../L1-infra/paths/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 writeTextFile,\n} from '../L1-infra/fileSystem/fileSystem.js'\nimport { slugify } from '../L0-pure/text/text.js'\nimport { generateSRT, generateVTT, generateStyledASS } from '../L0-pure/captions/captionGenerator.js'\nimport { ffprobe, burnCaptions } from '../L4-agents/videoServiceBridge.js'\nimport { transcribeVideo, analyzeVideoClipDirection } from '../L4-agents/analysisServiceBridge.js'\nimport { removeDeadSilence } from '../L4-agents/SilenceRemovalAgent.js'\nimport { generateShorts } from '../L4-agents/ShortsAgent.js'\nimport { generateMediumClips } from '../L4-agents/MediumVideoAgent.js'\nimport { generateChapters } from '../L4-agents/ChapterAgent.js'\nimport { ProducerAgent } from '../L4-agents/ProducerAgent.js'\nimport { generateSummary } from '../L4-agents/SummaryAgent.js'\nimport { generateSocialPosts, generateShortPosts } from '../L4-agents/SocialMediaAgent.js'\nimport { generateBlogPost } from '../L4-agents/BlogAgent.js'\nimport { buildPublishQueue, commitAndPush } from '../L4-agents/pipelineServiceBridge.js'\nimport { enhanceVideo } from './visualEnhancement.js'\nimport { getConfig } from '../L1-infra/config/environment.js'\nimport logger from '../L1-infra/logger/configLogger.js'\nimport type { ProduceResult } from '../L4-agents/ProducerAgent.js'\nimport type { QueueBuildResult } from '../L4-agents/pipelineServiceBridge.js'\nimport {\n Platform,\n} from '../L0-pure/types/index.js'\nimport type {\n Idea,\n ShortClip,\n MediumClip,\n Chapter,\n Transcript,\n VideoFile,\n VideoLayout,\n AspectRatio,\n VideoSummary,\n SocialPost,\n WebcamRegion,\n} from '../L0-pure/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 /** Content ideas linked to this video for editorial direction */\n private _ideas: Idea[] = []\n\n /** Set ideas for editorial direction */\n setIdeas(ideas: Idea[]): void {\n this._ideas = ideas\n }\n\n /** Get linked ideas */\n get ideas(): Idea[] {\n return this._ideas\n }\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 enhanced (visual overlays) video: videoDir/{slug}-enhanced.mp4 */\n get enhancedVideoPath(): string {\n return join(this.videoDir, `${this.slug}-enhanced.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 shorts completion marker */\n private get shortsCompletionMarkerPath(): string {\n return join(this.videoDir, 'shorts', 'shorts.complete')\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 /** Path to medium clips completion marker */\n private get mediumClipsCompletionMarkerPath(): string {\n return join(this.videoDir, 'medium-clips', 'medium-clips.complete')\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 summary metadata JSON */\n get summaryJsonPath(): string {\n return join(this.videoDir, 'summary.json')\n }\n\n /** Path to blog post */\n get blogPath(): string {\n return join(this.videoDir, 'blog-post.md')\n }\n\n /** Path to blog completion marker */\n get blogCompletionMarkerPath(): string {\n return join(this.videoDir, 'blog.complete')\n }\n\n /** Path to social posts completion marker */\n private get socialPostsCompletionMarkerPath(): string {\n return join(this.videoDir, 'social-posts', 'social-posts.complete')\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', 'enhancements']\n // Also clean test script output directories ({slug}-enhance-test)\n const allEntries = await listDirectory(videoDir)\n for (const entry of allEntries) {\n if (entry.endsWith('-enhance-test')) {\n await removeDirectory(join(videoDir, entry), { recursive: true, force: true })\n }\n }\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 'clip-direction.md',\n 'editorial-direction.md',\n 'cost-report.md',\n 'layout.json',\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('-enhanced.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 videoFile = await this.toVideoFile()\n const transcript = await transcribeVideo(videoFile)\n await writeJsonFile(this.transcriptPath, transcript)\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 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\n // Re-transcribe the edited video so captions align with the new timeline\n const editedVideoFile = { ...videoFile, repoPath: result.editedPath }\n const editedTranscript = await transcribeVideo(editedVideoFile)\n await writeJsonFile(this.adjustedTranscriptPath, editedTranscript)\n logger.info(`Saved edited-video transcript to ${this.adjustedTranscriptPath}`)\n\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 enhanced (visual overlays) video.\n * If not already generated, runs the visual enhancement stage.\n * Falls back to the edited video if enhancement is skipped or finds no opportunities.\n *\n * @param opts - Options controlling generation\n * @returns Path to the enhanced or edited video\n */\n async getEnhancedVideo(opts?: AssetOptions): Promise<string> {\n // Check if enhanced video already exists\n if (!opts?.force && (await fileExists(this.enhancedVideoPath))) {\n return this.enhancedVideoPath\n }\n\n const config = getConfig()\n if (config.SKIP_VISUAL_ENHANCEMENT) {\n return this.getEditedVideo(opts)\n }\n\n // Get edited video and transcript\n const editedPath = await this.getEditedVideo(opts)\n const transcript = await this.getTranscript()\n const videoFile = await this.toVideoFile()\n\n // Run visual enhancement\n const result = await enhanceVideo(editedPath, transcript, videoFile)\n\n if (result) {\n logger.info(`Visual enhancement completed: ${result.overlays.length} overlays composited`)\n return result.enhancedVideoPath\n }\n\n logger.info('No visual enhancements generated, using edited video')\n return editedPath\n }\n\n /**\n * Get the captioned video.\n * If not already generated, burns captions into the enhanced 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 enhanced video (includes editing + overlays) and captions\n const enhancedPath = await this.getEnhancedVideo(opts)\n const captions = await this.getCaptions()\n\n // Burn captions into video\n await burnCaptions(enhancedPath, 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 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 /** Check if shorts generation is complete */\n private async isShortsComplete(): Promise<boolean> {\n return fileExists(this.shortsCompletionMarkerPath)\n }\n\n /** Mark shorts generation as complete */\n private async markShortsComplete(): Promise<void> {\n await writeTextFile(this.shortsCompletionMarkerPath, new Date().toISOString())\n }\n\n /** Clear shorts completion marker for regeneration */\n private async clearShortsCompletion(): Promise<void> {\n await removeFile(this.shortsCompletionMarkerPath)\n }\n\n /** Directory containing medium clips */\n private get mediumClipsDir(): string {\n return join(this.videoDir, 'medium-clips')\n }\n\n /** Check if medium clips generation is complete */\n private async isMediumClipsComplete(): Promise<boolean> {\n return fileExists(this.mediumClipsCompletionMarkerPath)\n }\n\n /** Mark medium clips generation as complete */\n private async markMediumClipsComplete(): Promise<void> {\n await writeTextFile(this.mediumClipsCompletionMarkerPath, new Date().toISOString())\n }\n\n /** Clear medium clips completion marker */\n private async clearMediumClipsCompletion(): Promise<void> {\n await removeFile(this.mediumClipsCompletionMarkerPath)\n }\n\n /** Load medium clips data from disk */\n private async loadMediumClipsFromDisk(): Promise<MediumClip[]> {\n if (await fileExists(this.mediumClipsJsonPath)) {\n const data = await readJsonFile<{ clips: MediumClip[] }>(this.mediumClipsJsonPath)\n return data.clips ?? []\n }\n return []\n }\n\n /** Directory containing social posts */\n private get socialPostsDir(): string {\n return join(this.videoDir, 'social-posts')\n }\n\n /** Check if social posts generation is complete */\n private async isSocialPostsComplete(): Promise<boolean> {\n return fileExists(this.socialPostsCompletionMarkerPath)\n }\n\n /** Mark social posts generation as complete */\n private async markSocialPostsComplete(): Promise<void> {\n await ensureDirectory(this.socialPostsDir)\n await writeTextFile(this.socialPostsCompletionMarkerPath, new Date().toISOString())\n }\n\n /** Clear social posts completion marker for regeneration */\n private async clearSocialPostsCompletion(): Promise<void> {\n if (await fileExists(this.socialPostsCompletionMarkerPath)) {\n await removeFile(this.socialPostsCompletionMarkerPath)\n }\n this.cache.delete('socialPosts')\n }\n\n /** Load social posts from disk by parsing markdown files */\n private async loadSocialPostsFromDisk(): Promise<SocialPost[]> {\n const posts: SocialPost[] = []\n const platforms = [Platform.TikTok, Platform.YouTube, Platform.Instagram, Platform.LinkedIn, Platform.X]\n\n for (const platform of platforms) {\n const filePath = join(this.socialPostsDir, `${platform.toLowerCase()}.md`)\n if (await fileExists(filePath)) {\n const content = await readTextFile(filePath)\n const post = this.parseSocialPostFile(content, platform, filePath)\n if (post) {\n posts.push(post)\n }\n }\n }\n\n return posts\n }\n\n /**\n * Parse a social post markdown file into a SocialPost object.\n *\n * @param content - Markdown file content with YAML frontmatter\n * @param platform - Target platform\n * @param filePath - Path to the file (for outputPath field)\n * @returns Parsed SocialPost or null if parsing fails\n */\n private parseSocialPostFile(content: string, platform: Platform, filePath: string): SocialPost | 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 const bodyContent = content.slice(endIndex + 3).trim()\n\n // Parse YAML frontmatter\n const hashtags: string[] = []\n const links: string[] = []\n let characterCount = bodyContent.length\n\n const lines = yamlContent.split('\\n')\n let inHashtags = false\n let inLinks = false\n\n for (const line of lines) {\n const trimmed = line.trim()\n\n // Handle array items\n if (trimmed.startsWith('- ')) {\n if (inHashtags) {\n const tag = trimmed.slice(2).trim().replace(/^[\"']|[\"']$/g, '')\n if (tag) hashtags.push(tag)\n } else if (inLinks) {\n // Links can be in format: - url: \"...\" or just - \"...\"\n const urlMatch = trimmed.match(/url:\\s*[\"']([^\"']+)[\"']/)\n if (urlMatch) {\n links.push(urlMatch[1])\n }\n }\n continue\n }\n\n // Check for section starts\n const colonIndex = line.indexOf(':')\n if (colonIndex !== -1) {\n const key = line.slice(0, colonIndex).trim()\n const value = line.slice(colonIndex + 1).trim()\n\n inHashtags = false\n inLinks = false\n\n switch (key) {\n case 'hashtags':\n inHashtags = value === '' || value === '[]'\n if (value && value !== '[]') {\n // Inline array format not expected but handle it\n inHashtags = true\n }\n break\n case 'links':\n inLinks = value === '' || value === '[]'\n if (value && value !== '[]') {\n inLinks = true\n }\n break\n case 'characterCount':\n characterCount = parseInt(value, 10) || bodyContent.length\n break\n }\n }\n }\n\n return {\n platform,\n content: bodyContent,\n hashtags,\n links,\n characterCount,\n outputPath: filePath,\n }\n }\n\n /**\n * Get short clips for this video as ShortVideoAsset objects.\n * Uses completion marker pattern for idempotency.\n *\n * @param opts - Options controlling generation\n * @returns Array of ShortVideoAsset objects\n */\n async getShorts(opts?: AssetOptions): Promise<ShortVideoAsset[]> {\n if (opts?.force) {\n await this.clearShortsCompletion()\n }\n\n if (await this.isShortsComplete()) {\n const clips = await this.loadShortsFromDisk()\n return clips.map((clip) => new ShortVideoAsset(this, clip, this.shortsDir))\n }\n\n const clips = await this.generateShortsInternal()\n await this.markShortsComplete()\n return clips.map((clip) => new ShortVideoAsset(this, clip, this.shortsDir))\n }\n\n /** Load shorts data from disk */\n private async loadShortsFromDisk(): Promise<ShortClip[]> {\n if (await fileExists(this.shortsJsonPath)) {\n const data = await readJsonFile<{ shorts: ShortClip[] }>(this.shortsJsonPath)\n return data.shorts ?? []\n }\n return []\n }\n\n /**\n * Generate shorts via ShortsAgent.\n * Internal helper called when completion marker is absent.\n *\n * @returns Array of ShortClip objects\n */\n private async generateShortsInternal(): Promise<ShortClip[]> {\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 * Get medium clips for this video as MediumClipAsset objects.\n * Uses completion marker pattern for idempotency.\n *\n * @param opts - Options controlling generation\n * @returns Array of MediumClipAsset objects\n */\n async getMediumClips(opts?: AssetOptions): Promise<MediumClipAsset[]> {\n if (opts?.force) {\n await this.clearMediumClipsCompletion()\n }\n\n if (await this.isMediumClipsComplete()) {\n const clips = await this.loadMediumClipsFromDisk()\n return clips.map((clip) => new MediumClipAsset(this, clip, this.mediumClipsDir))\n }\n\n const clips = await this.generateMediumClipsInternal()\n await this.markMediumClipsComplete()\n return clips.map((clip) => new MediumClipAsset(this, clip, this.mediumClipsDir))\n }\n\n /**\n * Generate medium clips via MediumVideoAgent.\n * Internal helper called when completion marker is absent.\n *\n * @returns Array of MediumClip objects\n */\n private async generateMediumClipsInternal(): Promise<MediumClip[]> {\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 * Get social posts for this video.\n * Uses completion marker pattern for idempotency.\n * Loads from disk if available, otherwise generates via SocialMediaAgent.\n *\n * @param opts - Options controlling generation\n * @returns Array of SocialPost objects (one per platform)\n */\n async getSocialPosts(opts?: AssetOptions): Promise<SocialPost[]> {\n if (opts?.force) {\n await this.clearSocialPostsCompletion()\n }\n\n if (await this.isSocialPostsComplete()) {\n return this.loadSocialPostsFromDisk()\n }\n\n // Generate social posts using SocialMediaAgent\n const transcript = await this.getTranscript()\n const summary = await this.getSummary()\n const video = await this.toVideoFile()\n await ensureDirectory(this.socialPostsDir)\n const posts = await generateSocialPosts(video, transcript, summary, this.socialPostsDir)\n\n await this.markSocialPostsComplete()\n return posts\n }\n\n /**\n * Get the summary for this video.\n * Uses completion marker pattern for idempotency.\n * Loads from disk if available, otherwise generates via SummaryAgent.\n *\n * @param opts - Options controlling generation\n * @returns VideoSummary with title, overview, keyTopics, snapshots, and markdownPath\n */\n async getSummary(opts?: AssetOptions): Promise<VideoSummary> {\n if (opts?.force) {\n await this.clearSummaryCompletion()\n }\n\n if (await this.isSummaryComplete()) {\n return this.loadSummaryFromDisk()\n }\n\n // Generate summary using SummaryAgent\n const transcript = await this.getTranscript()\n const shorts = await this.getShorts().catch(() => [])\n const chapters = await this.getChapters().catch(() => [])\n // Convert ShortVideoAsset[] to ShortClip[]\n const shortClips = shorts.map((s) => s.clip)\n const summary = await this.generateSummaryInternal(transcript, shortClips, chapters)\n\n await this.markSummaryComplete()\n return summary\n }\n\n /**\n * Check if summary generation is complete.\n */\n private async isSummaryComplete(): Promise<boolean> {\n return (await fileExists(this.summaryJsonPath)) && (await fileExists(this.summaryPath))\n }\n\n /**\n * Mark summary as complete by ensuring JSON metadata exists.\n */\n private async markSummaryComplete(): Promise<void> {\n // No-op: summary.json is written during generation\n }\n\n /**\n * Clear summary completion marker to force regeneration.\n */\n private async clearSummaryCompletion(): Promise<void> {\n if (await fileExists(this.summaryJsonPath)) {\n await removeFile(this.summaryJsonPath)\n }\n if (await fileExists(this.summaryPath)) {\n await removeFile(this.summaryPath)\n }\n this.cache.delete('summary')\n }\n\n /**\n * Load summary from disk.\n * Reads the summary.json metadata file.\n */\n private async loadSummaryFromDisk(): Promise<VideoSummary> {\n const summary = await readJsonFile<VideoSummary>(this.summaryJsonPath)\n return summary\n }\n\n /**\n * Generate summary via SummaryAgent.\n * Internal helper called when completion marker is absent.\n */\n private async generateSummaryInternal(\n transcript: Transcript,\n shorts: ShortClip[],\n chapters: Chapter[],\n ): Promise<VideoSummary> {\n const video = await this.toVideoFile()\n const summary = await generateSummary(video, transcript, shorts, chapters)\n // Persist the VideoSummary metadata to JSON\n await writeJsonFile(this.summaryJsonPath, summary)\n logger.info(`Generated summary for ${this.slug}`)\n return summary\n }\n\n /**\n * Get the blog post content for this video.\n * Uses completion marker pattern for idempotency.\n * Loads from disk if available, otherwise generates via BlogAgent.\n *\n * @param opts - Options controlling generation\n * @returns Blog post markdown content string\n */\n async getBlog(opts?: AssetOptions): Promise<string> {\n if (opts?.force) {\n await this.clearBlogCompletion()\n }\n\n if (await this.isBlogComplete()) {\n return this.loadBlogFromDisk()\n }\n\n // Generate blog using BlogAgent\n const transcript = await this.getTranscript()\n const summary = await this.getSummary()\n const video = await this.toVideoFile()\n const blogContent = await generateBlogPost(video, transcript, summary)\n\n // Write to disk\n await writeTextFile(this.blogPath, blogContent)\n await this.markBlogComplete()\n\n return blogContent\n }\n\n /**\n * Check if blog generation is complete.\n */\n private async isBlogComplete(): Promise<boolean> {\n return fileExists(this.blogCompletionMarkerPath)\n }\n\n /**\n * Mark blog as complete.\n */\n private async markBlogComplete(): Promise<void> {\n await writeTextFile(this.blogCompletionMarkerPath, new Date().toISOString())\n }\n\n /**\n * Clear blog completion marker to force regeneration.\n */\n private async clearBlogCompletion(): Promise<void> {\n if (await fileExists(this.blogCompletionMarkerPath)) {\n await removeFile(this.blogCompletionMarkerPath)\n }\n if (await fileExists(this.blogPath)) {\n await removeFile(this.blogPath)\n }\n }\n\n /**\n * Load blog content from disk.\n */\n private async loadBlogFromDisk(): Promise<string> {\n return readTextFile(this.blogPath)\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 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 /**\n * Override base getCaptions to use the adjusted transcript (post silence-removal)\n * so that main video captions align with the edited video timeline.\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 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 // Use adjusted transcript (aligned to edited video) instead of original\n const transcript = await this.getAdjustedTranscript()\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 // ── 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 // ── Pipeline Stage Methods ─────────────────────────────────────────────────\n // Methods that wrap L4 agent/bridge calls for use by the L6 pipeline.\n // Each method accepts explicit parameters (no implicit caching) so the\n // pipeline can control data flow between stages.\n\n /**\n * Run silence removal via the ProducerAgent.\n * @returns ProduceResult with removals, keepSegments, and output path\n */\n async removeSilence(modelName?: string): Promise<ProduceResult> {\n const agent = new ProducerAgent(this, modelName)\n return agent.produce(this.editedVideoPath)\n }\n\n /**\n * Transcribe an edited video file (post silence-removal).\n * Creates a VideoFile pointing to the edited path and runs transcription.\n */\n async transcribeEditedVideo(editedVideoPath: string): Promise<Transcript> {\n const video = await this.toVideoFile()\n const editedVideo: VideoFile = { ...video, repoPath: editedVideoPath, filename: basename(editedVideoPath) }\n return transcribeVideo(editedVideo)\n }\n\n /**\n * Analyze edited video for clip direction suggestions via Gemini.\n */\n async analyzeClipDirection(videoPath: string, duration: number): Promise<string> {\n return analyzeVideoClipDirection(videoPath, duration)\n }\n\n /**\n * Generate social posts for a single short/medium clip.\n */\n async generateShortPostsData(\n short: ShortClip,\n transcript: Transcript,\n modelName?: string,\n summary?: VideoSummary,\n ): Promise<SocialPost[]> {\n const video = await this.toVideoFile()\n return generateShortPosts(video, short, transcript, modelName, summary)\n }\n\n /**\n * Generate social posts for a single medium clip.\n * Converts MediumClip to ShortClip format and generates posts,\n * then moves them to the medium clip's posts directory.\n */\n async generateMediumClipPostsData(\n clip: MediumClip,\n modelName?: string,\n summary?: VideoSummary,\n ): Promise<SocialPost[]> {\n const transcript = await this.getAdjustedTranscript()\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 this.generateShortPostsData(asShortClip, transcript, modelName, summary)\n\n // Move posts to medium-clips/{slug}/posts/\n const clipsDir = join(this.videoDir, '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\n return posts\n }\n\n /**\n * Build the publish queue via the queue builder service.\n */\n private async buildPublishQueueData(\n shorts: ShortClip[],\n mediumClips: MediumClip[],\n socialPosts: SocialPost[],\n captionedVideoPath: string | undefined,\n ): Promise<QueueBuildResult> {\n const video = await this.toVideoFile()\n const ideaIds = this._ideas.length > 0 ? this._ideas.map((idea) => idea.id) : undefined\n return buildPublishQueue(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds)\n }\n\n /**\n * Build the publish queue (simplified wrapper for pipeline).\n * Delegates to buildPublishQueueData with provided clip/post data.\n */\n async buildQueue(\n shorts: ShortClip[],\n mediumClips: MediumClip[],\n socialPosts: SocialPost[],\n captionedVideoPath: string | undefined,\n ): Promise<void> {\n await this.buildPublishQueueData(shorts, mediumClips, socialPosts, captionedVideoPath)\n }\n\n /**\n * Commit and push all generated assets via git.\n */\n async commitAndPushChanges(message?: string): Promise<void> {\n return commitAndPush(this.slug, message)\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 { ffprobe, detectSilence, singlePassEdit } from '../L3-services/videoOperations/videoOperations.js'\nimport type { ToolWithHandler } from '../L3-services/llm/providerFactory.js'\nimport { join } from '../L1-infra/paths/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport type { SilenceRegion } from '../L3-services/videoOperations/videoOperations.js'\nimport type { VideoFile, Transcript, SilenceRemovalResult } from '../L0-pure/types/index'\nimport logger from '../L1-infra/logger/configLogger'\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 resetForRetry(): void {\n this.removals = []\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","export { getProvider, resetProvider, getProviderName } from './providerFactory.js'\nexport type {\n LLMProvider,\n LLMSession,\n LLMResponse,\n SessionConfig,\n ToolWithHandler,\n TokenUsage,\n CostInfo,\n QuotaSnapshot,\n ProviderEvent,\n ProviderEventType,\n ProviderName,\n ToolDefinition,\n ToolCall,\n ToolHandler,\n ImageContent,\n ImageMimeType,\n MCPServerConfig,\n MCPLocalServerConfig,\n MCPRemoteServerConfig,\n UserInputRequest,\n UserInputResponse,\n UserInputHandler,\n} from './providerFactory.js'\n","import type { LLMProvider, LLMSession, ToolWithHandler, MCPServerConfig, UserInputHandler } from '../L3-services/llm/providerFactory.js'\nimport { getProvider } from '../L3-services/llm/index.js'\nimport { getModelForAgent } from '../L1-infra/config/modelConfig.js'\nimport { costTracker } from '../L3-services/costTracking/costTracker.js'\nimport logger from '../L1-infra/logger/configLogger.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 type { ToolWithHandler } from '../L3-services/llm/providerFactory.js'\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 /** User input handler for ask_user requests. Override in subclasses that need interactive user input. */\n protected getUserInputHandler(): UserInputHandler | undefined {\n return undefined\n }\n\n /** Timeout for sendAndWait calls. Override in interactive agents that need longer timeouts. */\n protected getTimeoutMs(): number {\n return 300_000 // 5 minutes\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 * Reset agent-specific state before a retry attempt.\n * Override in subclasses that accumulate state via tool calls.\n */\n protected resetForRetry(): void {\n // No-op by default — subclasses override to clear accumulated state\n }\n\n /** Max retries for transient API errors (stream drops, rate limits). */\n private static readonly MAX_RETRIES = 3\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 * 4. Retries on transient errors (stream drops, rate limits) with backoff\n */\n async run(userMessage: string): Promise<string> {\n let lastError: unknown\n\n for (let attempt = 1; attempt <= BaseAgent.MAX_RETRIES; attempt++) {\n try {\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: this.getTimeoutMs(),\n mcpServers: this.getMcpServers(),\n onUserInputRequest: this.getUserInputHandler(),\n })\n this.setupEventHandlers(this.session)\n }\n\n logger.info(`[${this.agentName}] Sending message (attempt ${attempt}/${BaseAgent.MAX_RETRIES}): ${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 } catch (err) {\n lastError = err\n const message = err instanceof Error ? err.message : String(err)\n\n if (!BaseAgent.isRetryableError(message) || attempt === BaseAgent.MAX_RETRIES) {\n throw err\n }\n\n // Destroy old session — close() + null prevents stale callbacks\n const staleSession = this.session\n this.session = null\n try { await staleSession?.close() } catch { /* best-effort cleanup */ }\n\n // Reset subclass state (e.g. plannedShorts, plannedClips) accumulated during the failed attempt\n this.resetForRetry()\n\n const delayMs = 2000 * Math.pow(2, attempt - 1) // 2s, 4s, 8s\n logger.warn(`[${this.agentName}] Transient error (attempt ${attempt}/${BaseAgent.MAX_RETRIES}), retrying in ${delayMs / 1000}s: ${message}`)\n await new Promise(resolve => setTimeout(resolve, delayMs))\n }\n }\n\n throw lastError\n }\n\n /** Check if an error message indicates a transient/retryable failure. */\n private static isRetryableError(message: string): boolean {\n const retryablePatterns = [\n 'missing finish_reason',\n 'ECONNRESET',\n 'ETIMEDOUT',\n 'ECONNREFUSED',\n 'socket hang up',\n 'network error',\n 'rate limit',\n '429',\n '500',\n '502',\n '503',\n '504',\n 'stream ended',\n 'aborted',\n ]\n const lower = message.toLowerCase()\n return retryablePatterns.some(p => lower.includes(p.toLowerCase()))\n }\n\n /** Wire up session event listeners for logging. Override for custom display. */\n protected 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 type { Idea } from '../types/index.js'\n\nexport function buildIdeaContext(ideas: readonly Idea[]): string {\n if (ideas.length === 0) return ''\n\n return `\\n\\n## Creator's Intent for This Video\\n\\n` +\n ideas.map(idea => [\n `### Idea: ${idea.topic}`,\n `- **Hook angle:** ${idea.hook}`,\n `- **Target audience:** ${idea.audience}`,\n `- **Key takeaway:** ${idea.keyTakeaway}`,\n `- **Talking points:** ${idea.talkingPoints.join(', ')}`,\n ].join('\\n')).join('\\n\\n') +\n `\\n\\n**PRIORITY:** Clips that deliver the creator's intended message AND score high on virality should be ranked above clips that are only generically viral. Ensure at least one clip directly delivers the key takeaway.\\n`\n}\n\nexport function buildIdeaContextForPosts(ideas: readonly Idea[]): string {\n if (ideas.length === 0) return ''\n\n return `\\n\\n## Creator's Content Intent\\n\\n` +\n ideas.map(idea => [\n `### Idea: ${idea.topic}`,\n `- **Hook:** ${idea.hook}`,\n `- **Target audience:** ${idea.audience}`,\n `- **Key takeaway:** ${idea.keyTakeaway}`,\n `- **Target platforms:** ${idea.platforms.join(', ')}`,\n ].join('\\n')).join('\\n\\n') +\n `\\n\\n**Posts should align to the creator's intended message and hook angle.** Use the key takeaway as the primary CTA where possible.\\n`\n}\n\nexport function buildIdeaContextForSummary(ideas: readonly Idea[]): string {\n if (ideas.length === 0) return ''\n\n return `\\n\\n## Creator's Intent\\n\\n` +\n `This video was created to cover the following ideas. The summary should reflect these themes:\\n\\n` +\n ideas.map(idea => `- **${idea.topic}:** ${idea.keyTakeaway}`).join('\\n') + '\\n'\n}\n\nexport function buildIdeaContextForBlog(ideas: readonly Idea[]): string {\n if (ideas.length === 0) return ''\n\n return `\\n\\n## Editorial Direction from Creator\\n\\n` +\n ideas.map(idea => [\n `### ${idea.topic}`,\n `- **Angle:** ${idea.hook}`,\n `- **Audience:** ${idea.audience}`,\n `- **Key takeaway:** ${idea.keyTakeaway}`,\n `- **Points to cover:** ${idea.talkingPoints.join('; ')}`,\n ].join('\\n')).join('\\n\\n') +\n `\\n\\n**Write the blog post to deliver these key takeaways.** The editorial angle should match the creator's intended hook.\\n`\n}\n","import type { ToolWithHandler } from '../L3-services/llm/providerFactory.js'\nimport { BaseAgent } from './BaseAgent'\nimport { VideoFile, Transcript, ShortClip, ShortSegment, ShortClipVariant, WebcamRegion } from '../L0-pure/types/index'\nimport type { HookType, EmotionalTrigger, ShortNarrativeStructure } from '../L0-pure/types/index'\nimport type { Idea } from '../L0-pure/types/index.js'\nimport { buildIdeaContext } from '../L0-pure/ideaContext/ideaContext.js'\nimport { extractClip, extractCompositeClip, burnCaptions, generatePlatformVariants, type Platform } from '../L3-services/videoOperations/videoOperations.js'\nimport { generateStyledASSForSegment, generateStyledASSForComposite, generatePortraitASSWithHook, generatePortraitASSWithHookComposite } from '../L0-pure/captions/captionGenerator'\n\nimport { generateId } from '../L0-pure/text/text.js'\nimport { slugify } from '../L0-pure/text/text.js'\nimport { writeTextFile, writeJsonFile, ensureDirectory } from '../L1-infra/fileSystem/fileSystem.js'\nimport { join, dirname } from '../L1-infra/paths/paths.js'\nimport logger from '../L1-infra/logger/configLogger'\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 hook: string\n hookType: string\n emotionalTrigger: string\n viralScore: number\n narrativeStructure: string\n shareReason: string\n isLoopCandidate: boolean\n}\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a viral short-form video strategist. Your job is to analyze a video transcript with word-level timestamps and extract the **most compelling, shareable moments** as shorts (15–60 seconds each).\n\n## Core Philosophy: Quality Over Quantity\n\nYour goal is NOT exhaustive coverage. Your goal is to find the moments that would make someone **stop scrolling, watch to the end, and share with a friend**. A single viral-worthy clip is worth more than ten mediocre ones.\n\nPlatform algorithms weight engagement signals as follows:\n- **Rewatches**: 5× weight (highest value)\n- **Shares/DM sends**: 3× weight\n- **Comments**: 2× weight\n- **Likes**: 1× weight (lowest value)\n\nDesign every clip to maximize rewatches and shares, not passive likes.\n\n## Your workflow\n1. Read the transcript and note the total duration.\n2. Work through the transcript **section by section**. For each chunk, identify moments with genuine viral potential.\n3. For each potential short, score it using the Viral Score Framework (see below). **Only extract clips scoring 8 or higher.**\n4. Call **add_shorts** for each batch of qualifying shorts. You can call it as many times as needed.\n5. After your first pass, call **review_shorts** to see everything you've planned so far.\n6. Review critically: Would YOU share each of these? Could any be combined into stronger composites? Are there moments you underscored?\n7. Drop any clip you're not confident about. A smaller set of strong clips beats a large set of mediocre ones.\n8. When you are confident every remaining clip has genuine viral potential, call **finalize_shorts**.\n\n## Viral Score Framework (rate each factor 1-5, then calculate)\n\n\\`\\`\\`\nViral Score = (Hook Strength × 3) + (Emotional Intensity × 2) + \n (Shareability × 3) + (Completion Likelihood × 2) + \n (Replay Potential × 2)\n\nMaximum score: 60 → Normalized to 1-20 scale (divide by 3)\nMinimum to extract: 8/20\n\\`\\`\\`\n\n| Factor | 1 (Weak) | 3 (Moderate) | 5 (Strong) |\n|--------|----------|--------------|------------|\n| **Hook Strength** | Generic statement, no tension | Interesting but expected | Bold claim, contradiction, or jaw-drop reveal |\n| **Emotional Intensity** | Neutral, purely informational | Mildly amusing or interesting | Triggers awe, laughter, surprise, outrage, or empathy |\n| **Shareability** | Niche interest only | \"That's cool\" but wouldn't send it | \"I NEED to send this to someone\" |\n| **Completion Likelihood** | Single flat idea, no arc | Has a point but pacing is uneven | Clear narrative with payoff — viewer must see the end |\n| **Replay Potential** | One-time value only | Worth a second look | Contains detail worth rewatching, natural loop, or surprising twist |\n\n## What makes a clip viral (prioritized)\n\n1. **Surprising contradictions** — \"Everyone thinks X, but actually Y\" — subverts expectations\n2. **Emotional peaks** — moments of genuine passion, vulnerability, frustration, or excitement\n3. **Quotable one-liners** — bold, memorable statements that stand alone as wisdom or hot takes\n4. **Visual reveals / transformations** — before/after, \"watch what happens next\"\n5. **Relatable struggles** — \"I've been there\" moments that create empathy and sharing impulse\n6. **Educational \"aha!\" moments** — the instant a complex concept clicks into clarity\n7. **Humor** — genuine wit, unexpected punchlines, or absurd juxtapositions\n8. **Controversy / debate fuel** — strong opinions that people will argue about in comments\n\n## Hook architecture (CRITICAL — the first 3 seconds decide everything)\n\n87% of viewers decide within 3 seconds whether to keep watching. Videos with 70-85% retention at the 3-second mark get **2.2× more total views**. Every short MUST have a deliberate hook strategy.\n\n### Hook types (classify every short)\n\n| Hook Type | Pattern | Best For |\n|-----------|---------|----------|\n| **cold-open** | Drop into the most compelling moment, then rewind | Stories, reveals, transformations |\n| **curiosity-gap** | \"The one thing nobody tells you about...\" | Tips, lessons, insider knowledge |\n| **contradiction** | \"Everyone says X, but actually Y\" | Hot takes, myth-busting |\n| **result-first** | Show the outcome immediately, then explain how | Tutorials, before/after |\n| **bold-claim** | Make a specific, surprising statement of fact | Data-driven, authority content |\n| **question** | \"Want to know why X?\" — engage curiosity directly | Engagement-focused, relatable |\n\n### Hook-First Video Ordering\n\nIf a short's natural content flows A→B→C→D, the final short should play as D→A→B→C — the **payoff moment (D) is moved to the front as the hook**, then the content plays from the beginning up to that point. The hook does NOT repeat.\n\n**How to implement:**\n1. Plan the content as normal (full story A→D)\n2. Identify the single most arresting 2-5 second moment — usually the payoff, punchline, or emotional peak\n3. That moment becomes the FIRST segment in the segments array\n4. The remaining content plays chronologically from start to just before the hook\n5. Example: content [120s–150s], best moment [145s–150s] → segments: [{start: 145, end: 150}, {start: 120, end: 145}]\n\n**Hook quality rules (NEVER violate):**\n- The hook segment MUST start and end on a **complete sentence or clause boundary**\n- The hook MUST be a **self-contained, complete thought** — understandable without prior context\n- If no moment qualifies as a clean hook, **keep segments chronological** and use hook text only\n\n## Narrative structures (classify every short)\n\n| Structure | Pattern | When to use |\n|-----------|---------|-------------|\n| **result-method-proof** | Show outcome → explain method → prove it works | Tutorials, demonstrations |\n| **doing-x-wrong** | Identify common mistake → show correct approach | Education, authority building |\n| **expectation-vs-reality** | What people think → what's actually true | Myth-busting, hot takes |\n| **mini-list** | \"3 things/tips/mistakes\" — each is a micro-payoff | Tips, advice, knowledge |\n| **tension-release** | Build to surprising/satisfying conclusion | Stories, reveals |\n| **loop** | End connects seamlessly to beginning → replay multiplier | Any content that naturally circles back |\n\n## Emotional triggers (classify every short)\n\nIdentify the PRIMARY emotion that will drive engagement:\n- **awe** — mind-blowing revelation, impressive skill, or scale that amazes\n- **humor** — genuine laughter, clever observation, relatable absurdity\n- **surprise** — unexpected twist, counter-intuitive fact, subverted expectation\n- **empathy** — shared struggle, vulnerability, \"I've been there\" connection\n- **outrage** — exposing injustice, calling out bad practices, righteous anger\n- **practical-value** — actionable tip, time-saving hack, \"I need to save this\"\n\n## Duration optimization\n\nPlatform-specific sweet spots (aim for these):\n- **TikTok**: 21-34 seconds (62% completion rate at this range)\n- **YouTube Shorts**: 15-30 seconds (highest viral potential)\n- **Instagram Reels**: 7-30 seconds (highest completion rates)\n\nGeneral guidance: Prefer 20-45 seconds. Under 15s lacks narrative depth. Over 50s requires exceptional retention quality.\n\n## Loop detection\n\nFlag shorts where the content **naturally circles back to the beginning**:\n- Speaker returns to an opening question and answers it\n- A transformation sequence that ends where it began\n- A statement that sets up a natural replay (\"and that's exactly why...\")\n\nLoop-engineered videos achieve 200-250% watch-through rates — a massive algorithmic boost.\n\n## Composite opportunities\n\nComposites (multi-segment shorts) often make the **best** shorts:\n- \"Every time X happens\" montages — collect recurring moments\n- Escalation arcs — build from mild to intense across the video\n- Contradiction compilations — multiple perspectives on one topic\n- Before/after pairs from different points in the video\n\n## Rules\n\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. Every short needs a catchy, descriptive title (5-10 words).\n5. Tags should be lowercase, no hashes, 3-6 per short.\n6. A 1-second buffer is automatically added around each segment boundary.\n7. Avoid significant timestamp overlap between shorts.\n8. **Minimum viral score of 8/20 to extract.** Be ruthless about quality.\n9. Every short MUST have a hook, hookType, emotionalTrigger, viralScore, narrativeStructure, and shareReason.\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## The shareability test (ask for EVERY clip)\n\nBefore adding a short, ask yourself: **\"Would I interrupt someone to show them this?\"**\n- If YES → strong clip, add it\n- If \"maybe, it's interesting\" → score it honestly and only keep if ≥8\n- If NO → drop it, no matter how \"complete\" the topic coverage feels`\n\n// ── JSON Schema for the add_shorts tool ──────────────────────────────────────\n\nconst ADD_SHORTS_SCHEMA = {\n type: 'object',\n properties: {\n shorts: {\n type: 'array',\n description: 'Array of short clips to add to the plan',\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 hook: { type: 'string', description: 'Short attention-grabbing text (≤60 chars) for visual overlay during the hook segment' },\n hookType: {\n type: 'string',\n enum: ['cold-open', 'curiosity-gap', 'contradiction', 'result-first', 'bold-claim', 'question'],\n description: 'Hook pattern classification — how the opening captures viewer attention',\n },\n emotionalTrigger: {\n type: 'string',\n enum: ['awe', 'humor', 'surprise', 'empathy', 'outrage', 'practical-value'],\n description: 'Primary emotional driver that makes this clip engaging and shareable',\n },\n viralScore: {\n type: 'number',\n description: 'Viral potential score (1-20) calculated from Hook Strength×3 + Emotional Intensity×2 + Shareability×3 + Completion Likelihood×2 + Replay Potential×2, then divided by 3',\n },\n narrativeStructure: {\n type: 'string',\n enum: ['result-method-proof', 'doing-x-wrong', 'expectation-vs-reality', 'mini-list', 'tension-release', 'loop'],\n description: 'Narrative arc pattern used in this clip',\n },\n shareReason: {\n type: 'string',\n description: 'Why would someone share this with a friend? Be specific.',\n },\n isLoopCandidate: {\n type: 'boolean',\n description: 'Whether the content naturally circles back to the beginning, enabling seamless replay',\n },\n segments: {\n type: 'array',\n description: 'One or more time segments that compose this short. For hook-first ordering, the hook segment comes first.',\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', 'hook', 'hookType', 'emotionalTrigger', 'viralScore', 'narrativeStructure', 'shareReason', 'isLoopCandidate'],\n },\n },\n },\n required: ['shorts'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass ShortsAgent extends BaseAgent {\n private plannedShorts: PlannedShort[] = []\n private isFinalized = false\n\n constructor(systemPrompt: string = SYSTEM_PROMPT, model?: string) {\n super('ShortsAgent', systemPrompt, undefined, model)\n }\n\n protected resetForRetry(): void {\n this.plannedShorts = []\n this.isFinalized = false\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'add_shorts',\n description:\n 'Add one or more shorts to your plan. ' +\n 'You can call this multiple times to build your list incrementally as you analyze each section of the transcript.',\n parameters: ADD_SHORTS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('add_shorts', args as Record<string, unknown>)\n },\n },\n {\n name: 'review_shorts',\n description:\n 'Review all shorts planned so far. Returns a summary of every short in your current plan. ' +\n 'Use this to check for gaps, overlaps, or missed opportunities before finalizing.',\n parameters: { type: 'object', properties: {} },\n handler: async () => {\n return this.handleToolCall('review_shorts', {})\n },\n },\n {\n name: 'finalize_shorts',\n description:\n 'Finalize your short clip plan and trigger extraction. ' +\n 'Call this ONCE after you have added all shorts and reviewed them for completeness.',\n parameters: { type: 'object', properties: {} },\n handler: async () => {\n return this.handleToolCall('finalize_shorts', {})\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 'add_shorts': {\n const newShorts = args.shorts as PlannedShort[]\n this.plannedShorts.push(...newShorts)\n logger.info(`[ShortsAgent] Added ${newShorts.length} shorts (total: ${this.plannedShorts.length})`)\n return `Added ${newShorts.length} shorts. Total planned: ${this.plannedShorts.length}. Call add_shorts for more, review_shorts to check your plan, or finalize_shorts when done.`\n }\n\n case 'review_shorts': {\n if (this.plannedShorts.length === 0) {\n return 'No shorts planned yet. Analyze the transcript and call add_shorts to start planning.'\n }\n const summary = this.plannedShorts.map((s, i) => {\n const totalDur = s.segments.reduce((sum, seg) => sum + (seg.end - seg.start), 0)\n const timeRanges = s.segments.map(seg => `${seg.start.toFixed(1)}s–${seg.end.toFixed(1)}s`).join(', ')\n const type = s.segments.length > 1 ? 'composite' : 'single'\n return `${i + 1}. \"${s.title}\" (${totalDur.toFixed(1)}s, ${type}, score: ${s.viralScore}/20) [${timeRanges}]\\n Hook: ${s.hook} (${s.hookType}) | Emotion: ${s.emotionalTrigger} | Structure: ${s.narrativeStructure}\\n Share reason: ${s.shareReason}\\n ${s.isLoopCandidate ? '🔄 Loop candidate' : ''}`\n }).join('\\n')\n const avgScore = this.plannedShorts.reduce((sum, s) => sum + s.viralScore, 0) / this.plannedShorts.length\n return `## Planned shorts (${this.plannedShorts.length} total, avg viral score: ${avgScore.toFixed(1)}/20)\\n\\n${summary}\\n\\nReview critically:\\n- Would YOU share each of these? Drop any clip scoring below 8.\\n- Can any be combined into stronger composites?\\n- Are there moments you underscored that deserve a second look?`\n }\n\n case 'finalize_shorts': {\n this.isFinalized = true\n logger.info(`[ShortsAgent] Finalized ${this.plannedShorts.length} shorts`)\n return `Finalized ${this.plannedShorts.length} shorts. Extraction will begin.`\n }\n\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n getPlannedShorts(): PlannedShort[] {\n return this.plannedShorts\n }\n\n getIsFinalized(): boolean {\n return this.isFinalized\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\nexport async function generateShorts(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n clipDirection?: string,\n webcamOverride?: WebcamRegion | null,\n ideas?: Idea[],\n): Promise<ShortClip[]> {\n const systemPrompt = SYSTEM_PROMPT + (ideas?.length ? buildIdeaContext(ideas) : '')\n const agent = new ShortsAgent(systemPrompt, 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 find the most viral-worthy moments for shorts.\\n`,\n `Video: ${video.filename}`,\n `Duration: ${transcript.duration.toFixed(1)}s`,\n `Focus on quality over quantity — only extract clips scoring 8+ on the viral score framework. Every clip must have a hook, hookType, emotionalTrigger, viralScore, narrativeStructure, and shareReason.\\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 let runError: Error | undefined\n \n // The Copilot SDK has a known bug where it throws \"missing finish_reason\"\n // even after tools completed successfully. We catch that specific error\n // and check if shorts were planned before re-throwing.\n try {\n await agent.run(prompt)\n } catch (err) {\n runError = err instanceof Error ? err : new Error(String(err))\n \n // Check if shorts were planned despite the error\n const partialPlanned = agent.getPlannedShorts()\n if (partialPlanned.length > 0 && runError.message.includes('missing finish_reason')) {\n logger.warn(`[ShortsAgent] SDK error after ${partialPlanned.length} shorts planned - proceeding with partial result`)\n } else {\n throw runError\n }\n }\n \n const planned = agent.getPlannedShorts()\n\n if (planned.length === 0) {\n // Re-throw original error if we have one but no shorts\n if (runError) throw runError\n logger.warn('[ShortsAgent] No shorts were planned')\n return []\n }\n\n await writeJsonFile(join(video.videoDir, 'shorts-plan.json'), planned)\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, { webcamOverride })\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 hookText = plan.hook ?? plan.title\n const portraitAssContent = segments.length === 1\n ? generatePortraitASSWithHook(transcript, hookText, segments[0].start, segments[0].end)\n : generatePortraitASSWithHookComposite(transcript, segments, hookText)\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 `**Viral Score:** ${plan.viralScore}/20`,\n `**Hook Type:** ${plan.hookType}`,\n `**Emotional Trigger:** ${plan.emotionalTrigger}`,\n `**Narrative Structure:** ${plan.narrativeStructure}`,\n `**Share Reason:** ${plan.shareReason}`,\n plan.isLoopCandidate ? `**Loop Candidate:** Yes 🔄` : '',\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 ].filter(Boolean).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 hook: plan.hook,\n variants,\n hookType: plan.hookType as HookType,\n emotionalTrigger: plan.emotionalTrigger as EmotionalTrigger,\n viralScore: plan.viralScore,\n narrativeStructure: plan.narrativeStructure as ShortNarrativeStructure,\n shareReason: plan.shareReason,\n isLoopCandidate: plan.isLoopCandidate,\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 '../L3-services/llm/providerFactory.js'\nimport { BaseAgent } from './BaseAgent'\nimport { VideoFile, Transcript, MediumClip, MediumSegment } from '../L0-pure/types/index'\nimport type { HookType, EmotionalTrigger, MediumNarrativeStructure, MediumClipType } from '../L0-pure/types/index'\nimport type { Idea } from '../L0-pure/types/index.js'\nimport { buildIdeaContext } from '../L0-pure/ideaContext/ideaContext.js'\nimport { extractClip, extractCompositeClipWithTransitions, burnCaptions } from '../L3-services/videoOperations/videoOperations.js'\nimport { generateStyledASSForSegment, generateStyledASSForComposite } from '../L0-pure/captions/captionGenerator'\n\nimport { generateId } from '../L0-pure/text/text.js'\nimport { slugify } from '../L0-pure/text/text.js'\nimport { writeTextFile, writeJsonFile, ensureDirectory } from '../L1-infra/fileSystem/fileSystem.js'\nimport { join, dirname } from '../L1-infra/paths/paths.js'\nimport logger from '../L1-infra/logger/configLogger'\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 hookType: string\n emotionalTrigger: string\n viralScore: number\n narrativeStructure: string\n clipType: string\n saveReason: string\n microHooks: 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 extract the **most valuable, engaging 1-3 minute segments** as standalone medium-form clips.\n\n## Core Philosophy: Value Density Over Coverage\n\nYour goal is NOT to cover every minute of the video. Your goal is to find segments where the speaker delivers **concentrated value** — complete ideas, clear tutorials, compelling stories, or insightful analysis that viewers would **save to reference later** or **share because it changed their thinking**.\n\nPlatform algorithms heavily weight saves and shares over likes:\n- **Saves** signal \"I'll come back to this\" — the highest-intent engagement action\n- **Shares** signal \"Someone I know needs to see this\" — the strongest distribution trigger\n- **Comments** signal \"I have something to say about this\" — drives conversation\n- **Likes** are passive approval — lowest algorithmic value\n\nDesign every clip to maximize saves and shares.\n\n## Your workflow\n1. Read the transcript and note the total duration.\n2. Work through the transcript **section by section** (roughly 5-8 minute chunks). For each chunk, identify segments with genuine standalone value.\n3. For each potential clip, score it using the Viral Score Framework (see below). **Only extract clips scoring 10 or higher.**\n4. Call **add_medium_clips** for each batch of clips you find. You can call it as many times as needed.\n5. After your first pass, call **review_medium_clips** to see everything you've planned so far.\n6. Review critically: Does each clip deliver clear, standalone value? Would someone save it? Could segments be combined into something stronger?\n7. Drop any clip you're not confident about. Fewer, stronger clips beat many mediocre ones.\n8. When you are confident every remaining clip has genuine value, call **finalize_medium_clips**.\n\n## Viral Score Framework (rate each factor 1-5, then calculate)\n\n\\`\\`\\`\nViral Score = (Hook Strength × 3) + (Emotional Intensity × 2) + \n (Shareability × 3) + (Completion Likelihood × 2) + \n (Replay Potential × 2)\n\nMaximum score: 60 → Normalized to 1-20 scale (divide by 3)\nMinimum to extract: 10/20 (higher bar than shorts — medium clips cost more to produce)\n\\`\\`\\`\n\n| Factor | 1 (Weak) | 3 (Moderate) | 5 (Strong) |\n|--------|----------|--------------|------------|\n| **Hook Strength** | Slow start, no clear value promise | Decent opening but predictable | Cold open with result/transformation, strong curiosity gap |\n| **Emotional Intensity** | Neutral, lecture-like delivery | Engaged but flat pacing | Genuine passion, escalating energy, vulnerability, or humor |\n| **Shareability** | Niche and theoretical | \"Good info\" but not shareable | \"My coworker/friend NEEDS to see this\" |\n| **Completion Likelihood** | No narrative arc, viewer can leave anytime | Has a point but meanders | Clear open loop → structured payoff, viewer must reach the end |\n| **Replay Potential** | One-time information dump | Worth bookmarking | Dense with detail worth rewatching, surprising insights throughout |\n\n## Clip types (classify every clip)\n\n| Type | Pattern | Best For |\n|------|---------|----------|\n| **deep-dive** | Single topic explored thoroughly with multiple angles | Complex explanations, analysis |\n| **tutorial** | Step-by-step instruction with clear outcome | How-to content, demonstrations |\n| **story-arc** | Setup → complication → climax → resolution | Anecdotes, case studies, experiences |\n| **debate** | \"X vs Y\" with evidence and clear winner | Comparisons, opinionated takes |\n| **problem-solution** | Problem defined → explored → solved | Troubleshooting, advice, recommendations |\n\n## Hook architecture (first 3-5 seconds decide everything)\n\nMedium clips have slightly more hook time than shorts (3-5 seconds vs 1-3 seconds), but the principle is the same: **front-load the value promise**.\n\n### Hook types (classify every clip)\n\n| Hook Type | Pattern | Best For |\n|-----------|---------|----------|\n| **cold-open** | Start with the result/conclusion, then explain how you got there | Tutorials, transformations, case studies |\n| **curiosity-gap** | \"The one thing that changed everything about how I...\" | Deep dives, insights, lessons learned |\n| **contradiction** | \"Everyone says X, but here's what actually works\" | Debate clips, myth-busting |\n| **result-first** | Show the outcome immediately, then walk through the process | Before/after, demonstrations |\n| **bold-claim** | \"This is the single most important thing about X\" | Authority content, strong opinions |\n| **question** | \"Have you ever wondered why...?\" | Explorations, investigations |\n\n### Cold Open Structure for Medium Clips\n\nUnlike shorts (which reorder segments), medium clips should **start with a verbal hook** that front-loads the payoff promise, then play in chronological order:\n\n1. Identify the most compelling conclusion, result, or insight in the clip\n2. Craft a hook that teases this payoff in the first 3-5 seconds\n3. Structure the remaining clip to build toward that payoff naturally\n4. The hook is the \\`hook\\` text field — it appears as text overlay during the opening\n\n## Narrative structures (classify every clip)\n\n| Structure | Pattern | When to use |\n|-----------|---------|-------------|\n| **open-loop-steps-payoff** | Tease outcome → deliver step-by-step → prove it | Tutorials, how-to, demonstrations |\n| **problem-deepdive-solution** | Define problem → explore thoroughly → resolve | Troubleshooting, advice, analysis |\n| **story-arc** | Setup → rising tension → climax → resolution | Case studies, experiences, anecdotes |\n| **debate-comparison** | Frame the question → present both sides → verdict | Opinions, tool comparisons, trade-offs |\n| **tutorial-micropayoffs** | Step 1 (mini-payoff) → Step 2 (mini-payoff) → final result | Multi-step processes, recipes, workflows |\n\n## Micro-hooks: Retention throughout the clip (CRITICAL for medium clips)\n\nMedium clips are 1-3 minutes — viewers need fresh reasons to keep watching every 15-20 seconds. Plan **micro-hooks** at regular intervals:\n\n- **Every 15-20 seconds**, there should be a new information beat, mini-reveal, or energy shift\n- These are NOT just topic transitions — they are deliberate moments that re-engage attention\n- Examples: surprising data point, contradiction of prior statement, humor, visual transition, \"but here's the thing...\", escalation of stakes\n\n**Videos with pattern interrupts every 4 seconds average 58% retention vs 41% for static content.** For medium clips, plan at least 3-5 micro-hooks.\n\nProvide a \\`microHooks\\` array describing each planned retention moment within the clip.\n\n## Emotional triggers (classify every clip)\n\nIdentify the PRIMARY emotion driving engagement:\n- **awe** — mind-blowing insight, impressive depth, revelation\n- **humor** — genuine wit, self-deprecation, absurd examples\n- **surprise** — counter-intuitive findings, unexpected conclusions\n- **empathy** — shared struggles, vulnerability, \"been there\" moments\n- **outrage** — calling out bad practices, exposing misconceptions\n- **practical-value** — actionable steps, time-saving knowledge, \"save this for later\"\n\n## Duration optimization\n\n- **Sweet spot**: 60-120 seconds (1-2 minutes)\n- **Maximum**: 180 seconds — only if retention quality is exceptional\n- **Under 60 seconds**: Too short for medium format — should be a short instead\n- **Over 120 seconds**: Requires multiple micro-hooks and exceptional pacing\n\n## Differences from shorts\n\n- Shorts capture **moments**; medium clips capture **complete ideas**\n- Shorts optimize for **shares** (\"send this to someone\"); medium clips optimize for **saves** (\"I'll reference this later\")\n- Shorts use hook-first segment reordering; medium clips use cold-open hooks with **chronological content**\n- Medium clips MUST maintain **strict chronological order** — NOT hook-first reordering\n- Medium clips have room for depth and nuance — don't sacrifice completeness for brevity\n- Medium clips MUST have micro-hooks planned to maintain retention through the full duration\n\n## Compilation opportunities\n\nCompilations (multi-segment clips) work well for medium clips when:\n- Multiple brief discussions of the same theme appear across the video\n- A clear narrative arc can be constructed from non-contiguous segments\n- \"Every perspective on X\" — collecting viewpoints into a comprehensive take\n\nFor compilations, segments must be in chronological order.\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 get value.\n5. Every clip needs a descriptive title (5-12 words) and a topic label.\n6. For compilations, specify segments in **chronological order**.\n7. Tags should be lowercase, no hashes, 3-6 per clip.\n8. A 1-second buffer is automatically added around each segment boundary.\n9. **Minimum viral score of 10/20 to extract.** Medium clips cost more to produce — quality bar is higher.\n10. Every clip MUST have hook, hookType, emotionalTrigger, viralScore, narrativeStructure, clipType, saveReason, and microHooks.\n11. Avoid significant overlap with content that would work better as a short.\n\n## The save test (ask for EVERY clip)\n\nBefore adding a clip, ask yourself: **\"Would I bookmark this to come back to later?\"**\n- If YES → strong clip, add it\n- If \"it's informative but not reference-worthy\" → score it honestly and only keep if ≥10\n- If NO → drop it or consider if it works better as a short\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 add_medium_clips tool ───────────────────────────────\n\nconst ADD_MEDIUM_CLIPS_SCHEMA = {\n type: 'object',\n properties: {\n clips: {\n type: 'array',\n description: 'Array of medium-length clips to add to the plan',\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 (chronological order)',\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: 'Compelling one-liner (≤60 chars) teasing the clip\\'s core value — shown as text overlay during opening' },\n topic: { type: 'string', description: 'Main topic covered in the clip' },\n hookType: {\n type: 'string',\n enum: ['cold-open', 'curiosity-gap', 'contradiction', 'result-first', 'bold-claim', 'question'],\n description: 'Hook pattern classification — how the opening captures viewer attention',\n },\n emotionalTrigger: {\n type: 'string',\n enum: ['awe', 'humor', 'surprise', 'empathy', 'outrage', 'practical-value'],\n description: 'Primary emotional driver that makes this clip engaging',\n },\n viralScore: {\n type: 'number',\n description: 'Viral potential score (1-20) calculated from Hook Strength×3 + Emotional Intensity×2 + Shareability×3 + Completion Likelihood×2 + Replay Potential×2, then divided by 3',\n },\n narrativeStructure: {\n type: 'string',\n enum: ['open-loop-steps-payoff', 'problem-deepdive-solution', 'story-arc', 'debate-comparison', 'tutorial-micropayoffs'],\n description: 'Narrative arc pattern used in this clip',\n },\n clipType: {\n type: 'string',\n enum: ['deep-dive', 'tutorial', 'story-arc', 'debate', 'problem-solution'],\n description: 'Content type classification for this clip',\n },\n saveReason: {\n type: 'string',\n description: 'Why would someone save this to reference later? Be specific.',\n },\n microHooks: {\n type: 'array',\n items: { type: 'string' },\n description: 'Planned retention hooks at ~15-20 second intervals within the clip. Each should describe a specific moment that re-engages viewer attention.',\n },\n },\n required: ['title', 'description', 'tags', 'segments', 'totalDuration', 'hook', 'topic', 'hookType', 'emotionalTrigger', 'viralScore', 'narrativeStructure', 'clipType', 'saveReason', 'microHooks'],\n },\n },\n },\n required: ['clips'],\n}\n\n// ── Agent ────────────────────────────────────────────────────────────────────\n\nclass MediumVideoAgent extends BaseAgent {\n private plannedClips: PlannedMediumClip[] = []\n private isFinalized = false\n\n constructor(systemPrompt: string = SYSTEM_PROMPT, model?: string) {\n super('MediumVideoAgent', systemPrompt, undefined, model)\n }\n\n protected resetForRetry(): void {\n this.plannedClips = []\n this.isFinalized = false\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'add_medium_clips',\n description:\n 'Add one or more medium clips to your plan. ' +\n 'You can call this multiple times to build your list incrementally as you analyze each section of the transcript.',\n parameters: ADD_MEDIUM_CLIPS_SCHEMA,\n handler: async (args: unknown) => {\n return this.handleToolCall('add_medium_clips', args as Record<string, unknown>)\n },\n },\n {\n name: 'review_medium_clips',\n description:\n 'Review all medium clips planned so far. Returns a summary of every clip in your current plan. ' +\n 'Use this to check for gaps, overlaps, or missed opportunities before finalizing.',\n parameters: { type: 'object', properties: {} },\n handler: async () => {\n return this.handleToolCall('review_medium_clips', {})\n },\n },\n {\n name: 'finalize_medium_clips',\n description:\n 'Finalize your medium clip plan and trigger extraction. ' +\n 'Call this ONCE after you have added all clips and reviewed them for completeness.',\n parameters: { type: 'object', properties: {} },\n handler: async () => {\n return this.handleToolCall('finalize_medium_clips', {})\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 'add_medium_clips': {\n const newClips = args.clips as PlannedMediumClip[]\n this.plannedClips.push(...newClips)\n logger.info(`[MediumVideoAgent] Added ${newClips.length} clips (total: ${this.plannedClips.length})`)\n return `Added ${newClips.length} clips. Total planned: ${this.plannedClips.length}. Call add_medium_clips for more, review_medium_clips to check your plan, or finalize_medium_clips when done.`\n }\n\n case 'review_medium_clips': {\n if (this.plannedClips.length === 0) {\n return 'No medium clips planned yet. Analyze the transcript and call add_medium_clips to start planning.'\n }\n const summary = this.plannedClips.map((c, i) => {\n const totalDur = c.segments.reduce((sum, seg) => sum + (seg.end - seg.start), 0)\n const timeRanges = c.segments.map(seg => `${seg.start.toFixed(1)}s–${seg.end.toFixed(1)}s`).join(', ')\n const type = c.segments.length > 1 ? 'compilation' : c.clipType\n return `${i + 1}. \"${c.title}\" (${totalDur.toFixed(1)}s, ${type}, score: ${c.viralScore}/20) [${timeRanges}]\\n Hook: ${c.hook} (${c.hookType}) | Emotion: ${c.emotionalTrigger} | Structure: ${c.narrativeStructure}\\n Topic: ${c.topic} | Save reason: ${c.saveReason}\\n Micro-hooks: ${c.microHooks.join(' → ')}`\n }).join('\\n')\n const avgScore = this.plannedClips.reduce((sum, c) => sum + c.viralScore, 0) / this.plannedClips.length\n return `## Planned medium clips (${this.plannedClips.length} total, avg viral score: ${avgScore.toFixed(1)}/20)\\n\\n${summary}\\n\\nReview critically:\\n- Would YOU save each of these to reference later? Drop any clip scoring below 10.\\n- Are the micro-hooks strong enough to maintain retention through the full duration?\\n- Could any segments be combined into something stronger?`\n }\n\n case 'finalize_medium_clips': {\n this.isFinalized = true\n logger.info(`[MediumVideoAgent] Finalized ${this.plannedClips.length} medium clips`)\n return `Finalized ${this.plannedClips.length} medium clips. Extraction will begin.`\n }\n\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n getPlannedClips(): PlannedMediumClip[] {\n return this.plannedClips\n }\n\n getIsFinalized(): boolean {\n return this.isFinalized\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\nexport async function generateMediumClips(\n video: VideoFile,\n transcript: Transcript,\n model?: string,\n clipDirection?: string,\n ideas?: Idea[],\n): Promise<MediumClip[]> {\n const systemPrompt = SYSTEM_PROMPT + (ideas?.length ? buildIdeaContext(ideas) : '')\n const agent = new MediumVideoAgent(systemPrompt, 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 find the most valuable segments for medium-length clips (1-3 minutes each).\\n`,\n `Video: ${video.filename}`,\n `Duration: ${transcript.duration.toFixed(1)}s`,\n `Focus on value density over coverage — only extract clips scoring 10+ on the viral score framework. Every clip must have hook, hookType, emotionalTrigger, viralScore, narrativeStructure, clipType, saveReason, and microHooks.\\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 let runError: Error | undefined\n \n // The Copilot SDK has a known bug where it throws \"missing finish_reason\"\n // even after tools completed successfully. We catch that specific error\n // and check if clips were planned before re-throwing.\n try {\n await agent.run(prompt)\n } catch (err) {\n runError = err instanceof Error ? err : new Error(String(err))\n \n // Check if clips were planned despite the error\n const partialPlanned = agent.getPlannedClips()\n if (partialPlanned.length > 0 && runError.message.includes('missing finish_reason')) {\n logger.warn(`[MediumVideoAgent] SDK error after ${partialPlanned.length} clips planned - proceeding with partial result`)\n } else {\n throw runError\n }\n }\n \n const planned = agent.getPlannedClips()\n\n if (planned.length === 0) {\n // Re-throw original error if we have one but no clips\n if (runError) throw runError\n logger.warn('[MediumVideoAgent] No medium clips were planned')\n return []\n }\n\n await writeJsonFile(join(video.videoDir, 'medium-clips-plan.json'), planned)\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 (chronological, no hook overlay)\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 `**Viral Score:** ${plan.viralScore}/20`,\n `**Hook:** ${plan.hook} (${plan.hookType})`,\n `**Emotional Trigger:** ${plan.emotionalTrigger}`,\n `**Narrative Structure:** ${plan.narrativeStructure}`,\n `**Clip Type:** ${plan.clipType}`,\n `**Save Reason:** ${plan.saveReason}`,\n '',\n plan.description,\n '',\n '## Micro-Hooks\\n',\n ...plan.microHooks.map((h, i) => `${i + 1}. ${h}`),\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 hookType: plan.hookType as HookType,\n emotionalTrigger: plan.emotionalTrigger as EmotionalTrigger,\n viralScore: plan.viralScore,\n narrativeStructure: plan.narrativeStructure as MediumNarrativeStructure,\n clipType: plan.clipType as MediumClipType,\n saveReason: plan.saveReason,\n microHooks: plan.microHooks,\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 '../L3-services/llm/providerFactory.js'\nimport { writeTextFile, writeJsonFile, ensureDirectory } from '../L1-infra/fileSystem/fileSystem.js'\nimport { join } from '../L1-infra/paths/paths.js'\n\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../L1-infra/logger/configLogger'\nimport { getConfig } from '../L1-infra/config/environment'\nimport type { VideoFile, Transcript, Chapter } from '../L0-pure/types/index'\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 type { ToolWithHandler } from '../L3-services/llm/providerFactory.js'\nimport { BaseAgent } from './BaseAgent.js'\nimport type { VideoInfo } from './agentTools.js'\nimport { singlePassEdit, type KeepSegment } from '../L3-services/videoOperations/videoOperations.js'\nimport type { VideoAsset } from '../L5-assets/VideoAsset.js'\nimport logger from '../L1-infra/logger/configLogger.js'\n\n// ── System prompt ───────────────────────────────────────────────────────────\n\nconst SYSTEM_PROMPT = `You are a professional video editor preparing raw footage for visual enhancement. Your goal is to produce a clean, tight edit that's ready for graphics overlays, captions, and social media distribution.\n\n## INFORMATION HIERARCHY\n\nYou have three sources of information:\n1. **Editorial direction** (from Gemini video AI) — provides editorial judgment: what to cut, pacing issues, hook advice. It watched the actual video and can see visual cues the transcript cannot.\n2. **Transcript** — the ground truth for **what was said and when**. Timestamps in the transcript are accurate. Use it to verify that editorial direction timestamps actually match the spoken content.\n3. **Your own judgment** — use this to resolve conflicts and make final decisions.\n\n## CONFLICT RESOLUTION\n\n- **Timestamps**: The transcript's timestamps are authoritative. Gemini's timestamps can drift. Always cross-reference the editorial direction's timestamps against the transcript before cutting. If Gemini says \"cut 85-108 because it's dead air\" but the transcript shows substantive speech at 92-105, trust the transcript.\n- **Pacing vs Cleaning**: If the Pacing Analysis recommends removing an entire range but Cleaning Recommendations only flags pieces, favor pacing — it reflects the broader viewing experience.\n- **Hook & Retention**: If this section recommends starting at a later point, that overrides granular cleaning cuts in the opening.\n- **Valuable content**: Never cut substantive content that the viewer needs to understand the video's message. Filler and dead air around valuable content should be trimmed, but the content itself must be preserved.\n\n## WHAT YOU'RE OPTIMIZING FOR\n\nThe video you produce will be further processed by a graphics agent that adds AI-generated image overlays, then captioned, then cut into shorts and medium clips. Your edit needs to:\n- Start with the strongest content — no dead air, no \"I'm going to make a quick video\" preambles\n- Flow naturally so captions and overlays land on clean, well-paced segments\n- Remove anything that isn't for the viewer (meta-commentary, editor instructions, false starts)\n\n## TOOLS\n\n- **get_video_info** — video duration, dimensions, frame rate\n- **get_editorial_direction** — Gemini's full editorial report (cut points, pacing, hook advice, cleaning recommendations)\n- **get_transcript** — timestamped transcript (supports start/end filtering)\n- **add_cuts** — queue regions for removal (call as many times as needed, use decimal-second precision)\n- **finalize_cuts** — merge adjacent cuts and trigger the render (call once at the end)`\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// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Merge overlapping or adjacent removals (gap <= 2 seconds) into larger ranges. */\nfunction mergeRemovals(removals: Removal[]): Removal[] {\n if (removals.length <= 1) return removals\n\n const sorted = [...removals].sort((a, b) => a.start - b.start)\n const merged: Removal[] = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i++) {\n const prev = merged[merged.length - 1]\n const curr = sorted[i]\n if (curr.start <= prev.end + 2) {\n prev.end = Math.max(prev.end, curr.end)\n prev.reason = `${prev.reason}; ${curr.reason}`\n } else {\n merged.push({ ...curr })\n }\n }\n\n return merged\n}\n\n// ── JSON Schemas ─────────────────────────────────────────────────────────────\n\nconst ADD_CUTS_SCHEMA = {\n type: 'object',\n properties: {\n removals: {\n type: 'array',\n description: 'One or more regions to remove from the video',\n items: {\n type: 'object',\n properties: {\n start: { type: 'number', description: 'Start time in seconds (decimal precision, e.g. 14.3)' },\n end: { type: 'number', description: 'End time in seconds (decimal precision, e.g. 37.0)' },\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 private renderPromise: Promise<string | void> | null = null\n private outputPath: string = ''\n\n constructor(video: VideoAsset, model?: string) {\n super('ProducerAgent', SYSTEM_PROMPT, undefined, model)\n this.video = video\n }\n\n protected resetForRetry(): void {\n this.videoDuration = 0\n this.removals = []\n this.renderPromise = null\n this.outputPath = ''\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: 'add_cuts',\n description:\n 'Add one or more regions to remove from the video. ' +\n 'You can call this multiple times to build your edit list incrementally as you analyze each section.',\n parameters: ADD_CUTS_SCHEMA,\n handler: async (rawArgs: unknown) =>\n this.handleToolCall('add_cuts', rawArgs as Record<string, unknown>),\n },\n {\n name: 'finalize_cuts',\n description:\n 'Finalize your edit list and trigger video rendering. ' +\n 'Call this ONCE after you have added all cuts with add_cuts. ' +\n 'Adjacent/overlapping cuts will be merged automatically.',\n parameters: { type: 'object', properties: {} },\n handler: async () => this.handleToolCall('finalize_cuts', {}),\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 'add_cuts': {\n const { removals } = args as { removals: Removal[] }\n this.removals.push(...removals)\n logger.info(`[ProducerAgent] Added ${removals.length} cuts (total: ${this.removals.length})`)\n return `Added ${removals.length} cuts. Total queued: ${this.removals.length}. Call add_cuts again for more, or finalize_cuts when done.`\n }\n\n case 'finalize_cuts': {\n this.removals = mergeRemovals(this.removals)\n logger.info(`[ProducerAgent] Finalized ${this.removals.length} cuts (after merging), starting render`)\n\n // Build keepSegments and start rendering (don't await — save promise)\n const sortedRemovals = [...this.removals].sort((a, b) => a.start - b.start)\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 const totalRemoval = this.removals.reduce((sum, r) => sum + (r.end - r.start), 0)\n logger.info(\n `[ProducerAgent] ${this.removals.length} removals → ${keepSegments.length} keep segments, removing ${totalRemoval.toFixed(1)}s`,\n )\n\n this.renderPromise = singlePassEdit(this.video.videoPath, keepSegments, this.outputPath)\n return `Rendering started with ${this.removals.length} cuts. The video is being processed in the background.`\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 this.renderPromise = null\n this.outputPath = outputPath\n\n const prompt = `Clean this video by removing unwanted segments.\n\n**Video:** ${this.video.videoPath}\n\nGet the video info, editorial direction, and transcript. Analyze them together, then add your cuts and finalize.`\n\n try {\n const response = await this.run(prompt)\n logger.info(`[ProducerAgent] Agent conversation complete for ${this.video.videoPath}`)\n\n // Wait for render if finalize_cuts was called\n if (this.renderPromise) {\n await this.renderPromise\n logger.info(`[ProducerAgent] Render complete: ${outputPath}`)\n\n const sortedRemovals = [...this.removals].sort((a, b) => a.start - b.start)\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 return {\n summary: response,\n outputPath,\n success: true,\n editCount: this.removals.length,\n removals: sortedRemovals.map(r => ({ start: r.start, end: r.end })),\n keepSegments,\n }\n }\n\n // Agent didn't finalize — no cuts planned\n logger.info(`[ProducerAgent] No cuts finalized — 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 } 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","import type { ToolWithHandler } from '../L3-services/llm/providerFactory.js'\nimport { writeTextFile, ensureDirectory } from '../L1-infra/fileSystem/fileSystem.js'\nimport { join } from '../L1-infra/paths/paths.js'\n\nimport { BaseAgent } from './BaseAgent'\nimport { captureFrame } from '../L3-services/videoOperations/videoOperations.js'\nimport logger from '../L1-infra/logger/configLogger'\nimport { getBrandConfig } from '../L1-infra/config/brand'\nimport { getConfig } from '../L1-infra/config/environment'\nimport type { VideoFile, Transcript, VideoSummary, VideoSnapshot, ShortClip, Chapter } from '../L0-pure/types/index'\nimport type { Idea } from '../L0-pure/types/index.js'\nimport { buildIdeaContextForSummary } from '../L0-pure/ideaContext/ideaContext.js'\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(\n shortsInfo: string,\n socialPostsInfo: string,\n captionsInfo: string,\n chaptersInfo: string,\n ideaContext = '',\n): 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.${ideaContext}\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 protected resetForRetry(): void {\n this.snapshots = []\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 ideas?: Idea[],\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(\n shortsInfo,\n socialPostsInfo,\n captionsInfo,\n chaptersInfo,\n ideas?.length ? buildIdeaContextForSummary(ideas) : '',\n )\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 '../L3-services/llm/providerFactory.js'\nimport { ensureDirectorySync, writeTextFileSync } from '../L1-infra/fileSystem/fileSystem.js'\nimport { join, dirname } from '../L1-infra/paths/paths.js'\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../L1-infra/logger/configLogger'\nimport type { MCPServerConfig } from '../L3-services/llm/providerFactory.js'\nimport { getConfig } from '../L1-infra/config/environment.js'\nimport {\n Platform,\n ShortClip,\n SocialPost,\n Transcript,\n VideoFile,\n VideoSummary,\n} from '../L0-pure/types/index'\nimport type { Idea } from '../L0-pure/types/index.js'\nimport { buildIdeaContextForPosts } from '../L0-pure/ideaContext/ideaContext.js'\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(systemPrompt: string = SYSTEM_PROMPT, model?: string) {\n super('SocialMediaAgent', systemPrompt, undefined, model)\n }\n\n protected resetForRetry(): void {\n this.collectedPosts = []\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 for (const post of posts) {\n if (post.platform.toLowerCase() === 'instagram' && post.hashtags.length > 30) {\n logger.warn(`[SocialMediaAgent] Instagram post has ${post.hashtags.length} hashtags, trimming to 30`)\n post.hashtags = post.hashtags.slice(0, 30)\n }\n }\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 summary?: VideoSummary,\n): Promise<SocialPost[]> {\n const agent = new SocialMediaAgent(undefined, 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 messageParts = [\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\n // Include broader video context when available\n if (summary) {\n messageParts.push(\n '',\n '## Broader Video Context',\n `This clip is from a longer video titled \"${summary.title}\".`,\n `**Video overview:** ${summary.overview}`,\n `**Key topics covered in the full video:** ${summary.keyTopics.join(', ')}`,\n '',\n 'Use this context to position the clip within the larger narrative. The post should tease the broader topic while highlighting what makes this specific clip compelling on its own.',\n )\n }\n\n messageParts.push(\n '',\n '## Relevant Transcript',\n relevantText.slice(0, 3000),\n )\n\n const userMessage = messageParts.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 ideas?: Idea[],\n): Promise<SocialPost[]> {\n const systemPrompt = SYSTEM_PROMPT + (ideas?.length ? buildIdeaContextForPosts(ideas) : '')\n const agent = new SocialMediaAgent(systemPrompt, 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 '../L3-services/llm/providerFactory.js'\nimport { BaseAgent } from './BaseAgent'\nimport logger from '../L1-infra/logger/configLogger'\nimport { getBrandConfig } from '../L1-infra/config/brand'\nimport { getConfig } from '../L1-infra/config/environment.js'\nimport type { Transcript, VideoFile, VideoSummary } from '../L0-pure/types/index'\nimport type { Idea } from '../L0-pure/types/index.js'\nimport { buildIdeaContextForBlog } from '../L0-pure/ideaContext/ideaContext.js'\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(ideaContext = ''): 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}).${ideaContext}\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(systemPrompt: string = buildSystemPrompt(), model?: string) {\n super('BlogAgent', systemPrompt, 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 ideas?: Idea[],\n): Promise<string> {\n const systemPrompt = buildSystemPrompt(ideas?.length ? buildIdeaContextForBlog(ideas) : '')\n const agent = new BlogAgent(systemPrompt, 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 return renderBlogMarkdown(blogContent)\n } finally {\n await agent.destroy()\n }\n}\n","import { readJsonFile, writeJsonFile, fileExistsSync } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join } from '../../L1-infra/paths/paths.js'\nimport { getConfig } from '../../L1-infra/config/environment.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type VideoStatus = 'pending' | 'processing' | 'completed' | 'failed'\n\nexport interface VideoState {\n status: VideoStatus\n sourcePath: string\n startedAt?: string\n completedAt?: string\n error?: string\n}\n\nexport interface ProcessingStateData {\n videos: Record<string, VideoState>\n}\n\n// ── State file path ──────────────────────────────────────────────────────────\n\nfunction getStatePath(): string {\n const config = getConfig()\n return join(config.OUTPUT_DIR, 'processing-state.json')\n}\n\n// ── Read / Write ─────────────────────────────────────────────────────────────\n\nasync function readState(): Promise<ProcessingStateData> {\n const statePath = getStatePath()\n if (!fileExistsSync(statePath)) {\n return { videos: {} }\n }\n return readJsonFile<ProcessingStateData>(statePath, { videos: {} })\n}\n\nasync function writeState(state: ProcessingStateData): Promise<void> {\n const statePath = getStatePath()\n await writeJsonFile(statePath, state)\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/** Get the processing status for a specific video slug. */\nexport async function getVideoStatus(slug: string): Promise<VideoState | undefined> {\n const state = await readState()\n return state.videos[slug]\n}\n\n/** Get all videos with a specific status. */\nexport async function getVideosByStatus(status: VideoStatus): Promise<Record<string, VideoState>> {\n const state = await readState()\n const result: Record<string, VideoState> = {}\n for (const [slug, video] of Object.entries(state.videos)) {\n if (video.status === status) {\n result[slug] = video\n }\n }\n return result\n}\n\n/** Get all unprocessed videos (pending or failed). */\nexport async function getUnprocessed(): Promise<Record<string, VideoState>> {\n const state = await readState()\n const result: Record<string, VideoState> = {}\n for (const [slug, video] of Object.entries(state.videos)) {\n if (video.status === 'pending' || video.status === 'failed') {\n result[slug] = video\n }\n }\n return result\n}\n\n/** Check if a video has been completed. */\nexport async function isCompleted(slug: string): Promise<boolean> {\n const status = await getVideoStatus(slug)\n return status?.status === 'completed'\n}\n\n/** Mark a video as pending (queued for processing). */\nexport async function markPending(slug: string, sourcePath: string): Promise<void> {\n const state = await readState()\n state.videos[slug] = {\n status: 'pending',\n sourcePath,\n }\n await writeState(state)\n logger.info(`[ProcessingState] Marked pending: ${slug}`)\n}\n\n/** Mark a video as currently processing. */\nexport async function markProcessing(slug: string): Promise<void> {\n const state = await readState()\n const existing = state.videos[slug]\n if (!existing) {\n logger.warn(`[ProcessingState] Cannot mark processing — unknown slug: ${slug}`)\n return\n }\n state.videos[slug] = {\n ...existing,\n status: 'processing',\n startedAt: new Date().toISOString(),\n }\n await writeState(state)\n logger.info(`[ProcessingState] Marked processing: ${slug}`)\n}\n\n/** Mark a video as completed. */\nexport async function markCompleted(slug: string): Promise<void> {\n const state = await readState()\n const existing = state.videos[slug]\n if (!existing) {\n logger.warn(`[ProcessingState] Cannot mark completed — unknown slug: ${slug}`)\n return\n }\n state.videos[slug] = {\n ...existing,\n status: 'completed',\n completedAt: new Date().toISOString(),\n error: undefined,\n }\n await writeState(state)\n logger.info(`[ProcessingState] Marked completed: ${slug}`)\n}\n\n/** Mark a video as failed with an error message. */\nexport async function markFailed(slug: string, error: string): Promise<void> {\n const state = await readState()\n const existing = state.videos[slug]\n if (!existing) {\n logger.warn(`[ProcessingState] Cannot mark failed — unknown slug: ${slug}`)\n return\n }\n state.videos[slug] = {\n ...existing,\n status: 'failed',\n completedAt: new Date().toISOString(),\n error,\n }\n await writeState(state)\n logger.info(`[ProcessingState] Marked failed: ${slug} — ${error}`)\n}\n\n/** Get the full state (for debugging/inspection). */\nexport async function getFullState(): Promise<ProcessingStateData> {\n return readState()\n}\n","import { execCommandSync } from '../../L1-infra/process/process.js'\nimport { getConfig } from '../../L1-infra/config/environment'\nimport logger from '../../L1-infra/logger/configLogger'\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, fileExists } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join, dirname } from '../../L1-infra/paths/paths.js'\nimport logger from '../../L1-infra/logger/configLogger'\nimport { PLATFORM_CHAR_LIMITS, toLatePlatform } from '../../L0-pure/types/index'\nimport { Platform } from '../../L0-pure/types/index'\nimport type { VideoFile, ShortClip, MediumClip, SocialPost } from '../../L0-pure/types/index'\nimport { getMediaRule, platformAcceptsMedia } from '../socialPosting/platformContentStrategy'\nimport type { ClipType } from '../socialPosting/platformContentStrategy'\nimport { createItem, itemExists, type QueueItemMetadata } from '../postStore/postStore'\nimport { generateImage } from '../imageGeneration/imageGeneration.js'\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 ideaIds?: string[],\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 // Generate a cover image for platform+clipType combos that are text-only\n let mediaType: 'video' | 'image' = 'video'\n if (!platformAcceptsMedia(post.platform, clipType)) {\n const coverDir = clipSlug && clipSlug !== video.slug\n ? join(video.repoPath, clipType === 'short' ? 'shorts' : 'medium-clips', clipSlug)\n : video.repoPath\n const coverPath = join(coverDir, 'cover.png')\n\n try {\n if (!await fileExists(coverPath)) {\n const stripped = stripFrontmatter(post.content)\n const textForPrompt = stripped.trim().length > 0 ? stripped : post.content\n const prompt = buildTextOnlyCoverPrompt(textForPrompt)\n await generateImage(prompt, coverPath, { size: '1024x1024', quality: 'high' })\n }\n mediaPath = coverPath\n mediaType = 'image'\n } catch {\n logger.warn(`Failed to generate cover image for ${post.platform}, falling back to text-only`)\n mediaPath = null\n }\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 mediaType,\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 ideaIds: ideaIds && ideaIds.length > 0 ? ideaIds : undefined,\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\n// ============================================================================\n// COVER IMAGE PROMPT\n// ============================================================================\n\nfunction buildTextOnlyCoverPrompt(postContent: string): string {\n const essence = postContent.substring(0, 500)\n return `Create a professional, eye-catching social media cover image for a tech content post. The image should visually represent the following topic:\n\n\"${essence}\"\n\nStyle requirements:\n- Modern, clean design with bold visual elements\n- Tech-focused aesthetic with code elements, circuit patterns, or abstract tech visuals\n- Vibrant colors that stand out in a social media feed\n- No text or words in the image — purely visual\n- Professional quality suitable for LinkedIn, YouTube, or blog headers\n- 1:1 square aspect ratio composition`\n}\n","import { Platform } from '../../L0-pure/types/index'\nimport type { VideoPlatform } from '../../L0-pure/types/index'\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 | — (text-only) | original, captioned | original, captioned |\n * | TikTok | — (not scheduled) | 9:16 portrait | 9:16 portrait |\n * | Instagram | original, captioned | 9:16 reels | original, captioned |\n * | X/Twitter | — (too long) | original, captioned | 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 short: { captions: true, variantKey: null },\n 'medium-clip': { captions: true, variantKey: null },\n },\n [Platform.TikTok]: {\n short: { captions: true, variantKey: 'tiktok' },\n 'medium-clip': { captions: true, variantKey: 'tiktok' },\n },\n [Platform.Instagram]: {\n video: { captions: true, variantKey: null },\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 'medium-clip': { 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 { fromLatePlatform } from '../../L0-pure/types/index.js'\nimport { getConfig } from '../../L1-infra/config/environment'\nimport logger from '../../L1-infra/logger/configLogger'\nimport { readTextFile, writeTextFile, writeJsonFile, ensureDirectory, copyFile, fileExists, listDirectoryWithTypes, removeDirectory, renameFile, copyDirectory } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join, basename, resolve, sep, extname } from '../../L1-infra/paths/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 /** Type of media attached: video file or generated image */\n mediaType?: 'video' | 'image'\n /** Content idea IDs that influenced this queue item */\n ideaIds?: string[]\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 mediaType?: 'video' | 'image'\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\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 // Check for media file (could be video or image)\n const videoPath = join(folderPath, 'media.mp4')\n const imagePath = join(folderPath, 'media.png')\n let mediaPath: string | null = null\n let hasMedia = false\n\n if (await fileExists(videoPath)) {\n mediaPath = videoPath\n hasMedia = true\n } else if (await fileExists(imagePath)) {\n mediaPath = imagePath\n hasMedia = true\n }\n\n return {\n id,\n metadata,\n postContent,\n hasMedia,\n mediaPath,\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 mediaType: first.metadata.mediaType,\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 ext = mediaSourcePath ? extname(mediaSourcePath) : '.mp4'\n const mediaFilename = `media${ext}`\n const mediaPath = join(folderPath, mediaFilename)\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 mediaType: updates.metadata.mediaType ?? existing.metadata.mediaType,\n ideaIds: Array.isArray(updates.metadata.ideaIds)\n ? updates.metadata.ideaIds.map(String)\n : (Array.isArray(existing.metadata.ideaIds) ? existing.metadata.ideaIds.map(String) : undefined),\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 // Trigger idea status updates when content is published\n if (item.metadata.ideaIds && item.metadata.ideaIds.length > 0) {\n try {\n const { markPublished } = await import('../ideation/ideaService.js')\n for (const ideaId of item.metadata.ideaIds) {\n await markPublished(ideaId, {\n clipType: item.metadata.clipType,\n platform: fromLatePlatform(item.metadata.platform),\n queueItemId: id,\n publishedAt: now,\n publishedUrl: item.metadata.publishedUrl ?? undefined,\n })\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.warn(`Failed to update idea status for ${id}: ${msg}`)\n }\n }\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 mediaType: item.metadata.mediaType,\n ideaIds: Array.isArray(item.metadata.ideaIds) ? item.metadata.ideaIds.map(String) : undefined,\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 getScheduledItemsByIdeaIds(ideaIds: string[]): Promise<QueueItem[]> {\n if (ideaIds.length === 0) return []\n\n const ideaIdSet = new Set(ideaIds)\n const [pendingItems, publishedItems] = await Promise.all([\n getPendingItems(),\n getPublishedItems(),\n ])\n\n return [...pendingItems, ...publishedItems].filter(item =>\n item.metadata.ideaIds?.some(id => ideaIdSet.has(id)) ?? false,\n )\n}\n\nexport async function getPublishedItemByLatePostId(latePostId: string): Promise<QueueItem | null> {\n const publishedItems = await getPublishedItems()\n return publishedItems.find(item => item.metadata.latePostId === latePostId) ?? null\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","/**\n * L4 bridge for pipeline infrastructure services.\n *\n * Wraps L3 services used by the pipeline orchestrator (L6) via L5 loaders,\n * maintaining strict layer hierarchy: L6 → L5 → L4 → L3.\n */\n\nimport { costTracker as _costTracker } from '../L3-services/costTracking/costTracker.js'\nimport { markPending as _markPending, markProcessing as _markProcessing, markCompleted as _markCompleted, markFailed as _markFailed } from '../L3-services/processingState/processingState.js'\nimport { commitAndPush as _commitAndPush } from '../L3-services/gitOperations/gitOperations.js'\nimport { buildPublishQueue as _buildPublishQueue } from '../L3-services/queueBuilder/queueBuilder.js'\n\n// Re-export types (exempt from layer rules)\nexport type { CostReport } from '../L3-services/costTracking/costTracker.js'\nexport type { QueueBuildResult } from '../L3-services/queueBuilder/queueBuilder.js'\n\n// Cost tracking — proxy object wrapping L3 singleton\nexport const costTracker = {\n reset: (...args: Parameters<typeof _costTracker.reset>) => _costTracker.reset(...args),\n setStage: (...args: Parameters<typeof _costTracker.setStage>) => _costTracker.setStage(...args),\n getReport: (...args: Parameters<typeof _costTracker.getReport>) => _costTracker.getReport(...args),\n formatReport: (...args: Parameters<typeof _costTracker.formatReport>) => _costTracker.formatReport(...args),\n recordServiceUsage: (...args: Parameters<typeof _costTracker.recordServiceUsage>) => _costTracker.recordServiceUsage(...args),\n} as const\n\n// Processing state\nexport function markPending(...args: Parameters<typeof _markPending>): ReturnType<typeof _markPending> {\n return _markPending(...args)\n}\n\nexport function markProcessing(...args: Parameters<typeof _markProcessing>): ReturnType<typeof _markProcessing> {\n return _markProcessing(...args)\n}\n\nexport function markCompleted(...args: Parameters<typeof _markCompleted>): ReturnType<typeof _markCompleted> {\n return _markCompleted(...args)\n}\n\nexport function markFailed(...args: Parameters<typeof _markFailed>): ReturnType<typeof _markFailed> {\n return _markFailed(...args)\n}\n\n// Git operations\nexport function commitAndPush(...args: Parameters<typeof _commitAndPush>): ReturnType<typeof _commitAndPush> {\n return _commitAndPush(...args)\n}\n\n// Queue builder\nexport function buildPublishQueue(...args: Parameters<typeof _buildPublishQueue>): ReturnType<typeof _buildPublishQueue> {\n return _buildPublishQueue(...args)\n}\n","import type { ToolWithHandler } from '../L3-services/llm/providerFactory.js'\nimport { BaseAgent } from './BaseAgent.js'\nimport type { EnhancementOpportunity, GeneratedOverlay, OverlayRegion } from '../L0-pure/types/index.js'\nimport { generateImage } from '../L3-services/imageGeneration/imageGeneration.js'\nimport { slugify } from '../L0-pure/text/text.js'\nimport { join } from '../L1-infra/paths/paths.js'\nimport { ensureDirectory } from '../L1-infra/fileSystem/fileSystem.js'\nimport logger from '../L1-infra/logger/configLogger.js'\nimport sharp from 'sharp'\n\nconst SYSTEM_PROMPT = `You are a visual content designer and editorial director for educational video content. You are given an editorial report from a video analyst describing moments in a video where AI-generated image overlays could enhance viewer comprehension.\n\nYour job is to make the FINAL editorial decision for each opportunity:\n1. Decide whether to generate an image or skip the opportunity\n2. Determine the exact timing — when the image should appear and disappear\n3. Choose the optimal screen placement to avoid blocking important content\n4. Write a refined, high-quality image generation prompt\n\nGuidelines for editorial decisions:\n- Only generate images that genuinely add value — quality over quantity\n- Timing should match the speaker's explanation: appear when the topic starts, disappear when they move on\n- Keep display duration between 5-12 seconds — long enough to register, short enough to not overstay\n- Ensure at least 10 seconds gap between consecutive overlays to avoid visual clutter\n- Choose placement regions that avoid the webcam, main content area, and any important UI elements\n- Size should be 15-30% of video width — large enough to see, small enough to not dominate\n\nGuidelines for image prompts:\n- Create clean, professional diagrams and illustrations\n- Use flat design / modern infographic style\n- Include labels and annotations when helpful\n- Avoid photorealistic imagery — prefer stylized educational graphics\n- Keep the image simple and immediately understandable at a glance\n- The image will be shown as a small overlay, so avoid tiny details\n- Use high contrast colors for visibility when overlaid on video\n- No text-heavy images — a few key labels at most\n- Let the image content dictate its natural aspect ratio — don't force square if the content is better as landscape or portrait\n- IMPORTANT: Every image MUST have a solid, opaque background (e.g., white, light gray, dark navy) — never transparent or borderless. The image will be overlaid on top of a video so it needs to stand out with clear visual separation. If the report mentions a dark video background, use a light image background (and vice versa). Add a subtle border or shadow effect in the prompt to ensure the image pops against the video content.\n\nProcess the report and call generate_enhancement for each image worth creating, or call skip_opportunity for those not worth generating.`\n\nconst GENERATE_ENHANCEMENT_SCHEMA = {\n type: 'object',\n properties: {\n prompt: {\n type: 'string',\n description: 'A refined, high-quality image generation prompt describing the visual to create',\n },\n timestampStart: {\n type: 'number',\n description: 'When to start showing the image (seconds from video start)',\n },\n timestampEnd: {\n type: 'number',\n description: 'When to stop showing the image (seconds from video start). Should be 5-12 seconds after timestampStart.',\n },\n region: {\n type: 'string',\n enum: ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'center-right', 'center-left'],\n description: 'Screen region for placement, chosen to avoid blocking important content',\n },\n sizePercent: {\n type: 'number',\n description: 'Image width as percentage of video width (15-30)',\n },\n topic: {\n type: 'string',\n description: 'Brief label for what this image illustrates',\n },\n reason: {\n type: 'string',\n description: 'Why this visual enhancement helps the viewer',\n },\n },\n required: ['prompt', 'timestampStart', 'timestampEnd', 'region', 'sizePercent', 'topic', 'reason'],\n} as const\n\nconst SKIP_OPPORTUNITY_SCHEMA = {\n type: 'object',\n properties: {\n topic: {\n type: 'string',\n description: 'The topic from the report that is being skipped',\n },\n reason: {\n type: 'string',\n description: 'Why this opportunity should be skipped',\n },\n },\n required: ['topic', 'reason'],\n} as const\n\nclass GraphicsAgent extends BaseAgent {\n private overlays: GeneratedOverlay[] = []\n private enhancementsDir = ''\n private imageIndex = 0\n\n constructor(model?: string) {\n super('GraphicsAgent', SYSTEM_PROMPT, undefined, model)\n }\n\n protected resetForRetry(): void {\n this.overlays = []\n this.imageIndex = 0\n }\n\n setContext(enhancementsDir: string): void {\n this.enhancementsDir = enhancementsDir\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'generate_enhancement',\n description:\n 'Generate an AI image overlay for a specific moment in the video. You decide the timing, placement, and prompt.',\n parameters: GENERATE_ENHANCEMENT_SCHEMA,\n handler: async (args: unknown) =>\n this.handleToolCall('generate_enhancement', args as Record<string, unknown>),\n },\n {\n name: 'skip_opportunity',\n description:\n 'Skip an enhancement opportunity from the report that is not worth generating.',\n parameters: SKIP_OPPORTUNITY_SCHEMA,\n handler: async (args: unknown) =>\n this.handleToolCall('skip_opportunity', args as Record<string, unknown>),\n },\n ]\n }\n\n protected async handleToolCall(\n toolName: string,\n args: Record<string, unknown>,\n ): Promise<unknown> {\n if (toolName === 'generate_enhancement') {\n const prompt = args.prompt as string\n const timestampStart = args.timestampStart as number\n const timestampEnd = args.timestampEnd as number\n const region = args.region as OverlayRegion\n const sizePercent = Math.min(30, Math.max(15, args.sizePercent as number))\n const topic = args.topic as string\n const reason = args.reason as string\n\n const slug = slugify(topic, { lower: true, strict: true })\n const filename = `${this.imageIndex}-${slug}.png`\n const outputPath = join(this.enhancementsDir, filename)\n\n try {\n // Let GPT decide the aspect ratio by using size: 'auto'\n await generateImage(prompt, outputPath, { size: 'auto' })\n\n // Read actual image dimensions from the generated file\n const metadata = await sharp(outputPath).metadata()\n const width = metadata.width ?? 1024\n const height = metadata.height ?? 1024\n\n const opportunity: EnhancementOpportunity = {\n timestampStart,\n timestampEnd,\n topic,\n imagePrompt: prompt,\n reason,\n placement: { region, avoidAreas: [], sizePercent },\n confidence: 1.0,\n }\n\n const overlay: GeneratedOverlay = {\n opportunity,\n imagePath: outputPath,\n width,\n height,\n }\n this.overlays.push(overlay)\n this.imageIndex++\n logger.info(`Generated enhancement image: ${filename} (${width}x${height})`)\n return { success: true, imagePath: outputPath, dimensions: `${width}x${height}` }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`Failed to generate image for \"${topic}\": ${message}`)\n return { error: message }\n }\n }\n\n if (toolName === 'skip_opportunity') {\n const topic = args.topic as string\n const reason = args.reason as string\n logger.info(`Skipped enhancement opportunity \"${topic}\": ${reason}`)\n return { success: true, skipped: true }\n }\n\n throw new Error(`Unknown tool: ${toolName}`)\n }\n\n getOverlays(): GeneratedOverlay[] {\n return this.overlays\n }\n}\n\n/**\n * Generate enhancement images based on Gemini's editorial report.\n * The GraphicsAgent makes all editorial decisions: timing, placement, and image content.\n *\n * @param enhancementReport - Raw editorial report from Gemini analysis\n * @param enhancementsDir - Directory to save generated images\n * @param videoDuration - Video duration in seconds (for context)\n * @param model - LLM model for the agent\n * @returns Generated overlays ready for FFmpeg compositing\n */\nexport async function generateEnhancementImages(\n enhancementReport: string,\n enhancementsDir: string,\n videoDuration: number,\n model?: string,\n): Promise<GeneratedOverlay[]> {\n await ensureDirectory(enhancementsDir)\n\n const agent = new GraphicsAgent(model)\n agent.setContext(enhancementsDir)\n\n try {\n const userMessage = `Here is the editorial report from our video analyst. The video is ${videoDuration.toFixed(1)} seconds long.\n\nReview each opportunity and make your editorial decision — generate an image or skip it.\n\n---\n\n${enhancementReport}`\n\n await agent.run(userMessage)\n return agent.getOverlays()\n } finally {\n await agent.destroy()\n }\n}\n","import { analyzeVideoForEnhancements } from '../L4-agents/analysisServiceBridge.js'\nimport { generateEnhancementImages } from '../L4-agents/GraphicsAgent.js'\nimport { compositeOverlays } from '../L4-agents/videoServiceBridge.js'\nimport { getModelForAgent } from '../L1-infra/config/modelConfig.js'\nimport { ensureDirectory, writeJsonFile } from '../L1-infra/fileSystem/fileSystem.js'\nimport { join } from '../L1-infra/paths/paths.js'\nimport logger from '../L1-infra/logger/configLogger.js'\nimport type { VideoFile, Transcript, VisualEnhancementResult } from '../L0-pure/types/index.js'\n\n/**\n * Run the visual enhancement stage.\n *\n * 1. Gemini analyzes the video to find enhancement opportunities\n * 2. GraphicsAgent generates images for each opportunity\n * 3. FFmpeg composites the images onto the video\n *\n * @param videoPath - Path to the cleaned (or original) video\n * @param transcript - Transcript for context (adjusted if silence was removed)\n * @param video - VideoFile metadata (for dimensions, slug, directory)\n * @returns Enhanced video path and overlay metadata, or undefined if no enhancements were made\n */\nexport async function enhanceVideo(\n videoPath: string,\n transcript: Transcript,\n video: VideoFile,\n): Promise<VisualEnhancementResult | undefined> {\n const enhancementsDir = join(video.videoDir, 'enhancements')\n await ensureDirectory(enhancementsDir)\n\n // Step 1: Gemini enhancement analysis (returns raw editorial report)\n logger.info('[VisualEnhancement] Step 1: Analyzing video for enhancement opportunities...')\n const enhancementReport = await analyzeVideoForEnhancements(\n videoPath,\n video.duration,\n transcript.text,\n )\n\n if (!enhancementReport || enhancementReport.trim().length === 0) {\n logger.info('[VisualEnhancement] No enhancement report generated — skipping')\n return undefined\n }\n\n logger.info(`[VisualEnhancement] Received editorial report (${enhancementReport.length} chars)`)\n\n // Step 2: GraphicsAgent makes editorial decisions and generates images\n logger.info('[VisualEnhancement] Step 2: GraphicsAgent making editorial decisions and generating images...')\n const overlays = await generateEnhancementImages(\n enhancementReport,\n enhancementsDir,\n video.duration,\n getModelForAgent('GraphicsAgent'),\n )\n\n if (overlays.length === 0) {\n logger.info('[VisualEnhancement] GraphicsAgent generated no images — skipping compositing')\n return undefined\n }\n\n logger.info(`[VisualEnhancement] Generated ${overlays.length} enhancement images`)\n\n await writeJsonFile(join(video.videoDir, 'enhancements-plan.json'), overlays)\n\n // Step 3: Composite overlays onto video\n logger.info('[VisualEnhancement] Step 3: Compositing overlays onto video...')\n const outputPath = join(video.videoDir, `${video.slug}-enhanced.mp4`)\n\n const videoWidth = video.layout?.width ?? 1920\n const videoHeight = video.layout?.height ?? 1080\n\n const enhancedVideoPath = await compositeOverlays(\n videoPath,\n overlays,\n outputPath,\n videoWidth,\n videoHeight,\n )\n\n logger.info(`[VisualEnhancement] Enhanced video created: ${enhancedVideoPath}`)\n\n let totalImageCost = 0\n for (const overlay of overlays) {\n totalImageCost += 0.07 // estimated per image (high quality)\n }\n\n return {\n enhancedVideoPath,\n overlays,\n analysisTokens: 0, // tracked by costTracker internally\n imageGenCost: totalImageCost,\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 '../../L1-infra/config/environment.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { getFileStats, openReadStream } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { Readable } from '../../L1-infra/http/network.js'\nimport { basename, extname } from '../../L1-infra/paths/paths.js'\nimport { fetchRaw } from '../../L1-infra/http/httpClient.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 fetchRaw(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 /** Reschedule a post and ensure it transitions out of draft status. */\n async schedulePost(postId: string, scheduledFor: string): Promise<LatePost> {\n return this.updatePost(postId, { scheduledFor, isDraft: false })\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 fetchRaw(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 /**\n * Fetch posts with pagination, iterating pages until all results are collected.\n * Supports filtering by status and platform.\n */\n async listPosts(options: {\n status?: string\n platform?: string\n limit?: number\n } = {}): Promise<LatePost[]> {\n const limit = options.limit ?? 100\n const allPosts: LatePost[] = []\n let page = 1\n\n while (true) {\n const params = new URLSearchParams()\n if (options.status) params.set('status', options.status)\n if (options.platform) params.set('platform', options.platform)\n params.set('limit', String(limit))\n params.set('page', String(page))\n\n const data = await this.request<{ posts?: LatePost[]; data?: LatePost[] }>(\n `/posts?${params}`,\n )\n const posts = data.posts ?? data.data ?? []\n allPosts.push(...posts)\n\n if (posts.length < limit) break\n page++\n }\n\n return allPosts\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","/**\n * L3 service wrapper for the Late API client.\n *\n * Wraps the L2 LateApiClient constructor so that L7 (and higher layers)\n * can access Late functionality without importing L2 directly.\n */\nimport { LateApiClient as _LateApiClient } from '../../L2-clients/late/lateApi.js'\n\nexport function createLateApiClient(\n ...args: ConstructorParameters<typeof _LateApiClient>\n): InstanceType<typeof _LateApiClient> {\n return new _LateApiClient(...args)\n}\n\nexport type { LateApiClient } from '../../L2-clients/late/lateApi.js'\nexport type {\n LateAccount,\n LateProfile,\n LatePost,\n LateMediaPresignResult,\n LateMediaUploadResult,\n CreatePostParams,\n} from '../../L2-clients/late/lateApi.js'\n","import { LateApiClient, type LatePost } from '../../L2-clients/late/lateApi.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport {\n getPublishedItemByLatePostId,\n getPublishedItems,\n getScheduledItemsByIdeaIds,\n type QueueItem,\n} from '../postStore/postStore.js'\nimport {\n getDisplacementConfig,\n getIdeaSpacingConfig,\n getPlatformSchedule,\n loadScheduleConfig,\n type DayOfWeek,\n type PlatformSchedule,\n} from './scheduleConfig.js'\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\nconst MAX_LOOKAHEAD_DAYS = 730\nconst DEFAULT_IDEA_WINDOW_DAYS = 14\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst HOUR_MS = 60 * 60 * 1000\n\ninterface BookedSlot {\n scheduledFor: string\n source: 'late' | 'local'\n postId?: string\n itemId?: string\n platform: string\n status?: string\n}\n\nexport interface SlotOptions {\n ideaIds?: string[]\n publishBy?: string\n}\n\nexport interface SlotResult {\n slot: string\n displaced?: {\n postId: string\n originalSlot: string\n newSlot: string\n }\n}\n\ntype CandidateGuard = (candidateMs: number, candidatePlatform: string) => boolean\n\ninterface IdeaReference {\n platform: string\n scheduledFor: string\n}\n\ninterface SearchWindow {\n emptyWindowEndMs?: number\n displacementWindowEndMs?: number\n}\n\ninterface FindEmptySlotParams {\n platformConfig: PlatformSchedule\n timezone: string\n bookedDatetimes: ReadonlySet<number>\n platform: string\n searchFromMs: number\n includeSearchDay?: boolean\n maxCandidateMs?: number\n passesCandidate?: CandidateGuard\n}\n\ninterface TryDisplacementParams {\n bookedSlots: readonly BookedSlot[]\n platform: string\n platformConfig: PlatformSchedule\n timezone: string\n bookedDatetimes: Set<number>\n options: SlotOptions\n nowMs: number\n maxCandidateMs?: number\n passesSpacing?: CandidateGuard\n}\n\nfunction sanitizeLogValue(value: string): string {\n return value.replace(/[\\r\\n]/g, '')\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((part) => part.type === 'timeZoneName')\n const match = tzPart?.value?.match(/GMT([+-]\\d{2}:\\d{2})/)\n if (match) return match[1]\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 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((part) => part.type === 'year')?.value\n const monthPart = parts.find((part) => part.type === 'month')?.value\n const dayPart = parts.find((part) => part.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',\n mon: 'mon',\n tue: 'tue',\n wed: 'wed',\n thu: 'thu',\n fri: 'fri',\n sat: 'sat',\n }\n return map[short] ?? 'mon'\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 scheduledPlatform of post.platforms) {\n if (!platform || scheduledPlatform.platform === platform) {\n slots.push({\n scheduledFor: post.scheduledFor,\n source: 'late',\n postId: post._id,\n platform: scheduledPlatform.platform,\n status: post.status,\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\nfunction buildIdeaReferences(\n sameIdeaPosts: readonly QueueItem[],\n allBookedSlots: readonly BookedSlot[],\n): IdeaReference[] {\n const lateSlotsByPostId = new Map<string, BookedSlot[]>()\n const localSlotsByItemId = new Map<string, BookedSlot[]>()\n\n for (const slot of allBookedSlots) {\n if (slot.postId) {\n const slots = lateSlotsByPostId.get(slot.postId) ?? []\n slots.push(slot)\n lateSlotsByPostId.set(slot.postId, slots)\n }\n if (slot.itemId) {\n const slots = localSlotsByItemId.get(slot.itemId) ?? []\n slots.push(slot)\n localSlotsByItemId.set(slot.itemId, slots)\n }\n }\n\n const references: IdeaReference[] = []\n const seen = new Set<string>()\n const addReference = (platformName: string, scheduledFor: string | null | undefined): void => {\n if (!scheduledFor) return\n const key = `${platformName}@${scheduledFor}`\n if (seen.has(key)) return\n seen.add(key)\n references.push({ platform: platformName, scheduledFor })\n }\n\n for (const item of sameIdeaPosts) {\n addReference(item.metadata.platform, item.metadata.scheduledFor)\n\n if (item.metadata.latePostId) {\n for (const slot of lateSlotsByPostId.get(item.metadata.latePostId) ?? []) {\n addReference(slot.platform, slot.scheduledFor)\n }\n }\n\n for (const slot of localSlotsByItemId.get(item.id) ?? []) {\n addReference(slot.platform, slot.scheduledFor)\n }\n }\n\n return references\n}\n\nfunction createSpacingGuard(\n ideaReferences: readonly IdeaReference[],\n samePlatformHours: number,\n crossPlatformHours: number,\n): CandidateGuard {\n const samePlatformWindowMs = samePlatformHours * HOUR_MS\n const crossPlatformWindowMs = crossPlatformHours * HOUR_MS\n\n return (candidateMs: number, candidatePlatform: string): boolean => {\n for (const reference of ideaReferences) {\n const referenceMs = normalizeDateTime(reference.scheduledFor)\n const diff = Math.abs(candidateMs - referenceMs)\n\n if (reference.platform === candidatePlatform && diff < samePlatformWindowMs) {\n return false\n }\n if (diff < crossPlatformWindowMs) {\n return false\n }\n }\n\n return true\n }\n}\n\nfunction resolveSearchWindow(nowMs: number, options?: SlotOptions): SearchWindow {\n const defaultWindowEndMs = nowMs + DEFAULT_IDEA_WINDOW_DAYS * DAY_MS\n const publishBy = options?.publishBy\n\n if (!publishBy) {\n return {\n emptyWindowEndMs: defaultWindowEndMs,\n displacementWindowEndMs: defaultWindowEndMs,\n }\n }\n\n const publishByMs = normalizeDateTime(publishBy)\n if (Number.isNaN(publishByMs)) {\n logger.warn(`Invalid publishBy \"${sanitizeLogValue(publishBy)}\" provided; scheduling normally without urgency bias`)\n return {}\n }\n\n const daysUntilPublishBy = (publishByMs - nowMs) / DAY_MS\n if (daysUntilPublishBy <= 0) {\n logger.warn(`publishBy \"${sanitizeLogValue(publishBy)}\" has already passed; scheduling normally without urgency bias`)\n return {}\n }\n\n if (daysUntilPublishBy < 3) {\n logger.debug(`Urgent publishBy \"${sanitizeLogValue(publishBy)}\"; prioritizing earliest displaceable slot`)\n }\n\n return {\n emptyWindowEndMs: publishByMs,\n displacementWindowEndMs:\n daysUntilPublishBy < 7\n ? Math.min(publishByMs, nowMs + 3 * DAY_MS)\n : publishByMs,\n }\n}\n\nfunction findEmptySlot({\n platformConfig,\n timezone,\n bookedDatetimes,\n platform,\n searchFromMs,\n includeSearchDay = false,\n maxCandidateMs,\n passesCandidate,\n}: FindEmptySlotParams): string | null {\n if (maxCandidateMs !== undefined && maxCandidateMs < searchFromMs) {\n return null\n }\n\n const baseDate = new Date(searchFromMs)\n const initialOffset = includeSearchDay ? 0 : 1\n let maxDayOffset = MAX_LOOKAHEAD_DAYS\n\n if (maxCandidateMs !== undefined) {\n maxDayOffset = Math.min(\n MAX_LOOKAHEAD_DAYS,\n Math.max(initialOffset, Math.ceil((maxCandidateMs - searchFromMs) / DAY_MS)),\n )\n }\n\n let startOffset = initialOffset\n while (startOffset <= maxDayOffset) {\n const endOffset = Math.min(startOffset + CHUNK_DAYS - 1, maxDayOffset)\n const candidates: string[] = []\n\n for (let dayOffset = startOffset; dayOffset <= endOffset; dayOffset++) {\n const candidateDate = new Date(baseDate)\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\n const candidate = buildSlotDatetime(candidateDate, slot.time, timezone)\n const candidateMs = normalizeDateTime(candidate)\n if (candidateMs <= searchFromMs) continue\n if (maxCandidateMs !== undefined && candidateMs > maxCandidateMs) continue\n if (bookedDatetimes.has(candidateMs)) continue\n if (passesCandidate && !passesCandidate(candidateMs, platform)) continue\n\n candidates.push(candidate)\n }\n }\n\n candidates.sort((left, right) => normalizeDateTime(left) - normalizeDateTime(right))\n if (candidates.length > 0) {\n return candidates[0]\n }\n\n startOffset = endOffset + 1\n }\n\n return null\n}\n\nasync function tryDisplacement({\n bookedSlots,\n platform,\n platformConfig,\n timezone,\n bookedDatetimes,\n options,\n nowMs,\n maxCandidateMs,\n passesSpacing,\n}: TryDisplacementParams): Promise<SlotResult | null> {\n const displacementConfig = getDisplacementConfig()\n if (!displacementConfig.enabled || !options.ideaIds?.length) {\n return null\n }\n\n const candidateSlots = bookedSlots\n .filter((slot) => {\n const slotMs = normalizeDateTime(slot.scheduledFor)\n if (slotMs <= nowMs) return false\n if (maxCandidateMs !== undefined && slotMs > maxCandidateMs) return false\n return true\n })\n .sort((left, right) => normalizeDateTime(left.scheduledFor) - normalizeDateTime(right.scheduledFor))\n\n const lateClient = new LateApiClient()\n const publishedItemCache = new Map<string, QueueItem | null>()\n\n for (const slot of candidateSlots) {\n if (slot.source !== 'late' || !slot.postId) continue\n\n const candidateMs = normalizeDateTime(slot.scheduledFor)\n if (passesSpacing && !passesSpacing(candidateMs, platform)) continue\n\n let publishedItem = publishedItemCache.get(slot.postId)\n if (publishedItem === undefined) {\n publishedItem = await getPublishedItemByLatePostId(slot.postId)\n publishedItemCache.set(slot.postId, publishedItem)\n }\n\n if (!publishedItem) {\n continue\n }\n\n if (publishedItem.metadata.ideaIds?.length) {\n continue\n }\n\n const displacedPlatformConfig = publishedItem?.metadata.clipType\n ? getPlatformSchedule(platform, publishedItem.metadata.clipType) ?? platformConfig\n : platformConfig\n\n const newSlot = findEmptySlot({\n platformConfig: displacedPlatformConfig,\n timezone,\n bookedDatetimes,\n platform,\n searchFromMs: candidateMs,\n includeSearchDay: true,\n })\n\n if (!newSlot) continue\n\n await lateClient.schedulePost(slot.postId, newSlot)\n logger.info(\n `Displaced post ${sanitizeLogValue(slot.postId)} from ${sanitizeLogValue(slot.scheduledFor)} ` +\n `to ${sanitizeLogValue(newSlot)} for idea-linked content`,\n )\n\n return {\n slot: slot.scheduledFor,\n displaced: {\n postId: slot.postId,\n originalSlot: slot.scheduledFor,\n newSlot,\n },\n }\n }\n\n return null\n}\n\n/**\n * Find the next available posting slot for a platform.\n */\nexport async function findNextSlot(\n platform: string,\n clipType?: string,\n options?: SlotOptions,\n): Promise<string | null> {\n const config = await loadScheduleConfig()\n const platformConfig = getPlatformSchedule(platform, clipType)\n if (!platformConfig) {\n logger.warn(`No schedule config found for platform \"${sanitizeLogValue(platform)}\"`)\n return null\n }\n\n const ideaIds = options?.ideaIds?.filter(Boolean) ?? []\n const isIdeaAware = ideaIds.length > 0\n const nowMs = Date.now()\n const { timezone } = config\n\n const [allBookedSlots, sameIdeaPosts] = await Promise.all([\n isIdeaAware ? buildBookedSlots() : Promise.resolve([] as BookedSlot[]),\n isIdeaAware ? getScheduledItemsByIdeaIds(ideaIds) : Promise.resolve([] as QueueItem[]),\n ])\n\n const bookedSlots = isIdeaAware\n ? allBookedSlots.filter((slot) => slot.platform === platform)\n : await buildBookedSlots(platform)\n const bookedDatetimes = new Set(bookedSlots.map((slot) => normalizeDateTime(slot.scheduledFor)))\n\n const spacingConfig = isIdeaAware ? getIdeaSpacingConfig() : null\n const spacingGuard = spacingConfig\n ? createSpacingGuard(\n buildIdeaReferences(sameIdeaPosts, allBookedSlots),\n spacingConfig.samePlatformHours,\n spacingConfig.crossPlatformHours,\n )\n : undefined\n\n const searchWindow = isIdeaAware ? resolveSearchWindow(nowMs, options) : {}\n const emptySlot = findEmptySlot({\n platformConfig,\n timezone,\n bookedDatetimes,\n platform,\n searchFromMs: nowMs,\n maxCandidateMs: searchWindow.emptyWindowEndMs,\n passesCandidate: spacingGuard,\n })\n\n if (emptySlot) {\n logger.debug(`Found available slot for ${sanitizeLogValue(platform)}: ${sanitizeLogValue(emptySlot)}`)\n return emptySlot\n }\n\n if (isIdeaAware) {\n const displaced = await tryDisplacement({\n bookedSlots,\n platform,\n platformConfig,\n timezone,\n bookedDatetimes,\n options: { ...options, ideaIds },\n nowMs,\n maxCandidateMs: searchWindow.displacementWindowEndMs,\n passesSpacing: spacingGuard,\n })\n if (displaced) {\n return displaced.slot\n }\n }\n\n logger.warn(`No available slot found for \"${sanitizeLogValue(platform)}\" 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\n .filter((slot) => slot.source === 'local' || slot.status === 'scheduled')\n .map((slot) => ({\n platform: slot.platform,\n scheduledFor: slot.scheduledFor,\n source: slot.source,\n postId: slot.postId,\n itemId: slot.itemId,\n }))\n\n if (startDate) {\n const startMs = startDate.getTime()\n filtered = filtered.filter((slot) => normalizeDateTime(slot.scheduledFor) >= startMs)\n }\n if (endDate) {\n const endMs = endDate.getTime()\n filtered = filtered.filter((slot) => normalizeDateTime(slot.scheduledFor) <= endMs)\n }\n\n filtered.sort((left, right) => normalizeDateTime(left.scheduledFor) - normalizeDateTime(right.scheduledFor))\n return filtered\n}\n","import { readTextFile, writeFileRaw } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join } from '../../L1-infra/paths/paths.js'\n\n/**\n * Read the raw schedule config JSON from disk.\n * Returns the raw string content for L3 to parse and validate.\n */\nexport async function readScheduleFile(filePath: string): Promise<string> {\n return readTextFile(filePath)\n}\n\n/**\n * Write schedule config JSON to disk with exclusive create (wx flag).\n * Throws EEXIST if the file already exists.\n */\nexport async function writeScheduleFile(filePath: string, content: string): Promise<void> {\n await writeFileRaw(filePath, content, {\n encoding: 'utf-8',\n flag: 'wx',\n mode: 0o600,\n })\n}\n\n/**\n * Resolve the default schedule config file path.\n */\nexport function resolveSchedulePath(configPath?: string): string {\n return configPath ?? join(process.cwd(), 'schedule.json')\n}\n","import { readScheduleFile, writeScheduleFile, resolveSchedulePath } from '../../L2-clients/scheduleStore/scheduleStore.js'\nimport logger from '../../L1-infra/logger/configLogger.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 ClipTypeSchedule {\n slots: TimeSlot[]\n avoidDays: DayOfWeek[]\n}\n\nexport interface PlatformSchedule {\n slots: TimeSlot[]\n avoidDays: DayOfWeek[]\n byClipType?: Record<string, ClipTypeSchedule>\n}\n\nexport interface IdeaSpacingConfig {\n samePlatformHours: number\n crossPlatformHours: number\n}\n\nexport interface DisplacementConfig {\n enabled: boolean\n canDisplace: 'non-idea-only'\n}\n\nexport interface ScheduleConfig {\n timezone: string\n platforms: Record<string, PlatformSchedule>\n ideaSpacing?: IdeaSpacingConfig\n displacement?: DisplacementConfig\n}\n\nconst VALID_DAYS: DayOfWeek[] = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']\nconst TIME_REGEX = /^([01]\\d|2[0-3]):[0-5]\\d$/\nconst defaultIdeaSpacing: IdeaSpacingConfig = {\n samePlatformHours: 24,\n crossPlatformHours: 6,\n}\nconst defaultDisplacement: DisplacementConfig = {\n enabled: true,\n canDisplace: 'non-idea-only',\n}\n\nlet cachedConfig: ScheduleConfig | null = null\n\nexport function getDefaultScheduleConfig(): ScheduleConfig {\n return {\n timezone: 'America/Chicago',\n ideaSpacing: { ...defaultIdeaSpacing },\n displacement: { ...defaultDisplacement },\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\nfunction validateSlots(slots: unknown[], context: string): TimeSlot[] {\n const validatedSlots: TimeSlot[] = []\n for (let i = 0; i < slots.length; i++) {\n const slot = slots[i] as Record<string, unknown>\n\n if (!Array.isArray(slot.days) || slot.days.length === 0) {\n throw new Error(`${context} 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(`${context} 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(`${context} 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(`${context} 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 return validatedSlots\n}\n\nfunction validateAvoidDays(avoidDays: unknown[], context: string): DayOfWeek[] {\n for (const day of avoidDays) {\n if (!VALID_DAYS.includes(day as DayOfWeek)) {\n throw new Error(`${context} avoidDays contains invalid day \"${day}\". Valid: ${VALID_DAYS.join(', ')}`)\n }\n }\n return avoidDays as DayOfWeek[]\n}\n\nfunction validateByClipType(byClipType: Record<string, unknown>, platformName: string): Record<string, ClipTypeSchedule> {\n const validated: Record<string, ClipTypeSchedule> = {}\n\n for (const [clipType, value] of Object.entries(byClipType)) {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`Platform \"${platformName}\" byClipType \"${clipType}\" must be an object`)\n }\n\n const sub = value as Record<string, unknown>\n\n if (!Array.isArray(sub.slots)) {\n throw new Error(`Platform \"${platformName}\" byClipType \"${clipType}\" must have a \"slots\" array`)\n }\n\n const rawAvoidDays = Array.isArray(sub.avoidDays) ? sub.avoidDays : []\n\n validated[clipType] = {\n slots: validateSlots(sub.slots, `Platform \"${platformName}\" byClipType \"${clipType}\"`),\n avoidDays: validateAvoidDays(rawAvoidDays, `Platform \"${platformName}\" byClipType \"${clipType}\"`),\n }\n }\n\n // Check for overlapping (day, time) pairs across clip types\n const clipTypes = Object.keys(validated)\n for (let a = 0; a < clipTypes.length; a++) {\n const aSlots = validated[clipTypes[a]]\n const aTimeDays = new Set<string>()\n for (const slot of aSlots.slots) {\n for (const day of slot.days) {\n aTimeDays.add(`${day}@${slot.time}`)\n }\n }\n\n for (let b = a + 1; b < clipTypes.length; b++) {\n const bSlots = validated[clipTypes[b]]\n for (const slot of bSlots.slots) {\n for (const day of slot.days) {\n if (aTimeDays.has(`${day}@${slot.time}`)) {\n logger.warn(\n `Platform \"${platformName}\": clip types \"${clipTypes[a]}\" and \"${clipTypes[b]}\" have overlapping slot (${day}, ${slot.time})`\n )\n }\n }\n }\n }\n }\n\n return validated\n}\n\nfunction validatePositiveNumber(value: unknown, fieldName: string): number {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n throw new Error(`${fieldName} must be a positive number`)\n }\n\n return value\n}\n\nfunction validateIdeaSpacingConfig(ideaSpacing: unknown): IdeaSpacingConfig {\n if (!ideaSpacing || typeof ideaSpacing !== 'object' || Array.isArray(ideaSpacing)) {\n throw new Error('Schedule config \"ideaSpacing\" must be an object')\n }\n\n const spacing = ideaSpacing as Record<string, unknown>\n return {\n samePlatformHours: validatePositiveNumber(\n spacing.samePlatformHours,\n 'Schedule config \"ideaSpacing.samePlatformHours\"'\n ),\n crossPlatformHours: validatePositiveNumber(\n spacing.crossPlatformHours,\n 'Schedule config \"ideaSpacing.crossPlatformHours\"'\n ),\n }\n}\n\nfunction validateDisplacementConfig(displacement: unknown): DisplacementConfig {\n if (!displacement || typeof displacement !== 'object' || Array.isArray(displacement)) {\n throw new Error('Schedule config \"displacement\" must be an object')\n }\n\n const validated = displacement as Record<string, unknown>\n\n if (typeof validated.enabled !== 'boolean') {\n throw new Error('Schedule config \"displacement.enabled\" must be a boolean')\n }\n\n if (validated.canDisplace !== 'non-idea-only') {\n throw new Error('Schedule config \"displacement.canDisplace\" must be \"non-idea-only\"')\n }\n\n return {\n enabled: validated.enabled,\n canDisplace: 'non-idea-only',\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 if (cfg.ideaSpacing !== undefined) {\n validated.ideaSpacing = validateIdeaSpacingConfig(cfg.ideaSpacing)\n }\n\n if (cfg.displacement !== undefined) {\n validated.displacement = validateDisplacementConfig(cfg.displacement)\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 const hasByClipType = plat.byClipType && typeof plat.byClipType === 'object' && !Array.isArray(plat.byClipType)\n\n // When byClipType is present, flat slots/avoidDays are optional defaults\n const hasSlots = Array.isArray(plat.slots)\n const hasAvoidDays = Array.isArray(plat.avoidDays)\n\n if (!hasByClipType) {\n if (!hasSlots) {\n throw new Error(`Platform \"${name}\" must have a \"slots\" array`)\n }\n if (!hasAvoidDays) {\n throw new Error(`Platform \"${name}\" must have an \"avoidDays\" array`)\n }\n }\n\n const validatedSlots = hasSlots\n ? validateSlots(plat.slots as unknown[], `Platform \"${name}\"`)\n : []\n const validatedAvoidDays = hasAvoidDays\n ? validateAvoidDays(plat.avoidDays as unknown[], `Platform \"${name}\"`)\n : []\n\n const result: PlatformSchedule = {\n slots: validatedSlots,\n avoidDays: validatedAvoidDays,\n }\n\n if (hasByClipType) {\n result.byClipType = validateByClipType(plat.byClipType as Record<string, unknown>, name)\n }\n\n validated.platforms[name] = result\n }\n\n return validated\n}\n\nexport async function loadScheduleConfig(configPath?: string): Promise<ScheduleConfig> {\n if (cachedConfig) return cachedConfig\n\n const filePath = resolveSchedulePath(configPath)\n\n let raw: string\n try {\n raw = await readScheduleFile(filePath)\n } catch {\n logger.info(`No schedule.json found at ${filePath}, creating with defaults`)\n const defaults = getDefaultScheduleConfig()\n try {\n await writeScheduleFile(filePath, JSON.stringify(defaults, null, 2))\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 readScheduleFile(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\nconst PLATFORM_ALIASES: Record<string, string> = { twitter: 'x' }\n\nexport function getPlatformSchedule(platform: string, clipType?: string): PlatformSchedule | null {\n if (!cachedConfig) return null\n const schedule = cachedConfig.platforms[platform] ?? cachedConfig.platforms[PLATFORM_ALIASES[platform] ?? ''] ?? null\n if (!schedule) return null\n\n if (clipType && schedule.byClipType?.[clipType]) {\n const sub = schedule.byClipType[clipType]\n return {\n slots: sub.slots,\n avoidDays: sub.avoidDays,\n }\n }\n\n // Fallback: if clipType has no dedicated entry AND top-level slots are empty,\n // aggregate all byClipType slots so text-only posts can use any available slot\n if (clipType && schedule.slots.length === 0 && schedule.byClipType) {\n const allSlots: TimeSlot[] = []\n const allAvoidDays = new Set<DayOfWeek>()\n for (const sub of Object.values(schedule.byClipType)) {\n allSlots.push(...sub.slots)\n for (const day of sub.avoidDays) allAvoidDays.add(day)\n }\n if (allSlots.length > 0) {\n logger.info(`No schedule slots for \"${platform}/${clipType}\", falling back to all ${allSlots.length} available slots`)\n return { slots: allSlots, avoidDays: [...allAvoidDays] }\n }\n }\n\n return schedule\n}\n\nexport function getIdeaSpacingConfig(): IdeaSpacingConfig {\n return cachedConfig?.ideaSpacing ?? { ...defaultIdeaSpacing }\n}\n\nexport function getDisplacementConfig(): DisplacementConfig {\n return cachedConfig?.displacement ?? { ...defaultDisplacement }\n}\n\nexport function clearScheduleCache(): void {\n cachedConfig = null\n}\n","import { LateApiClient } from '../../L2-clients/late/lateApi.js'\nimport type { LatePost } from '../../L2-clients/late/lateApi.js'\nimport { loadScheduleConfig, getPlatformSchedule, type DayOfWeek } from './scheduleConfig.js'\nimport { getPublishedItems } from '../postStore/postStore.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface RealignPost {\n post: LatePost\n platform: string\n clipType: 'short' | 'medium-clip' | 'video'\n oldScheduledFor: string | null\n newScheduledFor: string\n}\n\nexport interface CancelPost {\n post: LatePost\n platform: string\n clipType: 'short' | 'medium-clip' | 'video'\n reason: string\n}\n\nexport interface RealignPlan {\n posts: RealignPost[]\n toCancel: CancelPost[]\n skipped: number\n unmatched: number\n totalFetched: number\n}\n\nexport interface RealignResult {\n updated: number\n cancelled: number\n failed: number\n errors: Array<{ postId: string; error: string }>\n}\n\nexport interface PriorityRule {\n keywords: string[]\n saturation: number\n from?: string\n to?: string\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────\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 const match = tzPart?.value?.match(/GMT([+-]\\d{2}:\\d{2})/)\n if (match) return match[1]\n if (tzPart?.value === 'GMT') return '+00:00'\n return '+00:00'\n}\n\nfunction buildSlotDatetime(date: Date, time: string, timezone: string): string {\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 year = parts.find(p => p.type === 'year')?.value ?? String(date.getFullYear())\n const month = (parts.find(p => p.type === 'month')?.value ?? String(date.getMonth() + 1)).padStart(2, '0')\n const day = (parts.find(p => p.type === 'day')?.value ?? String(date.getDate())).padStart(2, '0')\n const offset = getTimezoneOffset(timezone, date)\n return `${year}-${month}-${day}T${time}:00${offset}`\n}\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// ── Core ───────────────────────────────────────────────────────────────\n\n/**\n * Late API uses \"twitter\" but schedule.json uses \"x\".\n * Normalize so slot lookups succeed.\n */\nconst PLATFORM_ALIASES: Record<string, string> = { twitter: 'x' }\nfunction normalizeSchedulePlatform(platform: string): string {\n return PLATFORM_ALIASES[platform] ?? platform\n}\n\n/**\n * Normalize post content for fuzzy matching: lowercase, collapse whitespace, trim.\n */\nfunction normalizeContent(content: string): string {\n return content.toLowerCase().replace(/\\s+/g, ' ').trim().slice(0, 200)\n}\n\nexport interface ClipTypeMaps {\n byLatePostId: Map<string, 'short' | 'medium-clip' | 'video'>\n byContent: Map<string, 'short' | 'medium-clip' | 'video'>\n}\n\n/**\n * Build maps for correlating Late posts with clip types.\n * Primary: latePostId → clipType\n * Fallback: normalized post content → clipType (for posts without latePostId)\n */\nasync function buildClipTypeMaps(): Promise<ClipTypeMaps> {\n const published = await getPublishedItems()\n const byLatePostId = new Map<string, 'short' | 'medium-clip' | 'video'>()\n const byContent = new Map<string, 'short' | 'medium-clip' | 'video'>()\n\n for (const item of published) {\n if (item.metadata.latePostId) {\n byLatePostId.set(item.metadata.latePostId, item.metadata.clipType)\n }\n // Build content-based fallback using post.md content + platform as key\n if (item.postContent) {\n const contentKey = `${item.metadata.platform}::${normalizeContent(item.postContent)}`\n byContent.set(contentKey, item.metadata.clipType)\n }\n }\n\n logger.debug(`Built clipType maps: ${byLatePostId.size} by latePostId, ${byContent.size} by content`)\n return { byLatePostId, byContent }\n}\n\n/**\n * Fetch all posts of given statuses from Late API with pagination.\n */\nasync function fetchAllPosts(\n client: LateApiClient,\n statuses: readonly string[],\n platform?: string,\n): Promise<LatePost[]> {\n const allPosts: LatePost[] = []\n for (const status of statuses) {\n const posts = await client.listPosts({ status, platform })\n allPosts.push(...posts)\n logger.info(`Fetched ${posts.length} ${status} post(s)${platform ? ` for ${platform}` : ''}`)\n }\n return allPosts\n}\n\n/**\n * Check if an ISO datetime falls on a valid schedule slot (correct day-of-week and time).\n */\nfunction isOnValidSlot(\n iso: string,\n platform: string,\n clipType: string,\n timezone: string,\n): boolean {\n const schedule = getPlatformSchedule(platform, clipType)\n if (!schedule || schedule.slots.length === 0) return false\n\n const date = new Date(iso)\n const dayOfWeek = getDayOfWeekInTimezone(date, timezone)\n if (schedule.avoidDays.includes(dayOfWeek)) return false\n\n // Extract HH:MM in the schedule's timezone\n const timeFormatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n })\n const timeParts = timeFormatter.formatToParts(date)\n const hour = timeParts.find(p => p.type === 'hour')?.value ?? '00'\n const minute = timeParts.find(p => p.type === 'minute')?.value ?? '00'\n const timeKey = `${hour}:${minute}`\n\n return schedule.slots.some(slot => slot.time === timeKey && slot.days.includes(dayOfWeek))\n}\n\n/**\n * Generate candidate slot datetimes for a platform+clipType, skipping booked slots.\n */\nfunction generateSlots(\n platform: string,\n clipType: string,\n count: number,\n bookedMs: Set<number>,\n timezone: string,\n): string[] {\n const schedule = getPlatformSchedule(platform, clipType)\n if (!schedule || schedule.slots.length === 0) {\n logger.warn(`No schedule slots for ${platform}/${clipType}`)\n return []\n }\n\n const available: string[] = []\n const now = new Date()\n const nowMs = now.getTime()\n\n for (let dayOffset = 0; dayOffset < 730 && available.length < count; dayOffset++) {\n const day = new Date(now)\n day.setDate(day.getDate() + dayOffset)\n\n const dayOfWeek = getDayOfWeekInTimezone(day, timezone)\n if (schedule.avoidDays.includes(dayOfWeek)) continue\n\n for (const slot of schedule.slots) {\n if (available.length >= count) break\n if (!slot.days.includes(dayOfWeek)) continue\n\n const iso = buildSlotDatetime(day, slot.time, timezone)\n const ms = new Date(iso).getTime()\n if (ms <= nowMs) continue // skip slots in the past\n if (!bookedMs.has(ms)) {\n available.push(iso)\n bookedMs.add(ms)\n }\n }\n }\n\n return available\n}\n\n/**\n * Build a realignment plan: determine new scheduledFor for each post.\n * @param options.clipTypeMaps - Injectable maps for testing (otherwise fetched from disk)\n */\nexport async function buildRealignPlan(options: {\n platform?: string\n clipTypeMaps?: ClipTypeMaps\n} = {}): Promise<RealignPlan> {\n const config = await loadScheduleConfig()\n const { timezone } = config\n const client = new LateApiClient()\n\n // Fetch all posts that need realignment\n const statuses = ['scheduled', 'draft', 'cancelled', 'failed'] as const\n const allPosts = await fetchAllPosts(client, statuses, options.platform)\n\n if (allPosts.length === 0) {\n return { posts: [], toCancel: [], skipped: 0, unmatched: 0, totalFetched: 0 }\n }\n\n // Build clipType maps from local published metadata (or use injected)\n const { byLatePostId, byContent } = options.clipTypeMaps ?? await buildClipTypeMaps()\n\n // Group posts by platform+clipType\n const grouped = new Map<string, Array<{ post: LatePost; platform: string; clipType: 'short' | 'medium-clip' | 'video' }>>()\n let unmatched = 0\n let contentMatched = 0\n\n for (const post of allPosts) {\n const platform = post.platforms[0]?.platform\n if (!platform) continue\n\n // Primary: match by latePostId\n let clipType = byLatePostId.get(post._id) ?? null\n\n // Fallback: match by normalized content\n if (!clipType && post.content) {\n const contentKey = `${platform}::${normalizeContent(post.content)}`\n clipType = byContent.get(contentKey) ?? null\n if (clipType) contentMatched++\n }\n\n if (!clipType) {\n clipType = 'short'\n unmatched++\n }\n\n const key = `${platform}::${clipType}`\n if (!grouped.has(key)) grouped.set(key, [])\n grouped.get(key)!.push({ post, platform, clipType })\n }\n\n // Track globally booked slots across all platforms\n const bookedMs = new Set<number>()\n\n if (contentMatched > 0) {\n logger.info(`${contentMatched} post(s) matched by content fallback (no latePostId)`)\n }\n\n // Assign ALL posts to slots from today, compacting the schedule to fill gaps.\n // Posts whose new slot matches their current slot are skipped (no update needed).\n const result: RealignPost[] = []\n const toCancel: CancelPost[] = []\n let skipped = 0\n\n for (const [key, posts] of grouped) {\n const [platform, clipType] = key.split('::')\n const schedulePlatform = normalizeSchedulePlatform(platform)\n\n // Check if this platform+clipType has any schedule config\n const schedule = getPlatformSchedule(schedulePlatform, clipType)\n const hasSlots = schedule && schedule.slots.length > 0\n\n if (!hasSlots) {\n // No schedule slots — cancel these posts (unless already cancelled)\n for (const { post, clipType: ct } of posts) {\n if (post.status === 'cancelled') continue\n toCancel.push({\n post,\n platform,\n clipType: ct,\n reason: `No schedule slots for ${schedulePlatform}/${clipType}`,\n })\n }\n continue\n }\n\n // Sort by original scheduledFor (earliest first), unscheduled at the end\n posts.sort((a, b) => {\n const aTime = a.post.scheduledFor ? new Date(a.post.scheduledFor).getTime() : Infinity\n const bTime = b.post.scheduledFor ? new Date(b.post.scheduledFor).getTime() : Infinity\n return aTime - bTime\n })\n\n const slots = generateSlots(schedulePlatform, clipType, posts.length, bookedMs, timezone)\n\n for (let i = 0; i < posts.length; i++) {\n const { post } = posts[i]\n const newSlot = slots[i]\n if (!newSlot) {\n // Ran out of slots — cancel overflow posts (unless already cancelled)\n if (post.status !== 'cancelled') {\n toCancel.push({\n post,\n platform,\n clipType: posts[i].clipType,\n reason: `No more available slots for ${schedulePlatform}/${clipType}`,\n })\n }\n continue\n }\n\n // Skip if already scheduled at this exact slot and status is fine\n const currentMs = post.scheduledFor ? new Date(post.scheduledFor).getTime() : 0\n const newMs = new Date(newSlot).getTime()\n if (currentMs === newMs && post.status === 'scheduled') {\n skipped++\n continue\n }\n\n result.push({\n post,\n platform,\n clipType: posts[i].clipType,\n oldScheduledFor: post.scheduledFor ?? null,\n newScheduledFor: newSlot,\n })\n }\n }\n\n // Sort final plan chronologically by new slot\n result.sort((a, b) => new Date(a.newScheduledFor).getTime() - new Date(b.newScheduledFor).getTime())\n\n return { posts: result, toCancel, skipped, unmatched, totalFetched: allPosts.length }\n}\n\n// ── Prioritized realign ────────────────────────────────────────────────\n\ninterface TaggedPost {\n post: LatePost\n platform: string\n clipType: 'short' | 'medium-clip' | 'video'\n}\n\n/**\n * Check if a slot date (YYYY-MM-DD in schedule timezone) falls within a rule's active range.\n */\nfunction isSlotInRange(slotIso: string, rule: PriorityRule, timezone: string): boolean {\n if (!rule.from && !rule.to) return true\n const date = new Date(slotIso)\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 year = parts.find(p => p.type === 'year')?.value ?? ''\n const month = parts.find(p => p.type === 'month')?.value ?? ''\n const day = parts.find(p => p.type === 'day')?.value ?? ''\n const slotDate = `${year}-${month}-${day}`\n if (rule.from && slotDate < rule.from) return false\n if (rule.to && slotDate > rule.to) return false\n return true\n}\n\n/**\n * Check if a post's content matches any of the keywords (case-insensitive substring).\n */\nfunction matchesKeywords(content: string, keywords: readonly string[]): boolean {\n const lower = content.toLowerCase()\n return keywords.some(kw => lower.includes(kw.toLowerCase()))\n}\n\n/**\n * Build a prioritized realignment plan.\n *\n * Works like `buildRealignPlan` but reorders posts per platform+clipType group\n * using priority rules before assigning to slots. For each slot, the rules are\n * checked in array order:\n * 1. If the slot date is in the rule's {from, to} range\n * 2. AND Math.random() < saturation\n * → pull the next keyword-matched post from that rule's queue\n * If no rule fires, pull from the remaining (non-priority) pool sorted by scheduledFor.\n * @param options.clipTypeMaps - Injectable maps for testing (otherwise fetched from disk)\n */\nexport async function buildPrioritizedRealignPlan(options: {\n priorities: PriorityRule[]\n platform?: string\n clipTypeMaps?: ClipTypeMaps\n} = { priorities: [] }): Promise<RealignPlan> {\n const config = await loadScheduleConfig()\n const { timezone } = config\n const client = new LateApiClient()\n\n const statuses = ['scheduled', 'draft', 'cancelled', 'failed'] as const\n const allPosts = await fetchAllPosts(client, statuses, options.platform)\n\n if (allPosts.length === 0) {\n return { posts: [], toCancel: [], skipped: 0, unmatched: 0, totalFetched: 0 }\n }\n\n const { byLatePostId, byContent } = options.clipTypeMaps ?? await buildClipTypeMaps()\n\n // Tag each post with platform + clipType\n const tagged: TaggedPost[] = []\n let unmatched = 0\n let contentMatched = 0\n\n for (const post of allPosts) {\n const platform = post.platforms[0]?.platform\n if (!platform) continue\n\n let clipType = byLatePostId.get(post._id) ?? null\n if (!clipType && post.content) {\n const contentKey = `${platform}::${normalizeContent(post.content)}`\n clipType = byContent.get(contentKey) ?? null\n if (clipType) contentMatched++\n }\n if (!clipType) {\n clipType = 'short'\n unmatched++\n }\n tagged.push({ post, platform, clipType })\n }\n\n if (contentMatched > 0) {\n logger.info(`${contentMatched} post(s) matched by content fallback (no latePostId)`)\n }\n\n // Group by platform+clipType\n const grouped = new Map<string, TaggedPost[]>()\n for (const tp of tagged) {\n const key = `${tp.platform}::${tp.clipType}`\n if (!grouped.has(key)) grouped.set(key, [])\n grouped.get(key)!.push(tp)\n }\n\n const bookedMs = new Set<number>()\n const result: RealignPost[] = []\n const toCancel: CancelPost[] = []\n let skipped = 0\n\n for (const [key, posts] of grouped) {\n const [platform, clipType] = key.split('::')\n const schedulePlatform = normalizeSchedulePlatform(platform)\n\n const schedule = getPlatformSchedule(schedulePlatform, clipType)\n const hasSlots = schedule && schedule.slots.length > 0\n\n if (!hasSlots) {\n for (const { post, clipType: ct } of posts) {\n if (post.status === 'cancelled') continue\n toCancel.push({ post, platform, clipType: ct, reason: `No schedule slots for ${schedulePlatform}/${clipType}` })\n }\n continue\n }\n\n const slots = generateSlots(schedulePlatform, clipType, posts.length, bookedMs, timezone)\n\n // Build per-rule queues: posts whose content matches the rule's keywords\n const usedPostIds = new Set<string>()\n const ruleQueues: TaggedPost[][] = options.priorities.map(rule => {\n const matched = posts.filter(tp =>\n tp.post.content && matchesKeywords(tp.post.content, rule.keywords),\n )\n // Sort matched by scheduledFor (earliest first)\n matched.sort((a, b) => {\n const at = a.post.scheduledFor ? new Date(a.post.scheduledFor).getTime() : Infinity\n const bt = b.post.scheduledFor ? new Date(b.post.scheduledFor).getTime() : Infinity\n return at - bt\n })\n return matched\n })\n\n // Collect IDs of posts matched by any priority rule\n const priorityMatchedIds = new Set<string>()\n for (const queue of ruleQueues) {\n for (const tp of queue) {\n priorityMatchedIds.add(tp.post._id)\n }\n }\n\n // Remaining pool: ONLY posts that don't match any priority rule.\n // Priority-matched posts are reserved for their rule queues — they are only\n // assignable when a rule fires. This prevents them from being consumed by\n // earlier slots before the rule's date range starts.\n const remainingPool = posts\n .filter(tp => !priorityMatchedIds.has(tp.post._id))\n .sort((a, b) => {\n const at = a.post.scheduledFor ? new Date(a.post.scheduledFor).getTime() : Infinity\n const bt = b.post.scheduledFor ? new Date(b.post.scheduledFor).getTime() : Infinity\n return at - bt\n })\n let remainingIdx = 0\n\n // Walk each slot and decide which post to assign\n for (let i = 0; i < slots.length; i++) {\n const slot = slots[i]\n let assigned: TaggedPost | undefined\n\n // Try each priority rule in array order\n for (let r = 0; r < options.priorities.length; r++) {\n const rule = options.priorities[r]\n const queue = ruleQueues[r]\n\n // Skip exhausted queues\n if (queue.length === 0) continue\n\n // Check if this slot's date is in the rule's active range\n if (!isSlotInRange(slot, rule, timezone)) continue\n\n // Saturation dice roll\n if (Math.random() >= rule.saturation) continue\n\n // Find next unused post from this queue\n while (queue.length > 0) {\n const candidate = queue.shift()!\n if (!usedPostIds.has(candidate.post._id)) {\n assigned = candidate\n usedPostIds.add(candidate.post._id)\n break\n }\n }\n if (assigned) break\n }\n\n // Fallback: pull from remaining pool\n if (!assigned) {\n while (remainingIdx < remainingPool.length) {\n const candidate = remainingPool[remainingIdx]\n remainingIdx++\n if (!usedPostIds.has(candidate.post._id)) {\n assigned = candidate\n usedPostIds.add(candidate.post._id)\n break\n }\n }\n }\n\n if (!assigned) continue\n\n // Skip if already at the correct slot\n const currentMs = assigned.post.scheduledFor ? new Date(assigned.post.scheduledFor).getTime() : 0\n const newMs = new Date(slot).getTime()\n if (currentMs === newMs && assigned.post.status === 'scheduled') {\n skipped++\n continue\n }\n\n result.push({\n post: assigned.post,\n platform: assigned.platform,\n clipType: assigned.clipType,\n oldScheduledFor: assigned.post.scheduledFor ?? null,\n newScheduledFor: slot,\n })\n }\n\n // Cancel posts that didn't get a slot\n for (const tp of posts) {\n if (usedPostIds.has(tp.post._id)) continue\n if (tp.post.status === 'cancelled') continue\n toCancel.push({\n post: tp.post,\n platform: tp.platform,\n clipType: tp.clipType,\n reason: `No more available slots for ${schedulePlatform}/${clipType}`,\n })\n }\n }\n\n result.sort((a, b) => new Date(a.newScheduledFor).getTime() - new Date(b.newScheduledFor).getTime())\n\n return { posts: result, toCancel, skipped, unmatched, totalFetched: allPosts.length }\n}\n\n/**\n * Execute a realignment plan: update each post via Late API.\n * Optionally reports progress via callback.\n */\nexport async function executeRealignPlan(\n plan: RealignPlan,\n onProgress?: (completed: number, total: number, phase: 'cancelling' | 'updating') => void,\n): Promise<RealignResult> {\n const client = new LateApiClient()\n let updated = 0\n let cancelled = 0\n let failed = 0\n const errors: Array<{ postId: string; error: string }> = []\n const totalOps = plan.toCancel.length + plan.posts.length\n let completed = 0\n\n // Cancel posts that have no matching schedule\n for (const entry of plan.toCancel) {\n completed++\n try {\n await client.updatePost(entry.post._id, { status: 'cancelled' })\n cancelled++\n const preview = entry.post.content.slice(0, 40).replace(/\\n/g, ' ')\n logger.info(`[${completed}/${totalOps}] 🚫 Cancelled ${entry.platform}/${entry.clipType}: \"${preview}...\"`)\n onProgress?.(completed, totalOps, 'cancelling')\n await new Promise(r => setTimeout(r, 300))\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n errors.push({ postId: entry.post._id, error: msg })\n failed++\n logger.error(`[${completed}/${totalOps}] ❌ Failed to cancel ${entry.post._id}: ${msg}`)\n }\n }\n\n // Update posts with new schedule slots\n for (const entry of plan.posts) {\n completed++\n try {\n // Late API schedulePost sends isDraft: false to ensure\n // draft posts transition to scheduled status.\n await client.schedulePost(entry.post._id, entry.newScheduledFor)\n updated++\n const preview = entry.post.content.slice(0, 40).replace(/\\n/g, ' ')\n logger.info(`[${completed}/${totalOps}] ✅ ${entry.platform}/${entry.clipType}: \"${preview}...\" → ${entry.newScheduledFor}`)\n onProgress?.(completed, totalOps, 'updating')\n\n // Small delay to respect rate limits\n await new Promise(r => setTimeout(r, 300))\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n errors.push({ postId: entry.post._id, error: msg })\n failed++\n logger.error(`[${completed}/${totalOps}] ❌ Failed to update ${entry.post._id}: ${msg}`)\n }\n }\n\n return { updated, cancelled, failed, errors }\n}\n","import { BaseAgent } from './BaseAgent.js'\nimport { createLateApiClient } from '../L3-services/lateApi/lateApiService.js'\nimport { findNextSlot, getScheduleCalendar } from '../L3-services/scheduler/scheduler.js'\nimport { loadScheduleConfig } from '../L3-services/scheduler/scheduleConfig.js'\nimport { buildRealignPlan, executeRealignPlan, buildPrioritizedRealignPlan } from '../L3-services/scheduler/realign.js'\nimport logger from '../L1-infra/logger/configLogger.js'\nimport type { LatePost } from '../L3-services/lateApi/lateApiService.js'\nimport type { RealignPlan, PriorityRule } from '../L3-services/scheduler/realign.js'\nimport type { ToolWithHandler, UserInputHandler, LLMSession } from '../L3-services/llm/providerFactory.js'\n\n/** Friendly labels for tool calls shown in chat mode */\nconst TOOL_LABELS: Record<string, string> = {\n list_posts: '📋 Listing posts',\n view_schedule_config: '⚙️ Loading schedule config',\n view_calendar: '📅 Loading calendar',\n reschedule_post: '🔄 Rescheduling post',\n cancel_post: '🚫 Cancelling post',\n find_next_slot: '🔍 Finding next slot',\n realign_schedule: '📐 Running realignment',\n start_prioritize_realign: '🎯 Starting prioritized realignment',\n check_realign_status: '📊 Checking realignment progress',\n ask_user: '💬 Asking for your input',\n}\n\ninterface RealignJob {\n id: string\n status: 'planning' | 'executing' | 'completed' | 'failed'\n startedAt: string\n completedAt?: string\n progress: { completed: number; total: number; phase: 'planning' | 'cancelling' | 'updating' }\n plan?: {\n totalFetched: number\n toReschedule: number\n toCancel: number\n skipped: number\n unmatched: number\n }\n result?: { updated: number; cancelled: number; failed: number; errors: Array<{ postId: string; error: string }> }\n error?: string\n}\n\nconst SYSTEM_PROMPT = `You are a schedule management assistant for Late.co social media posts.\n\nYou help the user view, analyze, and reprioritize their posting schedule across platforms.\n\nAvailable platforms: x (twitter), youtube, tiktok, instagram, linkedin\nClip types: short (15-60s vertical clips), medium-clip (60-180s clips), video (full-length)\n\nWhen listing posts, always show content previews (first 60 chars) so the user can identify them.\nUse ask_user when you need clarification on priorities or decisions — never guess at user intent.\nBe concise and actionable. Prefer tables or bullet lists over prose.\n\nFor themed scheduling, use start_prioritize_realign to kick off the job, then poll with\ncheck_realign_status until it completes. The priorities array is ordered — rule[0] is checked\nfirst for each slot. Each rule has keywords to match post content, a saturation (0.0–1.0) controlling\nhow aggressively to fill slots with matches, and optional from/to dates for the active range.\nExample: \"DevOps this week, hooks next week\" → two rules with different date ranges.\nWorkflow: start_prioritize_realign (dryRun=true) → check_realign_status → review plan → if approved,\nstart_prioritize_realign (dryRun=false) → check_realign_status (poll every few seconds until completed).`\n\nexport class ScheduleAgent extends BaseAgent {\n private userInputHandler?: UserInputHandler\n private chatOutput?: (message: string) => void\n private realignJobs = new Map<string, RealignJob>()\n\n constructor(userInputHandler?: UserInputHandler, model?: string) {\n super('ScheduleAgent', SYSTEM_PROMPT, undefined, model)\n this.userInputHandler = userInputHandler\n }\n\n /** Set a callback for chat-friendly status messages (tool starts, progress). */\n setChatOutput(fn: (message: string) => void): void {\n this.chatOutput = fn\n }\n\n protected getUserInputHandler(): UserInputHandler | undefined {\n return this.userInputHandler\n }\n\n protected getTimeoutMs(): number {\n return 1_800_000 // 30 minutes for interactive chat\n }\n\n protected setupEventHandlers(session: LLMSession): void {\n if (!this.chatOutput) {\n super.setupEventHandlers(session)\n return\n }\n\n const write = this.chatOutput\n\n session.on('delta', (event) => {\n const data = event.data as Record<string, unknown> | undefined\n const chunk = (data?.deltaContent as string) ?? ''\n if (chunk) process.stdout.write(`\\x1b[36m${chunk}\\x1b[0m`)\n })\n\n session.on('tool_start', (event) => {\n const data = event.data as Record<string, unknown> | undefined\n const toolName = (data?.toolName as string) ?? 'unknown'\n const label = TOOL_LABELS[toolName] ?? `🔧 ${toolName}`\n write(`\\x1b[90m${label}...\\x1b[0m`)\n })\n\n session.on('error', (event) => {\n const data = event.data as Record<string, unknown> | undefined\n const msg = (data?.message as string) ?? JSON.stringify(data)\n write(`\\x1b[31m❌ Error: ${msg}\\x1b[0m`)\n })\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'list_posts',\n description: 'List posts from the Late.co queue. Fetches ALL posts with pagination, then filters locally. Use search to find posts about specific topics.',\n parameters: {\n type: 'object',\n properties: {\n status: { type: 'string', description: 'Filter by status: scheduled, draft, cancelled, failed, published. Omit for all statuses.' },\n platform: { type: 'string', description: 'Filter by platform: x, twitter, youtube, tiktok, instagram, linkedin' },\n search: { type: 'string', description: 'Search text to filter posts by content (case-insensitive substring match)' },\n limit: { type: 'number', description: 'Max posts to return (default: 50). Use higher values to find all matches.' },\n },\n required: [],\n },\n handler: async (args) => this.handleToolCall('list_posts', args as Record<string, unknown>),\n },\n {\n name: 'view_schedule_config',\n description: 'Show the schedule.json slot configuration (posting windows per platform).',\n parameters: {\n type: 'object',\n properties: {\n platform: { type: 'string', description: 'Filter to a specific platform' },\n },\n required: [],\n },\n handler: async (args) => this.handleToolCall('view_schedule_config', args as Record<string, unknown>),\n },\n {\n name: 'view_calendar',\n description: 'Show upcoming scheduled posts as a calendar view.',\n parameters: {\n type: 'object',\n properties: {\n days: { type: 'number', description: 'Number of days to look ahead (default: 7)' },\n },\n required: [],\n },\n handler: async (args) => this.handleToolCall('view_calendar', args as Record<string, unknown>),\n },\n {\n name: 'reschedule_post',\n description: 'Move a post to a new scheduled time.',\n parameters: {\n type: 'object',\n properties: {\n postId: { type: 'string', description: 'The Late post ID' },\n scheduledFor: { type: 'string', description: 'New scheduled datetime (ISO 8601)' },\n },\n required: ['postId', 'scheduledFor'],\n },\n handler: async (args) => this.handleToolCall('reschedule_post', args as Record<string, unknown>),\n },\n {\n name: 'cancel_post',\n description: 'Cancel a scheduled post.',\n parameters: {\n type: 'object',\n properties: {\n postId: { type: 'string', description: 'The Late post ID to cancel' },\n },\n required: ['postId'],\n },\n handler: async (args) => this.handleToolCall('cancel_post', args as Record<string, unknown>),\n },\n {\n name: 'find_next_slot',\n description: 'Find the next available posting slot for a platform.',\n parameters: {\n type: 'object',\n properties: {\n platform: { type: 'string', description: 'Platform: x, twitter, youtube, tiktok, instagram, linkedin' },\n clipType: { type: 'string', description: 'Clip type: short, medium-clip, video' },\n },\n required: ['platform'],\n },\n handler: async (args) => this.handleToolCall('find_next_slot', args as Record<string, unknown>),\n },\n {\n name: 'realign_schedule',\n description: 'Run full schedule realignment — preview the plan or execute it.',\n parameters: {\n type: 'object',\n properties: {\n platform: { type: 'string', description: 'Limit realignment to a specific platform' },\n execute: { type: 'boolean', description: 'If true, execute the plan. If false (default), only preview.' },\n },\n required: [],\n },\n handler: async (args) => this.handleToolCall('realign_schedule', args as Record<string, unknown>),\n },\n {\n name: 'start_prioritize_realign',\n description: 'Start a prioritized realignment in the background. Returns a job ID immediately. Use check_realign_status to poll progress. The priorities array is ordered: rule[0] is checked first for each slot.',\n parameters: {\n type: 'object',\n properties: {\n priorities: {\n type: 'array',\n description: 'Ordered array of priority rules. Array order = priority rank — rule[0] is checked first for each slot, then rule[1], etc.',\n items: {\n type: 'object',\n properties: {\n keywords: {\n type: 'array',\n items: { type: 'string' },\n description: 'Search terms to match post content (case-insensitive). Posts matching ANY keyword are included.',\n },\n saturation: {\n type: 'number',\n description: 'Probability 0.0-1.0 of filling each slot with a match. 1.0 = always, 0.5 = ~50% of slots.',\n },\n from: { type: 'string', description: 'Start date (YYYY-MM-DD) for when this rule is active. Omit for immediately.' },\n to: { type: 'string', description: 'End date (YYYY-MM-DD) for when this rule expires. Omit for no end.' },\n },\n required: ['keywords', 'saturation'],\n },\n },\n platform: { type: 'string', description: 'Limit to one platform: x, youtube, tiktok, instagram, linkedin' },\n dryRun: { type: 'boolean', description: 'If true (default), only build the plan without executing. Set to false to build AND execute.' },\n },\n required: ['priorities'],\n },\n handler: async (args) => this.handleToolCall('start_prioritize_realign', args as Record<string, unknown>),\n },\n {\n name: 'check_realign_status',\n description: 'Check the progress of a running prioritized realignment job. Poll this periodically after calling start_prioritize_realign.',\n parameters: {\n type: 'object',\n properties: {\n jobId: { type: 'string', description: 'The job ID returned by start_prioritize_realign' },\n },\n required: ['jobId'],\n },\n handler: async (args) => this.handleToolCall('check_realign_status', args as Record<string, unknown>),\n },\n ]\n }\n\n protected async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {\n switch (toolName) {\n case 'list_posts': return this.listPosts(args)\n case 'view_schedule_config': return this.viewScheduleConfig(args)\n case 'view_calendar': return this.viewCalendar(args)\n case 'reschedule_post': return this.reschedulePost(args)\n case 'cancel_post': return this.cancelPost(args)\n case 'find_next_slot': return this.findNextSlot(args)\n case 'realign_schedule': return this.realignSchedule(args)\n case 'start_prioritize_realign': return this.startPrioritizeRealign(args)\n case 'check_realign_status': return this.checkRealignStatus(args)\n default: return { error: `Unknown tool: ${toolName}` }\n }\n }\n\n private async listPosts(args: Record<string, unknown>): Promise<unknown> {\n try {\n const status = args.status as string | undefined\n const platform = args.platform as string | undefined\n const search = args.search as string | undefined\n const limit = (args.limit as number) ?? 100\n const client = createLateApiClient()\n\n // Fetch all posts — if no status specified, fetch all active statuses\n let posts: LatePost[]\n if (status) {\n posts = await client.listPosts({ status, platform })\n } else {\n const statuses = ['scheduled', 'draft', 'cancelled', 'failed']\n const results = await Promise.all(\n statuses.map(s => client.listPosts({ status: s, platform })),\n )\n posts = results.flat()\n }\n\n // Client-side search filter\n if (search) {\n const needle = search.toLowerCase()\n posts = posts.filter(p => (p.content ?? '').toLowerCase().includes(needle))\n }\n\n // Sort by scheduledFor (earliest first), unscheduled at end\n posts.sort((a, b) => {\n const at = a.scheduledFor ? new Date(a.scheduledFor).getTime() : Infinity\n const bt = b.scheduledFor ? new Date(b.scheduledFor).getTime() : Infinity\n return at - bt\n })\n\n // Limit results to save tokens\n const limited = posts.slice(0, limit)\n\n return {\n total: posts.length,\n returned: limited.length,\n posts: limited.map((p: LatePost) => ({\n id: p._id,\n content_preview: (p.content ?? '').slice(0, 120),\n platform: p.platforms.map(pl => pl.platform).join(', '),\n status: p.status,\n scheduledFor: p.scheduledFor ?? null,\n })),\n }\n } catch (err) {\n logger.error('list_posts failed', { error: err })\n return { error: `Failed to list posts: ${(err as Error).message}` }\n }\n }\n\n private async viewScheduleConfig(args: Record<string, unknown>): Promise<unknown> {\n try {\n const platform = args.platform as string | undefined\n const config = await loadScheduleConfig()\n if (platform) {\n const normalized = platform === 'twitter' ? 'x' : platform\n const platformConfig = config.platforms[normalized]\n if (!platformConfig) return { error: `No schedule config for platform: ${normalized}` }\n return { timezone: config.timezone, platform: normalized, schedule: platformConfig }\n }\n return config\n } catch (err) {\n logger.error('view_schedule_config failed', { error: err })\n return { error: `Failed to load schedule config: ${(err as Error).message}` }\n }\n }\n\n private async viewCalendar(args: Record<string, unknown>): Promise<unknown> {\n try {\n const days = (args.days as number) ?? 7\n const startDate = new Date()\n const endDate = new Date()\n endDate.setDate(endDate.getDate() + days)\n const calendar = await getScheduleCalendar(startDate, endDate)\n return { days, slots: calendar }\n } catch (err) {\n logger.error('view_calendar failed', { error: err })\n return { error: `Failed to get calendar: ${(err as Error).message}` }\n }\n }\n\n private async reschedulePost(args: Record<string, unknown>): Promise<unknown> {\n try {\n const postId = args.postId as string\n const scheduledFor = args.scheduledFor as string\n const client = createLateApiClient()\n const updated = await client.schedulePost(postId, scheduledFor)\n return { success: true, postId, scheduledFor: updated.scheduledFor }\n } catch (err) {\n logger.error('reschedule_post failed', { error: err })\n return { error: `Failed to reschedule post: ${(err as Error).message}` }\n }\n }\n\n private async cancelPost(args: Record<string, unknown>): Promise<unknown> {\n try {\n const postId = args.postId as string\n const client = createLateApiClient()\n await client.updatePost(postId, { status: 'cancelled' })\n return { success: true, postId, status: 'cancelled' }\n } catch (err) {\n logger.error('cancel_post failed', { error: err })\n return { error: `Failed to cancel post: ${(err as Error).message}` }\n }\n }\n\n private async findNextSlot(args: Record<string, unknown>): Promise<unknown> {\n try {\n const platform = args.platform as string\n const clipType = args.clipType as string | undefined\n const normalized = platform === 'twitter' ? 'x' : platform\n const slot = await findNextSlot(normalized, clipType)\n if (!slot) return { error: `No available slot found for ${normalized}` }\n return { platform: normalized, clipType: clipType ?? 'any', nextSlot: slot }\n } catch (err) {\n logger.error('find_next_slot failed', { error: err })\n return { error: `Failed to find next slot: ${(err as Error).message}` }\n }\n }\n\n private async realignSchedule(args: Record<string, unknown>): Promise<unknown> {\n try {\n const platform = args.platform as string | undefined\n const execute = (args.execute as boolean) ?? false\n const plan: RealignPlan = await buildRealignPlan({ platform })\n if (!execute) {\n return {\n preview: true,\n totalFetched: plan.totalFetched,\n toReschedule: plan.posts.length,\n toCancel: plan.toCancel.length,\n skipped: plan.skipped,\n unmatched: plan.unmatched,\n moves: plan.posts.map(p => ({\n postId: p.post._id,\n platform: p.platform,\n clipType: p.clipType,\n from: p.oldScheduledFor,\n to: p.newScheduledFor,\n })),\n }\n }\n const result = await executeRealignPlan(plan)\n return { executed: true, ...result }\n } catch (err) {\n logger.error('realign_schedule failed', { error: err })\n return { error: `Failed to realign schedule: ${(err as Error).message}` }\n }\n }\n\n private async startPrioritizeRealign(args: Record<string, unknown>): Promise<unknown> {\n try {\n const priorities = (args.priorities as PriorityRule[]) ?? []\n const platform = args.platform as string | undefined\n const dryRun = (args.dryRun as boolean) ?? true\n\n const jobId = `realign-${Date.now()}`\n const job: RealignJob = {\n id: jobId,\n status: 'planning',\n startedAt: new Date().toISOString(),\n progress: { completed: 0, total: 0, phase: 'planning' },\n }\n this.realignJobs.set(jobId, job)\n\n // Fire-and-forget — runs in background\n this.runRealignJob(job, priorities, platform, dryRun).catch((err) => {\n job.status = 'failed'\n job.error = err instanceof Error ? err.message : String(err)\n job.completedAt = new Date().toISOString()\n logger.error('Realign job failed', { jobId, error: err })\n })\n\n return {\n started: true,\n jobId,\n dryRun,\n message: `Prioritized realignment started. Use check_realign_status with jobId \"${jobId}\" to monitor progress.`,\n }\n } catch (err) {\n logger.error('start_prioritize_realign failed', { error: err })\n return { error: `Failed to start prioritize realign: ${(err as Error).message}` }\n }\n }\n\n private async runRealignJob(\n job: RealignJob,\n priorities: PriorityRule[],\n platform: string | undefined,\n dryRun: boolean,\n ): Promise<void> {\n // Phase 1: Build plan\n const plan: RealignPlan = await buildPrioritizedRealignPlan({ priorities, platform })\n job.plan = {\n totalFetched: plan.totalFetched,\n toReschedule: plan.posts.length,\n toCancel: plan.toCancel.length,\n skipped: plan.skipped,\n unmatched: plan.unmatched,\n }\n\n if (dryRun) {\n job.status = 'completed'\n job.completedAt = new Date().toISOString()\n job.result = { updated: 0, cancelled: 0, failed: 0, errors: [] }\n return\n }\n\n // Phase 2: Execute\n job.status = 'executing'\n job.progress = { completed: 0, total: plan.toCancel.length + plan.posts.length, phase: 'cancelling' }\n\n const result = await executeRealignPlan(plan, (completed, total, phase) => {\n job.progress = { completed, total, phase }\n })\n\n job.status = 'completed'\n job.completedAt = new Date().toISOString()\n job.result = result\n }\n\n private async checkRealignStatus(args: Record<string, unknown>): Promise<unknown> {\n const jobId = args.jobId as string\n const job = this.realignJobs.get(jobId)\n if (!job) return { error: `No realign job found with ID: ${jobId}` }\n\n const response: Record<string, unknown> = {\n jobId: job.id,\n status: job.status,\n startedAt: job.startedAt,\n progress: job.progress,\n }\n\n if (job.plan) response.plan = job.plan\n if (job.completedAt) response.completedAt = job.completedAt\n if (job.result) response.result = job.result\n if (job.error) response.error = job.error\n\n return response\n }\n}\n","import { readJsonFile } from '../L1-infra/fileSystem/fileSystem.js'\nimport { getBrandConfig } from '../L1-infra/config/brand.js'\nimport { getConfig } from '../L1-infra/config/environment.js'\nimport { getModelForAgent } from '../L1-infra/config/modelConfig.js'\nimport { readIdeaBank, writeIdea } from '../L1-infra/ideaStore/ideaStore.js'\nimport logger from '../L1-infra/logger/configLogger.js'\nimport { getProvider } from '../L3-services/llm/providerFactory.js'\nimport { BaseAgent } from './BaseAgent.js'\nimport type { ToolWithHandler } from './BaseAgent.js'\nimport type { MCPServerConfig } from '../L2-clients/llm/types.js'\nimport type { Idea } from '../L0-pure/types/index.js'\n\nconst BASE_SYSTEM_PROMPT = `You are a content strategist for a tech content creator. Your role is to research trending topics, analyze what's working, and generate compelling video ideas grounded in real-world data.\n\n## CRITICAL: Research Before Creating\nYou MUST research before creating ideas. Do NOT skip the research phase. Ideas generated without research will be generic and stale. The value you provide is grounding ideas in what's ACTUALLY trending right now.\n\n## Your Research Process\n1. Load the brand context (get_brand_context) to understand the creator's voice, expertise, and content pillars.\n2. Check existing ideas (get_past_ideas) to avoid duplicates.\n3. **RESEARCH PHASE** — This is the most important step. Use the available MCP tools:\n - **web_search_exa**: Search for trending topics, viral content, recent announcements, and hot takes in the creator's niche. Search for specific topics from the creator's content pillars.\n - **youtube_search_videos** or **youtube_search**: Find what videos are performing well right now. Look at view counts, recent uploads on trending topics, and gaps in existing content.\n - **perplexity-search**: Get current analysis on promising topics, recent developments, and emerging trends.\n - Do at LEAST 2-3 research queries across different tools. More is better.\n4. Generate ideas that synthesize your research findings with the creator's brand and content pillars.\n\n## Idea Quality Bar\nEvery idea must:\n- Have a clear, specific hook (not generic like \"Learn about AI\")\n- Target a defined audience\n- Deliver one memorable takeaway\n- Be timely — the trendContext field MUST reference specific findings from your research (e.g., \"GitHub Copilot just released X feature this week\" or \"This topic has 2M views in the last 7 days on YouTube\")\n- Fit within the creator's established content pillars\n- Set publishBy based on timeliness:\n * Breaking news / hot trend: 3-5 days from now\n * Timely topic (release, event, announcement): 1-2 weeks from now\n * Evergreen content (tutorials, fundamentals): 3-6 months from now\n\n## Platform Targeting\n- Short-form (TikTok, YouTube Shorts, Instagram Reels): Hook-first, single concept, ≤60s\n- Long-form (YouTube): Deep dives, tutorials, analysis, 8-20 min\n- Written (LinkedIn, X/Twitter): Thought leadership, hot takes, thread-worthy\n\nGenerate 3-5 high-quality ideas. Quality over quantity. Every idea must be backed by research.`\n\nconst SUPPORTED_PLATFORMS = ['tiktok', 'youtube', 'instagram', 'linkedin', 'x'] as const\nconst MIN_IDEA_COUNT = 3\nconst MAX_IDEA_COUNT = 5\n\ntype SupportedPlatform = (typeof SUPPORTED_PLATFORMS)[number]\ntype BrandContext = ReturnType<typeof getBrandConfig> & Record<string, unknown>\n\ninterface ContentPillarSummary {\n pillar: string\n description?: string\n frequency?: string\n formats?: string[]\n}\n\ninterface GenerateIdeasOptions {\n seedTopics?: string[]\n count?: number\n ideasDir?: string\n brandPath?: string\n}\n\ninterface CreateIdeaArgs {\n id: string\n topic: string\n hook: string\n audience: string\n keyTakeaway: string\n talkingPoints: string[]\n platforms: string[]\n tags: string[]\n publishBy: string\n trendContext?: string\n}\n\ninterface IdeationAgentContext {\n readonly brandContext: BrandContext\n readonly existingIdeas: Idea[]\n readonly ideasDir?: string\n readonly targetCount: number\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\nfunction normalizeCount(count?: number): number {\n if (typeof count !== 'number' || Number.isNaN(count)) {\n return MIN_IDEA_COUNT\n }\n\n const rounded = Math.round(count)\n return Math.min(MAX_IDEA_COUNT, Math.max(MIN_IDEA_COUNT, rounded))\n}\n\nfunction normalizeSeedTopics(seedTopics?: string[]): string[] {\n return (seedTopics ?? [])\n .map((topic) => topic.trim())\n .filter((topic) => topic.length > 0)\n}\n\nfunction extractStringArrayField(source: Record<string, unknown>, field: string): string[] {\n const value = source[field]\n return isStringArray(value) ? value : []\n}\n\nfunction extractContentPillars(brand: BrandContext): ContentPillarSummary[] {\n const raw = brand.contentPillars\n if (!Array.isArray(raw)) {\n return []\n }\n\n return raw.flatMap((entry) => {\n if (typeof entry === 'string') {\n const pillar = entry.trim()\n return pillar ? [{ pillar }] : []\n }\n\n if (!isRecord(entry)) {\n return []\n }\n\n const pillar = typeof entry.pillar === 'string' ? entry.pillar.trim() : ''\n if (!pillar) {\n return []\n }\n\n const description = typeof entry.description === 'string' ? entry.description.trim() : undefined\n const frequency = typeof entry.frequency === 'string' ? entry.frequency.trim() : undefined\n const formats = isStringArray(entry.formats)\n ? entry.formats.map((format) => format.trim()).filter((format) => format.length > 0)\n : undefined\n\n return [{ pillar, description, frequency, formats }]\n })\n}\n\nfunction summarizeExistingIdeas(ideas: readonly Idea[]): string {\n if (ideas.length === 0) {\n return 'No existing ideas found in the bank.'\n }\n\n return ideas\n .slice(0, 25)\n .map((idea) => `- ${idea.id}: ${idea.topic} [${idea.status}]`)\n .join('\\n')\n}\n\nfunction buildPlatformGuidance(): string {\n return [\n `Allowed platforms for create_idea: ${SUPPORTED_PLATFORMS.join(', ')}`,\n `Create between ${MIN_IDEA_COUNT} and ${MAX_IDEA_COUNT} ideas unless the user explicitly requests fewer within that range.`,\n 'Call create_idea once per idea, then call finalize_ideas exactly once when done.',\n ].join('\\n')\n}\n\nfunction buildBrandPromptSection(brand: BrandContext): string {\n const contentPillars = extractContentPillars(brand)\n const expertise = extractStringArrayField(brand, 'expertise')\n const differentiators = extractStringArrayField(brand, 'differentiators')\n const positioning = typeof brand.positioning === 'string' ? brand.positioning.trim() : ''\n\n const lines = [\n '## Brand Context',\n `Creator: ${brand.name} (${brand.handle})`,\n `Tagline: ${brand.tagline}`,\n `Voice tone: ${brand.voice.tone}`,\n `Voice personality: ${brand.voice.personality}`,\n `Voice style: ${brand.voice.style}`,\n `Primary advocacy: ${brand.advocacy.primary.join(', ') || 'None specified'}`,\n `Interests: ${brand.advocacy.interests.join(', ') || 'None specified'}`,\n `Avoid: ${brand.advocacy.avoids.join(', ') || 'None specified'}`,\n `Social guidance: ${brand.contentGuidelines.socialFocus}`,\n ]\n\n if (positioning) {\n lines.push(`Positioning: ${positioning}`)\n }\n\n if (expertise.length > 0) {\n lines.push(`Expertise areas: ${expertise.join(', ')}`)\n }\n\n if (differentiators.length > 0) {\n lines.push('Differentiators:')\n lines.push(...differentiators.map((item) => `- ${item}`))\n }\n\n if (contentPillars.length > 0) {\n lines.push('Content pillars:')\n lines.push(\n ...contentPillars.map((pillar) => {\n const details = [pillar.description, pillar.frequency && `Frequency: ${pillar.frequency}`, pillar.formats?.length ? `Formats: ${pillar.formats.join(', ')}` : undefined]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' | ')\n\n return details ? `- ${pillar.pillar}: ${details}` : `- ${pillar.pillar}`\n }),\n )\n }\n\n return lines.join('\\n')\n}\n\nfunction buildSystemPrompt(\n brand: BrandContext,\n existingIdeas: readonly Idea[],\n seedTopics: readonly string[],\n count: number,\n): string {\n const promptSections = [\n BASE_SYSTEM_PROMPT,\n '',\n buildBrandPromptSection(brand),\n '',\n '## Existing Idea Bank',\n summarizeExistingIdeas(existingIdeas),\n '',\n '## Planning Constraints',\n `Target idea count: ${count}`,\n buildPlatformGuidance(),\n ]\n\n if (seedTopics.length > 0) {\n promptSections.push('', '## Seed Topics', ...seedTopics.map((topic) => `- ${topic}`))\n }\n\n return promptSections.join('\\n')\n}\n\nfunction buildUserMessage(count: number, seedTopics: readonly string[], hasMcpServers: boolean): string {\n const focusText = seedTopics.length > 0\n ? `Focus areas: ${seedTopics.join(', ')}`\n : 'Focus areas: choose the strongest timely opportunities from the creator context and current trends.'\n\n const steps = [\n '1. Call get_brand_context to load the creator profile.',\n '2. Call get_past_ideas to see what already exists.',\n ]\n\n if (hasMcpServers) {\n steps.push(\n '3. RESEARCH PHASE (REQUIRED): Before creating ANY ideas, use the available MCP tools to research current trends:',\n ' - Use web_search_exa to find trending topics, recent news, and viral content in the focus areas.',\n ' - Use youtube_search or youtube_search_videos to find what videos are performing well right now.',\n ' - Use perplexity-search to get current analysis on promising topics.',\n ' Do at least 2-3 research queries. Each idea you create MUST reference specific findings from this research in its trendContext field.',\n `4. Call create_idea for each of the ${count} ideas, grounding each in your research findings.`,\n '5. Call finalize_ideas when done.',\n )\n } else {\n steps.push(\n `3. Call create_idea for each of the ${count} ideas.`,\n '4. Call finalize_ideas when done.',\n )\n }\n\n return [\n `Generate ${count} new content ideas.`,\n focusText,\n '',\n 'Follow this exact workflow:',\n ...steps,\n ].join('\\n')\n}\n\nasync function loadBrandContext(brandPath?: string): Promise<BrandContext> {\n if (!brandPath) {\n return await Promise.resolve(getBrandConfig()) as BrandContext\n }\n\n return readJsonFile<BrandContext>(brandPath)\n}\n\nfunction normalizePlatforms(platforms: string[]): Idea['platforms'] {\n const normalized = platforms.map((platform) => platform.trim().toLowerCase())\n const invalid = normalized.filter((platform) => !SUPPORTED_PLATFORMS.includes(platform as SupportedPlatform))\n if (invalid.length > 0) {\n throw new Error(`Unsupported platforms: ${invalid.join(', ')}`)\n }\n\n return normalized as Idea['platforms']\n}\n\nfunction assertKebabCaseId(id: string): string {\n const normalized = id.trim()\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(normalized)) {\n throw new Error(`Idea ID must be kebab-case: ${id}`)\n }\n\n return normalized\n}\n\nfunction buildIdea(args: CreateIdeaArgs): Idea {\n const now = new Date().toISOString()\n const publishBy = args.publishBy.trim()\n\n if (args.hook.trim().length > 80) {\n throw new Error(`Idea hook must be 80 characters or fewer: ${args.id}`)\n }\n\n if (Number.isNaN(new Date(publishBy).getTime())) {\n throw new Error(`Invalid publishBy date: ${args.publishBy}`)\n }\n\n return {\n id: assertKebabCaseId(args.id),\n topic: args.topic.trim(),\n hook: args.hook.trim(),\n audience: args.audience.trim(),\n keyTakeaway: args.keyTakeaway.trim(),\n talkingPoints: args.talkingPoints.map((point) => point.trim()).filter((point) => point.length > 0),\n platforms: normalizePlatforms(args.platforms),\n status: 'draft',\n tags: args.tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0),\n trendContext: args.trendContext?.trim() || undefined,\n createdAt: now,\n updatedAt: now,\n publishBy,\n }\n}\n\nclass IdeationAgent extends BaseAgent {\n private readonly brandContext: BrandContext\n private readonly existingIdeas: Idea[]\n private readonly ideasDir?: string\n private readonly targetCount: number\n private generatedIdeas: Idea[] = []\n private finalized = false\n\n constructor(systemPrompt: string, context: IdeationAgentContext, model?: string) {\n super('IdeationAgent', systemPrompt, getProvider(), model ?? getModelForAgent('IdeationAgent'))\n this.brandContext = context.brandContext\n this.existingIdeas = [...context.existingIdeas]\n this.ideasDir = context.ideasDir\n this.targetCount = context.targetCount\n }\n\n protected resetForRetry(): void {\n this.generatedIdeas = []\n this.finalized = false\n }\n\n protected getMcpServers(): Record<string, MCPServerConfig> | undefined {\n const config = getConfig()\n const servers: Record<string, MCPServerConfig> = {}\n\n if (config.EXA_API_KEY) {\n servers.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 if (config.YOUTUBE_API_KEY) {\n servers.youtube = {\n type: 'local' as const,\n command: 'npx',\n args: ['-y', '@htekdev/youtube-mcp-server'],\n env: { YOUTUBE_API_KEY: config.YOUTUBE_API_KEY },\n tools: ['*'],\n }\n }\n\n if (config.PERPLEXITY_API_KEY) {\n servers.perplexity = {\n type: 'local' as const,\n command: 'npx',\n args: ['-y', 'perplexity-mcp'],\n env: { PERPLEXITY_API_KEY: config.PERPLEXITY_API_KEY },\n tools: ['*'],\n }\n }\n\n return Object.keys(servers).length > 0 ? servers : undefined\n }\n\n protected getTools(): ToolWithHandler[] {\n return [\n {\n name: 'get_brand_context',\n description: 'Return the creator brand context and content pillars.',\n parameters: {\n type: 'object',\n properties: {},\n },\n handler: async (args: Record<string, unknown>) => this.handleToolCall('get_brand_context', args),\n },\n {\n name: 'get_past_ideas',\n description: 'Return the current idea bank to help avoid duplicate ideas.',\n parameters: {\n type: 'object',\n properties: {},\n },\n handler: async (args: Record<string, unknown>) => this.handleToolCall('get_past_ideas', args),\n },\n {\n name: 'create_idea',\n description: 'Create a new draft content idea and persist it to the idea bank.',\n parameters: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Kebab-case idea identifier' },\n topic: { type: 'string', description: 'Main topic or title' },\n hook: { type: 'string', description: 'Attention-grabbing hook (80 chars max)' },\n audience: { type: 'string', description: 'Target audience' },\n keyTakeaway: { type: 'string', description: 'Single memorable takeaway' },\n talkingPoints: {\n type: 'array',\n items: { type: 'string' },\n description: 'Bullet points to cover in the recording',\n },\n platforms: {\n type: 'array',\n items: {\n type: 'string',\n enum: [...SUPPORTED_PLATFORMS],\n },\n description: 'Target publishing platforms',\n },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Categorization tags',\n },\n publishBy: {\n type: 'string',\n description: 'ISO 8601 date for when this content should be published by. Hot trends: 3-5 days, timely events: 1-2 weeks, evergreen: 3-6 months.',\n },\n trendContext: {\n type: 'string',\n description: 'Why this idea is timely right now',\n },\n },\n required: ['id', 'topic', 'hook', 'audience', 'keyTakeaway', 'talkingPoints', 'platforms', 'tags', 'publishBy'],\n },\n handler: async (args: Record<string, unknown>) => this.handleToolCall('create_idea', args),\n },\n {\n name: 'finalize_ideas',\n description: 'Signal that idea generation is complete.',\n parameters: {\n type: 'object',\n properties: {},\n },\n handler: async (args: Record<string, unknown>) => this.handleToolCall('finalize_ideas', args),\n },\n ]\n }\n\n protected async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {\n switch (toolName) {\n case 'get_brand_context':\n return this.brandContext ?? await Promise.resolve(getBrandConfig())\n case 'get_past_ideas': {\n const ideas = await readIdeaBank(this.ideasDir)\n return ideas.map((idea) => ({\n id: idea.id,\n topic: idea.topic,\n status: idea.status,\n }))\n }\n case 'create_idea':\n return this.handleCreateIdea(args)\n case 'finalize_ideas':\n this.finalized = true\n return { success: true, count: this.generatedIdeas.length }\n default:\n throw new Error(`Unknown tool: ${toolName}`)\n }\n }\n\n private async handleCreateIdea(args: Record<string, unknown>): Promise<{ success: true; idea: Idea }> {\n if (this.generatedIdeas.length >= this.targetCount) {\n throw new Error(`Target idea count already reached (${this.targetCount})`)\n }\n\n const createArgs = this.parseCreateIdeaArgs(args)\n const idea = buildIdea(createArgs)\n const duplicateTopic = this.findDuplicateTopic(idea.topic)\n if (duplicateTopic) {\n throw new Error(`Duplicate idea topic detected: ${duplicateTopic}`)\n }\n\n const duplicateId = this.findDuplicateId(idea.id)\n if (duplicateId) {\n throw new Error(`Duplicate idea ID detected: ${duplicateId}`)\n }\n\n await writeIdea(idea, this.ideasDir)\n this.generatedIdeas.push(idea)\n logger.info(`[IdeationAgent] Created idea ${idea.id}: ${idea.topic}`)\n\n return { success: true, idea }\n }\n\n private parseCreateIdeaArgs(args: Record<string, unknown>): CreateIdeaArgs {\n const { id, topic, hook, audience, keyTakeaway, talkingPoints, platforms, tags, publishBy, trendContext } = args\n\n if (\n typeof id !== 'string'\n || typeof topic !== 'string'\n || typeof hook !== 'string'\n || typeof audience !== 'string'\n || typeof keyTakeaway !== 'string'\n || !isStringArray(talkingPoints)\n || !isStringArray(platforms)\n || !isStringArray(tags)\n || typeof publishBy !== 'string'\n || (trendContext !== undefined && typeof trendContext !== 'string')\n ) {\n throw new Error('Invalid create_idea arguments')\n }\n\n return {\n id,\n topic,\n hook,\n audience,\n keyTakeaway,\n talkingPoints,\n platforms,\n tags,\n publishBy,\n trendContext,\n }\n }\n\n private findDuplicateId(id: string): string | undefined {\n const normalizedId = id.trim().toLowerCase()\n const existing = [...this.existingIdeas, ...this.generatedIdeas]\n .find((idea) => idea.id.trim().toLowerCase() === normalizedId)\n\n return existing?.id\n }\n\n private findDuplicateTopic(topic: string): string | undefined {\n const normalizedTopic = topic.trim().toLowerCase()\n const existing = [...this.existingIdeas, ...this.generatedIdeas]\n .find((idea) => idea.topic.trim().toLowerCase() === normalizedTopic)\n\n return existing?.topic\n }\n\n getGeneratedIdeas(): Idea[] {\n return [...this.generatedIdeas]\n }\n\n isFinalized(): boolean {\n return this.finalized\n }\n}\n\nexport async function generateIdeas(options: GenerateIdeasOptions = {}): Promise<Idea[]> {\n const seedTopics = normalizeSeedTopics(options.seedTopics)\n const count = normalizeCount(options.count)\n const config = getConfig()\n const previousBrandPath = config.BRAND_PATH\n\n if (options.brandPath) {\n config.BRAND_PATH = options.brandPath\n }\n\n const brandContext = await loadBrandContext(options.brandPath)\n const existingIdeas = await readIdeaBank(options.ideasDir)\n const systemPrompt = buildSystemPrompt(brandContext, existingIdeas, seedTopics, count)\n const agent = new IdeationAgent(systemPrompt, {\n brandContext,\n existingIdeas,\n ideasDir: options.ideasDir,\n targetCount: count,\n })\n\n try {\n const hasMcpServers = !!(config.EXA_API_KEY || config.YOUTUBE_API_KEY || config.PERPLEXITY_API_KEY)\n const userMessage = buildUserMessage(count, seedTopics, hasMcpServers)\n await agent.run(userMessage)\n\n const ideas = agent.getGeneratedIdeas()\n if (!agent.isFinalized()) {\n logger.warn('[IdeationAgent] finalize_ideas was not called before returning results')\n }\n\n return ideas\n } finally {\n config.BRAND_PATH = previousBrandPath\n await agent.destroy()\n }\n}\n","/**\n * L5 wrappers for pipeline infrastructure services from L4.\n * Maintains strict layer hierarchy: L6 → L5 → L4 → L3.\n */\n\nimport {\n costTracker as _costTracker,\n markPending as _markPending,\n markProcessing as _markProcessing,\n markCompleted as _markCompleted,\n markFailed as _markFailed,\n buildPublishQueue as _buildPublishQueue,\n commitAndPush as _commitAndPush,\n} from '../L4-agents/pipelineServiceBridge.js'\nimport { ScheduleAgent as _ScheduleAgent } from '../L4-agents/ScheduleAgent.js'\nimport { generateIdeas as _generateIdeas } from '../L4-agents/IdeationAgent.js'\n\n// Re-export types (exempt from layer rules)\nexport type { CostReport, QueueBuildResult } from '../L4-agents/pipelineServiceBridge.js'\n\n// Cost tracking — proxy delegating to L4 bridge\nexport const costTracker = {\n reset: (...args: Parameters<typeof _costTracker.reset>) => _costTracker.reset(...args),\n setStage: (...args: Parameters<typeof _costTracker.setStage>) => _costTracker.setStage(...args),\n getReport: (...args: Parameters<typeof _costTracker.getReport>) => _costTracker.getReport(...args),\n formatReport: (...args: Parameters<typeof _costTracker.formatReport>) => _costTracker.formatReport(...args),\n recordServiceUsage: (...args: Parameters<typeof _costTracker.recordServiceUsage>) => _costTracker.recordServiceUsage(...args),\n} as const\n\n// Processing state\nexport function markPending(...args: Parameters<typeof _markPending>): ReturnType<typeof _markPending> {\n return _markPending(...args)\n}\n\nexport function markProcessing(...args: Parameters<typeof _markProcessing>): ReturnType<typeof _markProcessing> {\n return _markProcessing(...args)\n}\n\nexport function markCompleted(...args: Parameters<typeof _markCompleted>): ReturnType<typeof _markCompleted> {\n return _markCompleted(...args)\n}\n\nexport function markFailed(...args: Parameters<typeof _markFailed>): ReturnType<typeof _markFailed> {\n return _markFailed(...args)\n}\n\n// Queue builder\nexport function buildPublishQueue(...args: Parameters<typeof _buildPublishQueue>): ReturnType<typeof _buildPublishQueue> {\n return _buildPublishQueue(...args)\n}\n\n// Git operations\nexport function commitAndPush(...args: Parameters<typeof _commitAndPush>): ReturnType<typeof _commitAndPush> {\n return _commitAndPush(...args)\n}\n\n// Ideation\nexport function generateIdeas(...args: Parameters<typeof _generateIdeas>): ReturnType<typeof _generateIdeas> {\n return _generateIdeas(...args)\n}\n\n// Schedule agent factory\nexport function createScheduleAgent(\n ...args: ConstructorParameters<typeof _ScheduleAgent>\n): InstanceType<typeof _ScheduleAgent> {\n return new _ScheduleAgent(...args)\n}\n","import { spawnCommand, createModuleRequire } from '../../L1-infra/process/process.js'\nimport { fileExistsSync } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join } from '../../L1-infra/paths/paths.js'\nimport { getConfig } from '../../L1-infra/config/environment.js'\nimport { createLateApiClient } from '../../L3-services/lateApi/lateApiService.js'\nimport { loadScheduleConfig } from '../../L3-services/scheduler/scheduleConfig.js'\nimport type { ProviderName } from '../../L3-services/llm/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 = createLateApiClient(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 '../../L1-infra/cli/cli.js'\nimport { writeTextFile, readTextFile, fileExists } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join } from '../../L1-infra/paths/paths.js'\nimport { getFFmpegPath, getFFprobePath } from '../../L3-services/diagnostics/diagnostics.js'\nimport { createLateApiClient } from '../../L3-services/lateApi/lateApiService.js'\nimport { getDefaultScheduleConfig } from '../../L3-services/scheduler/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 = createLateApiClient(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","/**\n * L3 service wrapper for FFmpeg path resolution.\n *\n * Wraps L2 path resolvers so that L7 (and higher layers) can access\n * FFmpeg/FFprobe binary paths without importing L2 directly.\n */\nimport { getFFmpegPath as _getFFmpegPath, getFFprobePath as _getFFprobePath } from '../../L2-clients/ffmpeg/ffmpeg.js'\n\nexport function getFFmpegPath(\n ...args: Parameters<typeof _getFFmpegPath>\n): ReturnType<typeof _getFFmpegPath> {\n return _getFFmpegPath(...args)\n}\n\nexport function getFFprobePath(\n ...args: Parameters<typeof _getFFprobePath>\n): ReturnType<typeof _getFFprobePath> {\n return _getFFprobePath(...args)\n}\n","import { getScheduleCalendar } from '../../L3-services/scheduler/scheduler'\nimport { loadScheduleConfig } from '../../L3-services/scheduler/scheduleConfig'\nimport { initConfig } from '../../L1-infra/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","import { initConfig } from '../../L1-infra/config/environment.js'\nimport { buildRealignPlan, executeRealignPlan } from '../../L3-services/scheduler/realign.js'\n\nexport interface RealignCommandOptions {\n platform?: string\n dryRun?: boolean\n}\n\nfunction formatDate(iso: string): string {\n const d = new Date(iso)\n return d.toLocaleDateString('en-US', {\n timeZone: 'America/Chicago',\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n }) + ' ' + d.toLocaleTimeString('en-US', {\n timeZone: 'America/Chicago',\n hour: 'numeric',\n minute: '2-digit',\n })\n}\n\nconst STATUS_ICON: Record<string, string> = {\n scheduled: '📅',\n draft: '📝',\n cancelled: '🚫',\n failed: '❌',\n}\n\nconst PLATFORM_ICON: Record<string, string> = {\n tiktok: '🎵',\n youtube: '▶️',\n instagram: '📸',\n linkedin: '💼',\n twitter: '🐦',\n}\n\nexport async function runRealign(options: RealignCommandOptions = {}): Promise<void> {\n initConfig()\n\n console.log('\\n🔄 Realign Late Posts\\n')\n\n if (options.platform) {\n console.log(` Platform filter: ${options.platform}`)\n }\n if (options.dryRun) {\n console.log(' Mode: DRY RUN (no changes will be made)\\n')\n }\n\n console.log(' Fetching posts from Late API...')\n const plan = await buildRealignPlan({ platform: options.platform })\n\n if (plan.totalFetched === 0) {\n console.log(' ✅ No posts found to realign.\\n')\n return\n }\n\n console.log(` Found ${plan.totalFetched} total post(s)`)\n if (plan.skipped > 0) {\n console.log(` ✅ ${plan.skipped} post(s) already on valid slots — skipped`)\n }\n if (plan.unmatched > 0) {\n console.log(` ⚠️ ${plan.unmatched} post(s) had no local metadata match (defaulting to \"short\" clip type)`)\n }\n console.log(` ${plan.posts.length} post(s) will be realigned`)\n if (plan.toCancel.length > 0) {\n console.log(` ${plan.toCancel.length} post(s) will be cancelled (no matching schedule slots)`)\n }\n console.log()\n\n if (plan.posts.length === 0 && plan.toCancel.length === 0) {\n console.log(' ✅ Nothing to realign.\\n')\n return\n }\n\n // Show posts to cancel\n if (plan.toCancel.length > 0) {\n console.log(' 🚫 Posts to cancel:')\n const cancelByPlatform = new Map<string, typeof plan.toCancel>()\n for (const p of plan.toCancel) {\n if (!cancelByPlatform.has(p.platform)) cancelByPlatform.set(p.platform, [])\n cancelByPlatform.get(p.platform)!.push(p)\n }\n for (const [platform, posts] of cancelByPlatform) {\n const icon = PLATFORM_ICON[platform] ?? '📱'\n console.log(` ${icon} ${platform} (${posts.length} posts) — ${posts[0].reason}`)\n for (const entry of posts.slice(0, 5)) {\n const preview = entry.post.content.slice(0, 50).replace(/\\n/g, ' ')\n console.log(` [${entry.clipType}] \"${preview}...\"`)\n }\n if (posts.length > 5) {\n console.log(` ... and ${posts.length - 5} more`)\n }\n }\n console.log()\n }\n\n // Show posts to realign\n if (plan.posts.length > 0) {\n // Group by platform for display\n const byPlatform = new Map<string, typeof plan.posts>()\n for (const p of plan.posts) {\n if (!byPlatform.has(p.platform)) byPlatform.set(p.platform, [])\n byPlatform.get(p.platform)!.push(p)\n }\n\n for (const [platform, posts] of byPlatform) {\n const icon = PLATFORM_ICON[platform] ?? '📱'\n console.log(` ${icon} ${platform} (${posts.length} posts)`)\n\n for (const entry of posts) {\n const statusIcon = STATUS_ICON[entry.post.status] ?? '❓'\n const oldTime = entry.oldScheduledFor ? formatDate(entry.oldScheduledFor) : 'unscheduled'\n const newTime = formatDate(entry.newScheduledFor)\n const preview = entry.post.content.slice(0, 50).replace(/\\n/g, ' ')\n console.log(` ${statusIcon} [${entry.clipType}] \"${preview}...\"`)\n console.log(` ${oldTime} → ${newTime}`)\n }\n console.log()\n }\n }\n\n if (options.dryRun) {\n console.log(' 🏁 Dry run complete — no changes made.\\n')\n return\n }\n\n console.log(' 🚀 Executing updates...\\n')\n const result = await executeRealignPlan(plan)\n\n console.log(` ✅ Updated: ${result.updated}`)\n if (result.cancelled > 0) {\n console.log(` 🚫 Cancelled: ${result.cancelled}`)\n }\n if (result.failed > 0) {\n console.log(` ❌ Failed: ${result.failed}`)\n for (const err of result.errors) {\n console.log(` ${err.postId}: ${err.error}`)\n }\n }\n console.log()\n}\n","import { initConfig } from '../../L1-infra/config/environment.js'\nimport { setChatMode } from '../../L1-infra/logger/configLogger.js'\nimport { createChatInterface } from '../../L1-infra/readline/readline.js'\nimport { createScheduleAgent } from '../../L6-pipeline/scheduleChat.js'\nimport type { UserInputRequest, UserInputResponse } from '../../L3-services/llm/providerFactory.js'\n\nexport async function runChat(): Promise<void> {\n initConfig()\n\n // Suppress Winston console transport so it doesn't corrupt readline\n setChatMode(true)\n\n const rl = createChatInterface()\n\n // CLI-based user input handler for ask_user tool\n const handleUserInput = (request: UserInputRequest): Promise<UserInputResponse> => {\n return new Promise((resolve) => {\n console.log()\n console.log(`\\x1b[33m🤖 Agent asks:\\x1b[0m ${request.question}`)\n\n if (request.choices && request.choices.length > 0) {\n for (let i = 0; i < request.choices.length; i++) {\n console.log(` ${i + 1}. ${request.choices[i]}`)\n }\n if (request.allowFreeform !== false) {\n console.log(` (or type a custom answer)`)\n }\n }\n\n rl.question('\\x1b[33m> \\x1b[0m', (answer) => {\n const trimmed = answer.trim()\n\n if (request.choices && request.choices.length > 0) {\n const num = parseInt(trimmed, 10)\n if (num >= 1 && num <= request.choices.length) {\n resolve({ answer: request.choices[num - 1], wasFreeform: false })\n return\n }\n }\n\n resolve({ answer: trimmed, wasFreeform: true })\n })\n })\n }\n\n const agent = createScheduleAgent(handleUserInput)\n\n // Wire clean chat output for tool progress\n agent.setChatOutput((message: string) => {\n process.stderr.write(`${message}\\n`)\n })\n\n console.log(`\n\\x1b[36m╔══════════════════════════════════════╗\n║ VidPipe Chat ║\n╚══════════════════════════════════════╝\\x1b[0m\n\nSchedule management assistant. Ask me about your posting schedule,\nreschedule posts, check what's coming up, or reprioritize content.\n\nType \\x1b[33mexit\\x1b[0m or \\x1b[33mquit\\x1b[0m to leave. Press Ctrl+C to stop.\n`)\n\n let closeRejector: ((err: Error) => void) | null = null\n const closePromise = new Promise<never>((_, reject) => {\n closeRejector = reject\n rl.once('close', () => reject(new Error('readline closed')))\n })\n\n const prompt = (): Promise<string> => {\n return Promise.race([\n new Promise<string>((resolve) => {\n rl.question('\\x1b[32mvidpipe>\\x1b[0m ', (answer) => {\n resolve(answer)\n })\n }),\n closePromise\n ])\n }\n\n try {\n while (true) {\n let input: string\n try {\n input = await prompt()\n } catch {\n break\n }\n\n const trimmed = input.trim()\n if (!trimmed) continue\n if (trimmed === 'exit' || trimmed === 'quit') {\n console.log('\\nGoodbye! 👋')\n break\n }\n\n try {\n await agent.run(trimmed)\n console.log('\\n') // newline after streamed response\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n console.error(`\\n\\x1b[31mError: ${message}\\x1b[0m\\n`)\n }\n }\n } finally {\n await agent.destroy()\n rl.close()\n setChatMode(false)\n }\n}\n","import { createInterface, type Interface } from 'node:readline'\n\nexport interface ChatInterfaceOptions {\n input?: NodeJS.ReadableStream\n output?: NodeJS.WritableStream\n}\n\n/**\n * Creates a readline interface configured for chat use.\n * Uses `terminal: false` to prevent double-echo on Windows.\n */\nexport function createChatInterface(options?: ChatInterfaceOptions): Interface {\n return createInterface({\n input: options?.input ?? process.stdin,\n output: options?.output ?? process.stdout,\n terminal: false,\n })\n}\n","/** Wrapper for ScheduleAgent for L7 consumption via L6 → L5 → L4. */\nimport { createScheduleAgent as _createScheduleAgent } from '../L5-assets/pipelineServices.js'\n\nexport function createScheduleAgent(\n ...args: Parameters<typeof _createScheduleAgent>\n): ReturnType<typeof _createScheduleAgent> {\n return _createScheduleAgent(...args)\n}\n","import { initConfig } from '../../L1-infra/config/environment.js'\nimport { readIdeaBank } from '../../L1-infra/ideaStore/ideaStore.js'\nimport { generateIdeas } from '../../L6-pipeline/ideation.js'\n\nexport interface IdeateCommandOptions {\n topics?: string\n count?: string\n output?: string\n brand?: string\n list?: boolean\n status?: string\n}\n\nexport async function runIdeate(options: IdeateCommandOptions = {}): Promise<void> {\n initConfig()\n\n if (options.list) {\n const ideas = await readIdeaBank(options.output)\n const filtered = options.status\n ? ideas.filter(i => i.status === options.status)\n : ideas\n\n if (filtered.length === 0) {\n console.log('No ideas found.')\n if (options.status) {\n console.log(`(filtered by status: ${options.status})`)\n }\n console.log('\\nRun `vidpipe ideate` to generate new ideas.')\n return\n }\n\n console.log('\\n💡 Content Ideas\\n')\n console.log(`${'ID'.padEnd(30)} ${'Topic'.padEnd(35)} ${'Status'.padEnd(12)} ${'Platforms'}`)\n console.log('─'.repeat(95))\n for (const idea of filtered) {\n console.log(\n `${idea.id.padEnd(30)} ${idea.topic.substring(0, 33).padEnd(35)} ${idea.status.padEnd(12)} ${idea.platforms.join(', ')}`,\n )\n }\n console.log(`\\n${filtered.length} idea(s) total`)\n return\n }\n\n const seedTopics = options.topics?.split(',').map(t => t.trim()).filter(Boolean)\n const count = options.count ? parseInt(options.count, 10) : 5\n\n console.log('\\n🧠 Generating content ideas...\\n')\n if (seedTopics?.length) {\n console.log(`Seed topics: ${seedTopics.join(', ')}`)\n }\n console.log(`Target count: ${count}\\n`)\n\n const ideas = await generateIdeas({\n seedTopics,\n count,\n ideasDir: options.output,\n brandPath: options.brand,\n })\n\n if (ideas.length === 0) {\n console.log('No ideas were generated. Check your API key configuration.')\n return\n }\n\n console.log(`\\n✅ Generated ${ideas.length} idea(s):\\n`)\n for (const idea of ideas) {\n console.log(` 📌 ${idea.topic}`)\n console.log(` Hook: ${idea.hook}`)\n console.log(` Audience: ${idea.audience}`)\n console.log(` Platforms: ${idea.platforms.join(', ')}`)\n console.log(` Status: ${idea.status}`)\n console.log('')\n }\n\n console.log('Ideas saved to ./ideas/ directory.')\n console.log('Use `vidpipe ideate --list` to view all ideas.')\n console.log('Use `vidpipe process video.mp4 --ideas <id1>,<id2>` to link ideas to a recording.')\n}\n","/**\n * L6 pipeline bridge for ideation.\n * Exposes generateIdeas to L7-app via the L5 → L4 chain.\n */\nimport { generateIdeas as _generateIdeas } from '../L5-assets/pipelineServices.js'\n\nexport function generateIdeas(...args: Parameters<typeof _generateIdeas>): ReturnType<typeof _generateIdeas> {\n return _generateIdeas(...args)\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 '../../L1-infra/http/http.js'\nimport { join, dirname, fileURLToPath } from '../../L1-infra/paths/paths.js'\nimport { createRouter } from './routes'\nimport { getConfig } from '../../L1-infra/config/environment'\nimport logger from '../../L1-infra/logger/configLogger'\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 { Router } from '../../L1-infra/http/http.js'\nimport {\n getPendingItems,\n getGroupedPendingItems,\n getItem,\n updateItem,\n rejectItem,\n type GroupedQueueItem,\n type QueueItem,\n} from '../../L3-services/postStore/postStore.js'\nimport { getIdeasByIds } from '../../L3-services/ideation/ideaService.js'\nimport { findNextSlot, getScheduleCalendar } from '../../L3-services/scheduler/scheduler.js'\nimport { createLateApiClient, type LateApiClient, type LateAccount, type LateProfile } from '../../L3-services/lateApi/lateApiService.js'\nimport { normalizePlatformString } from '../../L0-pure/types/index.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { enqueueApproval } from './approvalQueue.js'\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\ntype ReviewQueueItem = QueueItem & { ideaPublishBy?: string }\ntype ReviewGroupedQueueItem = Omit<GroupedQueueItem, 'items'> & { items: ReviewQueueItem[] }\n\nasync function getEarliestPublishBy(ideaIds: string[]): Promise<string | undefined> {\n try {\n const ideas = await getIdeasByIds(ideaIds)\n const publishByDates = ideas\n .map((idea) => idea.publishBy)\n .filter((publishBy): publishBy is string => Boolean(publishBy))\n .sort()\n return publishByDates[0]\n } catch {\n return undefined\n }\n}\n\nasync function enrichQueueItem(item: QueueItem): Promise<ReviewQueueItem> {\n const ideaPublishBy = item.metadata.ideaIds?.length\n ? await getEarliestPublishBy(item.metadata.ideaIds)\n : undefined\n\n return {\n ...item,\n ...(ideaPublishBy ? { ideaPublishBy } : {}),\n }\n}\n\nasync function enrichQueueItems(items: QueueItem[]): Promise<ReviewQueueItem[]> {\n return Promise.all(items.map((item) => enrichQueueItem(item)))\n}\n\nasync function enrichGroupedQueueItems(groups: GroupedQueueItem[]): Promise<ReviewGroupedQueueItem[]> {\n return Promise.all(groups.map(async (group) => ({\n ...group,\n items: await enrichQueueItems(group.items),\n })))\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 enrichQueueItems(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 enrichGroupedQueueItems(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 (async () => enrichGroupedQueueItems(await getGroupedPendingItems()))(),\n (async () => {\n const cached = getCached<LateAccount[]>('accounts')\n if (cached) return cached\n const client = createLateApiClient()\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 = createLateApiClient()\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(await enrichQueueItem(item))\n })\n\n // POST /api/posts/:id/approve — enqueue for sequential processing, return 202\n router.post('/api/posts/:id/approve', (req, res) => {\n const itemId = req.params.id\n\n res.status(202).json({ accepted: true })\n\n enqueueApproval([itemId]).then(result => {\n if (result.scheduled > 0) {\n logger.info(`Single approve completed: ${String(itemId).replace(/[\\r\\n]/g, '')} → ${result.results[0]?.scheduledFor}`)\n } else {\n logger.error(`Single approve failed: ${String(itemId).replace(/[\\r\\n]/g, '')}: ${result.results[0]?.error}`)\n }\n }).catch(() => {})\n })\n\n // POST /api/posts/bulk-approve — fire-and-forget: returns 202 immediately, processes sequentially in queue\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 enqueueApproval(itemIds).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 // 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 clipType = typeof req.query.clipType === 'string' ? req.query.clipType : undefined\n const slot = await findNextSlot(normalized, clipType)\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 = createLateApiClient()\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 = createLateApiClient()\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 { fileExists } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { getItem, approveItem, approveBulk } from '../../L3-services/postStore/postStore.js'\nimport { getIdeasByIds } from '../../L3-services/ideation/ideaService.js'\nimport { findNextSlot } from '../../L3-services/scheduler/scheduler.js'\nimport { loadScheduleConfig } from '../../L3-services/scheduler/scheduleConfig.js'\nimport { getAccountId } from '../../L3-services/socialPosting/accountMapping.js'\nimport { createLateApiClient } from '../../L3-services/lateApi/lateApiService.js'\nimport { fromLatePlatform, normalizePlatformString } from '../../L0-pure/types/index.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\n\n// ── Types ────────────────────────────────────────────────────────────────\n\ninterface ApprovalJob {\n itemIds: string[]\n resolve: (result: ApprovalResult) => void\n}\n\nexport interface ApprovalResult {\n scheduled: number\n failed: number\n results: Array<{\n itemId: string\n success: boolean\n scheduledFor?: string\n latePostId?: string\n error?: string\n }>\n rateLimitedPlatforms: string[]\n}\n\n// ── Sequential approval queue ────────────────────────────────────────────\n// All approve operations (single + bulk) funnel through this queue.\n// Items are processed one at a time, preventing findNextSlot() race conditions.\n\nconst queue: ApprovalJob[] = []\nlet processing = false\n\nexport function enqueueApproval(itemIds: string[]): Promise<ApprovalResult> {\n return new Promise(resolve => {\n queue.push({ itemIds, resolve })\n if (!processing) drain()\n })\n}\n\nasync function drain(): Promise<void> {\n processing = true\n while (queue.length > 0) {\n const job = queue.shift()!\n try {\n const result = await processApprovalBatch(job.itemIds)\n job.resolve(result)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n logger.error(`Approval queue drain error: ${msg.replace(/[\\r\\n]/g, '')}`)\n job.resolve({\n scheduled: 0,\n failed: job.itemIds.length,\n results: job.itemIds.map(id => ({ itemId: id, success: false, error: msg })),\n rateLimitedPlatforms: [],\n })\n }\n }\n processing = false\n}\n\nasync function processApprovalBatch(itemIds: string[]): Promise<ApprovalResult> {\n const client = createLateApiClient()\n const schedConfig = await loadScheduleConfig()\n const publishDataMap = new Map<string, { latePostId: string; scheduledFor: string; publishedUrl?: string; accountId?: string }>()\n const results: ApprovalResult['results'] = []\n const rateLimitedPlatforms = new Set<string>()\n\n interface EnrichedItem {\n id: string\n publishBy: string | null\n hasIdeas: boolean\n }\n\n const loadedItems = await Promise.all(\n itemIds.map(async (id) => ({ id, item: await getItem(id) })),\n )\n const itemMap = new Map(loadedItems.map(({ id, item }) => [id, item]))\n const enriched: EnrichedItem[] = await Promise.all(\n loadedItems.map(async ({ id, item }) => {\n if (!item?.metadata.ideaIds?.length) {\n return { id, publishBy: null, hasIdeas: false }\n }\n\n try {\n const ideas = await getIdeasByIds(item.metadata.ideaIds)\n const dates = ideas\n .map((idea) => idea.publishBy)\n .filter((publishBy): publishBy is string => Boolean(publishBy))\n .sort()\n return { id, publishBy: dates[0] ?? null, hasIdeas: true }\n } catch {\n return { id, publishBy: null, hasIdeas: true }\n }\n }),\n )\n\n const now = Date.now()\n const sevenDays = 7 * 24 * 60 * 60 * 1000\n enriched.sort((a, b) => {\n const aPublishByTime = a.publishBy ? new Date(a.publishBy).getTime() : Number.NaN\n const bPublishByTime = b.publishBy ? new Date(b.publishBy).getTime() : Number.NaN\n const aUrgent = a.hasIdeas && Number.isFinite(aPublishByTime) && (aPublishByTime - now) < sevenDays\n const bUrgent = b.hasIdeas && Number.isFinite(bPublishByTime) && (bPublishByTime - now) < sevenDays\n if (aUrgent && !bUrgent) return -1\n if (!aUrgent && bUrgent) return 1\n if (a.hasIdeas && !b.hasIdeas) return -1\n if (!a.hasIdeas && b.hasIdeas) return 1\n return 0\n })\n\n const sortedIds = enriched.map((entry) => entry.id)\n const publishByMap = new Map(\n enriched.flatMap((entry) => (entry.publishBy ? [[entry.id, entry.publishBy] as const] : [])),\n )\n\n for (const itemId of sortedIds) {\n const item = itemMap.get(itemId) ?? null\n\n try {\n if (!item) {\n results.push({ itemId, success: false, error: 'Item not found' })\n continue\n }\n\n const latePlatform = normalizePlatformString(item.metadata.platform)\n\n if (rateLimitedPlatforms.has(latePlatform)) {\n results.push({ itemId, success: false, error: `${latePlatform} rate-limited` })\n continue\n }\n\n const ideaIds = item.metadata.ideaIds\n const publishBy = publishByMap.get(itemId)\n const slot = ideaIds?.length\n ? await findNextSlot(latePlatform, item.metadata.clipType, { ideaIds, publishBy })\n : await findNextSlot(latePlatform, item.metadata.clipType)\n if (!slot) {\n results.push({ itemId, success: false, error: `No available slot for ${latePlatform}` })\n continue\n }\n\n const platform = fromLatePlatform(latePlatform)\n const accountId = item.metadata.accountId || await getAccountId(platform)\n if (!accountId) {\n results.push({ itemId, success: false, error: `No account 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 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 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 isDraft: false,\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 results.push({ itemId, success: true, scheduledFor: slot, latePostId: latePost._id })\n } catch (itemErr) {\n const itemMsg = itemErr instanceof Error ? itemErr.message : String(itemErr)\n if (itemMsg.includes('429') || itemMsg.includes('Daily post limit')) {\n const latePlatform = normalizePlatformString(item?.metadata.platform ?? '')\n rateLimitedPlatforms.add(latePlatform)\n logger.warn(`Approval queue: ${latePlatform} hit daily post limit, skipping remaining ${latePlatform} items`)\n results.push({ itemId, success: false, error: `${latePlatform} rate-limited` })\n } else {\n logger.error(`Approval queue: failed for ${String(itemId).replace(/[\\r\\n]/g, '')}: ${String(itemMsg).replace(/[\\r\\n]/g, '')}`)\n results.push({ itemId, success: false, error: itemMsg })\n }\n }\n }\n\n // Approve all successfully posted items\n const successIds = itemIds.filter(id => publishDataMap.has(id))\n if (successIds.length === 1) {\n const id = successIds[0]\n await approveItem(id, publishDataMap.get(id)!)\n } else if (successIds.length > 1) {\n await approveBulk(successIds, publishDataMap)\n }\n\n const scheduled = successIds.length\n const failed = itemIds.length - scheduled\n if (scheduled > 0) {\n logger.info(`Approval queue: ${scheduled} of ${itemIds.length} scheduled${rateLimitedPlatforms.size > 0 ? ` (rate-limited: ${[...rateLimitedPlatforms].join(', ')})` : ''}`)\n }\n\n return { scheduled, failed, results, rateLimitedPlatforms: [...rateLimitedPlatforms] }\n}\n","import { Platform } from '../../L0-pure/types/index.js'\nimport { LateApiClient } from '../../L2-clients/late/lateApi.js'\nimport type { LateAccount } from '../../L2-clients/late/lateApi.js'\nimport logger from '../../L1-infra/logger/configLogger.js'\nimport { readTextFile, writeTextFile, removeFile } from '../../L1-infra/fileSystem/fileSystem.js'\nimport { join, resolve, sep } from '../../L1-infra/paths/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","envPath","default","default","default","config","config","DEFAULT_MODEL","MAX_TOOL_ROUNDS","config","getProvider","getProvider","config","resolve","default","require","config","default","resolve","resolve","resolve","default","ffmpegPath","resolve","ffmpegPath","FONTS_DIR","resolve","resolve","resolve","default","ffmpegPath","ffprobePath","resolve","default","yFrom","yTo","xFrom","xTo","ffmpegPath","resolve","config","ffmpegPath","resolve","ffprobe","extractClip","extractCompositeClip","extractCompositeClipWithTransitions","singlePassEdit","burnCaptions","detectSilence","captureFrame","generatePlatformVariants","detectWebcamRegion","getVideoResolution","compositeOverlays","text","config","resolve","analyzeVideoEditorial","analyzeVideoClipDirection","analyzeVideoForEnhancements","config","config","resolve","config","default","config","default","generateImage","analyzeVideoEditorial","analyzeVideoClipDirection","analyzeVideoForEnhancements","transcribeVideo","generateImage","ffprobe","getVideoResolution","detectWebcamRegion","getConfig","config","analyzeVideoEditorial","analyzeVideoClipDirection","generateImage","Platform","extractCompositeClip","extractCompositeClip","getProvider","resolve","getVideoDuration","ffprobe","detectSilence","singlePassEdit","SYSTEM_PROMPT","extractClip","extractCompositeClip","generatePlatformVariants","burnCaptions","SYSTEM_PROMPT","extractClip","extractCompositeClipWithTransitions","burnCaptions","config","SYSTEM_PROMPT","singlePassEdit","fmtTime","buildTranscriptBlock","captureFrame","toYouTubeTimestamp","config","SYSTEM_PROMPT","config","buildSystemPrompt","config","config","markPublished","stripped","generateImage","costTracker","markPending","markProcessing","markCompleted","markFailed","commitAndPush","buildPublishQueue","SYSTEM_PROMPT","generateImage","analyzeVideoForEnhancements","compositeOverlays","config","resolve","transcribeVideo","burnCaptions","clips","analyzeVideoClipDirection","buildPublishQueue","commitAndPush","config","raw","parsed","config","getTimezoneOffset","buildSlotDatetime","getDayOfWeekInTimezone","PLATFORM_ALIASES","getDayOfWeekInTimezone","buildSlotDatetime","config","queue","SYSTEM_PROMPT","config","isRecord","isStringArray","pillar","buildSystemPrompt","getProvider","config","costTracker","markPending","markProcessing","markCompleted","markFailed","generateIdeas","costTracker","markPending","markProcessing","markCompleted","markFailed","require","config","getFFmpegPath","getFFprobePath","resolve","envPath","getFFmpegPath","ffprobe","getFFprobePath","config","createScheduleAgent","rl","resolve","createScheduleAgent","generateIdeas","ideas","generateIdeas","default","toLatePlatform","cache","resolve","CACHE_TTL_MS","accounts","profile","__dirname","default","resolve","config","getIdeasByIds","markRecorded","processing","queue"]}
|