@meldscience/meld
Version:
pipeable one-shot prompt scripting toolkit
172 lines • 6.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RC_FILE = void 0;
exports.loadConfig = loadConfig;
exports.setupConfig = setupConfig;
exports.checkAndPromptConfig = checkAndPromptConfig;
const fs_1 = require("fs");
const os_1 = require("os");
const path_1 = require("path");
const errors_1 = require("./errors");
const child_process_1 = require("child_process");
exports.RC_FILE = '.meldrc';
function resolveSecretReference(value) {
if (!value.startsWith('op://')) {
return value;
}
try {
return (0, child_process_1.execSync)(`op read "${value}"`, { encoding: 'utf-8' }).trim();
}
catch (error) {
throw new errors_1.ToolError(`Failed to resolve 1Password secret reference: ${error.message}`, 'SECRET_REFERENCE_ERROR');
}
}
function loadConfig(options) {
// Start with empty config
const config = {};
// Try to load .meldrc from home directory (lowest priority)
try {
const rcPath = (0, path_1.join)((0, os_1.homedir)(), exports.RC_FILE);
if ((0, fs_1.existsSync)(rcPath)) {
const rcContent = (0, fs_1.readFileSync)(rcPath, 'utf-8');
const rcConfig = JSON.parse(rcContent);
// Resolve any secret references in the config
if (rcConfig.anthropicApiKey) {
rcConfig.anthropicApiKey = resolveSecretReference(rcConfig.anthropicApiKey);
}
if (rcConfig.openaiApiKey) {
rcConfig.openaiApiKey = resolveSecretReference(rcConfig.openaiApiKey);
}
Object.assign(config, rcConfig);
}
}
catch (error) {
if (error instanceof errors_1.ToolError) {
throw error; // Re-throw our own errors
}
// Ignore other errors reading config file - it's optional
}
// Load from environment variables (middle priority)
if (process.env.DEFAULT_MODEL) {
config.defaultModel = process.env.DEFAULT_MODEL;
}
if (process.env.ANTHROPIC_API_KEY) {
config.anthropicApiKey = process.env.ANTHROPIC_API_KEY;
}
if (process.env.OPENAI_API_KEY) {
config.openaiApiKey = process.env.OPENAI_API_KEY;
}
// Override with any passed options (highest priority)
if (options) {
// Only override values that are explicitly set in options
Object.keys(options).forEach(key => {
const value = options[key];
if (value !== undefined) {
config[key] = value;
}
});
}
return config;
}
function setupConfig(options) {
// Don't create config if no options provided
if (Object.keys(options).length === 0) {
return;
}
const rcPath = (0, path_1.join)((0, os_1.homedir)(), exports.RC_FILE);
let config = {};
// Read existing config if it exists
if ((0, fs_1.existsSync)(rcPath)) {
try {
const rcContent = (0, fs_1.readFileSync)(rcPath, 'utf-8');
config = JSON.parse(rcContent);
}
catch (error) {
// If file exists but is invalid, we'll overwrite it
console.warn('⚠️ Existing config file is invalid, creating new one');
}
}
// Set default model aliases if none exist
if (!config.modelAliases) {
config.modelAliases = {
'claude': 'claude-3-5-sonnet-latest',
'sonnet': 'claude-3-5-sonnet-latest',
'opus': 'claude-3-opus-latest',
'haiku': 'claude-3-5-haiku-latest',
'4o': 'chatgpt-4o-latest',
'o1': 'o1',
'o1-mini': 'o1-mini'
};
}
// Set default models if not set
if (!config.defaultOneshotModel) {
config.defaultOneshotModel = 'claude-3-5-sonnet-latest';
}
if (!config.defaultOneshotcatModel) {
config.defaultOneshotcatModel = 'claude-3-opus-latest';
}
// Update config with new values
if (options.anthropicApiKey)
config.anthropicApiKey = options.anthropicApiKey;
if (options.openaiApiKey)
config.openaiApiKey = options.openaiApiKey;
if (options.defaultModel)
config.defaultModel = options.defaultModel;
if (options.defaultOneshotModel)
config.defaultOneshotModel = options.defaultOneshotModel;
if (options.defaultOneshotcatModel)
config.defaultOneshotcatModel = options.defaultOneshotcatModel;
if (options.modelAliases) {
config.modelAliases = {
...config.modelAliases,
...options.modelAliases
};
}
// Write config file
(0, fs_1.writeFileSync)(rcPath, JSON.stringify(config, null, 2));
}
function checkAndPromptConfig(models) {
const config = loadConfig();
const rcPath = (0, path_1.join)((0, os_1.homedir)(), exports.RC_FILE);
const needsAnthropicKey = models.some(m => m.startsWith('claude')) && !config.anthropicApiKey;
const needsOpenAIKey = models.some(m => m.startsWith('gpt')) && !config.openaiApiKey;
if (!needsAnthropicKey && !needsOpenAIKey) {
return; // All required keys are present
}
console.log('\n⚠️ Missing API key configuration');
console.log('--------------------------------');
if (needsAnthropicKey) {
console.log('• Anthropic API key required for Claude models');
}
if (needsOpenAIKey) {
console.log('• OpenAI API key required for GPT models');
}
console.log('\nTo configure your API keys, use:');
if (needsAnthropicKey) {
console.log(' meld-config --anthropic-key <your-key>');
}
if (needsOpenAIKey) {
console.log(' meld-config --openai-key <your-key>');
}
console.log('\nOr use 1Password CLI secret references in .meldrc:');
console.log(' {');
if (needsAnthropicKey) {
console.log(' "anthropicApiKey": "op://vault-name/anthropic/api-key"');
}
if (needsOpenAIKey) {
console.log(' "openaiApiKey": "op://vault-name/openai/api-key"');
}
console.log(' }');
console.log('\nOr set environment variables:');
if (needsAnthropicKey) {
console.log(' export ANTHROPIC_API_KEY=<your-key>');
}
if (needsOpenAIKey) {
console.log(' export OPENAI_API_KEY=<your-key>');
}
console.log(`\nConfiguration will be saved to: ${rcPath}`);
console.log('\nFor more options, run: meld-config --help');
// Exit with error
throw new errors_1.ToolError('API keys required', 'MISSING_API_KEYS');
}
//# sourceMappingURL=config.js.map