gent-cli 1.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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Checkout Command - Switch branches
3
+ * Changes the current working branch
4
+ */
5
+
6
+ const path = require('path');
7
+ const chalk = require('chalk');
8
+ const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
9
+ const { COMMITS_FILE } = require('../utils/constants');
10
+
11
+ /**
12
+ * Switch to a different branch
13
+ * @param {String} branch - Branch name
14
+ * @param {Object} options - Command options
15
+ */
16
+ async function checkout(branch, options) {
17
+ try {
18
+ const gentPath = await getGentPath();
19
+ const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
20
+
21
+ const branches = repository.branches || {};
22
+
23
+ // Create new branch if -b flag is used
24
+ if (options.create) {
25
+ if (branches.hasOwnProperty(branch)) {
26
+ console.error(chalk.red(`Error: Branch '${branch}' already exists`));
27
+ process.exit(1);
28
+ }
29
+
30
+ // Create and switch to new branch
31
+ const currentCommit = branches[repository.currentBranch] || null;
32
+ branches[branch] = currentCommit;
33
+ repository.branches = branches;
34
+ repository.currentBranch = branch;
35
+
36
+ await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
37
+
38
+ console.log(chalk.green(`✓ Created and switched to branch '${branch}'`));
39
+ return;
40
+ }
41
+
42
+ // Switch to existing branch
43
+ if (!branches.hasOwnProperty(branch)) {
44
+ console.error(chalk.red(`Error: Branch '${branch}' not found`));
45
+ console.log(chalk.yellow(`\nℹ Use "gent branch" to see available branches`));
46
+ console.log(chalk.yellow(`ℹ Use "gent checkout -b ${branch}" to create a new branch`));
47
+ process.exit(1);
48
+ }
49
+
50
+ if (branch === repository.currentBranch) {
51
+ console.log(chalk.yellow(`Already on branch '${branch}'`));
52
+ return;
53
+ }
54
+
55
+ repository.currentBranch = branch;
56
+ await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
57
+
58
+ const commitHash = branches[branch];
59
+ const commitInfo = commitHash ? chalk.gray(` at ${commitHash.substring(0, 7)}`) : '';
60
+
61
+ console.log(chalk.green(`✓ Switched to branch '${branch}'${commitInfo}`));
62
+
63
+ } catch (error) {
64
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
65
+ console.error(chalk.red('Error: Not a gent repository'));
66
+ console.log(chalk.yellow('\nℹ Run "gent init" to initialize a repository'));
67
+ } else {
68
+ console.error(chalk.red('Error:'), error.message);
69
+ }
70
+ process.exit(1);
71
+ }
72
+ }
73
+
74
+ module.exports = checkout;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Commit Command - Record changes to the repository
3
+ * Creates a new commit with staged files
4
+ */
5
+
6
+ const path = require('path');
7
+ const chalk = require('chalk');
8
+ const inquirer = require('inquirer');
9
+ const ora = require('ora');
10
+ const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
11
+ const { STAGING_FILE, COMMITS_FILE, CONFIG_FILE } = require('../utils/constants');
12
+ const { generateCommitHash, getFileHash } = require('../utils/helpers');
13
+
14
+ /**
15
+ * Create a new commit
16
+ * @param {Object} options - Command options
17
+ */
18
+ async function commit(options) {
19
+ try {
20
+ const gentPath = await getGentPath();
21
+ const cwd = process.cwd();
22
+
23
+ // Read staging area
24
+ const staging = await readJSON(path.join(gentPath, STAGING_FILE));
25
+ const stagedFiles = staging.files || [];
26
+
27
+ if (stagedFiles.length === 0) {
28
+ console.log(chalk.yellow('No changes added to commit'));
29
+ console.log(chalk.gray('Use "gent add <file>..." to stage files'));
30
+ return;
31
+ }
32
+
33
+ // Get commit message
34
+ let message = options.message;
35
+
36
+ if (!message) {
37
+ const answer = await inquirer.prompt([
38
+ {
39
+ type: 'input',
40
+ name: 'message',
41
+ message: 'Commit message:',
42
+ validate: (input) => {
43
+ return input.length > 0 || 'Commit message cannot be empty';
44
+ }
45
+ }
46
+ ]);
47
+ message = answer.message;
48
+ }
49
+
50
+ const spinner = ora('Creating commit...').start();
51
+
52
+ // Read config and repository
53
+ const config = await readJSON(path.join(gentPath, CONFIG_FILE));
54
+ const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
55
+
56
+ // Create commit object
57
+ const commit = {
58
+ hash: generateCommitHash(),
59
+ message: message,
60
+ author: {
61
+ name: config.user.name,
62
+ email: config.user.email
63
+ },
64
+ timestamp: new Date().toISOString(),
65
+ parent: repository.branches[repository.currentBranch] || null,
66
+ files: []
67
+ };
68
+
69
+ // Hash staged files
70
+ for (const file of stagedFiles) {
71
+ const filePath = path.join(cwd, file);
72
+ const hash = await getFileHash(filePath);
73
+ commit.files.push({
74
+ path: file,
75
+ hash: hash
76
+ });
77
+ }
78
+
79
+ // Add commit to repository
80
+ repository.commits = repository.commits || [];
81
+ repository.commits.push(commit);
82
+ repository.branches[repository.currentBranch] = commit.hash;
83
+
84
+ // Save repository and clear staging
85
+ await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
86
+ staging.files = [];
87
+ await writeJSON(path.join(gentPath, STAGING_FILE), staging);
88
+
89
+ spinner.succeed(chalk.green('✓ Changes committed successfully!'));
90
+
91
+ // Display commit info
92
+ console.log(chalk.cyan(`\n[${repository.currentBranch} ${commit.hash.substring(0, 7)}] ${message}`));
93
+ console.log(chalk.gray(`Author: ${commit.author.name} <${commit.author.email}>`));
94
+ console.log(chalk.gray(`Date: ${new Date(commit.timestamp).toLocaleString()}`));
95
+ console.log(chalk.gray(`\n${commit.files.length} file(s) changed`));
96
+
97
+ } catch (error) {
98
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
99
+ console.error(chalk.red('Error: Not a gent repository'));
100
+ console.log(chalk.yellow('\nℹ Run "gent init" to initialize a repository'));
101
+ } else {
102
+ console.error(chalk.red('Error:'), error.message);
103
+ }
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ module.exports = commit;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Init Command - Initialize a new gent repository
3
+ * Creates .gent directory and necessary files
4
+ */
5
+
6
+ const fs = require('fs').promises;
7
+ const path = require('path');
8
+ const chalk = require('chalk');
9
+ const inquirer = require('inquirer');
10
+ const ora = require('ora');
11
+ const boxen = require('boxen');
12
+ const { ensureDir, writeJSON, pathExists } = require('../utils/fileSystem');
13
+ const { GENT_DIR, CONFIG_FILE, STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
14
+
15
+ /**
16
+ * Initialize a new gent repository
17
+ * @param {Object} options - Command options
18
+ */
19
+ async function init(options) {
20
+ const spinner = ora('Initializing gent repository...').start();
21
+
22
+ try {
23
+ const cwd = process.cwd();
24
+ const gentPath = path.join(cwd, GENT_DIR);
25
+
26
+ // Check if already initialized
27
+ if (await pathExists(gentPath)) {
28
+ spinner.fail(chalk.red('Gent repository already exists!'));
29
+ console.log(chalk.yellow('\nℹ Use gent status to see the current state'));
30
+ return;
31
+ }
32
+
33
+ // Get user configuration if not using defaults
34
+ let config = {
35
+ user: {
36
+ name: 'Anonymous',
37
+ email: 'anonymous@example.com'
38
+ },
39
+ repository: {
40
+ name: path.basename(cwd),
41
+ description: 'A gent repository',
42
+ created: new Date().toISOString()
43
+ }
44
+ };
45
+
46
+ if (!options.yes) {
47
+ spinner.stop();
48
+
49
+ const answers = await inquirer.prompt([
50
+ {
51
+ type: 'input',
52
+ name: 'userName',
53
+ message: 'Enter your name:',
54
+ default: 'Anonymous'
55
+ },
56
+ {
57
+ type: 'input',
58
+ name: 'userEmail',
59
+ message: 'Enter your email:',
60
+ default: 'anonymous@example.com',
61
+ validate: (input) => {
62
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
63
+ return emailRegex.test(input) || 'Please enter a valid email';
64
+ }
65
+ },
66
+ {
67
+ type: 'input',
68
+ name: 'repoName',
69
+ message: 'Repository name:',
70
+ default: path.basename(cwd)
71
+ },
72
+ {
73
+ type: 'input',
74
+ name: 'repoDescription',
75
+ message: 'Repository description:',
76
+ default: 'A gent repository'
77
+ }
78
+ ]);
79
+
80
+ config.user.name = answers.userName;
81
+ config.user.email = answers.userEmail;
82
+ config.repository.name = answers.repoName;
83
+ config.repository.description = answers.repoDescription;
84
+
85
+ spinner.start('Creating repository structure...');
86
+ }
87
+
88
+ // Create directory structure
89
+ await ensureDir(gentPath);
90
+ await ensureDir(path.join(gentPath, 'objects'));
91
+ await ensureDir(path.join(gentPath, 'refs', 'heads'));
92
+ await ensureDir(path.join(gentPath, 'refs', 'tags'));
93
+
94
+ // Create initial files
95
+ await writeJSON(path.join(gentPath, CONFIG_FILE), config);
96
+ await writeJSON(path.join(gentPath, STAGING_FILE), { files: [] });
97
+ await writeJSON(path.join(gentPath, COMMITS_FILE), { commits: [], branches: { main: null }, currentBranch: 'main' });
98
+
99
+ // Create HEAD file
100
+ await fs.writeFile(path.join(gentPath, 'HEAD'), 'ref: refs/heads/main\n');
101
+
102
+ // Create .gentignore
103
+ const gentignore = `# Gent ignore patterns
104
+ node_modules/
105
+ .DS_Store
106
+ *.log
107
+ .env
108
+ .gent/
109
+ `;
110
+ await fs.writeFile(path.join(cwd, '.gentignore'), gentignore);
111
+
112
+ spinner.succeed(chalk.green('✓ Gent repository initialized successfully!'));
113
+
114
+ // Display success message
115
+ const message = chalk.white(`
116
+ ${chalk.bold('Repository:')} ${config.repository.name}
117
+ ${chalk.bold('User:')} ${config.user.name} <${config.user.email}>
118
+ ${chalk.bold('Branch:')} main
119
+
120
+ ${chalk.cyan('Next steps:')}
121
+ ${chalk.gray('•')} gent add <files> - Add files to staging
122
+ ${chalk.gray('•')} gent commit - Commit your changes
123
+ ${chalk.gray('•')} gent status - View repository status
124
+ `);
125
+
126
+ console.log(boxen(message, {
127
+ padding: 1,
128
+ margin: 1,
129
+ borderStyle: 'round',
130
+ borderColor: 'cyan'
131
+ }));
132
+
133
+ } catch (error) {
134
+ spinner.fail(chalk.red('Failed to initialize repository'));
135
+ console.error(chalk.red('\nError:'), error.message);
136
+ process.exit(1);
137
+ }
138
+ }
139
+
140
+ module.exports = init;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Log Command - Show commit logs
3
+ * Displays commit history with details
4
+ */
5
+
6
+ const path = require('path');
7
+ const chalk = require('chalk');
8
+ const { formatDistanceToNow } = require('date-fns');
9
+ const { getGentPath, readJSON } = require('../utils/fileSystem');
10
+ const { COMMITS_FILE } = require('../utils/constants');
11
+
12
+ /**
13
+ * Show commit history
14
+ * @param {Object} options - Command options
15
+ */
16
+ async function log(options) {
17
+ try {
18
+ const gentPath = await getGentPath();
19
+ const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
20
+
21
+ const commits = repository.commits || [];
22
+ const currentBranch = repository.currentBranch || 'main';
23
+
24
+ if (commits.length === 0) {
25
+ console.log(chalk.yellow('No commits yet'));
26
+ console.log(chalk.gray('Use "gent commit" to create your first commit'));
27
+ return;
28
+ }
29
+
30
+ // Limit number of commits to show
31
+ const limit = parseInt(options.number) || 10;
32
+ const commitsToShow = commits.slice(-limit).reverse();
33
+
34
+ if (options.oneline) {
35
+ displayOnelineLog(commitsToShow, repository.branches[currentBranch]);
36
+ } else {
37
+ displayDetailedLog(commitsToShow, repository.branches[currentBranch], currentBranch);
38
+ }
39
+
40
+ } catch (error) {
41
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
42
+ console.error(chalk.red('Error: Not a gent repository'));
43
+ console.log(chalk.yellow('\nℹ Run "gent init" to initialize a repository'));
44
+ } else {
45
+ console.error(chalk.red('Error:'), error.message);
46
+ }
47
+ process.exit(1);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Display detailed commit log
53
+ */
54
+ function displayDetailedLog(commits, currentCommitHash, currentBranch) {
55
+ console.log(chalk.bold.cyan(`\nCommit History (${currentBranch} branch):\n`));
56
+
57
+ commits.forEach((commit, index) => {
58
+ const isHead = commit.hash === currentCommitHash;
59
+ const headLabel = isHead ? chalk.yellow.bold(' (HEAD)') : '';
60
+
61
+ console.log(chalk.yellow(`commit ${commit.hash}`) + headLabel);
62
+ console.log(chalk.white(`Author: ${commit.author.name} <${commit.author.email}>`));
63
+ console.log(chalk.white(`Date: ${new Date(commit.timestamp).toLocaleString()}`));
64
+ console.log(chalk.gray(` (${formatDistanceToNow(new Date(commit.timestamp), { addSuffix: true })})`));
65
+ console.log();
66
+ console.log(chalk.white(` ${commit.message}`));
67
+ console.log();
68
+ console.log(chalk.gray(` ${commit.files.length} file(s) changed`));
69
+
70
+ if (index < commits.length - 1) {
71
+ console.log(chalk.gray(' │'));
72
+ }
73
+ console.log();
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Display oneline commit log
79
+ */
80
+ function displayOnelineLog(commits, currentCommitHash) {
81
+ commits.forEach(commit => {
82
+ const isHead = commit.hash === currentCommitHash;
83
+ const headLabel = isHead ? chalk.yellow(' (HEAD)') : '';
84
+ const shortHash = chalk.yellow(commit.hash.substring(0, 7));
85
+ const message = chalk.white(commit.message);
86
+ const timeAgo = chalk.gray(`(${formatDistanceToNow(new Date(commit.timestamp), { addSuffix: true })})`);
87
+
88
+ console.log(`${shortHash}${headLabel} ${message} ${timeAgo}`);
89
+ });
90
+ }
91
+
92
+ module.exports = log;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Status Command - Show the working tree status
3
+ * Displays staged, modified, and untracked files
4
+ */
5
+
6
+ const fs = require('fs').promises;
7
+ const path = require('path');
8
+ const chalk = require('chalk');
9
+ const { getGentPath, readJSON, pathExists, getTrackedFiles, getIgnorePatterns } = require('../utils/fileSystem');
10
+ const { STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
11
+ const { getFileHash, getAllFiles } = require('../utils/helpers');
12
+
13
+ /**
14
+ * Show repository status
15
+ * @param {Object} options - Command options
16
+ */
17
+ async function status(options) {
18
+ try {
19
+ const gentPath = await getGentPath();
20
+ const cwd = process.cwd();
21
+
22
+ // Read staging area and commits
23
+ const staging = await readJSON(path.join(gentPath, STAGING_FILE));
24
+ const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
25
+
26
+ const stagedFiles = staging.files || [];
27
+ const currentBranch = repository.currentBranch || 'main';
28
+ const lastCommit = repository.branches[currentBranch];
29
+
30
+ // Get all files in the working directory
31
+ const ignorePatterns = await getIgnorePatterns(cwd);
32
+ const allFiles = await getAllFiles(cwd, ignorePatterns);
33
+
34
+ // Get tracked files from last commit
35
+ const trackedFiles = await getTrackedFiles(gentPath, lastCommit);
36
+
37
+ // Categorize files
38
+ const stagedSet = new Set(stagedFiles);
39
+ const trackedSet = new Set(trackedFiles.map(f => f.path));
40
+
41
+ const modified = [];
42
+ const untracked = [];
43
+ const deleted = [];
44
+
45
+ // Check for modifications and untracked files
46
+ for (const file of allFiles) {
47
+ const relativePath = path.relative(cwd, file);
48
+
49
+ if (trackedSet.has(relativePath)) {
50
+ // Check if modified
51
+ const currentHash = await getFileHash(file);
52
+ const trackedFile = trackedFiles.find(f => f.path === relativePath);
53
+
54
+ if (trackedFile && currentHash !== trackedFile.hash && !stagedSet.has(relativePath)) {
55
+ modified.push(relativePath);
56
+ }
57
+ } else if (!stagedSet.has(relativePath)) {
58
+ untracked.push(relativePath);
59
+ }
60
+ }
61
+
62
+ // Check for deleted files
63
+ for (const trackedFile of trackedFiles) {
64
+ const fullPath = path.join(cwd, trackedFile.path);
65
+ if (!await pathExists(fullPath) && !stagedSet.has(trackedFile.path)) {
66
+ deleted.push(trackedFile.path);
67
+ }
68
+ }
69
+
70
+ // Display status
71
+ if (options.short) {
72
+ displayShortStatus(stagedFiles, modified, untracked, deleted);
73
+ } else {
74
+ displayDetailedStatus(currentBranch, stagedFiles, modified, untracked, deleted, lastCommit);
75
+ }
76
+
77
+ } catch (error) {
78
+ if (error.code === 'ENOENT' && error.message.includes('.gent')) {
79
+ console.error(chalk.red('Error: Not a gent repository'));
80
+ console.log(chalk.yellow('\nℹ Run "gent init" to initialize a repository'));
81
+ } else {
82
+ console.error(chalk.red('Error:'), error.message);
83
+ }
84
+ process.exit(1);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Display detailed status output
90
+ */
91
+ function displayDetailedStatus(branch, staged, modified, untracked, deleted, lastCommit) {
92
+ console.log(chalk.bold(`On branch ${chalk.cyan(branch)}`));
93
+
94
+ if (!lastCommit) {
95
+ console.log(chalk.gray('No commits yet\n'));
96
+ } else {
97
+ console.log(chalk.gray(`Last commit: ${lastCommit.substring(0, 7)}\n`));
98
+ }
99
+
100
+ // Staged files
101
+ if (staged.length > 0) {
102
+ console.log(chalk.green.bold('Changes to be committed:'));
103
+ console.log(chalk.gray(' (use "gent reset <file>..." to unstage)\n'));
104
+ staged.forEach(file => {
105
+ console.log(chalk.green(`\t${file}`));
106
+ });
107
+ console.log();
108
+ }
109
+
110
+ // Modified files
111
+ if (modified.length > 0) {
112
+ console.log(chalk.red.bold('Changes not staged for commit:'));
113
+ console.log(chalk.gray(' (use "gent add <file>..." to update what will be committed)\n'));
114
+ modified.forEach(file => {
115
+ console.log(chalk.red(`\tmodified: ${file}`));
116
+ });
117
+ console.log();
118
+ }
119
+
120
+ // Deleted files
121
+ if (deleted.length > 0) {
122
+ deleted.forEach(file => {
123
+ console.log(chalk.red(`\tdeleted: ${file}`));
124
+ });
125
+ console.log();
126
+ }
127
+
128
+ // Untracked files
129
+ if (untracked.length > 0) {
130
+ console.log(chalk.red.bold('Untracked files:'));
131
+ console.log(chalk.gray(' (use "gent add <file>..." to include in what will be committed)\n'));
132
+ untracked.forEach(file => {
133
+ console.log(chalk.red(`\t${file}`));
134
+ });
135
+ console.log();
136
+ }
137
+
138
+ // Status summary
139
+ if (staged.length === 0 && modified.length === 0 && untracked.length === 0 && deleted.length === 0) {
140
+ console.log(chalk.green('✓ Working tree clean'));
141
+ } else if (staged.length === 0) {
142
+ console.log(chalk.yellow('No changes added to commit (use "gent add" to track files)'));
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Display short status output
148
+ */
149
+ function displayShortStatus(staged, modified, untracked, deleted) {
150
+ staged.forEach(file => {
151
+ console.log(chalk.green('A ') + file);
152
+ });
153
+
154
+ modified.forEach(file => {
155
+ console.log(chalk.red(' M ') + file);
156
+ });
157
+
158
+ deleted.forEach(file => {
159
+ console.log(chalk.red(' D ') + file);
160
+ });
161
+
162
+ untracked.forEach(file => {
163
+ console.log(chalk.red('?? ') + file);
164
+ });
165
+ }
166
+
167
+ module.exports = status;
package/src/index.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Gent CLI - A Git-like version control system
5
+ * Main entry point for the CLI application
6
+ *
7
+ * @author Your Name
8
+ * @version 1.0.0
9
+ */
10
+
11
+ const { program } = require('commander');
12
+ const chalk = require('chalk');
13
+ const packageJson = require('../package.json');
14
+
15
+ // Import commands
16
+ const initCommand = require('./commands/init');
17
+ const statusCommand = require('./commands/status');
18
+ const addCommand = require('./commands/add');
19
+ const commitCommand = require('./commands/commit');
20
+ const logCommand = require('./commands/log');
21
+ const branchCommand = require('./commands/branch');
22
+ const checkoutCommand = require('./commands/checkout');
23
+
24
+ // Configure CLI
25
+ program
26
+ .name('gent')
27
+ .description(chalk.cyan('🚀 Gent - A Git-like version control CLI'))
28
+ .version(packageJson.version, '-v, --version', 'Output the current version');
29
+
30
+ // Register commands
31
+ program
32
+ .command('init')
33
+ .description('Initialize a new gent repository')
34
+ .option('-y, --yes', 'Skip prompts and use defaults')
35
+ .action(initCommand);
36
+
37
+ program
38
+ .command('status')
39
+ .description('Show the working tree status')
40
+ .option('-s, --short', 'Give the output in short format')
41
+ .action(statusCommand);
42
+
43
+ program
44
+ .command('add <files...>')
45
+ .description('Add file contents to the staging area')
46
+ .option('-A, --all', 'Add all files')
47
+ .action(addCommand);
48
+
49
+ program
50
+ .command('commit')
51
+ .description('Record changes to the repository')
52
+ .option('-m, --message <message>', 'Commit message')
53
+ .option('-a, --all', 'Automatically stage all modified files')
54
+ .action(commitCommand);
55
+
56
+ program
57
+ .command('log')
58
+ .description('Show commit logs')
59
+ .option('-n, --number <count>', 'Limit the number of commits to show', '10')
60
+ .option('--oneline', 'Show each commit on a single line')
61
+ .action(logCommand);
62
+
63
+ program
64
+ .command('branch')
65
+ .description('List, create, or delete branches')
66
+ .argument('[name]', 'Branch name to create')
67
+ .option('-d, --delete <name>', 'Delete a branch')
68
+ .option('-a, --all', 'List all branches')
69
+ .action(branchCommand);
70
+
71
+ program
72
+ .command('checkout <branch>')
73
+ .description('Switch branches or restore working tree files')
74
+ .option('-b, --create', 'Create a new branch')
75
+ .action(checkoutCommand);
76
+
77
+ // Help command
78
+ program
79
+ .command('help [command]')
80
+ .description('Display help for a specific command')
81
+ .action((command) => {
82
+ if (command) {
83
+ program.commands.find(cmd => cmd.name() === command)?.help();
84
+ } else {
85
+ program.help();
86
+ }
87
+ });
88
+
89
+ // Error handling
90
+ program.exitOverride();
91
+
92
+ try {
93
+ program.parse(process.argv);
94
+
95
+ // Show help if no command provided
96
+ if (!process.argv.slice(2).length) {
97
+ program.outputHelp();
98
+ }
99
+ } catch (err) {
100
+ if (err.code !== 'commander.help' && err.code !== 'commander.helpDisplayed') {
101
+ console.error(chalk.red('Error:'), err.message);
102
+ process.exit(1);
103
+ }
104
+ }