Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@lytics/dev-agent",
"version": "0.1.0",
"private": true,
"license": "MIT",
"workspaces": [
"packages/*"
],
Expand Down
133 changes: 133 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# @lytics/dev-agent-cli

Command-line interface for dev-agent - Multi-agent code intelligence platform.

## Installation

```bash
npm install -g @lytics/dev-agent-cli
```

## Usage

### Initialize

Initialize dev-agent in your repository:

```bash
dev init
```

This creates a `.dev-agent.json` configuration file.

### Index Repository

Index your repository for semantic search:

```bash
dev index .
```

Options:
- `-f, --force` - Force re-index even if unchanged
- `-v, --verbose` - Show verbose output

### Search

Search your indexed code semantically:

```bash
dev search "authentication logic"
```

Options:
- `-l, --limit <number>` - Maximum results (default: 10)
- `-t, --threshold <number>` - Minimum similarity score 0-1 (default: 0.7)
- `--json` - Output as JSON

### Update

Incrementally update the index with changed files:

```bash
dev update
```

Options:
- `-v, --verbose` - Show verbose output

### Stats

Show indexing statistics:

```bash
dev stats
```

Options:
- `--json` - Output as JSON

### Clean

Remove all indexed data:

```bash
dev clean --force
```

Options:
- `-f, --force` - Skip confirmation prompt

## Configuration

The `.dev-agent.json` file configures the indexer:

```json
{
"repositoryPath": "/path/to/repo",
"vectorStorePath": ".dev-agent/vectors.lance",
"embeddingModel": "Xenova/all-MiniLM-L6-v2",
"dimension": 384,
"excludePatterns": [
"**/node_modules/**",
"**/dist/**",
"**/.git/**"
],
"languages": ["typescript", "javascript", "markdown"]
}
```

## Features

- 🎨 **Beautiful UX** - Colored output, spinners, progress indicators
- ⚡ **Fast** - Incremental updates, efficient indexing
- 🧠 **Semantic Search** - Find code by meaning, not exact matches
- 🔧 **Configurable** - Customize patterns, languages, and more
- 📊 **Statistics** - Track indexing progress and stats

## Examples

```bash
# Initialize and index
dev init
dev index .

# Search for code
dev search "user authentication flow"
dev search "database connection pool" --limit 5

# Keep index up to date
dev update

# View statistics
dev stats

# Clean and re-index
dev clean --force
dev index . --force
```

## License

MIT

12 changes: 9 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"dev": "./dist/cli.js"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
Expand All @@ -22,10 +25,13 @@
"test:watch": "vitest"
},
"dependencies": {
"@lytics/dev-agent-core": "workspace:*"
"@lytics/dev-agent-core": "workspace:*",
"chalk": "^5.3.0",
"ora": "^8.0.1"
},
"devDependencies": {
"typescript": "^5.3.3",
"commander": "^11.1.0"
"@types/node": "^22.0.0",
"commander": "^12.1.0",
"typescript": "^5.3.3"
}
}
75 changes: 75 additions & 0 deletions packages/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, expect, it } from 'vitest';
import { cleanCommand } from './commands/clean';
import { indexCommand } from './commands/index';
import { initCommand } from './commands/init';
import { searchCommand } from './commands/search';
import { statsCommand } from './commands/stats';
import { updateCommand } from './commands/update';

describe('CLI Structure', () => {
it('should have init command', () => {
expect(initCommand.name()).toBe('init');
expect(initCommand.description()).toContain('Initialize');
});

it('should have index command', () => {
expect(indexCommand.name()).toBe('index');
expect(indexCommand.description()).toContain('Index');
});

it('should have search command', () => {
expect(searchCommand.name()).toBe('search');
expect(searchCommand.description()).toContain('Search');
});

it('should have update command', () => {
expect(updateCommand.name()).toBe('update');
expect(updateCommand.description()).toContain('Update');
});

it('should have stats command', () => {
expect(statsCommand.name()).toBe('stats');
expect(statsCommand.description()).toContain('statistics');
});

it('should have clean command', () => {
expect(cleanCommand.name()).toBe('clean');
expect(cleanCommand.description()).toContain('Clean');
});

describe('Command Options', () => {
it('index command should have force and verbose options', () => {
const options = indexCommand.options;
const forceOption = options.find((opt) => opt.long === '--force');
const verboseOption = options.find((opt) => opt.long === '--verbose');

expect(forceOption).toBeDefined();
expect(verboseOption).toBeDefined();
});

it('search command should have limit, threshold, and json options', () => {
const options = searchCommand.options;
const limitOption = options.find((opt) => opt.long === '--limit');
const thresholdOption = options.find((opt) => opt.long === '--threshold');
const jsonOption = options.find((opt) => opt.long === '--json');

expect(limitOption).toBeDefined();
expect(thresholdOption).toBeDefined();
expect(jsonOption).toBeDefined();
});

it('stats command should have json option', () => {
const options = statsCommand.options;
const jsonOption = options.find((opt) => opt.long === '--json');

expect(jsonOption).toBeDefined();
});

it('clean command should have force option', () => {
const options = cleanCommand.options;
const forceOption = options.find((opt) => opt.long === '--force');

expect(forceOption).toBeDefined();
});
});
});
32 changes: 32 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node

import chalk from 'chalk';
import { Command } from 'commander';
import { cleanCommand } from './commands/clean.js';
import { indexCommand } from './commands/index.js';
import { initCommand } from './commands/init.js';
import { searchCommand } from './commands/search.js';
import { statsCommand } from './commands/stats.js';
import { updateCommand } from './commands/update.js';

const program = new Command();

program
.name('dev')
.description(chalk.cyan('🤖 Dev-Agent - Multi-agent code intelligence platform'))
.version('0.1.0');

// Register commands
program.addCommand(initCommand);
program.addCommand(indexCommand);
program.addCommand(searchCommand);
program.addCommand(updateCommand);
program.addCommand(statsCommand);
program.addCommand(cleanCommand);

// Show help if no command provided
if (process.argv.length === 2) {
program.outputHelp();
}

program.parse(process.argv);
80 changes: 80 additions & 0 deletions packages/cli/src/commands/clean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import chalk from 'chalk';
import { Command } from 'commander';
import ora from 'ora';
import { loadConfig } from '../utils/config.js';
import { logger } from '../utils/logger.js';

export const cleanCommand = new Command('clean')
.description('Clean indexed data and cache')
.option('-f, --force', 'Skip confirmation prompt', false)
.action(async (options) => {
try {
// Load config
const config = await loadConfig();
if (!config) {
logger.warn('No config found');
logger.log('Nothing to clean.');
return;
}

const dataDir = path.dirname(config.vectorStorePath);
const stateFile = path.join(config.repositoryPath, '.dev-agent', 'indexer-state.json');

// Show what will be deleted
logger.log('');
logger.log(chalk.bold('The following will be deleted:'));
logger.log(` ${chalk.cyan('Vector store:')} ${config.vectorStorePath}`);
logger.log(` ${chalk.cyan('State file:')} ${stateFile}`);
logger.log(` ${chalk.cyan('Data directory:')} ${dataDir}`);
logger.log('');

// Confirm unless --force
if (!options.force) {
logger.warn('This action cannot be undone!');
logger.log(`Run with ${chalk.yellow('--force')} to skip this prompt.`);
logger.log('');
process.exit(0);
}

const spinner = ora('Cleaning indexed data...').start();

// Delete vector store
try {
await fs.rm(config.vectorStorePath, { recursive: true, force: true });
spinner.text = 'Deleted vector store';
} catch (error) {
logger.debug(`Vector store not found or already deleted: ${error}`);
}

// Delete state file
try {
await fs.rm(stateFile, { force: true });
spinner.text = 'Deleted state file';
} catch (error) {
logger.debug(`State file not found or already deleted: ${error}`);
}

// Delete data directory if empty
try {
const files = await fs.readdir(dataDir);
if (files.length === 0) {
await fs.rmdir(dataDir);
spinner.text = 'Deleted data directory';
}
} catch (error) {
logger.debug(`Data directory not found or not empty: ${error}`);
}

spinner.succeed(chalk.green('Cleaned successfully!'));

logger.log('');
logger.log('All indexed data has been removed.');
logger.log(`Run ${chalk.yellow('dev index')} to re-index your repository.`);
logger.log('');
} catch (error) {
logger.error(`Failed to clean: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
});
Loading