gitpal-cli 1.0.4 ā 1.0.6
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 +65 -3
- package/demo.cast +121 -0
- package/demo.gif +0 -0
- package/package.json +1 -1
- package/src/ai.js +26 -49
- package/src/commands/config.js +1 -0
- package/src/commands/issue.js +291 -0
- package/src/index.js +8 -0
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
git add .
|
|
13
|
+
|
|
12
14
|
## š¤ The Problem Every Developer Faces
|
|
13
15
|
|
|
14
16
|
```bash
|
|
@@ -157,7 +159,7 @@ gitpal changelog --ver 2.0.0
|
|
|
157
159
|
|
|
158
160
|
# š CHANGELOG v2.0.0:
|
|
159
161
|
#
|
|
160
|
-
# ## [2.0.0] - 2026-03-
|
|
162
|
+
# ## [2.0.0] - 2026-03-22
|
|
161
163
|
#
|
|
162
164
|
# ### Features
|
|
163
165
|
# - Payment gateway integration
|
|
@@ -223,6 +225,61 @@ gitpal review --review-only # Only review, skip commit step
|
|
|
223
225
|
|
|
224
226
|
---
|
|
225
227
|
|
|
228
|
+
### `gitpal explain` ā Explain Any Code
|
|
229
|
+
Explains any file, function or commit in plain English ā perfect for understanding old code or teammate's changes.
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Explain an entire file
|
|
233
|
+
gitpal explain src/auth.js
|
|
234
|
+
|
|
235
|
+
# š Explaining file: auth.js
|
|
236
|
+
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
237
|
+
# This file handles all authentication logic.
|
|
238
|
+
# It has 4 main functions:
|
|
239
|
+
# - login() ā verifies user credentials
|
|
240
|
+
# - register() ā creates new user account
|
|
241
|
+
# - verifyToken() ā checks if JWT is valid
|
|
242
|
+
# - logout() ā clears user session
|
|
243
|
+
#
|
|
244
|
+
# Depends on: bcrypt, jsonwebtoken, User model
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Explain a specific function
|
|
249
|
+
gitpal explain src/payment.js --function processPayment
|
|
250
|
+
|
|
251
|
+
# š Explaining function: processPayment()
|
|
252
|
+
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
253
|
+
# This function handles Razorpay payment processing.
|
|
254
|
+
# Step 1 ā Creates payment order with amount
|
|
255
|
+
# Step 2 ā Sends to Razorpay API
|
|
256
|
+
# Step 3 ā Waits for webhook confirmation
|
|
257
|
+
# Step 4 ā Updates database on success
|
|
258
|
+
#
|
|
259
|
+
# Depends on: razorpay, axios, Order model
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Explain any commit
|
|
264
|
+
gitpal explain a3f2c1
|
|
265
|
+
|
|
266
|
+
# š Explaining commit: a3f2c1
|
|
267
|
+
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
268
|
+
# This commit added JWT authentication.
|
|
269
|
+
# - Created login function with bcrypt password check
|
|
270
|
+
# - Added JWT token generation on success
|
|
271
|
+
# - Protected private routes with middleware
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Options:**
|
|
275
|
+
```bash
|
|
276
|
+
gitpal explain src/auth.js # Explain full file
|
|
277
|
+
gitpal explain src/auth.js --function login # Explain one function
|
|
278
|
+
gitpal explain a3f2c1 # Explain a commit
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
226
283
|
## š Full Daily Workflow
|
|
227
284
|
|
|
228
285
|
```
|
|
@@ -248,6 +305,9 @@ gitpal pr ā Full PR description, copy to GitHub
|
|
|
248
305
|
ā
|
|
249
306
|
Releasing v2.0?
|
|
250
307
|
gitpal changelog --ver 2.0.0 ā Full changelog ready
|
|
308
|
+
ā
|
|
309
|
+
Understanding old code?
|
|
310
|
+
gitpal explain src/auth.js ā Plain English explanation
|
|
251
311
|
```
|
|
252
312
|
|
|
253
313
|
---
|
|
@@ -261,6 +321,7 @@ gitpal changelog --ver 2.0.0 ā Full changelog ready
|
|
|
261
321
|
| Forget what you built last week | Plain English summary instantly |
|
|
262
322
|
| Write changelog manually | Auto-generated from commits |
|
|
263
323
|
| No code review before commit | AI catches bugs before they reach GitHub |
|
|
324
|
+
| Confused by old code | Explained in plain English instantly |
|
|
264
325
|
| Works with one AI only | Works with 4 AI providers |
|
|
265
326
|
|
|
266
327
|
---
|
|
@@ -281,7 +342,8 @@ gitpal/
|
|
|
281
342
|
ā āāā pr.js ā gitpal pr
|
|
282
343
|
ā āāā changelog.js ā gitpal changelog
|
|
283
344
|
ā āāā config.js ā gitpal config
|
|
284
|
-
ā
|
|
345
|
+
ā āāā review.js ā gitpal review
|
|
346
|
+
ā āāā explain.js ā gitpal explain
|
|
285
347
|
āāā tests/
|
|
286
348
|
āāā ai.test.js
|
|
287
349
|
```
|
|
@@ -339,4 +401,4 @@ MIT ā free to use, modify and distribute.
|
|
|
339
401
|
|
|
340
402
|
<p align="center">
|
|
341
403
|
<strong>If GitPal saves you time, give it a ā on GitHub!</strong>
|
|
342
|
-
</p>
|
|
404
|
+
</p>
|
package/demo.cast
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{"version":3,"term":{"cols":157,"rows":45,"type":"xterm-256color","version":"xterm.js(6.1.0-beta.191)","theme":{"fg":"#c9d1d9","bg":"#010409","palette":"#484f58:#ec8e2c:#58a6ff:#d29922:#58a6ff:#bc8cff:#39c5cf:#b1bac4:#6e7681:#fdac54:#79c0ff:#e3b341:#79c0ff:#d2a8ff:#56d4dd:#ffffff"}},"timestamp":1774179869,"env":{"SHELL":"/bin/zsh"}}
|
|
2
|
+
[4.712, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
3
|
+
[0.001, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air gitpal % \u001b[K\u001b[?2004h"]
|
|
4
|
+
[24.619, "o", "\u001b[7mgitpal --help\u001b[27m"]
|
|
5
|
+
[0.320, "o", "\u001b[13D\u001b[27mg\u001b[27mi\u001b[27mt\u001b[27mp\u001b[27ma\u001b[27ml\u001b[27m \u001b[27m-\u001b[27m-\u001b[27mh\u001b[27me\u001b[27ml\u001b[27mp\u001b[?2004l\r\r\n"]
|
|
6
|
+
[0.365, "o", "\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1mš¤ GitPal ā Your AI Git Assistant\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n"]
|
|
7
|
+
[0.002, "o", "Usage: gitpal [options] [command]\r\n\r\nAI-powered Git CLI ā commit messages, PR descriptions, summaries & changelogs\r\n\r\nOptions:\r\n -V, --version output the version number\r\n -h, --help display help for command\r\n\r\nCommands:\r\n commit [options] Auto-generate a commit message from your staged changes\r\n summary [options] Summarize recent commits in plain English\r\n pr [options] Generate a pull request description from branch diff\r\n changelog [options] Generate a changelog from commit history\r\n config Configure your AI provider and API key\r\n review [options] AI reviews your code for bugs and issues before committing\r\n explain [options] <target> Explain any file or commit in plain English\r\n issue [options] <number> Fetch and fix any GitHub issue with AI guidance\r\n help [command] display help for command\r\n"]
|
|
8
|
+
[0.002, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
9
|
+
[0.001, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air gitpal % \u001b[K\u001b[?2004h"]
|
|
10
|
+
[15.276, "o", "\u001b[7mcd ~/todo-app\u001b[27m"]
|
|
11
|
+
[0.847, "o", "\u001b[13D\u001b[27mc\u001b[27md\u001b[27m \u001b[27m~\u001b[27m/\u001b[27mt\u001b[27mo\u001b[27md\u001b[27mo\u001b[27m-\u001b[27ma\u001b[27mp\u001b[27m \b"]
|
|
12
|
+
[0.137, "o", "\b \b"]
|
|
13
|
+
[0.147, "o", "\b \b"]
|
|
14
|
+
[0.113, "o", "\b \b"]
|
|
15
|
+
[0.504, "o", "\b \b"]
|
|
16
|
+
[0.085, "o", "\b \b"]
|
|
17
|
+
[0.082, "o", "\b \b"]
|
|
18
|
+
[0.084, "o", "\b \b"]
|
|
19
|
+
[0.086, "o", "\b \b"]
|
|
20
|
+
[0.083, "o", "\b \b"]
|
|
21
|
+
[0.083, "o", "\b"]
|
|
22
|
+
[0.084, "o", "\b\bc \b"]
|
|
23
|
+
[0.083, "o", "\b \b"]
|
|
24
|
+
[0.464, "o", "c"]
|
|
25
|
+
[0.176, "o", "\bcd"]
|
|
26
|
+
[0.480, "o", " "]
|
|
27
|
+
[0.293, "o", "."]
|
|
28
|
+
[0.141, "o", "."]
|
|
29
|
+
[0.260, "o", "\u001b[?2004l"]
|
|
30
|
+
[0.000, "o", "\r\r\n"]
|
|
31
|
+
[0.001, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
32
|
+
[0.000, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air projects % \u001b[K\u001b[?2004h"]
|
|
33
|
+
[1.538, "o", "\u001b[7mcd ~/todo-app\u001b[27m"]
|
|
34
|
+
[0.858, "o", "\u001b[13D\u001b[27mc\u001b[27md\u001b[27m \u001b[27m~\u001b[27m/\u001b[27mt\u001b[27mo\u001b[27md\u001b[27mo\u001b[27m-\u001b[27ma\u001b[27mp\u001b[27mp\u001b[?2004l"]
|
|
35
|
+
[0.000, "o", "\r\r\n"]
|
|
36
|
+
[0.001, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
37
|
+
[0.000, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air todo-app % \u001b[K\u001b[?2004h"]
|
|
38
|
+
[5.946, "o", "\u001b[7mgit add .\u001b[27m"]
|
|
39
|
+
[0.511, "o", "\u001b[9D\u001b[27mg\u001b[27mi\u001b[27mt\u001b[27m \u001b[27ma\u001b[27md\u001b[27md\u001b[27m \u001b[27m.\u001b[?2004l\r\r\n"]
|
|
40
|
+
[0.108, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
41
|
+
[0.000, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air todo-app % \u001b[K\u001b[?2004h"]
|
|
42
|
+
[4.918, "o", "\u001b[7mgitpal commit\u001b[27m"]
|
|
43
|
+
[0.238, "o", "\u001b[13D\u001b[27mg\u001b[27mi\u001b[27mt\u001b[27mp\u001b[27ma\u001b[27ml\u001b[27m \u001b[27mc\u001b[27mo\u001b[27mm\u001b[27mm\u001b[27mi\u001b[27mt\u001b[?2004l"]
|
|
44
|
+
[0.000, "o", "\r\r\n"]
|
|
45
|
+
[0.353, "o", "\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1mš¤ GitPal ā Your AI Git Assistant\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n"]
|
|
46
|
+
[0.101, "o", "\u001b[?25l"]
|
|
47
|
+
[0.001, "o", "\u001b[1G"]
|
|
48
|
+
[0.000, "o", "\u001b[1G"]
|
|
49
|
+
[0.000, "o", "\u001b[36mā \u001b[39m Reading your staged changes..."]
|
|
50
|
+
[0.072, "o", "\u001b[1G\u001b[0K\u001b[?25h"]
|
|
51
|
+
[0.001, "o", "\u001b[32mā\u001b[39m Staged changes found.\r\n"]
|
|
52
|
+
[0.000, "o", "\u001b[?25l"]
|
|
53
|
+
[0.000, "o", "\u001b[1G\u001b[1G\u001b[36mā \u001b[39m Generating commit message with AI..."]
|
|
54
|
+
[0.082, "o", "\u001b[1G\u001b[0K\u001b[36mā \u001b[39m Generating commit message with AI..."]
|
|
55
|
+
[0.081, "o", "\u001b[1G\u001b[0K\u001b[36mā ¹\u001b[39m Generating commit message with AI..."]
|
|
56
|
+
[0.081, "o", "\u001b[1G\u001b[0K\u001b[36mā ø\u001b[39m Generating commit message with AI..."]
|
|
57
|
+
[0.076, "o", "\u001b[1G\u001b[0K"]
|
|
58
|
+
[0.000, "o", "\u001b[?25h\u001b[32mā\u001b[39m \u001b[32mCommit message generated!\u001b[39m\r\n"]
|
|
59
|
+
[0.000, "o", "\r\n\u001b[1mSuggested commit message:\u001b[22m\r\n"]
|
|
60
|
+
[0.000, "o", "\u001b[36m feat(auth): add login function\u001b[39m\r\n\u001b[36m\u001b[39m\r\n"]
|
|
61
|
+
[0.012, "o", "\u001b[32m?\u001b[39m \u001b[1mWhat would you like to do?\u001b[22m\u001b[0m \u001b[0m\u001b[2m(Use arrow keys)\u001b[22m\r\n\u001b[36m⯠ā
Use this message and commit\u001b[39m \r\n āļø Edit the message \r\n ā Cancel \u001b[?25l"]
|
|
62
|
+
[0.000, "o", "\u001b[12D"]
|
|
63
|
+
[0.000, "o", "\u001b[12C"]
|
|
64
|
+
[0.974, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[G"]
|
|
65
|
+
[0.000, "o", "\u001b[32m?\u001b[39m \u001b[1mWhat would you like to do?\u001b[22m\u001b[0m \u001b[0m\u001b[36mā
Use this message and commit\u001b[39m\u001b[?25l"]
|
|
66
|
+
[0.000, "o", "\u001b[59D\u001b[59C"]
|
|
67
|
+
[0.000, "o", "\r\n\u001b[?25h"]
|
|
68
|
+
[0.137, "o", "\u001b[32m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[32m\u001b[1mā
Committed successfully!\u001b[22m\u001b[39m\r\n"]
|
|
69
|
+
[0.051, "o", "\u001b[?25h"]
|
|
70
|
+
[0.005, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
71
|
+
[0.000, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air todo-app % \u001b[K"]
|
|
72
|
+
[0.000, "o", "\u001b[?2004h"]
|
|
73
|
+
[4.589, "o", "\u001b[7mgitpal summary\u001b[27m"]
|
|
74
|
+
[0.226, "o", "\u001b[14D\u001b[27mg\u001b[27mi\u001b[27mt\u001b[27mp\u001b[27ma\u001b[27ml\u001b[27m \u001b[27ms\u001b[27mu\u001b[27mm\u001b[27mm\u001b[27ma\u001b[27mr\u001b[27my\u001b[?2004l\r\r\n"]
|
|
75
|
+
[0.214, "o", "\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1mš¤ GitPal ā Your AI Git Assistant\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n"]
|
|
76
|
+
[0.094, "o", "\u001b[?25l"]
|
|
77
|
+
[0.000, "o", "\u001b[1G\u001b[1G"]
|
|
78
|
+
[0.000, "o", "\u001b[36mā \u001b[39m Fetching last 5 commits..."]
|
|
79
|
+
[0.075, "o", "\u001b[1G"]
|
|
80
|
+
[0.000, "o", "\u001b[0K"]
|
|
81
|
+
[0.000, "o", "\u001b[?25h"]
|
|
82
|
+
[0.000, "o", "\u001b[32mā\u001b[39m Found 5 commits.\r\n"]
|
|
83
|
+
[0.000, "o", "\u001b[?25l"]
|
|
84
|
+
[0.000, "o", "\u001b[1G\u001b[1G\u001b[36mā \u001b[39m Generating summary with AI..."]
|
|
85
|
+
[0.081, "o", "\u001b[1G\u001b[0K\u001b[36mā \u001b[39m Generating summary with AI..."]
|
|
86
|
+
[0.082, "o", "\u001b[1G"]
|
|
87
|
+
[0.000, "o", "\u001b[0K\u001b[36mā ¹\u001b[39m Generating summary with AI..."]
|
|
88
|
+
[0.080, "o", "\u001b[1G\u001b[0K\u001b[36mā ø\u001b[39m Generating summary with AI..."]
|
|
89
|
+
[0.082, "o", "\u001b[1G"]
|
|
90
|
+
[0.000, "o", "\u001b[0K"]
|
|
91
|
+
[0.000, "o", "\u001b[36mā ¼\u001b[39m Generating summary with AI..."]
|
|
92
|
+
[0.080, "o", "\u001b[1G\u001b[0K"]
|
|
93
|
+
[0.001, "o", "\u001b[36mā “\u001b[39m Generating summary with AI..."]
|
|
94
|
+
[0.081, "o", "\u001b[1G\u001b[0K\u001b[36mā ¦\u001b[39m Generating summary with AI..."]
|
|
95
|
+
[0.081, "o", "\u001b[1G\u001b[0K"]
|
|
96
|
+
[0.000, "o", "\u001b[36mā §\u001b[39m Generating summary with AI..."]
|
|
97
|
+
[0.045, "o", "\u001b[1G\u001b[0K\u001b[?25h"]
|
|
98
|
+
[0.000, "o", "\u001b[32mā\u001b[39m Summary ready!\r\n\r\n"]
|
|
99
|
+
[0.000, "o", "\u001b[1mš Summary of last 5 commits:\u001b[22m\r\n\u001b[1m\u001b[22m\r\n"]
|
|
100
|
+
[0.000, "o", "\u001b[37mHere's a summary of the git commits:\u001b[39m\r\n\u001b[37m\u001b[39m\r\n\u001b[37m* **Authentication and Todo Management**:\u001b[39m\r\n\u001b[37m + Added login function\u001b[39m\r\n\u001b[37m + Added todo management functions (including deletion)\u001b[39m\r\n\u001b[37m* **Todo Features**:\u001b[39m\r\n\u001b[37m + Added a todo counter\u001b[39m\r\n\u001b[37m + Added a search function for todos\u001b[39m\r\n\u001b[37mThese changes collectively improved the application's functionality, with a focus on user authentication and todo list management.\u001b[39m\r\n\r\n"]
|
|
101
|
+
[0.070, "o", "\u001b[?25h"]
|
|
102
|
+
[0.003, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
103
|
+
[0.000, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air todo-app % \u001b[K\u001b[?2004h"]
|
|
104
|
+
[4.575, "o", "\u001b[7mgitpal review\u001b[27m"]
|
|
105
|
+
[0.274, "o", "\u001b[13D\u001b[27mg\u001b[27mi\u001b[27mt\u001b[27mp\u001b[27ma\u001b[27ml\u001b[27m \u001b[27mr\u001b[27me\u001b[27mv\u001b[27mi\u001b[27me\u001b[27mw\u001b[?2004l\r\r\n"]
|
|
106
|
+
[0.204, "o", "\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1mš¤ GitPal ā Your AI Git Assistant\u001b[22m\u001b[39m\r\n\u001b[36m\u001b[1m\u001b[22m\u001b[39m\r\n"]
|
|
107
|
+
[0.089, "o", "\u001b[?25l"]
|
|
108
|
+
[0.000, "o", "\u001b[1G\u001b[1G"]
|
|
109
|
+
[0.000, "o", "\u001b[36mā \u001b[39m Reading your staged changes..."]
|
|
110
|
+
[0.080, "o", "\u001b[1G"]
|
|
111
|
+
[0.000, "o", "\u001b[0K"]
|
|
112
|
+
[0.000, "o", "\u001b[36mā \u001b[39m Reading your staged changes..."]
|
|
113
|
+
[0.041, "o", "\u001b[1G"]
|
|
114
|
+
[0.000, "o", "\u001b[0K"]
|
|
115
|
+
[0.000, "o", "\u001b[?25h"]
|
|
116
|
+
[0.000, "o", "\u001b[31mā\u001b[39m \u001b[33mNo staged changes found. Run: git add <files>\u001b[39m\r\n"]
|
|
117
|
+
[0.000, "o", "\u001b[?25h"]
|
|
118
|
+
[0.003, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
|
119
|
+
[0.000, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jharshitgangwar@harshits-MacBook-Air todo-app % \u001b[K\u001b[?2004h"]
|
|
120
|
+
[6.863, "o", "\u001b[?2004l\r\r\n"]
|
|
121
|
+
[0.018, "x", "1"]
|
package/demo.gif
ADDED
|
Binary file
|
package/package.json
CHANGED
package/src/ai.js
CHANGED
|
@@ -2,7 +2,6 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
|
|
5
|
-
// Config file stored in user's home directory
|
|
6
5
|
const CONFIG_PATH = path.join(os.homedir(), '.gitpal.json');
|
|
7
6
|
|
|
8
7
|
export function loadConfig() {
|
|
@@ -14,23 +13,11 @@ export function saveConfig(config) {
|
|
|
14
13
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
// āāā AI PROVIDER ROUTER āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
18
|
-
// All providers receive the same prompt and return a plain string response.
|
|
19
|
-
// Adding a new provider = add one function + one case below.
|
|
20
|
-
|
|
21
16
|
async function callAnthropic(prompt, apiKey) {
|
|
22
17
|
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
23
18
|
method: 'POST',
|
|
24
|
-
headers: {
|
|
25
|
-
|
|
26
|
-
'x-api-key': apiKey,
|
|
27
|
-
'anthropic-version': '2023-06-01',
|
|
28
|
-
},
|
|
29
|
-
body: JSON.stringify({
|
|
30
|
-
model: 'claude-3-haiku-20240307',
|
|
31
|
-
max_tokens: 500,
|
|
32
|
-
messages: [{ role: 'user', content: prompt }],
|
|
33
|
-
}),
|
|
19
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
|
|
20
|
+
body: JSON.stringify({ model: 'claude-3-haiku-20240307', max_tokens: 500, messages: [{ role: 'user', content: prompt }] }),
|
|
34
21
|
});
|
|
35
22
|
const data = await res.json();
|
|
36
23
|
if (!res.ok) throw new Error(data.error?.message || 'Anthropic API error');
|
|
@@ -40,15 +27,8 @@ async function callAnthropic(prompt, apiKey) {
|
|
|
40
27
|
async function callOpenAI(prompt, apiKey) {
|
|
41
28
|
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
42
29
|
method: 'POST',
|
|
43
|
-
headers: {
|
|
44
|
-
|
|
45
|
-
Authorization: `Bearer ${apiKey}`,
|
|
46
|
-
},
|
|
47
|
-
body: JSON.stringify({
|
|
48
|
-
model: 'gpt-3.5-turbo',
|
|
49
|
-
max_tokens: 500,
|
|
50
|
-
messages: [{ role: 'user', content: prompt }],
|
|
51
|
-
}),
|
|
30
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
31
|
+
body: JSON.stringify({ model: 'gpt-3.5-turbo', max_tokens: 500, messages: [{ role: 'user', content: prompt }] }),
|
|
52
32
|
});
|
|
53
33
|
const data = await res.json();
|
|
54
34
|
if (!res.ok) throw new Error(data.error?.message || 'OpenAI API error');
|
|
@@ -60,9 +40,7 @@ async function callGemini(prompt, apiKey) {
|
|
|
60
40
|
const res = await fetch(url, {
|
|
61
41
|
method: 'POST',
|
|
62
42
|
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify({
|
|
64
|
-
contents: [{ parts: [{ text: prompt }] }],
|
|
65
|
-
}),
|
|
43
|
+
body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
|
|
66
44
|
});
|
|
67
45
|
const data = await res.json();
|
|
68
46
|
if (!res.ok) throw new Error(data.error?.message || 'Gemini API error');
|
|
@@ -72,39 +50,38 @@ async function callGemini(prompt, apiKey) {
|
|
|
72
50
|
async function callGroq(prompt, apiKey) {
|
|
73
51
|
const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
|
74
52
|
method: 'POST',
|
|
75
|
-
headers: {
|
|
76
|
-
|
|
77
|
-
Authorization: `Bearer ${apiKey}`,
|
|
78
|
-
},
|
|
79
|
-
body: JSON.stringify({
|
|
80
|
-
model: 'llama-3.3-70b-versatile',
|
|
81
|
-
max_tokens: 500,
|
|
82
|
-
messages: [{ role: 'user', content: prompt }],
|
|
83
|
-
}),
|
|
53
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
54
|
+
body: JSON.stringify({ model: 'llama-3.3-70b-versatile', max_tokens: 500, messages: [{ role: 'user', content: prompt }] }),
|
|
84
55
|
});
|
|
85
56
|
const data = await res.json();
|
|
86
57
|
if (!res.ok) throw new Error(data.error?.message || 'Groq API error');
|
|
87
58
|
return data.choices[0].message.content.trim();
|
|
88
59
|
}
|
|
89
60
|
|
|
90
|
-
|
|
61
|
+
async function callOpenRouter(prompt, apiKey, model) {
|
|
62
|
+
const selectedModel = model || 'google/gemini-2.0-flash-exp:free';
|
|
63
|
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'HTTP-Referer': 'https://github.com/h1a2r3s4h/gitpal', 'X-Title': 'GitPal CLI' },
|
|
66
|
+
body: JSON.stringify({ model: selectedModel, max_tokens: 500, messages: [{ role: 'user', content: prompt }] }),
|
|
67
|
+
});
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
if (!res.ok) throw new Error(data.error?.message || 'OpenRouter API error');
|
|
70
|
+
return data.choices[0].message.content.trim();
|
|
71
|
+
}
|
|
91
72
|
|
|
92
73
|
export async function askAI(prompt) {
|
|
93
74
|
const config = loadConfig();
|
|
94
|
-
|
|
95
75
|
const provider = config.provider;
|
|
96
76
|
const apiKey = config.apiKey;
|
|
97
|
-
|
|
98
|
-
if (!provider || !apiKey)
|
|
99
|
-
throw new Error('No AI provider configured. Run: gitpal config');
|
|
100
|
-
}
|
|
101
|
-
|
|
77
|
+
const model = config.model;
|
|
78
|
+
if (!provider || !apiKey) throw new Error('No AI provider configured. Run: gitpal config');
|
|
102
79
|
switch (provider) {
|
|
103
|
-
case 'anthropic':
|
|
104
|
-
case 'openai':
|
|
105
|
-
case 'gemini':
|
|
106
|
-
case 'groq':
|
|
107
|
-
|
|
108
|
-
|
|
80
|
+
case 'anthropic': return callAnthropic(prompt, apiKey);
|
|
81
|
+
case 'openai': return callOpenAI(prompt, apiKey);
|
|
82
|
+
case 'gemini': return callGemini(prompt, apiKey);
|
|
83
|
+
case 'groq': return callGroq(prompt, apiKey);
|
|
84
|
+
case 'openrouter': return callOpenRouter(prompt, apiKey, model);
|
|
85
|
+
default: throw new Error(`Unknown provider "${provider}". Run: gitpal config`);
|
|
109
86
|
}
|
|
110
87
|
}
|
package/src/commands/config.js
CHANGED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import simpleGit from 'simple-git';
|
|
7
|
+
import { isGitRepo, getCurrentBranch } from '../git.js';
|
|
8
|
+
import { askAI, loadConfig, saveConfig } from '../ai.js';
|
|
9
|
+
|
|
10
|
+
const git = simpleGit();
|
|
11
|
+
|
|
12
|
+
// āāā GITHUB API āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
13
|
+
|
|
14
|
+
async function fetchIssue(repo, issueNumber, token) {
|
|
15
|
+
const url = `https://api.github.com/repos/${repo}/issues/${issueNumber}`;
|
|
16
|
+
const headers = {
|
|
17
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
18
|
+
'User-Agent': 'gitpal-cli',
|
|
19
|
+
};
|
|
20
|
+
if (token) headers['Authorization'] = `token ${token}`;
|
|
21
|
+
|
|
22
|
+
const res = await fetch(url, { headers });
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
if (res.status === 404) throw new Error(`Issue #${issueNumber} not found in ${repo}`);
|
|
25
|
+
if (res.status === 401) throw new Error('Invalid GitHub token. Run: gitpal config --github-token YOUR_TOKEN');
|
|
26
|
+
throw new Error(`GitHub API error: ${res.status}`);
|
|
27
|
+
}
|
|
28
|
+
return res.json();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function fetchRepoFiles(repo, token) {
|
|
32
|
+
const url = `https://api.github.com/repos/${repo}/git/trees/HEAD?recursive=1`;
|
|
33
|
+
const headers = {
|
|
34
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
35
|
+
'User-Agent': 'gitpal-cli',
|
|
36
|
+
};
|
|
37
|
+
if (token) headers['Authorization'] = `token ${token}`;
|
|
38
|
+
|
|
39
|
+
const res = await fetch(url, { headers });
|
|
40
|
+
if (!res.ok) return [];
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
return data.tree?.filter(f => f.type === 'blob').map(f => f.path) || [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// āāā READ LOCAL FILES āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
46
|
+
|
|
47
|
+
function readLocalFiles(maxFiles = 10) {
|
|
48
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go'];
|
|
49
|
+
const ignore = ['node_modules', '.git', 'dist', 'build', 'coverage'];
|
|
50
|
+
|
|
51
|
+
const files = [];
|
|
52
|
+
|
|
53
|
+
function walk(dir) {
|
|
54
|
+
if (files.length >= maxFiles) return;
|
|
55
|
+
try {
|
|
56
|
+
const entries = fs.readdirSync(dir);
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (ignore.includes(entry)) continue;
|
|
59
|
+
const fullPath = path.join(dir, entry);
|
|
60
|
+
const stat = fs.statSync(fullPath);
|
|
61
|
+
if (stat.isDirectory()) {
|
|
62
|
+
walk(fullPath);
|
|
63
|
+
} else if (extensions.includes(path.extname(entry))) {
|
|
64
|
+
try {
|
|
65
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
66
|
+
files.push({ path: fullPath, content: content.slice(0, 1000) });
|
|
67
|
+
if (files.length >= maxFiles) return;
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
walk(process.cwd());
|
|
75
|
+
return files;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// āāā MAIN COMMAND āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
79
|
+
|
|
80
|
+
export async function issueCommand(issueNumber, options) {
|
|
81
|
+
if (!(await isGitRepo())) {
|
|
82
|
+
console.log(chalk.red('ā Not a git repository. Clone the repo first.'));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const config = loadConfig();
|
|
87
|
+
|
|
88
|
+
// Get GitHub token
|
|
89
|
+
let token = options.githubToken || config.githubToken;
|
|
90
|
+
if (!token) {
|
|
91
|
+
console.log(chalk.yellow('ā ļø No GitHub token found. Using public API (rate limited).'));
|
|
92
|
+
console.log(chalk.dim('Add token: gitpal config --github-token YOUR_TOKEN\n'));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Get repo
|
|
96
|
+
let repo = options.repo;
|
|
97
|
+
if (!repo) {
|
|
98
|
+
try {
|
|
99
|
+
const remotes = await git.getRemotes(true);
|
|
100
|
+
const origin = remotes.find(r => r.name === 'origin');
|
|
101
|
+
if (origin) {
|
|
102
|
+
const match = origin.refs.fetch.match(/github\.com[:/](.+?)(?:\.git)?$/);
|
|
103
|
+
if (match) repo = match[1];
|
|
104
|
+
}
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!repo) {
|
|
109
|
+
console.log(chalk.red('ā Could not detect repo. Use: gitpal issue 234 --repo owner/repo'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(chalk.dim(`\nRepo: ${repo} | Issue: #${issueNumber}\n`));
|
|
114
|
+
|
|
115
|
+
// āā Step 1: Fetch Issue āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
116
|
+
|
|
117
|
+
const issueSpinner = ora(`Fetching issue #${issueNumber} from GitHub...`).start();
|
|
118
|
+
let issue;
|
|
119
|
+
try {
|
|
120
|
+
issue = await fetchIssue(repo, issueNumber, token);
|
|
121
|
+
issueSpinner.succeed(`Issue found: "${issue.title}"`);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
issueSpinner.fail(chalk.red(`Failed: ${err.message}`));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// āā Step 2: Read local codebase āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
128
|
+
|
|
129
|
+
const codeSpinner = ora('Reading your local codebase...').start();
|
|
130
|
+
const localFiles = readLocalFiles(10);
|
|
131
|
+
codeSpinner.succeed(`Read ${localFiles.length} files from codebase.`);
|
|
132
|
+
|
|
133
|
+
// āā Step 3: AI Analysis āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
134
|
+
|
|
135
|
+
const aiSpinner = ora('AI is analyzing the issue and your codebase...').start();
|
|
136
|
+
|
|
137
|
+
const codeContext = localFiles
|
|
138
|
+
.map(f => `File: ${f.path}\n${f.content}`)
|
|
139
|
+
.join('\n\n---\n\n');
|
|
140
|
+
|
|
141
|
+
const prompt = `You are a senior developer helping a junior developer fix a GitHub issue.
|
|
142
|
+
|
|
143
|
+
GITHUB ISSUE:
|
|
144
|
+
Title: ${issue.title}
|
|
145
|
+
Description: ${issue.body || 'No description'}
|
|
146
|
+
Labels: ${issue.labels?.map(l => l.name).join(', ') || 'None'}
|
|
147
|
+
|
|
148
|
+
LOCAL CODEBASE (first 10 files):
|
|
149
|
+
${codeContext.slice(0, 5000)}
|
|
150
|
+
|
|
151
|
+
Based on the issue and codebase, provide:
|
|
152
|
+
|
|
153
|
+
1. UNDERSTANDING (2-3 lines explaining the bug simply)
|
|
154
|
+
|
|
155
|
+
2. FILES TO CHANGE (list exact file paths that need changes)
|
|
156
|
+
|
|
157
|
+
3. HOW TO FIX (step by step, simple language, include code snippets)
|
|
158
|
+
|
|
159
|
+
4. DIFFICULTY: Easy / Medium / Hard
|
|
160
|
+
|
|
161
|
+
5. ESTIMATED TIME: X minutes/hours
|
|
162
|
+
|
|
163
|
+
6. COMMIT MESSAGE (conventional commit format)
|
|
164
|
+
|
|
165
|
+
7. PR TITLE (clear and professional)
|
|
166
|
+
|
|
167
|
+
8. PR DESCRIPTION (What changed, Why, How to test)
|
|
168
|
+
|
|
169
|
+
Be specific and practical. Junior developers should understand this.`;
|
|
170
|
+
|
|
171
|
+
let analysis;
|
|
172
|
+
try {
|
|
173
|
+
analysis = await askAI(prompt);
|
|
174
|
+
aiSpinner.succeed('Analysis complete!\n');
|
|
175
|
+
} catch (err) {
|
|
176
|
+
aiSpinner.fail(chalk.red(`AI Error: ${err.message}`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// āā Step 4: Display Analysis āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
181
|
+
|
|
182
|
+
console.log(chalk.bold.cyan(`\nš Issue #${issueNumber}: ${issue.title}\n`));
|
|
183
|
+
console.log(chalk.dim('ā'.repeat(60)));
|
|
184
|
+
console.log(chalk.white(analysis));
|
|
185
|
+
console.log(chalk.dim('ā'.repeat(60)));
|
|
186
|
+
|
|
187
|
+
// āā Step 5: Ask what to do āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
188
|
+
|
|
189
|
+
const { action } = await inquirer.prompt([{
|
|
190
|
+
type: 'list',
|
|
191
|
+
name: 'action',
|
|
192
|
+
message: '\nWhat would you like to do?',
|
|
193
|
+
choices: [
|
|
194
|
+
{ name: 'šæ Create a new branch for this fix', value: 'branch' },
|
|
195
|
+
{ name: 'š Generate full PR description', value: 'pr' },
|
|
196
|
+
{ name: 'š I will fix it manually', value: 'manual' },
|
|
197
|
+
{ name: 'ā Exit', value: 'exit' },
|
|
198
|
+
],
|
|
199
|
+
}]);
|
|
200
|
+
|
|
201
|
+
if (action === 'exit' || action === 'manual') {
|
|
202
|
+
console.log(chalk.yellow('\nGood luck with the fix! Run gitpal commit when done.'));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// āā Step 6: Create Branch āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
207
|
+
|
|
208
|
+
if (action === 'branch' || action === 'pr') {
|
|
209
|
+
const branchName = `fix/issue-${issueNumber}-${issue.title
|
|
210
|
+
.toLowerCase()
|
|
211
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
212
|
+
.replace(/\s+/g, '-')
|
|
213
|
+
.slice(0, 30)}`;
|
|
214
|
+
|
|
215
|
+
const { confirmBranch } = await inquirer.prompt([{
|
|
216
|
+
type: 'confirm',
|
|
217
|
+
name: 'confirmBranch',
|
|
218
|
+
message: `Create branch: ${chalk.cyan(branchName)}?`,
|
|
219
|
+
default: true,
|
|
220
|
+
}]);
|
|
221
|
+
|
|
222
|
+
if (confirmBranch) {
|
|
223
|
+
const branchSpinner = ora('Creating branch...').start();
|
|
224
|
+
try {
|
|
225
|
+
await git.checkoutLocalBranch(branchName);
|
|
226
|
+
branchSpinner.succeed(chalk.green(`Branch created: ${branchName}`));
|
|
227
|
+
} catch (err) {
|
|
228
|
+
branchSpinner.fail(chalk.red(`Branch error: ${err.message}`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// āā Step 7: Generate PR Description āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
234
|
+
|
|
235
|
+
if (action === 'pr') {
|
|
236
|
+
const prSpinner = ora('Generating PR description...').start();
|
|
237
|
+
|
|
238
|
+
const prPrompt = `Write a professional GitHub Pull Request description for fixing issue #${issueNumber}.
|
|
239
|
+
|
|
240
|
+
Issue Title: ${issue.title}
|
|
241
|
+
Issue Description: ${issue.body?.slice(0, 500) || 'No description'}
|
|
242
|
+
|
|
243
|
+
Format exactly like this:
|
|
244
|
+
## Fixes
|
|
245
|
+
Closes #${issueNumber}
|
|
246
|
+
|
|
247
|
+
## What changed
|
|
248
|
+
(bullet points)
|
|
249
|
+
|
|
250
|
+
## Why
|
|
251
|
+
(brief reason)
|
|
252
|
+
|
|
253
|
+
## How to test
|
|
254
|
+
(testing steps)
|
|
255
|
+
|
|
256
|
+
## Type of change
|
|
257
|
+
(Bug fix / Feature / etc)`;
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const prDesc = await askAI(prPrompt);
|
|
261
|
+
prSpinner.succeed('PR description ready!\n');
|
|
262
|
+
console.log(chalk.bold('\nš Pull Request Description:\n'));
|
|
263
|
+
console.log(chalk.dim('ā'.repeat(60)));
|
|
264
|
+
console.log(chalk.white(prDesc));
|
|
265
|
+
console.log(chalk.dim('ā'.repeat(60)));
|
|
266
|
+
console.log(chalk.dim('\nš” Copy the above and paste into your GitHub PR.\n'));
|
|
267
|
+
} catch (err) {
|
|
268
|
+
prSpinner.fail(chalk.red(`Error: ${err.message}`));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// āā Step 8: Final instructions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
273
|
+
|
|
274
|
+
console.log(chalk.bold.green('\nā
You are ready to contribute!\n'));
|
|
275
|
+
console.log(chalk.white('Next steps:'));
|
|
276
|
+
console.log(chalk.cyan(' 1.') + chalk.white(' Fix the issue in your editor'));
|
|
277
|
+
console.log(chalk.cyan(' 2.') + chalk.white(' git add .'));
|
|
278
|
+
console.log(chalk.cyan(' 3.') + chalk.white(' gitpal commit'));
|
|
279
|
+
console.log(chalk.cyan(' 4.') + chalk.white(` git push origin fix/issue-${issueNumber}`));
|
|
280
|
+
console.log(chalk.cyan(' 5.') + chalk.white(' Open PR on GitHub\n'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// āāā CONFIG GITHUB TOKEN āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
284
|
+
|
|
285
|
+
export function saveGithubToken(token) {
|
|
286
|
+
const config = loadConfig();
|
|
287
|
+
config.githubToken = token;
|
|
288
|
+
saveConfig(config);
|
|
289
|
+
console.log(chalk.green('\nā
GitHub token saved!'));
|
|
290
|
+
console.log(chalk.dim('You can now use: gitpal issue 234 --repo owner/repo\n'));
|
|
291
|
+
}
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { changelogCommand } from './commands/changelog.js';
|
|
|
7
7
|
import { configCommand } from './commands/config.js';
|
|
8
8
|
import { reviewCommand } from './commands/review.js';
|
|
9
9
|
import { explainCommand } from './commands/explain.js';
|
|
10
|
+
import { issueCommand, saveGithubToken } from './commands/issue.js';
|
|
10
11
|
const program = new Command();
|
|
11
12
|
|
|
12
13
|
console.log(chalk.cyan.bold('\nš¤ GitPal ā Your AI Git Assistant\n'));
|
|
@@ -58,6 +59,13 @@ program
|
|
|
58
59
|
.description('Explain any file or commit in plain English')
|
|
59
60
|
.option('-f, --function <name>', 'Explain a specific function')
|
|
60
61
|
.action(explainCommand);
|
|
62
|
+
|
|
63
|
+
program
|
|
64
|
+
.command('issue <number>')
|
|
65
|
+
.description('Fetch and fix any GitHub issue with AI guidance')
|
|
66
|
+
.option('-r, --repo <repo>', 'GitHub repo (owner/repo)')
|
|
67
|
+
.option('-t, --github-token <token>', 'GitHub personal access token')
|
|
68
|
+
.action(issueCommand);
|
|
61
69
|
program.parse(process.argv);
|
|
62
70
|
|
|
63
71
|
// Show help if no command given
|