autoagent-cli
Version:
Run autonomous AI agents using Claude or Gemini for task execution
453 lines (452 loc) • 16 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkGitAvailable = checkGitAvailable;
exports.getGitVersion = getGitVersion;
exports.isGitRepository = isGitRepository;
exports.getGitStatus = getGitStatus;
exports.stageAllChanges = stageAllChanges;
exports.createCommit = createCommit;
exports.getCurrentCommitHash = getCurrentCommitHash;
exports.getUncommittedChanges = getUncommittedChanges;
exports.hasChangesToCommit = hasChangesToCommit;
exports.revertToCommit = revertToCommit;
exports.getChangedFiles = getChangedFiles;
exports.validateGitEnvironment = validateGitEnvironment;
exports.getCurrentBranch = getCurrentBranch;
exports.hasUpstreamBranch = hasUpstreamBranch;
exports.checkGitRemote = checkGitRemote;
exports.pushToRemote = pushToRemote;
exports.validateRemoteForPush = validateRemoteForPush;
const child_process_1 = require("child_process");
const util_1 = require("util");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
async function checkGitAvailable() {
try {
await execAsync('git --version');
return true;
}
catch {
return false;
}
}
async function getGitVersion() {
try {
const { stdout } = await execAsync('git --version');
return stdout.trim();
}
catch {
return null;
}
}
async function isGitRepository() {
try {
await execAsync('git rev-parse --git-dir');
return true;
}
catch {
return false;
}
}
async function getGitStatus() {
const status = {
isRepo: false,
isDirty: false,
hasUncommitted: false,
hasUntracked: false,
branch: '',
ahead: 0,
behind: 0
};
try {
status.isRepo = await isGitRepository();
if (!status.isRepo) {
return status;
}
const { stdout: branch } = await execAsync('git rev-parse --abbrev-ref HEAD');
status.branch = branch.trim();
const { stdout: diffStatus } = await execAsync('git diff --stat');
status.hasUncommitted = diffStatus.trim() !== '';
const { stdout: untrackedFiles } = await execAsync('git ls-files --others --exclude-standard');
status.hasUntracked = untrackedFiles.trim() !== '';
try {
await execAsync('git diff-index --quiet HEAD --');
status.isDirty = false;
}
catch {
status.isDirty = true;
}
try {
const { stdout: revList } = await execAsync('git rev-list --left-right --count HEAD...@{upstream}');
const [ahead, behind] = revList.trim().split('\t').map(n => parseInt(n, 10));
status.ahead = ahead ?? 0;
status.behind = behind ?? 0;
}
catch {
}
return status;
}
catch (error) {
return status;
}
}
async function stageAllChanges() {
await execAsync('git add -A');
}
async function createCommit(options) {
try {
const { message, coAuthor, signoff, noVerify } = options;
let fullMessage = message;
if (coAuthor) {
fullMessage += `\n\nCo-authored-by: ${coAuthor.name} <${coAuthor.email}>`;
}
let command = `git commit -m "${fullMessage.replace(/"/g, '\\"')}"`;
if (signoff === true) {
command += ' --signoff';
}
if (noVerify === true) {
command += ' --no-verify';
}
const { stdout } = await execAsync(command);
const hashMatch = stdout.match(/\[[\w\s-]+\s+([a-f0-9]+)\]/);
const commitHash = hashMatch ? hashMatch[1] : undefined;
return {
success: true,
commitHash
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
async function getCurrentCommitHash() {
try {
const { stdout } = await execAsync('git rev-parse HEAD');
return stdout.trim();
}
catch {
return null;
}
}
async function getUncommittedChanges() {
try {
const { stdout } = await execAsync('git diff HEAD');
return stdout;
}
catch {
return '';
}
}
async function hasChangesToCommit() {
try {
const { stdout: staged } = await execAsync('git diff --cached --stat');
if (staged.trim() !== '') {
return true;
}
const { stdout: unstaged } = await execAsync('git diff --stat');
if (unstaged.trim() !== '') {
return true;
}
const { stdout: untracked } = await execAsync('git ls-files --others --exclude-standard');
return untracked.trim() !== '';
}
catch {
return false;
}
}
async function revertToCommit(commitHash) {
try {
await execAsync(`git reset --hard ${commitHash}`);
return true;
}
catch (error) {
return false;
}
}
async function getChangedFiles() {
try {
const files = [];
const { stdout: staged } = await execAsync('git diff --cached --name-only');
if (staged.trim() !== '') {
files.push(...staged.trim().split('\n'));
}
const { stdout: unstaged } = await execAsync('git diff --name-only');
if (unstaged.trim() !== '') {
files.push(...unstaged.trim().split('\n'));
}
const { stdout: untracked } = await execAsync('git ls-files --others --exclude-standard');
if (untracked.trim() !== '') {
files.push(...untracked.trim().split('\n'));
}
return Array.from(new Set(files));
}
catch {
return [];
}
}
async function validateGitEnvironment(options = {}) {
const errors = [];
const suggestions = [];
const debug = options.onDebug || (() => { });
let gitVersion;
debug('🔍 Starting git validation...');
debug('📋 Checking git availability...');
const gitAvailable = await checkGitAvailable();
if (!gitAvailable) {
debug('❌ Git is not available');
errors.push('Git is not installed or not accessible in PATH');
suggestions.push('Install Git from https://git-scm.com/downloads');
suggestions.push('Ensure git is added to your system PATH');
}
else {
const version = await getGitVersion();
gitVersion = version ?? undefined;
debug(`✅ Git is available: ${version ?? 'unknown version'}`);
}
debug('📁 Checking repository status...');
const isRepo = await isGitRepository();
if (!isRepo) {
debug('❌ Not a git repository');
errors.push('Current directory is not a Git repository');
suggestions.push('Initialize a new repository with: git init');
suggestions.push('Or clone an existing repository with: git clone <url>');
}
else {
debug('✅ Valid git repository detected');
}
if (gitAvailable && isRepo) {
debug('👤 Checking user configuration...');
try {
const { stdout: userName } = await execAsync('git config user.name');
if (!userName.trim()) {
debug('❌ Git user name is not configured');
errors.push('Git user name is not configured');
suggestions.push('Set your name with: git config --global user.name "Your Name"');
}
else {
debug(`✅ Git user name: ${userName.trim()}`);
}
}
catch {
debug('❌ Failed to check git user name');
errors.push('Git user name is not configured');
suggestions.push('Set your name with: git config --global user.name "Your Name"');
}
try {
const { stdout: userEmail } = await execAsync('git config user.email');
if (!userEmail.trim()) {
debug('❌ Git user email is not configured');
errors.push('Git user email is not configured');
suggestions.push('Set your email with: git config --global user.email "your.email@example.com"');
}
else {
debug(`✅ Git user email: ${userEmail.trim()}`);
}
}
catch {
debug('❌ Failed to check git user email');
errors.push('Git user email is not configured');
suggestions.push('Set your email with: git config --global user.email "your.email@example.com"');
}
debug('🌐 Checking remote repository...');
try {
const { stdout: remotes } = await execAsync('git remote -v');
if (!remotes.trim()) {
debug('⚠️ No remote repository configured (warning)');
errors.push('No remote repository configured');
suggestions.push('Add a remote with: git remote add origin <repository-url>');
suggestions.push('View existing remotes with: git remote -v');
}
else {
debug(`✅ Remote repository configured:\n${remotes.trim()}`);
}
}
catch {
debug('❌ Unable to check remote repositories');
errors.push('Unable to check remote repositories');
suggestions.push('Check git configuration with: git remote -v');
}
}
const isValid = errors.length === 0;
if (isValid) {
debug('✅ Git validation completed successfully');
}
else {
debug(`❌ Git validation failed with ${errors.length} error(s)`);
errors.forEach((error, index) => {
debug(` ${index + 1}. ${error}`);
});
}
return {
isValid,
errors,
suggestions,
gitVersion
};
}
async function getCurrentBranch() {
try {
const { stdout } = await execAsync('git rev-parse --abbrev-ref HEAD');
const branch = stdout.trim();
if (branch === 'HEAD') {
return null;
}
return branch;
}
catch {
return null;
}
}
async function hasUpstreamBranch() {
try {
await execAsync('git rev-parse --abbrev-ref --symbolic-full-name @{upstream}');
return true;
}
catch {
return false;
}
}
async function checkGitRemote(remote = 'origin') {
try {
const { stdout: remotes } = await execAsync('git remote');
const remotesList = remotes.trim().split('\n').filter(r => r);
if (!remotesList.includes(remote)) {
return {
exists: false,
accessible: false,
error: `Remote '${remote}' does not exist`
};
}
try {
await execAsync(`git ls-remote --exit-code ${remote} HEAD`, { timeout: 30000 });
return {
exists: true,
accessible: true
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('Authentication failed') ||
errorMessage.includes('Permission denied') ||
errorMessage.includes('Could not read from remote repository')) {
return {
exists: true,
accessible: false,
error: 'Authentication failed. Please check your credentials'
};
}
if (errorMessage.includes('Could not resolve host') ||
errorMessage.includes('unable to access') ||
errorMessage.includes('Network is unreachable')) {
return {
exists: true,
accessible: false,
error: 'Network error. Please check your internet connection'
};
}
return {
exists: true,
accessible: false,
error: `Remote is not accessible: ${errorMessage}`
};
}
}
catch (error) {
return {
exists: false,
accessible: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
async function pushToRemote(options = {}) {
try {
const { remote = 'origin', branch, force = false, setUpstream = false } = options;
const targetBranch = branch ?? await getCurrentBranch();
if (targetBranch === null) {
return {
success: false,
error: 'Cannot push from detached HEAD state. Please checkout a branch first'
};
}
let command = 'git push';
if (force) {
command += ' --force-with-lease';
}
if (setUpstream) {
command += ' --set-upstream';
}
command += ` ${remote} ${targetBranch}`;
const { stderr } = await execAsync(command);
return {
success: true,
remote,
branch: targetBranch,
stderr: stderr || undefined
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
let errorStderr;
if (error !== null && typeof error === 'object' && 'stderr' in error && typeof error.stderr === 'string') {
errorStderr = error.stderr;
}
return {
success: false,
error: errorMessage,
stderr: errorStderr
};
}
}
async function validateRemoteForPush(remote = 'origin') {
const errors = [];
const suggestions = [];
const isRepo = await isGitRepository();
if (!isRepo) {
errors.push('Not in a git repository');
suggestions.push('Initialize a repository with: git init');
suggestions.push('Or clone an existing repository with: git clone <url>');
return { isValid: false, errors, suggestions };
}
const currentBranch = await getCurrentBranch();
if (currentBranch === null) {
errors.push('Currently in detached HEAD state');
suggestions.push('Checkout a branch with: git checkout -b <branch-name>');
suggestions.push('Or checkout an existing branch with: git checkout <branch-name>');
return { isValid: false, errors, suggestions };
}
const remoteStatus = await checkGitRemote(remote);
if (!remoteStatus.exists) {
errors.push(`Remote '${remote}' does not exist`);
suggestions.push(`Add a remote with: git remote add ${remote} <repository-url>`);
suggestions.push('List existing remotes with: git remote -v');
}
else if (!remoteStatus.accessible) {
const errorMessage = remoteStatus.error ?? 'Unknown error';
errors.push(`Remote '${remote}' is not accessible: ${errorMessage}`);
if (remoteStatus.error !== undefined && remoteStatus.error.includes('Authentication')) {
suggestions.push('Check your git credentials are configured correctly');
suggestions.push('For HTTPS: Update credentials in your credential manager');
suggestions.push('For SSH: Ensure your SSH key is added to the remote repository');
}
else if (remoteStatus.error !== undefined && remoteStatus.error.includes('Network')) {
suggestions.push('Check your internet connection');
suggestions.push('Verify the remote URL is correct with: git remote -v');
}
else {
suggestions.push('Verify the remote URL with: git remote -v');
suggestions.push(`Test remote connectivity with: git ls-remote ${remote}`);
}
}
const hasUpstream = await hasUpstreamBranch();
if (!hasUpstream && remoteStatus.exists && remoteStatus.accessible) {
suggestions.push(`Set upstream tracking with: git push --set-upstream ${remote} ${currentBranch}`);
}
return {
isValid: errors.length === 0,
errors,
suggestions
};
}