ailock
Version:
AI-Proof File Guard - Protect sensitive files from accidental AI modifications
255 lines • 12.6 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import path from 'path';
import { render } from 'ink';
import React from 'react';
import { getRepoStatus } from '../core/git.js';
import { getQuotaUsage, getProjectQuotaUsage, initializeUserConfig } from '../core/directory-tracker.js';
import { getProjectDisplayPath } from '../core/project-utils.js';
import { getApiService } from '../services/CliApiService.js';
/**
* Check if we're in an interactive terminal environment
*/
function isInteractiveTerminal() {
return process.stdout.isTTY && !process.env.CI && !process.env.NON_INTERACTIVE;
}
/**
* Show simple status for scripts/CI environments
*/
async function showSimpleStatus(status) {
const totalProtected = status.protectedFiles.length;
const totalLocked = status.lockedFiles.length;
// Get quota information (use project quota)
try {
const projectQuotaUsage = await getProjectQuotaUsage();
console.log(`Protected: ${totalProtected}, Locked: ${totalLocked}, Projects: ${projectQuotaUsage.used}/${projectQuotaUsage.quota}, Git: ${status.isGitRepo ? 'Yes' : 'No'}, Hooks: ${status.hasAilockHook ? 'Yes' : 'No'}`);
}
catch {
console.log(`Protected: ${totalProtected}, Locked: ${totalLocked}, Git: ${status.isGitRepo ? 'Yes' : 'No'}, Hooks: ${status.hasAilockHook ? 'Yes' : 'No'}`);
}
if (totalProtected > 0) {
const currentDir = process.cwd();
for (const file of status.protectedFiles) {
const relativePath = path.relative(currentDir, file);
const isLocked = status.lockedFiles.includes(file);
console.log(`${isLocked ? 'LOCKED' : 'UNLOCKED'}: ${relativePath}`);
}
}
}
/**
* Show detailed interactive status
*/
async function showDetailedStatus(status) {
try {
const projectQuotaUsage = await getProjectQuotaUsage();
// 🎯 HERO SECTION: Big visual impact, immediate understanding
console.log(chalk.blue.bold('🛡️ AI-Lock Protection Dashboard\n'));
// Visual quota meter - make it impossible to miss
const progressBarWidth = 30;
const usedWidth = Math.min(progressBarWidth, Math.ceil((projectQuotaUsage.used / projectQuotaUsage.quota) * progressBarWidth));
const remainingWidth = progressBarWidth - usedWidth;
const usedBar = '█'.repeat(usedWidth);
const remainingBar = '░'.repeat(remainingWidth);
// Big status with visual meter
if (projectQuotaUsage.withinQuota) {
console.log(chalk.green.bold(`✅ ${projectQuotaUsage.used}/${projectQuotaUsage.quota} PROJECTS PROTECTED`));
console.log(chalk.green(` [${usedBar}${chalk.gray(remainingBar)}] ${Math.round((projectQuotaUsage.used / projectQuotaUsage.quota) * 100)}% quota used\n`));
}
else {
console.log(chalk.red.bold(`🚫 ${projectQuotaUsage.used}/${projectQuotaUsage.quota} PROJECTS (QUOTA EXCEEDED)`));
console.log(chalk.red(` [${usedBar}${chalk.gray(remainingBar)}] ${Math.round((projectQuotaUsage.used / projectQuotaUsage.quota) * 100)}% quota used\n`));
}
// 🎯 PROJECT SHOWCASE: Visual project cards
if (projectQuotaUsage.projects.length > 0) {
console.log(chalk.cyan.bold('📦 YOUR PROTECTED PROJECTS:'));
console.log(chalk.gray('─'.repeat(50)));
for (const project of projectQuotaUsage.projects) {
const displayPath = getProjectDisplayPath(project.rootPath);
const typeIcon = project.type === 'git' ? '📦' : '📁';
const typeColor = project.type === 'git' ? chalk.blue : chalk.yellow;
console.log(`${typeIcon} ${chalk.white.bold(project.name)} ${typeColor('(' + (project.type === 'git' ? 'Git Repository' : 'Directory') + ')')}`);
console.log(` ${chalk.gray('└─')} ${chalk.dim(displayPath)}`);
if (project.protectedPaths.length > 1) {
console.log(` ${chalk.gray('└─')} ${chalk.cyan(project.protectedPaths.length + ' protected files')}`);
}
console.log(''); // spacing
}
}
else {
console.log(chalk.yellow.bold('📭 NO PROJECTS PROTECTED YET'));
console.log(chalk.gray(' Your sensitive files are vulnerable to AI modifications!\n'));
}
}
catch (error) {
console.log(chalk.yellow('📊 Project Quota: Unable to load'));
console.log(chalk.gray(' 💡 Run: ailock auth --help'));
}
// 🎯 SYSTEM STATUS: Clean, scannable health check
console.log(chalk.cyan.bold('🔧 SYSTEM HEALTH:'));
console.log(chalk.gray('─'.repeat(30)));
// Git status with visual indicators
if (status.isGitRepo) {
console.log(`${chalk.green('●')} Git Repository ${chalk.gray('detected')}`);
if (status.hasAilockHook) {
console.log(`${chalk.green('●')} Pre-commit Hook ${chalk.gray('installed')}`);
}
else {
console.log(`${chalk.yellow('⚠')} Pre-commit Hook ${chalk.yellow('missing')} ${chalk.gray('← run: ailock hooks git')}`);
}
}
else {
console.log(`${chalk.gray('○')} Git Repository ${chalk.gray('not detected')}`);
}
// 🎯 FILE STATUS: Visual file protection overview
const totalProtected = status.protectedFiles.length;
const totalLocked = status.lockedFiles.length;
const unlockedCount = totalProtected - totalLocked;
console.log(''); // spacing
console.log(chalk.cyan.bold('📄 FILE PROTECTION STATUS:'));
console.log(chalk.gray('─'.repeat(35)));
if (totalProtected === 0) {
console.log(chalk.yellow.bold('⚠️ NO FILES CONFIGURED FOR PROTECTION'));
console.log(chalk.gray(' Add patterns to .ailock file or run: ailock lock <file>\n'));
}
else {
// Visual file status summary
console.log(`${chalk.green('🔒')} ${totalLocked} files ${chalk.green('PROTECTED')}`);
if (unlockedCount > 0) {
console.log(`${chalk.yellow('🔓')} ${unlockedCount} files ${chalk.yellow('VULNERABLE')} ${chalk.gray('← need locking!')}`);
}
// Show critical files that need attention
console.log(''); // spacing
const currentDir = process.cwd();
const maxFilesToShow = 5;
let filesShown = 0;
// Show unlocked files first (more urgent)
for (const file of status.protectedFiles) {
if (filesShown >= maxFilesToShow)
break;
const relativePath = path.relative(currentDir, file);
const isLocked = status.lockedFiles.includes(file);
if (!isLocked) {
console.log(` ${chalk.yellow('🔓')} ${chalk.white(relativePath)} ${chalk.yellow.bold('← NEEDS PROTECTION!')}`);
filesShown++;
}
}
// Then show some locked files
for (const file of status.protectedFiles) {
if (filesShown >= maxFilesToShow)
break;
const relativePath = path.relative(currentDir, file);
const isLocked = status.lockedFiles.includes(file);
if (isLocked) {
console.log(` ${chalk.green('🔒')} ${chalk.gray(relativePath)} ${chalk.green('protected')}`);
filesShown++;
}
}
if (totalProtected > maxFilesToShow) {
console.log(chalk.gray(` ... and ${totalProtected - maxFilesToShow} more files`));
}
console.log(''); // spacing
}
// 🎯 SMART ACTIONS: Context-aware, urgent first
console.log(chalk.cyan.bold('⚡ NEXT STEPS:'));
console.log(chalk.gray('─'.repeat(40)));
try {
const projectQuotaUsage = await getProjectQuotaUsage();
// Priority 1: Critical security gaps
if (unlockedCount > 0) {
if (projectQuotaUsage.withinQuota) {
console.log(`${chalk.yellow.bold('1.')} ${chalk.white('ailock lock')} ${chalk.gray('← Protect')} ${chalk.yellow.bold(unlockedCount + ' vulnerable files')}`);
}
else {
console.log(`${chalk.red.bold('1.')} ${chalk.red('QUOTA EXCEEDED!')} ${chalk.gray('→ run:')} ${chalk.cyan('ailock auth <code>')}`);
console.log(` ${chalk.gray(' Get codes at:')} ${chalk.blue.underline('https://ailock.dev')}`);
}
}
// Priority 2: System setup
if (!status.hasAilockHook && status.isGitRepo) {
const step = unlockedCount > 0 ? '2.' : '1.';
console.log(`${chalk.yellow.bold(step)} ${chalk.white('ailock hooks git')} ${chalk.gray('← Enable Git protection')}`);
}
// Priority 3: Getting started (if nothing protected)
if (totalProtected === 0) {
console.log(`${chalk.cyan.bold('1.')} ${chalk.white('ailock lock <file>')} ${chalk.gray('← Start protecting files')}`);
console.log(` ${chalk.gray(' or create .ailock file with patterns')}`);
}
// Priority 4: Expansion opportunities
if (projectQuotaUsage.withinQuota && projectQuotaUsage.available > 0 && totalProtected > 0) {
console.log(`${chalk.green('💡')} You can protect ${chalk.green.bold(projectQuotaUsage.available + ' more projects')} with current quota!`);
}
// Priority 5: Quota expansion
if (projectQuotaUsage.available <= 1 || !projectQuotaUsage.withinQuota) {
console.log(`${chalk.blue('🚀')} ${chalk.blue.underline('https://ailock.dev')} ${chalk.gray('← Get more protection slots')}`);
}
}
catch (error) {
// Fallback actions
if (unlockedCount > 0) {
console.log(`${chalk.yellow.bold('1.')} ${chalk.white('ailock lock')} ${chalk.gray('← Protect vulnerable files')}`);
}
if (!status.hasAilockHook && status.isGitRepo) {
console.log(`${chalk.yellow.bold('2.')} ${chalk.white('ailock hooks git')} ${chalk.gray('← Enable Git protection')}`);
}
if (totalProtected === 0) {
console.log(`${chalk.cyan.bold('1.')} ${chalk.white('ailock lock <file>')} ${chalk.gray('← Start protecting files')}`);
}
}
console.log(''); // final spacing
}
export const statusCommand = new Command('status')
.description('Show current ailock protection status')
.option('-i, --interactive', 'Show interactive status dashboard with real-time updates')
.option('-v, --verbose', 'Show detailed information')
.option('--simple', 'Force simple non-interactive output')
.option('--json', 'Output status as JSON')
.action(async (options) => {
try {
// Initialize user configuration if needed
await initializeUserConfig();
// Track status check analytics
const apiService = getApiService();
await apiService.trackUsage('status_check');
// Handle interactive dashboard
if (options.interactive) {
const { StatusDashboard } = await import('../ui/components/StatusDashboard.js');
const { waitUntilExit } = render(React.createElement(StatusDashboard, {
verbose: options.verbose,
onExit: () => process.exit(0)
}));
await waitUntilExit();
return;
}
const status = await getRepoStatus();
if (options.json) {
// Include quota information in JSON output
try {
const quotaUsage = await getQuotaUsage();
const statusWithQuota = {
...status,
quota: quotaUsage
};
console.log(JSON.stringify(statusWithQuota, null, 2));
}
catch {
console.log(JSON.stringify(status, null, 2));
}
return;
}
// Smart output detection: use detailed view in interactive terminals unless --simple is specified
const useDetailedOutput = (isInteractiveTerminal() && !options.simple) || options.verbose;
if (useDetailedOutput) {
// Rich interactive output
await showDetailedStatus(status);
}
else {
// Simple output for scripts/CI
await showSimpleStatus(status);
}
}
catch (error) {
console.error(chalk.red('Error getting status:'), error instanceof Error ? error.message : String(error));
process.exit(1);
}
});
//# sourceMappingURL=status.js.map