Initial commit with translated description
This commit is contained in:
213
scripts/onboarding.js
Normal file
213
scripts/onboarding.js
Normal file
@@ -0,0 +1,213 @@
|
||||
#!/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');
|
||||
}
|
||||
Reference in New Issue
Block a user