Files
ajarbot/bfk_workflow.json
Jordan Ramos 916f86725d feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements:
- RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector
- Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection
- Rich conversation storage for notable turns; compact_conversation truncates long user messages
- Task-type classifier (query/action/analysis/creative) for observation tagging
- Nested sub-agent visibility: deep delegations now register against the main agent's manager

Child safety (Gabriel profile):
- child_safety.py: filtering, audit logging, prompt constants for restricted sessions
- .kiro/specs/child-safety-profile: requirements, design, tasks specs
- GABRIEL_BOT_PROPOSAL.md: initial proposal doc
- Reduced context window (10 msgs) and tutor-mode identity for restricted users

Telegram adapter:
- Polling watchdog: auto-restarts updater if polling drops unexpectedly
- get_me() with exponential-backoff retry on NetworkError at startup
- Correct stop() ordering: signal watchdog before cancelling tasks

Email / Gmail:
- send_email: supports file attachments (attachments list param)
- get_email: surfaces attachment metadata in response

Scheduled tasks / weather:
- Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively
- New scheduled tasks and scheduler state persistence

Discord:
- adapters/discord/__init__.py scaffold
- discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config)

Infrastructure:
- n8n workflow exports (garvis_webhook, content_pipeline variants)
- memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs
- UCS C240 migration plan doc
- requirements.txt: new deps
- .claude/settings.json, fix_hooks.py: hook/permission tuning
2026-04-23 07:54:01 -06:00

1 line
27 KiB
JSON

{"updatedAt":"2026-03-20T00:44:39.310Z","createdAt":"2026-03-18T21:45:55.353Z","id":"g7Qzow7QWBxBT2yk","name":"Content Pipeline - BlendedFamilyKitchen","description":null,"active":false,"isArchived":false,"nodes":[{"parameters":{"rule":{"interval":[{"field":"minutes","minutesInterval":30}]}},"id":"eb4a0cfa-bebc-45b5-b783-e33709b17926","name":"Schedule Trigger","type":"n8n-nodes-base.scheduleTrigger","position":[-16,208],"typeVersion":1.1},{"parameters":{"url":"http://192.168.2.200:5000/webapi/auth.cgi","sendQuery":true,"queryParameters":{"parameters":[{"name":"api","value":"SYNO.API.Auth"},{"name":"version","value":"6"},{"name":"method","value":"login"},{"name":"account","value":"garvis-api"},{"name":"passwd","value":"@ms88Str"},{"name":"session","value":"FileStation"},{"name":"format","value":"sid"}]},"options":{}},"id":"0f2f5a90-a8fd-404a-b243-a824c240f565","name":"NAS Login","type":"n8n-nodes-base.httpRequest","position":[192,208],"typeVersion":4.1},{"parameters":{"url":"http://192.168.2.200:5000/webapi/entry.cgi","sendQuery":true,"queryParameters":{"parameters":[{"name":"api","value":"SYNO.FileStation.List"},{"name":"version","value":"2"},{"name":"method","value":"list"},{"name":"folder_path","value":"/BlendedFamilyKitchen/DropZone"},{"name":"additional","value":"size,time"},{"name":"_sid","value":"={{ $('NAS Login').item.json.data.sid }}"}]},"options":{}},"id":"02569323-a788-46aa-8f2a-b2e3da2904a3","name":"Poll NAS DropZone","type":"n8n-nodes-base.httpRequest","position":[416,208],"typeVersion":4.1},{"parameters":{"conditions":{"options":{"leftValue":"","caseSensitive":true,"typeValidation":"loose"},"combinator":"and","conditions":[{"id":"cond1","operator":{"type":"number","operation":"gt"},"leftValue":"={{ $json.data?.files?.length }}","rightValue":"0"}]},"options":{}},"id":"762a445d-c774-4e68-9abd-b05b6247c68a","name":"IF New Files?","type":"n8n-nodes-base.if","position":[640,208],"typeVersion":2},{"parameters":{"jsCode":"// Split data.files array into individual items\nconst files = $input.first().json.data?.files || $input.first().json.files || [];\nreturn files.map(file => ({\n json: typeof file === 'string' ? { name: file } : file\n}));"},"id":"cdf361f7-7b3f-46e3-97cc-a27cdb416e4e","name":"Split Into Batches","type":"n8n-nodes-base.code","position":[944,192],"typeVersion":2},{"parameters":{"assignments":{"assignments":[{"id":"a1","name":"filename","type":"string","value":"={{ $json.name }}"},{"id":"a2","name":"filepath","type":"string","value":"={{ $json.path }}"},{"id":"a3","name":"size","type":"number","value":"={{ $json.additional?.size }}"},{"id":"a4","name":"created","type":"string","value":"={{ $json.additional?.time?.crtime }}"},{"id":"a5","name":"extension","type":"string","value":"={{ $json.name.split('.').pop().toLowerCase() }}"}]},"options":{}},"id":"493da64e-3b20-4ab0-bfd2-dec138fc2148","name":"Extract Metadata","type":"n8n-nodes-base.set","position":[1168,208],"typeVersion":3.4},{"parameters":{"url":"=http://192.168.2.200:5000/webapi/entry.cgi","sendQuery":true,"queryParameters":{"parameters":[{"name":"api","value":"SYNO.FileStation.Download"},{"name":"version","value":"2"},{"name":"method","value":"download"},{"name":"path","value":"={{ $json.filepath }}"},{"name":"_sid","value":"={{ $('NAS Login').item.json.data.sid }}"},{"name":"mode","value":"download"}]},"options":{"response":{"response":{"responseFormat":"file"}}}},"id":"8e79824b-2f3a-4a16-924c-d04350db3f3b","name":"Download Raw Video","type":"n8n-nodes-base.httpRequest","position":[1376,192],"typeVersion":4.1},{"parameters":{"command":"=mkdir -p /opt/n8n/bfk_raw /opt/n8n/bfk_audio && ffmpeg -y -i /opt/n8n/bfk_raw/{{ $json.filename }} -vn -acodec pcm_s16le -ar 16000 -ac 1 /opt/n8n/bfk_audio/{{ $json.filename.replace(/\\.[^.]+$/, '.wav') }} && echo '{\"audio_path\": \"/opt/n8n/bfk_audio/{{ $json.filename.replace(/\\.[^.]+$/, '.wav') }}\", \"video_path\": \"/opt/n8n/bfk_raw/{{ $json.filename }}\"}'"},"id":"57a337cf-f7bc-4184-92fc-ba30e766399b","name":"FFmpeg Extract Audio","type":"n8n-nodes-base.executeCommand","position":[1824,192],"typeVersion":1},{"parameters":{"method":"POST","url":"http://192.168.2.102:8787/v1/audio/transcriptions","sendBody":true,"contentType":"multipart-form-data","bodyParameters":{"parameters":[{"parameterType":"formBinaryData","name":"file","inputDataFieldName":"data"},{"name":"model","value":"Systran/faster-whisper-base"},{"name":"language","value":"en"},{"name":"response_format","value":"srt"}]},"options":{}},"id":"aa607dad-22f6-4e2a-b430-c2e0a146755d","name":"Whisper Transcribe","type":"n8n-nodes-base.httpRequest","position":[2240,416],"typeVersion":4.1},{"parameters":{"method":"POST","url":"=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=AIzaSyDQPi9kXbVpxW790RBuUCeC8t_UgyxX7m4","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={\n \"contents\": [{\n \"parts\": [{\n \"text\": \"You are a TikTok content expert for a blended family cooking channel called BlendedFamilyKitchen. Given this transcript from a cooking video, generate:\\n1) Three hook text options (max 8 words each, attention-grabbing, food-focused)\\n2) A caption/description with relevant hashtags (mix of popular and niche)\\n3) Three title options\\n\\nFormat your response as JSON with keys: hooks (array of 3 strings), caption (string), titles (array of 3 strings).\\n\\nTranscript: {{ $(\"Whisper Transcribe\").item.json.data || $(\"Whisper Transcribe\").item.json.text }}\"\n }]\n }],\n \"generationConfig\": {\n \"temperature\": 0.8,\n \"maxOutputTokens\": 500,\n \"responseMimeType\": \"application/json\"\n }\n}","options":{}},"id":"d7f55aaa-9361-4aff-86cc-641c5d7c57dc","name":"AI Generate Hooks","type":"n8n-nodes-base.httpRequest","position":[2656,-64],"typeVersion":4.1},{"parameters":{"command":"=ffmpeg -y -i /opt/n8n/bfk_raw/{{ $('Extract Metadata').item.json.filename }} -vf \"scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,eq=brightness=0.04:contrast=1.05:saturation=1.15,unsharp=5:5:0.3\" -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 128k -ar 44100 -movflags +faststart /opt/n8n/bfk_processed/normalized_{{ $('Extract Metadata').item.json.filename }} && echo 'normalized'"},"id":"3a685677-482b-4f12-a912-13cb5cdcc45e","name":"FFmpeg Normalize Video","type":"n8n-nodes-base.executeCommand","position":[2768,-336],"typeVersion":1},{"parameters":{"command":"=ffmpeg -y -i /opt/n8n/bfk_processed/normalized_{{ $('Extract Metadata').item.json.filename }} -vf \"subtitles=/opt/n8n/bfk_subs/{{ $('Extract Metadata').item.json.filename.replace(/\\.[^.]+$/, '.srt') }}:force_style='FontName=Arial,FontSize=14,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2,Shadow=1,Alignment=2,MarginV=40'\" -c:v libx264 -preset medium -crf 23 -c:a copy /opt/n8n/bfk_processed/captioned_{{ $('Extract Metadata').item.json.filename }} && echo 'captioned'"},"id":"7d5d82f3-dbd5-4303-a005-b87c557eb077","name":"FFmpeg Burn Captions","type":"n8n-nodes-base.executeCommand","position":[2992,-336],"typeVersion":1},{"parameters":{"command":"=ffmpeg -y -i /opt/n8n/bfk_processed/captioned_{{ $('Extract Metadata').item.json.filename }} -i /data/music/kitchen_bg_01.mp3 -filter_complex \"[1:a]volume=0.12[bg];[0:a][bg]amix=inputs=2:duration=first:dropout_transition=2[aout]\" -map 0:v -map \"[aout]\" -c:v copy -c:a aac -b:a 128k /opt/n8n/bfk_processed/final_{{ $('Extract Metadata').item.json.filename }} && echo 'music_mixed'"},"id":"c76b07ed-aa36-4bcb-a0b3-527e24c7eb25","name":"FFmpeg Mix Background Music","type":"n8n-nodes-base.executeCommand","position":[3216,-336],"typeVersion":1},{"parameters":{"command":"=ffmpeg -y -i /opt/n8n/bfk_processed/final_{{ $('Extract Metadata').item.json.filename }} -vf \"select=gt(scene\\,0.3)\" -frames:v 1 -q:v 2 /opt/n8n/bfk_thumbs/thumb_{{ $('Extract Metadata').item.json.filename.replace(/\\.[^.]+$/, '.jpg') }} && echo 'thumbnail_extracted'"},"id":"0cd53a7d-c1e8-4a56-b899-44315c8ca04d","name":"FFmpeg Extract Thumbnail","type":"n8n-nodes-base.executeCommand","position":[3424,-336],"typeVersion":1},{"parameters":{"url":"https://ads.tiktok.com/creative_radar_api/v1/popular_trend/sound/list","sendQuery":true,"queryParameters":{"parameters":[{"name":"period","value":"7"},{"name":"page","value":"1"},{"name":"limit","value":"10"},{"name":"country_code","value":"US"}]},"options":{}},"id":"9b8e701d-83b1-41b5-bb6c-b03feec16feb","name":"Scrape Trending Sounds","type":"n8n-nodes-base.httpRequest","position":[2912,528],"typeVersion":4.1},{"parameters":{"assignments":{"assignments":[{"id":"s1","name":"trending_sounds","type":"string","value":"={{ $json.data?.sound_list?.slice(0,3).map(s => s.title + ' by ' + s.author).join('\\n') || 'No trending data available' }}"},{"id":"s2","name":"curated_kitchen_sounds","type":"string","value":"1. Upbeat Cooking Vibes - kitchen_bg_01.mp3\n2. Morning Kitchen Jazz - kitchen_bg_02.mp3\n3. Family Dinner Warmth - kitchen_bg_03.mp3\n4. Quick Recipe Energy - kitchen_bg_04.mp3\n5. Sunday Cook Chill - kitchen_bg_05.mp3"},{"id":"s3","name":"sound_recommendation","type":"string","value":"Curated kitchen tracks auto-applied at low volume. Trending sounds listed below for optional manual swap in TikTok app before posting."}]},"options":{}},"id":"9045886e-6aa1-44fc-851d-8d0b7a7e8b7d","name":"Format Sound Suggestions","type":"n8n-nodes-base.set","position":[3120,528],"typeVersion":3.4},{"parameters":{"mode":"combine","options":{}},"id":"03c9a02e-917a-4da1-84e3-545cd81ac79c","name":"Merge Results","type":"n8n-nodes-base.merge","position":[3632,304],"typeVersion":3},{"parameters":{"method":"POST","url":"http://192.168.2.200:5000/webapi/entry.cgi","sendBody":true,"bodyParameters":{"parameters":[{"name":"api","value":"SYNO.FileStation.Upload"},{"name":"version","value":"2"},{"name":"method","value":"upload"},{"name":"path","value":"/BlendedFamilyKitchen/Processed"},{"name":"_sid","value":"={{ $('NAS Login').item.json.data.sid }}"}]},"options":{}},"id":"cc5ed7db-528e-4bac-8e9a-5b86981983f5","name":"Upload to NAS Processed","type":"n8n-nodes-base.httpRequest","position":[3856,304],"typeVersion":4.1},{"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\n \"chat_id\": \"8088983654\",\n \"text\": \"🎬 *New Video Ready!*\\n\\n📁 {{ $('Extract Metadata').item.json.filename }}\\n\\n*Hook Options:*\\n{{ $('AI Generate Hooks').item.json.content?.[0]?.text || 'Processing...' }}\\n\\n*Sound Options:*\\n🎵 _Auto-applied:_ Kitchen background track\\n\\n📈 _Trending sounds this week:_\\n{{ $json.trending_sounds || 'No trending data' }}\\n\\n🎶 _Curated kitchen library:_\\n{{ $json.curated_kitchen_sounds }}\\n\\nTap below to decide:\",\n \"parse_mode\": \"Markdown\",\n \"reply_markup\": {\n \"inline_keyboard\": [\n [{\"text\": \"✅ Approve & Post\", \"callback_data\": \"approve_{{ $('Extract Metadata').item.json.filename }}\"}, {\"text\": \"📝 Edit First\", \"callback_data\": \"edit_{{ $('Extract Metadata').item.json.filename }}\"}],\n [{\"text\": \"🎵 Change Sound\", \"callback_data\": \"sound_{{ $('Extract Metadata').item.json.filename }}\"}, {\"text\": \"⏰ Schedule\", \"callback_data\": \"schedule_{{ $('Extract Metadata').item.json.filename }}\"}],\n [{\"text\": \"❌ Reject\", \"callback_data\": \"reject_{{ $('Extract Metadata').item.json.filename }}\"}]\n ]\n }\n}","options":{}},"id":"13b546d8-6383-491a-8bdb-87e47d3b7cc8","name":"Notify Cloe Telegram","type":"n8n-nodes-base.httpRequest","position":[4064,304],"typeVersion":4.1},{"parameters":{"url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/getUpdates","sendQuery":true,"queryParameters":{"parameters":[{"name":"offset","value":"-1"},{"name":"timeout","value":"30"}]},"options":{}},"id":"48abfe94-9a2f-45c9-9677-cc69b5b1c64c","name":"Wait for Cloe Response","type":"n8n-nodes-base.httpRequest","position":[4288,304],"typeVersion":4.1},{"parameters":{"rules":{"values":[{"conditions":{"conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.result?.[0]?.callback_query?.data?.split('_')[0] }}","rightValue":"approve"}]},"renameOutput":true,"outputKey":"Approve"},{"conditions":{"conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.result?.[0]?.callback_query?.data?.split('_')[0] }}","rightValue":"edit"}]},"renameOutput":true,"outputKey":"Edit"},{"conditions":{"conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.result?.[0]?.callback_query?.data?.split('_')[0] }}","rightValue":"sound"}]},"renameOutput":true,"outputKey":"Sound"},{"conditions":{"conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.result?.[0]?.callback_query?.data?.split('_')[0] }}","rightValue":"reject"}]},"renameOutput":true,"outputKey":"Reject"},{"conditions":{"conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.result?.[0]?.callback_query?.data?.split('_')[0] }}","rightValue":"schedule"}]},"renameOutput":true,"outputKey":"Schedule"}]},"options":{}},"id":"3065737b-5bc1-42d1-b1d1-ee2c9a1f5681","name":"Switch Cloe Decision","type":"n8n-nodes-base.switch","position":[4512,304],"typeVersion":3},{"parameters":{"method":"POST","url":"https://open.tiktokapis.com/v2/post/publish/video/init/","sendBody":true,"specifyBody":"json","jsonBody":"{\n \"post_info\": {\n \"title\": \"TODO: from AI hooks\",\n \"privacy_level\": \"PUBLIC_TO_EVERYONE\"\n },\n \"source_info\": {\n \"source\": \"FILE_UPLOAD\"\n }\n}","options":{}},"id":"24dd1511-a6ad-421d-9e6f-15c14904ca35","name":"Handle Approve","type":"n8n-nodes-base.httpRequest","position":[4768,64],"typeVersion":4.1},{"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\n \"chat_id\": \"8088983654\",\n \"text\": \"📝 Opening video for edit. File is in NAS /Processed folder. Reply here when done editing and I'll re-process.\"\n}","options":{}},"id":"f59a0455-cbf2-4316-a637-99b5c9da4362","name":"Handle Edit","type":"n8n-nodes-base.httpRequest","position":[4768,224],"typeVersion":4.1},{"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\n \"chat_id\": \"8088983654\",\n \"text\": \"🎵 Sound change requested. Apply your chosen trending sound in the TikTok app, then tap Approve to post.\\n\\nThis week's trending:\\n{{ $('Format Sound Suggestions').item.json.trending_sounds }}\"\n}","options":{}},"id":"b69cbfb8-160b-4c6f-a812-19c21a3f2f2d","name":"Handle Sound Change","type":"n8n-nodes-base.httpRequest","position":[4768,384],"typeVersion":4.1},{"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\n \"chat_id\": \"8088983654\",\n \"text\": \"❌ Video rejected. Moving to /Rejected folder on NAS. File: {{ $('Extract Metadata').item.json.filename }}\"\n}","options":{}},"id":"afe8eb7a-dd0f-4da7-8475-0ad655e0fa81","name":"Handle Reject","type":"n8n-nodes-base.httpRequest","position":[4768,544],"typeVersion":4.1},{"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\n \"chat_id\": \"8088983654\",\n \"text\": \"⏰ Schedule mode. Reply with your preferred post time (e.g. 'tomorrow 6pm', 'friday 12pm') and I'll queue it up.\"\n}","options":{}},"id":"0eb42673-7d47-4b9d-91b2-207aeaba1930","name":"Handle Schedule","type":"n8n-nodes-base.httpRequest","position":[4768,704],"typeVersion":4.1},{"parameters":{"method":"POST","url":"http://192.168.2.200:5000/webapi/entry.cgi","sendBody":true,"bodyParameters":{"parameters":[{"name":"api","value":"SYNO.FileStation.Rename"},{"name":"version","value":"2"},{"name":"method","value":"rename"},{"name":"path","value":"={{ $('Extract Metadata').item.json.filepath }}"},{"name":"name","value":"=/BlendedFamilyKitchen/Archive/{{ $('Extract Metadata').item.json.filename }}"},{"name":"_sid","value":"={{ $('NAS Login').item.json.data.sid }}"}]},"options":{}},"id":"566438cb-6b68-4618-8591-f561c90a402e","name":"Archive Original on NAS","type":"n8n-nodes-base.httpRequest","position":[4992,64],"typeVersion":4.1},{"parameters":{},"id":"438d4e2b-a5d0-4f6b-8eb2-3a37dd1e487e","name":"Error Trigger","type":"n8n-nodes-base.errorTrigger","position":[272,704],"typeVersion":1},{"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage","sendBody":true,"specifyBody":"json","jsonBody":"={\n \"chat_id\": \"8088983654\",\n \"text\": \"🚨 *Content Pipeline Error*\\n\\nWorkflow: {{ $workflow.name }}\\nNode: {{ $json.execution?.error?.node?.name || 'Unknown' }}\\nError: {{ $json.execution?.error?.message || 'Unknown error' }}\\nTimestamp: {{ new Date().toLocaleString('en-US', {timeZone: 'America/Denver'}) }}\",\n \"parse_mode\": \"Markdown\"\n}","options":{}},"id":"6c1aeca4-18bb-400e-a54a-8126454a4102","name":"Notify Jordan Error","type":"n8n-nodes-base.httpRequest","position":[480,704],"typeVersion":4.1},{"parameters":{"content":"## 📂 Stage 1: NAS Drop Zone Polling\n\nPolls Synology NAS every 30 min for new files in /BlendedFamilyKitchen/DropZone.\n\n**Folder Structure Needed on NAS:**\n- /BlendedFamilyKitchen/DropZone — Cloe drops raw videos here\n- /BlendedFamilyKitchen/Processed — enhanced videos land here\n- /BlendedFamilyKitchen/Archive — originals moved here after processing\n- /BlendedFamilyKitchen/Rejected — rejected videos moved here\n- /BlendedFamilyKitchen/Music — curated royalty-free background tracks\n\n**Synology FileStation API:**\n- Auth: POST /webapi/auth.cgi (SYNO.API.Auth v3)\n- List: POST /webapi/entry.cgi (SYNO.FileStation.List v2)\n- Download: POST /webapi/entry.cgi (SYNO.FileStation.Download v2)\n- Upload: POST /webapi/entry.cgi (SYNO.FileStation.Upload v2)\n\n**TODO:**\n1. Create NAS shared folders listed above\n2. Create a dedicated n8n API user on Synology\n3. Enable FileStation API in DSM\n4. Replace garvis-api and @ms88Str\n5. Test auth endpoint manually first\n6. Verify file extensions filter (.mp4, .mov, .avi)","height":580,"width":560},"id":"9debd5f5-f348-4e17-a3b9-6bbd5196b0fa","name":"Sticky - NAS Polling","type":"n8n-nodes-base.stickyNote","position":[-64,-640],"typeVersion":1},{"parameters":{"content":"## 🤖 Stage 2: AI Processing Pipeline\n\nDownloads raw video, processes through FFmpeg + Whisper + Claude.\n\n**Processing Steps (in order):**\n1. Download raw file from NAS to local /opt/n8n/bfk_raw/\n2. FFmpeg: Extract audio (WAV 16kHz mono for Whisper)\n3. Whisper: Transcribe → generate .srt subtitle file\n4. Claude API: Generate 3 hook options + caption + hashtags\n5. FFmpeg: Normalize video (9:16, color correct, sharpen)\n6. FFmpeg: Burn captions from .srt (white text, black outline)\n7. FFmpeg: Mix low-volume background music from curated library\n8. FFmpeg: Extract best thumbnail frame (scene detection)\n\n**Infrastructure Needed:**\n- Whisper Docker container (e.g. openai/whisper or faster-whisper)\n - Recommend: onerahmet/openai-whisper-asr-webservice\n - Deploy on VM with GPU or on CT 113 (CPU-only, slower)\n - API: POST /asr with audio_file + output=srt\n- FFmpeg installed on CT 113 (apt install ffmpeg)\n- Claude API key in n8n credentials\n- Temp dirs: /opt/n8n/bfk_raw, /opt/n8n/bfk_audio, /tmp/bfk_subs, /tmp/bfk_processed, /tmp/bfk_thumbs\n\n**TODO:**\n1. Deploy Whisper container\n2. Install FFmpeg on CT 113\n3. Create temp directories\n4. Add Claude API credential in n8n\n5. Test Whisper with a sample audio file","height":692,"width":704},"id":"eec19a28-a850-4e14-b186-bf94d163bbd4","name":"Sticky - AI Processing","type":"n8n-nodes-base.stickyNote","position":[1104,-784],"typeVersion":1},{"parameters":{"content":"## 🎵 Stage 3: Sound Suggestions\n\nParallel branch — scrapes trending TikTok sounds and merges with curated kitchen library.\n\n**Cloe's Creative Control:**\n- Background music auto-applied at LOW volume (12%) from curated library\n- Trending sounds presented as SUGGESTIONS only\n- Cloe can swap sounds in TikTok app before posting\n- Sound Change button in Telegram sends her the trending list\n\n**Curated Kitchen Music Library:**\n- Store royalty-free tracks in /BlendedFamilyKitchen/Music/ on NAS\n- Categorize: upbeat, chill, morning, family dinner, quick recipe\n- Sources: Epidemic Sound, Artlist, TikTok Commercial Music Library\n- Name format: kitchen_bg_01.mp3, kitchen_bg_02.mp3, etc.\n\n**Trending Sounds Scraping:**\n- TikTok Creative Center API (free, no auth needed)\n- Endpoint: ads.tiktok.com/creative_radar_api/v1/popular_trend/sound/list\n- Alternative: Apify TikTok Music Trend API\n- Refresh weekly, cache results\n\n**TODO:**\n1. Curate 10-20 royalty-free kitchen tracks\n2. Upload to NAS /BlendedFamilyKitchen/Music/\n3. Test TikTok Creative Center API endpoint\n4. Build fallback if API requires auth","height":620,"width":560},"id":"c5582fca-41ca-435e-af34-dc7385543527","name":"Sticky - Sound Suggestions","type":"n8n-nodes-base.stickyNote","position":[2848,704],"typeVersion":1},{"parameters":{"content":"## 📱 Stage 4: Cloe Approval via Telegram\n\nSends processed video preview to Cloe with inline action buttons.\n\n**Telegram Message Includes:**\n- Video filename and preview thumbnail\n- 3 AI-generated hook text options\n- AI-generated caption with hashtags\n- Auto-applied background music info\n- Trending sound suggestions (this week)\n- Curated kitchen sound options\n- Action buttons: Approve | Edit | Change Sound | Schedule | Reject\n\n**Button Actions:**\n- ✅ Approve: Posts directly to TikTok via API\n- 📝 Edit: Notifies Cloe file is in /Processed, waits for re-trigger\n- 🎵 Sound: Sends trending sound list, Cloe applies in TikTok app\n- ⏰ Schedule: Asks for preferred time, queues for later posting\n- ❌ Reject: Moves to /Rejected folder, notifies\n\n**Cloe Maintains Full Autonomy:**\n- Nothing posts without her explicit approval\n- She can edit videos before approving\n- She chooses final sound/music\n- She sets posting schedule\n- Pipeline does the grunt work, she makes creative decisions\n\n**TODO:**\n1. Create Telegram bot for BFK notifications\n2. Get Cloe's Telegram chat_id\n3. Set TELEGRAM_BOT_TOKEN in n8n environment\n4. Set up TikTok API app + OAuth for posting\n5. Test inline keyboard callbacks\n6. Add webhook endpoint for Telegram callback responses","height":744,"width":560},"id":"d1a9f460-f1e7-47de-9c21-9764d6665cac","name":"Sticky - Cloe Approval","type":"n8n-nodes-base.stickyNote","position":[3744,-560],"typeVersion":1},{"parameters":{"content":"## 🚨 Stage 5: Error Handling\n\nCatches any workflow errors and notifies Jordan via Telegram.\n\n**Error Notification Includes:**\n- Workflow name\n- Failed node name\n- Error message\n- MST timestamp\n\n**TODO:**\n1. Set Jordan's Telegram chat_id\n2. Consider adding retry logic for transient failures\n3. Add dead letter queue for persistent failures","height":380,"width":400},"id":"0d4ed488-aee6-4896-ac23-7b17eff44945","name":"Sticky - Error Handling","type":"n8n-nodes-base.stickyNote","position":[240,864],"typeVersion":1},{"parameters":{"fileName":"=/opt/n8n/bfk_raw/{{ $('Extract Metadata').item.json.filename }}","options":{}},"id":"7df7f187-a469-4317-a8c8-e63023ff7ed0","name":"Save Video to Disk","type":"n8n-nodes-base.writeBinaryFile","position":[1600,208],"typeVersion":1},{"parameters":{"httpMethod":"POST","path":"bfk-trigger","options":{}},"id":"webhook-trigger-001","name":"Webhook Trigger","type":"n8n-nodes-base.webhook","position":[-208,592],"webhookId":"bfk-trigger-001","typeVersion":2},{"parameters":{"filePath":"=/opt/n8n/bfk_audio/{{ $('Extract Metadata').item.json.filename.replace(/\\.[^.]+$/, '.wav') }}"},"id":"0a3985d5-7371-4096-a071-55bc4563ec1d","name":"Load Audio File","type":"n8n-nodes-base.readBinaryFile","position":[2032,208],"typeVersion":1},{"parameters":{"command":"=echo '{{ Buffer.from($json.data || $json.text || \"\").toString(\"base64\") }}' | base64 -d > /opt/n8n/bfk_subs/{{ $(\"Extract Metadata\").item.json.filename.replace(/\\.[^.]+$/, \".srt\") }} && echo \"saved\""},"id":"save-srt-to-disk-001","name":"Save SRT to Disk","type":"n8n-nodes-base.executeCommand","position":[2480,192],"typeVersion":1}],"connections":{"Schedule Trigger":{"main":[[{"node":"NAS Login","type":"main","index":0}]]},"NAS Login":{"main":[[{"node":"Poll NAS DropZone","type":"main","index":0}]]},"Poll NAS DropZone":{"main":[[{"node":"IF New Files?","type":"main","index":0}]]},"IF New Files?":{"main":[[{"node":"Split Into Batches","type":"main","index":0}]]},"Split Into Batches":{"main":[[{"node":"Extract Metadata","type":"main","index":0}]]},"Extract Metadata":{"main":[[{"node":"Download Raw Video","type":"main","index":0}]]},"Download Raw Video":{"main":[[{"node":"Save Video to Disk","type":"main","index":0}]]},"FFmpeg Extract Audio":{"main":[[{"node":"Load Audio File","type":"main","index":0}]]},"Whisper Transcribe":{"main":[[{"node":"Save SRT to Disk","type":"main","index":0}]]},"AI Generate Hooks":{"main":[[{"node":"FFmpeg Normalize Video","type":"main","index":0}]]},"FFmpeg Normalize Video":{"main":[[{"node":"FFmpeg Burn Captions","type":"main","index":0}]]},"FFmpeg Burn Captions":{"main":[[{"node":"FFmpeg Mix Background Music","type":"main","index":0}]]},"FFmpeg Mix Background Music":{"main":[[{"node":"FFmpeg Extract Thumbnail","type":"main","index":0}]]},"FFmpeg Extract Thumbnail":{"main":[[{"node":"Merge Results","type":"main","index":0}]]},"Scrape Trending Sounds":{"main":[[{"node":"Format Sound Suggestions","type":"main","index":0}]]},"Format Sound Suggestions":{"main":[[{"node":"Merge Results","type":"main","index":0}]]},"Merge Results":{"main":[[{"node":"Upload to NAS Processed","type":"main","index":0}]]},"Upload to NAS Processed":{"main":[[{"node":"Notify Cloe Telegram","type":"main","index":0}]]},"Notify Cloe Telegram":{"main":[[{"node":"Wait for Cloe Response","type":"main","index":0}]]},"Wait for Cloe Response":{"main":[[{"node":"Switch Cloe Decision","type":"main","index":0}]]},"Switch Cloe Decision":{"main":[[{"node":"Handle Approve","type":"main","index":0}],[{"node":"Handle Edit","type":"main","index":0}],[{"node":"Handle Sound Change","type":"main","index":0}],[{"node":"Handle Reject","type":"main","index":0}],[{"node":"Handle Schedule","type":"main","index":0}]]},"Handle Approve":{"main":[[{"node":"Archive Original on NAS","type":"main","index":0}]]},"Error Trigger":{"main":[[{"node":"Notify Jordan Error","type":"main","index":0}]]},"Save Video to Disk":{"main":[[{"node":"FFmpeg Extract Audio","type":"main","index":0}]]},"Webhook Trigger":{"main":[[{"node":"NAS Login","type":"main","index":0}]]},"Load Audio File":{"main":[[{"node":"Whisper Transcribe","type":"main","index":0}]]},"Save SRT to Disk":{"main":[[{"node":"AI Generate Hooks","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","callerPolicy":"workflowsFromSameOwner","availableInMCP":false,"binaryMode":"separate"},"staticData":{"node:Schedule Trigger":{"recurrenceRules":[]}},"meta":null,"pinData":{},"versionId":"f7bb89be-b54d-4364-835f-b1ff63e66f4a","activeVersionId":null,"versionCounter":94,"triggerCount":2,"shared":[{"updatedAt":"2026-03-18T21:45:55.353Z","createdAt":"2026-03-18T21:45:55.353Z","role":"workflow:owner","workflowId":"g7Qzow7QWBxBT2yk","projectId":"bPDtIdpuaSG2yKHe","project":{"updatedAt":"2026-03-18T04:50:54.011Z","createdAt":"2025-12-02T03:54:36.800Z","id":"bPDtIdpuaSG2yKHe","name":"Jordan Ramos <jramosdirect2@gmail.com>","type":"personal","icon":null,"description":null,"creatorId":"3e4c7deb-4c2f-43fe-b745-50eb81fb5275"}}],"tags":[],"activeVersion":null}