Skip to content

Commit 66599f4

Browse files
committed
feat(cli): enhance compact and clean command output
- Remove logger timestamps from compact and clean commands - Add before/after comparison table for compact operations - Show file sizes and total storage to be removed in clean - Display optimization savings and performance metrics - Consistent styling with dev stats and dev mcp commands Output Improvements: - compact: Table comparing vectors, storage size, fragments with change percentages - clean: List of files with sizes, total to remove, clear warnings - Both commands now follow hybrid output style (docker + gh CLI inspired) Total logger calls removed: ~27 (12 from compact, 15 from clean) Affects: temp/cli-output-improvements-plan.md (Phases 3-4 complete)
1 parent d1c1e5a commit 66599f4

File tree

3 files changed

+194
-42
lines changed

3 files changed

+194
-42
lines changed

packages/cli/src/commands/clean.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import chalk from 'chalk';
99
import { Command } from 'commander';
1010
import ora from 'ora';
1111
import { loadConfig } from '../utils/config.js';
12+
import { formatBytes, getDirectorySize } from '../utils/file.js';
1213
import { logger } from '../utils/logger.js';
14+
import { output, printCleanSuccess, printCleanSummary } from '../utils/output.js';
1315

1416
export const cleanCommand = new Command('clean')
1517
.description('Clean indexed data and cache')
@@ -33,21 +35,35 @@ export const cleanCommand = new Command('clean')
3335
await ensureStorageDirectory(storagePath);
3436
const filePaths = getStorageFilePaths(storagePath);
3537

38+
// Calculate sizes of files to be deleted
39+
const files = await Promise.all(
40+
[
41+
{ name: 'Vector store', path: filePaths.vectors },
42+
{ name: 'Indexer state', path: filePaths.indexerState },
43+
{ name: 'GitHub state', path: filePaths.githubState },
44+
{ name: 'Metadata', path: filePaths.metadata },
45+
].map(async (file) => {
46+
try {
47+
const stat = await fs.stat(file.path);
48+
const size = stat.isDirectory() ? await getDirectorySize(file.path) : stat.size;
49+
return { ...file, size };
50+
} catch {
51+
return { ...file, size: null };
52+
}
53+
})
54+
);
55+
56+
const totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0);
57+
3658
// Show what will be deleted
37-
logger.log('');
38-
logger.log(chalk.bold('The following will be deleted:'));
39-
logger.log(` ${chalk.cyan('Storage directory:')} ${storagePath}`);
40-
logger.log(` ${chalk.cyan('Vector store:')} ${filePaths.vectors}`);
41-
logger.log(` ${chalk.cyan('State file:')} ${filePaths.indexerState}`);
42-
logger.log(` ${chalk.cyan('GitHub state:')} ${filePaths.githubState}`);
43-
logger.log(` ${chalk.cyan('Metadata:')} ${filePaths.metadata}`);
44-
logger.log('');
59+
printCleanSummary({
60+
files,
61+
totalSize,
62+
force: options.force,
63+
});
4564

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

@@ -56,17 +72,14 @@ export const cleanCommand = new Command('clean')
5672
// Delete storage directory (contains all index files)
5773
try {
5874
await fs.rm(storagePath, { recursive: true, force: true });
59-
spinner.succeed(chalk.green('Cleaned successfully!'));
75+
spinner.succeed('Cleaned successfully');
76+
77+
printCleanSuccess({ totalSize });
6078
} catch (error) {
6179
spinner.fail('Failed to clean');
62-
logger.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
80+
output.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
6381
process.exit(1);
6482
}
65-
66-
logger.log('');
67-
logger.log('All indexed data has been removed.');
68-
logger.log(`Run ${chalk.yellow('dev index')} to re-index your repository.`);
69-
logger.log('');
7083
} catch (error) {
7184
logger.error(`Failed to clean: ${error instanceof Error ? error.message : String(error)}`);
7285
process.exit(1);

packages/cli/src/commands/compact.ts

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Command } from 'commander';
1010
import ora from 'ora';
1111
import { loadConfig } from '../utils/config.js';
1212
import { logger } from '../utils/logger.js';
13+
import { output, printCompactResults } from '../utils/output.js';
1314

1415
export const compactCommand = new Command('compact')
1516
.description('🗜️ Optimize and compact the vector store')
@@ -65,36 +66,25 @@ export const compactCommand = new Command('compact')
6566
// @ts-expect-error - accessing private property for optimization
6667
await indexer.vectorStorage.optimize();
6768

68-
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
69+
const duration = (Date.now() - startTime) / 1000;
6970

7071
// Get stats after optimization
7172
const statsAfter = await indexer.getStats();
7273

7374
await indexer.close();
7475

75-
spinner.succeed(chalk.green('Vector store optimized successfully!'));
76-
77-
// Show results
78-
logger.log('');
79-
logger.log(chalk.bold('Optimization Results:'));
80-
logger.log(` ${chalk.cyan('Duration:')} ${duration}s`);
81-
logger.log(` ${chalk.cyan('Total documents:')} ${statsAfter?.vectorsStored || 0}`);
82-
83-
if (options.verbose) {
84-
logger.log('');
85-
logger.log(chalk.bold('Before Optimization:'));
86-
logger.log(` ${chalk.cyan('Storage size:')} ${statsBefore.vectorsStored} vectors`);
87-
logger.log('');
88-
logger.log(chalk.bold('After Optimization:'));
89-
logger.log(` ${chalk.cyan('Storage size:')} ${statsAfter?.vectorsStored || 0} vectors`);
90-
}
91-
92-
logger.log('');
93-
logger.log(
94-
chalk.gray(
95-
'Optimization merges small data fragments, updates indices, and improves query performance.'
96-
)
97-
);
76+
spinner.succeed('Vector store optimized');
77+
78+
// Show results using new formatter
79+
printCompactResults({
80+
duration,
81+
before: {
82+
vectors: statsBefore.vectorsStored,
83+
},
84+
after: {
85+
vectors: statsAfter?.vectorsStored || 0,
86+
},
87+
});
9888
} catch (error) {
9989
spinner.fail('Failed to optimize vector store');
10090
logger.error(error instanceof Error ? error.message : String(error));

packages/cli/src/utils/output.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,155 @@ export function printMcpUninstallSuccess(data: {
712712
output.log();
713713
}
714714

715+
/**
716+
* Print compact/optimization results
717+
*/
718+
export function printCompactResults(data: {
719+
duration: number;
720+
before: {
721+
vectors: number;
722+
size?: number;
723+
fragments?: number;
724+
};
725+
after: {
726+
vectors: number;
727+
size?: number;
728+
fragments?: number;
729+
};
730+
}): void {
731+
const { duration, before, after } = data;
732+
733+
output.log();
734+
output.log(chalk.bold('Optimization Complete'));
735+
output.log();
736+
737+
// Create comparison table
738+
const table = new Table({
739+
head: [chalk.cyan('Metric'), chalk.cyan('Before'), chalk.cyan('After'), chalk.cyan('Change')],
740+
style: {
741+
head: [],
742+
border: ['gray'],
743+
},
744+
colAligns: ['left', 'right', 'right', 'right'],
745+
});
746+
747+
// Vectors row (should be unchanged)
748+
const vectorChange = after.vectors - before.vectors;
749+
const vectorChangeStr =
750+
vectorChange === 0
751+
? chalk.gray('—')
752+
: vectorChange > 0
753+
? chalk.green(`+${vectorChange}`)
754+
: chalk.red(`${vectorChange}`);
755+
table.push([
756+
'Vectors',
757+
formatNumber(before.vectors),
758+
formatNumber(after.vectors),
759+
vectorChangeStr,
760+
]);
761+
762+
// Storage size row (if available)
763+
if (before.size && after.size) {
764+
const sizeChange = after.size - before.size;
765+
const sizeChangePercent = before.size > 0 ? (sizeChange / before.size) * 100 : 0;
766+
const sizeChangeStr =
767+
sizeChange < 0
768+
? chalk.green(`${(sizeChangePercent).toFixed(1)}%`)
769+
: sizeChange > 0
770+
? chalk.red(`+${sizeChangePercent.toFixed(1)}%`)
771+
: chalk.gray('—');
772+
773+
table.push(['Storage Size', formatBytes(before.size), formatBytes(after.size), sizeChangeStr]);
774+
}
775+
776+
// Fragments row (if available)
777+
if (before.fragments !== undefined && after.fragments !== undefined) {
778+
const fragmentChange = after.fragments - before.fragments;
779+
const fragmentChangePercent =
780+
before.fragments > 0 ? (fragmentChange / before.fragments) * 100 : 0;
781+
const fragmentChangeStr =
782+
fragmentChange < 0
783+
? chalk.green(`${fragmentChangePercent.toFixed(1)}%`)
784+
: fragmentChange > 0
785+
? chalk.red(`+${fragmentChangePercent.toFixed(1)}%`)
786+
: chalk.gray('—');
787+
788+
table.push([
789+
'Fragments',
790+
formatNumber(before.fragments),
791+
formatNumber(after.fragments),
792+
fragmentChangeStr,
793+
]);
794+
}
795+
796+
output.log(table.toString());
797+
output.log();
798+
output.log(`✓ Completed in ${duration.toFixed(2)}s`);
799+
800+
// Show savings if any
801+
if (before.size && after.size && after.size < before.size) {
802+
const saved = before.size - after.size;
803+
output.log(`💾 Saved ${chalk.green(formatBytes(saved))}`);
804+
}
805+
806+
output.log();
807+
output.log(
808+
chalk.gray(
809+
'Optimization merged small data fragments and updated indices for better query performance.'
810+
)
811+
);
812+
output.log();
813+
}
814+
815+
/**
816+
* Print clean summary
817+
*/
818+
export function printCleanSummary(data: {
819+
files: Array<{
820+
name: string;
821+
path: string;
822+
size: number | null;
823+
}>;
824+
totalSize: number;
825+
force: boolean;
826+
}): void {
827+
const { files, totalSize, force } = data;
828+
829+
output.log();
830+
output.log(chalk.bold('This will remove:'));
831+
output.log();
832+
833+
for (const file of files) {
834+
const size = file.size !== null ? chalk.gray(`(${formatBytes(file.size)})`) : '';
835+
output.log(` ${chalk.cyan('•')} ${file.name} ${size}`);
836+
}
837+
838+
output.log();
839+
output.log(`Total to remove: ${chalk.bold(formatBytes(totalSize))}`);
840+
output.log();
841+
842+
if (!force) {
843+
output.warn('This action cannot be undone!');
844+
output.log(`Run with ${chalk.yellow('--force')} to skip this prompt.`);
845+
output.log();
846+
}
847+
}
848+
849+
/**
850+
* Print clean success
851+
*/
852+
export function printCleanSuccess(data: { totalSize: number }): void {
853+
const { totalSize } = data;
854+
855+
output.log();
856+
output.log(chalk.green('✓ All indexed data removed'));
857+
output.log();
858+
output.log(`Freed ${chalk.bold(formatBytes(totalSize))}`);
859+
output.log();
860+
output.log(`Run ${chalk.cyan('dev index')} to re-index your repository`);
861+
output.log();
862+
}
863+
715864
/**
716865
* Print GitHub indexing statistics (gh CLI inspired)
717866
*/

0 commit comments

Comments
 (0)