gent-cli 2.1.0 → 5.0.1

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 CHANGED
@@ -1,17 +1,47 @@
1
1
  # Gent CLI
2
2
 
3
- > A modern, Git-like version control CLI with built-in cloud authentication and global user identity management.
4
-
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!
3
+ ![npm](https://img.shields.io/npm/v/gent-cli)
4
+ ![downloads](https://img.shields.io/npm/dw/gent-cli)
6
5
 
7
- ## Features
6
+ > A modern, Git-like version control CLI with built-in cloud authentication and global user identity management.
8
7
 
9
- - **Cloud Authentication**: Login once, work everywhere. Your identity follows you across projects.
10
- - **Git-like Experience**: Familiar commands (init, add, commit, status, log, branch, checkout).
11
- - **Cloud Synchronization**: Push and pull repositories to/from the cloud.
12
- - **Zero Configuration**: `gent init` is silent and auto-detects your authenticated user profile.
13
- - **Global Identity**: Commits are automatically authored with your cloud profile.
14
- - **Secure**: Tokens stored securely in your home directory.
8
+ Gent is a lightweight version control system that feels like Git but handles user identity automatically through the cloud. No more configuring `user.name` and `user.email` for every repository.
9
+
10
+ ## Highlights
11
+
12
+ - **Cloud Authentication** Login once, work everywhere. JWT-based auth with automatic token refresh.
13
+ - **Git-like Experience** Familiar commands: `init`, `add`, `commit`, `status`, `log`, `branch`, `checkout`, `merge`, `push`, `pull`, `clone`, and more.
14
+ - **Zero Configuration** — `gent init` auto-detects your authenticated user profile.
15
+ - **Global Identity** — Commits are automatically authored with your cloud profile.
16
+ - **Content-Addressable Storage** — SHA-256 object store with zlib compression and deduplication.
17
+ - **Smart Diff & Merge** — Line-level LCS diff, three-way merge with conflict detection.
18
+ - **Remote Sync** — Push, pull, and clone repositories from the cloud backend.
19
+ - **Secure** — Tokens stored with AES encryption in `~/.gent/auth.json`.
20
+
21
+ ## Table of Contents
22
+
23
+ - [Requirements](#requirements)
24
+ - [Installation](#installation)
25
+ - [Quickstart](#quickstart)
26
+ - [Authentication](#authentication)
27
+ - [Commands](#commands)
28
+ - [Repository Setup](#repository-setup)
29
+ - [Staging & Working Tree](#staging--working-tree)
30
+ - [History](#history)
31
+ - [Branching & Merging](#branching--merging)
32
+ - [Remote & Sync](#remote--sync)
33
+ - [Authentication Commands](#authentication-commands)
34
+ - [Usage Examples](#usage-examples)
35
+ - [Repository Structure](#repository-structure)
36
+ - [Configuration](#configuration)
37
+ - [Docs](#docs)
38
+ - [Troubleshooting](#troubleshooting)
39
+ - [Contributing](#contributing)
40
+ - [License](#license)
41
+
42
+ ## Requirements
43
+
44
+ - Node.js >= 14
15
45
 
16
46
  ## Installation
17
47
 
@@ -19,11 +49,33 @@ Gent is a lightweight version control system that feels exactly like Git but han
19
49
  npm install -g gent-cli
20
50
  ```
21
51
 
52
+ Run locally without installing:
53
+
54
+ ```bash
55
+ cd apps/Cli
56
+ npm install
57
+ node src/index.js --help
58
+ ```
59
+
60
+ ## Quickstart
61
+
62
+ ```bash
63
+ # 1) Authenticate
64
+ gent login
65
+
66
+ # 2) Initialize a repo
67
+ gent init
68
+
69
+ # 3) Make a first commit
70
+ gent add .
71
+ gent commit -m "Initial commit"
72
+ ```
73
+
22
74
  ## Authentication
23
75
 
24
- Gent uses a global authentication system. You only need to login once.
76
+ Gent uses a global authentication system. You only need to log in once.
25
77
 
26
- ### Create an Account
78
+ ### Register a New Account
27
79
 
28
80
  ```bash
29
81
  gent register
@@ -33,11 +85,11 @@ gent register
33
85
 
34
86
  ```bash
35
87
  gent login
36
- # or
88
+ # or with flags
37
89
  gent login -e user@example.com -p YourPassword
38
90
  ```
39
91
 
40
- ### Check Status
92
+ ### Check Current User
41
93
 
42
94
  ```bash
43
95
  gent whoami
@@ -49,103 +101,201 @@ gent whoami
49
101
  gent logout
50
102
  ```
51
103
 
52
- ## Local Usage
104
+ ## Commands
53
105
 
54
- ### 1. Initialize a Repository
106
+ ### Repository Setup
107
+
108
+ | Command | Description |
109
+ |---|---|
110
+ | `gent init [-y]` | Initialize a new Gent repository in the current directory. Use `-y` to skip prompts. |
111
+ | `gent clone <url> [directory]` | Clone a remote repository from the cloud backend. |
112
+
113
+ ### Staging & Working Tree
114
+
115
+ | Command | Description |
116
+ |---|---|
117
+ | `gent status [-s]` | Show the working tree status. Use `-s` for short format. |
118
+ | `gent add <files...> [-A]` | Add files to the staging area. Use `-A` or `--all` to add all files. |
119
+ | `gent rm <files...> [--cached]` | Remove files from the working tree and staging area. Use `--cached` to keep the file on disk. |
120
+ | `gent reset [files...] [--hard <hash> \| --soft <hash>]` | Unstage files or reset HEAD to a specific commit. |
121
+ | `gent diff [files...] [--staged] [--stat]` | Show changes between working tree, staging area, and commits. |
122
+
123
+ ### History
124
+
125
+ | Command | Description |
126
+ |---|---|
127
+ | `gent commit [-m <message>] [-a]` | Record changes to the repository. Use `-a` to auto-stage all modified files. |
128
+ | `gent log [-n <count>] [--oneline] [--stat]` | Show commit history. Default limit is 10 commits. |
129
+ | `gent show [ref] [--no-patch]` | Show commit details and diff. |
130
+ | `gent tag [name] [-m <message>] [-d <name>]` | Create, list, or delete tags. |
131
+
132
+ ### Branching & Merging
133
+
134
+ | Command | Description |
135
+ |---|---|
136
+ | `gent branch [name] [-d <name>] [-a]` | List, create, or delete branches. |
137
+ | `gent checkout <branch> [-b]` | Switch branches. Use `-b` to create and switch to a new branch. |
138
+ | `gent merge <branch> [-m <message>]` | Merge a branch into the current branch using a three-way smart merge. |
139
+ | `gent stash [pop \| list \| drop \| apply] [-m <message>] [-i <index>]` | Stash working tree changes. |
140
+
141
+ ### Remote & Sync
142
+
143
+ | Command | Description |
144
+ |---|---|
145
+ | `gent remote [add \| remove \| set-url] [args...] [-v]` | Manage remote connections. |
146
+ | `gent push [remote] [branch] [-f]` | Push local commits to the remote. Use `-f` to force push. |
147
+ | `gent pull [remote] [branch]` | Pull and merge remote commits into the current branch. |
148
+
149
+ ### Authentication Commands
150
+
151
+ | Command | Description |
152
+ |---|---|
153
+ | `gent register` | Create a new user account interactively. |
154
+ | `gent login [-e <email>] [-p <password>]` | Log in to your account. |
155
+ | `gent logout` | Log out and clear stored tokens. |
156
+ | `gent whoami` | Display the currently logged-in user. |
157
+
158
+ ## Usage Examples
159
+
160
+ ### Initialize a Repository
55
161
 
56
162
  ```bash
57
163
  gent init
58
164
  # Output: Initialized empty Gent repository in /path/to/project
59
165
  ```
60
166
 
61
- ### 2. Check Status
167
+ ### Stage and Commit
62
168
 
63
169
  ```bash
64
- gent status
170
+ gent add .
171
+ gent commit -m "Initial commit"
65
172
  ```
66
173
 
67
- ### 3. Stage Files
174
+ ### Work with Branches
68
175
 
69
176
  ```bash
70
- gent add filename.js
71
- # or add all files
72
- gent add .
177
+ # Create and switch to a new branch
178
+ gent checkout -b feature-login
179
+
180
+ # List branches
181
+ gent branch
182
+
183
+ # Switch back to main
184
+ gent checkout main
185
+
186
+ # Delete a branch
187
+ gent branch -d feature-login
73
188
  ```
74
189
 
75
- ### 4. Commit Changes
190
+ ### View History
76
191
 
77
192
  ```bash
78
- gent commit -m "Initial commit"
79
- # Output: [main a1b2c3d] Initial commit
80
- # Author: Your Name <your.email@example.com>
193
+ gent log
194
+ gent log --oneline
195
+ gent log -n 5
81
196
  ```
82
197
 
83
- ## Cloud Features
84
-
85
- ### 1. Create a Cloud Repository
198
+ ### Remote Workflow
86
199
 
87
200
  ```bash
88
- # Create and link a local repo
89
- gent create my-repo --init-local
201
+ # Add a remote
202
+ gent remote add origin https://gent-api.onrender.com/api/repos/123/
203
+
204
+ # Push to remote
205
+ gent push origin main
90
206
 
91
- # Or initialize with cloud directly
92
- gent init --cloud
207
+ # Pull from remote
208
+ gent pull origin main
209
+
210
+ # Clone a repository
211
+ gent clone https://gent-api.onrender.com/api/repos/123/ my-project
93
212
  ```
94
213
 
95
- ### 2. List Your Repositories
214
+ ## Repository Structure
96
215
 
97
- ```bash
98
- gent list
99
- # or
100
- gent ls
216
+ Gent creates a `.gent` directory in your project root:
217
+
218
+ ```
219
+ .gent/
220
+ ├── config.json # Project configuration, remotes, user info
221
+ ├── commits.json # Full commit history, branches, and tags
222
+ ├── staging.json # Current staging area
223
+ ├── stash.json # Stashed changes (created on first stash)
224
+ ├── HEAD # Current branch reference
225
+ ├── objects/ # Content-addressable blob and tree store
226
+ │ ├── ab/ # First 2 characters of SHA-256 hash
227
+ │ │ └── cdef... # zlib-compressed object
228
+ │ └── ...
229
+ └── refs/
230
+ ├── heads/ # Branch references (reserved for future use)
231
+ └── tags/ # Tag references (reserved for future use)
101
232
  ```
102
233
 
103
- ### 3. Clone a Repository
234
+ Your authentication tokens are stored globally in `~/.gent/auth.json`.
235
+
236
+ ## Configuration
237
+
238
+ ### Ignore Files
239
+
240
+ Create a `.gentignore` file in your repository root to exclude files from tracking:
104
241
 
105
- ```bash
106
- gent clone <owner_id>/<repo_name>
107
- # Example: gent clone 1/my-repo
242
+ ```
243
+ node_modules/
244
+ dist/
245
+ .env
246
+ *.log
108
247
  ```
109
248
 
110
- ### 4. Push Changes
249
+ Default ignored patterns include: `.gent`, `node_modules`, `.git`, `.DS_Store`, `.env`, `dist`, `build`, `coverage`, `.vscode`, `.idea`, and `*.log`.
111
250
 
112
- ```bash
113
- gent push
114
- # or commit and push in one go
115
- gent commit -m "Update README" --push
251
+ ### Remotes
252
+
253
+ Remotes are stored in `.gent/config.json`:
254
+
255
+ ```json
256
+ {
257
+ "remotes": {
258
+ "origin": {
259
+ "url": "https://gent-api.onrender.com/api/repos/123/"
260
+ }
261
+ }
262
+ }
116
263
  ```
117
264
 
118
- ### 5. Pull Changes
265
+ ## Troubleshooting
119
266
 
267
+ **Command not found?**
268
+
269
+ Make sure the CLI is linked globally:
120
270
  ```bash
121
- gent pull
271
+ npm link
122
272
  ```
123
273
 
124
- ### 6. Manage Remotes
125
-
274
+ Or run it directly:
126
275
  ```bash
127
- gent remote add origin <owner_id>/<repo_name>
128
- gent remote -v
276
+ node src/index.js <command>
129
277
  ```
130
278
 
131
- ## Repository Structure
279
+ **Not a Gent repository?**
132
280
 
133
- Gent creates a `.gent` directory in your project root:
281
+ Run `gent init` in your project directory first.
134
282
 
135
- ```
136
- .gent/
137
- ├── config.json # Project configuration
138
- ├── objects/ # Stored file contents
139
- ├── refs/ # Branch pointers
140
- ├── remote.json # Remote configuration
141
- └── HEAD # Current branch reference
142
- ```
283
+ **No changes to commit?**
143
284
 
144
- Your authentication tokens are stored globally in `~/.gent/auth.json`.
285
+ Stage files with `gent add <files>` before committing.
286
+
287
+ **Authentication errors?**
288
+
289
+ Run `gent login` to refresh your session. Tokens expire automatically and should refresh; if not, log in again.
290
+
291
+ ## Docs
292
+
293
+ - [QUICKSTART.md](QUICKSTART.md)
294
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
145
295
 
146
296
  ## Contributing
147
297
 
148
- We welcome contributions! Please fork the repository and submit a Pull Request.
298
+ Contributions are welcome! Please fork the repository and submit a Pull Request.
149
299
 
150
300
  ## License
151
301
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gent-cli",
3
- "version": "2.1.0",
3
+ "version": "5.0.1",
4
4
  "description": "A modern, Git-like version control CLI with built-in cloud authentication and global user identity management.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,13 +1,16 @@
1
1
  /**
2
2
  * Add Command - Add file contents to the staging area
3
- * Stages files for the next commit
3
+ * Stages files with content snapshots (blobs) and diff stats
4
4
  */
5
5
 
6
+ const fs = require('fs').promises;
6
7
  const path = require('path');
7
8
  const chalk = require('chalk');
8
9
  const ora = require('ora');
9
10
  const { getGentPath, readJSON, writeJSON, pathExists, getAllFiles, getIgnorePatterns } = require('../utils/fileSystem');
10
- const { STAGING_FILE } = require('../utils/constants');
11
+ const { STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
12
+ const { storeBlob, hashBlob, isBinaryBuffer, snapshotFile } = require('../utils/hash-engine');
13
+ const { diffText, formatUnifiedDiff } = require('../utils/diff-engine');
11
14
 
12
15
  /**
13
16
  * Add files to staging area
@@ -24,7 +27,19 @@ async function add(files, options) {
24
27
 
25
28
  // Read current staging area
26
29
  const staging = await readJSON(stagingPath);
27
- const stagedFiles = new Set(staging.files || []);
30
+ const stagedEntries = staging.entries || [];
31
+ const stagedMap = new Map(stagedEntries.map(e => [e.path, e]));
32
+
33
+ // Get last commit tree for diff comparison
34
+ const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
35
+ const lastCommitHash = repository.branches[repository.currentBranch] || null;
36
+ const lastCommit = lastCommitHash
37
+ ? (repository.commits || []).find(c => c.hash === lastCommitHash)
38
+ : null;
39
+ const lastTreeMap = new Map(
40
+ (lastCommit && lastCommit.tree ? lastCommit.tree : (lastCommit ? lastCommit.files : []))
41
+ .map(f => [f.path || f.name, f.hash])
42
+ );
28
43
 
29
44
  let filesToAdd = [];
30
45
 
@@ -35,49 +50,113 @@ async function add(files, options) {
35
50
  const allFiles = await getAllFiles(cwd, ignorePatterns);
36
51
  filesToAdd = allFiles.map(f => path.relative(cwd, f));
37
52
  } else {
38
- // Add specified files
39
53
  for (const file of files) {
40
54
  const filePath = path.resolve(cwd, file);
41
-
42
55
  if (!await pathExists(filePath)) {
43
56
  spinner.warn(chalk.yellow(`Warning: File not found: ${file}`));
44
57
  continue;
45
58
  }
46
-
47
- const relativePath = path.relative(cwd, filePath);
48
- filesToAdd.push(relativePath);
59
+ filesToAdd.push(path.relative(cwd, filePath));
49
60
  }
50
61
  }
51
62
 
52
- // Add files to staging
63
+ // Snapshot each file → store blob, compute diff
53
64
  let addedCount = 0;
54
- for (const file of filesToAdd) {
55
- if (!stagedFiles.has(file)) {
56
- stagedFiles.add(file);
65
+ let totalInsertions = 0;
66
+ let totalDeletions = 0;
67
+ const diffSummaries = [];
68
+
69
+ for (const relPath of filesToAdd) {
70
+ const fullPath = path.join(cwd, relPath);
71
+ const content = await fs.readFile(fullPath);
72
+
73
+ // Skip binary files for diff (still store blob)
74
+ const binary = isBinaryBuffer(content);
75
+ const blobHash = await storeBlob(gentPath, content);
76
+
77
+ // Check if changed vs last commit
78
+ const prevHash = lastTreeMap.get(relPath);
79
+ if (prevHash === blobHash && stagedMap.has(relPath)) {
80
+ continue; // unchanged, already staged
81
+ }
82
+
83
+ // Determine change status
84
+ let status = 'added';
85
+ let stats = { insertions: 0, deletions: 0 };
86
+
87
+ if (prevHash && prevHash !== blobHash && !binary) {
88
+ status = 'modified';
89
+ try {
90
+ const { readBlobAsString } = require('../utils/hash-engine');
91
+ const oldContent = await readBlobAsString(gentPath, prevHash);
92
+ const diff = diffText(oldContent, content.toString('utf-8'));
93
+ stats = { insertions: diff.stats.insertions, deletions: diff.stats.deletions };
94
+ } catch {
95
+ // Old blob may not exist yet (first time adding objects)
96
+ }
97
+ } else if (!prevHash) {
98
+ status = 'added';
99
+ stats.insertions = content.toString('utf-8').split('\n').length;
100
+ }
101
+
102
+ totalInsertions += stats.insertions;
103
+ totalDeletions += stats.deletions;
104
+
105
+ // Update staging entry
106
+ stagedMap.set(relPath, {
107
+ path: relPath,
108
+ hash: blobHash,
109
+ status,
110
+ binary,
111
+ stats
112
+ });
113
+
114
+ diffSummaries.push({ path: relPath, status, stats, binary });
115
+ addedCount++;
116
+ }
117
+
118
+ // Detect deleted files (tracked in last commit but gone from disk)
119
+ for (const [trackedPath, trackedHash] of lastTreeMap) {
120
+ const fullPath = path.join(cwd, trackedPath);
121
+ if (!await pathExists(fullPath) && !stagedMap.has(trackedPath)) {
122
+ stagedMap.set(trackedPath, {
123
+ path: trackedPath,
124
+ hash: null,
125
+ status: 'deleted',
126
+ binary: false,
127
+ stats: { insertions: 0, deletions: 0 }
128
+ });
129
+ diffSummaries.push({ path: trackedPath, status: 'deleted', stats: { insertions: 0, deletions: 0 }, binary: false });
57
130
  addedCount++;
58
131
  }
59
132
  }
60
133
 
61
- // Save staging area
62
- staging.files = Array.from(stagedFiles);
134
+ // Save staging area (new format with entries)
135
+ staging.entries = Array.from(stagedMap.values());
136
+ staging.files = staging.entries.map(e => e.path); // backward compat
63
137
  await writeJSON(stagingPath, staging);
64
138
 
65
- spinner.succeed(chalk.green(`✓ Added ${addedCount} file(s) to staging area`));
139
+ spinner.succeed(chalk.green(`Added ${addedCount} file(s) to staging area`));
66
140
 
67
- if (addedCount > 0) {
68
- console.log(chalk.gray('\nStaged files:'));
69
- staging.files.forEach(file => {
70
- console.log(chalk.green(` ${file}`));
71
- });
72
- console.log(chalk.cyan('\nℹ Use "gent commit" to record your changes'));
141
+ if (diffSummaries.length > 0) {
142
+ console.log('');
143
+ for (const d of diffSummaries) {
144
+ const statusIcon = d.status === 'added' ? chalk.green('+ new')
145
+ : d.status === 'deleted' ? chalk.red('- del')
146
+ : chalk.yellow('~ mod');
147
+ const statsStr = d.binary ? chalk.gray('(binary)')
148
+ : chalk.green(`+${d.stats.insertions}`) + ' ' + chalk.red(`-${d.stats.deletions}`);
149
+ console.log(` ${statusIcon} ${d.path} ${statsStr}`);
150
+ }
151
+ console.log(chalk.gray(`\n Total: `) + chalk.green(`+${totalInsertions}`) + ' ' + chalk.red(`-${totalDeletions}`));
152
+ console.log(chalk.cyan('\nUse "gent commit" to record your changes'));
73
153
  }
74
154
 
75
155
  } catch (error) {
76
156
  spinner.fail(chalk.red('Failed to add files'));
77
-
78
157
  if (error.code === 'ENOENT' && error.message.includes('.gent')) {
79
158
  console.error(chalk.red('\nError: Not a gent repository'));
80
- console.log(chalk.yellow('Run "gent init" to initialize a repository'));
159
+ console.log(chalk.yellow('Run "gent init" to initialize a repository'));
81
160
  } else {
82
161
  console.error(chalk.red('\nError:'), error.message);
83
162
  }