@abhaydesu/git-pilot 1.0.2 → 1.1.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 CHANGED
@@ -1,55 +1,104 @@
1
- # Git Pilot
1
+ <div align="center" display="inline">
2
+ <img width="30" height="30" alt="logo" src="./public/logo-pilot.png" />
3
+ </div>
2
4
 
3
- [![NPM Version](https://img.shields.io/npm/v/@abhaydesu/git-pilot)](https://www.npmjs.com/package/@abhaydesu/git-pilot)
5
+ # Git Pilot API
4
6
 
5
- An AI-powered assistant that lives in your command line to streamline your Git workflow.
7
+ This is the backend API server that powers the <a href="https://www.npmjs.com/package/@abhaydesu/git-pilot">`git-pilot`</a> CLI tool. It handles the secure communication with AI models to generate Git commands, commit messages, undo actions, and branch names.
6
8
 
9
+ ## ◼️ Purpose
7
10
 
8
- ## ◼️ The Problem
9
- Remembering complex commands like interactive rebase can be a pain. Writing well-formatted commit messages is a chore. Git is powerful, but its interface can sometimes get in the way of a fast workflow.
11
+ The primary role of this API is to act as a secure intermediary between the <a href="https://github.com/abhaydesu/git-pilot-cli">`git-pilot-cli` </a> and the AI service (Google Gemini). This architecture ensures that the AI API keys are never exposed on a user's local machine.
10
12
 
11
- **Git Pilot** solves this by acting as your intelligent copilot, translating your natural language intent into the exact commands and messages you need.
13
+ ## ◼️ Technology Stack
12
14
 
13
- ## ◼️ Features
14
- * **Natural Language to Git Command:** Translate plain English requests like "squash the last 3 commits" into the precise Git command (`git rebase -i HEAD~3`).
15
- * **AI-Powered Commit Messages:** Analyzes your staged changes (`git diff`) and generates a perfect commit message following the Conventional Commits specification.
15
+ * **Runtime:** Node.js
16
+ * **Framework:** Express.js
17
+ * **AI Service:** Google Gemini API
18
+ * **Deployment:** Vercel
16
19
 
17
- * **Interactive & Safe:** Always asks for your confirmation before executing any command, ensuring you're always in control.
20
+ ## ◼️ API Endpoints
18
21
 
19
- ## ◼️ Installation
20
- Make sure you have Node.js (v18+) and Git installed. Then, run the following command to install Git Pilot globally:
22
+ The API exposes the following endpoints:
21
23
 
22
- ```bash
23
- npm install -g @abhaydesu/git-pilot
24
- ```
24
+ ### 1. Generate Commit Message
25
25
 
26
- ### ◾ Usage
26
+ * **Route:** `POST /api/pilot-commit`
27
+ * **Description:** Analyzes a Git diff and a user's intent to generate a conventional commit message.
28
+ * **Request Body (JSON):**
27
29
 
28
- #### **Running a Git Command**
29
- Run the run command with the task you want to perform in quotes.
30
+ ```json
31
+ {
32
+ "intent": "string",
33
+ "diff": "string"
34
+ }
35
+ ```
36
+ * **Success Response (200 - JSON):**
30
37
 
31
- ```Bash
38
+ ```json
39
+ {
40
+ "message": "string"
41
+ }
42
+ ```
32
43
 
33
- git pilot run "create a new branch called feature/auth"
34
- ```
35
- The tool will suggest the correct Git command and ask for confirmation before executing it.
44
+ ### 2. Generate Git Command
36
45
 
46
+ * **Route:** `POST /api/pilot-run`
47
+ * **Description:** Translates a user's natural language request into an executable, safe Git command.
48
+ * **Request Body (JSON):**
37
49
 
38
- #### **Generating a Commit Message**
39
- Stage your files (git add .).
50
+ ```json
51
+ {
52
+ "request": "string"
53
+ }
54
+ ```
55
+ * **Success Response (200 - JSON):**
40
56
 
41
- Run the commit command with your intent in quotes.
57
+ ```json
58
+ {
59
+ "command": "string"
60
+ }
61
+ ```
42
62
 
43
- ```Bash
63
+ ### 3. Undo Last Action
44
64
 
45
- git pilot commit "add new user profile page"
46
- ```
47
- The tool will suggest a message and ask for confirmation before committing.
65
+ * **Route:** `POST /api/pilot-undo`
66
+ * **Description:** Suggests a safe Git command to undo the most recent significant action (merge, rebase, commit).
67
+ * **Request Body (JSON):**
48
68
 
69
+ ```json
70
+ {
71
+ "reflog": "string"
72
+ }
73
+ ```
74
+ * **Success Response (200 - JSON):**
49
75
 
76
+ ```json
77
+ {
78
+ "command": "string",
79
+ "explanation": "string"
80
+ }
81
+ ```
50
82
 
51
- ### ◾ How It Works
52
- Git Pilot is a CLI tool that communicates with a secure backend API. This API uses Google's Gemini models to understand your intent and analyze code, keeping your API keys safe and off your local machine.
83
+ ### 4. Generate Branch Name
53
84
 
54
- ### ◾ License
55
- This project is licensed under the MIT License.
85
+ * **Route:** `POST /api/pilot-branch`
86
+ * **Description:** Converts a natural language description into a conventional, kebab-case Git branch name.
87
+ * **Request Body (JSON):**
88
+
89
+ ```json
90
+ {
91
+ "description": "string"
92
+ }
93
+ ```
94
+ * **Success Response (200 - JSON):**
95
+
96
+ ```json
97
+ {
98
+ "branchName": "string"
99
+ }
100
+ ```
101
+
102
+ ### ◾ Deployment
103
+
104
+ This API is designed for and deployed on Vercel.
package/index.js CHANGED
@@ -1,28 +1,30 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { program } from "commander";
4
4
  import { execa, execaCommand } from "execa";
5
5
  import axios from "axios";
6
6
  import inquirer from "inquirer";
7
7
  import chalk from "chalk";
8
-
9
8
  import ora from "ora";
10
9
 
11
- const API_COMMIT_URL = "http://git-pilot-api.vercel.app/api/commit-message";
12
- const API_COMMAND_URL ="http://git-pilot-api.vercel.app/api/git-command"
13
-
10
+ const API_BRANCH_URL = "https://git-pilot-api.vercel.app/api/pilot-branch";
11
+ const API_COMMIT_URL = "https://git-pilot-api.vercel.app/api/pilot-commit";
12
+ const API_COMMAND_URL = "https://git-pilot-api.vercel.app/api/pilot-run";
13
+ const API_UNDO_URL = "https://git-pilot-api.vercel.app/api/pilot-undo";
14
14
 
15
15
  const getStagedDiff = async () => {
16
16
  try {
17
17
  const { stdout } = await execa("git", ["diff", "--staged"]);
18
18
  return stdout;
19
19
  } catch (error) {
20
- console.error(chalk.red("Error getting git diff:"), error?.message || error);
20
+ console.error(
21
+ chalk.red("Error getting git diff:"),
22
+ error?.message || error
23
+ );
21
24
  process.exit(1);
22
25
  }
23
26
  };
24
27
 
25
-
26
28
  program
27
29
  .name("git-pilot")
28
30
  .description("An AI-powered Git assistant in your command line.");
@@ -35,56 +37,155 @@ program
35
37
  'The user\'s intent for the commit (e.g., "add a new feature")'
36
38
  )
37
39
  .action(async (intent) => {
38
- const spinner = ora('Analyzing your staged changes...').start();
40
+ const spinner = ora("Analyzing your staged changes...").start();
39
41
 
40
42
  const diff = await getStagedDiff();
41
43
 
42
44
  if (!diff) {
43
- spinner.warn("No staged changes found. Please stage your files with `git add`.");
45
+ spinner.warn(
46
+ "No staged changes found. Please stage your files with `git add`."
47
+ );
44
48
  process.exit(0);
45
49
  }
46
50
 
47
51
  try {
48
- spinner.text = 'Generating commit message...';
52
+ spinner.text = "Generating commit message...";
49
53
 
50
- const response = await axios.post(API_COMMIT_URL,
51
- { intent, diff},
54
+ const response = await axios.post(
55
+ API_COMMIT_URL,
56
+ { intent, diff },
52
57
  { timeout: 30000 }
53
- );
58
+ );
54
59
 
55
60
  const suggestedMessage = response.data.message;
56
-
61
+
57
62
  spinner.succeed();
58
63
 
59
64
  console.log(chalk.green("\n--- Suggested Message ---"));
60
65
  console.log(chalk.cyan(suggestedMessage));
61
66
  console.log(chalk.green("-------------------------\n"));
62
67
 
68
+ const { choice } = await inquirer.prompt([
69
+ {
70
+ type: "list",
71
+ name: "choice",
72
+ message: "What would you like to do?",
73
+ choices: [
74
+ { name: "Accept", value: "accept" },
75
+ { name: "Edit", value: "edit" },
76
+ { name: "Abort", value: "abort" },
77
+ ],
78
+ },
79
+ ]);
80
+
81
+ if (choice === 'abort') {
82
+ console.log(chalk.red('Commit aborted by user.'));
83
+ process.exit(0);
84
+ }
85
+
86
+ if (choice == "edit") {
87
+ const { editedMessage } = await inquirer.prompt([
88
+ {
89
+ type: "editor",
90
+ name: "editedMessage",
91
+ message: "Edit the commit message: ",
92
+ default: suggestedMessage,
93
+ validate: (input) =>
94
+ input.length > 0 ? true : "Commit message cannot be empty.",
95
+ },
96
+ ]);
97
+
98
+ await execa("git", ["commit", "-m", editedMessage]);
99
+ console.log(chalk.bgGreen.black("Commit successful!"));
100
+ }
101
+
102
+ if (choice === "accept") {
103
+ await execa("git", ["commit", "-m", suggestedMessage]);
104
+ console.log(chalk.bgGreen.black("Commit successful!"));
105
+ }
106
+
107
+ } catch (error) {
108
+ if (error.response) {
109
+ console.error(
110
+ chalk.red(
111
+ `API Error: ${error.response.status} - ${error.response.data.error}`
112
+ )
113
+ );
114
+ } else {
115
+ console.error(
116
+ chalk.red("An unexpected error occurred: ", error.message)
117
+ );
118
+ }
119
+
120
+ process.exit(1);
121
+ }
122
+ });
123
+
124
+ program
125
+ .command("run")
126
+ .description("Translate natural language into a Git command.")
127
+ .argument(
128
+ "<string>",
129
+ 'The task you want to perform (e.g. "squash the last 3 commits")'
130
+ )
131
+ .action(async (request) => {
132
+ try {
133
+ const spinner = ora("Fetching the right command...").start();
134
+
135
+ const response = await axios.post(
136
+ API_COMMAND_URL,
137
+ { request },
138
+ { timeout: 30000 }
139
+ );
140
+ const suggestedCommand = response.data.command;
141
+
142
+ if (suggestedCommand.startsWith("Error:")) {
143
+ spinner.fail(suggestedCommand);
144
+ process.exit(1);
145
+ }
146
+
147
+ spinner.succeed();
148
+
149
+ console.log(
150
+ chalk.yellow.bold(
151
+ "\nWARNING: The suggested command will be executed in your shell."
152
+ )
153
+ );
154
+ console.log(
155
+ chalk.yellow("Always review commands carefully before confirming.\n")
156
+ );
157
+
158
+ console.log(chalk.green("\n------ Suggested Command ------"));
159
+ console.log(chalk.cyan(suggestedCommand));
160
+ console.log(chalk.green("-------------------------------\n"));
161
+
63
162
  const { confirm } = await inquirer.prompt([
64
163
  {
65
164
  type: "confirm",
66
165
  name: "confirm",
67
- message: "Commit with this message?",
68
- default: true,
166
+ message: "Execute this command?",
167
+ default: false,
69
168
  },
70
169
  ]);
71
170
 
72
171
  if (confirm) {
73
- await execa("git", ["commit", "-m", suggestedMessage]);
74
- console.log(chalk.bgGreen.black("Commit successful!"));
172
+ await execaCommand(suggestedCommand, { stdio: "inherit" });
173
+ console.log(chalk.bgGreen.black("Command executed successfully!"));
75
174
  } else {
76
- console.log(chalk.red("Commit aborted by user."));
175
+ console.log(chalk.red("Execution aborted by user."));
77
176
  }
78
177
  } catch (error) {
79
- if (error.response) {
80
- console.error(
81
- chalk.red(
82
- `API Error: ${error.response.status} - ${error.response.data.error}`
83
- )
84
- );
178
+ if (error.response) {
179
+ console.error(
180
+ chalk.red(
181
+ `API Error: ${error.response.status} - ${
182
+ error.response.data.error || "No message"
183
+ }`
184
+ )
185
+ );
85
186
  } else {
86
187
  console.error(
87
- chalk.red("An unexpected error occured: ", error.message)
188
+ chalk.red(`An unexpected error occurred: ${error.message}`)
88
189
  );
89
190
  }
90
191
 
@@ -92,62 +193,163 @@ program
92
193
  }
93
194
  });
94
195
 
95
-
96
196
  program
97
- .command('run')
98
- .description('Translate natural language into a Git command.')
99
- .argument('<string>', 'The task you want to perform (e.g. "squash the last 3 commits')
100
- .action(async (request) => {
197
+ .command("undo")
198
+ .description("Suggests a command to undo your last major Git action")
199
+ .action(async () => {
200
+ const spinner = ora("Analyzing your recent Git history...").start();
201
+
101
202
  try {
102
- const spinner = ora('Fetching the right command...').start();
203
+ const { stdout: reflog } = await execa("git", ["reflog", "-n", "15"]);
103
204
 
104
- const response = await axios.post(
105
- API_COMMAND_URL,
106
- { request },
107
- { timeout: 30000 }
108
- );
109
- const suggestedCommand = response.data.command;
205
+ spinner.text = "Suggesting an undo command...";
110
206
 
111
- if (suggestedCommand.startsWith('Error:')) {
112
- spinner.fail(suggestedCommand);
113
- process.exit(1);
114
- }
207
+ const response = await axios.post(API_UNDO_URL, { reflog });
208
+ const { command, explanation } = response.data;
115
209
 
116
- spinner.succeed();
210
+ if (!command) {
211
+ spinner.warn("Couldn't determine a safe action to undo.");
212
+ process.exit(0);
213
+ }
117
214
 
118
- console.log(chalk.yellow.bold('\nWARNING: The suggested command will be executed in your shell.'));
119
- console.log(chalk.yellow('Always review commands carefully before confirming.\n'));
215
+ spinner.succeed();
120
216
 
121
- console.log(chalk.green('\n------ Suggested Command ------'));
122
- console.log(chalk.cyan(suggestedCommand));
123
- console.log(chalk.green('-------------------------------\n'));
217
+ console.log(chalk.green("\n--- Suggested Undo Command ---"));
218
+ console.log(chalk.cyan(command));
219
+ console.log(chalk.green("------------------------------\n"));
220
+ console.log(chalk.yellow(explanation));
124
221
 
125
- const { confirm } = await inquirer.prompt([
126
- {
127
- type: 'confirm',
128
- name: 'confirm',
129
- message: 'Execute this command?',
130
- default: false,
131
- },
132
- ]);
222
+ const { confirm } = await inquirer.prompt([
223
+ {
224
+ type: "confirm",
225
+ name: "confirm",
226
+ message: "Execute this undo command?",
227
+ default: false,
228
+ },
229
+ ]);
230
+
231
+ if (confirm) {
232
+ if (command.startsWith('git rebase --abort')) {
233
+ try {
234
+ const { stdout: gitDirRaw } = await execa('git', ['rev-parse', '--git-dir']);
235
+ const gitDir = gitDirRaw.trim();
133
236
 
134
- if (confirm) {
135
- await execaCommand(suggestedCommand, {stdio: 'inherit'});
136
- console.log(chalk.bgGreen.black('Command executed successfully!'));
137
- } else {
138
- console.log(chalk.red('Execution aborted by user.'));
237
+ const fs = await import('fs/promises');
238
+
239
+ const rebaseApply = `${gitDir}/rebase-apply`;
240
+ const rebaseMerge = `${gitDir}/rebase-merge`;
241
+
242
+ const hasRebaseApply = await fs.access(rebaseApply).then(() => true).catch(() => false);
243
+ const hasRebaseMerge = await fs.access(rebaseMerge).then(() => true).catch(() => false);
244
+
245
+ if (!hasRebaseApply && !hasRebaseMerge) {
246
+ console.log(chalk.yellow('No rebase in progress — abort not necessary.'));
247
+ process.exit(0);
248
+ }
249
+ } catch (e) {
250
+ }
139
251
  }
140
252
 
253
+ await execaCommand(command, { stdio: "inherit" });
254
+ console.log(chalk.bgGreen.black("Action successfully undone!"));
255
+ } else {
256
+ console.log(chalk.red("Execution aborted by user."));
257
+ }
141
258
  } catch (error) {
142
- if (error.response) {
143
- console.error(
144
- chalk.red(`API Error: ${error.response.status} - ${error.response.data.error || 'No message'}`));
145
- } else {
146
- console.error(chalk.red(`An unexpected error occurred: ${error.message}`));
147
- }
259
+ spinner.fail();
260
+
261
+ if (error.response) {
262
+ console.error(chalk.red(`API Error: ${error.response.status} - ${error.response.data.error || 'No message'}`));
263
+ } else {
264
+ console.error(chalk.red(`An unexpected error occurred: ${error.message}`));
265
+ }
266
+
267
+ process.exit(1);
268
+ }
269
+ });
148
270
 
149
- process.exit(1);
150
- }
151
- })
271
+ program
272
+ .command('branch')
273
+ .description('Generates a conventional branch name from a description and creates the branch.')
274
+ .argument('<string>', 'A description of the branch\'s purpose (e.g., "fix login bug")')
275
+ .action(async (description) => {
276
+ const spinner = ora('Generating a conventional branch name...').start();
277
+ try {
278
+ const response = await axios.post(API_BRANCH_URL, { description });
279
+ const { branchName } = response.data;
280
+
281
+ spinner.succeed();
282
+
283
+ console.log(chalk.green('\n--- Suggested Branch Name ---'));
284
+ console.log(chalk.cyan(branchName));
285
+ console.log(chalk.green('-----------------------------\n'));
286
+
287
+ const { choice } = await inquirer.prompt([
288
+ {
289
+ type: 'list',
290
+ name: 'choice',
291
+ message: 'What would you like to do?',
292
+ choices: [
293
+ { name: 'Accept', value: 'accept' },
294
+ { name: 'Edit', value: 'edit' },
295
+ { name: 'Abort', value: 'abort' },
296
+ ],
297
+ },
298
+ ]);
299
+
300
+ if (choice === 'abort') {
301
+ console.log(chalk.red('Branch creation aborted by user.'));
302
+ process.exit(0);
303
+ }
304
+
305
+ let finalBranchName = branchName;
306
+
307
+ if (choice === 'edit') {
308
+ const editResp = await inquirer.prompt({
309
+ type: 'editor',
310
+ name: 'editedBranchName',
311
+ message: 'Edit the branch name:',
312
+ default: branchName,
313
+ validate: value => (value && value.trim().length > 0) ? true : 'Branch name cannot be empty'
314
+ }, {
315
+ onCancel: () => {
316
+ console.log(chalk.red('\nEdit cancelled.'));
317
+ process.exit(0);
318
+ }
319
+ });
320
+
321
+ finalBranchName = editResp.editedBranchName;
322
+ }
323
+
324
+ const slugifyBranchName = (name) =>
325
+ name
326
+ .toLowerCase()
327
+ .trim()
328
+ .replace(/\s+/g, '-')
329
+ .replace(/[^a-z0-9._\-\/]/g, '')
330
+ .replace(/\/{2,}/g, '/')
331
+ .replace(/^-+|-+$/g, '')
332
+ .slice(0, 250);
333
+
334
+ const safeBranchName = slugifyBranchName(finalBranchName || '');
335
+
336
+ if (!safeBranchName) {
337
+ console.log(chalk.red('Resulting branch name is empty or invalid. Aborting.'));
338
+ process.exit(1);
339
+ }
340
+
341
+ await execa('git', ['switch', '-c', safeBranchName]);
342
+ console.log(chalk.bgGreen.black(`✔ Switched to new branch '${safeBranchName}'`));
343
+
344
+ } catch (error) {
345
+ spinner.fail();
346
+ if (error.response) {
347
+ console.error(chalk.red(`API Error: ${error.response.status} - ${error.response.data.error || 'No message'}`));
348
+ } else {
349
+ console.error(chalk.red(`An unexpected error occurred: ${error.message}`));
350
+ }
351
+ process.exit(1);
352
+ }
353
+ });
152
354
 
153
- program.parse(process.argv);
355
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,25 +1,26 @@
1
- {
2
- "name": "@abhaydesu/git-pilot",
3
- "version": "1.0.2",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "bin": {
10
- "git-pilot": "./index.js"
11
- },
12
- "type": "module",
13
- "keywords": [],
14
- "author": "",
15
- "license": "ISC",
16
- "dependencies": {
17
- "axios": "^1.12.2",
18
- "chalk": "^5.6.2",
19
- "commander": "^14.0.1",
20
- "dotenv": "^17.2.2",
21
- "execa": "^9.6.0",
22
- "inquirer": "^12.9.6",
23
- "ora": "^9.0.0"
24
- }
25
- }
1
+ {
2
+ "name": "@abhaydesu/git-pilot",
3
+ "version": "1.1.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "bin": {
10
+ "git-pilot": "./index.js"
11
+ },
12
+ "type": "module",
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "preferGlobal": true,
17
+ "dependencies": {
18
+ "axios": "^1.12.2",
19
+ "chalk": "^5.6.2",
20
+ "commander": "^14.0.1",
21
+ "dotenv": "^17.2.2",
22
+ "execa": "^9.6.0",
23
+ "inquirer": "^12.9.6",
24
+ "ora": "^9.0.0"
25
+ }
26
+ }
Binary file