Skip to content

Commit 245903c

Browse files
committed
Enhance CLI config command with 'clear' option and default value indicators (#117)
1 parent c157e10 commit 245903c

File tree

4 files changed

+166
-7
lines changed

4 files changed

+166
-7
lines changed

packages/cli/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ mycoder config get githubMode
7575
# Set a configuration value
7676
mycoder config set githubMode true
7777

78+
# Reset a configuration value to its default
79+
mycoder config clear customPrompt
80+
7881
# Configure model provider and model name
7982
mycoder config set modelProvider openai
8083
mycoder config set modelName gpt-4o-2024-05-13

packages/cli/src/commands/config.ts

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import chalk from 'chalk';
22
import { Logger } from 'mycoder-agent';
33

44
import { SharedOptions } from '../options.js';
5-
import { getConfig, updateConfig } from '../settings/config.js';
5+
import {
6+
getConfig,
7+
getDefaultConfig,
8+
updateConfig,
9+
} from '../settings/config.js';
610
import { nameToLogIndex } from '../utils/nameToLogIndex.js';
711

812
import type { CommandModule, ArgumentsCamelCase } from 'yargs';
913

1014
export interface ConfigOptions extends SharedOptions {
11-
command: 'get' | 'set' | 'list';
15+
command: 'get' | 'set' | 'list' | 'clear';
1216
key?: string;
1317
value?: string;
1418
}
@@ -20,7 +24,7 @@ export const command: CommandModule<SharedOptions, ConfigOptions> = {
2024
return yargs
2125
.positional('command', {
2226
describe: 'Config command to run',
23-
choices: ['get', 'set', 'list'],
27+
choices: ['get', 'set', 'list', 'clear'],
2428
type: 'string',
2529
demandOption: true,
2630
})
@@ -37,7 +41,11 @@ export const command: CommandModule<SharedOptions, ConfigOptions> = {
3741
'$0 config get githubMode',
3842
'Get the value of githubMode setting',
3943
)
40-
.example('$0 config set githubMode true', 'Enable GitHub mode') as any; // eslint-disable-line @typescript-eslint/no-explicit-any
44+
.example('$0 config set githubMode true', 'Enable GitHub mode')
45+
.example(
46+
'$0 config clear customPrompt',
47+
'Reset customPrompt to default value',
48+
) as any; // eslint-disable-line @typescript-eslint/no-explicit-any
4149
},
4250
handler: async (argv: ArgumentsCamelCase<ConfigOptions>) => {
4351
const logger = new Logger({
@@ -50,8 +58,16 @@ export const command: CommandModule<SharedOptions, ConfigOptions> = {
5058
// Handle 'list' command
5159
if (argv.command === 'list') {
5260
logger.info('Current configuration:');
61+
const defaultConfig = getDefaultConfig();
5362
Object.entries(config).forEach(([key, value]) => {
54-
logger.info(` ${key}: ${chalk.green(value)}`);
63+
const isDefault =
64+
JSON.stringify(value) ===
65+
JSON.stringify(defaultConfig[key as keyof typeof defaultConfig]);
66+
const valueDisplay = chalk.green(value);
67+
const statusIndicator = isDefault
68+
? chalk.dim(' (default)')
69+
: chalk.blue(' (custom)');
70+
logger.info(` ${key}: ${valueDisplay}${statusIndicator}`);
5571
});
5672
return;
5773
}
@@ -116,8 +132,51 @@ export const command: CommandModule<SharedOptions, ConfigOptions> = {
116132
return;
117133
}
118134

135+
// Handle 'clear' command
136+
if (argv.command === 'clear') {
137+
if (!argv.key) {
138+
logger.error('Key is required for clear command');
139+
return;
140+
}
141+
142+
const defaultConfig = getDefaultConfig();
143+
144+
// Check if the key exists in the config
145+
if (!(argv.key in config)) {
146+
logger.error(`Configuration key '${argv.key}' not found`);
147+
return;
148+
}
149+
150+
// Check if the key exists in the default config
151+
if (!(argv.key in defaultConfig)) {
152+
logger.error(
153+
`Configuration key '${argv.key}' does not have a default value`,
154+
);
155+
return;
156+
}
157+
158+
// Get the current config, create a new object without the specified key
159+
const currentConfig = getConfig();
160+
const { [argv.key]: _, ...newConfig } = currentConfig as Record<
161+
string,
162+
any
163+
>;
164+
165+
// Update the config file with the new object
166+
updateConfig(newConfig);
167+
168+
// Get the default value that will now be used
169+
const defaultValue =
170+
defaultConfig[argv.key as keyof typeof defaultConfig];
171+
172+
logger.info(
173+
`Cleared ${argv.key}, now using default value: ${chalk.green(defaultValue)}`,
174+
);
175+
return;
176+
}
177+
119178
// If command not recognized
120179
logger.error(`Unknown config command: ${argv.command}`);
121-
logger.info('Available commands: get, set, list');
180+
logger.info('Available commands: get, set, list, clear');
122181
},
123182
};

packages/cli/src/settings/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ const defaultConfig = {
2424

2525
export type Config = typeof defaultConfig;
2626

27+
// Export the default config for use in other functions
28+
export const getDefaultConfig = (): Config => {
29+
return { ...defaultConfig };
30+
};
31+
2732
export const getConfig = (): Config => {
2833
if (!fs.existsSync(configFile)) {
2934
return defaultConfig;

packages/cli/tests/commands/config.test.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import { Logger } from 'mycoder-agent';
22
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
33

44
import { command } from '../../src/commands/config.js';
5-
import { getConfig, updateConfig } from '../../src/settings/config.js';
5+
import {
6+
getConfig,
7+
getDefaultConfig,
8+
updateConfig,
9+
} from '../../src/settings/config.js';
610

711
// Mock dependencies
812
vi.mock('../../src/settings/config.js', () => ({
913
getConfig: vi.fn(),
14+
getDefaultConfig: vi.fn(),
1015
updateConfig: vi.fn(),
1116
}));
1217

@@ -39,6 +44,10 @@ describe.skip('Config Command', () => {
3944
};
4045
vi.mocked(Logger).mockImplementation(() => mockLogger as unknown as Logger);
4146
vi.mocked(getConfig).mockReturnValue({ githubMode: false });
47+
vi.mocked(getDefaultConfig).mockReturnValue({
48+
githubMode: false,
49+
customPrompt: '',
50+
});
4251
vi.mocked(updateConfig).mockImplementation((config) => ({
4352
githubMode: false,
4453
...config,
@@ -150,4 +159,87 @@ describe.skip('Config Command', () => {
150159
expect.stringContaining('Unknown config command'),
151160
);
152161
});
162+
163+
it('should list all configuration values with default indicators', async () => {
164+
// Mock getConfig to return a mix of default and custom values
165+
vi.mocked(getConfig).mockReturnValue({
166+
githubMode: false, // default value
167+
customPrompt: 'custom value', // custom value
168+
});
169+
170+
// Mock getDefaultConfig to return the default values
171+
vi.mocked(getDefaultConfig).mockReturnValue({
172+
githubMode: false,
173+
customPrompt: '',
174+
});
175+
176+
await command.handler!({
177+
_: ['config', 'config', 'list'],
178+
logLevel: 'info',
179+
interactive: false,
180+
command: 'list',
181+
} as any);
182+
183+
expect(getConfig).toHaveBeenCalled();
184+
expect(getDefaultConfig).toHaveBeenCalled();
185+
expect(mockLogger.info).toHaveBeenCalledWith('Current configuration:');
186+
187+
// Check for default indicator
188+
expect(mockLogger.info).toHaveBeenCalledWith(
189+
expect.stringContaining('githubMode') &&
190+
expect.stringContaining('(default)'),
191+
);
192+
193+
// Check for custom indicator
194+
expect(mockLogger.info).toHaveBeenCalledWith(
195+
expect.stringContaining('customPrompt') &&
196+
expect.stringContaining('(custom)'),
197+
);
198+
});
199+
200+
it('should clear a configuration value', async () => {
201+
await command.handler!({
202+
_: ['config', 'config', 'clear', 'customPrompt'],
203+
logLevel: 'info',
204+
interactive: false,
205+
command: 'clear',
206+
key: 'customPrompt',
207+
} as any);
208+
209+
// Verify updateConfig was called with an object that doesn't include the key
210+
expect(updateConfig).toHaveBeenCalled();
211+
212+
// Verify success message
213+
expect(mockLogger.info).toHaveBeenCalledWith(
214+
expect.stringContaining('Cleared customPrompt'),
215+
);
216+
});
217+
218+
it('should handle missing key for clear command', async () => {
219+
await command.handler!({
220+
_: ['config', 'config', 'clear'],
221+
logLevel: 'info',
222+
interactive: false,
223+
command: 'clear',
224+
key: undefined,
225+
} as any);
226+
227+
expect(mockLogger.error).toHaveBeenCalledWith(
228+
expect.stringContaining('Key is required'),
229+
);
230+
});
231+
232+
it('should handle non-existent key for clear command', async () => {
233+
await command.handler!({
234+
_: ['config', 'config', 'clear', 'nonExistentKey'],
235+
logLevel: 'info',
236+
interactive: false,
237+
command: 'clear',
238+
key: 'nonExistentKey',
239+
} as any);
240+
241+
expect(mockLogger.error).toHaveBeenCalledWith(
242+
expect.stringContaining('not found'),
243+
);
244+
});
153245
});

0 commit comments

Comments
 (0)