Skip to content

Commit 52fba7a

Browse files
authored
feat(cli): Beautiful CLI with dev command (#16)
* chore(cli): add CLI dependencies and setup - Add MIT license to root package.json - Add chalk, ora, commander to CLI package - Add @types/node for TypeScript support - Configure bin entry point for 'dev' command * feat(cli): add logger and config utilities Logger: - Colored output with emojis (info, success, error, warn) - Debug mode support via DEBUG env var - Clean, consistent messaging interface Config: - Load/save .dev-agent.json configuration - Find config file in parent directories - Default configuration generation - Type-safe configuration interface * feat(cli): add main CLI entry point - Create 'dev' CLI with commander - Register 6 subcommands: init, index, search, update, stats, clean - Beautiful help output with emoji and colors - Auto-show help when no command provided * feat(cli): implement all CLI commands Commands: - init: Initialize dev-agent configuration - index: Index repository with progress tracking - search: Semantic code search with formatted results - update: Incremental index updates - stats: Show indexing statistics - clean: Clean indexed data and cache Features: - Beautiful spinners with ora - Colored output with chalk - Progress tracking and percentage updates - JSON output options for piping - Verbose mode for debugging - Error handling with helpful suggestions * test(cli): add CLI structure tests - Test all 6 commands are registered - Verify command names and descriptions - Test command options (force, verbose, json, limit, threshold) - 100% coverage of CLI structure Total: 133 tests passing (+30 CLI tests) * docs(cli): add comprehensive CLI documentation - Installation instructions - Usage examples for all 6 commands - Configuration file format - Feature highlights - Real-world examples and workflows - MIT license
1 parent 3c04783 commit 52fba7a

File tree

17 files changed

+1197
-63
lines changed

17 files changed

+1197
-63
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@lytics/dev-agent",
33
"version": "0.1.0",
44
"private": true,
5+
"license": "MIT",
56
"workspaces": [
67
"packages/*"
78
],

packages/cli/README.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# @lytics/dev-agent-cli
2+
3+
Command-line interface for dev-agent - Multi-agent code intelligence platform.
4+
5+
## Installation
6+
7+
```bash
8+
npm install -g @lytics/dev-agent-cli
9+
```
10+
11+
## Usage
12+
13+
### Initialize
14+
15+
Initialize dev-agent in your repository:
16+
17+
```bash
18+
dev init
19+
```
20+
21+
This creates a `.dev-agent.json` configuration file.
22+
23+
### Index Repository
24+
25+
Index your repository for semantic search:
26+
27+
```bash
28+
dev index .
29+
```
30+
31+
Options:
32+
- `-f, --force` - Force re-index even if unchanged
33+
- `-v, --verbose` - Show verbose output
34+
35+
### Search
36+
37+
Search your indexed code semantically:
38+
39+
```bash
40+
dev search "authentication logic"
41+
```
42+
43+
Options:
44+
- `-l, --limit <number>` - Maximum results (default: 10)
45+
- `-t, --threshold <number>` - Minimum similarity score 0-1 (default: 0.7)
46+
- `--json` - Output as JSON
47+
48+
### Update
49+
50+
Incrementally update the index with changed files:
51+
52+
```bash
53+
dev update
54+
```
55+
56+
Options:
57+
- `-v, --verbose` - Show verbose output
58+
59+
### Stats
60+
61+
Show indexing statistics:
62+
63+
```bash
64+
dev stats
65+
```
66+
67+
Options:
68+
- `--json` - Output as JSON
69+
70+
### Clean
71+
72+
Remove all indexed data:
73+
74+
```bash
75+
dev clean --force
76+
```
77+
78+
Options:
79+
- `-f, --force` - Skip confirmation prompt
80+
81+
## Configuration
82+
83+
The `.dev-agent.json` file configures the indexer:
84+
85+
```json
86+
{
87+
"repositoryPath": "/path/to/repo",
88+
"vectorStorePath": ".dev-agent/vectors.lance",
89+
"embeddingModel": "Xenova/all-MiniLM-L6-v2",
90+
"dimension": 384,
91+
"excludePatterns": [
92+
"**/node_modules/**",
93+
"**/dist/**",
94+
"**/.git/**"
95+
],
96+
"languages": ["typescript", "javascript", "markdown"]
97+
}
98+
```
99+
100+
## Features
101+
102+
- 🎨 **Beautiful UX** - Colored output, spinners, progress indicators
103+
-**Fast** - Incremental updates, efficient indexing
104+
- 🧠 **Semantic Search** - Find code by meaning, not exact matches
105+
- 🔧 **Configurable** - Customize patterns, languages, and more
106+
- 📊 **Statistics** - Track indexing progress and stats
107+
108+
## Examples
109+
110+
```bash
111+
# Initialize and index
112+
dev init
113+
dev index .
114+
115+
# Search for code
116+
dev search "user authentication flow"
117+
dev search "database connection pool" --limit 5
118+
119+
# Keep index up to date
120+
dev update
121+
122+
# View statistics
123+
dev stats
124+
125+
# Clean and re-index
126+
dev clean --force
127+
dev index . --force
128+
```
129+
130+
## License
131+
132+
MIT
133+

packages/cli/package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"private": true,
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",
7+
"bin": {
8+
"dev": "./dist/cli.js"
9+
},
710
"exports": {
811
".": {
912
"types": "./dist/index.d.ts",
@@ -22,10 +25,13 @@
2225
"test:watch": "vitest"
2326
},
2427
"dependencies": {
25-
"@lytics/dev-agent-core": "workspace:*"
28+
"@lytics/dev-agent-core": "workspace:*",
29+
"chalk": "^5.3.0",
30+
"ora": "^8.0.1"
2631
},
2732
"devDependencies": {
28-
"typescript": "^5.3.3",
29-
"commander": "^11.1.0"
33+
"@types/node": "^22.0.0",
34+
"commander": "^12.1.0",
35+
"typescript": "^5.3.3"
3036
}
3137
}

packages/cli/src/cli.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { cleanCommand } from './commands/clean';
3+
import { indexCommand } from './commands/index';
4+
import { initCommand } from './commands/init';
5+
import { searchCommand } from './commands/search';
6+
import { statsCommand } from './commands/stats';
7+
import { updateCommand } from './commands/update';
8+
9+
describe('CLI Structure', () => {
10+
it('should have init command', () => {
11+
expect(initCommand.name()).toBe('init');
12+
expect(initCommand.description()).toContain('Initialize');
13+
});
14+
15+
it('should have index command', () => {
16+
expect(indexCommand.name()).toBe('index');
17+
expect(indexCommand.description()).toContain('Index');
18+
});
19+
20+
it('should have search command', () => {
21+
expect(searchCommand.name()).toBe('search');
22+
expect(searchCommand.description()).toContain('Search');
23+
});
24+
25+
it('should have update command', () => {
26+
expect(updateCommand.name()).toBe('update');
27+
expect(updateCommand.description()).toContain('Update');
28+
});
29+
30+
it('should have stats command', () => {
31+
expect(statsCommand.name()).toBe('stats');
32+
expect(statsCommand.description()).toContain('statistics');
33+
});
34+
35+
it('should have clean command', () => {
36+
expect(cleanCommand.name()).toBe('clean');
37+
expect(cleanCommand.description()).toContain('Clean');
38+
});
39+
40+
describe('Command Options', () => {
41+
it('index command should have force and verbose options', () => {
42+
const options = indexCommand.options;
43+
const forceOption = options.find((opt) => opt.long === '--force');
44+
const verboseOption = options.find((opt) => opt.long === '--verbose');
45+
46+
expect(forceOption).toBeDefined();
47+
expect(verboseOption).toBeDefined();
48+
});
49+
50+
it('search command should have limit, threshold, and json options', () => {
51+
const options = searchCommand.options;
52+
const limitOption = options.find((opt) => opt.long === '--limit');
53+
const thresholdOption = options.find((opt) => opt.long === '--threshold');
54+
const jsonOption = options.find((opt) => opt.long === '--json');
55+
56+
expect(limitOption).toBeDefined();
57+
expect(thresholdOption).toBeDefined();
58+
expect(jsonOption).toBeDefined();
59+
});
60+
61+
it('stats command should have json option', () => {
62+
const options = statsCommand.options;
63+
const jsonOption = options.find((opt) => opt.long === '--json');
64+
65+
expect(jsonOption).toBeDefined();
66+
});
67+
68+
it('clean command should have force option', () => {
69+
const options = cleanCommand.options;
70+
const forceOption = options.find((opt) => opt.long === '--force');
71+
72+
expect(forceOption).toBeDefined();
73+
});
74+
});
75+
});

packages/cli/src/cli.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env node
2+
3+
import chalk from 'chalk';
4+
import { Command } from 'commander';
5+
import { cleanCommand } from './commands/clean.js';
6+
import { indexCommand } from './commands/index.js';
7+
import { initCommand } from './commands/init.js';
8+
import { searchCommand } from './commands/search.js';
9+
import { statsCommand } from './commands/stats.js';
10+
import { updateCommand } from './commands/update.js';
11+
12+
const program = new Command();
13+
14+
program
15+
.name('dev')
16+
.description(chalk.cyan('🤖 Dev-Agent - Multi-agent code intelligence platform'))
17+
.version('0.1.0');
18+
19+
// Register commands
20+
program.addCommand(initCommand);
21+
program.addCommand(indexCommand);
22+
program.addCommand(searchCommand);
23+
program.addCommand(updateCommand);
24+
program.addCommand(statsCommand);
25+
program.addCommand(cleanCommand);
26+
27+
// Show help if no command provided
28+
if (process.argv.length === 2) {
29+
program.outputHelp();
30+
}
31+
32+
program.parse(process.argv);

packages/cli/src/commands/clean.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import * as fs from 'node:fs/promises';
2+
import * as path from 'node:path';
3+
import chalk from 'chalk';
4+
import { Command } from 'commander';
5+
import ora from 'ora';
6+
import { loadConfig } from '../utils/config.js';
7+
import { logger } from '../utils/logger.js';
8+
9+
export const cleanCommand = new Command('clean')
10+
.description('Clean indexed data and cache')
11+
.option('-f, --force', 'Skip confirmation prompt', false)
12+
.action(async (options) => {
13+
try {
14+
// Load config
15+
const config = await loadConfig();
16+
if (!config) {
17+
logger.warn('No config found');
18+
logger.log('Nothing to clean.');
19+
return;
20+
}
21+
22+
const dataDir = path.dirname(config.vectorStorePath);
23+
const stateFile = path.join(config.repositoryPath, '.dev-agent', 'indexer-state.json');
24+
25+
// Show what will be deleted
26+
logger.log('');
27+
logger.log(chalk.bold('The following will be deleted:'));
28+
logger.log(` ${chalk.cyan('Vector store:')} ${config.vectorStorePath}`);
29+
logger.log(` ${chalk.cyan('State file:')} ${stateFile}`);
30+
logger.log(` ${chalk.cyan('Data directory:')} ${dataDir}`);
31+
logger.log('');
32+
33+
// Confirm unless --force
34+
if (!options.force) {
35+
logger.warn('This action cannot be undone!');
36+
logger.log(`Run with ${chalk.yellow('--force')} to skip this prompt.`);
37+
logger.log('');
38+
process.exit(0);
39+
}
40+
41+
const spinner = ora('Cleaning indexed data...').start();
42+
43+
// Delete vector store
44+
try {
45+
await fs.rm(config.vectorStorePath, { recursive: true, force: true });
46+
spinner.text = 'Deleted vector store';
47+
} catch (error) {
48+
logger.debug(`Vector store not found or already deleted: ${error}`);
49+
}
50+
51+
// Delete state file
52+
try {
53+
await fs.rm(stateFile, { force: true });
54+
spinner.text = 'Deleted state file';
55+
} catch (error) {
56+
logger.debug(`State file not found or already deleted: ${error}`);
57+
}
58+
59+
// Delete data directory if empty
60+
try {
61+
const files = await fs.readdir(dataDir);
62+
if (files.length === 0) {
63+
await fs.rmdir(dataDir);
64+
spinner.text = 'Deleted data directory';
65+
}
66+
} catch (error) {
67+
logger.debug(`Data directory not found or not empty: ${error}`);
68+
}
69+
70+
spinner.succeed(chalk.green('Cleaned successfully!'));
71+
72+
logger.log('');
73+
logger.log('All indexed data has been removed.');
74+
logger.log(`Run ${chalk.yellow('dev index')} to re-index your repository.`);
75+
logger.log('');
76+
} catch (error) {
77+
logger.error(`Failed to clean: ${error instanceof Error ? error.message : String(error)}`);
78+
process.exit(1);
79+
}
80+
});

0 commit comments

Comments
 (0)