ailock
Version:
AI-Proof File Guard - Protect sensitive files from accidental AI modifications
183 lines • 6.15 kB
JavaScript
import { Command } from 'commander';
import { loadConfig, findProtectedFiles } from '../core/config.js';
import { getPlatformAdapter } from '../core/platform.js';
import fastGlob from 'fast-glob';
import path from 'path';
/**
* Hidden command that provides dynamic completions for shell scripts
*/
export const completionHelperCommand = new Command('completion-helper')
.description('Internal command for shell completion (hidden)')
.option('--type <type>', 'Type of completion needed')
.option('--partial <partial>', 'Partial input to complete')
.option('--command <command>', 'Current command context')
.option('--cwd <cwd>', 'Current working directory')
.option('--json', 'Output in JSON format')
.action(async (options) => {
try {
const request = {
type: options.type || 'commands',
partial: options.partial || '',
command: options.command,
cwd: options.cwd || process.cwd()
};
const suggestions = await getCompletions(request);
if (options.json) {
const response = { suggestions };
console.log(JSON.stringify(response));
}
else {
suggestions.forEach(s => console.log(s));
}
}
catch (error) {
// Silent fail for completion to avoid breaking shell
if (!options.json) {
process.exit(0);
}
console.log(JSON.stringify({ suggestions: [] }));
}
});
async function getCompletions(request) {
const { type, partial = '', command, cwd = process.cwd() } = request;
switch (type) {
case 'commands':
return getCommandCompletions(partial);
case 'files':
return getFileCompletions(partial, cwd);
case 'locked-files':
return getLockedFileCompletions(partial, cwd);
case 'unlocked-files':
return getUnlockedFileCompletions(partial, cwd);
case 'patterns':
return getPatternCompletions(partial, cwd);
case 'options':
return getOptionCompletions(command || '', partial);
default:
return [];
}
}
function getCommandCompletions(partial) {
const commands = [
'init',
'lock',
'unlock',
'protect',
'status',
'list',
'diagnose',
'generate',
'hooks',
'completion',
'help'
];
return commands.filter(cmd => cmd.startsWith(partial));
}
async function getFileCompletions(partial, cwd) {
try {
// Get config patterns if available
const config = await loadConfig(cwd);
const patterns = config?.patterns || ['**/*'];
// Find matching files
const files = await fastGlob(patterns, {
cwd,
ignore: ['node_modules/**', '.git/**', 'dist/**', 'coverage/**'],
onlyFiles: true,
dot: true
});
// Filter by partial and limit results
return files
.filter(file => file.startsWith(partial))
.slice(0, 50); // Limit to prevent slow completions
}
catch {
// Fallback to common patterns
return [];
}
}
async function getLockedFileCompletions(partial, cwd) {
try {
const config = await loadConfig(cwd);
const protectedFiles = await findProtectedFiles(config);
const adapter = getPlatformAdapter();
const lockedFiles = [];
for (const file of protectedFiles) {
try {
const relativePath = path.relative(cwd, file);
if (relativePath.startsWith(partial) && await adapter.isLocked(file)) {
lockedFiles.push(relativePath);
}
}
catch {
// Skip files that can't be checked
}
}
return lockedFiles.slice(0, 50);
}
catch {
return [];
}
}
async function getUnlockedFileCompletions(partial, cwd) {
try {
const config = await loadConfig(cwd);
const protectedFiles = await findProtectedFiles(config);
const adapter = getPlatformAdapter();
const unlockedFiles = [];
for (const file of protectedFiles) {
try {
const relativePath = path.relative(cwd, file);
if (relativePath.startsWith(partial) && !(await adapter.isLocked(file))) {
unlockedFiles.push(relativePath);
}
}
catch {
// Skip files that can't be checked
}
}
return unlockedFiles.slice(0, 50);
}
catch {
return [];
}
}
async function getPatternCompletions(partial, cwd) {
try {
const config = await loadConfig(cwd);
if (!config?.patterns)
return [];
return config.patterns
.filter(pattern => pattern.startsWith(partial))
.slice(0, 20);
}
catch {
// Suggest common patterns
return [
'*.env',
'*.key',
'*.pem',
'*.secret',
'.env*',
'config/*.json',
'secrets/**/*',
'**/*.key',
'**/*.pem'
].filter(pattern => pattern.startsWith(partial));
}
}
function getOptionCompletions(command, partial) {
const optionsByCommand = {
init: ['--force', '--interactive', '--config-only'],
lock: ['--verbose', '--dry-run', '--no-gitignore', '--no-hooks', '--hooks-only'],
unlock: ['--verbose', '--dry-run', '--all', '--no-gitignore'],
protect: ['--verbose', '--dry-run', '--no-gitignore'],
status: ['--verbose', '--simple', '--json', '--interactive'],
list: ['--long', '--locked-only', '--json'],
diagnose: ['--verbose'],
generate: ['--template', '--category', '--list', '--force', '--dry-run'],
hooks: ['setup', 'install', 'uninstall', 'status', 'git']
};
const options = optionsByCommand[command] || [];
return options.filter(opt => opt.startsWith(partial));
}
//# sourceMappingURL=completion-helper.js.map