Files
ajarbot/n8n_workflows/content_pipeline.json

608 lines
27 KiB
JSON
Raw Normal View History

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
{
"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": "🍳 Kitchen Vibes:\n• Cozy Cooking Lo-Fi (royalty-free)\n• Sunday Morning Jazz (royalty-free)\n• 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\": \"🎬 <b>New Video Ready for Review!</b>\\n\\n📁 <b>File:</b> {{ $('Extract Metadata').item.json.filename }}\\n\\n✍ <b>Hook Options:</b>\\n1⃣ {{ $('AI Generate Hooks').item.json.content[0].text.hooks[0] }}\\n2⃣ {{ $('AI Generate Hooks').item.json.content[0].text.hooks[1] }}\\n3⃣ {{ $('AI Generate Hooks').item.json.content[0].text.hooks[2] }}\\n\\n📝 <b>Description:</b>\\n{{ $('AI Generate Hooks').item.json.content[0].text.description }}\\n\\n🎵 <b>Trending Sounds This Week:</b>\\n{{ $('Format Sound Suggestions').item.json.trending_sounds }}\\n\\n🍳 <b>Kitchen-Appropriate Picks:</b>\\n{{ $('Format Sound Suggestions').item.json.curated_picks }}\\n\\n⏰ <b>Best Time to Post:</b> {{ $('AI Generate Hooks').item.json.content[0].text.best_time }}\\n\\n👇 <b>What would you like to do?</b>\",\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\": \"❌ Reject\", \"callback_data\": \"reject_{{ $('Extract Metadata').item.json.filename }}\"}],\n [{\"text\": \"⏰ 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\": \"✅ 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\": \"📝 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\": \"🎵 Pick a sound to use:\\n\\n🔥 Trending:\\n{{ $('Format Sound Suggestions').item.json.trending_sounds }}\\n\\n🍳 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\": \"❌ 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\": \"⏰ 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\": \"🚨 <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": "## 📁 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": "## 🤖 Stage 2: AI Processing Pipeline\n\n**Processing chain:**\n1. FFmpeg extracts audio → Whisper transcribes → .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": "## 🎵 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": "## 📱 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✅ Approve & Post — publishes as-is with selected hook\n📝 Edit First — sends video back for manual edits\n🎵 Change Sound — shows full sound list to pick from\n❌ Reject — archives the video\n⏰ Schedule — 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": "## 🚨 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"
},
"staticData": null,
"tags": [],
"triggerCount": 1,
"active": false
}