-
Notifications
You must be signed in to change notification settings - Fork 51
Closed
Labels
Description
Add Config Command to MyCoder
Goal
Implement a config
command for MyCoder that allows users to manage global configuration settings using a familiar syntax similar to npm and gcloud.
Problem Statement
Currently, MyCoder doesn't have a standardized way to manage global configuration settings. Adding a dedicated config
command would provide a consistent interface for setting and retrieving configuration values, which will be useful for features like GitHub mode and other future enhancements.
Requirements
- Create a
config
command with get, set, and list subcommands - Follow patterns from familiar tools like npm and gcloud
- Support different value types (string, boolean, number)
- Store configuration in the
.mycoder
directory - Provide clear error messages and examples
Implementation Plan
1. Add a new config
command
packages/cli/src/commands/config.ts
(New File)
import chalk from 'chalk';
import { Logger, LogLevel } from 'mycoder-agent';
import { SharedOptions } from '../options.js';
import { getConfig, updateConfig } from '../config/config.js';
import { nameToLogIndex } from '../utils/nameToLogIndex.js';
import type { CommandModule, Argv } from 'yargs';
interface ConfigArgs extends SharedOptions {
_: string[];
}
export const command: CommandModule<SharedOptions, ConfigArgs> = {
command: 'config <command> [key] [value]',
describe: 'Manage MyCoder configuration',
builder: (yargs: Argv<object>): Argv<ConfigArgs> => {
return yargs
.positional('command', {
describe: 'Config command to run',
choices: ['get', 'set', 'list'],
type: 'string',
})
.positional('key', {
describe: 'Configuration key',
type: 'string',
})
.positional('value', {
describe: 'Configuration value (for set command)',
type: 'string',
})
.example('$0 config list', 'List all configuration values')
.example('$0 config get githubMode', 'Get the value of githubMode setting')
.example('$0 config set githubMode true', 'Enable GitHub mode');
},
handler: async (argv) => {
const logger = new Logger({
name: 'Config',
logLevel: nameToLogIndex(argv.logLevel),
});
const config = getConfig();
const [, , command, key, value] = argv._;
// Handle 'list' command
if (command === 'list') {
logger.info('Current configuration:');
Object.entries(config).forEach(([key, value]) => {
logger.info(` ${key}: ${chalk.green(value)}`);
});
return;
}
// Handle 'get' command
if (command === 'get') {
if (!key) {
logger.error('Key is required for get command');
return;
}
if (key in config) {
logger.info(`${key}: ${chalk.green(config[key])}`);
} else {
logger.error(`Configuration key '${key}' not found`);
}
return;
}
// Handle 'set' command
if (command === 'set') {
if (!key) {
logger.error('Key is required for set command');
return;
}
if (value === undefined) {
logger.error('Value is required for set command');
return;
}
// Parse the value based on current type or infer boolean
let parsedValue: any = value;
// Check if config already exists to determine type
if (key in config) {
if (typeof config[key as keyof typeof config] === 'boolean') {
parsedValue = value.toLowerCase() === 'true';
} else if (typeof config[key as keyof typeof config] === 'number') {
parsedValue = Number(value);
}
} else {
// If config doesn't exist yet, try to infer type
if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
parsedValue = value.toLowerCase() === 'true';
} else if (!isNaN(Number(value))) {
parsedValue = Number(value);
}
}
const updatedConfig = updateConfig({ [key]: parsedValue });
logger.info(`Updated ${key}: ${chalk.green(updatedConfig[key as keyof typeof updatedConfig])}`);
return;
}
// If command not recognized
logger.error(`Unknown config command: ${command}`);
logger.info('Available commands: get, set, list');
},
};
2. Create Config Module
packages/cli/src/config/config.ts
(New File)
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
const configDir = path.join(os.homedir(), '.mycoder');
export const getConfigDir = (): string => {
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
return configDir;
};
const configFile = path.join(configDir, 'config.json');
// Default configuration
const defaultConfig = {
// Add default configuration values here
githubMode: false,
};
export type Config = typeof defaultConfig;
export const getConfig = (): Config => {
if (!fs.existsSync(configFile)) {
return defaultConfig;
}
try {
return JSON.parse(fs.readFileSync(configFile, 'utf-8'));
} catch (error) {
return defaultConfig;
}
};
export const updateConfig = (config: Partial<Config>): Config => {
const currentConfig = getConfig();
const updatedConfig = { ...currentConfig, ...config };
fs.writeFileSync(configFile, JSON.stringify(updatedConfig, null, 2));
return updatedConfig;
};
3. Update Main CLI to Include Config Command
packages/cli/src/index.ts
// Add to imports
import { command as configCommand } from './commands/config.js';
// Update commands array
.command([
defaultCommand,
testSentryCommand,
toolsCommand,
configCommand, // Add config command
] as CommandModule[])
Example Usage
# Enable GitHub mode
mycoder config set githubMode true
# List all configuration values
mycoder config list
# Get a specific configuration value
mycoder config get githubMode
Dependencies
- This feature requires no external dependencies
- It will be useful for the GitHub mode feature but can be implemented independently
Testing Plan
- Verify that the config command works for all subcommands (get, set, list)
- Test with different value types (string, boolean, number)
- Ensure configuration persists between runs
- Test error handling for invalid commands or missing arguments
Documentation
- Add documentation for the config command in the CLI README
- Include examples of common usage patterns