wordlift-cli
Version:
WordLift CLI - A customized CLI for WordLift SEO workflows with agent memory system and smart project directory detection
190 lines (156 loc) • 6.57 kB
JavaScript
/**
* WordLift CLI - A customized Gemini CLI for WordLift SEO workflows
* @license Apache-2.0
*/
const { spawn, execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
// Get the actual working directory where the user ran the command
const userWorkingDir = process.cwd();
const packageDir = path.dirname(__dirname);
// Set up environment variables - prefer local .gemini if it exists
const userGeminiDir = path.join(userWorkingDir, '.gemini');
const configDir = fs.existsSync(userGeminiDir) ? userGeminiDir : path.join(packageDir, '.gemini');
process.env.GEMINI_CONFIG_DIR = configDir;
// Ensure WORDLIFT.md is available in the user's working directory
function ensureWordLiftContext() {
const packageWordLiftFile = path.join(packageDir, 'WORDLIFT.md');
const userWordLiftFile = path.join(userWorkingDir, 'WORDLIFT.md');
const userGeminiDir = path.join(userWorkingDir, '.gemini');
const userSettingsFile = path.join(userGeminiDir, 'settings.json');
// Only copy if user doesn't have their own WORDLIFT.md
if (fs.existsSync(packageWordLiftFile) && !fs.existsSync(userWordLiftFile)) {
try {
fs.copyFileSync(packageWordLiftFile, userWordLiftFile);
console.log('\x1b[2m📄 WordLift context file added to your project directory\x1b[0m');
} catch (error) {
console.warn('⚠️ Could not copy WORDLIFT.md context file:', error.message);
}
}
// Ensure local .gemini directory with context settings
if (!fs.existsSync(userGeminiDir)) {
try {
fs.mkdirSync(userGeminiDir, { recursive: true });
} catch (error) {
console.warn('⚠️ Could not create .gemini directory:', error.message);
return;
}
}
// Create or update local settings.json to reference WORDLIFT.md
if (!fs.existsSync(userSettingsFile)) {
try {
// Copy the package settings and modify for local use
const packageSettingsFile = path.join(packageDir, '.gemini', 'settings.json');
let packageSettings = {};
if (fs.existsSync(packageSettingsFile)) {
packageSettings = JSON.parse(fs.readFileSync(packageSettingsFile, 'utf8'));
}
// Ensure contextFileName points to local WORDLIFT.md
const localSettings = {
...packageSettings,
"contextFileName": "WORDLIFT.md"
};
fs.writeFileSync(userSettingsFile, JSON.stringify(localSettings, null, 2));
console.log('\x1b[2m⚙️ WordLift configuration added to your project\x1b[0m');
} catch (error) {
console.warn('⚠️ Could not create settings.json:', error.message);
}
}
}
// Check if this is an interactive session (no arguments)
const isInteractiveMode = process.argv.length === 2;
// Show WordLift branding for interactive mode
if (isInteractiveMode) {
console.log('\x1b[2m🤖 Agent WordLift - Your AI SEO Assistant\x1b[0m');
console.log(`\x1b[2m📁 Working directory: ${userWorkingDir}\x1b[0m`);
// Ensure WordLift context is available
ensureWordLiftContext();
// Start Gemini CLI - ASCII art should already be replaced during installation
const geminiProcess = spawn('npx', ['@google/gemini-cli'], {
stdio: 'inherit',
cwd: userWorkingDir,
env: {
...process.env,
WORDLIFT_PROJECT_DIR: userWorkingDir,
WORDLIFT_PACKAGE_DIR: packageDir,
GEMINI_CONFIG_DIR: configDir
}
});
geminiProcess.on('error', (error) => {
console.error('❌ Error running WordLift CLI:', error.message);
process.exit(1);
});
geminiProcess.on('close', (code) => {
process.exit(code);
});
return; // Exit early for interactive mode
}
// For non-interactive commands (with arguments), show branding only for help
if (process.argv.includes('--help') || process.argv.includes('-h')) {
ensureWordLiftContext(); // Deploy context file for help commands too
}
// Also ensure context file for version commands
if (process.argv.includes('--version') || process.argv.includes('-v')) {
ensureWordLiftContext(); // Deploy context file for version commands too
}
// Ensure WordLift context is available for all modes
ensureWordLiftContext();
// Use the standard Gemini CLI from npm
const geminiCommand = 'npx';
const geminiArgs = ['@google/gemini-cli'];
// Prepare arguments - pass through all command line arguments
let userArgs = process.argv.slice(2);
// If using -p (prompt mode), ensure MCP servers are allowed and WordLift context is loaded
const hasPromptFlag = userArgs.includes('-p') || userArgs.includes('--prompt');
const hasPromptInteractiveFlag = userArgs.includes('-i') || userArgs.includes('--prompt-interactive');
if (hasPromptFlag || hasPromptInteractiveFlag) {
// Add allowed MCP server names if not already specified
const hasAllowedMcpFlag = userArgs.some(arg => arg.startsWith('--allowed-mcp-server-names'));
if (!hasAllowedMcpFlag) {
userArgs.push('--allowed-mcp-server-names', 'wordlift');
}
// Add YOLO mode for prompt mode to auto-accept actions
const hasYoloFlag = userArgs.includes('-y') || userArgs.includes('--yolo');
if (!hasYoloFlag) {
userArgs.push('--yolo');
}
// Ensure WordLift context is loaded for prompt mode
console.log('\x1b[2m🤖 Agent WordLift - Loading SEO specialist context...\x1b[0m');
ensureWordLiftContext();
}
const args = [...geminiArgs, ...userArgs];
// Configure stdio for truly non-interactive -p mode
let stdioCfg = 'inherit';
if (hasPromptFlag && !hasPromptInteractiveFlag) {
// For -p mode, pipe stdin to avoid interactive prompts getting stuck
stdioCfg = ['pipe', 'inherit', 'inherit'];
}
// Spawn the Gemini CLI with WordLift configuration
const geminiProcess = spawn(geminiCommand, args, {
stdio: stdioCfg,
cwd: userWorkingDir, // Run Gemini CLI in the user's working directory
env: {
...process.env,
WORDLIFT_PROJECT_DIR: userWorkingDir,
WORDLIFT_PACKAGE_DIR: packageDir,
GEMINI_CONFIG_DIR: configDir
}
});
// For -p mode, immediately close stdin to prevent hanging on interactive prompts
if (hasPromptFlag && !hasPromptInteractiveFlag && geminiProcess.stdin) {
geminiProcess.stdin.end();
}
geminiProcess.on('error', (error) => {
if (error.code === 'ENOENT') {
console.error('❌ Gemini CLI not found. Please install it first:');
console.error(' npm install -g @google/gemini-cli');
console.error(' or run: npm run install-deps');
} else {
console.error('❌ Error running WordLift CLI:', error.message);
}
process.exit(1);
});
geminiProcess.on('close', (code) => {
process.exit(code);
});