gent-cli 1.5.0 → 1.7.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,123 @@
1
+ /**
2
+ * Pull Command - Pull commits from cloud to local repository
3
+ * Downloads commits, trees, and blobs from the cloud
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const path = require('path');
8
+ const ora = require('ora');
9
+ const { pathExists, readJSON, writeJSON } = require('../utils/fileSystem');
10
+ const { GENT_DIR, COMMITS_FILE } = require('../utils/constants');
11
+ const { getRemote, syncCommitsFromCloud } = require('../utils/cloud-sync');
12
+ const authStorage = require('../utils/auth-storage');
13
+ const repoService = require('../services/repo-service');
14
+
15
+ /**
16
+ * Pull commits from cloud to local
17
+ * @param {string} remoteName - Remote name (default: origin)
18
+ * @param {string} branchName - Branch name (optional)
19
+ * @param {Object} options - Command options
20
+ */
21
+ async function pull(remoteName, branchName, options) {
22
+ try {
23
+ const cwd = process.cwd();
24
+ const gentPath = path.join(cwd, GENT_DIR);
25
+
26
+ // Check if in a gent repository
27
+ if (!(await pathExists(gentPath))) {
28
+ console.error(chalk.red('Error: Not a gent repository'));
29
+ console.log(chalk.yellow('Run'), chalk.cyan('gent init'), chalk.yellow('to initialize a repository'));
30
+ process.exit(1);
31
+ }
32
+
33
+ // Check authentication
34
+ const user = await authStorage.getUser();
35
+ if (!user) {
36
+ console.error(chalk.red('Error: You must be logged in to pull'));
37
+ console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
38
+ process.exit(1);
39
+ }
40
+
41
+ // Default to 'origin' if no remote specified
42
+ remoteName = remoteName || 'origin';
43
+
44
+ // Get remote configuration
45
+ const remote = await getRemote(remoteName, cwd);
46
+ if (!remote) {
47
+ console.error(chalk.red(`Error: Remote '${remoteName}' not found`));
48
+ console.log(chalk.yellow('Add a remote with:'), chalk.cyan('gent remote add origin <owner_id>/<repo_name>'));
49
+ console.log(chalk.yellow('Or list remotes with:'), chalk.cyan('gent remote'));
50
+ process.exit(1);
51
+ }
52
+
53
+ // Get current branch if not specified
54
+ const commitsData = await readJSON(path.join(gentPath, COMMITS_FILE));
55
+ branchName = branchName || commitsData.currentBranch;
56
+
57
+ if (!branchName) {
58
+ console.error(chalk.red('Error: No branch specified and no current branch found'));
59
+ process.exit(1);
60
+ }
61
+
62
+ console.log(chalk.cyan(`Pulling from ${remoteName}/${branchName}...\n`));
63
+
64
+ // Verify repository exists
65
+ const spinner = ora('Verifying remote repository...').start();
66
+ try {
67
+ await repoService.getRepository(remote.owner_id, remote.repo_name);
68
+ spinner.succeed('Remote repository verified');
69
+ } catch (error) {
70
+ spinner.fail('Remote repository not found or access denied');
71
+ throw error;
72
+ }
73
+
74
+ // Pull commits
75
+ const cloudCommits = await syncCommitsFromCloud(remote.owner_id, remote.repo_name, cwd);
76
+
77
+ // Update local commits file with cloud commits
78
+ const localCommits = commitsData.commits || [];
79
+ const mergedCommits = [...localCommits];
80
+
81
+ // Add cloud commits that don't exist locally
82
+ for (const cloudCommit of cloudCommits) {
83
+ const exists = localCommits.find(c => c.sha === cloudCommit.sha);
84
+ if (!exists) {
85
+ // Convert cloud commit format to local format
86
+ mergedCommits.push({
87
+ sha: cloudCommit.sha,
88
+ message: cloudCommit.message,
89
+ author: {
90
+ name: cloudCommit.author_name,
91
+ email: cloudCommit.author_email
92
+ },
93
+ timestamp: cloudCommit.committed_at,
94
+ parent: cloudCommit.parent_shas || [],
95
+ files: {} // Files are stored in objects directory
96
+ });
97
+ }
98
+ }
99
+
100
+ // Update branch pointer if we have cloud commits
101
+ if (cloudCommits.length > 0) {
102
+ const latestCloudCommit = cloudCommits[cloudCommits.length - 1];
103
+ commitsData.branches[branchName] = latestCloudCommit.sha;
104
+ }
105
+
106
+ commitsData.commits = mergedCommits;
107
+
108
+ await writeJSON(path.join(gentPath, COMMITS_FILE), commitsData);
109
+
110
+ if (cloudCommits.length > 0) {
111
+ console.log(chalk.green(`\n✓ Successfully pulled ${cloudCommits.length} commit(s) from ${remoteName}/${branchName}`));
112
+ } else {
113
+ console.log(chalk.yellow('Already up to date'));
114
+ }
115
+
116
+ } catch (error) {
117
+ console.error(chalk.red('Failed to pull'));
118
+ console.error(chalk.red('Error:'), error.message);
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ module.exports = pull;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Push Command - Push local commits to cloud repository
3
+ * Uploads commits, trees, and blobs to the cloud
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const path = require('path');
8
+ const ora = require('ora');
9
+ const { pathExists, readJSON } = require('../utils/fileSystem');
10
+ const { GENT_DIR, COMMITS_FILE } = require('../utils/constants');
11
+ const { getRemote, syncCommitsToCloud } = require('../utils/cloud-sync');
12
+ const authStorage = require('../utils/auth-storage');
13
+ const repoService = require('../services/repo-service');
14
+
15
+ /**
16
+ * Push local commits to cloud
17
+ * @param {string} remoteName - Remote name (default: origin)
18
+ * @param {string} branchName - Branch name (optional)
19
+ * @param {Object} options - Command options
20
+ */
21
+ async function push(remoteName, branchName, options) {
22
+ try {
23
+ const cwd = process.cwd();
24
+ const gentPath = path.join(cwd, GENT_DIR);
25
+
26
+ // Check if in a gent repository
27
+ if (!(await pathExists(gentPath))) {
28
+ console.error(chalk.red('Error: Not a gent repository'));
29
+ console.log(chalk.yellow('Run'), chalk.cyan('gent init'), chalk.yellow('to initialize a repository'));
30
+ process.exit(1);
31
+ }
32
+
33
+ // Check authentication
34
+ const user = await authStorage.getUser();
35
+ if (!user) {
36
+ console.error(chalk.red('Error: You must be logged in to push'));
37
+ console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
38
+ process.exit(1);
39
+ }
40
+
41
+ // Default to 'origin' if no remote specified
42
+ remoteName = remoteName || 'origin';
43
+
44
+ // Get remote configuration
45
+ const remote = await getRemote(remoteName, cwd);
46
+ if (!remote) {
47
+ console.error(chalk.red(`Error: Remote '${remoteName}' not found`));
48
+ console.log(chalk.yellow('Add a remote with:'), chalk.cyan('gent remote add origin <owner_id>/<repo_name>'));
49
+ console.log(chalk.yellow('Or list remotes with:'), chalk.cyan('gent remote'));
50
+ process.exit(1);
51
+ }
52
+
53
+ // Get current branch if not specified
54
+ const commitsData = await readJSON(path.join(gentPath, COMMITS_FILE));
55
+ branchName = branchName || commitsData.currentBranch;
56
+
57
+ if (!branchName) {
58
+ console.error(chalk.red('Error: No branch specified and no current branch found'));
59
+ process.exit(1);
60
+ }
61
+
62
+ // Get commits for the branch
63
+ const commits = commitsData.commits || [];
64
+ const branchCommitSha = commitsData.branches[branchName];
65
+
66
+ if (!branchCommitSha) {
67
+ console.error(chalk.red(`Error: Branch '${branchName}' not found`));
68
+ process.exit(1);
69
+ }
70
+
71
+ // Get commits to push (find all commits in the branch)
72
+ const commitsToPush = [];
73
+ let currentSha = branchCommitSha;
74
+
75
+ while (currentSha) {
76
+ const commit = commits.find(c => c.sha === currentSha);
77
+ if (!commit) break;
78
+
79
+ commitsToPush.unshift(commit); // Add to beginning to maintain order
80
+ currentSha = commit.parent ? commit.parent[0] : null;
81
+ }
82
+
83
+ if (commitsToPush.length === 0) {
84
+ console.log(chalk.yellow('Nothing to push'));
85
+ return;
86
+ }
87
+
88
+ console.log(chalk.cyan(`Pushing ${commitsToPush.length} commit(s) to ${remoteName}/${branchName}...`));
89
+
90
+ // Verify repository exists
91
+ const spinner = ora('Verifying remote repository...').start();
92
+ try {
93
+ await repoService.getRepository(remote.owner_id, remote.repo_name);
94
+ spinner.succeed('Remote repository verified');
95
+ } catch (error) {
96
+ spinner.fail('Remote repository not found or access denied');
97
+ throw error;
98
+ }
99
+
100
+ // Push commits
101
+ await syncCommitsToCloud(commitsToPush, remote.owner_id, remote.repo_name, branchName, cwd);
102
+
103
+ console.log(chalk.green(`\n✓ Successfully pushed to ${remoteName}/${branchName}`));
104
+
105
+ } catch (error) {
106
+ console.error(chalk.red('Failed to push'));
107
+ console.error(chalk.red('Error:'), error.message);
108
+ process.exit(1);
109
+ }
110
+ }
111
+
112
+ module.exports = push;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Remote Command - Manage remote repositories
3
+ * Add, remove, and list remote repository configurations
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const path = require('path');
8
+ const ora = require('ora');
9
+ const { getRemoteConfig, addRemote, removeRemote } = require('../utils/cloud-sync');
10
+ const { GENT_DIR } = require('../utils/constants');
11
+ const { pathExists } = require('../utils/fileSystem');
12
+ const repoService = require('../services/repo-service');
13
+ const authStorage = require('../utils/auth-storage');
14
+
15
+ /**
16
+ * Manage remote repositories
17
+ * @param {string} action - Action to perform (add, remove, or list)
18
+ * @param {string} name - Remote name
19
+ * @param {string} url - Remote URL (owner_id/repo_name)
20
+ * @param {Object} options - Command options
21
+ */
22
+ async function remote(action, name, url, options) {
23
+ try {
24
+ const cwd = process.cwd();
25
+ const gentPath = path.join(cwd, GENT_DIR);
26
+
27
+ // Check if in a gent repository
28
+ if (!(await pathExists(gentPath))) {
29
+ console.error(chalk.red('Error: Not a gent repository'));
30
+ console.log(chalk.yellow('Run'), chalk.cyan('gent init'), chalk.yellow('to initialize a repository'));
31
+ process.exit(1);
32
+ }
33
+
34
+ // List remotes (default action)
35
+ if (!action || action === 'list' || options.verbose) {
36
+ const config = await getRemoteConfig(cwd);
37
+ const remotes = Object.keys(config.remotes || {});
38
+
39
+ if (remotes.length === 0) {
40
+ console.log(chalk.yellow('No remotes configured'));
41
+ console.log(chalk.gray('Add a remote with:'), chalk.cyan('gent remote add <name> <owner_id>/<repo_name>'));
42
+ return;
43
+ }
44
+
45
+ console.log(chalk.cyan('Configured remotes:\n'));
46
+ for (const remoteName of remotes) {
47
+ const remote = config.remotes[remoteName];
48
+ if (options.verbose) {
49
+ console.log(chalk.bold(remoteName));
50
+ console.log(chalk.gray(` Owner ID: ${remote.owner_id}`));
51
+ console.log(chalk.gray(` Repository: ${remote.repo_name}`));
52
+ console.log(chalk.gray(` URL: ${remote.owner_id}/${remote.repo_name}\n`));
53
+ } else {
54
+ console.log(`${remoteName}\t${remote.owner_id}/${remote.repo_name}`);
55
+ }
56
+ }
57
+ return;
58
+ }
59
+
60
+ // Add remote
61
+ if (action === 'add') {
62
+ if (!name || !url) {
63
+ console.error(chalk.red('Error: Remote name and URL are required'));
64
+ console.log(chalk.yellow('Usage:'), chalk.cyan('gent remote add <name> <owner_id>/<repo_name>'));
65
+ process.exit(1);
66
+ }
67
+
68
+ // Parse owner_id/repo_name
69
+ const parts = url.split('/');
70
+ if (parts.length !== 2) {
71
+ console.error(chalk.red('Error: Invalid remote URL format'));
72
+ console.log(chalk.yellow('Expected:'), chalk.cyan('<owner_id>/<repo_name>'));
73
+ process.exit(1);
74
+ }
75
+
76
+ const ownerId = parseInt(parts[0]);
77
+ const repoName = parts[1];
78
+
79
+ if (isNaN(ownerId)) {
80
+ console.error(chalk.red('Error: Owner ID must be a number'));
81
+ process.exit(1);
82
+ }
83
+
84
+ // Check authentication
85
+ const user = await authStorage.getUser();
86
+ if (!user) {
87
+ console.error(chalk.red('Error: You must be logged in to add a remote'));
88
+ console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
89
+ process.exit(1);
90
+ }
91
+
92
+ // Verify repository exists
93
+ const spinner = ora('Verifying repository...').start();
94
+ try {
95
+ await repoService.getRepository(ownerId, repoName);
96
+ spinner.succeed('Repository verified');
97
+ } catch (error) {
98
+ spinner.fail('Repository not found or access denied');
99
+ throw error;
100
+ }
101
+
102
+ // Add remote
103
+ await addRemote(name, ownerId, repoName, cwd);
104
+ console.log(chalk.green(`✓ Remote '${name}' added: ${ownerId}/${repoName}`));
105
+ return;
106
+ }
107
+
108
+ // Remove remote
109
+ if (action === 'remove' || action === 'rm') {
110
+ if (!name) {
111
+ console.error(chalk.red('Error: Remote name is required'));
112
+ console.log(chalk.yellow('Usage:'), chalk.cyan('gent remote remove <name>'));
113
+ process.exit(1);
114
+ }
115
+
116
+ await removeRemote(name, cwd);
117
+ console.log(chalk.green(`✓ Remote '${name}' removed`));
118
+ return;
119
+ }
120
+
121
+ // Unknown action
122
+ console.error(chalk.red(`Error: Unknown action '${action}'`));
123
+ console.log(chalk.yellow('Available actions:'), chalk.cyan('add, remove, list'));
124
+ process.exit(1);
125
+
126
+ } catch (error) {
127
+ console.error(chalk.red('Failed to manage remote'));
128
+ console.error(chalk.red('Error:'), error.message);
129
+ process.exit(1);
130
+ }
131
+ }
132
+
133
+ module.exports = remote;
package/src/index.js CHANGED
@@ -27,6 +27,14 @@ const loginCommand = require('./commands/login');
27
27
  const logoutCommand = require('./commands/logout');
28
28
  const whoamiCommand = require('./commands/whoami');
29
29
 
30
+ // Import cloud repository commands
31
+ const createCommand = require('./commands/create');
32
+ const listCommand = require('./commands/list');
33
+ const cloneCommand = require('./commands/clone');
34
+ const pushCommand = require('./commands/push');
35
+ const pullCommand = require('./commands/pull');
36
+ const remoteCommand = require('./commands/remote');
37
+
30
38
  // Configure CLI
31
39
  program
32
40
  .name('gent')
@@ -38,6 +46,9 @@ program
38
46
  .command('init')
39
47
  .description('Initialize a new gent repository')
40
48
  .option('-y, --yes', 'Skip prompts and use defaults')
49
+ .option('--cloud', 'Create a corresponding cloud repository')
50
+ .option('-d, --description <description>', 'Repository description (for cloud)')
51
+ .option('-p, --private', 'Make cloud repository private')
41
52
  .action(initCommand);
42
53
 
43
54
  program
@@ -57,6 +68,7 @@ program
57
68
  .description('Record changes to the repository')
58
69
  .option('-m, --message <message>', 'Commit message')
59
70
  .option('-a, --all', 'Automatically stage all modified files')
71
+ .option('--push', 'Push to remote after commit')
60
72
  .action(commitCommand);
61
73
 
62
74
  program
@@ -103,6 +115,43 @@ program
103
115
  .description('Display current user information')
104
116
  .action(whoamiCommand);
105
117
 
118
+ // Cloud repository commands
119
+ program
120
+ .command('create <repo-name>')
121
+ .description('Create a new cloud repository')
122
+ .option('-d, --description <description>', 'Repository description')
123
+ .option('-p, --private', 'Make repository private')
124
+ .option('-y, --yes', 'Skip prompts and use defaults')
125
+ .option('--init-local', 'Initialize local repository and link remote')
126
+ .action(createCommand);
127
+
128
+ program
129
+ .command('list')
130
+ .alias('ls')
131
+ .description('List all your cloud repositories')
132
+ .action(listCommand);
133
+
134
+ program
135
+ .command('clone <repo-url> [directory]')
136
+ .description('Clone a cloud repository (format: owner_id/repo_name)')
137
+ .action(cloneCommand);
138
+
139
+ program
140
+ .command('push [remote] [branch]')
141
+ .description('Push local commits to cloud')
142
+ .action(pushCommand);
143
+
144
+ program
145
+ .command('pull [remote] [branch]')
146
+ .description('Pull commits from cloud to local')
147
+ .action(pullCommand);
148
+
149
+ program
150
+ .command('remote [action] [name] [url]')
151
+ .description('Manage remote repositories')
152
+ .option('-v, --verbose', 'Show verbose output')
153
+ .action(remoteCommand);
154
+
106
155
  // Help command
107
156
  program
108
157
  .command('help [command]')