@optiqcode/cli 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +96 -81
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -6,13 +6,12 @@ import ora from 'ora';
|
|
|
6
6
|
import chokidar from 'chokidar';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
|
-
import logUpdate from 'log-update';
|
|
10
9
|
import { getConfig, saveConfig } from './utils/config.js';
|
|
11
10
|
import { isValidDirectory, getGitIgnorePatterns, shouldIgnoreFile } from './utils/files.js';
|
|
12
11
|
const BACKEND_URL = process.env.OPTIQ_BACKEND_URL || 'https://api.optiqcode.com';
|
|
13
12
|
async function showBanner() {
|
|
14
13
|
console.clear();
|
|
15
|
-
console.log(chalk.
|
|
14
|
+
console.log(chalk.white.bold(`
|
|
16
15
|
___ _ _
|
|
17
16
|
/ _ \\ _ __ | |_(_) __ _
|
|
18
17
|
| | | | '_ \\| __| |/ _\` |
|
|
@@ -21,7 +20,7 @@ async function showBanner() {
|
|
|
21
20
|
|_| |_|
|
|
22
21
|
`));
|
|
23
22
|
console.log(chalk.gray(' AI-powered code indexing & search'));
|
|
24
|
-
console.log(chalk.gray('
|
|
23
|
+
console.log(chalk.gray(' v2.0.0\n'));
|
|
25
24
|
}
|
|
26
25
|
function showHelp() {
|
|
27
26
|
console.log(chalk.cyan.bold('Optiq CLI') + chalk.gray(' - AI-powered code indexing\n'));
|
|
@@ -260,36 +259,35 @@ async function indexOnce(targetPath, config) {
|
|
|
260
259
|
try {
|
|
261
260
|
const files = await collectFiles(targetPath);
|
|
262
261
|
spinner.text = `Reading ${files.length} files...`;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
// Read files in parallel (100 at a time)
|
|
263
|
+
const PARALLEL_READS = 100;
|
|
264
|
+
const filesArray = [];
|
|
265
|
+
for (let i = 0; i < files.length; i += PARALLEL_READS) {
|
|
266
|
+
const chunk = files.slice(i, i + PARALLEL_READS);
|
|
267
|
+
const results = await Promise.allSettled(chunk.map(async (file) => {
|
|
267
268
|
const content = await fs.readFile(file, 'utf-8');
|
|
268
269
|
const relativePath = path.relative(targetPath, file).replace(/\\/g, '/');
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
return { path: relativePath, content };
|
|
271
|
+
}));
|
|
272
|
+
// Filter successful reads
|
|
273
|
+
for (const result of results) {
|
|
274
|
+
if (result.status === 'fulfilled') {
|
|
275
|
+
filesArray.push(result.value);
|
|
273
276
|
}
|
|
274
277
|
}
|
|
275
|
-
|
|
276
|
-
// Skip unreadable files
|
|
277
|
-
}
|
|
278
|
+
spinner.text = `Reading... ${Math.min(i + PARALLEL_READS, files.length)}/${files.length}`;
|
|
278
279
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
content,
|
|
282
|
-
}));
|
|
283
|
-
// Batch upload
|
|
284
|
-
const BATCH_SIZE = 50;
|
|
280
|
+
// Upload in larger batches (150 files per batch)
|
|
281
|
+
const BATCH_SIZE = 150;
|
|
285
282
|
let totalFiles = 0;
|
|
286
283
|
let totalEntities = 0;
|
|
287
284
|
let repoId = '';
|
|
285
|
+
spinner.text = 'Uploading...';
|
|
288
286
|
for (let i = 0; i < filesArray.length; i += BATCH_SIZE) {
|
|
289
287
|
const batch = filesArray.slice(i, i + BATCH_SIZE);
|
|
290
288
|
const batchNum = Math.floor(i / BATCH_SIZE) + 1;
|
|
291
289
|
const totalBatches = Math.ceil(filesArray.length / BATCH_SIZE);
|
|
292
|
-
spinner.text = `Uploading...
|
|
290
|
+
spinner.text = `Uploading... ${batchNum}/${totalBatches}`;
|
|
293
291
|
const response = await axios.post(`${BACKEND_URL}/api/nexus/index/content`, {
|
|
294
292
|
repository_path: targetPath,
|
|
295
293
|
files: batch,
|
|
@@ -316,7 +314,7 @@ async function indexOnce(targetPath, config) {
|
|
|
316
314
|
console.log(chalk.gray('\n Use this ID with the MCP server\n'));
|
|
317
315
|
}
|
|
318
316
|
catch (error) {
|
|
319
|
-
spinner.fail(chalk.red('
|
|
317
|
+
spinner.fail(chalk.red('Failed'));
|
|
320
318
|
if (error.response?.data?.error) {
|
|
321
319
|
console.log(chalk.gray(' ' + error.response.data.error));
|
|
322
320
|
}
|
|
@@ -349,39 +347,35 @@ async function watchDirectory(targetPath, config) {
|
|
|
349
347
|
}
|
|
350
348
|
// Only do full index if repo doesn't exist
|
|
351
349
|
if (!repoId) {
|
|
352
|
-
const spinner = ora({ text: 'Collecting files...', color: '
|
|
350
|
+
const spinner = ora({ text: 'Collecting files...', color: 'cyan' }).start();
|
|
353
351
|
try {
|
|
354
352
|
const files = await collectFiles(targetPath);
|
|
355
|
-
spinner.text = `
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
353
|
+
spinner.text = `Reading ${files.length} files...`;
|
|
354
|
+
// Read files in parallel (100 at a time)
|
|
355
|
+
const PARALLEL_READS = 100;
|
|
356
|
+
const filesArray = [];
|
|
357
|
+
for (let i = 0; i < files.length; i += PARALLEL_READS) {
|
|
358
|
+
const chunk = files.slice(i, i + PARALLEL_READS);
|
|
359
|
+
const results = await Promise.allSettled(chunk.map(async (file) => {
|
|
360
360
|
const content = await fs.readFile(file, 'utf-8');
|
|
361
|
-
const relativePath = path.relative(targetPath, file);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
catch (error) {
|
|
369
|
-
// Skip files that can't be read
|
|
361
|
+
const relativePath = path.relative(targetPath, file).replace(/\\/g, '/');
|
|
362
|
+
return { path: relativePath, content };
|
|
363
|
+
}));
|
|
364
|
+
for (const result of results) {
|
|
365
|
+
if (result.status === 'fulfilled') {
|
|
366
|
+
filesArray.push(result.value);
|
|
367
|
+
}
|
|
370
368
|
}
|
|
369
|
+
spinner.text = `Reading... ${Math.min(i + PARALLEL_READS, files.length)}/${files.length}`;
|
|
371
370
|
}
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
path,
|
|
375
|
-
content,
|
|
376
|
-
}));
|
|
377
|
-
// Batch upload files (50 at a time to avoid 413 errors)
|
|
378
|
-
const BATCH_SIZE = 50;
|
|
371
|
+
// Upload in larger batches
|
|
372
|
+
const BATCH_SIZE = 150;
|
|
379
373
|
let uploadedCount = 0;
|
|
380
374
|
for (let i = 0; i < filesArray.length; i += BATCH_SIZE) {
|
|
381
375
|
const batch = filesArray.slice(i, i + BATCH_SIZE);
|
|
382
376
|
const batchNum = Math.floor(i / BATCH_SIZE) + 1;
|
|
383
377
|
const totalBatches = Math.ceil(filesArray.length / BATCH_SIZE);
|
|
384
|
-
spinner.text = `Uploading
|
|
378
|
+
spinner.text = `Uploading... ${batchNum}/${totalBatches}`;
|
|
385
379
|
const response = await axios.post(`${BACKEND_URL}/api/nexus/index/content`, {
|
|
386
380
|
repository_path: targetPath,
|
|
387
381
|
files: batch,
|
|
@@ -390,10 +384,10 @@ async function watchDirectory(targetPath, config) {
|
|
|
390
384
|
'X-API-Key': config.apiKey,
|
|
391
385
|
'Content-Type': 'application/json',
|
|
392
386
|
},
|
|
393
|
-
timeout: 0,
|
|
387
|
+
timeout: 0,
|
|
394
388
|
});
|
|
395
389
|
if (!response.data.success) {
|
|
396
|
-
spinner.fail(chalk.
|
|
390
|
+
spinner.fail(chalk.red('Failed'));
|
|
397
391
|
console.log(chalk.gray(response.data.error || 'Unknown error'));
|
|
398
392
|
return;
|
|
399
393
|
}
|
|
@@ -446,43 +440,49 @@ async function watchDirectory(targetPath, config) {
|
|
|
446
440
|
return `${minutes}m ${seconds % 60}s`;
|
|
447
441
|
return `${seconds}s`;
|
|
448
442
|
};
|
|
449
|
-
|
|
443
|
+
let dashboardShown = false;
|
|
444
|
+
const showDashboard = () => {
|
|
450
445
|
const uptime = formatUptime(Date.now() - sessionStartTime);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
446
|
+
// Move cursor up 5 lines to overwrite previous dashboard (but not on first display)
|
|
447
|
+
if (dashboardShown) {
|
|
448
|
+
process.stdout.write('\x1b[5A'); // Move up 5 lines
|
|
449
|
+
process.stdout.write('\x1b[0J'); // Clear from cursor to end of screen
|
|
450
|
+
}
|
|
451
|
+
dashboardShown = true;
|
|
452
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
453
|
+
console.log(chalk.cyan('👀 Watching') + chalk.gray(` • ${uptime} uptime • Press Ctrl+C to stop`));
|
|
454
|
+
const details = [];
|
|
455
|
+
if (totalIndexed > 0 && lastIndexedFile) {
|
|
456
|
+
const timeSinceLastIndex = Math.floor((Date.now() - lastIndexedTime) / 1000);
|
|
457
457
|
const timeStr = timeSinceLastIndex < 60 ? `${timeSinceLastIndex}s` : `${Math.floor(timeSinceLastIndex / 60)}m`;
|
|
458
|
-
|
|
458
|
+
details.push(chalk.gray(` Indexed: ${totalIndexed} changes • ${allIndexedFiles.size} files • Last: ${lastIndexedFile} (${timeStr} ago)`));
|
|
459
459
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
460
|
+
else {
|
|
461
|
+
details.push(chalk.gray(` Ready • 0 changes • Waiting for file modifications...`));
|
|
462
|
+
}
|
|
463
|
+
console.log(details.join(''));
|
|
464
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
465
|
+
console.log('');
|
|
463
466
|
};
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
467
|
+
// Initial dashboard display
|
|
468
|
+
showDashboard();
|
|
469
|
+
// Update dashboard every 10 seconds to refresh uptime and "time ago"
|
|
470
|
+
setInterval(showDashboard, 10000);
|
|
467
471
|
const processChanges = async () => {
|
|
468
472
|
if (pendingChanges.size === 0 || isProcessing)
|
|
469
473
|
return;
|
|
470
474
|
isProcessing = true;
|
|
471
475
|
const changes = Array.from(pendingChanges.entries());
|
|
472
476
|
pendingChanges.clear();
|
|
473
|
-
// Collect unique files first
|
|
474
|
-
for (const [filePath] of changes) {
|
|
475
|
-
const relativePath = path.relative(targetPath, filePath);
|
|
476
|
-
allIndexedFiles.add(relativePath);
|
|
477
|
-
}
|
|
478
477
|
try {
|
|
479
478
|
const filesArray = [];
|
|
480
479
|
let hasChanges = false;
|
|
481
480
|
for (const [filePath, changeType] of changes) {
|
|
482
|
-
const relativePath = path.relative(targetPath, filePath);
|
|
481
|
+
const relativePath = path.relative(targetPath, filePath).replace(/\\/g, '/');
|
|
483
482
|
if (changeType === 'unlink') {
|
|
484
483
|
filesArray.push({ path: relativePath, content: null });
|
|
485
484
|
fileContentCache.delete(relativePath);
|
|
485
|
+
allIndexedFiles.delete(relativePath);
|
|
486
486
|
hasChanges = true;
|
|
487
487
|
}
|
|
488
488
|
else {
|
|
@@ -518,6 +518,12 @@ async function watchDirectory(targetPath, config) {
|
|
|
518
518
|
timeout: 60000,
|
|
519
519
|
});
|
|
520
520
|
if (response.data.success) {
|
|
521
|
+
// Add successfully indexed files to the set
|
|
522
|
+
for (const file of filesArray) {
|
|
523
|
+
if (file.content !== null) {
|
|
524
|
+
allIndexedFiles.add(file.path);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
521
527
|
totalIndexed += filesArray.length;
|
|
522
528
|
lastIndexedTime = Date.now();
|
|
523
529
|
// Update last indexed file
|
|
@@ -528,20 +534,24 @@ async function watchDirectory(targetPath, config) {
|
|
|
528
534
|
lastIndexedFile = `${filesArray.length} files`;
|
|
529
535
|
}
|
|
530
536
|
// Update dashboard
|
|
531
|
-
|
|
537
|
+
showDashboard();
|
|
532
538
|
}
|
|
533
539
|
else {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
540
|
+
// Clear dashboard, show error, redraw dashboard
|
|
541
|
+
process.stdout.write('\x1b[5A');
|
|
542
|
+
process.stdout.write('\x1b[0J');
|
|
543
|
+
dashboardShown = false;
|
|
544
|
+
console.log(chalk.red(`✗ Failed to index: ${response.data.error}\n`));
|
|
545
|
+
showDashboard();
|
|
538
546
|
}
|
|
539
547
|
}
|
|
540
548
|
catch (error) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
549
|
+
// Clear dashboard, show error, redraw dashboard
|
|
550
|
+
process.stdout.write('\x1b[5A');
|
|
551
|
+
process.stdout.write('\x1b[0J');
|
|
552
|
+
dashboardShown = false;
|
|
553
|
+
console.log(chalk.red(`✗ Failed to index: ${error.response?.data?.error || error.message}\n`));
|
|
554
|
+
showDashboard();
|
|
545
555
|
}
|
|
546
556
|
isProcessing = false;
|
|
547
557
|
// Check if there are more pending changes and process them
|
|
@@ -569,13 +579,18 @@ async function watchDirectory(targetPath, config) {
|
|
|
569
579
|
scheduleProcess();
|
|
570
580
|
})
|
|
571
581
|
.on('error', (error) => {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
582
|
+
// Clear dashboard, show error, redraw dashboard
|
|
583
|
+
process.stdout.write('\x1b[5A');
|
|
584
|
+
process.stdout.write('\x1b[0J');
|
|
585
|
+
dashboardShown = false;
|
|
586
|
+
console.log(chalk.red(`✗ Watcher error: ${error.message}\n`));
|
|
587
|
+
showDashboard();
|
|
575
588
|
});
|
|
576
589
|
process.on('SIGINT', () => {
|
|
577
|
-
|
|
578
|
-
|
|
590
|
+
// Clear dashboard and show exit message
|
|
591
|
+
process.stdout.write('\x1b[5A');
|
|
592
|
+
process.stdout.write('\x1b[0J');
|
|
593
|
+
console.log(chalk.cyan('✓ Stopped watching\n'));
|
|
579
594
|
watcher.close();
|
|
580
595
|
process.exit(0);
|
|
581
596
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optiqcode/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CLI tool for Optiq - automatic code indexing and context engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
"axios": "^1.6.0",
|
|
43
43
|
"chalk": "^5.3.0",
|
|
44
44
|
"chokidar": "^3.5.3",
|
|
45
|
-
"log-update": "^7.0.1",
|
|
46
45
|
"ora": "^8.0.1",
|
|
47
46
|
"prompts": "^2.4.2"
|
|
48
47
|
},
|