@deriv-com/shiftai-cli
Version:
A comprehensive AI code detection and analysis CLI tool for tracking AI-generated code in projects
372 lines (317 loc) • 12.1 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const { execSync } = require('child_process');
const chalk = require('chalk');
const display = require('../utils/display');
const AIStorage = require('../core/ai-storage');
/**
* Status Command - Shows ShiftAI installation and configuration status
*/
async function statusCommand(options = {}) {
try {
display.clearAndShowHeader('ShiftAI Status', 'Current installation and configuration status');
const cwd = process.cwd();
const checks = await runStatusChecks(cwd, options.verbose);
// Display status sections
console.log(display.statusList('Installation Status', checks.installation));
console.log();
console.log(display.statusList('Configuration Status', checks.configuration));
console.log();
console.log(display.statusList('Git Hooks Status', checks.hooks));
// Show detailed information if verbose
if (options.verbose) {
if (checks.config) {
console.log(display.summary('Configuration Details', {
'Organization': checks.config.organization || 'Not set',
'Local Storage': 'Enabled',
'Strict Mode': checks.config.detection?.strictMode || false
}));
}
if (checks.environment) {
console.log();
console.log(display.statusList('Environment Variables', checks.environment));
}
if (checks.analytics) {
console.log(display.summary('Quick Analytics', checks.analytics));
}
}
// Show recommendations
const recommendations = generateRecommendations(checks);
if (recommendations.length > 0) {
console.log();
console.log(chalk.bold.blue('💡 Recommendations:'));
recommendations.forEach((rec, i) => {
console.log(chalk.gray(` ${i + 1}. ${rec}`));
});
}
} catch (error) {
console.log(display.error('Status check failed', error.message));
process.exit(1);
}
}
/**
* Run comprehensive status checks
*/
async function runStatusChecks(cwd, verbose = false) {
const checks = {
installation: [],
configuration: [],
hooks: [],
environment: [],
config: null,
analytics: null
};
// Check installation status
await checkInstallation(cwd, checks);
// Check configuration
await checkConfiguration(cwd, checks);
// Check git hooks
await checkGitHooks(cwd, checks);
// Check environment variables
if (verbose) {
await checkEnvironment(cwd, checks);
await checkAnalytics(cwd, checks);
}
return checks;
}
/**
* Check installation status
*/
async function checkInstallation(cwd, checks) {
// Check if in git repository
try {
execSync('git rev-parse --git-dir', { cwd, stdio: 'pipe' });
checks.installation.push({ label: 'Git Repository', status: 'success' });
} catch (error) {
checks.installation.push({ label: 'Git Repository', status: 'error', value: 'Not found' });
}
// Check package.json
const packageJsonPath = path.join(cwd, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
checks.installation.push({ label: 'package.json', status: 'success' });
try {
const packageJson = await fs.readJson(packageJsonPath);
// Check for husky
const hasHusky = (packageJson.devDependencies && packageJson.devDependencies.husky) ||
(packageJson.dependencies && packageJson.dependencies.husky);
if (hasHusky) {
checks.installation.push({ label: 'Husky', status: 'success', value: 'Installed' });
} else {
checks.installation.push({ label: 'Husky', status: 'warning', value: 'Not installed' });
}
// Check prepare script
if (packageJson.scripts && packageJson.scripts.prepare) {
checks.installation.push({ label: 'Prepare Script', status: 'success' });
} else {
checks.installation.push({ label: 'Prepare Script', status: 'warning', value: 'Missing' });
}
} catch (error) {
checks.installation.push({ label: 'package.json', status: 'error', value: 'Invalid JSON' });
}
} else {
checks.installation.push({ label: 'package.json', status: 'warning', value: 'Not found' });
}
// Check Node.js version
try {
const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim();
const major = parseInt(nodeVersion.slice(1).split('.')[0]);
if (major >= 14) {
checks.installation.push({ label: 'Node.js', status: 'success', value: nodeVersion });
} else {
checks.installation.push({ label: 'Node.js', status: 'warning', value: `${nodeVersion} (requires >=14)` });
}
} catch (error) {
checks.installation.push({ label: 'Node.js', status: 'error', value: 'Not found' });
}
}
/**
* Check configuration status
*/
async function checkConfiguration(cwd, checks) {
// Check ShiftAI config file
const configPath = AIStorage.getConfigPath();
if (await fs.pathExists(configPath)) {
checks.configuration.push({ label: 'ShiftAI Config', status: 'success', value: 'Found' });
try {
delete require.cache[require.resolve(configPath)];
const config = require(configPath);
checks.config = config;
// Validate config structure
if (config.organization) {
checks.configuration.push({ label: 'Organization', status: 'success', value: config.organization });
} else {
checks.configuration.push({ label: 'Organization', status: 'warning', value: 'Not set' });
}
} catch (error) {
checks.configuration.push({ label: 'Config Parsing', status: 'error', value: 'Invalid format' });
}
} else {
checks.configuration.push({ label: 'ShiftAI Config', status: 'error', value: 'Not found' });
}
// Check .env file
const envPath = path.join(cwd, '.env');
if (await fs.pathExists(envPath)) {
checks.configuration.push({ label: 'Environment File', status: 'success', value: 'Found' });
} else {
checks.configuration.push({ label: 'Environment File', status: 'warning', value: 'Not found' });
}
// Note: .env.example check removed - no longer created during installation
}
/**
* Check git hooks status
*/
async function checkGitHooks(cwd, checks) {
// Check .husky directory
const huskyDir = path.join(cwd, '.husky');
if (await fs.pathExists(huskyDir)) {
checks.hooks.push({ label: 'Husky Directory', status: 'success' });
// Check pre-commit hook
const preCommitPath = path.join(huskyDir, 'pre-commit');
if (await fs.pathExists(preCommitPath)) {
checks.hooks.push({ label: 'Pre-commit Hook', status: 'success', value: 'Installed' });
try {
const hookContent = await fs.readFile(preCommitPath, 'utf8');
if (hookContent.includes('shiftai-cli hook pre-commit')) {
checks.hooks.push({ label: 'ShiftAI Hook', status: 'success', value: 'Configured' });
} else {
checks.hooks.push({ label: 'ShiftAI Hook', status: 'warning', value: 'Not configured' });
}
// Check if executable
const stats = await fs.stat(preCommitPath);
if (stats.mode & parseInt('111', 8)) {
checks.hooks.push({ label: 'Hook Permissions', status: 'success' });
} else {
checks.hooks.push({ label: 'Hook Permissions', status: 'warning', value: 'Not executable' });
}
} catch (error) {
checks.hooks.push({ label: 'Hook Analysis', status: 'error', value: 'Cannot read hook' });
}
} else {
checks.hooks.push({ label: 'Pre-commit Hook', status: 'error', value: 'Not found' });
}
} else {
checks.hooks.push({ label: 'Husky Directory', status: 'error', value: 'Not found' });
}
// Check git hooks directory
const gitHooksDir = path.join(cwd, '.git', 'hooks');
if (await fs.pathExists(gitHooksDir)) {
checks.hooks.push({ label: 'Git Hooks Directory', status: 'success' });
} else {
checks.hooks.push({ label: 'Git Hooks Directory', status: 'error', value: 'Not found' });
}
}
/**
* Check environment variables
*/
async function checkEnvironment(cwd, checks) {
require('dotenv').config({ path: path.join(cwd, '.env') });
const envVars = [
'SHIFTAI_ORG_NAME',
'SHIFTAI_REPO_NAME',
'SHIFTAI_SKIP_SAVE',
'SHIFTAI_STRICT_MODE'
];
for (const envVar of envVars) {
const value = process.env[envVar];
if (value) {
checks.environment.push({
label: envVar,
status: 'success',
value: envVar.includes('TOKEN') ? '***hidden***' : value
});
} else {
checks.environment.push({ label: envVar, status: 'warning', value: 'Not set' });
}
}
}
/**
* Check analytics and usage
*/
async function checkAnalytics(cwd, checks) {
try {
// Simple file scan for AI markers - scan all text-like files
const AIDetector = require('../core/ai-detector');
const detector = new AIDetector();
const glob = require('glob');
// Scan common text files (excluding binary extensions)
const patterns = [
'**/*', // All files
'!**/*.{png,jpg,jpeg,gif,svg,ico,bmp,tiff,webp}', // Exclude images
'!**/*.{mp3,mp4,avi,mov,wmv,flv,webm,ogg,wav}', // Exclude media
'!**/*.{zip,rar,tar,gz,7z,pdf,doc,docx,xls,xlsx}', // Exclude archives/docs
'!**/*.{exe,dll,so,dylib,bin}', // Exclude binaries
'!**/*.json', // Exclude JSON files (data/config files)
'!**/node_modules/**',
'!**/.git/**',
'!**/dist/**',
'!**/build/**',
'!**/.next/**',
'!**/coverage/**',
'!**/yarn.lock', // Exclude yarn lock files
'!**/pnpm-lock.yaml' // Exclude pnpm lock files
];
const files = glob.sync(patterns, {
cwd,
dot: true // Include dotfiles
});
let totalFiles = 0;
let filesWithAI = 0;
let totalBlocks = 0;
for (const file of files.slice(0, 200)) { // Limit for performance
const filePath = path.join(cwd, file);
try {
const result = await detector.scanFile(filePath);
if (result.supported) {
totalFiles++;
if (result.hasAICode) {
filesWithAI++;
totalBlocks += result.blocks.length;
}
}
} catch (error) {
// Skip files with errors
}
}
checks.analytics = {
totalFiles: totalFiles,
filesWithAI: filesWithAI,
totalBlocks: totalBlocks,
percentage: totalFiles > 0 ? Math.round((filesWithAI / totalFiles) * 100) : 0
};
} catch (error) {
// Analytics is optional
checks.analytics = null;
}
}
/**
* Generate recommendations based on status
*/
function generateRecommendations(checks) {
const recommendations = [];
// Check for missing configuration
if (checks.configuration.some(c => c.label === 'ShiftAI Config' && c.status === 'error')) {
recommendations.push('Run "shai install" to set up ShiftAI configuration');
}
// Check for missing environment file
if (checks.configuration.some(c => c.label === 'Environment File' && c.status === 'warning')) {
recommendations.push('Create a .env file to customize organization and repository settings (optional)');
}
// Check for missing hooks
if (checks.hooks.some(c => c.label === 'Pre-commit Hook' && c.status === 'error')) {
recommendations.push('Run "shai install" to set up git hooks');
}
// Check for permission issues
if (checks.hooks.some(c => c.label === 'Hook Permissions' && c.status === 'warning')) {
recommendations.push('Fix hook permissions with: chmod +x .husky/pre-commit');
}
// Check for missing dependencies
if (checks.installation.some(c => c.label === 'Husky' && c.status === 'warning')) {
recommendations.push('Install husky with: npm install --save-dev husky');
}
// Check Node.js version
if (checks.installation.some(c => c.label === 'Node.js' && c.status === 'warning')) {
recommendations.push('Update Node.js to version 14 or higher');
}
return recommendations;
}
module.exports = statusCommand;