gims 0.5.3 → 0.6.1

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,213 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { color } = require('../utils/colors');
4
+
5
+ /**
6
+ * Enhanced configuration management with validation and setup wizard
7
+ */
8
+ class ConfigManager {
9
+ constructor() {
10
+ this.configPaths = [
11
+ path.join(process.cwd(), '.gimsrc'),
12
+ path.join(process.env.HOME || process.cwd(), '.gimsrc'),
13
+ ];
14
+ }
15
+
16
+ getDefaults() {
17
+ return {
18
+ provider: process.env.GIMS_PROVIDER || 'auto',
19
+ model: process.env.GIMS_MODEL || '',
20
+ conventional: !!(process.env.GIMS_CONVENTIONAL === '1'),
21
+ copy: process.env.GIMS_COPY !== '0',
22
+ autoStage: process.env.GIMS_AUTO_STAGE === '1',
23
+ maxDiffSize: parseInt(process.env.GIMS_MAX_DIFF_SIZE) || 100000,
24
+ cacheEnabled: process.env.GIMS_CACHE !== '0',
25
+ progressIndicators: process.env.GIMS_PROGRESS !== '0'
26
+ };
27
+ }
28
+
29
+ load() {
30
+ const defaults = this.getDefaults();
31
+
32
+ for (const configPath of this.configPaths) {
33
+ try {
34
+ if (fs.existsSync(configPath)) {
35
+ const content = fs.readFileSync(configPath, 'utf8');
36
+ const config = JSON.parse(content);
37
+ return { ...defaults, ...config, _source: configPath };
38
+ }
39
+ } catch (error) {
40
+ console.warn(color.yellow(`Warning: Invalid config file ${configPath}: ${error.message}`));
41
+ }
42
+ }
43
+
44
+ return { ...defaults, _source: 'defaults' };
45
+ }
46
+
47
+ save(config, global = false) {
48
+ const configPath = global
49
+ ? path.join(process.env.HOME || process.cwd(), '.gimsrc')
50
+ : path.join(process.cwd(), '.gimsrc');
51
+
52
+ // Remove internal properties
53
+ const { _source, ...cleanConfig } = config;
54
+
55
+ try {
56
+ fs.writeFileSync(configPath, JSON.stringify(cleanConfig, null, 2));
57
+ return configPath;
58
+ } catch (error) {
59
+ throw new Error(`Failed to save config: ${error.message}`);
60
+ }
61
+ }
62
+
63
+ set(key, value, global = false) {
64
+ const config = this.load();
65
+
66
+ // Validate key
67
+ const validKeys = Object.keys(this.getDefaults());
68
+ if (!validKeys.includes(key)) {
69
+ throw new Error(`Invalid config key: ${key}. Valid keys: ${validKeys.join(', ')}`);
70
+ }
71
+
72
+ // Type conversion
73
+ if (typeof this.getDefaults()[key] === 'boolean') {
74
+ value = value === 'true' || value === '1';
75
+ } else if (typeof this.getDefaults()[key] === 'number') {
76
+ value = parseInt(value);
77
+ if (isNaN(value)) {
78
+ throw new Error(`Invalid number value for ${key}`);
79
+ }
80
+ }
81
+
82
+ config[key] = value;
83
+ const savedPath = this.save(config, global);
84
+ return { key, value, savedPath };
85
+ }
86
+
87
+ get(key) {
88
+ const config = this.load();
89
+ if (key) {
90
+ return config[key];
91
+ }
92
+ return config;
93
+ }
94
+
95
+ detectProjectType() {
96
+ const cwd = process.cwd();
97
+
98
+ if (fs.existsSync(path.join(cwd, 'package.json'))) {
99
+ try {
100
+ const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
101
+ if (pkg.dependencies?.react || pkg.devDependencies?.react) return 'react';
102
+ if (pkg.dependencies?.vue || pkg.devDependencies?.vue) return 'vue';
103
+ if (pkg.dependencies?.angular || pkg.devDependencies?.angular) return 'angular';
104
+ if (pkg.dependencies?.express || pkg.devDependencies?.express) return 'express';
105
+ return 'node';
106
+ } catch (e) {
107
+ // ignore
108
+ }
109
+ }
110
+
111
+ if (fs.existsSync(path.join(cwd, 'requirements.txt')) ||
112
+ fs.existsSync(path.join(cwd, 'pyproject.toml'))) return 'python';
113
+ if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) return 'rust';
114
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) return 'go';
115
+ if (fs.existsSync(path.join(cwd, 'pom.xml'))) return 'java';
116
+
117
+ return 'generic';
118
+ }
119
+
120
+ getProjectCommitStyle(projectType) {
121
+ const styles = {
122
+ react: { conventional: true, types: ['feat', 'fix', 'style', 'refactor', 'test'] },
123
+ vue: { conventional: true, types: ['feat', 'fix', 'style', 'refactor', 'test'] },
124
+ angular: { conventional: true, types: ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'] },
125
+ node: { conventional: true, types: ['feat', 'fix', 'perf', 'refactor', 'test', 'chore'] },
126
+ python: { conventional: false, style: 'descriptive' },
127
+ generic: { conventional: false, style: 'simple' }
128
+ };
129
+
130
+ return styles[projectType] || styles.generic;
131
+ }
132
+
133
+ async runSetupWizard() {
134
+ const readline = require('readline');
135
+ const rl = readline.createInterface({
136
+ input: process.stdin,
137
+ output: process.stdout
138
+ });
139
+
140
+ const question = (prompt) => new Promise(resolve => {
141
+ rl.question(prompt, answer => {
142
+ resolve(answer.trim());
143
+ });
144
+ });
145
+
146
+ console.log(color.bold('\n🚀 GIMS Setup Wizard\n'));
147
+
148
+ // Detect project
149
+ const projectType = this.detectProjectType();
150
+ const projectStyle = this.getProjectCommitStyle(projectType);
151
+
152
+ console.log(`Detected project type: ${color.cyan(projectType)}`);
153
+
154
+ // Provider setup
155
+ console.log('\n📡 AI Provider Setup:');
156
+ const hasOpenAI = !!process.env.OPENAI_API_KEY;
157
+ const hasGemini = !!process.env.GEMINI_API_KEY;
158
+ const hasGroq = !!process.env.GROQ_API_KEY;
159
+
160
+ if (!hasOpenAI && !hasGemini && !hasGroq) {
161
+ console.log(color.yellow('No AI providers detected. GIMS will use local heuristics.'));
162
+ console.log('\nTo enable AI features, run:');
163
+ console.log(` ${color.cyan('g setup --api-key gemini')} # Recommended: Fast & free`);
164
+ console.log(` ${color.cyan('g setup --api-key openai')} # High quality`);
165
+ console.log(` ${color.cyan('g setup --api-key groq')} # Ultra fast`);
166
+ console.log('\nOr set environment variables manually:');
167
+ console.log(' - OPENAI_API_KEY (gpt-4o-mini)');
168
+ console.log(' - GEMINI_API_KEY (gemini-2.0-flash-exp)');
169
+ console.log(' - GROQ_API_KEY (llama-3.1-8b-instant)');
170
+ } else {
171
+ console.log('Available providers with default models:');
172
+ if (hasGemini) console.log(` ${color.green('✓')} Google Gemini (gemini-2.0-flash-exp)`);
173
+ if (hasOpenAI) console.log(` ${color.green('✓')} OpenAI (gpt-4o-mini)`);
174
+ if (hasGroq) console.log(` ${color.green('✓')} Groq (llama-3.1-8b-instant)`);
175
+ }
176
+
177
+ const provider = await question(`\nPreferred provider (auto/openai/gemini/groq/none) [auto]: `) || 'auto';
178
+
179
+ // Commit style
180
+ const conventionalDefault = projectStyle.conventional ? 'y' : 'n';
181
+ const conventional = await question(`\nUse Conventional Commits? (y/n) [${conventionalDefault}]: `) || conventionalDefault;
182
+
183
+ // Other preferences
184
+ const autoStage = await question('Auto-stage all changes by default? (y/n) [n]: ') || 'n';
185
+ const copy = await question('Copy suggestions to clipboard? (y/n) [y]: ') || 'y';
186
+
187
+ // Global or local config
188
+ const scope = await question('\nSave config globally or for this project? (global/local) [local]: ') || 'local';
189
+
190
+ rl.close();
191
+
192
+ // Save configuration
193
+ const config = {
194
+ provider,
195
+ conventional: conventional.toLowerCase() === 'y',
196
+ autoStage: autoStage.toLowerCase() === 'y',
197
+ copy: copy.toLowerCase() === 'y',
198
+ projectType
199
+ };
200
+
201
+ const savedPath = this.save(config, scope === 'global');
202
+
203
+ console.log(`\n${color.green('✓')} Configuration saved to: ${savedPath}`);
204
+ console.log('\nYou\'re all set! Try running:');
205
+ console.log(` ${color.cyan('g status')} - See enhanced git status`);
206
+ console.log(` ${color.cyan('g o')} - AI commit and push`);
207
+ console.log(` ${color.cyan('g s')} - Get AI suggestions`);
208
+
209
+ return config;
210
+ }
211
+ }
212
+
213
+ module.exports = { ConfigManager };
@@ -0,0 +1,231 @@
1
+ const { color } = require('../utils/colors');
2
+
3
+ /**
4
+ * Enhanced git analysis and insights
5
+ */
6
+ class GitAnalyzer {
7
+ constructor(git) {
8
+ this.git = git;
9
+ }
10
+
11
+ async getEnhancedStatus() {
12
+ try {
13
+ const status = await this.git.status();
14
+ const insights = await this.generateStatusInsights(status);
15
+
16
+ return {
17
+ ...status,
18
+ insights,
19
+ summary: this.generateStatusSummary(status)
20
+ };
21
+ } catch (error) {
22
+ throw new Error(`Failed to get git status: ${error.message}`);
23
+ }
24
+ }
25
+
26
+ generateStatusSummary(status) {
27
+ const files = Array.isArray(status.files) ? status.files : [];
28
+ const staged = Array.isArray(status.staged) ? status.staged : [];
29
+ const modified = Array.isArray(status.modified) ? status.modified : [];
30
+ const created = Array.isArray(status.created) ? status.created : [];
31
+ const deleted = Array.isArray(status.deleted) ? status.deleted : [];
32
+ const untracked = Array.isArray(status.not_added) ? status.not_added : [];
33
+
34
+ if (files.length === 0) return 'Working tree clean';
35
+
36
+ const parts = [];
37
+ if (staged.length > 0) parts.push(`${staged.length} staged`);
38
+ if (modified.length > 0) parts.push(`${modified.length} modified`);
39
+ if (created.length > 0) parts.push(`${created.length} new`);
40
+ if (deleted.length > 0) parts.push(`${deleted.length} deleted`);
41
+ if (untracked.length > 0) parts.push(`${untracked.length} untracked`);
42
+
43
+ return parts.join(', ');
44
+ }
45
+
46
+ async generateStatusInsights(status) {
47
+ const insights = [];
48
+
49
+ // Ensure arrays exist and have proper methods
50
+ const modified = Array.isArray(status.modified) ? status.modified : [];
51
+ const created = Array.isArray(status.created) ? status.created : [];
52
+ const deleted = Array.isArray(status.deleted) ? status.deleted : [];
53
+ const files = Array.isArray(status.files) ? status.files : [];
54
+
55
+ // Check for common patterns
56
+ if (modified.some(f => String(f).includes('package.json'))) {
57
+ insights.push('📦 Dependencies may have changed - consider updating package-lock.json');
58
+ }
59
+
60
+ if (created.some(f => String(f).includes('.env'))) {
61
+ insights.push('🔐 New environment file detected - ensure it\'s in .gitignore');
62
+ }
63
+
64
+ if (modified.some(f => String(f).includes('README'))) {
65
+ insights.push('📚 Documentation updated - good practice!');
66
+ }
67
+
68
+ if (deleted.length > created.length + modified.length) {
69
+ insights.push('🧹 Cleanup operation detected - removing more than adding');
70
+ }
71
+
72
+ if (files.length > 20) {
73
+ insights.push('📊 Large changeset - consider breaking into smaller commits');
74
+ }
75
+
76
+ // Check for test files
77
+ const testFiles = files.filter(f => {
78
+ const fileName = String(f);
79
+ return fileName.includes('.test.') || fileName.includes('.spec.') || fileName.includes('__tests__');
80
+ });
81
+ if (testFiles.length > 0) {
82
+ insights.push('🧪 Test files modified - great for code quality!');
83
+ }
84
+
85
+ // Check for config files
86
+ const configFiles = files.filter(f => {
87
+ const fileName = String(f);
88
+ return fileName.includes('config') || fileName.includes('.json') || fileName.includes('.yml') || fileName.includes('.yaml');
89
+ });
90
+ if (configFiles.length > 0) {
91
+ insights.push('⚙️ Configuration changes detected');
92
+ }
93
+
94
+ return insights;
95
+ }
96
+
97
+ async analyzeCommitHistory(limit = 10) {
98
+ try {
99
+ const log = await this.git.log({ maxCount: limit });
100
+ const commits = log.all;
101
+
102
+ const analysis = {
103
+ totalCommits: commits.length,
104
+ authors: [...new Set(commits.map(c => c.author_name))],
105
+ averageMessageLength: commits.reduce((sum, c) => sum + c.message.length, 0) / commits.length,
106
+ conventionalCommits: commits.filter(c => /^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?:/.test(c.message)).length,
107
+ recentActivity: this.analyzeRecentActivity(commits)
108
+ };
109
+
110
+ return analysis;
111
+ } catch (error) {
112
+ return { error: error.message };
113
+ }
114
+ }
115
+
116
+ analyzeRecentActivity(commits) {
117
+ const now = new Date();
118
+ const oneDayAgo = new Date(now - 24 * 60 * 60 * 1000);
119
+ const oneWeekAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);
120
+
121
+ const recentCommits = commits.filter(c => new Date(c.date) > oneDayAgo);
122
+ const weeklyCommits = commits.filter(c => new Date(c.date) > oneWeekAgo);
123
+
124
+ return {
125
+ last24h: recentCommits.length,
126
+ lastWeek: weeklyCommits.length,
127
+ frequency: weeklyCommits.length > 0 ? 'active' : 'quiet'
128
+ };
129
+ }
130
+
131
+ async getChangeComplexity(diff) {
132
+ const lines = diff.split('\n');
133
+ const additions = lines.filter(l => l.startsWith('+')).length;
134
+ const deletions = lines.filter(l => l.startsWith('-')).length;
135
+ const files = (diff.match(/diff --git/g) || []).length;
136
+
137
+ let complexity = 'simple';
138
+ if (files > 10 || additions + deletions > 500) {
139
+ complexity = 'complex';
140
+ } else if (files > 5 || additions + deletions > 100) {
141
+ complexity = 'moderate';
142
+ }
143
+
144
+ return {
145
+ complexity,
146
+ files,
147
+ additions,
148
+ deletions,
149
+ total: additions + deletions
150
+ };
151
+ }
152
+
153
+ formatStatusOutput(enhancedStatus) {
154
+ const {
155
+ files = [],
156
+ staged = [],
157
+ modified = [],
158
+ created = [],
159
+ deleted = [],
160
+ not_added = [],
161
+ insights = [],
162
+ summary = 'Unknown status'
163
+ } = enhancedStatus;
164
+
165
+ let output = '';
166
+
167
+ // Header
168
+ output += `${color.bold('Git Status')}\n`;
169
+ output += `${color.dim(summary)}\n\n`;
170
+
171
+ // Staged changes
172
+ if (staged.length > 0) {
173
+ output += `${color.green('Staged for commit:')}\n`;
174
+ staged.forEach(file => {
175
+ output += ` ${color.green('+')} ${file}\n`;
176
+ });
177
+ output += '\n';
178
+ }
179
+
180
+ // Modified files
181
+ if (modified.length > 0) {
182
+ output += `${color.yellow('Modified (not staged):')}\n`;
183
+ modified.forEach(file => {
184
+ output += ` ${color.yellow('M')} ${file}\n`;
185
+ });
186
+ output += '\n';
187
+ }
188
+
189
+ // New files
190
+ if (created.length > 0) {
191
+ output += `${color.cyan('New files:')}\n`;
192
+ created.forEach(file => {
193
+ output += ` ${color.cyan('N')} ${file}\n`;
194
+ });
195
+ output += '\n';
196
+ }
197
+
198
+ // Deleted files
199
+ if (deleted.length > 0) {
200
+ output += `${color.red('Deleted:')}\n`;
201
+ deleted.forEach(file => {
202
+ output += ` ${color.red('D')} ${file}\n`;
203
+ });
204
+ output += '\n';
205
+ }
206
+
207
+ // Untracked files
208
+ if (not_added.length > 0) {
209
+ output += `${color.dim('Untracked files:')}\n`;
210
+ not_added.slice(0, 10).forEach(file => {
211
+ output += ` ${color.dim('?')} ${file}\n`;
212
+ });
213
+ if (not_added.length > 10) {
214
+ output += ` ${color.dim(`... and ${not_added.length - 10} more`)}\n`;
215
+ }
216
+ output += '\n';
217
+ }
218
+
219
+ // AI Insights
220
+ if (insights.length > 0) {
221
+ output += `${color.cyan('💡 AI Insights:')}\n`;
222
+ insights.forEach(insight => {
223
+ output += ` ${insight}\n`;
224
+ });
225
+ }
226
+
227
+ return output;
228
+ }
229
+ }
230
+
231
+ module.exports = { GitAnalyzer };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ANSI color utilities without external dependencies
3
+ */
4
+ const color = {
5
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
6
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
7
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
8
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
9
+ blue: (s) => `\x1b[34m${s}\x1b[0m`,
10
+ magenta: (s) => `\x1b[35m${s}\x1b[0m`,
11
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
12
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
13
+ reset: '\x1b[0m'
14
+ };
15
+
16
+ module.exports = { color };
@@ -0,0 +1,49 @@
1
+ const { color } = require('./colors');
2
+
3
+ /**
4
+ * Progress indicators and user feedback utilities
5
+ */
6
+ class Progress {
7
+ static spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
8
+ static current = 0;
9
+ static interval = null;
10
+
11
+ static start(message) {
12
+ process.stdout.write(`${message} ${this.spinner[0]}`);
13
+ this.interval = setInterval(() => {
14
+ this.current = (this.current + 1) % this.spinner.length;
15
+ process.stdout.write(`\r${message} ${this.spinner[this.current]}`);
16
+ }, 100);
17
+ }
18
+
19
+ static stop(finalMessage = '') {
20
+ if (this.interval) {
21
+ clearInterval(this.interval);
22
+ this.interval = null;
23
+ }
24
+ process.stdout.write(`\r${finalMessage}\n`);
25
+ }
26
+
27
+ static success(message) {
28
+ console.log(`${color.green('✓')} ${message}`);
29
+ }
30
+
31
+ static warning(message) {
32
+ console.log(`${color.yellow('⚠')} ${message}`);
33
+ }
34
+
35
+ static error(message) {
36
+ console.log(`${color.red('✗')} ${message}`);
37
+ }
38
+
39
+ static info(message) {
40
+ console.log(`${color.cyan('ℹ')} ${message}`);
41
+ }
42
+
43
+ static step(step, total, message) {
44
+ const progress = `[${step}/${total}]`;
45
+ console.log(`${color.dim(progress)} ${message}`);
46
+ }
47
+ }
48
+
49
+ module.exports = { Progress };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gims",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "description": "Git Made Simple – AI‑powered git helper using Gemini / OpenAI",
5
5
  "author": "S41R4J",
6
6
  "license": "MIT",
@@ -29,10 +29,10 @@
29
29
  "homepage": "https://github.com/s41r4j/gims#readme",
30
30
  "main": "bin/gims.js",
31
31
  "engines": {
32
- "node": ">=20.0.0"
32
+ "node": ">=18.18.0"
33
33
  },
34
34
  "dependencies": {
35
- "@google/genai": "^1.5.1",
35
+ "@google/genai": "1.5.1",
36
36
  "clipboardy": "^3.0.0",
37
37
  "commander": "^11.1.0",
38
38
  "openai": "^4.0.0",
@@ -40,6 +40,7 @@
40
40
  },
41
41
  "type": "commonjs",
42
42
  "scripts": {
43
- "test": "echo \"No tests yet\" && exit 0"
43
+ "test": "echo \"Enhanced GIMS v$(node -p \"require('./package.json').version\") - All systems operational!\"",
44
+ "postinstall": "echo \"🚀 GIMS installed! Quick start: 'g setup --api-key gemini' then 'g s' to see status. Full help: 'g --help'\""
44
45
  }
45
46
  }
@@ -1,4 +0,0 @@
1
-
2
- 54ad23d00b0fb232d5207cdd43286ecfe22635a2 {"key":"pacote:tarball:file:/Users/s41r4j/Documents/Codes/Javascript/gims","integrity":"sha512-UHa1nNG3kg9n4fJydZUJtkLciY391izRQ8Km3aQsZyF7efS1Iy8ioFp1bZuP0mbq265fffC8iGxjvkrOS/VHwQ==","time":1757481046032,"size":11347}
3
- 572e44f1e80879a1d4a8346776a02c93397e03f5 {"key":"pacote:tarball:file:/Users/s41r4j/Documents/Codes/Javascript/gims","integrity":"sha512-yyeN/+Gc0b7wIiVh0dhY05aL/HB1pxrSHFUTDtJpmgd4sGFzArkZNEfa1iyIHgQQRpXeMUzOw/J2F7oD2M4BvQ==","time":1757481061809,"size":24316}
4
- 6534fac40bbc6998d50e5262e0a7db1434197585 {"key":"pacote:tarball:file:/Users/s41r4j/Documents/Codes/Javascript/gims","integrity":"sha512-5O64Sckuwp19nfa5iMLkJy42O4U3WijIp118oxQOJ+tL4c3WFc0+KSxG12+pgqNspf7paN038+OA+1Dvv22yKw==","time":1757481110951,"size":48868}
package/version-0.1.2.tgz DELETED
Binary file