#!/usr/bin/env node /** * TikTok App Marketing — Onboarding Config Validator * * The onboarding is CONVERSATIONAL — the agent talks to the user naturally, * not through this script. This script validates the resulting config is complete. * * Usage: * node onboarding.js --validate --config tiktok-marketing/config.json * node onboarding.js --init --dir tiktok-marketing/ * * --validate: Check config completeness, show what's missing * --init: Create the directory structure and empty config files */ const fs = require('fs'); const path = require('path'); const args = process.argv.slice(2); const configPath = args.includes('--config') ? args[args.indexOf('--config') + 1] : null; const validate = args.includes('--validate'); const init = args.includes('--init'); const dir = args.includes('--dir') ? args[args.indexOf('--dir') + 1] : 'tiktok-marketing'; if (init) { // Create directory structure const dirs = [dir, `${dir}/posts`, `${dir}/hooks`]; dirs.forEach(d => { if (!fs.existsSync(d)) { fs.mkdirSync(d, { recursive: true }); console.log(`šŸ“ Created ${d}/`); } }); // Empty config template const configTemplate = { app: { name: '', description: '', audience: '', problem: '', differentiator: '', appStoreUrl: '', category: '', isMobileApp: false }, imageGen: { provider: '', apiKey: '', model: '' }, postiz: { apiKey: '', integrationIds: { tiktok: '' } }, revenuecat: { enabled: false, v2SecretKey: '', projectId: '' }, posting: { privacyLevel: 'SELF_ONLY', schedule: ['07:30', '16:30', '21:00'], crossPost: [] }, competitors: `${dir}/competitor-research.json`, strategy: `${dir}/strategy.json` }; const cfgPath = `${dir}/config.json`; if (!fs.existsSync(cfgPath)) { fs.writeFileSync(cfgPath, JSON.stringify(configTemplate, null, 2)); console.log(`šŸ“ Created ${cfgPath}`); } // Empty competitor research template const compPath = `${dir}/competitor-research.json`; if (!fs.existsSync(compPath)) { fs.writeFileSync(compPath, JSON.stringify({ researchDate: '', competitors: [], nicheInsights: { trendingSounds: [], commonFormats: [], gapOpportunities: '', avoidPatterns: '' } }, null, 2)); console.log(`šŸ“ Created ${compPath}`); } // Empty strategy template const stratPath = `${dir}/strategy.json`; if (!fs.existsSync(stratPath)) { fs.writeFileSync(stratPath, JSON.stringify({ hooks: [], postingSchedule: ['07:30', '16:30', '21:00'], hookCategories: { testing: [], proven: [], dropped: [] }, crossPostPlatforms: [], notes: '' }, null, 2)); console.log(`šŸ“ Created ${stratPath}`); } // Empty hook performance tracker const hookPath = `${dir}/hook-performance.json`; if (!fs.existsSync(hookPath)) { fs.writeFileSync(hookPath, JSON.stringify({ hooks: [], rules: { doubleDown: [], testing: [], dropped: [] } }, null, 2)); console.log(`šŸ“ Created ${hookPath}`); } console.log('\nāœ… Directory structure ready. Start the conversational onboarding to fill in config.'); process.exit(0); } if (validate && configPath) { if (!fs.existsSync(configPath)) { console.error(`āŒ Config not found: ${configPath}`); process.exit(1); } const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); const required = []; const optional = []; // App profile (required) if (!config.app?.name) required.push('app.name — What is the app called?'); if (!config.app?.description) required.push('app.description — What does it do?'); if (!config.app?.audience) required.push('app.audience — Who is it for?'); if (!config.app?.problem) required.push('app.problem — What problem does it solve?'); if (!config.app?.category) required.push('app.category — What category?'); // Image generation (required) if (!config.imageGen?.provider) required.push('imageGen.provider — Which image tool?'); if (config.imageGen?.provider && config.imageGen.provider !== 'local' && !config.imageGen?.apiKey) { required.push('imageGen.apiKey — API key for image generation'); } // Postiz (required) if (!config.postiz?.apiKey) required.push('postiz.apiKey — Postiz API key'); if (!config.postiz?.integrationIds?.tiktok) required.push('postiz.integrationIds.tiktok — TikTok integration ID'); // Competitor research (important but not blocking) const compPath = config.competitors; if (compPath && fs.existsSync(compPath)) { const comp = JSON.parse(fs.readFileSync(compPath, 'utf-8')); if (!comp.competitors || comp.competitors.length === 0) { optional.push('Competitor research — no competitors analyzed yet (run browser research)'); } } else { optional.push('Competitor research — file not created yet'); } // Strategy const stratPath = config.strategy; if (stratPath && fs.existsSync(stratPath)) { const strat = JSON.parse(fs.readFileSync(stratPath, 'utf-8')); if (!strat.hooks || strat.hooks.length === 0) { optional.push('Content strategy — no hooks planned yet'); } } else { optional.push('Content strategy — file not created yet'); } // RevenueCat (optional) if (config.app?.isMobileApp && !config.revenuecat?.enabled) { optional.push('RevenueCat — mobile app detected but RC not connected (recommended for conversion tracking)'); } // App Store link if (!config.app?.appStoreUrl) optional.push('App Store / website URL — helpful for competitor research'); // Results if (required.length === 0) { console.log('āœ… Core config complete! Ready to start posting.\n'); } else { console.log('āŒ Missing required config:\n'); required.forEach(r => console.log(` ⬚ ${r}`)); console.log(''); } if (optional.length > 0) { console.log('šŸ’” Recommended (not blocking):\n'); optional.forEach(o => console.log(` ā—‹ ${o}`)); console.log(''); } // Summary console.log('šŸ“‹ Setup Summary:'); console.log(` App: ${config.app?.name || '(not set)'}`); console.log(` Category: ${config.app?.category || '(not set)'}`); console.log(` Image Gen: ${config.imageGen?.provider || '(not set)'}${config.imageGen?.model ? ` (${config.imageGen.model})` : ''}`); console.log(` TikTok: ${config.postiz?.integrationIds?.tiktok ? 'Connected' : 'Not connected'}`); const crossPost = Object.keys(config.postiz?.integrationIds || {}).filter(k => k !== 'tiktok' && config.postiz.integrationIds[k]); if (crossPost.length > 0) console.log(` Cross-posting: ${crossPost.join(', ')}`); if (config.revenuecat?.enabled) console.log(` RevenueCat: Connected`); console.log(` Privacy: ${config.posting?.privacyLevel || 'SELF_ONLY'}`); console.log(` Schedule: ${(config.posting?.schedule || []).join(', ')}`); process.exit(required.length > 0 ? 1 : 0); } else { console.log('Usage:'); console.log(' node onboarding.js --init --dir tiktok-marketing/ Create directory structure'); console.log(' node onboarding.js --validate --config config.json Validate config completeness'); }