build-in-public-bot
Version:
AI-powered CLI bot for automating build-in-public tweets with code screenshots
257 lines • 10.2 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigService = void 0;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
const yaml_1 = __importDefault(require("yaml"));
const errors_1 = require("../utils/errors");
const logger_1 = require("../utils/logger");
class ConfigService {
static instance;
configDir;
configPath;
config = null;
constructor(configDir) {
if (configDir) {
this.configDir = configDir;
}
else if (process.env.NODE_ENV === 'test' && process.env.TEST_HOME) {
this.configDir = path_1.default.join(process.env.TEST_HOME, '.bip');
}
else {
this.configDir = path_1.default.join(os_1.default.homedir(), '.bip');
}
this.configPath = path_1.default.join(this.configDir, 'config.yml');
}
static getInstance() {
if (!ConfigService.instance) {
ConfigService.instance = new ConfigService();
}
return ConfigService.instance;
}
async init() {
try {
await promises_1.default.mkdir(this.configDir, { recursive: true });
try {
await promises_1.default.access(this.configPath);
throw new errors_1.ConfigError('Configuration already exists. Use "bip style" to modify settings.');
}
catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
const defaultConfig = {
version: '1.0.0',
twitter: {
username: '',
sessionData: null
},
ai: {
provider: 'openai',
model: 'gpt-4-turbo-preview',
apiKey: process.env.OPENROUTER_API_KEY || ''
},
style: {
tone: 'casual-technical',
emojis: {
frequency: 'moderate',
preferred: ['🚀', '💡', '🔧', '✨', '🎯', '💻', '🛠️', '⚡']
},
hashtags: {
always: ['#buildinpublic'],
contextual: ['#webdev', '#typescript', '#nodejs', '#opensource']
},
examples: [
'Just shipped a new feature that makes X 10x faster 🚀 Used Y technique to optimize Z. The difference is wild! #buildinpublic',
'Debugging session turned into a refactoring marathon 🔧 Sometimes the best features come from fixing bugs. Added proper error handling and the UX is so much smoother now ✨',
'TIL: You can use X to solve Y problem. Been struggling with this for hours and the solution was so simple 💡 Love when things just click! #buildinpublic #webdev'
]
},
screenshots: {
theme: 'dracula',
backgroundColor: '#282a36',
windowTheme: 'mac',
padding: 32,
language: 'auto'
}
};
await this.save(defaultConfig);
logger_1.logger.success('Configuration initialized successfully!');
logger_1.logger.info(`Config file created at: ${this.configPath}`);
}
catch (error) {
if (error instanceof errors_1.ConfigError) {
throw error;
}
throw new errors_1.ConfigError('Failed to initialize configuration', error);
}
}
async load() {
if (this.config) {
return this.config;
}
try {
const configContent = await promises_1.default.readFile(this.configPath, 'utf-8');
this.config = yaml_1.default.parse(configContent);
return this.config;
}
catch (error) {
if (error.code === 'ENOENT') {
throw new errors_1.ConfigError('Configuration not found. Run "bip init" first.');
}
throw new errors_1.ConfigError('Failed to load configuration', error);
}
}
async save(config) {
try {
const yamlContent = yaml_1.default.stringify(config, { indent: 2 });
await promises_1.default.writeFile(this.configPath, yamlContent, 'utf-8');
this.config = config;
}
catch (error) {
throw new errors_1.ConfigError('Failed to save configuration', error);
}
}
async update(updates) {
const current = await this.load();
const updated = this.deepMerge(current, updates);
await this.save(updated);
}
async updateStyle(styleUpdates) {
const current = await this.load();
current.style = this.deepMerge(current.style, styleUpdates);
await this.save(current);
}
deepMerge(target, source) {
if (Array.isArray(source)) {
return source;
}
if (!source || typeof source !== 'object') {
return source;
}
const output = { ...target };
for (const key in source) {
if (source[key] instanceof Object && !Array.isArray(source[key]) && key in target && target[key] instanceof Object && !Array.isArray(target[key])) {
output[key] = this.deepMerge(target[key], source[key]);
}
else {
output[key] = source[key];
}
}
return output;
}
getConfigPath() {
return this.configPath;
}
getConfigDir() {
return this.configDir;
}
async validate() {
const errors = [];
try {
const config = await this.load();
if (!config.version) {
errors.push('Missing required field: version');
}
if (!config.twitter) {
errors.push('Missing required field: twitter');
}
else {
if (!config.twitter.username) {
errors.push('Twitter username is required');
}
}
if (!config.ai) {
errors.push('Missing required field: ai');
}
else {
if (!config.ai.provider) {
errors.push('AI provider is required');
}
if (!config.ai.model) {
errors.push('AI model is required');
}
if (!config.ai.apiKey) {
errors.push('AI API key is required');
}
}
if (!config.style) {
errors.push('Missing required field: style');
}
else {
const validTones = ['casual', 'professional', 'technical', 'humorous', 'casual-technical', 'enthusiastic', 'minimalist'];
if (!validTones.includes(config.style.tone)) {
errors.push(`Invalid tone: ${config.style.tone}. Must be one of: ${validTones.join(', ')}`);
}
const validFrequencies = ['none', 'low', 'moderate', 'high'];
if (!validFrequencies.includes(config.style.emojis.frequency)) {
errors.push(`Invalid emoji frequency: ${config.style.emojis.frequency}. Must be one of: ${validFrequencies.join(', ')}`);
}
}
if (!config.screenshots) {
errors.push('Missing required field: screenshots');
}
return {
valid: errors.length === 0,
errors
};
}
catch (error) {
return {
valid: false,
errors: ['Failed to load configuration for validation']
};
}
}
async reset() {
try {
const defaultConfig = {
version: '1.0.0',
twitter: {
username: '',
sessionData: null
},
ai: {
provider: 'openai',
model: 'gpt-4-turbo-preview',
apiKey: process.env.OPENROUTER_API_KEY || ''
},
style: {
tone: 'casual-technical',
emojis: {
frequency: 'moderate',
preferred: ['🚀', '💡', '🔧', '✨', '🎯', '💻', '🛠️', '⚡']
},
hashtags: {
always: ['#buildinpublic'],
contextual: ['#webdev', '#typescript', '#nodejs', '#opensource']
},
examples: [
'Just shipped a new feature that makes X 10x faster 🚀 Used Y technique to optimize Z. The difference is wild! #buildinpublic',
'Debugging session turned into a refactoring marathon 🔧 Sometimes the best features come from fixing bugs. Added proper error handling and the UX is so much smoother now ✨',
'TIL: You can use X to solve Y problem. Been struggling with this for hours and the solution was so simple 💡 Love when things just click! #buildinpublic #webdev'
]
},
screenshots: {
theme: 'dracula',
backgroundColor: '#282a36',
windowTheme: 'mac',
padding: 32,
language: 'auto'
}
};
await this.save(defaultConfig);
this.config = null;
}
catch (error) {
throw new errors_1.ConfigError('Failed to reset configuration', error);
}
}
}
exports.ConfigService = ConfigService;
//# sourceMappingURL=config.js.map
;