@webdevtoday/grok-cli
Version:
A sophisticated CLI tool for interacting with xAI Grok 4, featuring conversation history, file reference, custom commands, memory system, and genetic development workflows
380 lines • 14 kB
JavaScript
"use strict";
/**
* File reference system for @ commands
* Provides file discovery, selection, and reference management
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileReferenceManager = void 0;
const globby_1 = require("globby");
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
/**
* File reference manager for @ command system
*/
class FileReferenceManager {
constructor(baseDir = process.cwd()) {
this.gitTrackedFiles = new Set();
this.fileCache = new Map();
this.lastCacheUpdate = 0;
this.cacheTimeout = 30000; // 30 seconds
this.baseDir = (0, path_1.resolve)(baseDir);
}
/**
* Search for files matching a pattern
*/
async searchFiles(query, options = {}) {
const { maxResults = 50, includeDirectories = false, extensions = [], sortBy = 'relevance' } = options;
// Update cache if needed
await this.updateCacheIfNeeded();
let files = Array.from(this.fileCache.values());
// Filter by type
if (!includeDirectories) {
files = files.filter(f => f.type === 'file');
}
// Filter by extensions
if (extensions.length > 0) {
const extSet = new Set(extensions.map(ext => ext.toLowerCase()));
files = files.filter(f => f.extension && extSet.has(f.extension.toLowerCase()));
}
// Filter by query
if (query) {
const queryLower = query.toLowerCase();
files = files.filter(f => {
const name = f.name.toLowerCase();
const path = f.relativePath.toLowerCase();
return name.includes(queryLower) ||
path.includes(queryLower) ||
this.fuzzyMatch(name, queryLower) ||
this.fuzzyMatch(path, queryLower);
});
}
// Sort files
files = this.sortFiles(files, sortBy, query);
// Limit results
return files.slice(0, maxResults);
}
/**
* Get file suggestions for autocomplete
*/
async getFileSuggestions(partial) {
// If partial contains path separators, treat as path
if (partial.includes('/') || partial.includes('\\')) {
return this.getPathSuggestions(partial);
}
// Otherwise, search by filename
return this.searchFiles(partial, {
maxResults: 20,
sortBy: 'relevance'
});
}
/**
* Get path-based suggestions
*/
async getPathSuggestions(partial) {
const normalizedPartial = partial.replace(/\\/g, '/');
const pathParts = normalizedPartial.split('/');
const directory = pathParts.slice(0, -1).join('/');
const filename = pathParts[pathParts.length - 1];
const searchDir = directory ? (0, path_1.resolve)(this.baseDir, directory) : this.baseDir;
try {
const entries = await (0, fs_extra_1.readdir)(searchDir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
if (filename && !entry.name.toLowerCase().includes(filename.toLowerCase())) {
continue;
}
const fullPath = (0, path_1.join)(searchDir, entry.name);
const relativePath = (0, path_1.relative)(this.baseDir, fullPath);
const stats = await (0, fs_extra_1.stat)(fullPath);
const extension = entry.isFile() ? (0, path_1.extname)(entry.name).slice(1) : '';
const fileRef = {
path: fullPath,
relativePath,
name: entry.name,
size: stats.size,
modified: stats.mtime,
type: entry.isDirectory() ? 'directory' : 'file',
isGitTracked: this.gitTrackedFiles.has(relativePath),
...(extension && { extension }),
};
files.push(fileRef);
}
return files.slice(0, 20);
}
catch {
return [];
}
}
/**
* Get file details by path
*/
async getFileDetails(path) {
const resolvedPath = (0, path_1.resolve)(this.baseDir, path);
const relativePath = (0, path_1.relative)(this.baseDir, resolvedPath);
try {
const stats = await (0, fs_extra_1.stat)(resolvedPath);
const extension = stats.isFile() ? (0, path_1.extname)(resolvedPath).slice(1) : '';
const fileRef = {
path: resolvedPath,
relativePath,
name: (0, path_1.basename)(resolvedPath),
size: stats.size,
modified: stats.mtime,
type: stats.isDirectory() ? 'directory' : 'file',
isGitTracked: this.gitTrackedFiles.has(relativePath),
...(extension && { extension }),
};
return fileRef;
}
catch {
return null;
}
}
/**
* Get recently modified files
*/
async getRecentFiles(limit = 20) {
await this.updateCacheIfNeeded();
return Array.from(this.fileCache.values())
.filter(f => f.type === 'file')
.sort((a, b) => b.modified.getTime() - a.modified.getTime())
.slice(0, limit);
}
/**
* Get git tracked files
*/
async getGitTrackedFiles() {
await this.updateGitTrackedFiles();
return Array.from(this.fileCache.values())
.filter(f => f.isGitTracked);
}
/**
* Get files by extension
*/
async getFilesByExtension(extension) {
await this.updateCacheIfNeeded();
const ext = extension.toLowerCase();
return Array.from(this.fileCache.values())
.filter(f => f.extension?.toLowerCase() === ext);
}
/**
* Get commonly used file extensions in project
*/
async getCommonExtensions() {
await this.updateCacheIfNeeded();
const extensionCounts = new Map();
for (const file of this.fileCache.values()) {
if (file.extension && file.type === 'file') {
const ext = file.extension.toLowerCase();
extensionCounts.set(ext, (extensionCounts.get(ext) || 0) + 1);
}
}
return Array.from(extensionCounts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 20)
.map(([ext]) => ext);
}
/**
* Update file cache if needed
*/
async updateCacheIfNeeded() {
const now = Date.now();
if (now - this.lastCacheUpdate < this.cacheTimeout) {
return;
}
await this.updateFileCache();
await this.updateGitTrackedFiles();
this.lastCacheUpdate = now;
}
/**
* Update the file cache
*/
async updateFileCache() {
try {
const patterns = [
'**/*',
'!node_modules/**',
'!.git/**',
'!dist/**',
'!build/**',
'!coverage/**',
'!.next/**',
'!.nuxt/**',
'!.vscode/**',
'!.idea/**',
];
const files = await (0, globby_1.globby)(patterns, {
cwd: this.baseDir,
absolute: true,
onlyFiles: false,
});
this.fileCache.clear();
for (const filePath of files) {
const stats = await (0, fs_extra_1.stat)(filePath);
const relativePath = (0, path_1.relative)(this.baseDir, filePath);
const extension = stats.isFile() ? (0, path_1.extname)(filePath).slice(1) : '';
const fileRef = {
path: filePath,
relativePath,
name: (0, path_1.basename)(filePath),
size: stats.size,
modified: stats.mtime,
type: stats.isDirectory() ? 'directory' : 'file',
isGitTracked: false, // Will be updated separately
...(extension && { extension }),
};
this.fileCache.set(relativePath, fileRef);
}
}
catch (error) {
console.error('Failed to update file cache:', error);
}
}
/**
* Update git tracked files
*/
async updateGitTrackedFiles() {
try {
const { execa } = await Promise.resolve().then(() => __importStar(require('execa')));
const result = await execa('git', ['ls-files'], {
cwd: this.baseDir,
reject: false,
});
this.gitTrackedFiles.clear();
if (result.exitCode === 0) {
const files = result.stdout.split('\n').filter(Boolean);
for (const file of files) {
this.gitTrackedFiles.add(file);
// Update cache entry if it exists
const cached = this.fileCache.get(file);
if (cached) {
cached.isGitTracked = true;
}
}
}
}
catch {
// Git not available or not in a git repo
}
}
/**
* Fuzzy matching for file names
*/
fuzzyMatch(text, pattern) {
const textLower = text.toLowerCase();
const patternLower = pattern.toLowerCase();
let patternIndex = 0;
for (let i = 0; i < textLower.length && patternIndex < patternLower.length; i++) {
if (textLower[i] === patternLower[patternIndex]) {
patternIndex++;
}
}
return patternIndex === patternLower.length;
}
/**
* Sort files based on criteria
*/
sortFiles(files, sortBy, query) {
switch (sortBy) {
case 'name':
return files.sort((a, b) => a.name.localeCompare(b.name));
case 'modified':
return files.sort((a, b) => b.modified.getTime() - a.modified.getTime());
case 'size':
return files.sort((a, b) => b.size - a.size);
case 'relevance':
default:
if (!query) {
// Default to modified time if no query
return files.sort((a, b) => b.modified.getTime() - a.modified.getTime());
}
return files.sort((a, b) => {
const aScore = this.calculateRelevanceScore(a, query);
const bScore = this.calculateRelevanceScore(b, query);
return bScore - aScore;
});
}
}
/**
* Calculate relevance score for file matching
*/
calculateRelevanceScore(file, query) {
const queryLower = query.toLowerCase();
const nameLower = file.name.toLowerCase();
const pathLower = file.relativePath.toLowerCase();
let score = 0;
// Exact name match gets highest score
if (nameLower === queryLower)
score += 100;
// Name starts with query
if (nameLower.startsWith(queryLower))
score += 50;
// Name contains query
if (nameLower.includes(queryLower))
score += 25;
// Path contains query
if (pathLower.includes(queryLower))
score += 10;
// Fuzzy match
if (this.fuzzyMatch(nameLower, queryLower))
score += 5;
// Git tracked files get slight boost
if (file.isGitTracked)
score += 2;
// Recent files get slight boost
const daysSinceModified = (Date.now() - file.modified.getTime()) / (1000 * 60 * 60 * 24);
if (daysSinceModified < 7)
score += 3;
if (daysSinceModified < 1)
score += 2;
// Common development file extensions get boost
const importantExtensions = ['ts', 'js', 'py', 'md', 'json', 'yaml', 'yml'];
if (file.extension && importantExtensions.includes(file.extension.toLowerCase())) {
score += 1;
}
return score;
}
/**
* Set base directory
*/
setBaseDirectory(dir) {
this.baseDir = (0, path_1.resolve)(dir);
this.fileCache.clear();
this.lastCacheUpdate = 0;
}
}
exports.FileReferenceManager = FileReferenceManager;
//# sourceMappingURL=file-reference.js.map