gims 0.4.3 β†’ 0.5.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.
@@ -0,0 +1,25 @@
1
+ name: release to npm
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ build:
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
+
21
+ - name: Install dependencies
22
+ run: npm ci
23
+
24
+ - name: Run tests
25
+ run: npm test
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,140 +1,304 @@
1
+ # πŸš€ GIMS - Git Made Simple
1
2
 
3
+ <div align="center">
4
+
5
+ [![npm version](https://img.shields.io/npm/v/gims.svg)](https://npmjs.org/package/gims)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js Version](https://img.shields.io/node/v/gims.svg)](https://nodejs.org/)
8
+ [![AI Powered](https://img.shields.io/badge/AI-Powered-blueviolet.svg)](https://github.com/yourusername/gims)
2
9
 
3
- ## πŸš€ Installation
10
+ **The AI-powered Git CLI that writes your commit messages for you**
11
+
12
+ *Because life's too short for "fix stuff" commits* 🎯
4
13
 
5
- ```bash
6
- npm install -g gims
7
- ```
8
-
9
- This installs the `gims` command and a shortcut alias:
10
-
11
- ```bash
12
- g # shortcut for gims
13
- ```
14
+ </div>
14
15
 
15
16
  ---
16
17
 
17
- ## πŸ”§ Environment Setup (Optional for AI)
18
+ ## ✨ What is GIMS?
18
19
 
19
- To enable AI-generated commit messages:
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.
20
21
 
21
- ### βœ… Option 1: Gemini API
22
+ ### 🎬 See It In Action
22
23
 
23
24
  ```bash
24
- export GEMINI_API_KEY='your_gemini_api_key'
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!
25
32
  ```
26
33
 
27
- ### βœ… Option 2: OpenAI API (used if Gemini key not found)
34
+ ## 🌟 Features
28
35
 
29
- ```bash
30
- export OPENAI_API_KEY='your_openai_api_key'
31
- ```
36
+ ### πŸ€– **AI-Powered Commit Messages**
37
+ - OpenAI, Google Gemini, and Groq support with automatic provider selection
38
+ - Smart diff analysis that understands your code changes
39
+ - Handles large codebases with intelligent summarization and safe truncation
40
+ - Optional Conventional Commits formatting and optional commit body generation (`--conventional`, `--body`)
32
41
 
33
- If neither is set, it defaults to a generic commit message.
42
+ ### ⚑ **Lightning Fast Workflow**
43
+ - One command commits: `g o` - analyze, commit, and push
44
+ - Smart suggestions: `g s` - get AI-generated messages copied to clipboard
45
+ - Local commits: `g l` - commit locally with AI messages
46
+ - Staged-only by default for suggestions for precise control (use `--all` to stage everything)
34
47
 
35
- ---
48
+ ### 🧠 **Intelligent Code Analysis**
49
+ - Analyzes actual code changes, not just file names
50
+ - Understands context from function changes, imports, and logic
51
+ - Graceful fallbacks for extremely large changesets and offline use
52
+
53
+ ### πŸ› οΈ **Developer-Friendly**
54
+ - Numbered commit history with `g ls` / `g ll` and index-aware commands
55
+ - Smart branching: `g b 5` creates branch from commit #5
56
+ - Safe operations with confirmations and dry-run support
57
+ - JSON output for editor integrations
58
+ - Quality-of-life: `--amend`, `undo` command, and automatic upstream setup on push
59
+ - Manual commit command for custom messages: `g m "your message"`
36
60
 
37
- ## 🧠 AI Commit Modes
61
+ ## πŸš€ Quick Start
38
62
 
39
- ### 1. Commit Locally with AI Message
63
+ ### Installation
40
64
 
41
65
  ```bash
42
- g l # or gims local
66
+ npm install -g gims
43
67
  ```
44
68
 
45
- β†’ Adds and commits current changes locally with a descriptive commit message generated by AI.
69
+ ### Setup AI (Choose One)
46
70
 
47
- ### 2. Commit & Push with AI Message
71
+ **Option 1: OpenAI**
72
+ ```bash
73
+ export OPENAI_API_KEY="your-api-key-here"
74
+ ```
75
+
76
+ **Option 2: Google Gemini**
77
+ ```bash
78
+ export GEMINI_API_KEY="your-api-key-here"
79
+ ```
48
80
 
81
+ **Option 3: Groq**
49
82
  ```bash
50
- g o # or gims online
83
+ export GROQ_API_KEY="your-api-key-here"
84
+ # Optional, if self-hosting/proxying
85
+ export GROQ_BASE_URL="https://api.groq.com/openai/v1"
51
86
  ```
52
87
 
53
- β†’ Adds, commits, and pushes the current changes to the remote branch.
88
+ GIMS auto-detects configured providers. If none are configured, it uses a local heuristic to generate sensible messages.
54
89
 
55
- ---
90
+ ### Your First AI Commit
56
91
 
57
- ## πŸ“š Version History Navigation
92
+ ```bash
93
+ # Make some changes to your code
94
+ echo "console.log('Hello GIMS!');" > hello.js
58
95
 
59
- ### View Git Logs (Short)
96
+ # Let AI commit it for you
97
+ g o
98
+ # Output: Committed & pushed: "Add hello world console log"
99
+ ```
60
100
 
101
+ ## πŸ“– Commands Reference
102
+
103
+ | Command | Alias | Description | Example |
104
+ |---------|-------|-------------|---------|
105
+ | `gims init` | `g i` | Initialize new Git repo | `g i` |
106
+ | `gims clone <repo>` | `g c` | Clone repository | `g c https://github.com/user/repo` |
107
+ | `gims suggest` | `g s` | Generate & copy commit message from staged changes (use `--all` to stage) | `g s --all` |
108
+ | `gims local` | `g l` | AI commit locally | `g l` |
109
+ | `gims online` | `g o` | AI commit + push (use `--set-upstream` on first push) | `g o --set-upstream` |
110
+ | `gims commit <message...>` | `g m` | Commit with a custom message (no AI) | `g m "fix: handle empty input"` |
111
+ | `gims pull` | `g p` | Pull latest changes | `g p` |
112
+ | `gims list` | `g ls` | Show numbered commit history | `g ls` |
113
+ | `gims largelist` | `g ll` | Detailed commit history | `g ll` |
114
+ | `gims branch <n>` | `g b` | Branch from commit #n | `g b 3 feature-x` |
115
+ | `gims reset <n>` | `g r` | Reset to commit #n (`--hard` needs `--yes`) | `g r 5 --hard --yes` |
116
+ | `gims revert <n>` | `g rv` | Safely revert commit #n (requires `--yes`) | `g rv 2 --yes` |
117
+ | `gims undo` | `g u` | Undo last commit (soft reset by default) | `g u` or `g u --hard --yes` |
118
+
119
+ ### Global Options
120
+
121
+ - `--provider <name>`: AI provider: `auto` | `openai` | `gemini` | `groq` | `none`
122
+ - `--model <name>`: Override model identifier for the chosen provider
123
+ - `--staged-only`: Use only staged changes (default behavior for `g s`)
124
+ - `--all`: Stage all changes before running
125
+ - `--no-clipboard`: Do not copy suggestion to clipboard (for `g s`)
126
+ - `--body`: Generate a commit body in addition to subject
127
+ - `--conventional`: Format subject using Conventional Commits
128
+ - `--dry-run`: Print what would happen without committing/pushing
129
+ - `--verbose`: Verbose logging
130
+ - `--json`: Machine-readable output for `g s`
131
+ - `--yes`: Confirm destructive actions without prompting (e.g., reset/revert/undo)
132
+ - `--amend`: Amend the last commit instead of creating a new one
133
+ - `--set-upstream`: On push, set upstream if the current branch has none
134
+
135
+ ## πŸ’‘ Real-World Examples
136
+
137
+ ### πŸ”§ Bug Fix
61
138
  ```bash
62
- g ls # or gims list
139
+ # You fix a null pointer exception
140
+ g o
141
+ # AI generates: "Fix null pointer exception in user authentication"
63
142
  ```
64
143
 
65
- β†’ Shows a numbered short log:
144
+ ### ✨ New Feature
145
+ ```bash
146
+ # You add a search function
147
+ g o
148
+ # AI generates: "Add search functionality with pagination support"
149
+ ```
66
150
 
151
+ ### πŸ“š Documentation
152
+ ```bash
153
+ # You update README and add comments
154
+ g o
155
+ # AI generates: "Update documentation and add inline code comments"
67
156
  ```
68
- 1. 1234abc Added login feature
69
- 2. 5678def Fixed bug in auth flow
157
+
158
+ ### 🎨 Refactoring
159
+ ```bash
160
+ # You clean up code structure
161
+ g o
162
+ # AI generates: "Refactor authentication module for better maintainability"
70
163
  ```
71
164
 
72
- ### View Git Logs (Full)
165
+ ## πŸ”₯ Pro Tips
73
166
 
167
+ ### 🎯 Perfect Workflow
74
168
  ```bash
75
- g ll # or gims largelist
169
+ g p # Pull latest changes
170
+ # ... code your features ...
171
+ g s # Preview AI suggestion from staged changes
172
+ g s --all # Or stage everything and suggest
173
+ g l # Commit locally first
174
+ # ... test your changes ...
175
+ g o --set-upstream # Push with automatic upstream setup on first push
76
176
  ```
77
177
 
78
- β†’ Full `git log` (without pager).
178
+ ### 🧠 Smart Branching
179
+ ```bash
180
+ g ls # See numbered history
181
+ g b 5 hotfix # Branch from commit #5
182
+ g l # Make changes and commit
183
+ g checkout main && g pull # Back to main
184
+ ```
79
185
 
80
- ---
186
+ ### πŸ›‘οΈ Safe Experimentation
187
+ ```bash
188
+ g l # Commit your experiment
189
+ # ... code breaks something ...
190
+ g r 1 --soft --yes # Soft reset to previous commit (confirmed)
191
+ # ... or ...
192
+ g u --yes # Undo last commit (soft)
193
+ ```
81
194
 
82
- ## 🌿 Branching & Resetting
195
+ ## βš™οΈ Configuration
83
196
 
84
- ### Create Branch from a Commit or Number
197
+ ### Environment Variables
85
198
 
86
- ```bash
87
- g b 2 try-new-idea
88
- ```
199
+ | Variable | Purpose |
200
+ |----------|---------|
201
+ | `OPENAI_API_KEY` | OpenAI API access |
202
+ | `GEMINI_API_KEY` | Google Gemini API access |
203
+ | `GROQ_API_KEY` | Groq API access (OpenAI-compatible) |
204
+ | `GROQ_BASE_URL` | Groq API base URL (optional) |
205
+ | `GIMS_PROVIDER` | Default provider: `auto` | `openai` | `gemini` | `groq` | `none` |
206
+ | `GIMS_MODEL` | Default model identifier for provider |
207
+ | `GIMS_CONVENTIONAL` | `1` to enable Conventional Commits by default |
208
+ | `GIMS_COPY` | `0` to disable clipboard copying in `g s` by default |
89
209
 
90
- β†’ Branches off from commit #2 (resolved via `g ls`) into a new branch `try-new-idea`.
210
+ ### .gimsrc (optional)
91
211
 
92
- ### Reset to a Commit or Number
212
+ Place a `.gimsrc` JSON file in your repo root or home directory to set defaults:
93
213
 
94
- ```bash
95
- g r 1 --hard
214
+ ```json
215
+ {
216
+ "provider": "auto",
217
+ "model": "gpt-4o-mini",
218
+ "conventional": true,
219
+ "copy": true
220
+ }
96
221
  ```
97
222
 
98
- β†’ Resets the current branch to commit #1 permanently (`--hard` is optional).
223
+ ### Smart Fallbacks
99
224
 
100
- ### Revert a Commit or Number
225
+ GIMS handles edge cases gracefully:
101
226
 
102
- ```bash
103
- g rv 3
104
- ```
227
+ - πŸ”„ Large diffs: Automatically switches to summary or status view
228
+ - βœ‚οΈ Massive text: Truncates safely with informative context
229
+ - πŸ›œ No API key: Uses a local heuristic that summarizes your changes
230
+ - ⚠️ API failures: Clear errors and local fallback so you keep moving
231
+ - πŸ”’ Privacy-first: Only sends diffs when you explicitly run AI features
105
232
 
106
- β†’ Reverts changes made by commit #3 (safe, non-destructive).
233
+ ## 🀝 Contributing
107
234
 
108
- ---
235
+ We love contributions! Here's how to get involved:
109
236
 
110
- ## πŸ†• Git Init
237
+ 1. **🍴 Fork** the repository
238
+ 2. **🌿 Create** your feature branch: `git checkout -b amazing-feature`
239
+ 3. **πŸ’» Code** your improvements
240
+ 4. **πŸ§ͺ Test** thoroughly
241
+ 5. **πŸ“ Commit** with GIMS: `g l` (dogfooding!)
242
+ 6. **πŸš€ Push** and create a Pull Request
111
243
 
112
- ```bash
113
- g i # or gims init
114
- ```
244
+ ### πŸ› Found a Bug?
115
245
 
116
- β†’ Initializes a new Git repository.
246
+ 1. Check [existing issues](https://github.com/yourusername/gims/issues)
247
+ 2. Create a [new issue](https://github.com/yourusername/gims/issues/new) with:
248
+ - Clear description
249
+ - Steps to reproduce
250
+ - Expected vs actual behavior
251
+ - Your environment details
117
252
 
118
- ---
253
+ ## πŸ“Š Why GIMS?
119
254
 
120
- ## πŸ“ Example Workflow
255
+ ### Before GIMS 😫
256
+ ```bash
257
+ git log --oneline
258
+ abc1234 fix
259
+ def5678 update
260
+ ghi9012 changes
261
+ jkl3456 stuff
262
+ mno7890 final fix
263
+ ```
121
264
 
265
+ ### After GIMS ✨
122
266
  ```bash
123
- cd myproject/
124
- g i # Initialize repository
125
- touch index.js # Add file
126
- g l # Auto commit locally with AI
127
- g ls # See commit history
128
- g b 1 try-feature # Branch out from earlier version
267
+ git log --oneline
268
+ abc1234 Fix authentication timeout in user login service
269
+ def5678 Add responsive design for mobile navigation menu
270
+ ghi9012 Refactor database connection pool for better performance
271
+ jkl3456 Update API documentation with new endpoint examples
272
+ mno7890 Fix memory leak in image processing pipeline
129
273
  ```
130
274
 
275
+ ## πŸ“ˆ Stats
276
+
277
+ - ⚑ Faster commits than traditional Git workflow
278
+ - 🎯 High accuracy in commit message relevance
279
+ - πŸ“š Zero learning curve - if you know Git, you know GIMS
280
+ - 🌍 Works everywhere - Mac, Windows, Linux, WSL
281
+
282
+ ## πŸ—ΊοΈ Roadmap
283
+
284
+ - [ ] πŸ”Œ Plugin system for custom AI providers
285
+ - [ ] πŸ“Š Commit message templates and customization
286
+ - [ ] 🌐 Multi-language commit message support
287
+ - [ ] πŸ”„ Integration with popular Git GUIs
288
+ - [ ] πŸ“± Mobile companion app
289
+
290
+ ## πŸ“„ License
291
+
292
+ MIT Β© [Your Name](https://github.com/yourusername)
293
+
131
294
  ---
132
295
 
133
- ## πŸ“¦ Dependencies Used
296
+ <div align="center">
297
+
298
+ **⭐ Star this repo if GIMS makes your Git workflow awesome!**
134
299
 
135
- * [`simple-git`](https://www.npmjs.com/package/simple-git) – Git CLI wrapper
136
- * [`openai`](https://www.npmjs.com/package/openai) – OpenAI SDK
137
- * [`@google-ai/gemini`](https://www.npmjs.com/package/@google-ai/gemini) – Gemini SDK
138
- * [`commander`](https://www.npmjs.com/package/commander) – CLI argument parser
300
+ [Report Bug](https://github.com/yourusername/gims/issues) β€’ [Request Feature](https://github.com/yourusername/gims/issues) β€’ [Documentation](https://github.com/yourusername/gims/wiki)
139
301
 
302
+ *Made with ❀️ by developers who hate writing commit messages*
140
303
 
304
+ </div>
package/bin/gims.js CHANGED
@@ -9,10 +9,82 @@ const clipboard = require('clipboardy');
9
9
  const process = require('process');
10
10
  const { OpenAI } = require('openai');
11
11
  const { GoogleGenAI } = require('@google/genai');
12
+ const fs = require('fs');
13
+ const path = require('path');
12
14
 
13
15
  const program = new Command();
14
16
  const git = simpleGit();
15
17
 
18
+ // Utility: ANSI colors without extra deps
19
+ const color = {
20
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
21
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
22
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
23
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
24
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
25
+ };
26
+
27
+ // Load simple config from .gimsrc (JSON) in cwd or home and env vars
28
+ function loadConfig() {
29
+ const defaults = {
30
+ provider: process.env.GIMS_PROVIDER || 'auto', // auto | openai | gemini | groq | none
31
+ model: process.env.GIMS_MODEL || '',
32
+ conventional: !!(process.env.GIMS_CONVENTIONAL === '1'),
33
+ copy: process.env.GIMS_COPY !== '0',
34
+ };
35
+ const tryFiles = [
36
+ path.join(process.cwd(), '.gimsrc'),
37
+ path.join(process.env.HOME || process.cwd(), '.gimsrc'),
38
+ ];
39
+ for (const fp of tryFiles) {
40
+ try {
41
+ if (fs.existsSync(fp)) {
42
+ const txt = fs.readFileSync(fp, 'utf8');
43
+ const json = JSON.parse(txt);
44
+ return { ...defaults, ...json };
45
+ }
46
+ } catch (_) {
47
+ // ignore malformed config
48
+ }
49
+ }
50
+ return defaults;
51
+ }
52
+
53
+ function getOpts() {
54
+ // Merge precedence: CLI > config > env handled in loadConfig
55
+ const cfg = loadConfig();
56
+ const cli = program.opts();
57
+ return {
58
+ provider: cli.provider || cfg.provider,
59
+ model: cli.model || cfg.model,
60
+ stagedOnly: !!cli.stagedOnly,
61
+ all: !!cli.all,
62
+ noClipboard: !!cli.noClipboard || cfg.copy === false,
63
+ body: !!cli.body,
64
+ conventional: !!cli.conventional || cfg.conventional,
65
+ dryRun: !!cli.dryRun,
66
+ verbose: !!cli.verbose,
67
+ json: !!cli.json,
68
+ yes: !!cli.yes,
69
+ amend: !!cli.amend,
70
+ setUpstream: !!cli.setUpstream,
71
+ };
72
+ }
73
+
74
+ async function ensureRepo() {
75
+ const isRepo = await git.checkIsRepo();
76
+ if (!isRepo) {
77
+ console.error(color.red('Not a git repository (or any of the parent directories).'));
78
+ process.exit(1);
79
+ }
80
+ }
81
+
82
+ function handleError(prefix, err) {
83
+ const msg = err && err.message ? err.message : String(err);
84
+ console.error(color.red(`${prefix}: ${msg}`));
85
+ process.exit(1);
86
+ }
87
+
16
88
  // Safe log: returns { all: [] } on empty repo
17
89
  async function safeLog() {
18
90
  try {
@@ -24,7 +96,8 @@ async function safeLog() {
24
96
  }
25
97
 
26
98
  // Clean up AI-generated commit message
27
- function cleanCommitMessage(message) {
99
+ function cleanCommitMessage(message, { body = false } = {}) {
100
+ if (!message) return 'Update project code';
28
101
  // Remove markdown code blocks and formatting
29
102
  let cleaned = message
30
103
  .replace(/```[\s\S]*?```/g, '') // Remove code blocks
@@ -34,28 +107,98 @@ function cleanCommitMessage(message) {
34
107
  .replace(/^\s*#+\s*/gm, '') // Remove headers
35
108
  .replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold formatting
36
109
  .replace(/\*(.*?)\*/g, '$1') // Remove italic formatting
110
+ .replace(/[\u{1F300}-\u{1FAFF}]/gu, '') // strip most emojis
111
+ .replace(/[\t\r]+/g, ' ')
37
112
  .trim();
38
-
39
- // Take only the first line if multiple lines exist
40
- const firstLine = cleaned.split('\n')[0].trim();
41
-
42
- // Ensure it's not too long
43
- return firstLine.length > 72 ? firstLine.substring(0, 69) + '...' : firstLine;
113
+
114
+ // If a body is allowed, split subject/body, otherwise keep first line only
115
+ const lines = cleaned.split('\n').map(l => l.trim()).filter(Boolean);
116
+ let subject = (lines[0] || '').replace(/\s{2,}/g, ' ').replace(/[\s:,.!;]+$/g, '').trim();
117
+ if (subject.length === 0) subject = 'Update project code';
118
+ // Enforce concise subject
119
+ if (subject.length > 72) subject = subject.substring(0, 69) + '...';
120
+
121
+ if (!body) return subject;
122
+
123
+ const bodyLines = lines.slice(1).filter(l => l.length > 0);
124
+ const bodyText = bodyLines.join('\n').trim();
125
+ return bodyText ? `${subject}\n\n${bodyText}` : subject;
44
126
  }
45
127
 
46
128
  // Estimate tokens (rough approximation: 1 token β‰ˆ 4 characters)
47
129
  function estimateTokens(text) {
48
- return Math.ceil(text.length / 4);
130
+ return Math.ceil((text || '').length / 4);
131
+ }
132
+
133
+ function resolveProvider(pref) {
134
+ // pref: auto|openai|gemini|groq|none
135
+ if (pref === 'none') return 'none';
136
+ if (pref === 'openai') return process.env.OPENAI_API_KEY ? 'openai' : 'none';
137
+ if (pref === 'gemini') return process.env.GEMINI_API_KEY ? 'gemini' : 'none';
138
+ if (pref === 'groq') return process.env.GROQ_API_KEY ? 'groq' : 'none';
139
+ // auto
140
+ if (process.env.GEMINI_API_KEY) return 'gemini';
141
+ if (process.env.OPENAI_API_KEY) return 'openai';
142
+ if (process.env.GROQ_API_KEY) return 'groq';
143
+ return 'none';
144
+ }
145
+
146
+ async function getHumanReadableChanges(limitPerList = 10) {
147
+ try {
148
+ const status = await git.status();
149
+ const modified = status.modified.slice(0, limitPerList);
150
+ const created = status.created.slice(0, limitPerList);
151
+ const deleted = status.deleted.slice(0, limitPerList);
152
+ const renamed = status.renamed.map(r => `${r.from}β†’${r.to}`).slice(0, limitPerList);
153
+ const parts = [];
154
+ if (created.length) parts.push(`Added: ${created.join(', ')}`);
155
+ if (modified.length) parts.push(`Modified: ${modified.join(', ')}`);
156
+ if (deleted.length) parts.push(`Deleted: ${deleted.join(', ')}`);
157
+ if (renamed.length) parts.push(`Renamed: ${renamed.join(', ')}`);
158
+ return parts.join('\n');
159
+ } catch (_) {
160
+ return 'Multiple file changes.';
161
+ }
162
+ }
163
+
164
+ function localHeuristicMessage(status, { conventional = false } = {}) {
165
+ const created = status.created.length;
166
+ const modified = status.modified.length;
167
+ const deleted = status.deleted.length;
168
+ const total = created + modified + deleted + status.renamed.length;
169
+
170
+ const listFew = (arr) => arr.slice(0, 3).join(', ') + (arr.length > 3 ? ` and ${arr.length - 3} more` : '');
171
+
172
+ let type = 'chore';
173
+ let subject = 'update files';
174
+ if (created > 0 && modified === 0 && deleted === 0) {
175
+ type = 'feat';
176
+ subject = created <= 3 ? `add ${listFew(status.created)}` : `add ${created} files`;
177
+ } else if (deleted > 0 && created === 0 && modified === 0) {
178
+ type = 'chore';
179
+ subject = deleted <= 3 ? `remove ${listFew(status.deleted)}` : `remove ${deleted} files`;
180
+ } else if (modified > 0 && created === 0 && deleted === 0) {
181
+ type = 'chore';
182
+ subject = modified <= 3 ? `update ${listFew(status.modified)}` : `update ${modified} files`;
183
+ } else if (created > 0 || deleted > 0 || modified > 0) {
184
+ type = 'chore';
185
+ subject = `update ${total} files`;
186
+ }
187
+ const msg = conventional ? `${type}: ${subject}` : subject.charAt(0).toUpperCase() + subject.slice(1);
188
+ return msg;
49
189
  }
50
190
 
51
191
  // Generate commit message with multiple fallback strategies
52
- async function generateCommitMessage(rawDiff) {
192
+ async function generateCommitMessage(rawDiff, options = {}) {
193
+ const { conventional = false, body = false, provider: prefProvider = 'auto', model = '', verbose = false } = options;
53
194
  const MAX_TOKENS = 100000; // Conservative limit (well below 128k)
54
195
  const MAX_CHARS = MAX_TOKENS * 4;
55
-
196
+
56
197
  let content = rawDiff;
57
198
  let strategy = 'full';
58
199
 
200
+ const logv = (m) => { if (verbose) console.log(color.cyan(`[gims] ${m}`)); };
201
+
59
202
  // Strategy 1: Check if full diff is too large
60
203
  if (estimateTokens(rawDiff) > MAX_TOKENS) {
61
204
  strategy = 'summary';
@@ -78,13 +221,15 @@ async function generateCommitMessage(rawDiff) {
78
221
  const modified = status.modified.slice(0, 10);
79
222
  const created = status.created.slice(0, 10);
80
223
  const deleted = status.deleted.slice(0, 10);
81
-
224
+ const renamed = status.renamed.map(r => `${r.from}β†’${r.to}`).slice(0, 10);
225
+
82
226
  content = [
83
227
  modified.length > 0 ? `Modified: ${modified.join(', ')}` : '',
84
228
  created.length > 0 ? `Added: ${created.join(', ')}` : '',
85
- deleted.length > 0 ? `Deleted: ${deleted.join(', ')}` : ''
229
+ deleted.length > 0 ? `Deleted: ${deleted.join(', ')}` : '',
230
+ renamed.length > 0 ? `Renamed: ${renamed.join(', ')}` : '',
86
231
  ].filter(Boolean).join('\n');
87
-
232
+
88
233
  if (status.files.length > 30) {
89
234
  content += `\n... and ${status.files.length - 30} more files`;
90
235
  }
@@ -105,54 +250,81 @@ async function generateCommitMessage(rawDiff) {
105
250
  summary: 'Changes are large; using summary. Write a concise git commit message for these changes:',
106
251
  status: 'Many files changed. Write a concise git commit message based on these file changes:',
107
252
  truncated: 'Large diff truncated. Write a concise git commit message for these changes:',
108
- fallback: 'Write a concise git commit message for:'
253
+ fallback: 'Write a concise git commit message for:',
109
254
  };
110
255
 
111
- const prompt = `${prompts[strategy]}\n${content}`;
256
+ const style = conventional ? 'Use Conventional Commits (e.g., feat:, fix:, chore:) for the subject.' : 'Subject must be a single short line.';
257
+ const bodyInstr = body ? 'Provide a short subject line followed by an optional body separated by a blank line.' : 'Return only a short subject line without extra quotes.';
258
+ const prompt = `${prompts[strategy]}\n${content}\n\n${style} ${bodyInstr}`;
112
259
 
113
260
  // Final safety check
114
261
  if (estimateTokens(prompt) > MAX_TOKENS) {
115
- console.warn('Changes too large for AI analysis, using default message');
116
- return 'Update multiple files';
262
+ console.warn(color.yellow('Changes too large for AI analysis, using default message'));
263
+ return cleanCommitMessage('Update multiple files', { body });
117
264
  }
118
265
 
119
266
  let message = 'Update project code'; // Default fallback
267
+ const provider = resolveProvider(prefProvider);
268
+ logv(`strategy=${strategy}, provider=${provider}${model ? `, model=${model}` : ''}`);
120
269
 
121
270
  try {
122
- if (process.env.GEMINI_API_KEY) {
271
+ if (provider === 'gemini') {
123
272
  const genai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
124
- const res = await genai.models.generateContent({
125
- model: 'gemini-2.0-flash',
126
- contents: prompt
273
+ const res = await genai.models.generateContent({
274
+ model: model || 'gemini-2.0-flash',
275
+ contents: prompt,
127
276
  });
128
277
  message = (await res.response.text()).trim();
129
- } else if (process.env.OPENAI_API_KEY) {
278
+ } else if (provider === 'openai') {
130
279
  const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
131
280
  const res = await openai.chat.completions.create({
132
- model: 'gpt-4o-mini',
281
+ model: model || 'gpt-4o-mini',
133
282
  messages: [{ role: 'user', content: prompt }],
134
- temperature: 0.5,
135
- max_tokens: 100 // Limit response length
283
+ temperature: 0.3,
284
+ max_tokens: body ? 200 : 80,
136
285
  });
137
- message = res.choices[0].message.content.trim();
286
+ message = (res.choices[0] && res.choices[0].message && res.choices[0].message.content || '').trim();
287
+ } else if (provider === 'groq') {
288
+ // Use OpenAI-compatible API via baseURL
289
+ const groq = new OpenAI({ apiKey: process.env.GROQ_API_KEY, baseURL: process.env.GROQ_BASE_URL || 'https://api.groq.com/openai/v1' });
290
+ const res = await groq.chat.completions.create({
291
+ model: model || 'llama-3.1-8b-instant',
292
+ messages: [{ role: 'user', content: prompt }],
293
+ temperature: 0.3,
294
+ max_tokens: body ? 200 : 80,
295
+ });
296
+ message = (res.choices[0] && res.choices[0].message && res.choices[0].message.content || '').trim();
297
+ } else {
298
+ // Local heuristic fallback
299
+ const status = await git.status();
300
+ message = localHeuristicMessage(status, { conventional });
301
+ const human = await getHumanReadableChanges();
302
+ if (body) message = `${message}\n\n${human}`;
138
303
  }
139
304
  } catch (error) {
140
- if (error.code === 'context_length_exceeded') {
141
- console.warn('Content still too large for AI, using default message');
142
- return 'Update multiple files';
305
+ if (error && error.code === 'context_length_exceeded') {
306
+ console.warn(color.yellow('Content still too large for AI, using default message'));
307
+ return cleanCommitMessage('Update multiple files', { body });
143
308
  }
144
- console.warn('AI generation failed:', error.message);
309
+ console.warn(color.yellow(`AI generation failed: ${error && error.message ? error.message : error}`));
310
+ // fallback to local heuristic
311
+ const status = await git.status();
312
+ message = localHeuristicMessage(status, { conventional });
313
+ const human = await getHumanReadableChanges();
314
+ if (body) message = `${message}\n\n${human}`;
145
315
  }
146
316
 
147
- return cleanCommitMessage(message);
317
+ return cleanCommitMessage(message, { body });
148
318
  }
149
319
 
150
320
  async function resolveCommit(input) {
151
321
  if (/^\d+$/.test(input)) {
152
322
  const { all } = await safeLog();
323
+ // Align with list/largelist which show oldest -> newest
324
+ const ordered = [...all].reverse();
153
325
  const idx = Number(input) - 1;
154
- if (idx < 0 || idx >= all.length) throw new Error('Index out of range');
155
- return all[idx].hash;
326
+ if (idx < 0 || idx >= ordered.length) throw new Error('Index out of range');
327
+ return ordered[idx].hash;
156
328
  }
157
329
  return input;
158
330
  }
@@ -162,144 +334,296 @@ async function hasChanges() {
162
334
  return status.files.length > 0;
163
335
  }
164
336
 
165
- program.name('gims').alias('g').version('0.4.3');
337
+ program
338
+ .name('gims')
339
+ .alias('g')
340
+ .version(require('../package.json').version)
341
+ .option('--provider <name>', 'AI provider: auto|openai|gemini|groq|none')
342
+ .option('--model <name>', 'Model identifier for provider')
343
+ .option('--staged-only', 'Use only staged changes (default for suggest)')
344
+ .option('--all', 'Stage all changes before running')
345
+ .option('--no-clipboard', 'Do not copy suggestions to clipboard')
346
+ .option('--body', 'Generate a commit body in addition to subject')
347
+ .option('--conventional', 'Format messages using Conventional Commits')
348
+ .option('--dry-run', 'Do not perform writes (no commit or push)')
349
+ .option('--verbose', 'Verbose logging')
350
+ .option('--json', 'JSON output for suggest')
351
+ .option('--yes', 'Assume yes for confirmations')
352
+ .option('--amend', 'Amend the last commit instead of creating a new one')
353
+ .option('--set-upstream', 'Set upstream on push if missing');
166
354
 
167
355
  program.command('init').alias('i')
168
356
  .description('Initialize a new Git repository')
169
- .action(async () => { await git.init(); console.log('Initialized repo.'); });
357
+ .action(async () => {
358
+ try { await git.init(); console.log('Initialized repo.'); }
359
+ catch (e) { handleError('Init error', e); }
360
+ });
170
361
 
171
362
  program.command('clone <repo>').alias('c')
172
363
  .description('Clone a Git repository')
173
364
  .action(async (repo) => {
174
365
  try { await git.clone(repo); console.log(`Cloned ${repo}`); }
175
- catch (e) { console.error('Clone error:', e.message); }
366
+ catch (e) { handleError('Clone error', e); }
176
367
  });
177
368
 
178
369
  program.command('suggest').alias('s')
179
370
  .description('Suggest commit message and copy to clipboard')
180
371
  .action(async () => {
181
- if (!(await hasChanges())) {
182
- return console.log('No changes to suggest.');
183
- }
372
+ await ensureRepo();
373
+ const opts = getOpts();
184
374
 
185
- const { all } = await safeLog();
186
- const isFirst = all.length === 0;
187
-
188
- // Always add changes first
189
- await git.add('.');
190
-
191
- // Get the appropriate diff
192
- const rawDiff = await git.diff(['--cached']);
193
-
194
- if (!rawDiff.trim()) {
195
- return console.log('No changes to suggest.');
196
- }
197
-
198
- const msg = await generateCommitMessage(rawDiff);
199
-
200
375
  try {
201
- clipboard.writeSync(msg);
202
- console.log(`Suggested: "${msg}" (copied to clipboard)`);
203
- } catch (error) {
204
- console.log(`Suggested: "${msg}" (clipboard copy failed)`);
376
+ if (opts.all) {
377
+ await git.add('.');
378
+ }
379
+
380
+ // Use staged changes only; do not auto-stage unless --all
381
+ const rawDiff = await git.diff(['--cached', '--no-ext-diff']);
382
+ if (!rawDiff.trim()) {
383
+ if (opts.all) {
384
+ console.log('No changes to suggest.');
385
+ return;
386
+ }
387
+ console.log('No staged changes. Use --all to stage everything or stage files manually.');
388
+ return;
389
+ }
390
+
391
+ const msg = await generateCommitMessage(rawDiff, opts);
392
+
393
+ if (opts.json) {
394
+ const out = { message: msg };
395
+ console.log(JSON.stringify(out));
396
+ return;
397
+ }
398
+
399
+ if (!opts.noClipboard) {
400
+ try { clipboard.writeSync(msg); console.log(`Suggested: "${msg}" ${color.green('(copied to clipboard)')}`); }
401
+ catch (_) { console.log(`Suggested: "${msg}" ${color.yellow('(clipboard copy failed)')}`); }
402
+ } else {
403
+ console.log(`Suggested: "${msg}"`);
404
+ }
405
+ } catch (e) {
406
+ handleError('Suggest error', e);
205
407
  }
206
408
  });
207
409
 
208
410
  program.command('local').alias('l')
209
411
  .description('AI-powered local commit')
210
412
  .action(async () => {
211
- if (!(await hasChanges())) {
212
- return console.log('No changes to commit.');
213
- }
413
+ await ensureRepo();
414
+ const opts = getOpts();
214
415
 
215
- const { all } = await safeLog();
216
- const isFirst = all.length === 0;
217
-
218
- // Always add changes first
219
- await git.add('.');
220
-
221
- // Get the appropriate diff
222
- const rawDiff = await git.diff(['--cached']);
223
-
224
- if (!rawDiff.trim()) {
225
- return console.log('No changes to commit.');
416
+ try {
417
+ if (!(await hasChanges()) && !opts.all) {
418
+ console.log('No changes to commit.');
419
+ return;
420
+ }
421
+
422
+ if (opts.all) await git.add('.');
423
+
424
+ const rawDiff = await git.diff(['--cached', '--no-ext-diff']);
425
+ if (!rawDiff.trim()) { console.log('No staged changes to commit.'); return; }
426
+
427
+ const msg = await generateCommitMessage(rawDiff, opts);
428
+
429
+ if (opts.dryRun) {
430
+ console.log(color.yellow('[dry-run] Would commit with message:'));
431
+ console.log(msg);
432
+ return;
433
+ }
434
+
435
+ if (opts.amend) {
436
+ await git.raw(['commit', '--amend', '-m', msg]);
437
+ } else {
438
+ await git.commit(msg);
439
+ }
440
+ console.log(`Committed locally: "${msg}"`);
441
+ } catch (e) {
442
+ handleError('Local commit error', e);
226
443
  }
227
-
228
- const msg = await generateCommitMessage(rawDiff);
229
- await git.commit(msg);
230
- console.log(`Committed locally: "${msg}"`);
231
444
  });
232
445
 
233
446
  program.command('online').alias('o')
234
447
  .description('AI commit + push')
235
448
  .action(async () => {
236
- if (!(await hasChanges())) {
237
- return console.log('No changes to commit.');
449
+ await ensureRepo();
450
+ const opts = getOpts();
451
+
452
+ try {
453
+ if (!(await hasChanges()) && !opts.all) {
454
+ console.log('No changes to commit.');
455
+ return;
456
+ }
457
+
458
+ if (opts.all) await git.add('.');
459
+
460
+ const rawDiff = await git.diff(['--cached', '--no-ext-diff']);
461
+ if (!rawDiff.trim()) { console.log('No staged changes to commit.'); return; }
462
+
463
+ const msg = await generateCommitMessage(rawDiff, opts);
464
+
465
+ if (opts.dryRun) {
466
+ console.log(color.yellow('[dry-run] Would commit & push with message:'));
467
+ console.log(msg);
468
+ return;
469
+ }
470
+
471
+ if (opts.amend) {
472
+ await git.raw(['commit', '--amend', '-m', msg]);
473
+ } else {
474
+ await git.commit(msg);
475
+ }
476
+
477
+ try {
478
+ await git.push();
479
+ console.log(`Committed & pushed: "${msg}"`);
480
+ } catch (pushErr) {
481
+ const msgErr = pushErr && pushErr.message ? pushErr.message : String(pushErr);
482
+ if (/no upstream|set the remote as upstream|have no upstream/.test(msgErr)) {
483
+ // Try to set upstream if requested
484
+ if (opts.setUpstream) {
485
+ const branch = (await git.raw(['rev-parse', '--abbrev-ref', 'HEAD'])).trim();
486
+ await git.push(['--set-upstream', 'origin', branch]);
487
+ console.log(`Committed & pushed (upstream set to origin/${branch}): "${msg}"`);
488
+ } else {
489
+ console.log(color.yellow('Current branch has no upstream. Use --set-upstream to set origin/<branch> automatically.'));
490
+ }
491
+ } else {
492
+ throw pushErr;
493
+ }
494
+ }
495
+ } catch (e) {
496
+ handleError('Online commit error', e);
238
497
  }
498
+ });
239
499
 
240
- const { all } = await safeLog();
241
- const isFirst = all.length === 0;
242
-
243
- // Always add changes first
244
- await git.add('.');
245
-
246
- // Get the appropriate diff
247
- const rawDiff = await git.diff(['--cached']);
248
-
249
- if (!rawDiff.trim()) {
250
- return console.log('No changes to commit.');
500
+ program.command('commit <message...>').alias('m')
501
+ .description('Commit with a custom message (no AI)')
502
+ .action(async (messageParts) => {
503
+ await ensureRepo();
504
+ const opts = getOpts();
505
+
506
+ try {
507
+ const msg = (messageParts || []).join(' ').trim();
508
+ if (!msg) { console.log('Provide a commit message.'); return; }
509
+
510
+ if (!(await hasChanges()) && !opts.all) {
511
+ console.log('No changes to commit.');
512
+ return;
513
+ }
514
+
515
+ if (opts.all) await git.add('.');
516
+
517
+ const rawDiff = await git.diff(['--cached', '--no-ext-diff']);
518
+ if (!rawDiff.trim()) { console.log('No staged changes to commit.'); return; }
519
+
520
+ if (opts.dryRun) {
521
+ console.log(color.yellow('[dry-run] Would commit with custom message:'));
522
+ console.log(msg);
523
+ return;
524
+ }
525
+
526
+ if (opts.amend) {
527
+ await git.raw(['commit', '--amend', '-m', msg]);
528
+ } else {
529
+ await git.commit(msg);
530
+ }
531
+ console.log(`Committed locally: "${msg}"`);
532
+ } catch (e) {
533
+ handleError('Commit error', e);
251
534
  }
252
-
253
- const msg = await generateCommitMessage(rawDiff);
254
- await git.commit(msg);
255
- await git.push();
256
- console.log(`Committed & pushed: "${msg}"`);
257
535
  });
258
536
 
259
537
  program.command('pull').alias('p')
260
538
  .description('Pull latest changes')
261
539
  .action(async () => {
540
+ await ensureRepo();
262
541
  try { await git.pull(); console.log('Pulled latest.'); }
263
- catch (e) { console.error('Pull error:', e.message); }
542
+ catch (e) { handleError('Pull error', e); }
264
543
  });
265
544
 
266
545
  program.command('list').alias('ls')
267
546
  .description('Short numbered git log (oldest β†’ newest)')
268
547
  .action(async () => {
269
- const { all } = await safeLog();
270
- all.reverse().forEach((c, i) => console.log(`${i+1}. ${c.hash.slice(0,7)} ${c.message}`));
548
+ await ensureRepo();
549
+ try {
550
+ const { all } = await safeLog();
551
+ [...all].reverse().forEach((c, i) => console.log(`${i+1}. ${c.hash.slice(0,7)} ${c.message}`));
552
+ } catch (e) { handleError('List error', e); }
271
553
  });
272
554
 
273
555
  program.command('largelist').alias('ll')
274
556
  .description('Full numbered git log (oldest β†’ newest)')
275
557
  .action(async () => {
276
- const { all } = await safeLog();
277
- all.reverse().forEach((c, i) => {
278
- const date = new Date(c.date).toLocaleString();
279
- console.log(`${i+1}. ${c.hash.slice(0,7)} | ${date} | ${c.author_name} β†’ ${c.message}`);
280
- });
558
+ await ensureRepo();
559
+ try {
560
+ const { all } = await safeLog();
561
+ [...all].reverse().forEach((c, i) => {
562
+ const date = new Date(c.date).toLocaleString();
563
+ console.log(`${i+1}. ${c.hash.slice(0,7)} | ${date} | ${c.author_name} β†’ ${c.message}`);
564
+ });
565
+ } catch (e) { handleError('Largelist error', e); }
281
566
  });
282
567
 
283
568
  program.command('branch <c> [name]').alias('b')
284
569
  .description('Branch from commit/index')
285
570
  .action(async (c, name) => {
571
+ await ensureRepo();
286
572
  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}`); }
287
- catch (e) { console.error('Branch error:', e.message); }
573
+ catch (e) { handleError('Branch error', e); }
288
574
  });
289
575
 
290
576
  program.command('reset <c>').alias('r')
291
577
  .description('Reset branch to commit/index')
292
578
  .option('--hard','hard reset')
293
- .action(async (c, opts) => {
294
- try { const sha = await resolveCommit(c); const mode = opts.hard? '--hard':'--soft'; await git.raw(['reset', mode, sha]); console.log(`Reset (${mode}) to ${sha}`); }
295
- catch (e) { console.error('Reset error:', e.message); }
579
+ .action(async (c, optsCmd) => {
580
+ await ensureRepo();
581
+ try {
582
+ const sha = await resolveCommit(c);
583
+ const mode = optsCmd.hard? '--hard':'--soft';
584
+ const opts = getOpts();
585
+ if (!opts.yes) {
586
+ console.log(color.yellow(`About to run: git reset ${mode} ${sha}. Use --yes to confirm.`));
587
+ process.exit(1);
588
+ }
589
+ await git.raw(['reset', mode, sha]);
590
+ console.log(`Reset (${mode}) to ${sha}`);
591
+ }
592
+ catch (e) { handleError('Reset error', e); }
296
593
  });
297
594
 
298
595
  program.command('revert <c>').alias('rv')
299
596
  .description('Revert commit/index safely')
300
597
  .action(async (c) => {
301
- try { const sha = await resolveCommit(c); await git.revert(sha); console.log(`Reverted ${sha}`); }
302
- catch (e) { console.error('Revert error:', e.message); }
598
+ await ensureRepo();
599
+ try {
600
+ const sha = await resolveCommit(c);
601
+ const opts = getOpts();
602
+ if (!opts.yes) {
603
+ console.log(color.yellow(`About to run: git revert ${sha}. Use --yes to confirm.`));
604
+ process.exit(1);
605
+ }
606
+ await git.revert(sha);
607
+ console.log(`Reverted ${sha}`);
608
+ }
609
+ catch (e) { handleError('Revert error', e); }
610
+ });
611
+
612
+ program.command('undo').alias('u')
613
+ .description('Undo last commit (soft reset to HEAD~1)')
614
+ .option('--hard', 'Hard reset instead (destructive)')
615
+ .action(async (cmd) => {
616
+ await ensureRepo();
617
+ try {
618
+ const mode = cmd.hard ? '--hard' : '--soft';
619
+ const opts = getOpts();
620
+ if (!opts.yes) {
621
+ console.log(color.yellow(`About to run: git reset ${mode} HEAD~1. Use --yes to confirm.`));
622
+ process.exit(1);
623
+ }
624
+ await git.raw(['reset', mode, 'HEAD~1']);
625
+ console.log(`Reset (${mode}) to HEAD~1`);
626
+ } catch (e) { handleError('Undo error', e); }
303
627
  });
304
628
 
305
- program.parse(process.argv);
629
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gims",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Git Made Simple – AI‑powered git helper using Gemini / OpenAI",
5
5
  "author": "S41R4J",
6
6
  "license": "MIT",