organ-ai-zer
Version:
AI-powered file organizer CLI tool
286 lines • 13.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigService = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const crypto = __importStar(require("crypto"));
const joi_1 = __importDefault(require("joi"));
const config_1 = require("../types/config");
class ConfigService {
constructor(customConfigPath) {
this.config = null;
this.configPath = customConfigPath || this.getDefaultConfigPath();
}
static getInstance(customConfigPath) {
if (!ConfigService.instance) {
ConfigService.instance = new ConfigService(customConfigPath);
}
return ConfigService.instance;
}
getDefaultConfigPath() {
const configDir = path.join(os.homedir(), '.organ-ai-zer');
return path.join(configDir, 'config.json');
}
async loadConfig() {
if (this.config) {
return this.config;
}
try {
if (await fs.pathExists(this.configPath)) {
const configData = await fs.readJson(this.configPath);
this.config = this.validateAndMergeConfig(configData);
}
else {
console.log('⚠️ No config file found. Use "organ-ai-zer init" to create one.');
this.config = config_1.DEFAULT_CONFIG;
}
}
catch (error) {
console.error(`❌ Error loading config: ${error}`);
throw new Error(`Failed to load configuration: ${error}`);
}
return this.config;
}
async saveConfig(config) {
try {
const validatedConfig = this.validateConfig(config);
await fs.ensureDir(path.dirname(this.configPath));
await fs.writeJson(this.configPath, validatedConfig, { spaces: 2 });
this.config = validatedConfig;
console.log(`✅ Config saved to: ${this.configPath}`);
}
catch (error) {
throw new Error(`Failed to save configuration: ${error}`);
}
}
async initializeConfig(force = false) {
const configExists = await fs.pathExists(this.configPath);
if (configExists && !force) {
console.log(`⚠️ Config file already exists at: ${this.configPath}`);
console.log('Use --force to overwrite or specify a different path.');
return;
}
try {
// Use a more lenient validation for initialization
const validatedConfig = this.validateInitConfig(config_1.DEFAULT_CONFIG);
await fs.ensureDir(path.dirname(this.configPath));
await fs.writeJson(this.configPath, validatedConfig, { spaces: 2 });
this.config = validatedConfig;
console.log(`✅ Config saved to: ${this.configPath}`);
}
catch (error) {
throw new Error(`Failed to save configuration: ${error}`);
}
console.log('🎉 Default configuration created!');
console.log('📝 Edit the config file to customize your preferences:');
console.log(` ${this.configPath}`);
console.log('');
console.log('🔑 Don\'t forget to add your AI API key in the config file!');
}
validateInitConfig(config) {
// More lenient validation for initialization - allows empty API key
const schema = joi_1.default.object({
ai: joi_1.default.object({
provider: joi_1.default.string().valid('openai', 'anthropic').required(),
apiKey: joi_1.default.string().allow('').required(),
model: joi_1.default.string().optional(),
maxTokens: joi_1.default.number().min(100).max(4000).optional(),
temperature: joi_1.default.number().min(0).max(2).optional(),
timeout: joi_1.default.number().min(1000).max(180000).optional()
}).required(),
organization: joi_1.default.object({
confidenceThreshold: joi_1.default.number().min(0).max(1).required(),
createBackups: joi_1.default.boolean().required(),
preserveOriginalNames: joi_1.default.boolean().required(),
maxDepth: joi_1.default.number().min(1).max(10).required(),
excludePatterns: joi_1.default.array().items(joi_1.default.string()).required(),
includePatterns: joi_1.default.array().items(joi_1.default.string()).required()
}).required(),
fileTypes: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({
enabled: joi_1.default.boolean().required(),
namingPatterns: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({
pattern: joi_1.default.string().required(),
description: joi_1.default.string().required(),
example: joi_1.default.string().required()
})).required(),
organizationRules: joi_1.default.object({
byDate: joi_1.default.boolean().optional(),
byType: joi_1.default.boolean().optional(),
byProject: joi_1.default.boolean().optional(),
customPath: joi_1.default.string().optional()
}).required()
})).required(),
customCategories: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({
extensions: joi_1.default.array().items(joi_1.default.string()).required(),
config: joi_1.default.object({
enabled: joi_1.default.boolean().required(),
namingPatterns: joi_1.default.object().required(),
organizationRules: joi_1.default.object().required()
}).required()
})).optional()
});
const { error, value } = schema.validate(config, { allowUnknown: false });
if (error) {
throw new Error(`Invalid configuration: ${error.details[0].message}`);
}
return value;
}
validateConfig(config) {
const schema = joi_1.default.object({
ai: joi_1.default.object({
provider: joi_1.default.string().valid('openai', 'anthropic').required(),
apiKey: joi_1.default.string().required(),
model: joi_1.default.string().optional(),
maxTokens: joi_1.default.number().min(100).max(4000).optional(),
temperature: joi_1.default.number().min(0).max(2).optional(),
timeout: joi_1.default.number().min(1000).max(180000).optional()
}).required(),
organization: joi_1.default.object({
confidenceThreshold: joi_1.default.number().min(0).max(1).required(),
createBackups: joi_1.default.boolean().required(),
preserveOriginalNames: joi_1.default.boolean().required(),
maxDepth: joi_1.default.number().min(1).max(10).required(),
excludePatterns: joi_1.default.array().items(joi_1.default.string()).required(),
includePatterns: joi_1.default.array().items(joi_1.default.string()).required()
}).required(),
fileTypes: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({
enabled: joi_1.default.boolean().required(),
namingPatterns: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({
pattern: joi_1.default.string().required(),
description: joi_1.default.string().required(),
example: joi_1.default.string().required()
})).required(),
organizationRules: joi_1.default.object({
byDate: joi_1.default.boolean().optional(),
byType: joi_1.default.boolean().optional(),
byProject: joi_1.default.boolean().optional(),
customPath: joi_1.default.string().optional()
}).required()
})).required(),
customCategories: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({
extensions: joi_1.default.array().items(joi_1.default.string()).required(),
config: joi_1.default.object({
enabled: joi_1.default.boolean().required(),
namingPatterns: joi_1.default.object().required(),
organizationRules: joi_1.default.object().required()
}).required()
})).optional()
});
const { error, value } = schema.validate(config, { allowUnknown: false });
if (error) {
throw new Error(`Invalid configuration: ${error.details[0].message}`);
}
return value;
}
validateAndMergeConfig(userConfig) {
// Deep merge user config with default config
const mergedConfig = this.deepMerge(config_1.DEFAULT_CONFIG, userConfig);
return this.validateConfig(mergedConfig);
}
deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = this.deepMerge(target[key] || {}, source[key]);
}
else {
result[key] = source[key];
}
}
return result;
}
getConfigPath() {
return this.configPath;
}
async configExists() {
return await fs.pathExists(this.configPath);
}
async saveInteractiveConfig(config) {
try {
const validatedConfig = this.validateInitConfig(config);
await fs.ensureDir(path.dirname(this.configPath));
await fs.writeJson(this.configPath, validatedConfig, { spaces: 2 });
this.config = validatedConfig;
console.log(`✅ Config saved to: ${this.configPath}`);
}
catch (error) {
throw new Error(`Failed to save interactive configuration: ${error}`);
}
}
isConfigured() {
return this.config?.ai?.apiKey !== '';
}
async validateApiKey() {
const config = await this.loadConfig();
if (!config.ai.apiKey) {
console.error('❌ No API key configured. Please add your API key to the config file.');
return false;
}
// Basic validation - actual API validation would require making a test call
if (config.ai.apiKey.length < 10) {
console.error('❌ API key appears to be invalid (too short).');
return false;
}
return true;
}
getConfigHash() {
if (!this.config) {
throw new Error('Config not loaded');
}
// Create hash of configuration that affects AI suggestions
const relevantConfig = {
ai: {
provider: this.config.ai.provider,
model: this.config.ai.model,
temperature: this.config.ai.temperature
},
organization: {
confidenceThreshold: this.config.organization.confidenceThreshold,
preserveOriginalNames: this.config.organization.preserveOriginalNames,
excludePatterns: this.config.organization.excludePatterns,
includePatterns: this.config.organization.includePatterns
},
fileTypes: this.config.fileTypes
};
return crypto.createHash('md5').update(JSON.stringify(relevantConfig)).digest('hex');
}
}
exports.ConfigService = ConfigService;
//# sourceMappingURL=config-service.js.map