@tirth.s/readme-wizard-cli
Version:
An interactive CLI tool to generate README files for your projects using Google's Gemini AI
839 lines (728 loc) ⢠29.4 kB
JavaScript
import dotenv from 'dotenv';
import { Command } from 'commander';
import inquirer from 'inquirer';
import chalk from 'chalk';
import { promises as fs } from 'fs';
import path from 'path';
import { glob } from 'glob';
import { GoogleGenAI } from '@google/genai';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import os from 'os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenv.config();
const program = new Command();
// API key management functions
async function getConfigPath() {
const homedir = process.env.HOME || process.env.USERPROFILE;
const configDir = path.join(homedir, '.readme-wizard');
await fs.mkdir(configDir, { recursive: true });
return path.join(configDir, 'config.json');
}
async function saveApiKey(apiKey) {
const configPath = await getConfigPath();
await fs.writeFile(configPath, JSON.stringify({ apiKey }, null, 2));
}
async function loadApiKey() {
try {
const configPath = await getConfigPath();
const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
return config.apiKey;
} catch {
return null;
}
}
async function clearApiKey() {
const configPath = await getConfigPath();
await fs.unlink(configPath).catch(() => {});
}
async function getApiKey(options) {
// Check command line flag
if (options.apiKey) {
await saveApiKey(options.apiKey);
return options.apiKey;
}
// Check environment variable
if (process.env.GOOGLE_API_KEY) {
return process.env.GOOGLE_API_KEY;
}
// Check saved configuration
const savedKey = await loadApiKey();
if (savedKey) {
return savedKey;
}
// Prompt user for API key with improved messaging
console.log(chalk.yellow('\nNo API key found. You need a Google Gemini API key to use this tool.'));
console.log(chalk.gray('You can get one for free at: https://aistudio.google.com/app/apikey\n'));
const { apiKey } = await inquirer.prompt([{
type: 'input',
name: 'apiKey',
message: 'Please enter your Google Gemini API key:',
validate: input => {
if (!input) return 'API key is required';
if (input.length < 10) return 'This doesn\'t look like a valid API key';
return true;
}
}]);
// Ask if they want to save the key
const { saveKey } = await inquirer.prompt([{
type: 'confirm',
name: 'saveKey',
message: 'Would you like to save this API key for future use?',
default: true
}]);
if (saveKey) {
await saveApiKey(apiKey);
console.log(chalk.green('\nAPI key saved successfully! You won\'t need to enter it again.\n'));
}
return apiKey;
}
// Configuration for the CLI prompt questions
const questions = {
projectName: {
type: 'input',
name: 'projectName',
message: 'What is your project name?',
default: path.basename(process.cwd())
},
description: {
type: 'input',
name: 'description',
message: 'Please provide a brief description of your project (press Enter to let AI generate it):',
}
};
// Helper function to generate common badges
async function generateBadges(projectInfo) {
const badges = [];
// Version badge from package.json
try {
const packageJson = JSON.parse(await fs.readFile(path.join(process.cwd(), 'package.json'), 'utf8'));
if (packageJson.version) {
badges.push(``);
}
} catch (error) {
// Skip if no package.json
}
// License badge
if (projectInfo.license) {
badges.push(``);
}
// Node.js version badge if it's a Node.js project
if (projectInfo.dependencies.length > 0 || projectInfo.devDependencies.length > 0) {
badges.push('');
}
// Test badge if tests exist
if (projectInfo.hasTests) {
badges.push('');
}
return badges.join(' ');
}
// Helper function to analyze project type and add specific badges
async function analyzeProjectType(projectInfo) {
const frameworkBadges = [];
// Frontend Frameworks
if (projectInfo.dependencies.includes('react')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('next')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('vue')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('angular')) {
frameworkBadges.push('');
}
// Backend Frameworks
if (projectInfo.dependencies.includes('express')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('@nestjs/core')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('fastify')) {
frameworkBadges.push('');
}
// Languages & Tools
if (projectInfo.dependencies.includes('typescript')) {
frameworkBadges.push('');
}
if (projectInfo.devDependencies.includes('jest')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('prisma')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('typeorm')) {
frameworkBadges.push('');
}
// Databases (inferred from dependencies)
if (projectInfo.dependencies.includes('mongoose')) {
frameworkBadges.push('');
}
if (projectInfo.dependencies.includes('pg')) {
frameworkBadges.push('');
}
return frameworkBadges.join(' ');
}
// Tech stack detection helpers
function detectFramework(projectInfo) {
const { dependencies = {}, devDependencies = {} } = projectInfo;
const allDeps = [...dependencies, ...devDependencies];
return {
frontend: allDeps.includes('react') ? 'React' :
allDeps.includes('vue') ? 'Vue.js' :
allDeps.includes('@angular/core') ? 'Angular' :
allDeps.includes('svelte') ? 'Svelte' : null,
backend: allDeps.includes('express') ? 'Express.js' :
allDeps.includes('@nestjs/core') ? 'NestJS' :
allDeps.includes('fastify') ? 'Fastify' :
allDeps.includes('koa') ? 'Koa' : null,
meta: allDeps.includes('next') ? 'Next.js' :
allDeps.includes('@nuxt/core') ? 'Nuxt.js' :
allDeps.includes('@sveltejs/kit') ? 'SvelteKit' : null
};
}
function detectTestingSetup(projectInfo) {
const { dependencies = {}, devDependencies = {} } = projectInfo;
const allDeps = [...dependencies, ...devDependencies];
return {
framework: allDeps.includes('jest') ? 'Jest' :
allDeps.includes('mocha') ? 'Mocha' :
allDeps.includes('vitest') ? 'Vitest' :
allDeps.includes('@playwright/test') ? 'Playwright' : null,
e2e: allDeps.includes('cypress') ? 'Cypress' :
allDeps.includes('@playwright/test') ? 'Playwright' :
allDeps.includes('puppeteer') ? 'Puppeteer' : null,
coverage: allDeps.includes('nyc') || allDeps.includes('@istanbul/core') ? true : false
};
}
function detectDatabase(projectInfo) {
const { dependencies = {} } = projectInfo;
return {
orm: dependencies.includes('prisma') ? 'Prisma' :
dependencies.includes('typeorm') ? 'TypeORM' :
dependencies.includes('sequelize') ? 'Sequelize' :
dependencies.includes('mongoose') ? 'Mongoose' : null,
database: dependencies.includes('pg') ? 'PostgreSQL' :
dependencies.includes('mysql2') ? 'MySQL' :
dependencies.includes('mongodb') ? 'MongoDB' :
dependencies.includes('sqlite3') ? 'SQLite' : null
};
}
function detectDeployment(projectInfo) {
const hasDocker = projectInfo.docker;
const hasCI = projectInfo.ci;
return {
containerization: hasDocker ? 'Docker' : null,
ci: hasCI,
pm: projectInfo.dependencies.includes('pm2') ? 'PM2' : null
};
}
function detectDocumentation(projectInfo) {
const { dependencies = {}, devDependencies = {} } = projectInfo;
const allDeps = [...dependencies, ...devDependencies];
return {
api: projectInfo.apiDocs ? 'OpenAPI/Swagger' : null,
docs: allDeps.includes('typedoc') ? 'TypeDoc' :
allDeps.includes('jsdoc') ? 'JSDoc' : null
};
}
function detectUILibraries(projectInfo) {
const { dependencies = {} } = projectInfo;
return {
component: dependencies.includes('@mui/material') ? 'Material UI' :
dependencies.includes('@chakra-ui/react') ? 'Chakra UI' :
dependencies.includes('tailwindcss') ? 'Tailwind CSS' :
dependencies.includes('@mantine/core') ? 'Mantine' : null,
styling: dependencies.includes('styled-components') ? 'styled-components' :
dependencies.includes('@emotion/react') ? 'Emotion' :
dependencies.includes('sass') ? 'Sass' : null
};
}
function detectStateManagement(projectInfo) {
const { dependencies = {} } = projectInfo;
return {
global: dependencies.includes('redux') ? 'Redux' :
dependencies.includes('recoil') ? 'Recoil' :
dependencies.includes('mobx') ? 'MobX' :
dependencies.includes('zustand') ? 'Zustand' : null,
server: dependencies.includes('react-query') ? 'React Query' :
dependencies.includes('@tanstack/react-query') ? 'TanStack Query' :
dependencies.includes('swr') ? 'SWR' : null
};
}
function detectAPILayer(projectInfo) {
const { dependencies = {} } = projectInfo;
return {
rest: dependencies.includes('axios') ? 'Axios' :
dependencies.includes('got') ? 'Got' : null,
graphql: dependencies.includes('graphql') ? 'GraphQL' :
dependencies.includes('@apollo/client') ? 'Apollo Client' : null
};
}
// Docker analysis helpers
async function analyzeDockerSetup(projectInfo) {
try {
const dockerFiles = await glob('**/Dockerfile*', { ignore: 'node_modules/**' });
const dockerComposeFiles = await glob('**/docker-compose*.{yml,yaml}', { ignore: 'node_modules/**' });
const dockerIgnoreExists = await fs.access('.dockerignore').then(() => true).catch(() => false);
if (dockerFiles.length === 0 && dockerComposeFiles.length === 0) {
return null;
}
const setup = {
hasDockerfile: dockerFiles.length > 0,
hasCompose: dockerComposeFiles.length > 0,
hasDockerIgnore: dockerIgnoreExists,
services: [],
volumes: [],
networks: [],
buildStages: []
};
// Analyze Dockerfile if exists
if (setup.hasDockerfile) {
const dockerfile = await fs.readFile(dockerFiles[0], 'utf8');
setup.buildStages = dockerfile.match(/^FROM .+ (as|AS) .+$/gm)?.map(line => {
const match = line.match(/^FROM .+ (?:as|AS) (.+)$/);
return match ? match[1] : null;
}).filter(Boolean) || [];
setup.baseImage = dockerfile.match(/^FROM ([^\s]+)/m)?.[1];
setup.exposedPorts = dockerfile.match(/^EXPOSE (\d+)/gm)?.map(line => line.match(/^EXPOSE (\d+)/)[1]) || [];
}
// Analyze docker-compose if exists
if (setup.hasCompose) {
const composeContent = await fs.readFile(dockerComposeFiles[0], 'utf8');
try {
const { parse } = await import('yaml');
const compose = parse(composeContent);
setup.services = Object.keys(compose.services || {});
setup.volumes = Object.keys(compose.volumes || {});
setup.networks = Object.keys(compose.networks || {});
} catch (e) {
// Skip silently if compose file can't be parsed
setup.hasCompose = false;
}
}
return setup;
} catch (error) {
return null;
}
}
// CI/CD analysis helpers
async function analyzeCICD(projectInfo) {
const ciConfig = {
provider: null,
workflows: [],
features: {
testing: false,
building: false,
deployment: false,
dockerBuild: false,
codeQuality: false,
security: false
}
};
try {
// Import yaml module
let yamlParser;
try {
yamlParser = await import('yaml');
} catch (e) {
console.warn(chalk.yellow('Warning: yaml module not available, skipping CI/CD analysis'));
return ciConfig;
}
// Check GitHub Actions
const githubWorkflows = await glob('.github/workflows/*.{yml,yaml}');
if (githubWorkflows.length > 0) {
ciConfig.provider = 'GitHub Actions';
for (const workflow of githubWorkflows) {
const content = await fs.readFile(workflow, 'utf8');
try {
const parsed = yamlParser.parse(content);
ciConfig.workflows.push({
name: path.basename(workflow, path.extname(workflow)),
triggers: Object.keys(parsed.on || {})
});
// Detect features
const jobs = Object.values(parsed.jobs || {});
const steps = jobs.flatMap(job => job.steps || []);
const stepNames = steps.map(step => step.name?.toLowerCase() || '');
ciConfig.features.testing = stepNames.some(name => name.includes('test'));
ciConfig.features.building = stepNames.some(name => name.includes('build'));
ciConfig.features.deployment = stepNames.some(name => name.includes('deploy'));
ciConfig.features.dockerBuild = stepNames.some(name => name.includes('docker'));
ciConfig.features.codeQuality = stepNames.some(name =>
name.includes('lint') || name.includes('sonar') || name.includes('quality')
);
ciConfig.features.security = stepNames.some(name =>
name.includes('security') || name.includes('scan') || name.includes('snyk')
);
} catch (e) {
// Skip this workflow if it can't be parsed
console.warn(chalk.yellow(`Warning: Could not parse workflow file: ${workflow}`));
}
}
}
// Check GitLab CI
const gitlabCI = await fs.access('.gitlab-ci.yml').then(() => true).catch(() => false);
if (gitlabCI) {
ciConfig.provider = 'GitLab CI';
const content = await fs.readFile('.gitlab-ci.yml', 'utf8');
try {
const parsed = yamlParser.parse(content);
ciConfig.workflows = Object.keys(parsed).filter(key => !key.startsWith('.'));
} catch (e) {
console.warn(chalk.yellow('Warning: Could not parse .gitlab-ci.yml'));
}
}
// Check Circle CI
const circleCI = await fs.access('.circleci/config.yml').then(() => true).catch(() => false);
if (circleCI) {
ciConfig.provider = 'Circle CI';
const content = await fs.readFile('.circleci/config.yml', 'utf8');
try {
const parsed = yamlParser.parse(content);
ciConfig.workflows = Object.keys(parsed.workflows || {});
} catch (e) {
console.warn(chalk.yellow('Warning: Could not parse CircleCI config'));
}
}
return ciConfig;
} catch (error) {
return ciConfig;
}
}
async function analyzeProject() {
const packageJsonPath = path.join(process.cwd(), 'package.json');
let projectInfo = {
dependencies: [],
devDependencies: [],
scripts: {},
hasTests: false,
mainFile: '',
projectType: 'Unknown',
contribution: false,
ci: false,
docker: false,
git: false,
coverage: false,
apiDocs: false,
testFramework: null,
bundler: null,
linter: null,
formatter: null,
commitlint: false,
roadmap: false,
changelog: false,
testFiles: [],
features: [],
folders: [],
isMonorepo: false,
security: false,
techStack: {},
performance: {},
};
try {
// Read package.json for deep analysis
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
projectInfo.dependencies = Object.keys(packageJson.dependencies || {});
projectInfo.devDependencies = Object.keys(packageJson.devDependencies || {});
projectInfo.scripts = packageJson.scripts || {};
projectInfo.mainFile = packageJson.main;
projectInfo.projectName = packageJson.name || path.basename(process.cwd());
projectInfo.version = packageJson.version;
projectInfo.license = packageJson.license;
projectInfo.author = packageJson.author;
projectInfo.description = packageJson.description;
projectInfo.repository = packageJson.repository;
projectInfo.bugs = packageJson.bugs;
projectInfo.homepage = packageJson.homepage;
projectInfo.engines = packageJson.engines;
// Detect monorepo
projectInfo.isMonorepo = await fs.access('lerna.json').then(() => true).catch(() => false) ||
await fs.access('pnpm-workspace.yaml').then(() => true).catch(() => false);
// Analyze project structure
const allFiles = await glob('**/*', {
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'],
nodir: true
});
// Get folder structure for better context
const folders = [...new Set(allFiles.map(file => path.dirname(file)))];
projectInfo.folders = folders.filter(f => f !== '.' && f !== '');
// Check for common files
projectInfo.contribution = allFiles.some(f => /contributing/i.test(f));
projectInfo.changelog = allFiles.some(f => /changelog/i.test(f));
projectInfo.docker = allFiles.some(f => /dockerfile|docker-compose/i.test(f));
projectInfo.security = allFiles.some(f => /security/i.test(f));
projectInfo.roadmap = allFiles.some(f => /roadmap/i.test(f));
// Check for CI/CD
projectInfo.ci = await fs.access('.github/workflows').then(() => true).catch(() => false) ||
await fs.access('.gitlab-ci.yml').then(() => true).catch(() => false) ||
await fs.access('.circleci').then(() => true).catch(() => false);
// Git analysis
try {
projectInfo.git = await fs.access('.git').then(() => true).catch(() => false);
if (projectInfo.git) {
const gitConfig = await fs.readFile('.git/config', 'utf8');
projectInfo.remoteUrl = gitConfig.match(/url = (.+)/)?.[1];
}
} catch {}
// Determine project type and tech stack
projectInfo.techStack = {
framework: detectFramework(projectInfo),
testing: detectTestingSetup(projectInfo),
database: detectDatabase(projectInfo),
deployment: detectDeployment(projectInfo),
documentation: detectDocumentation(projectInfo),
ui: detectUILibraries(projectInfo),
stateManagement: detectStateManagement(projectInfo),
api: detectAPILayer(projectInfo)
};
// Deep analysis of project setup
projectInfo.dockerSetup = await analyzeDockerSetup(projectInfo);
projectInfo.cicd = await analyzeCICD(projectInfo);
// Check for tests and coverage
const testFiles = await glob('**/*{test,spec}.*', { ignore: 'node_modules/**' });
projectInfo.hasTests = testFiles.length > 0;
projectInfo.testFiles = testFiles;
projectInfo.coverage = await fs.access('coverage').then(() => true).catch(() => false);
// Enhanced API documentation detection
projectInfo.apiDocs = allFiles.some(f =>
/swagger|openapi|api\.json|api\.yaml|postman/i.test(f)
);
// Analyze Docker setup
projectInfo.dockerSetup = await analyzeDockerSetup(projectInfo);
// Analyze CI/CD setup
projectInfo.ciSetup = await analyzeCICD(projectInfo);
return projectInfo;
} catch (error) {
console.warn(chalk.yellow('No package.json found, continuing with limited analysis'));
return projectInfo;
}
}
async function generateAIContent(projectInfo) {
try {
// Get API key through the unified API key management
const apiKey = await getApiKey(program.opts());
if (!apiKey) {
console.error(chalk.red('\nError: No API key provided.'));
console.log(chalk.gray('You can get a free API key at: https://aistudio.google.com/app/apikey'));
process.exit(1);
}
const ai = new GoogleGenAI({ apiKey });
// Generate badges
let commonBadges, frameworkBadges;
try {
commonBadges = await generateBadges(projectInfo);
frameworkBadges = await analyzeProjectType(projectInfo);
} catch (error) {
console.warn(chalk.yellow('\nWarning: Could not generate some badges, continuing with minimal badges.'));
commonBadges = '';
frameworkBadges = '';
}
// Add badges to project info
projectInfo.badges = `${commonBadges}\n${frameworkBadges}`;
const techStackInfo = JSON.stringify(projectInfo.techStack, null, 2);
const prompt = `Generate a beautiful, modern, and comprehensive README.md that follows best practices for a ${projectInfo.projectType} project named "${projectInfo.projectName}". Make it visually appealing with strategic use of badges, emojis, and formatting.
Project Analysis:
${JSON.stringify({
name: projectInfo.projectName,
version: projectInfo.version,
description: projectInfo.description,
author: projectInfo.author,
license: projectInfo.license,
isMonorepo: projectInfo.isMonorepo,
hasTests: projectInfo.hasTests,
hasCoverage: projectInfo.coverage,
hasCI: projectInfo.ci,
hasDocker: projectInfo.docker,
hasContribution: projectInfo.contribution,
hasChangelog: projectInfo.changelog,
hasAPIDoc: projectInfo.apiDocs,
techStack: projectInfo.techStack,
projectStructure: projectInfo.folders
}, null, 2)}
Structure Requirements (Standard-README Specification):
1. Title & Description
- Clear project name with logo/banner (if available)
- Concise description of the project's purpose
- Essential badges (build status, version, license)
2. Table of Contents
- Automatically generated, collapsible ToC
- Links to all major sections
3. Background/Security (if applicable)
- Context and problem the project solves
- Security considerations and warnings
4. Installation & Prerequisites
- System requirements and dependencies
- Step-by-step installation guide
- Environment setup with .env example
5. Usage/Examples
- Basic usage examples with code blocks
- Common use cases and examples
- API documentation if applicable
6. Project Architecture
- High-level architecture overview
- Project structure with explanations
- Key components and their interactions
7. Development
- Setting up the development environment
- Running tests and code coverage
- Contribution guidelines
- Code style and standards
8. Maintenance & Support
- Issue reporting guidelines
- Troubleshooting common issues
- Support channels and community
9. License & Credits
- License information
- Acknowledgments and contributors
- Third-party licenses
Style Guidelines:
- Use semantic emojis for section headers (š¦ Installation, š Usage, etc.)
- Include code blocks with proper language tags
- Use tables for structured information
- Include collapsible sections for lengthy content
- Add relevant screenshots/GIFs for UI components
- Use badges from shields.io for status indicators
- Follow proper Markdown heading hierarchy
- Include inline comments for clarity
- Add direct links to important files/folders
- Add quick links at the top for key resources (docs, live demo, issues)
- Include a "Quick Start" section for experienced developers
- Add status badges for CI/CD pipelines if applicable
- Include compatibility matrix for supported versions
- Add a "Features" section with key highlights
- Include performance metrics and benchmarks if available
Make the README professional, comprehensive, and maintainable while following the Standard-README specification and modern documentation best practices.`;
try {
const response = await ai.models.generateContent({
model: "gemini-2.0-flash",
contents: prompt
});
// Clean up the response text:
// 1. Remove any ```markdown blocks
// 2. Remove any extra backtick blocks at the start/end
// 3. Preserve actual code blocks within the content
let cleanedText = response.text
.replace(/^```markdown\n/, '') // Remove opening ```markdown
.replace(/\n```$/, '') // Remove closing ```
.replace(/^````markdown\n/, '') // Remove opening ````markdown
.replace(/\n````$/, ''); // Remove closing ````
if (!cleanedText || cleanedText.trim().length === 0) {
throw new Error('Generated content is empty');
}
return cleanedText;
} catch (error) {
console.error(chalk.red('\nError generating README content:'), error.message);
if (error.response?.status === 401) {
console.log(chalk.yellow('\nThis might be due to an invalid API key. Please try clearing your saved configuration:'));
console.log(chalk.gray('readme-wizard --clear-config'));
console.log(chalk.gray('Then run the tool again with a valid API key.'));
} else {
console.log(chalk.yellow('\nPlease try again. If the problem persists, you can:'));
console.log(chalk.gray('1. Check your internet connection'));
console.log(chalk.gray('2. Verify your API key at https://aistudio.google.com/app/apikey'));
console.log(chalk.gray('3. Try running with a new API key using: readme-wizard -k YOUR_NEW_API_KEY'));
}
process.exit(1);
}
} catch (error) {
console.error(chalk.red('\nFatal error:'), error.message);
console.log(chalk.yellow('\nPlease try again with a valid Google Gemini API key:'));
console.log(chalk.gray('readme-wizard -k YOUR_API_KEY'));
process.exit(1);
}
}
async function checkExistingReadme() {
try {
const readmePath = path.join(process.cwd(), 'README.md');
await fs.access(readmePath);
return true;
} catch {
return false;
}
}
async function main() {
try {
console.log(chalk.blue('Welcome to README Wizard! š'));
console.log(chalk.gray('Analyzing your project...\n'));
// Check for existing README
const hasReadme = await checkExistingReadme();
let shouldContinue = true;
if (hasReadme) {
const { overwrite } = await inquirer.prompt([{
type: 'confirm',
name: 'overwrite',
message: 'A README.md already exists. Would you like to overwrite it?',
default: false
}]);
if (!overwrite) {
const { newName } = await inquirer.prompt([{
type: 'input',
name: 'newName',
message: 'Enter a new name for the README file (without .md extension):',
default: 'README.new'
}]);
global.readmeFileName = `${newName}.md`;
} else {
global.readmeFileName = 'README.md';
}
} else {
global.readmeFileName = 'README.md';
}
// Analyze project
const projectInfo = await analyzeProject();
console.log(chalk.gray('Project analysis complete!\n'));
// Get user input for any missing information
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'What is your project name?',
default: path.basename(process.cwd())
},
{
type: 'input',
name: 'description',
message: 'Please provide a brief description of your project (press Enter to let AI generate it):',
}
]);
console.log(chalk.gray('\nGenerating README content using AI...\n'));
// Generate content using OpenAI
const readmeContent = await generateAIContent({
...projectInfo,
projectName: answers.projectName,
description: answers.description
});
// Write the file
await fs.writeFile(global.readmeFileName, readmeContent);
console.log(chalk.green(`\n⨠${global.readmeFileName} has been successfully created!`));
console.log(chalk.gray('\nFeel free to edit it further to match your needs.'));
} catch (error) {
console.error(chalk.red('An error occurred:'), error.message);
process.exit(1);
}
}
program
.name('readme-wizard')
.description('An interactive CLI tool to generate README files for your projects using AI')
.version('1.0.0')
.option('-k, --api-key <key>', 'Google AI (Gemini) API key')
.option('--clear-config', 'Clear saved configuration including API key')
.action(async (options) => {
if (options.clearConfig) {
try {
await clearApiKey();
console.log(chalk.green('Configuration cleared successfully!'));
process.exit(0);
} catch (error) {
console.error(chalk.red('Error clearing configuration:', error.message));
process.exit(1);
}
}
await main();
});
program.parse();