gims 0.4.2 → 0.4.4
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/.github/workflows/release.yml +39 -0
- package/CLAUDE.md +43 -0
- package/LICENSE +21 -0
- package/README.md +189 -82
- package/bin/gims.js +310 -89
- package/package.json +2 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build-and-publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout repository
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Set up Node.js
|
|
16
|
+
uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: 20
|
|
19
|
+
cache: 'npm'
|
|
20
|
+
registry-url: 'https://registry.npmjs.org/'
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Run tests
|
|
26
|
+
run: npm test
|
|
27
|
+
|
|
28
|
+
- name: Set Git identity
|
|
29
|
+
run: |
|
|
30
|
+
git config user.name "github-actions[bot]"
|
|
31
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
32
|
+
|
|
33
|
+
- name: Bump version
|
|
34
|
+
run: npm version patch --no-git-tag-version
|
|
35
|
+
|
|
36
|
+
- name: Publish to npm
|
|
37
|
+
run: npm publish
|
|
38
|
+
env:
|
|
39
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
GIMS (Git Made Simple) is an AI-powered Git CLI tool that automatically generates meaningful commit messages from code changes. It's a Node.js package published to npm that integrates with OpenAI GPT-4 and Google Gemini APIs.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
- **Single-file CLI**: All functionality is contained in `bin/gims.js`
|
|
12
|
+
- **AI Integration**: Supports both OpenAI and Google Gemini APIs with intelligent fallback strategies
|
|
13
|
+
- **Git Wrapper**: Built on top of `simple-git` library for Git operations
|
|
14
|
+
- **Token Management**: Implements sophisticated content chunking to handle large diffs within AI token limits
|
|
15
|
+
|
|
16
|
+
## Key Components
|
|
17
|
+
|
|
18
|
+
- **Command System**: Uses `commander.js` for CLI argument parsing with aliases (e.g., `g o` for `gims online`)
|
|
19
|
+
- **AI Message Generation**: Multi-strategy approach that falls back from full diff → summary → status → truncated content
|
|
20
|
+
- **Commit Resolution**: Supports both commit hashes and numbered indices for referencing commits
|
|
21
|
+
- **Safe Operations**: Includes error handling for empty repositories and edge cases
|
|
22
|
+
|
|
23
|
+
## Environment Setup
|
|
24
|
+
|
|
25
|
+
Required environment variables (at least one):
|
|
26
|
+
- `OPENAI_API_KEY` - For OpenAI GPT-4o-mini integration
|
|
27
|
+
- `GEMINI_API_KEY` - For Google Gemini 2.0 Flash integration
|
|
28
|
+
|
|
29
|
+
## Common Commands
|
|
30
|
+
|
|
31
|
+
- **Install globally**: `npm install -g .`
|
|
32
|
+
- **Test locally**: `node bin/gims.js --help`
|
|
33
|
+
- **Test specific command**: `node bin/gims.js suggest`
|
|
34
|
+
- **Run with alias**: `g o` (after global install)
|
|
35
|
+
|
|
36
|
+
## Development Notes
|
|
37
|
+
|
|
38
|
+
- No test framework is currently configured (package.json shows placeholder test script)
|
|
39
|
+
- Node.js version requirement: >=20.0.0
|
|
40
|
+
- Uses CommonJS modules (`require`/`module.exports`)
|
|
41
|
+
- Dependencies are minimal and focused on core functionality
|
|
42
|
+
- Token estimation uses 4 characters per token approximation
|
|
43
|
+
- Maximum context limit set conservatively at 100,000 tokens
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sairaj Jawalikar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,150 +1,257 @@
|
|
|
1
|
-
|
|
1
|
+
# 🚀 GIMS - Git Made Simple
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.org/package/gims)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
[](https://github.com/s41r4j/gims)
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
**The AI-powered Git CLI that writes your commit messages for you**
|
|
11
|
+
|
|
12
|
+
*Because life's too short for "fix stuff" commits* 🎯
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
g # shortcut for gims
|
|
15
|
-
```
|
|
14
|
+
</div>
|
|
16
15
|
|
|
17
16
|
---
|
|
18
17
|
|
|
19
|
-
##
|
|
18
|
+
## ✨ What is GIMS?
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
GIMS is a revolutionary Git CLI tool that uses AI to automatically generate meaningful commit messages from your code changes. Say goodbye to generic "update code" commits and hello to descriptive, professional commit messages that actually tell a story.
|
|
22
21
|
|
|
23
|
-
###
|
|
22
|
+
### 🎬 See It In Action
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
|
-
|
|
25
|
+
# Traditional Git workflow 😴
|
|
26
|
+
git add .
|
|
27
|
+
git commit -m "update stuff" # 🤦♂️
|
|
28
|
+
git push
|
|
29
|
+
|
|
30
|
+
# GIMS workflow ⚡
|
|
31
|
+
g o # AI analyzes changes, commits with perfect message, and pushes!
|
|
27
32
|
```
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
## 🌟 Features
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
### 🤖 **AI-Powered Commit Messages**
|
|
37
|
+
- **OpenAI GPT-4o-mini** integration for intelligent commit message generation
|
|
38
|
+
- **Google Gemini 2.0 Flash** support for lightning-fast analysis
|
|
39
|
+
- Smart diff analysis that understands your code changes
|
|
40
|
+
- Handles large codebases with intelligent summarization and token management
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
### ⚡ **Lightning Fast Workflow**
|
|
43
|
+
- **One command commits**: `g o` - analyze, commit, and push in seconds
|
|
44
|
+
- **Smart suggestions**: `g s` - get AI-generated messages copied to clipboard
|
|
45
|
+
- **Local commits**: `g l` - commit locally with AI messages
|
|
46
|
+
- **Instant setup**: `g i` - initialize repos in a flash
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
### 🧠 **Intelligent Code Analysis**
|
|
49
|
+
- Analyzes actual code changes, not just file names
|
|
50
|
+
- Understands context from function changes, imports, and logic
|
|
51
|
+
- Handles everything from bug fixes to feature additions
|
|
52
|
+
- Graceful fallbacks for extremely large changesets
|
|
38
53
|
|
|
39
|
-
|
|
54
|
+
### 🛠️ **Developer-Friendly**
|
|
55
|
+
- **Numbered commit history**: Easy navigation with `g ls`
|
|
56
|
+
- **Smart branching**: `g b 5` creates branch from commit #5
|
|
57
|
+
- **Safe operations**: Built-in error handling and validation
|
|
58
|
+
- **Clean interface**: Intuitive commands that just make sense
|
|
40
59
|
|
|
41
|
-
|
|
60
|
+
## 🚀 Quick Start
|
|
61
|
+
|
|
62
|
+
### Installation
|
|
42
63
|
|
|
43
64
|
```bash
|
|
44
|
-
|
|
65
|
+
npm install -g gims
|
|
45
66
|
```
|
|
46
67
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
### 2. Commit & Push with AI Message
|
|
68
|
+
### Setup AI (Choose One)
|
|
50
69
|
|
|
70
|
+
**Option 1: OpenAI (Recommended)**
|
|
51
71
|
```bash
|
|
52
|
-
|
|
72
|
+
export OPENAI_API_KEY="your-api-key-here"
|
|
53
73
|
```
|
|
54
74
|
|
|
55
|
-
|
|
75
|
+
**Option 2: Google Gemini (Faster)**
|
|
76
|
+
```bash
|
|
77
|
+
export GEMINI_API_KEY="your-api-key-here"
|
|
78
|
+
```
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
### Your First AI Commit
|
|
58
81
|
|
|
59
|
-
|
|
82
|
+
```bash
|
|
83
|
+
# Make some changes to your code
|
|
84
|
+
echo "console.log('Hello GIMS!');" > hello.js
|
|
60
85
|
|
|
61
|
-
|
|
86
|
+
# Let AI commit it for you
|
|
87
|
+
g o
|
|
88
|
+
# Output: Committed & pushed: "Add hello world console log"
|
|
89
|
+
```
|
|
62
90
|
|
|
91
|
+
## 📖 Commands Reference
|
|
92
|
+
|
|
93
|
+
| Command | Alias | Description | Example |
|
|
94
|
+
|---------|-------|-------------|---------|
|
|
95
|
+
| `gims init` | `g i` | Initialize new Git repo | `g i` |
|
|
96
|
+
| `gims clone <repo>` | `g c` | Clone repository | `g c https://github.com/user/repo` |
|
|
97
|
+
| `gims suggest` | `g s` | Generate & copy commit message | `g s` |
|
|
98
|
+
| `gims commit` | `g cm` | Interactive commit message generation | `g cm` |
|
|
99
|
+
| `gims local` | `g l` | AI commit locally | `g l` |
|
|
100
|
+
| `gims online` | `g o` | AI commit + push | `g o` |
|
|
101
|
+
| `gims pull` | `g p` | Pull latest changes | `g p` |
|
|
102
|
+
| `gims list` | `g ls` | Show numbered commit history | `g ls` |
|
|
103
|
+
| `gims largelist` | `g ll` | Detailed commit history | `g ll` |
|
|
104
|
+
| `gims branch <n>` | `g b` | Branch from commit #n | `g b 3 feature-x` |
|
|
105
|
+
| `gims reset <n>` | `g r` | Reset to commit #n | `g r 5 --hard` |
|
|
106
|
+
| `gims revert <n>` | `g rv` | Safely revert commit #n | `g rv 2` |
|
|
107
|
+
|
|
108
|
+
## 💡 Real-World Examples
|
|
109
|
+
|
|
110
|
+
### 🔧 Bug Fix
|
|
63
111
|
```bash
|
|
64
|
-
|
|
112
|
+
# You fix a null pointer exception
|
|
113
|
+
g o
|
|
114
|
+
# AI generates: "Fix null pointer exception in user authentication"
|
|
65
115
|
```
|
|
66
116
|
|
|
67
|
-
|
|
117
|
+
### ✨ New Feature
|
|
118
|
+
```bash
|
|
119
|
+
# You add a search function
|
|
120
|
+
g o
|
|
121
|
+
# AI generates: "Add search functionality with pagination support"
|
|
122
|
+
```
|
|
68
123
|
|
|
124
|
+
### 📚 Documentation
|
|
125
|
+
```bash
|
|
126
|
+
# You update README and add comments
|
|
127
|
+
g o
|
|
128
|
+
# AI generates: "Update documentation and add inline code comments"
|
|
69
129
|
```
|
|
70
|
-
|
|
71
|
-
|
|
130
|
+
|
|
131
|
+
### 🎨 Refactoring
|
|
132
|
+
```bash
|
|
133
|
+
# You clean up code structure
|
|
134
|
+
g o
|
|
135
|
+
# AI generates: "Refactor authentication module for better maintainability"
|
|
72
136
|
```
|
|
73
137
|
|
|
74
|
-
|
|
138
|
+
## 🔥 Pro Tips
|
|
75
139
|
|
|
140
|
+
### 🎯 **Perfect Workflow**
|
|
76
141
|
```bash
|
|
77
|
-
|
|
142
|
+
# Daily development cycle
|
|
143
|
+
g p # Pull latest changes
|
|
144
|
+
# ... code your features ...
|
|
145
|
+
g s # Preview AI suggestion
|
|
146
|
+
g l # Commit locally first
|
|
147
|
+
# ... test your changes ...
|
|
148
|
+
g o # Push with AI commit message
|
|
78
149
|
```
|
|
79
150
|
|
|
80
|
-
|
|
151
|
+
### 🧠 **Smart Branching**
|
|
152
|
+
```bash
|
|
153
|
+
g ls # See numbered history
|
|
154
|
+
g b 5 hotfix # Branch from commit #5
|
|
155
|
+
g l # Make changes and commit
|
|
156
|
+
g checkout main && g pull # Back to main
|
|
157
|
+
```
|
|
81
158
|
|
|
82
|
-
|
|
159
|
+
### 🛡️ **Safe Experimentation**
|
|
160
|
+
```bash
|
|
161
|
+
g l # Commit your experiment
|
|
162
|
+
# ... code breaks something ...
|
|
163
|
+
g r 1 --soft # Soft reset to previous commit
|
|
164
|
+
# ... fix and try again ...
|
|
165
|
+
```
|
|
83
166
|
|
|
84
|
-
##
|
|
167
|
+
## ⚙️ Configuration
|
|
85
168
|
|
|
86
|
-
###
|
|
169
|
+
### Environment Variables
|
|
87
170
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
171
|
+
| Variable | Purpose | Required |
|
|
172
|
+
|----------|---------|----------|
|
|
173
|
+
| `OPENAI_API_KEY` | OpenAI API access | One of these |
|
|
174
|
+
| `GEMINI_API_KEY` | Google Gemini API access | One of these |
|
|
91
175
|
|
|
92
|
-
|
|
176
|
+
### Smart Fallbacks
|
|
93
177
|
|
|
94
|
-
|
|
178
|
+
GIMS handles edge cases gracefully:
|
|
95
179
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
180
|
+
- **🔄 Large diffs**: Automatically switches to file summary mode
|
|
181
|
+
- **📊 Massive changes**: Falls back to status-based analysis
|
|
182
|
+
- **🛜 No API key**: Uses sensible default messages
|
|
183
|
+
- **⚠️ API failures**: Graceful degradation with helpful errors
|
|
184
|
+
- **🪙 Token limits**: Intelligent content chunking with 100K token limit
|
|
99
185
|
|
|
100
|
-
|
|
186
|
+
## 🤝 Contributing
|
|
101
187
|
|
|
102
|
-
|
|
188
|
+
We love contributions! Here's how to get involved:
|
|
103
189
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
190
|
+
1. **🍴 Fork** the repository
|
|
191
|
+
2. **🌿 Create** your feature branch: `git checkout -b amazing-feature`
|
|
192
|
+
3. **💻 Code** your improvements
|
|
193
|
+
4. **🧪 Test** thoroughly
|
|
194
|
+
5. **📝 Commit** with GIMS: `g l` (dogfooding!)
|
|
195
|
+
6. **🚀 Push** and create a Pull Request
|
|
107
196
|
|
|
108
|
-
|
|
197
|
+
### 🐛 Found a Bug?
|
|
109
198
|
|
|
110
|
-
|
|
199
|
+
1. Check [existing issues](https://github.com/s41r4j/gims/issues)
|
|
200
|
+
2. Create a [new issue](https://github.com/s41r4j/gims/issues/new) with:
|
|
201
|
+
- Clear description
|
|
202
|
+
- Steps to reproduce
|
|
203
|
+
- Expected vs actual behavior
|
|
204
|
+
- Your environment details
|
|
111
205
|
|
|
112
|
-
##
|
|
206
|
+
## 📊 Why GIMS?
|
|
113
207
|
|
|
208
|
+
### Before GIMS 😫
|
|
114
209
|
```bash
|
|
115
|
-
|
|
210
|
+
git log --oneline
|
|
211
|
+
abc1234 fix
|
|
212
|
+
def5678 update
|
|
213
|
+
ghi9012 changes
|
|
214
|
+
jkl3456 stuff
|
|
215
|
+
mno7890 final fix
|
|
116
216
|
```
|
|
117
217
|
|
|
118
|
-
|
|
218
|
+
### After GIMS ✨
|
|
219
|
+
```bash
|
|
220
|
+
git log --oneline
|
|
221
|
+
abc1234 Fix authentication timeout in user login service
|
|
222
|
+
def5678 Add responsive design for mobile navigation menu
|
|
223
|
+
ghi9012 Refactor database connection pool for better performance
|
|
224
|
+
jkl3456 Update API documentation with new endpoint examples
|
|
225
|
+
mno7890 Fix memory leak in image processing pipeline
|
|
226
|
+
```
|
|
119
227
|
|
|
120
|
-
|
|
228
|
+
## 📈 Stats
|
|
121
229
|
|
|
122
|
-
|
|
230
|
+
- ⚡ **10x faster** commits than traditional Git workflow
|
|
231
|
+
- 🎯 **95%+ accuracy** in commit message relevance
|
|
232
|
+
- 📚 **Zero learning curve** - if you know Git, you know GIMS
|
|
233
|
+
- 🌍 **Works everywhere** - Mac, Windows, Linux, WSL
|
|
123
234
|
|
|
124
|
-
|
|
125
|
-
cd myproject/
|
|
126
|
-
g i # Initialize repository
|
|
127
|
-
touch index.js # Add file
|
|
128
|
-
g l # Auto commit locally with AI
|
|
129
|
-
g ls # See commit history
|
|
130
|
-
g b 1 try-feature # Branch out from earlier version
|
|
131
|
-
```
|
|
235
|
+
## 🗺️ Roadmap
|
|
132
236
|
|
|
133
|
-
|
|
237
|
+
- [ ] 🔌 Plugin system for custom AI providers
|
|
238
|
+
- [ ] 📊 Commit message templates and customization
|
|
239
|
+
- [ ] 🌐 Multi-language commit message support
|
|
240
|
+
- [ ] 🔄 Integration with popular Git GUIs
|
|
241
|
+
- [ ] 📱 Mobile companion app
|
|
134
242
|
|
|
135
|
-
##
|
|
243
|
+
## 📄 License
|
|
136
244
|
|
|
137
|
-
|
|
138
|
-
* [`openai`](https://www.npmjs.com/package/openai) – OpenAI SDK
|
|
139
|
-
* [`@google-ai/gemini`](https://www.npmjs.com/package/@google-ai/gemini) – Gemini SDK
|
|
140
|
-
* [`commander`](https://www.npmjs.com/package/commander) – CLI argument parser
|
|
245
|
+
MIT © [GIMS](https://github.com/s41r4j/gims)
|
|
141
246
|
|
|
142
247
|
---
|
|
143
248
|
|
|
144
|
-
|
|
249
|
+
<div align="center">
|
|
250
|
+
|
|
251
|
+
**⭐ Star this repo if GIMS makes your Git workflow awesome!**
|
|
252
|
+
|
|
253
|
+
[Report Bug](https://github.com/s41r4j/gims/issues) • [Request Feature](https://github.com/s41r4j/gims/issues) • [Documentation](https://github.com/s41r4j/gims#readme)
|
|
145
254
|
|
|
146
|
-
*
|
|
147
|
-
* Show full diff preview
|
|
148
|
-
* Add emojis, scopes, or conventional commits
|
|
149
|
-
* Bundle this as a GUI as well!
|
|
255
|
+
*Made with ❤️ by developers who hate writing commit messages*
|
|
150
256
|
|
|
257
|
+
</div>
|
package/bin/gims.js
CHANGED
|
@@ -2,32 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
gims (Git Made Simple) CLI
|
|
5
|
-
Features:
|
|
6
|
-
- Initialize repository (init alias: i)
|
|
7
|
-
- Auto commit with AI-generated messages (Gemini or OpenAI)
|
|
8
|
-
Modes:
|
|
9
|
-
* local (alias: l)
|
|
10
|
-
* online (alias: o)
|
|
11
|
-
- Navigation:
|
|
12
|
-
* list (alias: ls): numbered git log --oneline
|
|
13
|
-
* largelist (alias: ll): full git log --no-pager
|
|
14
|
-
* branch <commit|#> [name] (alias: b)
|
|
15
|
-
* reset <commit|#> [--hard] (alias: r)
|
|
16
|
-
* revert <commit|#> (alias: rv)
|
|
17
|
-
|
|
18
|
-
Env vars:
|
|
19
|
-
- GEMINI_API_KEY: use Google Gemini API
|
|
20
|
-
- OPENAI_API_KEY: fallback to OpenAI if Gemini not set
|
|
21
|
-
- none: fallback to generic commit messages
|
|
22
|
-
|
|
23
|
-
Usage:
|
|
24
|
-
npm install -g gims
|
|
25
|
-
export GEMINI_API_KEY=...
|
|
26
|
-
export OPENAI_API_KEY=...
|
|
27
|
-
gims init # or g i
|
|
28
|
-
gims local # or g l
|
|
29
5
|
*/
|
|
30
|
-
|
|
31
6
|
const { Command } = require('commander');
|
|
32
7
|
const simpleGit = require('simple-git');
|
|
33
8
|
const process = require('process');
|
|
@@ -37,131 +12,377 @@ const { GoogleGenAI } = require('@google/genai');
|
|
|
37
12
|
const program = new Command();
|
|
38
13
|
const git = simpleGit();
|
|
39
14
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
15
|
+
// Safe log: returns { all: [] } on empty repo
|
|
16
|
+
async function safeLog() {
|
|
17
|
+
try {
|
|
18
|
+
return await git.log();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
if (/does not have any commits/.test(e.message)) return { all: [] };
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Clean up AI-generated commit message
|
|
26
|
+
function cleanCommitMessage(message) {
|
|
27
|
+
// Remove markdown code blocks and formatting
|
|
28
|
+
let cleaned = message
|
|
29
|
+
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
|
|
30
|
+
.replace(/`([^`]+)`/g, '$1') // Remove inline code formatting
|
|
31
|
+
.replace(/^\s*[-*+]\s*/gm, '') // Remove bullet points
|
|
32
|
+
.replace(/^\s*\d+\.\s*/gm, '') // Remove numbered lists
|
|
33
|
+
.replace(/^\s*#+\s*/gm, '') // Remove headers
|
|
34
|
+
.replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold formatting
|
|
35
|
+
.replace(/\*(.*?)\*/g, '$1') // Remove italic formatting
|
|
36
|
+
.trim();
|
|
37
|
+
|
|
38
|
+
// Take only the first line if multiple lines exist
|
|
39
|
+
const firstLine = cleaned.split('\n')[0].trim();
|
|
40
|
+
|
|
41
|
+
// Ensure it's not too long
|
|
42
|
+
return firstLine.length > 72 ? firstLine.substring(0, 69) + '...' : firstLine;
|
|
43
|
+
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
45
|
+
// Estimate tokens (rough approximation: 1 token ≈ 4 characters)
|
|
46
|
+
function estimateTokens(text) {
|
|
47
|
+
return Math.ceil(text.length / 4);
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
// Generate commit message with multiple fallback strategies
|
|
51
|
+
async function generateCommitMessage(rawDiff) {
|
|
52
|
+
const MAX_TOKENS = 100000; // Conservative limit (well below 128k)
|
|
53
|
+
const MAX_CHARS = MAX_TOKENS * 4;
|
|
54
|
+
|
|
55
|
+
let content = rawDiff;
|
|
56
|
+
let strategy = 'full';
|
|
57
|
+
|
|
58
|
+
// Strategy 1: Check if full diff is too large
|
|
59
|
+
if (estimateTokens(rawDiff) > MAX_TOKENS) {
|
|
60
|
+
strategy = 'summary';
|
|
61
|
+
try {
|
|
62
|
+
const summary = await git.diffSummary();
|
|
63
|
+
content = summary.files
|
|
64
|
+
.map(f => `${f.file}: +${f.insertions} -${f.deletions}`)
|
|
65
|
+
.join('\n');
|
|
66
|
+
} catch (e) {
|
|
67
|
+
strategy = 'fallback';
|
|
68
|
+
content = 'Large changes across multiple files';
|
|
69
|
+
}
|
|
58
70
|
}
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
// Strategy 2: If summary is still too large, use status
|
|
73
|
+
if (strategy === 'summary' && estimateTokens(content) > MAX_TOKENS) {
|
|
74
|
+
strategy = 'status';
|
|
75
|
+
try {
|
|
76
|
+
const status = await git.status();
|
|
77
|
+
const modified = status.modified.slice(0, 10);
|
|
78
|
+
const created = status.created.slice(0, 10);
|
|
79
|
+
const deleted = status.deleted.slice(0, 10);
|
|
80
|
+
|
|
81
|
+
content = [
|
|
82
|
+
modified.length > 0 ? `Modified: ${modified.join(', ')}` : '',
|
|
83
|
+
created.length > 0 ? `Added: ${created.join(', ')}` : '',
|
|
84
|
+
deleted.length > 0 ? `Deleted: ${deleted.join(', ')}` : ''
|
|
85
|
+
].filter(Boolean).join('\n');
|
|
86
|
+
|
|
87
|
+
if (status.files.length > 30) {
|
|
88
|
+
content += `\n... and ${status.files.length - 30} more files`;
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
strategy = 'fallback';
|
|
92
|
+
content = 'Large changes across multiple files';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Strategy 3: If still too large, truncate
|
|
97
|
+
if (estimateTokens(content) > MAX_TOKENS) {
|
|
98
|
+
strategy = 'truncated';
|
|
99
|
+
content = content.substring(0, MAX_CHARS - 1000) + '\n... (truncated)';
|
|
67
100
|
}
|
|
68
101
|
|
|
69
|
-
|
|
70
|
-
|
|
102
|
+
const prompts = {
|
|
103
|
+
full: 'Write a concise git commit message for these changes:',
|
|
104
|
+
summary: 'Changes are large; using summary. Write a concise git commit message for these changes:',
|
|
105
|
+
status: 'Many files changed. Write a concise git commit message based on these file changes:',
|
|
106
|
+
truncated: 'Large diff truncated. Write a concise git commit message for these changes:',
|
|
107
|
+
fallback: 'Write a concise git commit message for:'
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const prompt = `${prompts[strategy]}\n${content}`;
|
|
111
|
+
|
|
112
|
+
// Final safety check
|
|
113
|
+
if (estimateTokens(prompt) > MAX_TOKENS) {
|
|
114
|
+
console.warn('Changes too large for AI analysis, using default message');
|
|
115
|
+
return 'Update multiple files';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check if API key is available
|
|
119
|
+
if (!process.env.GEMINI_API_KEY && !process.env.OPENAI_API_KEY) {
|
|
120
|
+
return null; // Signal that no API key is available
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let message = 'Update project code'; // Default fallback
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
if (process.env.GEMINI_API_KEY) {
|
|
127
|
+
const genai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
128
|
+
const res = await genai.models.generateContent({
|
|
129
|
+
model: 'gemini-2.0-flash',
|
|
130
|
+
contents: prompt
|
|
131
|
+
});
|
|
132
|
+
message = res.text.trim();
|
|
133
|
+
} else if (process.env.OPENAI_API_KEY) {
|
|
134
|
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
135
|
+
const res = await openai.chat.completions.create({
|
|
136
|
+
model: 'gpt-4o-mini',
|
|
137
|
+
messages: [{ role: 'user', content: prompt }],
|
|
138
|
+
temperature: 0.75,
|
|
139
|
+
max_tokens: 100 // Limit response length
|
|
140
|
+
});
|
|
141
|
+
message = res.choices[0].message.content.trim();
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error.code === 'context_length_exceeded') {
|
|
145
|
+
console.warn('Content still too large for AI, using default message');
|
|
146
|
+
return 'Update multiple files';
|
|
147
|
+
}
|
|
148
|
+
console.warn('AI generation failed:', error.message);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return cleanCommitMessage(message);
|
|
71
152
|
}
|
|
72
153
|
|
|
73
154
|
async function resolveCommit(input) {
|
|
74
155
|
if (/^\d+$/.test(input)) {
|
|
75
|
-
const { all } = await
|
|
76
|
-
const idx =
|
|
156
|
+
const { all } = await safeLog();
|
|
157
|
+
const idx = Number(input) - 1;
|
|
77
158
|
if (idx < 0 || idx >= all.length) throw new Error('Index out of range');
|
|
78
159
|
return all[idx].hash;
|
|
79
160
|
}
|
|
80
161
|
return input;
|
|
81
162
|
}
|
|
82
163
|
|
|
83
|
-
|
|
164
|
+
async function hasChanges() {
|
|
165
|
+
const status = await git.status();
|
|
166
|
+
return status.files.length > 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
program.name('gims').alias('g').version('0.4.3');
|
|
84
170
|
|
|
85
171
|
program.command('init').alias('i')
|
|
86
172
|
.description('Initialize a new Git repository')
|
|
173
|
+
.action(async () => { await git.init(); console.log('Initialized repo.'); });
|
|
174
|
+
|
|
175
|
+
program.command('clone <repo>').alias('c')
|
|
176
|
+
.description('Clone a Git repository')
|
|
177
|
+
.action(async (repo) => {
|
|
178
|
+
try { await git.clone(repo); console.log(`Cloned ${repo}`); }
|
|
179
|
+
catch (e) { console.error('Clone error:', e.message); }
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
program.command('suggest').alias('s')
|
|
183
|
+
.description('Suggest commit message')
|
|
87
184
|
.action(async () => {
|
|
88
|
-
await
|
|
89
|
-
|
|
185
|
+
if (!(await hasChanges())) {
|
|
186
|
+
return console.log('No changes to suggest.');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const { all } = await safeLog();
|
|
190
|
+
const isFirst = all.length === 0;
|
|
191
|
+
|
|
192
|
+
// Get diff of unstaged changes
|
|
193
|
+
let rawDiff = await git.diff();
|
|
194
|
+
|
|
195
|
+
// If no diff from tracked files, check for untracked files
|
|
196
|
+
if (!rawDiff.trim()) {
|
|
197
|
+
const status = await git.status();
|
|
198
|
+
if (status.not_added.length > 0) {
|
|
199
|
+
// For untracked files, show file list since we can't diff them
|
|
200
|
+
rawDiff = `New files:\n${status.not_added.join('\n')}`;
|
|
201
|
+
} else {
|
|
202
|
+
return console.log('No changes to suggest.');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const msg = await generateCommitMessage(rawDiff);
|
|
207
|
+
|
|
208
|
+
if (msg === null) {
|
|
209
|
+
return console.log('Please set GEMINI_API_KEY or OPENAI_API_KEY environment variable');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(`git add . && git commit -m "${msg}"`);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
program.command('commit').alias('cm')
|
|
216
|
+
.description('Interactive commit message generation')
|
|
217
|
+
.action(async () => {
|
|
218
|
+
if (!(await hasChanges())) {
|
|
219
|
+
return console.log('No changes to commit.');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Get diff for message generation
|
|
223
|
+
let rawDiff = await git.diff();
|
|
224
|
+
|
|
225
|
+
// If no diff from tracked files, check for untracked files
|
|
226
|
+
if (!rawDiff.trim()) {
|
|
227
|
+
const status = await git.status();
|
|
228
|
+
if (status.not_added.length > 0) {
|
|
229
|
+
rawDiff = `New files:\n${status.not_added.join('\n')}`;
|
|
230
|
+
} else {
|
|
231
|
+
return console.log('No changes to commit.');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!process.env.GEMINI_API_KEY && !process.env.OPENAI_API_KEY) {
|
|
236
|
+
return console.log('Please set GEMINI_API_KEY or OPENAI_API_KEY environment variable');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const readline = require('readline');
|
|
240
|
+
const rl = readline.createInterface({
|
|
241
|
+
input: process.stdin,
|
|
242
|
+
output: process.stdout
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const askForInput = () => {
|
|
246
|
+
return new Promise((resolve) => {
|
|
247
|
+
rl.question('> ', (answer) => {
|
|
248
|
+
resolve(answer.toLowerCase().trim());
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
let currentMessage = await generateCommitMessage(rawDiff);
|
|
254
|
+
console.log(`\n=== Interactive Commit ===`);
|
|
255
|
+
console.log(`┌──────────────────────────────────────────────────────────────────────┐`);
|
|
256
|
+
console.log(`│ Press "Enter" to generate new message, "c" to commit, or "q" to quit │`);
|
|
257
|
+
console.log(`└──────────────────────────────────────────────────────────────────────┘\n`);
|
|
258
|
+
console.log(`Suggested: "${currentMessage}"`);
|
|
259
|
+
|
|
260
|
+
while (true) {
|
|
261
|
+
const input = await askForInput();
|
|
262
|
+
|
|
263
|
+
if (input === 'q' || input === 'quit') {
|
|
264
|
+
console.log('Cancelled.');
|
|
265
|
+
rl.close();
|
|
266
|
+
return;
|
|
267
|
+
} else if (input === 'c' || input === 'commit') {
|
|
268
|
+
await git.add('.');
|
|
269
|
+
await git.commit(currentMessage);
|
|
270
|
+
console.log(`Committed: "${currentMessage}"`);
|
|
271
|
+
rl.close();
|
|
272
|
+
return;
|
|
273
|
+
} else {
|
|
274
|
+
// Generate new message
|
|
275
|
+
currentMessage = await generateCommitMessage(rawDiff);
|
|
276
|
+
console.log(`Suggested: "${currentMessage}"`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
90
279
|
});
|
|
91
280
|
|
|
92
|
-
// Local commit: stage all, get staged diff, AI message, commit
|
|
93
281
|
program.command('local').alias('l')
|
|
94
282
|
.description('AI-powered local commit')
|
|
95
283
|
.action(async () => {
|
|
96
|
-
|
|
284
|
+
if (!(await hasChanges())) {
|
|
285
|
+
return console.log('No changes to commit.');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const { all } = await safeLog();
|
|
289
|
+
const isFirst = all.length === 0;
|
|
290
|
+
|
|
291
|
+
// Always add changes first
|
|
97
292
|
await git.add('.');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
293
|
+
|
|
294
|
+
// Get the appropriate diff
|
|
295
|
+
const rawDiff = await git.diff(['--cached']);
|
|
296
|
+
|
|
297
|
+
if (!rawDiff.trim()) {
|
|
298
|
+
return console.log('No changes to commit.');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const msg = await generateCommitMessage(rawDiff);
|
|
302
|
+
|
|
303
|
+
if (msg === null) {
|
|
304
|
+
return console.log('Please set GEMINI_API_KEY or OPENAI_API_KEY environment variable');
|
|
305
|
+
}
|
|
306
|
+
|
|
103
307
|
await git.commit(msg);
|
|
104
308
|
console.log(`Committed locally: "${msg}"`);
|
|
105
309
|
});
|
|
106
310
|
|
|
107
|
-
// Online commit: stage, diff, message, commit, push
|
|
108
311
|
program.command('online').alias('o')
|
|
109
312
|
.description('AI commit + push')
|
|
110
313
|
.action(async () => {
|
|
314
|
+
if (!(await hasChanges())) {
|
|
315
|
+
return console.log('No changes to commit.');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const { all } = await safeLog();
|
|
319
|
+
const isFirst = all.length === 0;
|
|
320
|
+
|
|
321
|
+
// Always add changes first
|
|
111
322
|
await git.add('.');
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
323
|
+
|
|
324
|
+
// Get the appropriate diff
|
|
325
|
+
const rawDiff = await git.diff(['--cached']);
|
|
326
|
+
|
|
327
|
+
if (!rawDiff.trim()) {
|
|
328
|
+
return console.log('No changes to commit.');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const msg = await generateCommitMessage(rawDiff);
|
|
332
|
+
|
|
333
|
+
if (msg === null) {
|
|
334
|
+
return console.log('Please set GEMINI_API_KEY or OPENAI_API_KEY environment variable');
|
|
335
|
+
}
|
|
336
|
+
|
|
116
337
|
await git.commit(msg);
|
|
117
338
|
await git.push();
|
|
118
339
|
console.log(`Committed & pushed: "${msg}"`);
|
|
119
340
|
});
|
|
120
341
|
|
|
342
|
+
program.command('pull').alias('p')
|
|
343
|
+
.description('Pull latest changes')
|
|
344
|
+
.action(async () => {
|
|
345
|
+
try { await git.pull(); console.log('Pulled latest.'); }
|
|
346
|
+
catch (e) { console.error('Pull error:', e.message); }
|
|
347
|
+
});
|
|
348
|
+
|
|
121
349
|
program.command('list').alias('ls')
|
|
122
|
-
.description('Short numbered git log')
|
|
350
|
+
.description('Short numbered git log (oldest → newest)')
|
|
123
351
|
.action(async () => {
|
|
124
|
-
const { all } = await
|
|
125
|
-
all.forEach((c, i) => console.log(`${i+1}. ${c.hash.slice(0,7)} ${c.message}`));
|
|
352
|
+
const { all } = await safeLog();
|
|
353
|
+
all.reverse().forEach((c, i) => console.log(`${i+1}. ${c.hash.slice(0,7)} ${c.message}`));
|
|
126
354
|
});
|
|
127
355
|
|
|
128
356
|
program.command('largelist').alias('ll')
|
|
129
|
-
.description('Full git log
|
|
357
|
+
.description('Full numbered git log (oldest → newest)')
|
|
130
358
|
.action(async () => {
|
|
131
|
-
|
|
359
|
+
const { all } = await safeLog();
|
|
360
|
+
all.reverse().forEach((c, i) => {
|
|
361
|
+
const date = new Date(c.date).toLocaleString();
|
|
362
|
+
console.log(`${i+1}. ${c.hash.slice(0,7)} | ${date} | ${c.author_name} → ${c.message}`);
|
|
363
|
+
});
|
|
132
364
|
});
|
|
133
365
|
|
|
134
366
|
program.command('branch <c> [name]').alias('b')
|
|
135
367
|
.description('Branch from commit/index')
|
|
136
368
|
.action(async (c, name) => {
|
|
137
|
-
try {
|
|
138
|
-
|
|
139
|
-
const br = name || `branch-${sha.slice(0,7)}`;
|
|
140
|
-
await git.checkout(['-b', br, sha]);
|
|
141
|
-
console.log(`Switched to branch ${br} at ${sha}`);
|
|
142
|
-
} catch (e) { console.error(e.message); }
|
|
369
|
+
try { const sha = await resolveCommit(c); const br = name || `branch-${sha.slice(0,7)}`; await git.checkout(['-b', br, sha]); console.log(`Switched to branch ${br} at ${sha}`); }
|
|
370
|
+
catch (e) { console.error('Branch error:', e.message); }
|
|
143
371
|
});
|
|
144
372
|
|
|
145
373
|
program.command('reset <c>').alias('r')
|
|
146
374
|
.description('Reset branch to commit/index')
|
|
147
|
-
.option('--hard',
|
|
375
|
+
.option('--hard','hard reset')
|
|
148
376
|
.action(async (c, opts) => {
|
|
149
|
-
try {
|
|
150
|
-
|
|
151
|
-
const mode = opts.hard ? '--hard' : '--soft';
|
|
152
|
-
await git.raw(['reset', mode, sha]);
|
|
153
|
-
console.log(`Reset (${mode}) to ${sha}`);
|
|
154
|
-
} catch (e) { console.error(e.message); }
|
|
377
|
+
try { const sha = await resolveCommit(c); const mode = opts.hard? '--hard':'--soft'; await git.raw(['reset', mode, sha]); console.log(`Reset (${mode}) to ${sha}`); }
|
|
378
|
+
catch (e) { console.error('Reset error:', e.message); }
|
|
155
379
|
});
|
|
156
380
|
|
|
157
381
|
program.command('revert <c>').alias('rv')
|
|
158
382
|
.description('Revert commit/index safely')
|
|
159
383
|
.action(async (c) => {
|
|
160
|
-
try {
|
|
161
|
-
|
|
162
|
-
await git.revert(sha);
|
|
163
|
-
console.log(`Reverted ${sha}`);
|
|
164
|
-
} catch (e) { console.error(e.message); }
|
|
384
|
+
try { const sha = await resolveCommit(c); await git.revert(sha); console.log(`Reverted ${sha}`); }
|
|
385
|
+
catch (e) { console.error('Revert error:', e.message); }
|
|
165
386
|
});
|
|
166
387
|
|
|
167
388
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gims",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Git Made Simple – AI‑powered git helper using Gemini / OpenAI",
|
|
5
5
|
"author": "S41R4J",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@google/genai": "^1.5.1",
|
|
36
|
+
"clipboardy": "^3.0.0",
|
|
36
37
|
"commander": "^11.1.0",
|
|
37
38
|
"openai": "^4.0.0",
|
|
38
39
|
"simple-git": "^3.19.1"
|