gent-cli 1.0.0 ā 1.2.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/QUICKSTART.md +198 -0
- package/package.json +23 -10
- package/src/commands/login.js +85 -0
- package/src/commands/logout.js +40 -0
- package/src/commands/register.js +109 -0
- package/src/commands/whoami.js +69 -0
- package/src/index.js +29 -0
- package/src/services/auth-service.js +177 -0
- package/src/utils/api-client.js +179 -0
- package/src/utils/auth-storage.js +168 -0
- package/src/utils/constants.js +11 -0
- package/commands-reference.js +0 -116
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Quick Start Guide - Gent CLI
|
|
2
|
+
|
|
3
|
+
## Installation & Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Navigate to the CLI directory
|
|
7
|
+
cd apps/Cli
|
|
8
|
+
|
|
9
|
+
# Install dependencies
|
|
10
|
+
npm install
|
|
11
|
+
|
|
12
|
+
# Test the CLI
|
|
13
|
+
node src/index.js --help
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Make it Globally Available (Optional)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Link the CLI globally
|
|
20
|
+
npm link
|
|
21
|
+
|
|
22
|
+
# Now you can use 'gent' from anywhere
|
|
23
|
+
gent --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Your First Repository
|
|
27
|
+
|
|
28
|
+
### 1. Initialize
|
|
29
|
+
```bash
|
|
30
|
+
# Create a new directory
|
|
31
|
+
mkdir my-project
|
|
32
|
+
cd my-project
|
|
33
|
+
|
|
34
|
+
# Initialize gent repository
|
|
35
|
+
gent init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
You'll be prompted for:
|
|
39
|
+
- Your name
|
|
40
|
+
- Your email
|
|
41
|
+
- Repository name
|
|
42
|
+
- Repository description
|
|
43
|
+
|
|
44
|
+
Or skip prompts with `-y`:
|
|
45
|
+
```bash
|
|
46
|
+
gent init -y
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Add Files
|
|
50
|
+
```bash
|
|
51
|
+
# Create some files
|
|
52
|
+
echo "console.log('Hello');" > index.js
|
|
53
|
+
|
|
54
|
+
# Add to staging area
|
|
55
|
+
gent add index.js
|
|
56
|
+
|
|
57
|
+
# Or add all files
|
|
58
|
+
gent add .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Check Status
|
|
62
|
+
```bash
|
|
63
|
+
gent status
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Commit Changes
|
|
67
|
+
```bash
|
|
68
|
+
# With message flag
|
|
69
|
+
gent commit -m "Initial commit"
|
|
70
|
+
|
|
71
|
+
# Or interactive
|
|
72
|
+
gent commit
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 5. View History
|
|
76
|
+
```bash
|
|
77
|
+
# See all commits
|
|
78
|
+
gent log
|
|
79
|
+
|
|
80
|
+
# Compact view
|
|
81
|
+
gent log --oneline
|
|
82
|
+
|
|
83
|
+
# Limit commits shown
|
|
84
|
+
gent log -n 5
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Working with Branches
|
|
88
|
+
|
|
89
|
+
### Create a Branch
|
|
90
|
+
```bash
|
|
91
|
+
gent branch feature-name
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Switch to Branch
|
|
95
|
+
```bash
|
|
96
|
+
gent checkout feature-name
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Create and Switch
|
|
100
|
+
```bash
|
|
101
|
+
gent checkout -b new-feature
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### List Branches
|
|
105
|
+
```bash
|
|
106
|
+
gent branch
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Delete Branch
|
|
110
|
+
```bash
|
|
111
|
+
gent branch -d old-feature
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Common Workflows
|
|
115
|
+
|
|
116
|
+
### Feature Development
|
|
117
|
+
```bash
|
|
118
|
+
# Start a new feature
|
|
119
|
+
gent checkout -b feature-login
|
|
120
|
+
|
|
121
|
+
# Make changes
|
|
122
|
+
echo "// Login code" > login.js
|
|
123
|
+
gent add login.js
|
|
124
|
+
gent commit -m "Add login feature"
|
|
125
|
+
|
|
126
|
+
# View your work
|
|
127
|
+
gent log
|
|
128
|
+
|
|
129
|
+
# Switch back to main
|
|
130
|
+
gent checkout main
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Quick Commit All
|
|
134
|
+
```bash
|
|
135
|
+
# Stage and commit all changes
|
|
136
|
+
gent add .
|
|
137
|
+
gent commit -m "Update all files"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Check What Changed
|
|
141
|
+
```bash
|
|
142
|
+
# See status
|
|
143
|
+
gent status
|
|
144
|
+
|
|
145
|
+
# Short format
|
|
146
|
+
gent status -s
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Tips & Tricks
|
|
150
|
+
|
|
151
|
+
1. **Use .gentignore** - Exclude files like `node_modules/`
|
|
152
|
+
2. **Commit Often** - Small commits are easier to track
|
|
153
|
+
3. **Descriptive Messages** - Write clear commit messages
|
|
154
|
+
4. **Branch for Features** - Keep main branch stable
|
|
155
|
+
5. **Check Status** - Always review before committing
|
|
156
|
+
|
|
157
|
+
## Running the Demo
|
|
158
|
+
|
|
159
|
+
See the CLI in action:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
chmod +x demo.sh
|
|
163
|
+
./demo.sh
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Troubleshooting
|
|
167
|
+
|
|
168
|
+
**Not a gent repository error?**
|
|
169
|
+
```bash
|
|
170
|
+
# Make sure you initialized
|
|
171
|
+
gent init
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**No changes to commit?**
|
|
175
|
+
```bash
|
|
176
|
+
# Add files first
|
|
177
|
+
gent add <files>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Command not found?**
|
|
181
|
+
```bash
|
|
182
|
+
# Use node directly
|
|
183
|
+
node src/index.js <command>
|
|
184
|
+
|
|
185
|
+
# Or link globally
|
|
186
|
+
npm link
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Next Steps
|
|
190
|
+
|
|
191
|
+
- Read the full [README.md](README.md)
|
|
192
|
+
- Explore the [source code](src/)
|
|
193
|
+
- Try the [demo script](demo.sh)
|
|
194
|
+
- Build your own commands!
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
**Happy coding with Gent! š**
|
package/package.json
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gent-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A Git-like version control CLI tool",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "A Git-like version control CLI tool with cloud authentication",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"gent": "
|
|
7
|
+
"gent": "src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/index.js",
|
|
11
11
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
12
|
"demo": "bash demo.sh",
|
|
13
13
|
"link": "npm link",
|
|
14
|
-
"unlink": "npm unlink"
|
|
14
|
+
"unlink": "npm unlink",
|
|
15
|
+
"prepublishOnly": "echo 'Ready to publish gent-cli!'"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"cli",
|
|
18
19
|
"version-control",
|
|
19
20
|
"git-like",
|
|
20
|
-
"gent"
|
|
21
|
+
"gent",
|
|
22
|
+
"authentication",
|
|
23
|
+
"jwt",
|
|
24
|
+
"api",
|
|
25
|
+
"command-line",
|
|
26
|
+
"developer-tools"
|
|
21
27
|
],
|
|
22
28
|
"author": "Abdalrahman Kanawati <kanawatiabdalrahman@gmail.com>",
|
|
23
29
|
"license": "ISC",
|
|
@@ -30,14 +36,21 @@
|
|
|
30
36
|
"url": "https://github.com/SaadShaya7/gent/issues"
|
|
31
37
|
},
|
|
32
38
|
"dependencies": {
|
|
33
|
-
"
|
|
39
|
+
"axios": "^1.6.0",
|
|
40
|
+
"boxen": "^5.1.2",
|
|
34
41
|
"chalk": "^4.1.2",
|
|
42
|
+
"commander": "^11.1.0",
|
|
43
|
+
"crypto-js": "^4.2.0",
|
|
44
|
+
"date-fns": "^2.30.0",
|
|
35
45
|
"inquirer": "^8.2.5",
|
|
36
|
-
"ora": "^5.4.1"
|
|
37
|
-
"boxen": "^5.1.2",
|
|
38
|
-
"date-fns": "^2.30.0"
|
|
46
|
+
"ora": "^5.4.1"
|
|
39
47
|
},
|
|
40
48
|
"engines": {
|
|
41
49
|
"node": ">=14.0.0"
|
|
42
|
-
}
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"src/",
|
|
53
|
+
"README.md",
|
|
54
|
+
"QUICKSTART.md"
|
|
55
|
+
]
|
|
43
56
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Command - Authenticate user
|
|
3
|
+
* Handles user login with email and password
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const boxen = require('boxen');
|
|
10
|
+
const authService = require('../services/auth-service');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Login user
|
|
14
|
+
* @param {Object} options - Command options
|
|
15
|
+
*/
|
|
16
|
+
async function login(options) {
|
|
17
|
+
console.log(chalk.cyan('\nš Login to your Gent account\n'));
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
let email = options.email;
|
|
21
|
+
let password = options.password;
|
|
22
|
+
|
|
23
|
+
// If credentials not provided via flags, prompt for them
|
|
24
|
+
if (!email || !password) {
|
|
25
|
+
const answers = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'email',
|
|
29
|
+
message: 'Email address:',
|
|
30
|
+
when: !email,
|
|
31
|
+
validate: (input) => {
|
|
32
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
33
|
+
return emailRegex.test(input) || 'Please enter a valid email address';
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'password',
|
|
38
|
+
name: 'password',
|
|
39
|
+
message: 'Password:',
|
|
40
|
+
when: !password,
|
|
41
|
+
mask: '*'
|
|
42
|
+
}
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
email = email || answers.email;
|
|
46
|
+
password = password || answers.password;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const spinner = ora('Logging in...').start();
|
|
50
|
+
|
|
51
|
+
// Login user
|
|
52
|
+
const user = await authService.login(email, password);
|
|
53
|
+
|
|
54
|
+
spinner.succeed(chalk.green('ā Login successful!'));
|
|
55
|
+
|
|
56
|
+
// Display welcome message
|
|
57
|
+
const message = chalk.white(`
|
|
58
|
+
${chalk.bold('Welcome back!')}
|
|
59
|
+
|
|
60
|
+
${chalk.bold('Email:')} ${user.email}
|
|
61
|
+
${chalk.bold('Name:')} ${user.first_name || ''} ${user.last_name || ''}
|
|
62
|
+
|
|
63
|
+
${chalk.cyan('You are now logged in!')}
|
|
64
|
+
|
|
65
|
+
${chalk.bold('Commands:')}
|
|
66
|
+
${chalk.gray('ā¢')} gent whoami - View your profile
|
|
67
|
+
${chalk.gray('ā¢')} gent init - Initialize a repository
|
|
68
|
+
${chalk.gray('ā¢')} gent help - See all commands
|
|
69
|
+
`);
|
|
70
|
+
|
|
71
|
+
console.log(boxen(message, {
|
|
72
|
+
padding: 1,
|
|
73
|
+
margin: 1,
|
|
74
|
+
borderStyle: 'round',
|
|
75
|
+
borderColor: 'cyan'
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(chalk.red('\nā Login failed'));
|
|
80
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = login;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout Command - End user session
|
|
3
|
+
* Handles user logout and clearing authentication data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const authService = require('../services/auth-service');
|
|
9
|
+
const authStorage = require('../utils/auth-storage');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Logout user
|
|
13
|
+
* @param {Object} options - Command options
|
|
14
|
+
*/
|
|
15
|
+
async function logout(options) {
|
|
16
|
+
try {
|
|
17
|
+
// Check if user is authenticated
|
|
18
|
+
const isAuth = await authStorage.isAuthenticated();
|
|
19
|
+
|
|
20
|
+
if (!isAuth) {
|
|
21
|
+
console.log(chalk.yellow('\nā¹ You are not logged in\n'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const spinner = ora('Logging out...').start();
|
|
26
|
+
|
|
27
|
+
// Logout user (calls API and clears local storage)
|
|
28
|
+
await authService.logout();
|
|
29
|
+
|
|
30
|
+
spinner.succeed(chalk.green('ā Logged out successfully!'));
|
|
31
|
+
console.log(chalk.gray('Your session has been ended.\n'));
|
|
32
|
+
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(chalk.red('\nā Logout failed'));
|
|
35
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = logout;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register Command - Create a new user account
|
|
3
|
+
* Handles user registration with email and password
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const boxen = require('boxen');
|
|
10
|
+
const authService = require('../services/auth-service');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Register a new user
|
|
14
|
+
* @param {Object} options - Command options
|
|
15
|
+
*/
|
|
16
|
+
async function register(options) {
|
|
17
|
+
console.log(chalk.cyan('\nš Create your Gent account\n'));
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Prompt for user information
|
|
21
|
+
const answers = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: 'input',
|
|
24
|
+
name: 'email',
|
|
25
|
+
message: 'Email address:',
|
|
26
|
+
validate: (input) => {
|
|
27
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
28
|
+
return emailRegex.test(input) || 'Please enter a valid email address';
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'password',
|
|
33
|
+
name: 'password',
|
|
34
|
+
message: 'Password:',
|
|
35
|
+
mask: '*',
|
|
36
|
+
validate: (input) => {
|
|
37
|
+
if (input.length < 8) {
|
|
38
|
+
return 'Password must be at least 8 characters long';
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'password',
|
|
45
|
+
name: 'passwordConfirm',
|
|
46
|
+
message: 'Confirm password:',
|
|
47
|
+
mask: '*',
|
|
48
|
+
validate: (input, answers) => {
|
|
49
|
+
return input === answers.password || 'Passwords do not match';
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'firstName',
|
|
55
|
+
message: 'First name:',
|
|
56
|
+
default: ''
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'input',
|
|
60
|
+
name: 'lastName',
|
|
61
|
+
message: 'Last name:',
|
|
62
|
+
default: ''
|
|
63
|
+
}
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const spinner = ora('Creating your account...').start();
|
|
67
|
+
|
|
68
|
+
// Register user
|
|
69
|
+
const user = await authService.register(
|
|
70
|
+
answers.email,
|
|
71
|
+
answers.password,
|
|
72
|
+
answers.passwordConfirm,
|
|
73
|
+
answers.firstName,
|
|
74
|
+
answers.lastName
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
spinner.succeed(chalk.green('ā Account created successfully!'));
|
|
78
|
+
|
|
79
|
+
// Display success message
|
|
80
|
+
const message = chalk.white(`
|
|
81
|
+
${chalk.bold('Welcome to Gent!')}
|
|
82
|
+
|
|
83
|
+
${chalk.bold('Email:')} ${user.email}
|
|
84
|
+
${chalk.bold('Name:')} ${user.first_name || ''} ${user.last_name || ''}
|
|
85
|
+
${chalk.bold('Account created:')} ${new Date(user.date_joined).toLocaleDateString()}
|
|
86
|
+
|
|
87
|
+
${chalk.cyan('You are now logged in!')}
|
|
88
|
+
|
|
89
|
+
${chalk.bold('Next steps:')}
|
|
90
|
+
${chalk.gray('ā¢')} gent init - Initialize a repository
|
|
91
|
+
${chalk.gray('ā¢')} gent whoami - View your profile
|
|
92
|
+
${chalk.gray('ā¢')} gent help - See all commands
|
|
93
|
+
`);
|
|
94
|
+
|
|
95
|
+
console.log(boxen(message, {
|
|
96
|
+
padding: 1,
|
|
97
|
+
margin: 1,
|
|
98
|
+
borderStyle: 'round',
|
|
99
|
+
borderColor: 'green'
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(chalk.red('\nā Registration failed'));
|
|
104
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = register;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whoami Command - Display current user information
|
|
3
|
+
* Shows authenticated user profile
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const boxen = require('boxen');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const { formatDistanceToNow } = require('date-fns');
|
|
10
|
+
const authStorage = require('../utils/auth-storage');
|
|
11
|
+
const authService = require('../services/auth-service');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Display current user information
|
|
15
|
+
* @param {Object} options - Command options
|
|
16
|
+
*/
|
|
17
|
+
async function whoami(options) {
|
|
18
|
+
try {
|
|
19
|
+
// Check if user is authenticated
|
|
20
|
+
const isAuth = await authStorage.isAuthenticated();
|
|
21
|
+
|
|
22
|
+
if (!isAuth) {
|
|
23
|
+
console.log(chalk.yellow('\nā¹ You are not logged in'));
|
|
24
|
+
console.log(chalk.gray('Use "gent login" or "gent register" to authenticate\n'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const spinner = ora('Fetching user profile...').start();
|
|
29
|
+
|
|
30
|
+
// Get user profile from API (this will also test token validity)
|
|
31
|
+
const user = await authService.getProfile();
|
|
32
|
+
|
|
33
|
+
spinner.stop();
|
|
34
|
+
|
|
35
|
+
// Format date
|
|
36
|
+
const joinedDate = new Date(user.date_joined);
|
|
37
|
+
const joinedAgo = formatDistanceToNow(joinedDate, { addSuffix: true });
|
|
38
|
+
|
|
39
|
+
// Display user information
|
|
40
|
+
const message = chalk.white(`
|
|
41
|
+
${chalk.bold.cyan('š¤ User Profile')}
|
|
42
|
+
|
|
43
|
+
${chalk.bold('Email:')} ${user.email}
|
|
44
|
+
${chalk.bold('Name:')} ${user.first_name || 'N/A'} ${user.last_name || ''}
|
|
45
|
+
${chalk.bold('Account ID:')} ${user.id}
|
|
46
|
+
${chalk.bold('Joined:')} ${joinedDate.toLocaleDateString()} ${chalk.gray(`(${joinedAgo})`)}
|
|
47
|
+
${chalk.bold('Status:')} ${user.is_active ? chalk.green('Active') : chalk.red('Inactive')}
|
|
48
|
+
`);
|
|
49
|
+
|
|
50
|
+
console.log(boxen(message, {
|
|
51
|
+
padding: 1,
|
|
52
|
+
margin: 1,
|
|
53
|
+
borderStyle: 'round',
|
|
54
|
+
borderColor: 'cyan'
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(chalk.red('\nā Failed to fetch user profile'));
|
|
59
|
+
console.error(chalk.red(`Error: ${error.message}\n`));
|
|
60
|
+
|
|
61
|
+
if (error.message.includes('login')) {
|
|
62
|
+
console.log(chalk.gray('Use "gent login" to authenticate\n'));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = whoami;
|
package/src/index.js
CHANGED
|
@@ -21,6 +21,12 @@ const logCommand = require('./commands/log');
|
|
|
21
21
|
const branchCommand = require('./commands/branch');
|
|
22
22
|
const checkoutCommand = require('./commands/checkout');
|
|
23
23
|
|
|
24
|
+
// Import auth commands
|
|
25
|
+
const registerCommand = require('./commands/register');
|
|
26
|
+
const loginCommand = require('./commands/login');
|
|
27
|
+
const logoutCommand = require('./commands/logout');
|
|
28
|
+
const whoamiCommand = require('./commands/whoami');
|
|
29
|
+
|
|
24
30
|
// Configure CLI
|
|
25
31
|
program
|
|
26
32
|
.name('gent')
|
|
@@ -74,6 +80,29 @@ program
|
|
|
74
80
|
.option('-b, --create', 'Create a new branch')
|
|
75
81
|
.action(checkoutCommand);
|
|
76
82
|
|
|
83
|
+
// Authentication commands
|
|
84
|
+
program
|
|
85
|
+
.command('register')
|
|
86
|
+
.description('Create a new user account')
|
|
87
|
+
.action(registerCommand);
|
|
88
|
+
|
|
89
|
+
program
|
|
90
|
+
.command('login')
|
|
91
|
+
.description('Login to your account')
|
|
92
|
+
.option('-e, --email <email>', 'Email address')
|
|
93
|
+
.option('-p, --password <password>', 'Password')
|
|
94
|
+
.action(loginCommand);
|
|
95
|
+
|
|
96
|
+
program
|
|
97
|
+
.command('logout')
|
|
98
|
+
.description('Logout from your account')
|
|
99
|
+
.action(logoutCommand);
|
|
100
|
+
|
|
101
|
+
program
|
|
102
|
+
.command('whoami')
|
|
103
|
+
.description('Display current user information')
|
|
104
|
+
.action(whoamiCommand);
|
|
105
|
+
|
|
77
106
|
// Help command
|
|
78
107
|
program
|
|
79
108
|
.command('help [command]')
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Service - Authentication operations
|
|
3
|
+
* Handles registration, login, logout, and token management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const apiClient = require('../utils/api-client');
|
|
7
|
+
const authStorage = require('../utils/auth-storage');
|
|
8
|
+
const { API_ENDPOINTS } = require('../utils/constants');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register a new user
|
|
12
|
+
* @param {string} email - User email
|
|
13
|
+
* @param {string} password - User password
|
|
14
|
+
* @param {string} passwordConfirm - Password confirmation
|
|
15
|
+
* @param {string} firstName - User first name
|
|
16
|
+
* @param {string} lastName - User last name
|
|
17
|
+
* @returns {Promise<Object>} User data
|
|
18
|
+
*/
|
|
19
|
+
async function register(email, password, passwordConfirm, firstName, lastName) {
|
|
20
|
+
try {
|
|
21
|
+
const payload = {
|
|
22
|
+
email,
|
|
23
|
+
password,
|
|
24
|
+
password_confirm: passwordConfirm,
|
|
25
|
+
first_name: firstName,
|
|
26
|
+
last_name: lastName
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const response = await apiClient.post(API_ENDPOINTS.REGISTER, payload);
|
|
30
|
+
|
|
31
|
+
// API returns { message, user: {...}, tokens: { access, refresh } }
|
|
32
|
+
const { tokens, user } = response;
|
|
33
|
+
|
|
34
|
+
// Store tokens
|
|
35
|
+
await authStorage.saveTokens(tokens.access, tokens.refresh, user);
|
|
36
|
+
|
|
37
|
+
return user;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.response?.data) {
|
|
40
|
+
// Extract API error messages
|
|
41
|
+
const errors = error.response.data;
|
|
42
|
+
const errorMessages = [];
|
|
43
|
+
|
|
44
|
+
for (const [field, messages] of Object.entries(errors)) {
|
|
45
|
+
if (Array.isArray(messages)) {
|
|
46
|
+
errorMessages.push(...messages);
|
|
47
|
+
} else {
|
|
48
|
+
errorMessages.push(messages);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Error(errorMessages.join(', '));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new Error(error.message || 'Registration failed');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Login user
|
|
61
|
+
* @param {string} email - User email
|
|
62
|
+
* @param {string} password - User password
|
|
63
|
+
* @returns {Promise<Object>} User data
|
|
64
|
+
*/
|
|
65
|
+
async function login(email, password) {
|
|
66
|
+
try {
|
|
67
|
+
const payload = { email, password };
|
|
68
|
+
|
|
69
|
+
const response = await apiClient.post(API_ENDPOINTS.LOGIN, payload);
|
|
70
|
+
|
|
71
|
+
// API returns { message, user: {...}, tokens: { access, refresh } }
|
|
72
|
+
const { tokens, user } = response;
|
|
73
|
+
|
|
74
|
+
// Store tokens
|
|
75
|
+
await authStorage.saveTokens(tokens.access, tokens.refresh, user);
|
|
76
|
+
|
|
77
|
+
return user;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error.response?.status === 401) {
|
|
80
|
+
throw new Error('Invalid email or password');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (error.response?.data) {
|
|
84
|
+
const errors = error.response.data;
|
|
85
|
+
const errorMessages = [];
|
|
86
|
+
|
|
87
|
+
for (const [field, messages] of Object.entries(errors)) {
|
|
88
|
+
if (Array.isArray(messages)) {
|
|
89
|
+
errorMessages.push(...messages);
|
|
90
|
+
} else {
|
|
91
|
+
errorMessages.push(messages);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error(errorMessages.join(', '));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new Error(error.message || 'Login failed');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Logout user
|
|
104
|
+
* @returns {Promise<void>}
|
|
105
|
+
*/
|
|
106
|
+
async function logout() {
|
|
107
|
+
try {
|
|
108
|
+
const refreshToken = await authStorage.getRefreshToken();
|
|
109
|
+
|
|
110
|
+
if (refreshToken) {
|
|
111
|
+
// Call logout endpoint to blacklist refresh token
|
|
112
|
+
await apiClient.post(API_ENDPOINTS.LOGOUT, {
|
|
113
|
+
refresh: refreshToken
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// Even if API call fails, clear local auth
|
|
118
|
+
console.error('Logout API call failed:', error.message);
|
|
119
|
+
} finally {
|
|
120
|
+
// Always clear local authentication
|
|
121
|
+
await authStorage.clearAuth();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Refresh access token
|
|
127
|
+
* @returns {Promise<string>} New access token
|
|
128
|
+
*/
|
|
129
|
+
async function refreshToken() {
|
|
130
|
+
try {
|
|
131
|
+
const refreshToken = await authStorage.getRefreshToken();
|
|
132
|
+
|
|
133
|
+
if (!refreshToken) {
|
|
134
|
+
throw new Error('No refresh token available');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const response = await apiClient.post(API_ENDPOINTS.REFRESH, {
|
|
138
|
+
refresh: refreshToken
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const { access } = response;
|
|
142
|
+
|
|
143
|
+
// Update only access token
|
|
144
|
+
await authStorage.updateAccessToken(access);
|
|
145
|
+
|
|
146
|
+
return access;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
// If refresh fails, clear auth
|
|
149
|
+
await authStorage.clearAuth();
|
|
150
|
+
throw new Error('Session expired. Please login again.');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get current user profile
|
|
156
|
+
* @returns {Promise<Object>} User profile data
|
|
157
|
+
*/
|
|
158
|
+
async function getProfile() {
|
|
159
|
+
try {
|
|
160
|
+
const response = await apiClient.get(API_ENDPOINTS.PROFILE);
|
|
161
|
+
return response;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error.response?.status === 401) {
|
|
164
|
+
throw new Error('Not authenticated. Please login first.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new Error(error.message || 'Failed to fetch profile');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
register,
|
|
173
|
+
login,
|
|
174
|
+
logout,
|
|
175
|
+
refreshToken,
|
|
176
|
+
getProfile
|
|
177
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client - HTTP client for making authenticated API requests
|
|
3
|
+
* Handles request/response interceptors and automatic token refresh
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const axios = require('axios');
|
|
7
|
+
const { API_BASE_URL } = require('./constants');
|
|
8
|
+
const authStorage = require('./auth-storage');
|
|
9
|
+
|
|
10
|
+
// Create axios instance with base configuration
|
|
11
|
+
const apiClient = axios.create({
|
|
12
|
+
baseURL: API_BASE_URL,
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json'
|
|
15
|
+
},
|
|
16
|
+
timeout: 60000 // 60 seconds
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Track if we're currently refreshing token to avoid multiple refresh requests
|
|
20
|
+
let isRefreshing = false;
|
|
21
|
+
let failedRequestsQueue = [];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Process queued requests after token refresh
|
|
25
|
+
* @param {Error|null} error - Error if refresh failed
|
|
26
|
+
* @param {string|null} token - New access token if refresh succeeded
|
|
27
|
+
*/
|
|
28
|
+
function processQueue(error, token = null) {
|
|
29
|
+
failedRequestsQueue.forEach(promise => {
|
|
30
|
+
if (error) {
|
|
31
|
+
promise.reject(error);
|
|
32
|
+
} else {
|
|
33
|
+
promise.resolve(token);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
failedRequestsQueue = [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Request interceptor - Add JWT token to headers
|
|
41
|
+
apiClient.interceptors.request.use(
|
|
42
|
+
async (config) => {
|
|
43
|
+
const token = await authStorage.getAccessToken();
|
|
44
|
+
|
|
45
|
+
if (token) {
|
|
46
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return config;
|
|
50
|
+
},
|
|
51
|
+
(error) => {
|
|
52
|
+
return Promise.reject(error);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Response interceptor - Handle 401 errors and refresh token
|
|
57
|
+
apiClient.interceptors.response.use(
|
|
58
|
+
(response) => {
|
|
59
|
+
return response;
|
|
60
|
+
},
|
|
61
|
+
async (error) => {
|
|
62
|
+
const originalRequest = error.config;
|
|
63
|
+
|
|
64
|
+
// If error is 401 and we haven't tried to refresh yet
|
|
65
|
+
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
66
|
+
if (isRefreshing) {
|
|
67
|
+
// If already refreshing, queue this request
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
failedRequestsQueue.push({ resolve, reject });
|
|
70
|
+
})
|
|
71
|
+
.then(token => {
|
|
72
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
73
|
+
return apiClient(originalRequest);
|
|
74
|
+
})
|
|
75
|
+
.catch(err => {
|
|
76
|
+
return Promise.reject(err);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
originalRequest._retry = true;
|
|
81
|
+
isRefreshing = true;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const refreshToken = await authStorage.getRefreshToken();
|
|
85
|
+
|
|
86
|
+
if (!refreshToken) {
|
|
87
|
+
// No refresh token, user needs to login again
|
|
88
|
+
await authStorage.clearAuth();
|
|
89
|
+
throw new Error('Session expired. Please login again.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Call refresh endpoint
|
|
93
|
+
const response = await axios.post(
|
|
94
|
+
`${API_BASE_URL}/api/auth/token/refresh/`,
|
|
95
|
+
{ refresh: refreshToken }
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const { access } = response.data;
|
|
99
|
+
|
|
100
|
+
// Update stored access token
|
|
101
|
+
await authStorage.updateAccessToken(access);
|
|
102
|
+
|
|
103
|
+
// Update authorization header
|
|
104
|
+
originalRequest.headers.Authorization = `Bearer ${access}`;
|
|
105
|
+
|
|
106
|
+
// Process queued requests
|
|
107
|
+
processQueue(null, access);
|
|
108
|
+
|
|
109
|
+
isRefreshing = false;
|
|
110
|
+
|
|
111
|
+
// Retry original request
|
|
112
|
+
return apiClient(originalRequest);
|
|
113
|
+
|
|
114
|
+
} catch (refreshError) {
|
|
115
|
+
// Refresh failed, clear auth and reject
|
|
116
|
+
processQueue(refreshError, null);
|
|
117
|
+
isRefreshing = false;
|
|
118
|
+
await authStorage.clearAuth();
|
|
119
|
+
return Promise.reject(refreshError);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Promise.reject(error);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Make GET request
|
|
129
|
+
* @param {string} url - Endpoint URL
|
|
130
|
+
* @param {Object} config - Axios config
|
|
131
|
+
* @returns {Promise} Response data
|
|
132
|
+
*/
|
|
133
|
+
async function get(url, config = {}) {
|
|
134
|
+
const response = await apiClient.get(url, config);
|
|
135
|
+
return response.data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Make POST request
|
|
140
|
+
* @param {string} url - Endpoint URL
|
|
141
|
+
* @param {Object} data - Request payload
|
|
142
|
+
* @param {Object} config - Axios config
|
|
143
|
+
* @returns {Promise} Response data
|
|
144
|
+
*/
|
|
145
|
+
async function post(url, data = {}, config = {}) {
|
|
146
|
+
const response = await apiClient.post(url, data, config);
|
|
147
|
+
return response.data;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Make PUT request
|
|
152
|
+
* @param {string} url - Endpoint URL
|
|
153
|
+
* @param {Object} data - Request payload
|
|
154
|
+
* @param {Object} config - Axios config
|
|
155
|
+
* @returns {Promise} Response data
|
|
156
|
+
*/
|
|
157
|
+
async function put(url, data = {}, config = {}) {
|
|
158
|
+
const response = await apiClient.put(url, data, config);
|
|
159
|
+
return response.data;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Make DELETE request
|
|
164
|
+
* @param {string} url - Endpoint URL
|
|
165
|
+
* @param {Object} config - Axios config
|
|
166
|
+
* @returns {Promise} Response data
|
|
167
|
+
*/
|
|
168
|
+
async function del(url, config = {}) {
|
|
169
|
+
const response = await apiClient.delete(url, config);
|
|
170
|
+
return response.data;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
get,
|
|
175
|
+
post,
|
|
176
|
+
put,
|
|
177
|
+
delete: del,
|
|
178
|
+
apiClient // Export raw client if needed
|
|
179
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Storage - Secure token and user data storage
|
|
3
|
+
* Handles encryption and persistence of authentication data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises;
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const CryptoJS = require('crypto-js');
|
|
9
|
+
const { GENT_DIR, AUTH_FILE } = require('./constants');
|
|
10
|
+
|
|
11
|
+
// Simple encryption key (in production, use environment variable or OS keychain)
|
|
12
|
+
const ENCRYPTION_KEY = 'gent-cli-secret-key-v1';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the auth file path
|
|
16
|
+
* @returns {string} Path to auth.json file
|
|
17
|
+
*/
|
|
18
|
+
function getAuthFilePath() {
|
|
19
|
+
return path.join(process.cwd(), GENT_DIR, AUTH_FILE);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Encrypt data
|
|
24
|
+
* @param {Object} data - Data to encrypt
|
|
25
|
+
* @returns {string} Encrypted string
|
|
26
|
+
*/
|
|
27
|
+
function encrypt(data) {
|
|
28
|
+
const jsonString = JSON.stringify(data);
|
|
29
|
+
return CryptoJS.AES.encrypt(jsonString, ENCRYPTION_KEY).toString();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Decrypt data
|
|
34
|
+
* @param {string} encryptedData - Encrypted string
|
|
35
|
+
* @returns {Object} Decrypted data
|
|
36
|
+
*/
|
|
37
|
+
function decrypt(encryptedData) {
|
|
38
|
+
const bytes = CryptoJS.AES.decrypt(encryptedData, ENCRYPTION_KEY);
|
|
39
|
+
const decryptedString = bytes.toString(CryptoJS.enc.Utf8);
|
|
40
|
+
return JSON.parse(decryptedString);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Save authentication tokens and user data
|
|
45
|
+
* @param {string} accessToken - JWT access token
|
|
46
|
+
* @param {string} refreshToken - JWT refresh token
|
|
47
|
+
* @param {Object} user - User profile data
|
|
48
|
+
*/
|
|
49
|
+
async function saveTokens(accessToken, refreshToken, user) {
|
|
50
|
+
const authFilePath = getAuthFilePath();
|
|
51
|
+
const gentDir = path.join(process.cwd(), GENT_DIR);
|
|
52
|
+
|
|
53
|
+
const authData = {
|
|
54
|
+
accessToken,
|
|
55
|
+
refreshToken,
|
|
56
|
+
user,
|
|
57
|
+
timestamp: new Date().toISOString()
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const encryptedData = encrypt(authData);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Ensure .gent directory exists
|
|
64
|
+
await fs.mkdir(gentDir, { recursive: true });
|
|
65
|
+
await fs.writeFile(authFilePath, JSON.stringify({ data: encryptedData }), 'utf8');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(`Failed to save authentication data: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read authentication data from file
|
|
73
|
+
* @returns {Object|null} Decrypted auth data or null if not found
|
|
74
|
+
*/
|
|
75
|
+
async function readAuthData() {
|
|
76
|
+
const authFilePath = getAuthFilePath();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const fileContent = await fs.readFile(authFilePath, 'utf8');
|
|
80
|
+
const { data } = JSON.parse(fileContent);
|
|
81
|
+
return decrypt(data);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// File doesn't exist or is corrupted
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get access token
|
|
90
|
+
* @returns {string|null} Access token or null
|
|
91
|
+
*/
|
|
92
|
+
async function getAccessToken() {
|
|
93
|
+
const authData = await readAuthData();
|
|
94
|
+
return authData ? authData.accessToken : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get refresh token
|
|
99
|
+
* @returns {string|null} Refresh token or null
|
|
100
|
+
*/
|
|
101
|
+
async function getRefreshToken() {
|
|
102
|
+
const authData = await readAuthData();
|
|
103
|
+
return authData ? authData.refreshToken : null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get user profile
|
|
108
|
+
* @returns {Object|null} User data or null
|
|
109
|
+
*/
|
|
110
|
+
async function getUser() {
|
|
111
|
+
const authData = await readAuthData();
|
|
112
|
+
return authData ? authData.user : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if user is authenticated
|
|
117
|
+
* @returns {boolean} True if authenticated
|
|
118
|
+
*/
|
|
119
|
+
async function isAuthenticated() {
|
|
120
|
+
const authData = await readAuthData();
|
|
121
|
+
return authData !== null && authData.accessToken !== null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Clear authentication data
|
|
126
|
+
*/
|
|
127
|
+
async function clearAuth() {
|
|
128
|
+
const authFilePath = getAuthFilePath();
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await fs.unlink(authFilePath);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// File doesn't exist, nothing to clear
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Update only the access token (used after refresh)
|
|
139
|
+
* @param {string} newAccessToken - New access token
|
|
140
|
+
*/
|
|
141
|
+
async function updateAccessToken(newAccessToken) {
|
|
142
|
+
const authData = await readAuthData();
|
|
143
|
+
|
|
144
|
+
if (!authData) {
|
|
145
|
+
throw new Error('No authentication data found');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
authData.accessToken = newAccessToken;
|
|
149
|
+
authData.timestamp = new Date().toISOString();
|
|
150
|
+
|
|
151
|
+
const authFilePath = getAuthFilePath();
|
|
152
|
+
const gentDir = path.join(process.cwd(), GENT_DIR);
|
|
153
|
+
const encryptedData = encrypt(authData);
|
|
154
|
+
|
|
155
|
+
// Ensure .gent directory exists
|
|
156
|
+
await fs.mkdir(gentDir, { recursive: true });
|
|
157
|
+
await fs.writeFile(authFilePath, JSON.stringify({ data: encryptedData }), 'utf8');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
saveTokens,
|
|
162
|
+
getAccessToken,
|
|
163
|
+
getRefreshToken,
|
|
164
|
+
getUser,
|
|
165
|
+
isAuthenticated,
|
|
166
|
+
clearAuth,
|
|
167
|
+
updateAccessToken
|
|
168
|
+
};
|
package/src/utils/constants.js
CHANGED
|
@@ -10,6 +10,17 @@ module.exports = {
|
|
|
10
10
|
STAGING_FILE: 'staging.json',
|
|
11
11
|
COMMITS_FILE: 'commits.json',
|
|
12
12
|
HEAD_FILE: 'HEAD',
|
|
13
|
+
AUTH_FILE: 'auth.json',
|
|
14
|
+
|
|
15
|
+
// API Configuration
|
|
16
|
+
API_BASE_URL: 'https://gent-api.onrender.com',
|
|
17
|
+
API_ENDPOINTS: {
|
|
18
|
+
LOGIN: '/api/auth/login/',
|
|
19
|
+
REGISTER: '/api/auth/register/',
|
|
20
|
+
LOGOUT: '/api/auth/logout/',
|
|
21
|
+
REFRESH: '/api/auth/token/refresh/',
|
|
22
|
+
PROFILE: '/api/auth/profile/'
|
|
23
|
+
},
|
|
13
24
|
|
|
14
25
|
// Default ignore patterns
|
|
15
26
|
DEFAULT_IGNORE_PATTERNS: [
|
package/commands-reference.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Gent CLI - Command Summary
|
|
5
|
-
* Quick reference for all available commands
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const chalk = require('chalk');
|
|
9
|
-
const boxen = require('boxen');
|
|
10
|
-
|
|
11
|
-
const commands = [
|
|
12
|
-
{
|
|
13
|
-
name: 'init',
|
|
14
|
-
description: 'Initialize a new gent repository',
|
|
15
|
-
usage: 'gent init [options]',
|
|
16
|
-
options: ['-y, --yes Skip prompts and use defaults'],
|
|
17
|
-
examples: ['gent init', 'gent init -y']
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: 'status',
|
|
21
|
-
description: 'Show the working tree status',
|
|
22
|
-
usage: 'gent status [options]',
|
|
23
|
-
options: ['-s, --short Give output in short format'],
|
|
24
|
-
examples: ['gent status', 'gent status -s']
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: 'add',
|
|
28
|
-
description: 'Add file contents to the staging area',
|
|
29
|
-
usage: 'gent add <files...> [options]',
|
|
30
|
-
options: ['-A, --all Add all files'],
|
|
31
|
-
examples: ['gent add file.js', 'gent add .', 'gent add --all']
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: 'commit',
|
|
35
|
-
description: 'Record changes to the repository',
|
|
36
|
-
usage: 'gent commit [options]',
|
|
37
|
-
options: [
|
|
38
|
-
'-m, --message <message> Commit message',
|
|
39
|
-
'-a, --all Auto stage modified files'
|
|
40
|
-
],
|
|
41
|
-
examples: ['gent commit -m "Fix bug"', 'gent commit', 'gent commit -a -m "Update all"']
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: 'log',
|
|
45
|
-
description: 'Show commit logs',
|
|
46
|
-
usage: 'gent log [options]',
|
|
47
|
-
options: [
|
|
48
|
-
'-n, --number <count> Limit commits (default: 10)',
|
|
49
|
-
'--oneline Show each commit on one line'
|
|
50
|
-
],
|
|
51
|
-
examples: ['gent log', 'gent log -n 5', 'gent log --oneline']
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'branch',
|
|
55
|
-
description: 'List, create, or delete branches',
|
|
56
|
-
usage: 'gent branch [name] [options]',
|
|
57
|
-
options: [
|
|
58
|
-
'-d, --delete <name> Delete a branch',
|
|
59
|
-
'-a, --all List all branches'
|
|
60
|
-
],
|
|
61
|
-
examples: ['gent branch', 'gent branch feature-x', 'gent branch -d old-feature']
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
name: 'checkout',
|
|
65
|
-
description: 'Switch branches',
|
|
66
|
-
usage: 'gent checkout <branch> [options]',
|
|
67
|
-
options: ['-b, --create Create a new branch'],
|
|
68
|
-
examples: ['gent checkout main', 'gent checkout -b new-feature']
|
|
69
|
-
}
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
console.log(chalk.bold.cyan('\nš Gent CLI - Command Reference\n'));
|
|
73
|
-
|
|
74
|
-
commands.forEach((cmd, index) => {
|
|
75
|
-
console.log(chalk.yellow.bold(`${index + 1}. ${cmd.name.toUpperCase()}`));
|
|
76
|
-
console.log(chalk.white(` ${cmd.description}`));
|
|
77
|
-
console.log(chalk.gray(` Usage: ${cmd.usage}`));
|
|
78
|
-
|
|
79
|
-
if (cmd.options.length > 0) {
|
|
80
|
-
console.log(chalk.gray(' Options:'));
|
|
81
|
-
cmd.options.forEach(opt => {
|
|
82
|
-
console.log(chalk.gray(` ${opt}`));
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
console.log(chalk.cyan(' Examples:'));
|
|
87
|
-
cmd.examples.forEach(ex => {
|
|
88
|
-
console.log(chalk.green(` $ ${ex}`));
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
console.log();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const tips = `
|
|
95
|
-
${chalk.bold('š” Quick Tips:')}
|
|
96
|
-
|
|
97
|
-
${chalk.cyan('ā¢')} Always run ${chalk.yellow('gent init')} first in a new project
|
|
98
|
-
${chalk.cyan('ā¢')} Use ${chalk.yellow('gent status')} to see what changed
|
|
99
|
-
${chalk.cyan('ā¢')} Stage files with ${chalk.yellow('gent add')} before committing
|
|
100
|
-
${chalk.cyan('ā¢')} Create branches for new features
|
|
101
|
-
${chalk.cyan('ā¢')} Check ${chalk.yellow('gent log')} to view history
|
|
102
|
-
|
|
103
|
-
${chalk.bold('š Documentation:')}
|
|
104
|
-
${chalk.gray('⢠README.md - Complete documentation')}
|
|
105
|
-
${chalk.gray('⢠QUICKSTART.md - Quick start guide')}
|
|
106
|
-
${chalk.gray('⢠demo.sh - Interactive demo')}
|
|
107
|
-
`;
|
|
108
|
-
|
|
109
|
-
console.log(boxen(tips, {
|
|
110
|
-
padding: 1,
|
|
111
|
-
margin: 1,
|
|
112
|
-
borderStyle: 'round',
|
|
113
|
-
borderColor: 'cyan'
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
console.log(chalk.gray('Run'), chalk.yellow('gent --help'), chalk.gray('for more information\n'));
|