@abhaydesu/git-pilot 1.0.3 → 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/README.md CHANGED
@@ -1,22 +1,28 @@
1
+ <div align="center" display="inline">
2
+ <img width="40" height="40" alt="logo" src="./public/logo-dark-new.png" />
3
+ </div>
4
+
1
5
  # Git Pilot
2
6
 
3
7
  [![NPM Version](https://img.shields.io/npm/v/@abhaydesu/git-pilot)](https://www.npmjs.com/package/@abhaydesu/git-pilot)
4
8
 
5
- An AI-powered assistant that lives in your command line to streamline your Git workflow.
9
+ An AI-powered assistant that lives in your command line to streamline your Git workflow. Never write a commit message or look up a command again.
6
10
 
11
+ ---
7
12
 
8
- ## ◼️ The Problem
13
+ ## The Problem
9
14
  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.
10
15
 
11
16
  **Git Pilot** solves this by acting as your intelligent copilot, translating your natural language intent into the exact commands and messages you need.
12
17
 
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.
16
-
17
- * **Interactive & Safe:** Always asks for your confirmation before executing any command, ensuring you're always in control.
18
+ ## Features
19
+ * **AI-Powered Commit Messages:** Analyzes your staged changes (`git diff`) and generates a perfect commit message following the Conventional Commits specification. Just run `git pilot commit <your_intent>`
20
+ * **Natural Language to Git Command:** Translate plain English requests like "squash the last 3 commits" into the precise Git command. One simple `git pilot run <your_request>`
21
+ * **Magic Undo:** Made a mistake? `git pilot undo` analyzes your recent history and suggests the exact command to reverse your last action.
22
+ * **Intelligent Branching:** Describe your goal, and `git pilot branch` will create a clean, conventional branch name for you.
23
+ * **Interactive & Safe:** Always asks for your confirmation before executing any command. You can also **edit** any AI suggestion to get it just right.
18
24
 
19
- ## ◼️ Installation
25
+ ## Installation
20
26
  Make sure you have Node.js (v18+) and Git installed. Then, run the following command to install Git Pilot globally:
21
27
 
22
28
  ```bash
@@ -25,28 +31,51 @@ npm install -g @abhaydesu/git-pilot
25
31
 
26
32
  ### ◾ Usage
27
33
 
28
- #### **Running a Git Command**
29
- Run the run command with the task you want to perform in quotes.
34
+ #### *Generating a Commit Message*
35
+ Stage your files (git add .), then run git pilot commit. This command analyzes your staged changes and generates a conventional commit message.
36
+
37
+ ##### With an Intent
38
+ Provide a brief description of your changes, and the AI will use it to write the commit message.
30
39
 
31
40
  ```Bash
32
41
 
33
- git pilot run "create a new branch called feature/auth"
42
+ git pilot commit "add new user profile page"
34
43
  ```
35
- The tool will suggest the correct Git command and ask for confirmation before executing it.
36
44
 
45
+ ##### Without an Intent
46
+ If you don't provide an intent, the AI will analyze the diff of your staged files and generate a message automatically.
37
47
 
38
- #### **Generating a Commit Message**
39
- Stage your files (git add .).
48
+ ```Bash
49
+
50
+ git pilot commit
51
+ ```
52
+ After running, you can accept, edit, or abort the suggested commit.
40
53
 
41
- Run the commit command with your intent in quotes.
54
+
55
+ #### *Running a Git Command*
56
+ Run the run command with the task you want to perform. The tool will suggest a command and ask for confirmation.
42
57
 
43
58
  ```Bash
44
59
 
45
- git pilot commit "add new user profile page"
60
+ git pilot run "cherry-pick the last commit from the main branch"
46
61
  ```
47
- The tool will suggest a message and ask for confirmation before committing.
48
62
 
63
+ #### *Creating a New Branch*
64
+ Describe the purpose of your new branch, and let the AI generate a conventional name.
49
65
 
66
+ ```Bash
67
+
68
+ git pilot branch "create a new feature for the auth system"
69
+ ```
70
+
71
+ #### *Undoing a Mistake*
72
+
73
+ If you've made a mistake (like a bad commit or merge), this command will analyze your history and suggest the safest way to undo it.
74
+
75
+ ```Bash
76
+
77
+ git pilot undo
78
+ ```
50
79
 
51
80
  ### ◾ How It Works
52
81
  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.
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.");
@@ -31,60 +33,172 @@ program
31
33
  .command("commit")
32
34
  .description("Generate an AI-powered commit message.")
33
35
  .argument(
34
- "<string>",
35
- 'The user\'s intent for the commit (e.g., "add a new feature")'
36
+ "[intent]",
37
+ 'Optional: 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
 
51
+ const MAX_PAYLOAD_SIZE = 4 * 1024 * 1024;
52
+ const diffSize = Buffer.byteLength(diff, 'utf8');
53
+ let diffToSend = diff;
54
+
55
+ if (diffSize > MAX_PAYLOAD_SIZE) {
56
+
57
+ const { stdout: diffSummary } = await execa("git", ["diff", "--staged", "--name-status"]);
58
+
59
+ diffToSend = "The staged changes are too large to display the full diff. " +
60
+ "Please generate a commit message based on the user's intent and this summary of changed files:\n\n" +
61
+ diffSummary;
62
+ }
63
+
47
64
  try {
48
- spinner.text = 'Generating commit message...';
65
+ spinner.text = "Generating commit message...";
49
66
 
50
- const response = await axios.post(API_COMMIT_URL,
51
- { intent, diff},
67
+ const response = await axios.post(
68
+ API_COMMIT_URL,
69
+ { intent, diff: diffToSend },
52
70
  { timeout: 30000 }
53
- );
71
+ );
54
72
 
55
73
  const suggestedMessage = response.data.message;
56
-
74
+
57
75
  spinner.succeed();
58
76
 
59
77
  console.log(chalk.green("\n--- Suggested Message ---"));
60
78
  console.log(chalk.cyan(suggestedMessage));
61
79
  console.log(chalk.green("-------------------------\n"));
62
80
 
81
+ const { choice } = await inquirer.prompt([
82
+ {
83
+ type: "list",
84
+ name: "choice",
85
+ message: "What would you like to do?",
86
+ choices: [
87
+ { name: "Accept", value: "accept" },
88
+ { name: "Edit", value: "edit" },
89
+ { name: "Abort", value: "abort" },
90
+ ],
91
+ },
92
+ ]);
93
+
94
+ if (choice === 'abort') {
95
+ console.log(chalk.red('Commit aborted by user.'));
96
+ process.exit(0);
97
+ }
98
+
99
+ if (choice == "edit") {
100
+ const { editedMessage } = await inquirer.prompt([
101
+ {
102
+ type: "editor",
103
+ name: "editedMessage",
104
+ message: "Edit the commit message: ",
105
+ default: suggestedMessage,
106
+ validate: (input) =>
107
+ input.length > 0 ? true : "Commit message cannot be empty.",
108
+ },
109
+ ]);
110
+
111
+ await execa("git", ["commit", "-m", editedMessage]);
112
+ console.log(chalk.bgGreen.black("Commit successful!"));
113
+ }
114
+
115
+ if (choice === "accept") {
116
+ await execa("git", ["commit", "-m", suggestedMessage]);
117
+ console.log(chalk.bgGreen.black("Commit successful!"));
118
+ }
119
+
120
+ } catch (error) {
121
+ if (error.response) {
122
+ console.error(
123
+ chalk.red(
124
+ `API Error: ${error.response.status} - ${error.response.data.error}`
125
+ )
126
+ );
127
+ } else {
128
+ console.error(
129
+ chalk.red("An unexpected error occurred: ", error.message)
130
+ );
131
+ }
132
+
133
+ process.exit(1);
134
+ }
135
+ });
136
+
137
+ program
138
+ .command("run")
139
+ .description("Translate natural language into a Git command.")
140
+ .argument(
141
+ "<string>",
142
+ 'The task you want to perform (e.g. "squash the last 3 commits")'
143
+ )
144
+ .action(async (request) => {
145
+ try {
146
+ const spinner = ora("Fetching the right command...").start();
147
+
148
+ const response = await axios.post(
149
+ API_COMMAND_URL,
150
+ { request },
151
+ { timeout: 30000 }
152
+ );
153
+ const suggestedCommand = response.data.command;
154
+
155
+ if (suggestedCommand.startsWith("Error:")) {
156
+ spinner.fail(suggestedCommand);
157
+ process.exit(1);
158
+ }
159
+
160
+ spinner.succeed();
161
+
162
+ console.log(
163
+ chalk.yellow.bold(
164
+ "\nWARNING: The suggested command will be executed in your shell."
165
+ )
166
+ );
167
+ console.log(
168
+ chalk.yellow("Always review commands carefully before confirming.\n")
169
+ );
170
+
171
+ console.log(chalk.green("\n------ Suggested Command ------"));
172
+ console.log(chalk.cyan(suggestedCommand));
173
+ console.log(chalk.green("-------------------------------\n"));
174
+
63
175
  const { confirm } = await inquirer.prompt([
64
176
  {
65
177
  type: "confirm",
66
178
  name: "confirm",
67
- message: "Commit with this message?",
68
- default: true,
179
+ message: "Execute this command?",
180
+ default: false,
69
181
  },
70
182
  ]);
71
183
 
72
184
  if (confirm) {
73
- await execa("git", ["commit", "-m", suggestedMessage]);
74
- console.log(chalk.bgGreen.black("Commit successful!"));
185
+ await execaCommand(suggestedCommand, { stdio: "inherit" });
186
+ console.log(chalk.bgGreen.black("Command executed successfully!"));
75
187
  } else {
76
- console.log(chalk.red("Commit aborted by user."));
188
+ console.log(chalk.red("Execution aborted by user."));
77
189
  }
78
190
  } catch (error) {
79
- if (error.response) {
80
- console.error(
81
- chalk.red(
82
- `API Error: ${error.response.status} - ${error.response.data.error}`
83
- )
84
- );
191
+ if (error.response) {
192
+ console.error(
193
+ chalk.red(
194
+ `API Error: ${error.response.status} - ${
195
+ error.response.data.error || "No message"
196
+ }`
197
+ )
198
+ );
85
199
  } else {
86
200
  console.error(
87
- chalk.red("An unexpected error occured: ", error.message)
201
+ chalk.red(`An unexpected error occurred: ${error.message}`)
88
202
  );
89
203
  }
90
204
 
@@ -92,62 +206,163 @@ program
92
206
  }
93
207
  });
94
208
 
95
-
96
209
  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) => {
210
+ .command("undo")
211
+ .description("Suggests a command to undo your last major Git action")
212
+ .action(async () => {
213
+ const spinner = ora("Analyzing your recent Git history...").start();
214
+
101
215
  try {
102
- const spinner = ora('Fetching the right command...').start();
216
+ const { stdout: reflog } = await execa("git", ["reflog", "-n", "15"]);
103
217
 
104
- const response = await axios.post(
105
- API_COMMAND_URL,
106
- { request },
107
- { timeout: 30000 }
108
- );
109
- const suggestedCommand = response.data.command;
218
+ spinner.text = "Suggesting an undo command...";
110
219
 
111
- if (suggestedCommand.startsWith('Error:')) {
112
- spinner.fail(suggestedCommand);
113
- process.exit(1);
114
- }
220
+ const response = await axios.post(API_UNDO_URL, { reflog });
221
+ const { command, explanation } = response.data;
115
222
 
116
- spinner.succeed();
223
+ if (!command) {
224
+ spinner.warn("Couldn't determine a safe action to undo.");
225
+ process.exit(0);
226
+ }
117
227
 
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'));
228
+ spinner.succeed();
120
229
 
121
- console.log(chalk.green('\n------ Suggested Command ------'));
122
- console.log(chalk.cyan(suggestedCommand));
123
- console.log(chalk.green('-------------------------------\n'));
230
+ console.log(chalk.green("\n--- Suggested Undo Command ---"));
231
+ console.log(chalk.cyan(command));
232
+ console.log(chalk.green("------------------------------\n"));
233
+ console.log(chalk.yellow(explanation));
124
234
 
125
- const { confirm } = await inquirer.prompt([
126
- {
127
- type: 'confirm',
128
- name: 'confirm',
129
- message: 'Execute this command?',
130
- default: false,
131
- },
132
- ]);
235
+ const { confirm } = await inquirer.prompt([
236
+ {
237
+ type: "confirm",
238
+ name: "confirm",
239
+ message: "Execute this undo command?",
240
+ default: false,
241
+ },
242
+ ]);
243
+
244
+ if (confirm) {
245
+ if (command.startsWith('git rebase --abort')) {
246
+ try {
247
+ const { stdout: gitDirRaw } = await execa('git', ['rev-parse', '--git-dir']);
248
+ const gitDir = gitDirRaw.trim();
249
+
250
+ const fs = await import('fs/promises');
133
251
 
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.'));
252
+ const rebaseApply = `${gitDir}/rebase-apply`;
253
+ const rebaseMerge = `${gitDir}/rebase-merge`;
254
+
255
+ const hasRebaseApply = await fs.access(rebaseApply).then(() => true).catch(() => false);
256
+ const hasRebaseMerge = await fs.access(rebaseMerge).then(() => true).catch(() => false);
257
+
258
+ if (!hasRebaseApply && !hasRebaseMerge) {
259
+ console.log(chalk.yellow('No rebase in progress — abort not necessary.'));
260
+ process.exit(0);
261
+ }
262
+ } catch (e) {
263
+ }
139
264
  }
140
265
 
266
+ await execaCommand(command, { stdio: "inherit" });
267
+ console.log(chalk.bgGreen.black("Action successfully undone!"));
268
+ } else {
269
+ console.log(chalk.red("Execution aborted by user."));
270
+ }
141
271
  } 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
- }
272
+ spinner.fail();
148
273
 
149
- process.exit(1);
150
- }
151
- })
274
+ if (error.response) {
275
+ console.error(chalk.red(`API Error: ${error.response.status} - ${error.response.data.error || 'No message'}`));
276
+ } else {
277
+ console.error(chalk.red(`An unexpected error occurred: ${error.message}`));
278
+ }
279
+
280
+ process.exit(1);
281
+ }
282
+ });
283
+
284
+ program
285
+ .command('branch')
286
+ .description('Generates a conventional branch name from a description and creates the branch.')
287
+ .argument('<string>', 'A description of the branch\'s purpose (e.g., "fix login bug")')
288
+ .action(async (description) => {
289
+ const spinner = ora('Generating a conventional branch name...').start();
290
+ try {
291
+ const response = await axios.post(API_BRANCH_URL, { description });
292
+ const { branchName } = response.data;
293
+
294
+ spinner.succeed();
295
+
296
+ console.log(chalk.green('\n--- Suggested Branch Name ---'));
297
+ console.log(chalk.cyan(branchName));
298
+ console.log(chalk.green('-----------------------------\n'));
299
+
300
+ const { choice } = await inquirer.prompt([
301
+ {
302
+ type: 'list',
303
+ name: 'choice',
304
+ message: 'What would you like to do?',
305
+ choices: [
306
+ { name: 'Accept', value: 'accept' },
307
+ { name: 'Edit', value: 'edit' },
308
+ { name: 'Abort', value: 'abort' },
309
+ ],
310
+ },
311
+ ]);
312
+
313
+ if (choice === 'abort') {
314
+ console.log(chalk.red('Branch creation aborted by user.'));
315
+ process.exit(0);
316
+ }
317
+
318
+ let finalBranchName = branchName;
319
+
320
+ if (choice === 'edit') {
321
+ const editResp = await inquirer.prompt({
322
+ type: 'editor',
323
+ name: 'editedBranchName',
324
+ message: 'Edit the branch name:',
325
+ default: branchName,
326
+ validate: value => (value && value.trim().length > 0) ? true : 'Branch name cannot be empty'
327
+ }, {
328
+ onCancel: () => {
329
+ console.log(chalk.red('\nEdit cancelled.'));
330
+ process.exit(0);
331
+ }
332
+ });
333
+
334
+ finalBranchName = editResp.editedBranchName;
335
+ }
336
+
337
+ const slugifyBranchName = (name) =>
338
+ name
339
+ .toLowerCase()
340
+ .trim()
341
+ .replace(/\s+/g, '-')
342
+ .replace(/[^a-z0-9._\-\/]/g, '')
343
+ .replace(/\/{2,}/g, '/')
344
+ .replace(/^-+|-+$/g, '')
345
+ .slice(0, 250);
346
+
347
+ const safeBranchName = slugifyBranchName(finalBranchName || '');
348
+
349
+ if (!safeBranchName) {
350
+ console.log(chalk.red('Resulting branch name is empty or invalid. Aborting.'));
351
+ process.exit(1);
352
+ }
353
+
354
+ await execa('git', ['switch', '-c', safeBranchName]);
355
+ console.log(chalk.bgGreen.black(`✔ Switched to new branch '${safeBranchName}'`));
356
+
357
+ } catch (error) {
358
+ spinner.fail();
359
+ if (error.response) {
360
+ console.error(chalk.red(`API Error: ${error.response.status} - ${error.response.data.error || 'No message'}`));
361
+ } else {
362
+ console.error(chalk.red(`An unexpected error occurred: ${error.message}`));
363
+ }
364
+ process.exit(1);
365
+ }
366
+ });
152
367
 
153
- program.parse(process.argv);
368
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,26 +1,26 @@
1
- {
2
- "name": "@abhaydesu/git-pilot",
3
- "version": "1.0.3",
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
- }
1
+ {
2
+ "name": "@abhaydesu/git-pilot",
3
+ "version": "1.2.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