Files
ajarbot/n8n_workflows/content_pipeline_clean.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
23 KiB
JSON

{"name": "Content Pipeline - BlendedFamilyKitchen", "nodes": [{"parameters": {"rule": {"interval": [{"field": "minutes", "minutesInterval": 30}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000001", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [0, 300]}, {"parameters": {"method": "POST", "url": "http://192.168.2.210:5000/webapi/auth.cgi", "sendBody": true, "bodyParameters": {"parameters": [{"name": "api", "value": "SYNO.API.Auth"}, {"name": "version", "value": "6"}, {"name": "method", "value": "login"}, {"name": "account", "value": "={{ $env.SYNOLOGY_USER }}"}, {"name": "passwd", "value": "={{ $env.SYNOLOGY_PASS }}"}, {"name": "format", "value": "sid"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000002", "name": "NAS Login", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [220, 300]}, {"parameters": {"method": "POST", "url": "http://192.168.2.210:5000/webapi/entry.cgi", "sendBody": true, "bodyParameters": {"parameters": [{"name": "api", "value": "SYNO.FileStation.List"}, {"name": "version", "value": "2"}, {"name": "method", "value": "list"}, {"name": "folder_path", "value": "/BlendedFamilyKitchen/DropZone"}, {"name": "_sid", "value": "={{ $json.data.sid }}"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000003", "name": "Poll NAS DropZone", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [440, 300]}, {"parameters": {"conditions": {"options": {"caseSensitive": true, "leftValue": "", "typeValidation": "strict"}, "conditions": [{"id": "cond1", "leftValue": "={{ $json.data.files.length }}", "rightValue": "0", "operator": {"type": "number", "operation": "gt"}}], "combinator": "and"}}, "id": "a1b2c3d4-1111-4000-8000-000000000004", "name": "IF New Files?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [660, 300]}, {"parameters": {"fieldToSplitOut": "data.files"}, "id": "a1b2c3d4-1111-4000-8000-000000000005", "name": "Split Into Batches", "type": "n8n-nodes-base.splitOut", "typeVersion": 1, "position": [880, 200]}, {"parameters": {"mode": "manual", "duplicateItem": false, "assignments": {"assignments": [{"id": "a1", "name": "filename", "value": "={{ $json.name }}", "type": "string"}, {"id": "a2", "name": "filepath", "value": "={{ $json.path }}", "type": "string"}, {"id": "a3", "name": "filesize", "value": "={{ $json.additional?.size }}", "type": "number"}, {"id": "a4", "name": "timestamp", "value": "={{ $now.toISO() }}", "type": "string"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000006", "name": "Extract Metadata", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [1100, 200]}, {"parameters": {"method": "POST", "url": "http://192.168.2.210:5000/webapi/entry.cgi", "sendBody": true, "bodyParameters": {"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 }}"}]}, "options": {"response": {"response": {"responseFormat": "file"}}}}, "id": "a1b2c3d4-1111-4000-8000-000000000007", "name": "Download Raw Video", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1320, 200]}, {"parameters": {"command": "=ffmpeg -i /tmp/{{ $json.filename }} -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/{{ $json.filename }}_audio.wav && echo '{\"status\":\"ok\",\"audio_file\":\"/tmp/{{ $json.filename }}_audio.wav\"}'"}, "id": "a1b2c3d4-1111-4000-8000-000000000008", "name": "FFmpeg Extract Audio", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [1540, 200]}, {"parameters": {"method": "POST", "url": "http://localhost:9000/asr", "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": {"parameters": [{"name": "audio_file", "value": "={{ $json.audio_file }}"}, {"name": "task", "value": "transcribe"}, {"name": "language", "value": "en"}, {"name": "output", "value": "srt"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000009", "name": "Whisper Transcribe", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1760, 200]}, {"parameters": {"method": "POST", "url": "https://api.anthropic.com/v1/messages", "sendHeaders": true, "headerParameters": {"parameters": [{"name": "x-api-key", "value": "={{ $env.ANTHROPIC_API_KEY }}"}, {"name": "anthropic-version", "value": "2023-06-01"}, {"name": "content-type", "value": "application/json"}]}, "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"model\": \"claude-haiku-4-5-20251001\",\n \"max_tokens\": 500,\n \"messages\": [{\n \"role\": \"user\",\n \"content\": \"You are a TikTok content strategist for a blended family cooking channel. Given this video transcript, generate:\\n1. Three hook text options (bold, attention-grabbing, 5-8 words max)\\n2. A brief video description with relevant hashtags\\n3. Best posting time suggestion\\n\\nTranscript: {{ $json.data }}\\n\\nRespond in JSON format: {hooks: [str,str,str], description: str, hashtags: [str], best_time: str}\"\n }]\n}"}, "id": "a1b2c3d4-1111-4000-8000-000000000010", "name": "AI Generate Hooks", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1980, 200]}, {"parameters": {"command": "=ffmpeg -i /tmp/{{ $('Extract Metadata').item.json.filename }} \\\n -vf \"scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,eq=brightness=0.04:contrast=1.1:saturation=1.15,unsharp=5:5:0.3\" \\\n -af \"loudnorm=I=-16:TP=-1.5:LRA=11\" \\\n -c:v libx264 -preset medium -b:v 6M \\\n -c:a aac -b:a 128k \\\n /tmp/{{ $('Extract Metadata').item.json.filename }}_normalized.mp4 && echo '{\"status\":\"ok\"}'"}, "id": "a1b2c3d4-1111-4000-8000-000000000011", "name": "FFmpeg Normalize Video", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [2200, 200]}, {"parameters": {"command": "=ffmpeg -i /tmp/{{ $('Extract Metadata').item.json.filename }}_normalized.mp4 \\\n -vf \"subtitles=/tmp/{{ $('Extract Metadata').item.json.filename }}_captions.srt:force_style='FontName=Arial,FontSize=22,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2,Shadow=1,Alignment=2,MarginV=40'\" \\\n -c:v libx264 -preset medium -b:v 6M \\\n -c:a copy \\\n /tmp/{{ $('Extract Metadata').item.json.filename }}_captioned.mp4 && echo '{\"status\":\"ok\"}'"}, "id": "a1b2c3d4-1111-4000-8000-000000000012", "name": "FFmpeg Burn Captions", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [2420, 200]}, {"parameters": {"command": "=ffmpeg -i /tmp/{{ $('Extract Metadata').item.json.filename }}_captioned.mp4 \\\n -i /opt/music_library/kitchen_vibes/track_01.mp3 \\\n -filter_complex \"[1:a]volume=0.12[bg];[0:a][bg]amix=inputs=2:duration=first\" \\\n -c:v copy -c:a aac -b:a 128k \\\n /tmp/{{ $('Extract Metadata').item.json.filename }}_final.mp4 && echo '{\"status\":\"ok\"}'"}, "id": "a1b2c3d4-1111-4000-8000-000000000013", "name": "FFmpeg Mix Background Music", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [2640, 200]}, {"parameters": {"command": "=ffmpeg -i /tmp/{{ $('Extract Metadata').item.json.filename }}_final.mp4 \\\n -vf \"select='gt(scene,0.3)',scale=1080:1920\" \\\n -frames:v 1 \\\n /tmp/{{ $('Extract Metadata').item.json.filename }}_thumbnail.jpg && echo '{\"status\":\"ok\"}'"}, "id": "a1b2c3d4-1111-4000-8000-000000000014", "name": "FFmpeg Extract Thumbnail", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [2860, 200]}, {"parameters": {"method": "GET", "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"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000015", "name": "Scrape Trending Sounds", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [2860, 500]}, {"parameters": {"mode": "manual", "duplicateItem": false, "assignments": {"assignments": [{"id": "s1", "name": "trending_sounds", "value": "={{ $json.data?.sound_list?.slice(0,3).map(s => s.title + ' by ' + s.author).join('\\n') || 'No trending data available' }}", "type": "string"}, {"id": "s2", "name": "curated_picks", "value": "\ud83c\udf73 Kitchen Vibes:\n\u2022 Cozy Cooking Lo-Fi (royalty-free)\n\u2022 Sunday Morning Jazz (royalty-free)\n\u2022 Feel Good Acoustic (royalty-free)", "type": "string"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000016", "name": "Format Sound Suggestions", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [3080, 500]}, {"parameters": {"mode": "combine", "mergeByFields": {}, "combinationMode": "mergeByPosition", "options": {}}, "id": "a1b2c3d4-1111-4000-8000-000000000017", "name": "Merge Results", "type": "n8n-nodes-base.merge", "typeVersion": 3, "position": [3300, 300]}, {"parameters": {"method": "POST", "url": "http://192.168.2.210: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 }}"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000018", "name": "Upload to NAS Processed", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [3520, 300]}, {"parameters": {"method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"chat_id\": \"{{ $env.CLOE_CHAT_ID }}\",\n \"parse_mode\": \"HTML\",\n \"text\": \"\ud83c\udfac <b>New Video Ready for Review!</b>\\n\\n\ud83d\udcc1 <b>File:</b> {{ $('Extract Metadata').item.json.filename }}\\n\\n\u270d\ufe0f <b>Hook Options:</b>\\n1\ufe0f\u20e3 {{ $('AI Generate Hooks').item.json.content[0].text.hooks[0] }}\\n2\ufe0f\u20e3 {{ $('AI Generate Hooks').item.json.content[0].text.hooks[1] }}\\n3\ufe0f\u20e3 {{ $('AI Generate Hooks').item.json.content[0].text.hooks[2] }}\\n\\n\ud83d\udcdd <b>Description:</b>\\n{{ $('AI Generate Hooks').item.json.content[0].text.description }}\\n\\n\ud83c\udfb5 <b>Trending Sounds This Week:</b>\\n{{ $('Format Sound Suggestions').item.json.trending_sounds }}\\n\\n\ud83c\udf73 <b>Kitchen-Appropriate Picks:</b>\\n{{ $('Format Sound Suggestions').item.json.curated_picks }}\\n\\n\u23f0 <b>Best Time to Post:</b> {{ $('AI Generate Hooks').item.json.content[0].text.best_time }}\\n\\n\ud83d\udc47 <b>What would you like to do?</b>\",\n \"reply_markup\": {\n \"inline_keyboard\": [\n [{\"text\": \"\u2705 Approve & Post\", \"callback_data\": \"approve_{{ $('Extract Metadata').item.json.filename }}\"}, {\"text\": \"\ud83d\udcdd Edit First\", \"callback_data\": \"edit_{{ $('Extract Metadata').item.json.filename }}\"}],\n [{\"text\": \"\ud83c\udfb5 Change Sound\", \"callback_data\": \"sound_{{ $('Extract Metadata').item.json.filename }}\"}, {\"text\": \"\u274c Reject\", \"callback_data\": \"reject_{{ $('Extract Metadata').item.json.filename }}\"}],\n [{\"text\": \"\u23f0 Schedule for Later\", \"callback_data\": \"schedule_{{ $('Extract Metadata').item.json.filename }}\"}]\n ]\n }\n}"}, "id": "a1b2c3d4-1111-4000-8000-000000000019", "name": "Notify Cloe via Telegram", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [3740, 300]}, {"parameters": {"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/getUpdates", "sendQuery": true, "queryParameters": {"parameters": [{"name": "offset", "value": "-1"}, {"name": "timeout", "value": "30"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000020", "name": "Wait for Cloe Response", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [3960, 300]}, {"parameters": {"rules": {"rules": [{"value": "approve", "output": 0}, {"value": "edit", "output": 1}, {"value": "sound", "output": 2}, {"value": "reject", "output": 3}, {"value": "schedule", "output": 4}]}, "value": "={{ $json.result?.[0]?.callback_query?.data?.split('_')[0] }}"}, "id": "a1b2c3d4-1111-4000-8000-000000000021", "name": "Switch Cloe Decision", "type": "n8n-nodes-base.switch", "typeVersion": 3, "position": [4180, 300]}, {"parameters": {"method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"chat_id\": \"{{ $env.CLOE_CHAT_ID }}\", \"text\": \"\u2705 Video approved! Moving to publish queue...\"}"}, "id": "a1b2c3d4-1111-4000-8000-000000000022", "name": "Handle Approve", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [4400, 60]}, {"parameters": {"method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"chat_id\": \"{{ $env.CLOE_CHAT_ID }}\", \"text\": \"\ud83d\udcdd Opening video in edit mode. Make your changes and re-drop when ready!\"}"}, "id": "a1b2c3d4-1111-4000-8000-000000000023", "name": "Handle Edit", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [4400, 200]}, {"parameters": {"method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"chat_id\": \"{{ $env.CLOE_CHAT_ID }}\", \"text\": \"\ud83c\udfb5 Pick a sound to use:\\n\\n\ud83d\udd25 Trending:\\n{{ $('Format Sound Suggestions').item.json.trending_sounds }}\\n\\n\ud83c\udf73 Kitchen Picks:\\n{{ $('Format Sound Suggestions').item.json.curated_picks }}\\n\\nReply with the sound name or paste a TikTok sound link!\"}"}, "id": "a1b2c3d4-1111-4000-8000-000000000024", "name": "Handle Sound Change", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [4400, 340]}, {"parameters": {"method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"chat_id\": \"{{ $env.CLOE_CHAT_ID }}\", \"text\": \"\u274c Video rejected. Moving to archive.\"}"}, "id": "a1b2c3d4-1111-4000-8000-000000000025", "name": "Handle Reject", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [4400, 480]}, {"parameters": {"method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"chat_id\": \"{{ $env.CLOE_CHAT_ID }}\", \"text\": \"\u23f0 When should this go live? Reply with a date/time (e.g., 'Tomorrow 6pm' or 'Friday 12pm MST')\"}"}, "id": "a1b2c3d4-1111-4000-8000-000000000026", "name": "Handle Schedule", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [4400, 620]}, {"parameters": {"method": "POST", "url": "http://192.168.2.210: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": "=archived_{{ $('Extract Metadata').item.json.filename }}"}, {"name": "_sid", "value": "={{ $('NAS Login').item.json.data.sid }}"}]}}, "id": "a1b2c3d4-1111-4000-8000-000000000027", "name": "Archive Original on NAS", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [4620, 60]}, {"parameters": {}, "id": "a1b2c3d4-1111-4000-8000-000000000028", "name": "Error Trigger", "type": "n8n-nodes-base.errorTrigger", "typeVersion": 1, "position": [0, 700]}, {"parameters": {"method": "POST", "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"chat_id\": \"{{ $env.JORDAN_CHAT_ID }}\",\n \"parse_mode\": \"HTML\",\n \"text\": \"\ud83d\udea8 <b>Content Pipeline Error</b>\\n\\n<b>Node:</b> {{ $json.execution?.error?.node?.name || 'Unknown' }}\\n<b>Error:</b> {{ $json.execution?.error?.message || 'Unknown error' }}\\n<b>Execution:</b> {{ $json.execution?.id }}\\n\\n<a href='http://192.168.2.113:5678/workflow/{{ $json.workflow?.id }}/executions/{{ $json.execution?.id }}'>View in n8n</a>\"\n}"}, "id": "a1b2c3d4-1111-4000-8000-000000000029", "name": "Notify Jordan Error", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [220, 700]}, {"parameters": {"content": "## \ud83d\udcc1 Stage 1: NAS Drop Zone Polling\n\n**How it works:**\nPolls Synology NAS every 30 min for new files in /BlendedFamilyKitchen/DropZone\n\n**Cloe's workflow:**\n1. Records video on phone\n2. Saves/uploads to NAS DropZone folder\n3. Pipeline auto-detects and processes\n\n**TODO - Infrastructure:**\n- [ ] Create NAS folders: /BlendedFamilyKitchen/DropZone, /Processed, /Archive\n- [ ] Set up Synology FileStation API access\n- [ ] Add SYNOLOGY_USER and SYNOLOGY_PASS to n8n env vars\n- [ ] Test NAS API connectivity from CT 113\n- [ ] Set up Synology DS file app on Cloe's phone for easy upload", "width": 520, "height": 380}, "id": "a1b2c3d4-1111-4000-8000-000000000030", "name": "Sticky - NAS Polling", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-40, -140]}, {"parameters": {"content": "## \ud83e\udd16 Stage 2: AI Processing Pipeline\n\n**Processing chain:**\n1. FFmpeg extracts audio \u2192 Whisper transcribes \u2192 .srt captions\n2. Claude AI generates 3 hook options + description + hashtags\n3. FFmpeg normalizes video (9:16, color, audio levels)\n4. FFmpeg burns captions (white text, black outline, bottom-third)\n5. FFmpeg mixes low-volume background music from curated library\n6. FFmpeg extracts best thumbnail frame (scene detection)\n\n**TODO - Infrastructure:**\n- [ ] Deploy Whisper Docker container (onerahmet/openai-whisper-asr-webservice)\n- [ ] Install FFmpeg on CT 113 (apt install ffmpeg)\n- [ ] Add ANTHROPIC_API_KEY to n8n env vars\n- [ ] Create /opt/music_library/kitchen_vibes/ with 10-20 royalty-free tracks\n- [ ] Test full processing chain with a sample video", "width": 560, "height": 420}, "id": "a1b2c3d4-1111-4000-8000-000000000031", "name": "Sticky - AI Processing", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1280, -180]}, {"parameters": {"content": "## \ud83c\udfb5 Stage 3: Sound Suggestions\n\n**Trending Sounds:**\nScrapes TikTok Creative Center API weekly for top 10 trending sounds in US region. Presents top 3 to Cloe as suggestions.\n\n**Curated Kitchen Picks:**\nPre-selected royalty-free tracks appropriate for cooking content. Cloe can choose from these without worrying about copyright.\n\n**Cloe's autonomy preserved:**\n- She picks the final sound (or uses her own)\n- Suggestions are recommendations, not auto-applied\n- She can paste any TikTok sound link to use instead\n\n**TODO:**\n- [ ] Test TikTok Creative Radar API access\n- [ ] Build curated music library (royalty-free)\n- [ ] If API blocked, fallback to manual weekly curation", "width": 520, "height": 420}, "id": "a1b2c3d4-1111-4000-8000-000000000032", "name": "Sticky - Sound Suggestions", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [2820, 680]}, {"parameters": {"content": "## \ud83d\udcf1 Stage 4: Cloe Approval Flow\n\n**Telegram notification includes:**\n- Video filename and preview info\n- 3 AI-generated hook options (she picks one or writes her own)\n- Auto-generated description with hashtags\n- Trending sound suggestions + curated kitchen picks\n- Best posting time recommendation\n\n**Cloe's options (inline keyboard buttons):**\n\u2705 Approve & Post \u2014 publishes as-is with selected hook\n\ud83d\udcdd Edit First \u2014 sends video back for manual edits\n\ud83c\udfb5 Change Sound \u2014 shows full sound list to pick from\n\u274c Reject \u2014 archives the video\n\u23f0 Schedule \u2014 asks for preferred date/time\n\n**TODO:**\n- [ ] Set up Telegram bot for Cloe (or use Garvis bot)\n- [ ] Add TELEGRAM_BOT_TOKEN and CLOE_CHAT_ID to n8n env\n- [ ] Implement callback query handler for button responses\n- [ ] Test full approval flow end-to-end", "width": 560, "height": 480}, "id": "a1b2c3d4-1111-4000-8000-000000000033", "name": "Sticky - Cloe Approval", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [3680, -160]}, {"parameters": {"content": "## \ud83d\udea8 Error Handling\n\n**On any node failure:**\n- Error trigger catches the failure\n- Sends detailed error notification to Jordan via Telegram\n- Includes: failed node name, error message, execution ID\n- Direct link to the failed execution in n8n UI\n\n**TODO:**\n- [ ] Add JORDAN_CHAT_ID to n8n env vars\n- [ ] Test error handling with intentional failure\n- [ ] Consider retry logic for transient failures (NAS timeout, API rate limit)", "width": 480, "height": 320}, "id": "a1b2c3d4-1111-4000-8000-000000000034", "name": "Sticky - Error Handling", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-40, 560]}], "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": "FFmpeg Extract Audio", "type": "main", "index": 0}]]}, "FFmpeg Extract Audio": {"main": [[{"node": "Whisper Transcribe", "type": "main", "index": 0}]]}, "Whisper Transcribe": {"main": [[{"node": "AI Generate Hooks", "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": 1}]]}, "Merge Results": {"main": [[{"node": "Upload to NAS Processed", "type": "main", "index": 0}]]}, "Upload to NAS Processed": {"main": [[{"node": "Notify Cloe via Telegram", "type": "main", "index": 0}]]}, "Notify Cloe via 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}]]}}, "settings": {"executionOrder": "v1"}}