gent-cli 5.0.3 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,23 +11,13 @@
11
11
  * gent pull → Pull from origin/current-branch
12
12
  * gent pull <remote> <branch> → Pull specific remote/branch
13
13
  *
14
- * ALGORITHM:
15
- * 1. GET /api/repos/:id/pull/?branch=<branch>&since=<lastKnownHash>
16
- * 2. Receive commits + blob objects
17
- * 3. Store blobs in local object store
18
- * 4. Append commits to local history
19
- * 5. If diverged: run 3-way merge (same as gent merge)
20
- * 6. If fast-forward: just advance pointer
21
- *
22
- * BACKEND EXPECTATIONS:
23
- * GET /api/repos/:id/pull/?branch=main&since=abc1234
24
- * Returns:
25
- * {
26
- * branch: "main",
27
- * commits: [...],
28
- * objects: [ { hash, type, data: "<base64>" } ],
29
- * head: "<remoteHeadHash>"
30
- * }
14
+ * ALGORITHM (client-side, no /pull/ endpoint):
15
+ * 1. GET .../branches/{branch}/ → get remote branch head SHA
16
+ * 2. GET .../commits/ list all remote commits
17
+ * 3. Diff local vs remote commits, find new ones
18
+ * 4. For each new commit, fetch tree + blobs via individual endpoints
19
+ * 5. Store objects locally
20
+ * 6. If diverged: run 3-way merge. If fast-forward: advance pointer
31
21
  *
32
22
  * ============================================================================
33
23
  */
@@ -37,10 +27,10 @@ const path = require('path');
37
27
  const chalk = require('chalk');
38
28
  const ora = require('ora');
39
29
  const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
40
- const { COMMITS_FILE, CONFIG_FILE } = require('../utils/constants');
30
+ const { COMMITS_FILE, CONFIG_FILE, API_ENDPOINTS, buildRepoUrl, parseRemoteUrl } = require('../utils/constants');
41
31
  const apiClient = require('../utils/api-client');
42
32
  const authStorage = require('../utils/auth-storage');
43
- const { storeBlob, objectExists } = require('../utils/hash-engine');
33
+ const { storeBlob, objectExists, readBlobAsString, decodeRemoteBlobContent } = require('../utils/hash-engine');
44
34
  const { findMergeBase, mergeTreeEntries } = require('../utils/merge-engine');
45
35
  const { generateCommitHash } = require('../utils/helpers');
46
36
 
@@ -72,57 +62,136 @@ async function pull(remoteName, branchName, options) {
72
62
  return;
73
63
  }
74
64
 
65
+ const repoInfo = parseRemoteUrl(remoteConfig.url);
66
+ if (!repoInfo) {
67
+ spinner.fail(chalk.red('Invalid remote URL format'));
68
+ console.log(chalk.yellow('Expected: /api/repos/{owner_id}/{repo_name}'));
69
+ return;
70
+ }
71
+
75
72
  const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
76
73
  const branch = branchName || repository.currentBranch;
77
74
  const localHead = repository.branches[branch] || null;
78
75
 
79
- // Fetch from remote
80
- config.remoteRefs = config.remoteRefs || {};
81
- const since = config.remoteRefs[`${remote}/${branch}`] || localHead || '';
76
+ // 1. Get remote branch info to find remote HEAD
77
+ spinner.text = `Fetching branch info for ${branch}...`;
78
+ let remoteHead;
79
+ try {
80
+ const branchInfo = await apiClient.get(
81
+ buildRepoUrl(API_ENDPOINTS.REPO_BRANCH_DETAIL, { ...repoInfo, branch_name: branch })
82
+ );
83
+ remoteHead = branchInfo.commit_sha;
84
+ } catch (error) {
85
+ if (error.response?.status === 404) {
86
+ spinner.succeed(chalk.green('Remote branch not found — nothing to pull'));
87
+ return;
88
+ }
89
+ throw error;
90
+ }
82
91
 
83
- spinner.text = `Fetching from ${remote}/${branch}...`;
92
+ if (!remoteHead || remoteHead === localHead) {
93
+ spinner.succeed(chalk.green('Already up-to-date'));
94
+ return;
95
+ }
84
96
 
85
- const response = await apiClient.get(
86
- `${remoteConfig.url}/pull/`,
87
- { params: { branch, since } }
97
+ // 2. Fetch all remote commits
98
+ spinner.text = `Fetching commits...`;
99
+ const remoteCommits = await apiClient.get(
100
+ buildRepoUrl(API_ENDPOINTS.REPO_COMMITS, repoInfo)
88
101
  );
89
102
 
90
- const remoteCommits = response.commits || [];
91
- const remoteObjects = response.objects || [];
92
- const remoteHead = response.head;
103
+ // 3. Find commits we don't have locally
104
+ const localCommitSet = new Set((repository.commits || []).map(c => c.hash || c.sha));
105
+ const newRemoteCommits = remoteCommits.filter(c => !localCommitSet.has(c.sha));
93
106
 
94
- if (remoteCommits.length === 0) {
107
+ if (newRemoteCommits.length === 0) {
108
+ // We have all commits but pointer is different — update ref
109
+ config.remoteRefs = config.remoteRefs || {};
110
+ config.remoteRefs[`${remote}/${branch}`] = remoteHead;
111
+ await writeJSON(path.join(gentPath, CONFIG_FILE), config);
95
112
  spinner.succeed(chalk.green('Already up-to-date'));
96
113
  return;
97
114
  }
98
115
 
99
- // Store received blob objects
100
- spinner.text = `Storing ${remoteObjects.length} object(s)...`;
101
- for (const obj of remoteObjects) {
102
- if (obj.type === 'blob' && obj.data) {
103
- const buf = Buffer.from(obj.data, 'base64');
104
- await storeBlob(gentPath, buf);
116
+ // 4. For each new commit, fetch tree and blobs
117
+ spinner.text = `Fetching ${newRemoteCommits.length} new commit(s) + objects...`;
118
+
119
+ const fetchedCommits = [];
120
+ for (const commit of newRemoteCommits) {
121
+ // Fetch tree for this commit
122
+ let treeEntries = [];
123
+ if (commit.tree_sha) {
124
+ try {
125
+ const tree = await apiClient.get(
126
+ buildRepoUrl(API_ENDPOINTS.REPO_TREE_DETAIL, { ...repoInfo, sha: commit.tree_sha })
127
+ );
128
+ treeEntries = tree.entries || [];
129
+ } catch {
130
+ // Tree may not be available
131
+ }
105
132
  }
106
- }
107
133
 
108
- // Check if fast-forward is possible
109
- const allCommits = [...(repository.commits || []), ...remoteCommits];
110
- const commitSet = new Set((repository.commits || []).map(c => c.hash));
134
+ // Fetch and store blobs
135
+ for (const entry of treeEntries) {
136
+ if (entry.type === 'blob' && entry.sha) {
137
+ if (!(await objectExists(gentPath, entry.sha))) {
138
+ try {
139
+ const blob = await apiClient.get(
140
+ buildRepoUrl(API_ENDPOINTS.REPO_BLOB_DETAIL, { ...repoInfo, sha: entry.sha })
141
+ );
142
+ if (blob.content) {
143
+ const buf = decodeRemoteBlobContent(blob.content, entry.sha);
144
+ await storeBlob(gentPath, buf);
145
+ }
146
+ } catch {
147
+ // Blob fetch failed, continue
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ // Convert remote commit format to local format
154
+ fetchedCommits.push({
155
+ hash: commit.sha,
156
+ message: commit.message,
157
+ author: { name: commit.author_name, email: commit.author_email },
158
+ timestamp: commit.committed_at,
159
+ parent: commit.parent_shas && commit.parent_shas[0] || null,
160
+ mergeParent: commit.parent_shas && commit.parent_shas[1] || null,
161
+ treeHash: commit.tree_sha,
162
+ tree: treeEntries.map(e => ({
163
+ mode: e.mode || '100644',
164
+ name: e.name,
165
+ hash: e.sha,
166
+ type: e.type || 'blob'
167
+ })),
168
+ files: treeEntries.map(e => ({ path: e.name, hash: e.sha })),
169
+ stats: {}
170
+ });
171
+ }
111
172
 
112
- // Add new commits (dedup)
173
+ // 5. Add new commits to local store (dedup)
113
174
  let newCount = 0;
114
- for (const commit of remoteCommits) {
175
+ const commitSet = new Set((repository.commits || []).map(c => c.hash));
176
+ for (const commit of fetchedCommits) {
115
177
  if (!commitSet.has(commit.hash)) {
178
+ repository.commits = repository.commits || [];
116
179
  repository.commits.push(commit);
117
180
  commitSet.add(commit.hash);
118
181
  newCount++;
119
182
  }
120
183
  }
121
184
 
185
+ // 6. Merge strategy
186
+ config.remoteRefs = config.remoteRefs || {};
187
+
122
188
  if (!localHead || isAncestor(repository.commits, localHead, remoteHead)) {
123
189
  // Fast-forward
190
+ const previousTree = localHead ? getCommitTree(repository.commits, localHead) : [];
191
+ const nextTree = getCommitTree(repository.commits, remoteHead);
124
192
  repository.branches[branch] = remoteHead;
125
193
  await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
194
+ await checkoutTree(gentPath, process.cwd(), previousTree, nextTree);
126
195
 
127
196
  config.remoteRefs[`${remote}/${branch}`] = remoteHead;
128
197
  await writeJSON(path.join(gentPath, CONFIG_FILE), config);
@@ -168,6 +237,9 @@ async function pull(remoteName, branchName, options) {
168
237
  repository.commits.push(mergeCommit);
169
238
  repository.branches[branch] = mergeCommit.hash;
170
239
  await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
240
+ if (!mergeResult.hasConflicts) {
241
+ await checkoutTree(gentPath, process.cwd(), oursTree, mergeResult.mergedEntries);
242
+ }
171
243
 
172
244
  config.remoteRefs[`${remote}/${branch}`] = remoteHead;
173
245
  await writeJSON(path.join(gentPath, CONFIG_FILE), config);
@@ -187,8 +259,8 @@ async function pull(remoteName, branchName, options) {
187
259
  spinner.fail(chalk.red('Pull failed'));
188
260
  if (error.response?.status === 401) {
189
261
  console.error(chalk.red('Authentication failed — run "gent login"'));
190
- } else if (error.response?.data?.message) {
191
- console.error(chalk.red(error.response.data.message));
262
+ } else if (error.response?.data) {
263
+ console.error(chalk.red(JSON.stringify(error.response.data)));
192
264
  } else {
193
265
  console.error(chalk.red('Error:'), error.message);
194
266
  }
@@ -210,4 +282,40 @@ function isAncestor(commits, hashA, hashB) {
210
282
  return false;
211
283
  }
212
284
 
285
+ function getCommitTree(commits, hash) {
286
+ const commit = commits.find(c => c.hash === hash);
287
+ if (!commit) return [];
288
+ return commit.tree || (commit.files || []).map(f => ({
289
+ mode: '100644',
290
+ name: f.path || f.name,
291
+ hash: f.hash,
292
+ type: 'blob'
293
+ }));
294
+ }
295
+
296
+ async function checkoutTree(gentPath, cwd, previousTree, nextTree) {
297
+ const nextPaths = new Set(nextTree.map(e => e.name || e.path));
298
+
299
+ for (const entry of previousTree) {
300
+ const relPath = entry.name || entry.path;
301
+ if (!relPath || nextPaths.has(relPath)) continue;
302
+ try {
303
+ await fs.unlink(path.join(cwd, relPath));
304
+ } catch {
305
+ // File already absent.
306
+ }
307
+ }
308
+
309
+ for (const entry of nextTree) {
310
+ if (entry.type && entry.type !== 'blob') continue;
311
+ const relPath = entry.name || entry.path;
312
+ if (!relPath || !entry.hash) continue;
313
+
314
+ const content = await readBlobAsString(gentPath, entry.hash);
315
+ const fullPath = path.join(cwd, relPath);
316
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
317
+ await fs.writeFile(fullPath, content, 'utf-8');
318
+ }
319
+ }
320
+
213
321
  module.exports = pull;
@@ -14,28 +14,22 @@
14
14
  *
15
15
  * ALGORITHM:
16
16
  * 1. Read local commits since last known remote HEAD
17
- * 2. Collect all blob objects referenced by those commits
18
- * 3. POST packfile (commits + blobs + trees) to remote /push/ endpoint
19
- * 4. Remote updates branch pointer
17
+ * 2. Build proper pack: collect tree objects + blob objects
18
+ * 3. POST packfile to /api/repos/{owner_id}/{repo_name}/push/
19
+ * 4. Remote updates branch pointer via branch_updates
20
20
  *
21
- * DATA FORMAT SENT TO BACKEND:
22
- * POST /api/repos/:id/push/
21
+ * DATA FORMAT SENT TO BACKEND (PushPackRequest):
22
+ * POST /api/repos/{owner_id}/{repo_name}/push/
23
23
  * {
24
- * branch: "main",
25
- * force: false,
26
- * commits: [ { hash, message, author, timestamp, parent, treeHash, tree, files, stats } ],
27
- * objects: [ { hash, type: "blob", data: "<base64>" } ],
24
+ * pack: {
25
+ * commits: [{ sha, message, tree_sha, parent_shas, author_name, author_email, committed_at }],
26
+ * trees: [{ sha, entries: [{ type, mode, name, sha }] }],
27
+ * blobs: [{ sha, size, content, encoding }]
28
+ * },
29
+ * branch_updates: [{ name, commit_sha }],
28
30
  * tags: { "v1.0": { hash, message, ... } }
29
31
  * }
30
32
  *
31
- * BACKEND EXPECTATIONS:
32
- * - Validate auth (JWT Bearer token)
33
- * - Verify fast-forward (reject non-ff unless force=true)
34
- * - Store blob objects in backend object store
35
- * - Append commits to branch history
36
- * - Update branch refs
37
- * - Return { success, ref, hash }
38
- *
39
33
  * ============================================================================
40
34
  */
41
35
 
@@ -44,10 +38,10 @@ const path = require('path');
44
38
  const chalk = require('chalk');
45
39
  const ora = require('ora');
46
40
  const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
47
- const { COMMITS_FILE, CONFIG_FILE } = require('../utils/constants');
41
+ const { COMMITS_FILE, CONFIG_FILE, API_ENDPOINTS, buildRepoUrl, parseRemoteUrl } = require('../utils/constants');
48
42
  const apiClient = require('../utils/api-client');
49
43
  const authStorage = require('../utils/auth-storage');
50
- const { readBlob, objectExists } = require('../utils/hash-engine');
44
+ const { readBlob, readTree, objectExists, readBlobAsString } = require('../utils/hash-engine');
51
45
 
52
46
  /**
53
47
  * Push commits to remote
@@ -81,6 +75,15 @@ async function push(remoteName, branchName, options) {
81
75
  return;
82
76
  }
83
77
 
78
+ // Parse remote URL to get owner_id and repo_name
79
+ const repoInfo = parseRemoteUrl(remoteConfig.url);
80
+ if (!repoInfo) {
81
+ spinner.fail(chalk.red('Invalid remote URL format'));
82
+ console.log(chalk.yellow('Expected: /api/repos/{owner_id}/{repo_name}'));
83
+ console.log(chalk.yellow('Use "gent remote set-url origin <url>" to fix'));
84
+ return;
85
+ }
86
+
84
87
  const repository = await readJSON(path.join(gentPath, COMMITS_FILE));
85
88
  const branch = branchName || repository.currentBranch;
86
89
  const localHead = repository.branches[branch];
@@ -103,26 +106,71 @@ async function push(remoteName, branchName, options) {
103
106
 
104
107
  spinner.text = `Pushing ${commitsToPush.length} commit(s) to ${remote}/${branch}...`;
105
108
 
106
- // Collect all blob hashes from commits to push
107
- const blobHashes = new Set();
109
+ // Collect all tree and blob objects from commits
110
+ const treeShas = new Set();
111
+ const blobShas = new Set();
112
+
108
113
  for (const commit of commitsToPush) {
114
+ // Collect tree SHA
115
+ if (commit.treeHash) {
116
+ treeShas.add(commit.treeHash);
117
+ }
118
+ // Collect blob hashes from tree entries
109
119
  const tree = commit.tree || commit.files || [];
110
120
  for (const entry of tree) {
111
- const h = entry.hash;
112
- if (h) blobHashes.add(h);
121
+ if (entry.hash) blobShas.add(entry.hash);
113
122
  }
114
123
  }
115
124
 
116
- // Read blob data for transfer
117
- const objects = [];
118
- for (const hash of blobHashes) {
125
+ // Build tree objects for the pack
126
+ const packTrees = [];
127
+ for (const treeSha of treeShas) {
128
+ try {
129
+ if (await objectExists(gentPath, treeSha)) {
130
+ const entries = await readTree(gentPath, treeSha);
131
+ packTrees.push({
132
+ sha: treeSha,
133
+ entries: entries.map(e => ({
134
+ type: e.type || 'blob',
135
+ mode: e.mode || '100644',
136
+ name: e.name,
137
+ sha: e.hash
138
+ }))
139
+ });
140
+ }
141
+ } catch {
142
+ // If tree can't be read from object store, build from commit data
143
+ }
144
+ }
145
+
146
+ // If no tree objects from object store, build from commit tree data
147
+ if (packTrees.length === 0) {
148
+ for (const commit of commitsToPush) {
149
+ if (commit.treeHash && commit.tree) {
150
+ packTrees.push({
151
+ sha: commit.treeHash,
152
+ entries: commit.tree.map(e => ({
153
+ type: e.type || 'blob',
154
+ mode: e.mode || '100644',
155
+ name: e.name || e.path,
156
+ sha: e.hash
157
+ }))
158
+ });
159
+ }
160
+ }
161
+ }
162
+
163
+ // Build blob objects for the pack
164
+ const packBlobs = [];
165
+ for (const hash of blobShas) {
119
166
  try {
120
167
  if (await objectExists(gentPath, hash)) {
121
168
  const data = await readBlob(gentPath, hash);
122
- objects.push({
123
- hash,
124
- type: 'blob',
125
- data: data.toString('base64')
169
+ packBlobs.push({
170
+ sha: hash,
171
+ size: data.length,
172
+ content: data.toString('base64'),
173
+ encoding: 'base64'
126
174
  });
127
175
  }
128
176
  } catch {
@@ -130,31 +178,34 @@ async function push(remoteName, branchName, options) {
130
178
  }
131
179
  }
132
180
 
133
- // Build push payload
181
+ // Build commits for the pack
182
+ const packCommits = commitsToPush.map(c => ({
183
+ sha: c.hash,
184
+ message: c.message,
185
+ tree_sha: c.treeHash || '',
186
+ parent_shas: [c.parent, c.mergeParent].filter(Boolean),
187
+ author_name: typeof c.author === 'object' ? (c.author.name || 'Unknown') : (c.author || 'Unknown'),
188
+ author_email: typeof c.author === 'object' ? (c.author.email || '') : '',
189
+ committed_at: c.timestamp || new Date().toISOString()
190
+ }));
191
+
192
+ // Build push payload matching PushPackRequest schema
134
193
  const payload = {
135
- branch,
136
- force: !!options.force,
137
- commits: commitsToPush.map(c => ({
138
- hash: c.hash,
139
- message: c.message,
140
- author: c.author,
141
- timestamp: c.timestamp,
142
- parent: c.parent,
143
- mergeParent: c.mergeParent || null,
144
- treeHash: c.treeHash || null,
145
- tree: c.tree || null,
146
- files: c.files || [],
147
- stats: c.stats || {}
148
- })),
149
- objects,
194
+ pack: {
195
+ commits: packCommits,
196
+ trees: packTrees,
197
+ blobs: packBlobs
198
+ },
199
+ branch_updates: [{
200
+ name: branch,
201
+ commit_sha: localHead
202
+ }],
150
203
  tags: repository.tags || {}
151
204
  };
152
205
 
153
206
  // Send to backend
154
- const response = await apiClient.post(
155
- `${remoteConfig.url}/push/`,
156
- payload
157
- );
207
+ const pushUrl = buildRepoUrl(API_ENDPOINTS.REPO_PUSH, repoInfo);
208
+ const response = await apiClient.post(pushUrl, payload);
158
209
 
159
210
  // Update remote ref
160
211
  config.remoteRefs[`${remote}/${branch}`] = localHead;
@@ -162,7 +213,7 @@ async function push(remoteName, branchName, options) {
162
213
 
163
214
  spinner.succeed(chalk.green(`Pushed ${commitsToPush.length} commit(s) to ${remote}/${branch}`));
164
215
  console.log(chalk.gray(` ${localHead.substring(0, 7)} → ${remote}/${branch}`));
165
- console.log(chalk.gray(` ${objects.length} object(s) transferred`));
216
+ console.log(chalk.gray(` ${packBlobs.length} blob(s), ${packTrees.length} tree(s) transferred`));
166
217
 
167
218
  } catch (error) {
168
219
  spinner.fail(chalk.red('Push failed'));
@@ -172,8 +223,10 @@ async function push(remoteName, branchName, options) {
172
223
  console.log(chalk.yellow('Run "gent pull" first, or use "gent push --force"'));
173
224
  } else if (error.response?.status === 401) {
174
225
  console.error(chalk.red('Authentication failed — run "gent login"'));
175
- } else if (error.response?.data?.message) {
176
- console.error(chalk.red(error.response.data.message));
226
+ } else if (error.response?.status === 403) {
227
+ console.error(chalk.red('Permission denied — only repo owner can push'));
228
+ } else if (error.response?.data) {
229
+ console.error(chalk.red(JSON.stringify(error.response.data, null, 2)));
177
230
  } else {
178
231
  console.error(chalk.red('Error:'), error.message);
179
232
  }
@@ -17,12 +17,19 @@ async function register(options) {
17
17
  console.log(chalk.cyan('\n🚀 Create your Gent account\n'));
18
18
 
19
19
  try {
20
+ let email = options.email;
21
+ let password = options.password;
22
+ let passwordConfirm = options.passwordConfirm;
23
+ let firstName = options.firstName;
24
+ let lastName = options.lastName;
25
+
20
26
  // Prompt for user information
21
27
  const answers = await inquirer.prompt([
22
28
  {
23
29
  type: 'input',
24
30
  name: 'email',
25
31
  message: 'Email address:',
32
+ when: !email,
26
33
  validate: (input) => {
27
34
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
28
35
  return emailRegex.test(input) || 'Please enter a valid email address';
@@ -33,6 +40,7 @@ async function register(options) {
33
40
  name: 'password',
34
41
  message: 'Password:',
35
42
  mask: '*',
43
+ when: !password,
36
44
  validate: (input) => {
37
45
  if (input.length < 8) {
38
46
  return 'Password must be at least 8 characters long';
@@ -45,33 +53,46 @@ async function register(options) {
45
53
  name: 'passwordConfirm',
46
54
  message: 'Confirm password:',
47
55
  mask: '*',
56
+ when: !passwordConfirm,
48
57
  validate: (input, answers) => {
49
- return input === answers.password || 'Passwords do not match';
58
+ return input === (password || answers.password) || 'Passwords do not match';
50
59
  }
51
60
  },
52
61
  {
53
62
  type: 'input',
54
63
  name: 'firstName',
55
64
  message: 'First name:',
65
+ when: firstName === undefined,
56
66
  default: ''
57
67
  },
58
68
  {
59
69
  type: 'input',
60
70
  name: 'lastName',
61
71
  message: 'Last name:',
72
+ when: lastName === undefined,
62
73
  default: ''
63
74
  }
64
75
  ]);
65
76
 
77
+ email = email || answers.email;
78
+ password = password || answers.password;
79
+ passwordConfirm = passwordConfirm || answers.passwordConfirm;
80
+ firstName = firstName !== undefined ? firstName : answers.firstName;
81
+ lastName = lastName !== undefined ? lastName : answers.lastName;
82
+
83
+ if (!email || !password || !passwordConfirm) {
84
+ throw new Error('Email, password, and password confirmation are required');
85
+ }
86
+
66
87
  const spinner = ora('Creating your account...').start();
67
88
 
68
89
  // Register user
69
90
  const user = await authService.register(
70
- answers.email,
71
- answers.password,
72
- answers.passwordConfirm,
73
- answers.firstName,
74
- answers.lastName
91
+ email,
92
+ password,
93
+ passwordConfirm,
94
+ firstName || '',
95
+ lastName || ''
75
96
  );
76
97
 
77
98
  spinner.succeed(chalk.green('✓ Account created successfully!'));
@@ -29,7 +29,7 @@
29
29
  const path = require('path');
30
30
  const chalk = require('chalk');
31
31
  const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
32
- const { CONFIG_FILE } = require('../utils/constants');
32
+ const { CONFIG_FILE, parseRemoteUrl } = require('../utils/constants');
33
33
 
34
34
  /**
35
35
  * Manage remotes
@@ -55,6 +55,13 @@ async function remote(subcommand, args, options) {
55
55
  console.error(chalk.red(`Remote '${name}' already exists`));
56
56
  return;
57
57
  }
58
+ // Validate URL format
59
+ if (!parseRemoteUrl(url)) {
60
+ console.error(chalk.red('Invalid remote URL format'));
61
+ console.log(chalk.yellow('Expected: /api/repos/{owner_id}/{repo_name}'));
62
+ console.log(chalk.yellow('Example: /api/repos/1/my-project'));
63
+ return;
64
+ }
58
65
  config.remotes[name] = { url };
59
66
  await writeJSON(configPath, config);
60
67
  console.log(chalk.green(`Added remote '${name}' → ${url}`));
@@ -85,6 +92,12 @@ async function remote(subcommand, args, options) {
85
92
  console.error(chalk.red(`Remote '${name}' not found`));
86
93
  return;
87
94
  }
95
+ // Validate URL format
96
+ if (!parseRemoteUrl(url)) {
97
+ console.error(chalk.red('Invalid remote URL format'));
98
+ console.log(chalk.yellow('Expected: /api/repos/{owner_id}/{repo_name}'));
99
+ return;
100
+ }
88
101
  config.remotes[name].url = url;
89
102
  await writeJSON(configPath, config);
90
103
  console.log(chalk.green(`Updated '${name}' → ${url}`));