Skip to content

Add Config Command to MyCoder #68

@bhouston

Description

@bhouston

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions