Skip to content

Commit 40643be

Browse files
committed
feat(cli): add initial citty implementation and migration framework
This commit: - Adds citty as a dependency - Creates a parallel implementation using citty - Sets up a feature flag (USE_CITTY) to switch between implementations - Preserves the original yargs implementation - Adds start:citty script to test the new implementation Refs #258
1 parent 72f9801 commit 40643be

File tree

16 files changed

+1155
-101
lines changed

16 files changed

+1155
-101
lines changed

migration-plan.md

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
# Migration Plan: Yargs to Citty
2+
3+
## Overview
4+
5+
This document outlines the plan to migrate the CLI package from yargs to citty. Citty is a modern, lightweight CLI builder that offers several advantages over yargs, including better TypeScript support, a more elegant API, and a more modular approach.
6+
7+
## Current Implementation Analysis
8+
9+
### CLI Structure
10+
11+
The current CLI implementation uses yargs with the following components:
12+
13+
- `index.ts`: Main entry point that sets up yargs and registers commands
14+
- `commands/*.ts`: Individual command implementations
15+
- `options.ts`: Shared options definition
16+
- Custom command loading from config
17+
18+
### Command Pattern
19+
20+
Commands are implemented as yargs command modules with:
21+
22+
- `command`: Command definition string
23+
- `describe`: Command description
24+
- `builder`: Function to define command-specific options
25+
- `handler`: Function to execute the command
26+
27+
### Shared Options
28+
29+
Shared options are defined in `options.ts` and used across commands.
30+
31+
## Citty Implementation Plan
32+
33+
### 1. Dependencies Update
34+
35+
- Add citty as a dependency
36+
- Keep yargs temporarily during the migration
37+
38+
### 2. Command Structure Refactoring
39+
40+
- Create a new directory structure for citty commands
41+
- Implement the main command definition using citty's `defineCommand`
42+
- Implement subcommands using citty's nested command structure
43+
44+
### 3. Migration Steps
45+
46+
#### Step 1: Create Base Command Structure
47+
48+
```typescript
49+
// src/cli.ts
50+
import { defineCommand, runMain } from 'citty';
51+
import { sharedArgs } from './args';
52+
53+
const main = defineCommand({
54+
meta: {
55+
name: 'mycoder',
56+
version: '1.3.1',
57+
description:
58+
'A command line tool using agent that can do arbitrary tasks, including coding tasks',
59+
},
60+
args: sharedArgs,
61+
subCommands: {
62+
// Will be populated with commands
63+
},
64+
});
65+
66+
export default main;
67+
```
68+
69+
#### Step 2: Convert Shared Options
70+
71+
```typescript
72+
// src/args.ts
73+
import type { CommandArgs } from 'citty';
74+
75+
export const sharedArgs: CommandArgs = {
76+
logLevel: {
77+
type: 'string',
78+
description: 'Set minimum logging level',
79+
options: ['debug', 'verbose', 'info', 'warn', 'error'],
80+
},
81+
profile: {
82+
type: 'boolean',
83+
description: 'Enable performance profiling of CLI startup',
84+
},
85+
provider: {
86+
type: 'string',
87+
description: 'AI model provider to use',
88+
options: ['anthropic', 'ollama', 'openai'],
89+
},
90+
model: {
91+
type: 'string',
92+
description: 'AI model name to use',
93+
},
94+
maxTokens: {
95+
type: 'number',
96+
description: 'Maximum number of tokens to generate',
97+
},
98+
temperature: {
99+
type: 'number',
100+
description: 'Temperature for text generation (0.0-1.0)',
101+
},
102+
interactive: {
103+
type: 'boolean',
104+
alias: 'i',
105+
description: 'Run in interactive mode, asking for prompts',
106+
default: false,
107+
},
108+
file: {
109+
type: 'string',
110+
alias: 'f',
111+
description: 'Read prompt from a file',
112+
},
113+
tokenUsage: {
114+
type: 'boolean',
115+
description: 'Output token usage at info log level',
116+
},
117+
headless: {
118+
type: 'boolean',
119+
description: 'Use browser in headless mode with no UI showing',
120+
},
121+
userSession: {
122+
type: 'boolean',
123+
description:
124+
"Use user's existing browser session instead of sandboxed session",
125+
},
126+
pageFilter: {
127+
type: 'string',
128+
description: 'Method to process webpage content',
129+
options: ['simple', 'none', 'readability'],
130+
},
131+
tokenCache: {
132+
type: 'boolean',
133+
description: 'Enable token caching for LLM API calls',
134+
},
135+
userPrompt: {
136+
type: 'boolean',
137+
description: 'Alias for userPrompt: enable or disable the userPrompt tool',
138+
},
139+
githubMode: {
140+
type: 'boolean',
141+
description:
142+
'Enable GitHub mode for working with issues and PRs (requires git and gh CLI tools)',
143+
default: true,
144+
},
145+
upgradeCheck: {
146+
type: 'boolean',
147+
description: 'Disable version upgrade check (for automated/remote usage)',
148+
},
149+
ollamaBaseUrl: {
150+
type: 'string',
151+
description: 'Base URL for Ollama API (default: http://localhost:11434)',
152+
},
153+
};
154+
```
155+
156+
#### Step 3: Convert Default Command
157+
158+
```typescript
159+
// src/commands/default.ts
160+
import { defineCommand } from 'citty';
161+
import { sharedArgs } from '../args';
162+
import { executePrompt } from '../utils/execute-prompt';
163+
import { loadConfig, getConfigFromArgv } from '../settings/config';
164+
import * as fs from 'fs/promises';
165+
import { userPrompt } from 'mycoder-agent';
166+
167+
export const defaultCommand = defineCommand({
168+
meta: {
169+
name: 'default',
170+
description: 'Execute a prompt or start interactive mode',
171+
},
172+
args: {
173+
...sharedArgs,
174+
prompt: {
175+
type: 'positional',
176+
description: 'The prompt to execute',
177+
},
178+
},
179+
async run({ args }) {
180+
// Get configuration for model provider and name
181+
const config = await loadConfig(getConfigFromArgv(args));
182+
183+
let prompt: string | undefined;
184+
185+
// If file is specified, read from file
186+
if (args.file) {
187+
prompt = await fs.readFile(args.file, 'utf-8');
188+
}
189+
190+
// If interactive mode
191+
if (args.interactive) {
192+
prompt = await userPrompt(
193+
"Type your request below or 'help' for usage information. Use Ctrl+C to exit.",
194+
);
195+
} else if (!prompt) {
196+
// Use command line prompt if provided
197+
prompt = args.prompt;
198+
}
199+
200+
if (!prompt) {
201+
throw new Error(
202+
'No prompt provided. Either specify a prompt, use --file, or run in --interactive mode.',
203+
);
204+
}
205+
206+
// Execute the prompt
207+
await executePrompt(prompt, config);
208+
},
209+
});
210+
```
211+
212+
#### Step 4: Convert Other Commands
213+
214+
Convert each command in the `commands/` directory to use citty's `defineCommand` function.
215+
216+
#### Step 5: Implement Custom Command Loading
217+
218+
```typescript
219+
// src/commands/custom.ts
220+
import { defineCommand, CommandDef } from 'citty';
221+
import { loadConfig } from '../settings/config';
222+
import { executePrompt } from '../utils/execute-prompt';
223+
224+
export async function getCustomCommands(): Promise<Record<string, CommandDef>> {
225+
const config = await loadConfig();
226+
227+
if (!config.commands) {
228+
return {};
229+
}
230+
231+
const commands: Record<string, CommandDef> = {};
232+
233+
for (const [name, commandConfig] of Object.entries(config.commands)) {
234+
const args: Record<string, any> = {};
235+
236+
// Convert args to citty format
237+
(commandConfig.args || []).forEach((arg) => {
238+
args[arg.name] = {
239+
type: 'string',
240+
description: arg.description,
241+
default: arg.default,
242+
required: arg.required,
243+
};
244+
});
245+
246+
commands[name] = defineCommand({
247+
meta: {
248+
name,
249+
description: commandConfig.description || `Custom command: ${name}`,
250+
},
251+
args,
252+
async run({ args }) {
253+
// Load config
254+
const config = await loadConfig();
255+
256+
// Execute the command
257+
const prompt = await commandConfig.execute(args);
258+
259+
// Execute the prompt using the default command handler
260+
await executePrompt(prompt, config);
261+
},
262+
});
263+
}
264+
265+
return commands;
266+
}
267+
```
268+
269+
#### Step 6: Update Entry Point
270+
271+
```typescript
272+
// src/index.ts
273+
import { runMain } from 'citty';
274+
import main from './cli';
275+
276+
// Start the CLI
277+
runMain(main);
278+
```
279+
280+
#### Step 7: Update bin/cli.js
281+
282+
```javascript
283+
#!/usr/bin/env node
284+
import '../dist/index.js';
285+
```
286+
287+
### 4. Testing Strategy
288+
289+
- Implement unit tests for each converted command
290+
- Test command execution with various arguments
291+
- Test help output and argument parsing
292+
- Test custom command loading
293+
294+
### 5. Incremental Migration Approach
295+
296+
1. Implement citty commands alongside existing yargs commands
297+
2. Add a feature flag to switch between implementations
298+
3. Test thoroughly with both implementations
299+
4. Switch to citty implementation by default
300+
5. Remove yargs implementation when stable
301+
302+
## Benefits of Migration
303+
304+
- Better TypeScript support and type safety
305+
- More modular and composable command structure
306+
- Improved performance due to lighter dependencies
307+
- Better maintainability with modern API design
308+
- Enhanced developer experience
309+
310+
## Potential Challenges
311+
312+
- Ensuring backward compatibility for all command options
313+
- Handling custom command loading logic
314+
- Managing the transition period with both implementations

packages/agent/CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# [mycoder-agent-v1.3.1](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.3.0...mycoder-agent-v1.3.1) (2025-03-13)
22

3-
43
### Bug Fixes
54

6-
* redo ollama llm provider using ollama sdk ([586fe82](https://github.com/drivecore/mycoder/commit/586fe827d048aa6c13675ba838bd50309b3980e2))
7-
* update Ollama provider to use official npm package API correctly ([738a84a](https://github.com/drivecore/mycoder/commit/738a84aff560076e4ad24129f5dc9bf09d304ffa))
5+
- redo ollama llm provider using ollama sdk ([586fe82](https://github.com/drivecore/mycoder/commit/586fe827d048aa6c13675ba838bd50309b3980e2))
6+
- update Ollama provider to use official npm package API correctly ([738a84a](https://github.com/drivecore/mycoder/commit/738a84aff560076e4ad24129f5dc9bf09d304ffa))
87

98
# [mycoder-agent-v1.3.0](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.2.0...mycoder-agent-v1.3.0) (2025-03-12)
109

packages/cli/CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# [mycoder-v1.3.1](https://github.com/drivecore/mycoder/compare/mycoder-v1.3.0...mycoder-v1.3.1) (2025-03-13)
22

3-
43
### Bug Fixes
54

6-
* redo ollama llm provider using ollama sdk ([586fe82](https://github.com/drivecore/mycoder/commit/586fe827d048aa6c13675ba838bd50309b3980e2))
5+
- redo ollama llm provider using ollama sdk ([586fe82](https://github.com/drivecore/mycoder/commit/586fe827d048aa6c13675ba838bd50309b3980e2))
76

87
# [mycoder-v1.3.0](https://github.com/drivecore/mycoder/compare/mycoder-v1.2.0...mycoder-v1.3.0) (2025-03-12)
98

packages/cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"scripts": {
2222
"start": "node --no-deprecation bin/cli.js",
23+
"start:citty": "USE_CITTY=true node --no-deprecation bin/cli.js",
2324
"typecheck": "tsc --noEmit",
2425
"build": "tsc",
2526
"clean": "rimraf dist",
@@ -47,6 +48,7 @@
4748
"dependencies": {
4849
"@sentry/node": "^9.3.0",
4950
"chalk": "^5",
51+
"citty": "^0.1.6",
5052
"cosmiconfig": "^9.0.0",
5153
"deepmerge": "^4.3.1",
5254
"dotenv": "^16",

0 commit comments

Comments
 (0)