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.
- package/README.md +47 -33
- package/package.json +1 -1
- package/src/commands/clone.js +133 -0
- package/src/commands/commit.js +45 -3
- package/src/commands/create.js +118 -0
- package/src/commands/init.js +33 -5
- package/src/commands/list.js +67 -0
- package/src/commands/pull.js +123 -0
- package/src/commands/push.js +112 -0
- package/src/commands/remote.js +133 -0
- package/src/index.js +49 -0
- package/src/services/repo-service.js +283 -0
- package/src/utils/cloud-sync.js +316 -0
- package/src/utils/constants.js +28 -1
- package/src/utils/diff.js +121 -0
- package/src/utils/fileSystem.js +46 -2
package/README.md
CHANGED
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
> A modern, Git-like version control CLI with built-in cloud authentication and global user identity management.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Gent is a lightweight version control system that feels exactly like Git but handles user identity automatically through the cloud. No more configuring `user.name` and `user.email` for every repository!
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Features
|
|
8
8
|
|
|
9
9
|
- **Cloud Authentication**: Login once, work everywhere. Your identity follows you across projects.
|
|
10
|
-
- **Git-like Experience**: Familiar commands (
|
|
10
|
+
- **Git-like Experience**: Familiar commands (init, add, commit, status, log, branch, checkout).
|
|
11
|
+
- **Cloud Synchronization**: Push and pull repositories to/from the cloud.
|
|
11
12
|
- **Zero Configuration**: `gent init` is silent and auto-detects your authenticated user profile.
|
|
12
13
|
- **Global Identity**: Commits are automatically authored with your cloud profile.
|
|
13
14
|
- **Secure**: Tokens stored securely in your home directory.
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Installation
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
19
|
npm install -g gent-cli
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
##
|
|
22
|
+
## Authentication
|
|
22
23
|
|
|
23
24
|
Gent uses a global authentication system. You only need to login once.
|
|
24
25
|
|
|
@@ -48,12 +49,10 @@ gent whoami
|
|
|
48
49
|
gent logout
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
##
|
|
52
|
+
## Local Usage
|
|
52
53
|
|
|
53
54
|
### 1. Initialize a Repository
|
|
54
55
|
|
|
55
|
-
Just like Git, `gent init` is silent and sets up a new repository in your current directory. It automatically uses your logged-in identity for configuration.
|
|
56
|
-
|
|
57
56
|
```bash
|
|
58
57
|
gent init
|
|
59
58
|
# Output: Initialized empty Gent repository in /path/to/project
|
|
@@ -61,16 +60,12 @@ gent init
|
|
|
61
60
|
|
|
62
61
|
### 2. Check Status
|
|
63
62
|
|
|
64
|
-
See which files are modified or untracked.
|
|
65
|
-
|
|
66
63
|
```bash
|
|
67
64
|
gent status
|
|
68
65
|
```
|
|
69
66
|
|
|
70
67
|
### 3. Stage Files
|
|
71
68
|
|
|
72
|
-
Add files to the staging area.
|
|
73
|
-
|
|
74
69
|
```bash
|
|
75
70
|
gent add filename.js
|
|
76
71
|
# or add all files
|
|
@@ -79,43 +74,61 @@ gent add .
|
|
|
79
74
|
|
|
80
75
|
### 4. Commit Changes
|
|
81
76
|
|
|
82
|
-
Create a commit. Gent automatically fetches your name and email from your global login session.
|
|
83
|
-
|
|
84
77
|
```bash
|
|
85
78
|
gent commit -m "Initial commit"
|
|
86
79
|
# Output: [main a1b2c3d] Initial commit
|
|
87
80
|
# Author: Your Name <your.email@example.com>
|
|
88
81
|
```
|
|
89
82
|
|
|
90
|
-
|
|
83
|
+
## Cloud Features
|
|
84
|
+
|
|
85
|
+
### 1. Create a Cloud Repository
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Create and link a local repo
|
|
89
|
+
gent create my-repo --init-local
|
|
90
|
+
|
|
91
|
+
# Or initialize with cloud directly
|
|
92
|
+
gent init --cloud
|
|
93
|
+
```
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
### 2. List Your Repositories
|
|
93
96
|
|
|
94
97
|
```bash
|
|
95
|
-
gent
|
|
96
|
-
# or
|
|
97
|
-
gent
|
|
98
|
+
gent list
|
|
99
|
+
# or
|
|
100
|
+
gent ls
|
|
98
101
|
```
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
### 3. Clone a Repository
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
```bash
|
|
106
|
+
gent clone <owner_id>/<repo_name>
|
|
107
|
+
# Example: gent clone 1/my-repo
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 4. Push Changes
|
|
103
111
|
|
|
104
112
|
```bash
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
gent push
|
|
114
|
+
# or commit and push in one go
|
|
115
|
+
gent commit -m "Update README" --push
|
|
116
|
+
```
|
|
107
117
|
|
|
108
|
-
|
|
109
|
-
gent branch
|
|
118
|
+
### 5. Pull Changes
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
gent
|
|
120
|
+
```bash
|
|
121
|
+
gent pull
|
|
122
|
+
```
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
### 6. Manage Remotes
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
gent remote add origin <owner_id>/<repo_name>
|
|
128
|
+
gent remote -v
|
|
116
129
|
```
|
|
117
130
|
|
|
118
|
-
##
|
|
131
|
+
## Repository Structure
|
|
119
132
|
|
|
120
133
|
Gent creates a `.gent` directory in your project root:
|
|
121
134
|
|
|
@@ -124,19 +137,20 @@ Gent creates a `.gent` directory in your project root:
|
|
|
124
137
|
├── config.json # Project configuration
|
|
125
138
|
├── objects/ # Stored file contents
|
|
126
139
|
├── refs/ # Branch pointers
|
|
140
|
+
├── remote.json # Remote configuration
|
|
127
141
|
└── HEAD # Current branch reference
|
|
128
142
|
```
|
|
129
143
|
|
|
130
144
|
Your authentication tokens are stored globally in `~/.gent/auth.json`.
|
|
131
145
|
|
|
132
|
-
##
|
|
146
|
+
## Contributing
|
|
133
147
|
|
|
134
148
|
We welcome contributions! Please fork the repository and submit a Pull Request.
|
|
135
149
|
|
|
136
|
-
##
|
|
150
|
+
## License
|
|
137
151
|
|
|
138
152
|
ISC
|
|
139
153
|
|
|
140
154
|
---
|
|
141
155
|
|
|
142
|
-
Built with
|
|
156
|
+
Built with love by [Abdalrahman Kanawati](https://github.com/abdo-ka)
|
package/package.json
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone Command - Clone a cloud repository to local
|
|
3
|
+
* Downloads repository data and sets up local working copy
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises;
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const repoService = require('../services/repo-service');
|
|
11
|
+
const authStorage = require('../utils/auth-storage');
|
|
12
|
+
const { ensureDir, writeJSON, pathExists } = require('../utils/fileSystem');
|
|
13
|
+
const { GENT_DIR, CONFIG_FILE, STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
|
|
14
|
+
const { addRemote, syncCommitsFromCloud } = require('../utils/cloud-sync');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Clone a cloud repository
|
|
18
|
+
* @param {string} repoUrl - Repository URL (owner_id/repo_name)
|
|
19
|
+
* @param {string} directory - Target directory (optional)
|
|
20
|
+
* @param {Object} options - Command options
|
|
21
|
+
*/
|
|
22
|
+
async function clone(repoUrl, directory, options) {
|
|
23
|
+
try {
|
|
24
|
+
// Check authentication
|
|
25
|
+
const user = await authStorage.getUser();
|
|
26
|
+
if (!user) {
|
|
27
|
+
console.error(chalk.red('Error: You must be logged in to clone a repository'));
|
|
28
|
+
console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Parse repository URL
|
|
33
|
+
if (!repoUrl) {
|
|
34
|
+
console.error(chalk.red('Error: Repository URL is required'));
|
|
35
|
+
console.log(chalk.yellow('Usage:'), chalk.cyan('gent clone <owner_id>/<repo_name> [directory]'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const parts = repoUrl.split('/');
|
|
40
|
+
if (parts.length !== 2) {
|
|
41
|
+
console.error(chalk.red('Error: Invalid repository URL format'));
|
|
42
|
+
console.log(chalk.yellow('Expected:'), chalk.cyan('<owner_id>/<repo_name>'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ownerId = parseInt(parts[0]);
|
|
47
|
+
const repoName = parts[1];
|
|
48
|
+
|
|
49
|
+
if (isNaN(ownerId)) {
|
|
50
|
+
console.error(chalk.red('Error: Owner ID must be a number'));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Determine target directory
|
|
55
|
+
const targetDir = directory || repoName;
|
|
56
|
+
const targetPath = path.resolve(process.cwd(), targetDir);
|
|
57
|
+
|
|
58
|
+
// Check if directory already exists
|
|
59
|
+
if (await pathExists(targetPath)) {
|
|
60
|
+
console.error(chalk.red(`Error: Directory '${targetDir}' already exists`));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(chalk.cyan(`Cloning ${ownerId}/${repoName} into '${targetDir}'...\n`));
|
|
65
|
+
|
|
66
|
+
// Fetch repository metadata
|
|
67
|
+
const spinner = ora('Fetching repository...').start();
|
|
68
|
+
const repository = await repoService.getRepository(ownerId, repoName);
|
|
69
|
+
spinner.succeed('Repository fetched');
|
|
70
|
+
|
|
71
|
+
// Create directory structure
|
|
72
|
+
await ensureDir(targetPath);
|
|
73
|
+
const gentPath = path.join(targetPath, GENT_DIR);
|
|
74
|
+
await ensureDir(gentPath);
|
|
75
|
+
await ensureDir(path.join(gentPath, 'objects'));
|
|
76
|
+
await ensureDir(path.join(gentPath, 'refs', 'heads'));
|
|
77
|
+
await ensureDir(path.join(gentPath, 'refs', 'tags'));
|
|
78
|
+
|
|
79
|
+
// Create configuration
|
|
80
|
+
const config = {
|
|
81
|
+
user: {
|
|
82
|
+
name: user.first_name && user.last_name ? `${user.first_name} ${user.last_name}` : '',
|
|
83
|
+
email: user.email
|
|
84
|
+
},
|
|
85
|
+
repository: {
|
|
86
|
+
name: repository.name,
|
|
87
|
+
description: repository.description,
|
|
88
|
+
created: new Date().toISOString()
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
await writeJSON(path.join(gentPath, CONFIG_FILE), config);
|
|
92
|
+
|
|
93
|
+
// Create initial files
|
|
94
|
+
await writeJSON(path.join(gentPath, STAGING_FILE), { files: [] });
|
|
95
|
+
await writeJSON(path.join(gentPath, COMMITS_FILE), {
|
|
96
|
+
commits: [],
|
|
97
|
+
branches: { [repository.default_branch]: null },
|
|
98
|
+
currentBranch: repository.default_branch
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Create HEAD file
|
|
102
|
+
await fs.writeFile(
|
|
103
|
+
path.join(gentPath, 'HEAD'),
|
|
104
|
+
`ref: refs/heads/${repository.default_branch}\n`
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Add remote
|
|
108
|
+
await addRemote('origin', ownerId, repoName, targetPath);
|
|
109
|
+
|
|
110
|
+
// Fetch commits, branches, and files
|
|
111
|
+
try {
|
|
112
|
+
await syncCommitsFromCloud(ownerId, repoName, targetPath);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Ignore if repository has no commits yet
|
|
115
|
+
if (!error.message.includes('404')) {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
console.log(chalk.yellow('Note: Repository has no commits yet'));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(chalk.green(`\n✓ Successfully cloned into '${targetDir}'`));
|
|
122
|
+
console.log(chalk.yellow('\nNext steps:'));
|
|
123
|
+
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
124
|
+
console.log(chalk.cyan(' gent status'), chalk.gray('- Check repository status'));
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(chalk.red('Failed to clone repository'));
|
|
128
|
+
console.error(chalk.red('Error:'), error.message);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = clone;
|
package/src/commands/commit.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const fs = require('fs').promises;
|
|
7
8
|
const chalk = require('chalk');
|
|
8
9
|
const inquirer = require('inquirer');
|
|
9
10
|
const ora = require('ora');
|
|
10
|
-
const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
|
|
11
|
+
const { getGentPath, readJSON, writeJSON, saveBlob, getBlob } = require('../utils/fileSystem');
|
|
11
12
|
const authStorage = require('../utils/auth-storage');
|
|
12
13
|
const { STAGING_FILE, COMMITS_FILE, CONFIG_FILE } = require('../utils/constants');
|
|
13
14
|
const { generateCommitHash, getFileHash } = require('../utils/helpers');
|
|
15
|
+
const { computeDiff } = require('../utils/diff');
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Create a new commit
|
|
@@ -91,13 +93,47 @@ async function commit(options) {
|
|
|
91
93
|
files: []
|
|
92
94
|
};
|
|
93
95
|
|
|
94
|
-
//
|
|
96
|
+
// Create a map of parent files for quick lookup
|
|
97
|
+
const parentFilesMap = new Map();
|
|
98
|
+
if (commit.parent) {
|
|
99
|
+
const parentCommit = repository.commits.find(c => c.hash === commit.parent);
|
|
100
|
+
if (parentCommit) {
|
|
101
|
+
parentCommit.files.forEach(f => parentFilesMap.set(f.path, f.hash));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Hash staged files and compute diffs
|
|
95
106
|
for (const file of stagedFiles) {
|
|
96
107
|
const filePath = path.join(cwd, file);
|
|
108
|
+
let content;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
content = await fs.readFile(filePath, 'utf-8');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
// If file is deleted or unreadable, we handle it as empty content or handle deletion if we supported it
|
|
114
|
+
// For now assuming staged files exist
|
|
115
|
+
content = '';
|
|
116
|
+
}
|
|
117
|
+
|
|
97
118
|
const hash = await getFileHash(filePath);
|
|
119
|
+
|
|
120
|
+
// Save the blob for future diffs
|
|
121
|
+
await saveBlob(content, hash);
|
|
122
|
+
|
|
123
|
+
// Get previous content
|
|
124
|
+
let oldContent = '';
|
|
125
|
+
if (parentFilesMap.has(file)) {
|
|
126
|
+
const oldHash = parentFilesMap.get(file);
|
|
127
|
+
oldContent = await getBlob(oldHash);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Compute Myers diff
|
|
131
|
+
const changes = computeDiff(oldContent, content);
|
|
132
|
+
|
|
98
133
|
commit.files.push({
|
|
99
134
|
path: file,
|
|
100
|
-
hash: hash
|
|
135
|
+
hash: hash,
|
|
136
|
+
diff: changes
|
|
101
137
|
});
|
|
102
138
|
}
|
|
103
139
|
|
|
@@ -119,6 +155,12 @@ async function commit(options) {
|
|
|
119
155
|
console.log(chalk.gray(`Date: ${new Date(commit.timestamp).toLocaleString()}`));
|
|
120
156
|
console.log(chalk.gray(`\n${commit.files.length} file(s) changed`));
|
|
121
157
|
|
|
158
|
+
// Handle --push option
|
|
159
|
+
if (options.push) {
|
|
160
|
+
const push = require('./push');
|
|
161
|
+
await push('origin', repository.currentBranch, {});
|
|
162
|
+
}
|
|
163
|
+
|
|
122
164
|
} catch (error) {
|
|
123
165
|
if (error.code === 'ENOENT' && error.message.includes('.gent')) {
|
|
124
166
|
console.error(chalk.red('Error: Not a gent repository'));
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Command - Create a new cloud repository
|
|
3
|
+
* Creates a repository on the cloud and optionally initializes locally
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const repoService = require('../services/repo-service');
|
|
11
|
+
const authStorage = require('../utils/auth-storage');
|
|
12
|
+
const { ensureDir, writeJSON } = require('../utils/fileSystem');
|
|
13
|
+
const { GENT_DIR } = require('../utils/constants');
|
|
14
|
+
const { addRemote } = require('../utils/cloud-sync');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a new repository on the cloud
|
|
18
|
+
* @param {string} repoName - Repository name
|
|
19
|
+
* @param {Object} options - Command options
|
|
20
|
+
*/
|
|
21
|
+
async function create(repoName, options) {
|
|
22
|
+
try {
|
|
23
|
+
// Check authentication
|
|
24
|
+
const user = await authStorage.getUser();
|
|
25
|
+
if (!user) {
|
|
26
|
+
console.error(chalk.red('Error: You must be logged in to create a repository'));
|
|
27
|
+
console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate repository name
|
|
32
|
+
if (!repoName) {
|
|
33
|
+
console.error(chalk.red('Error: Repository name is required'));
|
|
34
|
+
console.log(chalk.yellow('Usage:'), chalk.cyan('gent create <repo-name>'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Prompt for additional details if not provided
|
|
39
|
+
let description = options.description || '';
|
|
40
|
+
let isPrivate = options.private || false;
|
|
41
|
+
|
|
42
|
+
if (!options.yes) {
|
|
43
|
+
const answers = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'input',
|
|
46
|
+
name: 'description',
|
|
47
|
+
message: 'Repository description (optional):',
|
|
48
|
+
default: description
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'confirm',
|
|
52
|
+
name: 'isPrivate',
|
|
53
|
+
message: 'Make repository private?',
|
|
54
|
+
default: isPrivate
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'initLocal',
|
|
59
|
+
message: 'Initialize local repository and link remote?',
|
|
60
|
+
default: true
|
|
61
|
+
}
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
description = answers.description;
|
|
65
|
+
isPrivate = answers.isPrivate;
|
|
66
|
+
options.initLocal = answers.initLocal;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Create repository
|
|
70
|
+
const spinner = ora('Creating repository...').start();
|
|
71
|
+
|
|
72
|
+
const repository = await repoService.createRepository(
|
|
73
|
+
repoName,
|
|
74
|
+
description,
|
|
75
|
+
isPrivate,
|
|
76
|
+
'main'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
spinner.succeed(chalk.green(`Repository created: ${repository.name}`));
|
|
80
|
+
|
|
81
|
+
console.log(chalk.cyan('\nRepository Details:'));
|
|
82
|
+
console.log(chalk.gray(' Name:'), repository.name);
|
|
83
|
+
console.log(chalk.gray(' Owner:'), repository.owner_email);
|
|
84
|
+
console.log(chalk.gray(' Private:'), repository.is_private ? 'Yes' : 'No');
|
|
85
|
+
console.log(chalk.gray(' Description:'), repository.description || '(none)');
|
|
86
|
+
console.log(chalk.gray(' Created:'), new Date(repository.created_at).toLocaleString());
|
|
87
|
+
|
|
88
|
+
// Initialize local repository if requested
|
|
89
|
+
if (options.initLocal) {
|
|
90
|
+
const cwd = process.cwd();
|
|
91
|
+
const gentPath = path.join(cwd, GENT_DIR);
|
|
92
|
+
|
|
93
|
+
// Create .gent directory
|
|
94
|
+
await ensureDir(gentPath);
|
|
95
|
+
await ensureDir(path.join(gentPath, 'objects'));
|
|
96
|
+
await ensureDir(path.join(gentPath, 'refs', 'heads'));
|
|
97
|
+
|
|
98
|
+
// Add remote
|
|
99
|
+
await addRemote('origin', repository.owner_id, repository.name, cwd);
|
|
100
|
+
|
|
101
|
+
console.log(chalk.green('\n✓ Local repository initialized and remote added'));
|
|
102
|
+
console.log(chalk.yellow('\nNext steps:'));
|
|
103
|
+
console.log(chalk.cyan(' gent add <files>'), chalk.gray('- Add files to staging'));
|
|
104
|
+
console.log(chalk.cyan(' gent commit -m "message"'), chalk.gray('- Commit changes'));
|
|
105
|
+
console.log(chalk.cyan(' gent push'), chalk.gray('- Push to cloud'));
|
|
106
|
+
} else {
|
|
107
|
+
console.log(chalk.yellow('\nTo clone this repository:'));
|
|
108
|
+
console.log(chalk.cyan(` gent clone ${repository.owner_id}/${repository.name}`));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(chalk.red('Failed to create repository'));
|
|
113
|
+
console.error(chalk.red('Error:'), error.message);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = create;
|
package/src/commands/init.js
CHANGED
|
@@ -28,9 +28,10 @@ async function init(options) {
|
|
|
28
28
|
// Get authenticated user profile if available
|
|
29
29
|
let defaultName = '';
|
|
30
30
|
let defaultEmail = '';
|
|
31
|
+
let user = null;
|
|
31
32
|
|
|
32
33
|
try {
|
|
33
|
-
|
|
34
|
+
user = await authStorage.getUser();
|
|
34
35
|
if (user) {
|
|
35
36
|
if (user.first_name || user.last_name) {
|
|
36
37
|
defaultName = [user.first_name, user.last_name].filter(Boolean).join(' ');
|
|
@@ -62,12 +63,8 @@ async function init(options) {
|
|
|
62
63
|
await ensureDir(path.join(gentPath, 'refs', 'tags'));
|
|
63
64
|
|
|
64
65
|
// Create/Update configuration
|
|
65
|
-
// Only write config if it doesn't exist OR if we have valid user info to update
|
|
66
66
|
const configPath = path.join(gentPath, CONFIG_FILE);
|
|
67
67
|
if (!(await pathExists(configPath)) || (defaultName && defaultEmail)) {
|
|
68
|
-
// If re-init, we might want to preserve existing config unless we have better info?
|
|
69
|
-
// Git re-init doesn't overwrite config usually.
|
|
70
|
-
// But for now, let's write ensuring we have a config file.
|
|
71
68
|
if (!isReinit || !(await pathExists(configPath))) {
|
|
72
69
|
await writeJSON(configPath, config);
|
|
73
70
|
}
|
|
@@ -109,6 +106,37 @@ node_modules/
|
|
|
109
106
|
console.log(chalk.gray(`Initialized empty Gent repository in ${gentPath}`));
|
|
110
107
|
}
|
|
111
108
|
|
|
109
|
+
// Handle --cloud option
|
|
110
|
+
if (options.cloud) {
|
|
111
|
+
if (!user) {
|
|
112
|
+
console.log(chalk.yellow('\nWarning: Skipping cloud repository creation (not logged in)'));
|
|
113
|
+
console.log(chalk.gray('Run "gent login" then "gent create <name> --init-local"'));
|
|
114
|
+
} else {
|
|
115
|
+
try {
|
|
116
|
+
const repoService = require('../services/repo-service');
|
|
117
|
+
const { addRemote } = require('../utils/cloud-sync');
|
|
118
|
+
const ora = require('ora');
|
|
119
|
+
|
|
120
|
+
const spinner = ora('Creating cloud repository...').start();
|
|
121
|
+
const repoName = path.basename(cwd);
|
|
122
|
+
|
|
123
|
+
const repository = await repoService.createRepository(
|
|
124
|
+
repoName,
|
|
125
|
+
options.description || 'A gent repository',
|
|
126
|
+
options.private || false,
|
|
127
|
+
'main'
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await addRemote('origin', repository.owner_id, repository.name, cwd);
|
|
131
|
+
spinner.succeed(chalk.green(`Created cloud repository: ${repository.owner_id}/${repository.name}`));
|
|
132
|
+
console.log(chalk.gray(`Remote 'origin' added`));
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.log(chalk.red(`\nFailed to create cloud repository: ${error.message}`));
|
|
135
|
+
console.log(chalk.gray('You can create it later with "gent create <name>"'));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
112
140
|
} catch (error) {
|
|
113
141
|
console.error(chalk.red('Failed to initialize repository'));
|
|
114
142
|
console.error(chalk.red('Error:'), error.message);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Command - List all cloud repositories
|
|
3
|
+
* Displays all repositories owned by the authenticated user
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const repoService = require('../services/repo-service');
|
|
9
|
+
const authStorage = require('../utils/auth-storage');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format date for display
|
|
13
|
+
* @param {string} dateString - ISO date string
|
|
14
|
+
* @returns {string} Formatted date
|
|
15
|
+
*/
|
|
16
|
+
function formatDate(dateString) {
|
|
17
|
+
const date = new Date(dateString);
|
|
18
|
+
return date.toLocaleDateString();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* List all repositories owned by the authenticated user
|
|
23
|
+
* @param {Object} options - Command options
|
|
24
|
+
*/
|
|
25
|
+
async function list(options) {
|
|
26
|
+
try {
|
|
27
|
+
// Check authentication
|
|
28
|
+
const user = await authStorage.getUser();
|
|
29
|
+
if (!user) {
|
|
30
|
+
console.error(chalk.red('Error: You must be logged in to list repositories'));
|
|
31
|
+
console.log(chalk.yellow('Run'), chalk.cyan('gent login'), chalk.yellow('to authenticate'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Fetch repositories
|
|
36
|
+
const spinner = ora('Fetching repositories...').start();
|
|
37
|
+
const repositories = await repoService.listRepositories();
|
|
38
|
+
spinner.stop();
|
|
39
|
+
|
|
40
|
+
if (repositories.length === 0) {
|
|
41
|
+
console.log(chalk.yellow('No repositories found'));
|
|
42
|
+
console.log(chalk.gray('Create a new repository with:'), chalk.cyan('gent create <repo-name>'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.cyan(`\nYour Repositories (${repositories.length}):\n`));
|
|
47
|
+
|
|
48
|
+
repositories.forEach(repo => {
|
|
49
|
+
const privacy = repo.is_private ? chalk.red('🔒 Private') : chalk.green('🌐 Public');
|
|
50
|
+
console.log(chalk.bold(repo.name), privacy);
|
|
51
|
+
if (repo.description) {
|
|
52
|
+
console.log(chalk.gray(` ${repo.description}`));
|
|
53
|
+
}
|
|
54
|
+
console.log(chalk.gray(` Owner: ${repo.owner_email}`));
|
|
55
|
+
console.log(chalk.gray(` Created: ${formatDate(repo.created_at)}`));
|
|
56
|
+
console.log(chalk.gray(` Default branch: ${repo.default_branch}`));
|
|
57
|
+
console.log(chalk.gray(` Clone: gent clone ${repo.owner_id}/${repo.name}\n`));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(chalk.red('Failed to list repositories'));
|
|
62
|
+
console.error(chalk.red('Error:'), error.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = list;
|