ai-context-commit-tools
Version:
AI context builder with automated commit message generation and changelog maintenance for enhanced AI-assisted development
205 lines (204 loc) • 7.85 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.CursorClient = void 0;
const tslib_1 = require("tslib");
const child_process_1 = require("child_process");
const os = tslib_1.__importStar(require("os"));
const path = tslib_1.__importStar(require("path"));
class CursorClient {
constructor(options = {}) {
this.options = {
debugMode: false,
timeout: 30000,
model: 'sonnet-4',
maxRetries: 2,
retryDelay: 2000,
retryMultiplier: 2,
...options,
};
this.cursorPath = this.findCursorPath();
}
async generateResponse(prompt) {
if (!this.cursorPath) {
return {
success: false,
error: 'Cursor CLI not found. Please install Cursor CLI for AI features.',
};
}
const maxRetries = this.options.maxRetries || 2;
let lastError = '';
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
if (attempt > 1) {
const retryMessage = this.options.debugMode
? `🔄 Retry attempt ${attempt - 1}/${maxRetries}`
: `🔄 Retrying AI generation (${attempt - 1}/${maxRetries})...`;
this.log(retryMessage);
}
const result = await this.attemptGeneration(prompt, attempt);
if (result.success) {
if (this.options.debugMode && attempt > 1) {
this.log(`✅ Success on attempt ${attempt}`);
}
return result;
}
lastError = result.error || 'Unknown error';
if (!this.isRetryableError(lastError) || attempt === maxRetries + 1) {
return result;
}
const baseDelay = this.options.retryDelay || 2000;
const multiplier = this.options.retryMultiplier || 2;
const delay = Math.min(baseDelay * Math.pow(multiplier, attempt - 1), 10000);
if (this.options.debugMode) {
this.log(`⏳ Waiting ${delay}ms before retry...`);
}
await new Promise(resolve => setTimeout(resolve, delay));
}
return {
success: false,
error: lastError,
};
}
async attemptGeneration(prompt, attempt) {
return new Promise(resolve => {
try {
const fs = require('fs');
const tempFile = `/tmp/cursor-prompt-ai-${Date.now()}-${attempt}.txt`;
fs.writeFileSync(tempFile, prompt, 'utf8');
const currentModel = 'sonnet-4';
const command = this.cursorPath === 'cursor-agent'
? `cursor-agent --print --model ${currentModel} --output-format text < "${tempFile}"`
: `"${this.cursorPath}" --print --model ${currentModel} --output-format text < "${tempFile}"`;
if (this.options.debugMode) {
this.log('=== CURSOR COMMAND ===');
this.log(command);
if (attempt > 1) {
this.log(`🔄 Trying model: ${currentModel} (attempt ${attempt})`);
}
this.log('=== PROMPT ===');
this.log(`${prompt.substring(0, 500)}...`);
this.log('=== END DEBUG ===\n');
}
const timeout = attempt === 1 ? this.options.timeout * 1.5 : this.options.timeout;
(0, child_process_1.exec)(command, {
timeout,
shell: '/bin/bash',
maxBuffer: 1024 * 1024,
}, (error, stdout, _stderr) => {
try {
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
}
catch (cleanupError) {
}
if (error) {
let errorMessage = `Cursor CLI execution failed: ${error.message}`;
if (error.message.includes('Cannot use this model')) {
errorMessage += '\n\nAvailable models: auto, sonnet-4, gpt-5, opus-4.1, grok';
errorMessage += '\nTry using --model auto or --model sonnet-4';
}
resolve({
success: false,
error: errorMessage,
});
return;
}
try {
const cleanResponse = stdout.replace(/\x1b\[[0-9;]*m/g, '').trim();
if (!cleanResponse) {
resolve({
success: false,
error: 'Cursor CLI returned empty response',
});
return;
}
resolve({
success: true,
message: cleanResponse,
});
}
catch (parseError) {
resolve({
success: false,
error: `Failed to parse Cursor response: ${parseError}`,
});
}
});
}
catch (setupError) {
resolve({
success: false,
error: `Failed to setup Cursor CLI call: ${setupError instanceof Error ? setupError.message : String(setupError)}`,
});
}
});
}
isRetryableError(errorMessage) {
const retryablePatterns = [
'timeout',
'etimedout',
'econnreset',
'econnrefused',
'enotfound',
'network',
'connection',
'temporarily unavailable',
'service unavailable',
'internal server error',
'gateway timeout',
'bad gateway',
'command failed',
'cursor cli execution failed',
'execution error',
'spawn',
'no such file',
];
const lowerErrorMessage = errorMessage.toLowerCase();
return retryablePatterns.some(pattern => lowerErrorMessage.includes(pattern));
}
isAvailable() {
return !!this.cursorPath;
}
async getVersion() {
if (!this.cursorPath) {
return null;
}
return new Promise(resolve => {
(0, child_process_1.exec)(`${this.cursorPath} --version`, (error, stdout) => {
if (error) {
resolve(null);
return;
}
resolve(stdout.trim());
});
});
}
findCursorPath() {
const possiblePaths = [
path.join(os.homedir(), '.local/bin/cursor-agent'),
'/usr/local/bin/cursor',
'/opt/homebrew/bin/cursor',
'cursor',
'cursor-agent',
];
for (const cursorPath of possiblePaths) {
try {
require('child_process').execSync(`${cursorPath} --help`, { stdio: 'ignore' });
return cursorPath;
}
catch (error) {
continue;
}
}
return undefined;
}
log(message) {
if (this.options.silentMode) {
return;
}
if (this.options.debugMode || process.env.NODE_ENV !== 'test') {
console.log(message);
}
}
}
exports.CursorClient = CursorClient;
;