maiass 5.7.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,310 @@
1
+ // Configuration command handler for MAIASS CLI
2
+ // Implements: nma config [options] [key[=value]]
3
+
4
+ import { log, logger } from './logger.js';
5
+ import colors from './colors.js';
6
+ import { SYMBOLS } from './symbols.js';
7
+ import { MAIASS_VARIABLES } from './maiass-variables.js';
8
+ import {
9
+ getConfigPaths,
10
+ configExists,
11
+ readConfig,
12
+ writeConfig,
13
+ setConfigValue,
14
+ getConfigValue,
15
+ listConfig,
16
+ editConfig,
17
+ validateConfig
18
+ } from './config-manager.js';
19
+
20
+ /**
21
+ * Display configuration values in a formatted table
22
+ * @param {Object} config - Configuration data from listConfig()
23
+ * @param {Object} options - Display options
24
+ */
25
+ function displayConfig(config, options = {}) {
26
+ const { scope = 'all', showSensitive = false, showPaths = true } = options;
27
+
28
+ logger.header(SYMBOLS.GEAR, 'Configuration Status');
29
+
30
+ if (showPaths) {
31
+ logger.section('Configuration Files:', colors.BBlue);
32
+
33
+ const globalStatus = config.files.global.exists ?
34
+ colors.BGreen(`${SYMBOLS.CHECKMARK} EXISTS`) :
35
+ colors.BYellow(`${SYMBOLS.WARNING} Not found`);
36
+
37
+ const projectStatus = config.files.project.exists ?
38
+ colors.BGreen(`${SYMBOLS.CHECKMARK} EXISTS`) :
39
+ colors.BYellow(`${SYMBOLS.WARNING} Not found`);
40
+
41
+ console.log(` 1. Global Config ${globalStatus}`);
42
+ console.log(` ${colors.Gray(config.files.global.path)}`);
43
+ console.log(` 2. Project Config ${projectStatus}`);
44
+ console.log(` ${colors.Gray(config.files.project.path)}`);
45
+ console.log();
46
+ }
47
+
48
+ // Filter variables based on scope
49
+ let varsToShow = Object.entries(config.merged);
50
+
51
+ if (scope === 'global') {
52
+ varsToShow = varsToShow.filter(([key, info]) => info.source === 'global');
53
+ } else if (scope === 'project') {
54
+ varsToShow = varsToShow.filter(([key, info]) => info.source === 'project');
55
+ } else if (scope === 'set') {
56
+ varsToShow = varsToShow.filter(([key, info]) => info.source !== 'default' && info.source !== 'not_set');
57
+ }
58
+
59
+ if (varsToShow.length === 0) {
60
+ console.log(colors.BYellow(`${SYMBOLS.INFO} No configuration values found for scope: ${scope}`));
61
+ return;
62
+ }
63
+
64
+ logger.section('Configuration Values:', colors.BBlue);
65
+
66
+ // Group by category for better display
67
+ const categories = {
68
+ 'Core System': ['MAIASS_DEBUG', 'MAIASS_VERBOSITY', 'MAIASS_LOGGING', 'MAIASS_BRAND'],
69
+ 'AI Integration': ['MAIASS_AI_MODE', 'MAIASS_AI_TOKEN', 'MAIASS_AI_MODEL', 'MAIASS_AI_TEMPERATURE', 'MAIASS_AI_HOST', 'MAIASS_AI_MAX_CHARACTERS', 'MAIASS_AI_COMMIT_MESSAGE_STYLE'],
70
+ 'Git Branches': ['MAIASS_DEVELOPBRANCH', 'MAIASS_STAGINGBRANCH', 'MAIASS_MAINBRANCH'],
71
+ 'Repository Settings': ['MAIASS_REPO_TYPE', 'MAIASS_GITHUB_OWNER', 'MAIASS_GITHUB_REPO', 'MAIASS_BITBUCKET_WORKSPACE', 'MAIASS_BITBUCKET_REPO_SLUG'],
72
+ 'Pull Requests': ['MAIASS_STAGING_PULLREQUESTS', 'MAIASS_MAIN_PULLREQUESTS'],
73
+ 'Version Management': ['MAIASS_VERSION_PATH', 'MAIASS_VERSION_PRIMARY_FILE', 'MAIASS_VERSION_PRIMARY_TYPE', 'MAIASS_VERSION_PRIMARY_LINE_START', 'MAIASS_VERSION_SECONDARY_FILES'],
74
+ 'Changelog': ['MAIASS_CHANGELOG_PATH', 'MAIASS_CHANGELOG_NAME', 'MAIASS_CHANGELOG_INTERNAL_NAME']
75
+ };
76
+
77
+ Object.entries(categories).forEach(([categoryName, categoryVars]) => {
78
+ const categoryEntries = varsToShow.filter(([key]) => categoryVars.includes(key));
79
+
80
+ if (categoryEntries.length > 0) {
81
+ console.log(colors.BWhite(` ${categoryName}:`));
82
+ console.log();
83
+
84
+ categoryEntries.forEach(([key, info]) => {
85
+ const displayKey = key.replace('MAIASS_', '').toLowerCase();
86
+ const sourceColor = {
87
+ 'project local': colors.BCyan,
88
+ 'project': colors.BGreen,
89
+ 'global': colors.BBlue,
90
+ 'default': colors.Gray,
91
+ 'not_set': colors.BYellow
92
+ }[info.source] || colors.White;
93
+
94
+ const sourceText = {
95
+ 'project local': 'project local',
96
+ 'project': 'project',
97
+ 'global': 'global',
98
+ 'default': 'default',
99
+ 'not_set': 'not set'
100
+ }[info.source];
101
+
102
+ let displayValue = info.value || '(not set)';
103
+ if (info.sensitive && !showSensitive && info.value) {
104
+ displayValue = '***' + info.value.slice(-4);
105
+ }
106
+
107
+ console.log(` ${colors.BWhite(displayKey.padEnd(25))} = ${colors.White(displayValue)}`);
108
+ console.log(` ${' '.repeat(25)} ${sourceColor(`→ ${sourceText}`)} ${colors.Gray(`(${info.description})`)}`);
109
+ console.log();
110
+ });
111
+ }
112
+ });
113
+
114
+ // Show other variables not in categories
115
+ const otherEntries = varsToShow.filter(([key]) =>
116
+ !Object.values(categories).flat().includes(key)
117
+ );
118
+
119
+ if (otherEntries.length > 0) {
120
+ console.log(colors.BWhite(' Other Settings:'));
121
+ console.log();
122
+
123
+ otherEntries.forEach(([key, info]) => {
124
+ const displayKey = key.replace('MAIASS_', '').toLowerCase();
125
+ const sourceColor = {
126
+ 'project local': colors.BCyan,
127
+ 'project': colors.BGreen,
128
+ 'global': colors.BBlue,
129
+ 'default': colors.Gray,
130
+ 'not_set': colors.BYellow
131
+ }[info.source] || colors.White;
132
+
133
+ const sourceText = {
134
+ 'project local': 'project local',
135
+ 'project': 'project',
136
+ 'global': 'global',
137
+ 'default': 'default',
138
+ 'not_set': 'not set'
139
+ }[info.source];
140
+
141
+ let displayValue = info.value || '(not set)';
142
+ if (info.sensitive && !showSensitive && info.value) {
143
+ displayValue = '***' + info.value.slice(-4);
144
+ }
145
+
146
+ console.log(` ${colors.BWhite(displayKey.padEnd(25))} = ${colors.White(displayValue)}`);
147
+ console.log(` ${' '.repeat(25)} ${sourceColor(`→ ${sourceText}`)} ${colors.Gray(`(${info.description})`)}`);
148
+ console.log();
149
+ });
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Handle config command
155
+ * @param {Object} args - Command arguments from yargs
156
+ */
157
+ export async function handleConfigCommand(args) {
158
+ const {
159
+ global: isGlobal,
160
+ project: isProject,
161
+ edit,
162
+ list,
163
+ 'show-sensitive': showSensitive,
164
+ 'list-vars': listVars,
165
+ key
166
+ } = args;
167
+
168
+ const paths = getConfigPaths();
169
+
170
+ try {
171
+ // Handle --list-vars flag
172
+ if (listVars) {
173
+ log.info(SYMBOLS.INFO, 'Available Configuration Variables:');
174
+ log.space();
175
+
176
+ Object.entries(MAIASS_VARIABLES).forEach(([key, def]) => {
177
+ const displayKey = key.replace('MAIASS_', '').toLowerCase();
178
+ const sensitive = def.sensitive ? colors.BYellow(' (sensitive)') : '';
179
+ console.log(` ${colors.BWhite(displayKey.padEnd(30))} - ${colors.Gray(def.description)}${sensitive}`);
180
+ });
181
+
182
+ log.space();
183
+ log.blue(SYMBOLS.INFO, 'Usage examples:');
184
+ console.log(` nma config --global maiass_token=your_token_here`);
185
+ console.log(` nma config --project debug=true`);
186
+ console.log(` nma config verbosity`);
187
+ return;
188
+ }
189
+
190
+ // Handle --edit flag
191
+ if (edit) {
192
+ const configPath = isGlobal ? paths.global :
193
+ isProject ? paths.project :
194
+ paths.project; // Default to project
195
+
196
+ log.blue(SYMBOLS.INFO, `Editing ${isGlobal ? 'global' : 'project'} configuration...`);
197
+ editConfig(configPath);
198
+ return;
199
+ }
200
+
201
+ // Handle key=value assignment
202
+ if (key) {
203
+ const arg = key;
204
+
205
+ if (arg.includes('=')) {
206
+ // Set configuration value
207
+ const [rawKey, ...valueParts] = arg.split('=');
208
+ const key = `MAIASS_${rawKey.toUpperCase()}`;
209
+ const value = valueParts.join('=');
210
+
211
+ // Validate key exists
212
+ if (!MAIASS_VARIABLES[key]) {
213
+ console.error(colors.Red(`${SYMBOLS.CROSS} Unknown configuration variable: ${rawKey}`));
214
+ console.log(colors.BYellow(`${SYMBOLS.INFO} Use --list-vars to see available variables`));
215
+ return;
216
+ }
217
+
218
+ // Determine target config file
219
+ const configPath = isGlobal ? paths.global : paths.project;
220
+ const scope = isGlobal ? 'global' : 'project';
221
+
222
+ // Validate value
223
+ const tempConfig = { [key]: value };
224
+ const errors = validateConfig(tempConfig);
225
+
226
+ if (errors.length > 0) {
227
+ console.error(colors.Red(`${SYMBOLS.CROSS} Configuration validation failed:`));
228
+ errors.forEach(error => {
229
+ console.error(colors.Red(` ${error.key}: ${error.error}`));
230
+ if (error.current) {
231
+ console.error(colors.Gray(` Current value: ${error.current}`));
232
+ }
233
+ });
234
+ return;
235
+ }
236
+
237
+ // Set the value
238
+ setConfigValue(key, value, { global: isGlobal });
239
+
240
+ const varDef = MAIASS_VARIABLES[key];
241
+ const displayKey = rawKey.toLowerCase();
242
+ let displayValue = value;
243
+
244
+ if (varDef.sensitive) {
245
+ displayValue = '***' + value.slice(-4);
246
+ }
247
+
248
+ log.success(SYMBOLS.CHECKMARK, 'Configuration updated');
249
+ console.log(` ${colors.BWhite(displayKey)} = ${colors.White(displayValue)} ${colors.Gray(`(${scope})`)}`);
250
+ console.log(` ${colors.Gray(`File: ${configPath}`)}`);
251
+
252
+ } else {
253
+ // Get specific configuration value
254
+ const rawKey = arg;
255
+ const key = `MAIASS_${rawKey.toUpperCase()}`;
256
+
257
+ if (!MAIASS_VARIABLES[key]) {
258
+ console.error(colors.Red(`${SYMBOLS.CROSS} Unknown configuration variable: ${rawKey}`));
259
+ console.log(colors.BYellow(`${SYMBOLS.INFO} Use --list-vars to see available variables`));
260
+ return;
261
+ }
262
+
263
+ const valueInfo = getConfigValue(key);
264
+ const varDef = MAIASS_VARIABLES[key];
265
+
266
+ let displayValue = valueInfo.value || '(not set)';
267
+ if (varDef.sensitive && !showSensitive && valueInfo.value) {
268
+ displayValue = '***' + valueInfo.value.slice(-4);
269
+ }
270
+
271
+ const sourceColor = {
272
+ 'project local': colors.BCyan,
273
+ 'project': colors.BGreen,
274
+ 'global': colors.BBlue,
275
+ 'default': colors.Gray,
276
+ 'not_set': colors.BYellow
277
+ }[valueInfo.source] || colors.White;
278
+
279
+ log.info(SYMBOLS.INFO, 'Configuration Value:');
280
+ log.space();
281
+ console.log(` ${colors.BWhite(rawKey.toLowerCase())} = ${colors.White(displayValue)}`);
282
+ console.log(` ${sourceColor(`→ ${valueInfo.source}`)} ${colors.Gray(`(${varDef.description})`)}`);
283
+
284
+ if (valueInfo.path) {
285
+ console.log(` ${colors.Gray(`File: ${valueInfo.path}`)}`);
286
+ }
287
+ }
288
+
289
+ return;
290
+ }
291
+
292
+ // Default: show configuration
293
+ const config = listConfig({ showSensitive });
294
+
295
+ let scope = 'all';
296
+ if (isGlobal && !isProject) scope = 'global';
297
+ else if (isProject && !isGlobal) scope = 'project';
298
+ else if (list) scope = 'set';
299
+
300
+ displayConfig(config, {
301
+ scope,
302
+ showSensitive,
303
+ showPaths: scope === 'all'
304
+ });
305
+
306
+ } catch (error) {
307
+ console.error(colors.Red(`${SYMBOLS.CROSS} Configuration error: ${error.message}`));
308
+ process.exit(1);
309
+ }
310
+ }
@@ -0,0 +1,344 @@
1
+ // Configuration management for MAIASS
2
+ // Handles global and project-level configuration settings
3
+
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import os from 'os';
7
+ import { execSync } from 'child_process';
8
+ import { log, logger } from './logger.js';
9
+ import colors from './colors.js';
10
+ import { SYMBOLS } from './symbols.js';
11
+ import { MAIASS_VARIABLES } from './maiass-variables.js';
12
+ import { loadEnvironmentConfig } from './config.js';
13
+
14
+ /**
15
+ * Get configuration file paths
16
+ * @returns {Object} Configuration file paths
17
+ */
18
+ export function getConfigPaths() {
19
+ return {
20
+ global: path.join(os.homedir(), '.env.maiass'),
21
+ project: path.resolve(process.cwd(), '.env.maiass'),
22
+ projectDir: process.cwd(),
23
+ homeDir: os.homedir()
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Check if a configuration file exists
29
+ * @param {string} configPath - Path to config file
30
+ * @returns {boolean} True if file exists
31
+ */
32
+ export function configExists(configPath) {
33
+ return fs.existsSync(configPath);
34
+ }
35
+
36
+ /**
37
+ * Read configuration file contents
38
+ * @param {string} configPath - Path to config file
39
+ * @returns {Object} Parsed configuration object
40
+ */
41
+ export function readConfig(configPath) {
42
+ if (!configExists(configPath)) {
43
+ return {};
44
+ }
45
+
46
+ try {
47
+ const content = fs.readFileSync(configPath, 'utf8');
48
+ const config = {};
49
+
50
+ // Parse .env format (KEY=value)
51
+ content.split('\n').forEach(line => {
52
+ line = line.trim();
53
+ if (line && !line.startsWith('#')) {
54
+ const [key, ...valueParts] = line.split('=');
55
+ if (key && valueParts.length > 0) {
56
+ let value = valueParts.join('=').trim();
57
+
58
+ // Remove surrounding quotes if present
59
+ if ((value.startsWith('"') && value.endsWith('"')) ||
60
+ (value.startsWith("'") && value.endsWith("'"))) {
61
+ value = value.slice(1, -1);
62
+ }
63
+
64
+ config[key.trim()] = value;
65
+ }
66
+ }
67
+ });
68
+
69
+ return config;
70
+ } catch (error) {
71
+ console.error(colors.Red(`${SYMBOLS.CROSS} Error reading config file ${configPath}: ${error.message}`));
72
+ return {};
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Write configuration to file
78
+ * @param {string} configPath - Path to config file
79
+ * @param {Object} config - Configuration object to write
80
+ * @param {Object} options - Write options
81
+ */
82
+ export function writeConfig(configPath, config, options = {}) {
83
+ const { backup = true, createDir = true } = options;
84
+
85
+ try {
86
+ // Create directory if it doesn't exist
87
+ if (createDir) {
88
+ const dir = path.dirname(configPath);
89
+ if (!fs.existsSync(dir)) {
90
+ fs.mkdirSync(dir, { recursive: true });
91
+ }
92
+ }
93
+
94
+ // Create backup if file exists
95
+ if (backup && configExists(configPath)) {
96
+ const backupPath = `${configPath}.backup.${Date.now()}`;
97
+ fs.copyFileSync(configPath, backupPath);
98
+ }
99
+
100
+ // Generate .env format content
101
+ const lines = [];
102
+ lines.push('# MAIASS Configuration');
103
+ lines.push(`# Generated on ${new Date().toISOString()}`);
104
+ lines.push('');
105
+
106
+ // Group variables by category for better organization
107
+ const categories = {
108
+ 'Core': ['MAIASS_DEBUG', 'MAIASS_VERBOSITY', 'MAIASS_LOGGING', 'MAIASS_BRAND'],
109
+ 'AI': ['MAIASS_AI_MODE', 'MAIASS_AI_TOKEN', 'MAIASS_AI_MODEL', 'MAIASS_AI_TEMPERATURE', 'MAIASS_AI_ENDPOINT', 'MAIASS_AI_MAX_CHARACTERS', 'MAIASS_AI_COMMIT_MESSAGE_STYLE'],
110
+ 'Branches': ['MAIASS_DEVELOPBRANCH', 'MAIASS_STAGINGBRANCH', 'MAIASS_MAINBRANCH'],
111
+ 'Repository': ['MAIASS_REPO_TYPE', 'MAIASS_GITHUB_OWNER', 'MAIASS_GITHUB_REPO', 'MAIASS_BITBUCKET_WORKSPACE', 'MAIASS_BITBUCKET_REPO_SLUG'],
112
+ 'Pull Requests': ['MAIASS_STAGING_PULLREQUESTS', 'MAIASS_MAIN_PULLREQUESTS'],
113
+ 'Versioning': ['MAIASS_VERSION_PATH', 'MAIASS_VERSION_PRIMARY_FILE', 'MAIASS_VERSION_PRIMARY_TYPE', 'MAIASS_VERSION_PRIMARY_LINE_START', 'MAIASS_VERSION_SECONDARY_FILES'],
114
+ 'Changelog': ['MAIASS_CHANGELOG_PATH', 'MAIASS_CHANGELOG_NAME', 'MAIASS_CHANGELOG_INTERNAL_NAME'],
115
+ 'Other': []
116
+ };
117
+
118
+ // Write variables by category
119
+ Object.entries(categories).forEach(([categoryName, categoryVars]) => {
120
+ const varsInCategory = Object.keys(config).filter(key =>
121
+ categoryVars.includes(key) || (categoryName === 'Other' && !Object.values(categories).flat().includes(key))
122
+ );
123
+
124
+ if (varsInCategory.length > 0) {
125
+ lines.push(`# ${categoryName} Settings`);
126
+ varsInCategory.forEach(key => {
127
+ const value = config[key];
128
+ const varDef = MAIASS_VARIABLES[key];
129
+
130
+ if (varDef && varDef.description) {
131
+ lines.push(`# ${varDef.description}`);
132
+ }
133
+
134
+ // Quote values that contain spaces or special characters
135
+ const needsQuotes = /[\s#"'\\]/.test(value);
136
+ const quotedValue = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
137
+
138
+ lines.push(`${key}=${quotedValue}`);
139
+ });
140
+ lines.push('');
141
+ }
142
+ });
143
+
144
+ fs.writeFileSync(configPath, lines.join('\n'), 'utf8');
145
+
146
+ } catch (error) {
147
+ console.error(colors.Red(`${SYMBOLS.CROSS} Error writing config file ${configPath}: ${error.message}`));
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Set a configuration value
154
+ * @param {string} key - Configuration key
155
+ * @param {string} value - Configuration value
156
+ * @param {Object} options - Options like { global: true }
157
+ */
158
+ export function setConfigValue(key, value, options = {}) {
159
+ const paths = getConfigPaths();
160
+ const configPath = options.global ? paths.global : paths.project;
161
+
162
+ const config = readConfig(configPath);
163
+ config[key] = value;
164
+ writeConfig(configPath, config);
165
+ }
166
+
167
+ /**
168
+ * Get a configuration value with fallback hierarchy
169
+ * @param {string} key - Configuration key
170
+ * @returns {Object} Value info with source
171
+ */
172
+ export function getConfigValue(key) {
173
+ const paths = getConfigPaths();
174
+
175
+ // Check project local config first (highest priority)
176
+ const projectLocalPath = path.resolve(process.cwd(), '.env.maiass.local');
177
+ const projectLocalConfig = readConfig(projectLocalPath);
178
+ if (projectLocalConfig[key] !== undefined) {
179
+ return {
180
+ value: projectLocalConfig[key],
181
+ source: 'project local',
182
+ path: projectLocalPath
183
+ };
184
+ }
185
+
186
+ // Check project config (second priority)
187
+ const projectConfig = readConfig(paths.project);
188
+ if (projectConfig[key] !== undefined) {
189
+ return {
190
+ value: projectConfig[key],
191
+ source: 'project',
192
+ path: paths.project
193
+ };
194
+ }
195
+
196
+ // Check global config
197
+ const globalConfig = readConfig(paths.global);
198
+ if (globalConfig[key] !== undefined) {
199
+ return {
200
+ value: globalConfig[key],
201
+ source: 'global',
202
+ path: paths.global
203
+ };
204
+ }
205
+
206
+ // Check default value
207
+ const varDef = MAIASS_VARIABLES[key];
208
+ if (varDef && varDef.default !== undefined) {
209
+ return {
210
+ value: varDef.default,
211
+ source: 'default',
212
+ path: null
213
+ };
214
+ }
215
+
216
+ return {
217
+ value: undefined,
218
+ source: 'not_set',
219
+ path: null
220
+ };
221
+ }
222
+
223
+ /**
224
+ * List all configuration values with their sources
225
+ * @param {Object} options - Listing options
226
+ * @returns {Object} Configuration listing
227
+ */
228
+ export function listConfig(options = {}) {
229
+ const { scope = 'all', showSensitive = false } = options;
230
+ const paths = getConfigPaths();
231
+
232
+ const result = {
233
+ global: {},
234
+ project: {},
235
+ merged: {},
236
+ files: {
237
+ global: { exists: configExists(paths.global), path: paths.global },
238
+ project: { exists: configExists(paths.project), path: paths.project }
239
+ }
240
+ };
241
+
242
+ // Read config files
243
+ if (result.files.global.exists) {
244
+ result.global = readConfig(paths.global);
245
+ }
246
+
247
+ if (result.files.project.exists) {
248
+ result.project = readConfig(paths.project);
249
+ }
250
+
251
+ // Create merged config with source tracking
252
+ Object.keys(MAIASS_VARIABLES).forEach(key => {
253
+ const valueInfo = getConfigValue(key);
254
+ const varDef = MAIASS_VARIABLES[key];
255
+
256
+ // Skip sensitive variables unless explicitly requested
257
+ if (varDef.sensitive && !showSensitive) {
258
+ if (valueInfo.value) {
259
+ valueInfo.value = '***' + valueInfo.value.slice(-4);
260
+ }
261
+ }
262
+
263
+ result.merged[key] = {
264
+ ...valueInfo,
265
+ description: varDef.description,
266
+ sensitive: varDef.sensitive || false
267
+ };
268
+ });
269
+
270
+ return result;
271
+ }
272
+
273
+ /**
274
+ * Open configuration file in default editor
275
+ * @param {string} configPath - Path to config file
276
+ */
277
+ export function editConfig(configPath) {
278
+ try {
279
+ // Create file if it doesn't exist
280
+ if (!configExists(configPath)) {
281
+ writeConfig(configPath, {});
282
+ }
283
+
284
+ // Try to detect and use appropriate editor
285
+ const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
286
+
287
+ log.blue(SYMBOLS.INFO, `Opening ${configPath} in ${editor}...`);
288
+
289
+ execSync(`${editor} "${configPath}"`, {
290
+ stdio: 'inherit',
291
+ cwd: process.cwd()
292
+ });
293
+
294
+ log.success(SYMBOLS.CHECKMARK, 'Configuration file updated');
295
+
296
+ } catch (error) {
297
+ console.error(colors.Red(`${SYMBOLS.CROSS} Error opening editor: ${error.message}`));
298
+ log.warning(SYMBOLS.INFO, `You can manually edit: ${configPath}`);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Validate configuration values
304
+ * @param {Object} config - Configuration object to validate
305
+ * @returns {Array} Array of validation errors
306
+ */
307
+ export function validateConfig(config) {
308
+ const errors = [];
309
+
310
+ Object.entries(config).forEach(([key, value]) => {
311
+ const varDef = MAIASS_VARIABLES[key];
312
+
313
+ if (!varDef) {
314
+ errors.push({
315
+ key,
316
+ error: 'Unknown configuration variable',
317
+ suggestion: 'Check available variables with: nma config --list-vars'
318
+ });
319
+ return;
320
+ }
321
+
322
+ // Add specific validation rules here as needed
323
+ if (key === 'MAIASS_AI_TEMPERATURE') {
324
+ const temp = parseFloat(value);
325
+ if (isNaN(temp) || temp < 0 || temp > 2) {
326
+ errors.push({
327
+ key,
328
+ error: 'Temperature must be a number between 0 and 2',
329
+ current: value
330
+ });
331
+ }
332
+ }
333
+
334
+ if (key.includes('PULLREQUESTS') && !['on', 'off'].includes(value)) {
335
+ errors.push({
336
+ key,
337
+ error: 'Pull request setting must be "on" or "off"',
338
+ current: value
339
+ });
340
+ }
341
+ });
342
+
343
+ return errors;
344
+ }