ai-cmg 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/index.js +342 -35
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# ai-cmg
|
|
2
|
+
|
|
3
|
+
An AI-powered CLI that analyzes staged changes and generates a commit message. You can commit immediately, edit in your editor, or copy the message.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g ai-cmg
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ai-cmg
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Flow:
|
|
18
|
+
- If there are staged changes, it generates a message right away.
|
|
19
|
+
- If nothing is staged, it offers `git add .` or a file picker to stage selected files.
|
|
20
|
+
- You can choose to commit, edit, or copy the generated message.
|
|
21
|
+
|
|
22
|
+
## Provide a Hint or Prompt
|
|
23
|
+
|
|
24
|
+
Use `-m` for a short hint (commit message guidance), and `-p` for a prompt (instruction).
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ai-cmg -m "Improve login error handling"
|
|
28
|
+
ai-cmg -p "Use a short title and 3 bullet points"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
The Worker URL defaults to the built-in value but can be overridden.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ai-cmg config --show
|
|
37
|
+
ai-cmg config --set https://your-worker.example.com
|
|
38
|
+
ai-cmg config --reset
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Interactive configuration is also available.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
ai-cmg config
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Version
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
ai-cmg --version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Notes
|
|
54
|
+
|
|
55
|
+
- The tool uses the **staged diff** to generate commit messages.
|
|
56
|
+
- Large diffs, binary/media files, and lockfiles are summarized to reduce token usage.
|
|
57
|
+
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,314 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync, spawnSync } from 'child_process';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
3
6
|
import prompts from 'prompts';
|
|
4
|
-
import pc from 'picocolors';
|
|
5
7
|
import clipboardy from 'clipboardy';
|
|
6
8
|
import Conf from 'conf';
|
|
7
|
-
const
|
|
9
|
+
const DEFAULT_WORKER_URL = 'https://commit.d-code.workers.dev';
|
|
8
10
|
const config = new Conf({ projectName: 'ai-cmg' });
|
|
11
|
+
const MEDIA_EXTENSIONS = new Set([
|
|
12
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.bmp', '.tiff',
|
|
13
|
+
'.mp4', '.mov', '.webm', '.mp3', '.wav', '.flac', '.ogg',
|
|
14
|
+
'.pdf', '.zip', '.tar', '.gz', '.7z', '.rar', '.woff', '.woff2', '.ttf', '.eot',
|
|
15
|
+
]);
|
|
16
|
+
const LOCK_FILES = new Set([
|
|
17
|
+
'package-lock.json',
|
|
18
|
+
'npm-shrinkwrap.json',
|
|
19
|
+
'yarn.lock',
|
|
20
|
+
'pnpm-lock.yaml',
|
|
21
|
+
'bun.lockb',
|
|
22
|
+
]);
|
|
23
|
+
const GENERATED_PREFIXES = ['dist/', 'build/', '.next/', 'coverage/', 'node_modules/'];
|
|
24
|
+
const MAX_BLOCK_LINES = 400;
|
|
25
|
+
const MAX_BLOCK_CHARS = 20000;
|
|
26
|
+
function getPackageVersion() {
|
|
27
|
+
try {
|
|
28
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
29
|
+
const currentDir = dirname(currentFile);
|
|
30
|
+
const packageJsonPath = join(currentDir, '..', 'package.json');
|
|
31
|
+
const raw = readFileSync(packageJsonPath, 'utf8');
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
return parsed.version ?? null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function getWorkerUrl() {
|
|
40
|
+
return config.get('workerUrl') ?? DEFAULT_WORKER_URL;
|
|
41
|
+
}
|
|
42
|
+
function showConfig() {
|
|
43
|
+
const workerUrl = getWorkerUrl();
|
|
44
|
+
console.log('Current configuration:');
|
|
45
|
+
console.log(`- workerUrl: ${workerUrl}`);
|
|
46
|
+
}
|
|
47
|
+
function showConfigHelp() {
|
|
48
|
+
console.log('Usage: ai-cmg config [--show | --set <url> | --reset]');
|
|
49
|
+
console.log(' --show Show current configuration');
|
|
50
|
+
console.log(' --set <url> Set Worker URL');
|
|
51
|
+
console.log(' --reset Reset Worker URL to default');
|
|
52
|
+
}
|
|
53
|
+
function setWorkerUrl(url) {
|
|
54
|
+
config.set('workerUrl', url);
|
|
55
|
+
console.log('Updated workerUrl.');
|
|
56
|
+
}
|
|
57
|
+
function resetWorkerUrl() {
|
|
58
|
+
config.delete('workerUrl');
|
|
59
|
+
console.log('Reset workerUrl to default.');
|
|
60
|
+
}
|
|
61
|
+
async function runConfigCommand(args) {
|
|
62
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
63
|
+
showConfigHelp();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (args.includes('--show')) {
|
|
67
|
+
showConfig();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const setIndex = args.indexOf('--set');
|
|
71
|
+
if (setIndex >= 0) {
|
|
72
|
+
const nextUrl = args[setIndex + 1]?.trim();
|
|
73
|
+
if (!nextUrl) {
|
|
74
|
+
console.log('Error: --set requires a URL.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
setWorkerUrl(nextUrl);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (args.includes('--reset')) {
|
|
81
|
+
resetWorkerUrl();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
showConfig();
|
|
85
|
+
const response = await prompts({
|
|
86
|
+
type: 'text',
|
|
87
|
+
name: 'workerUrl',
|
|
88
|
+
message: 'Enter Worker URL (leave empty to keep current):'
|
|
89
|
+
});
|
|
90
|
+
const nextUrl = response.workerUrl?.trim();
|
|
91
|
+
if (nextUrl) {
|
|
92
|
+
setWorkerUrl(nextUrl);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log('No changes made.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function parseMessageArgs(rawArgs) {
|
|
99
|
+
const hintParts = [];
|
|
100
|
+
let hint;
|
|
101
|
+
let prompt;
|
|
102
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
103
|
+
const arg = rawArgs[i];
|
|
104
|
+
if (arg === '-m') {
|
|
105
|
+
const value = rawArgs[i + 1];
|
|
106
|
+
if (!value) {
|
|
107
|
+
console.log('Error: -m requires a value.');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
hint = value;
|
|
111
|
+
i += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (arg === '-p') {
|
|
115
|
+
const value = rawArgs[i + 1];
|
|
116
|
+
if (!value) {
|
|
117
|
+
console.log('Error: -p requires a value.');
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
prompt = value;
|
|
121
|
+
i += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
hintParts.push(arg);
|
|
125
|
+
}
|
|
126
|
+
if (!hint && hintParts.length > 0) {
|
|
127
|
+
hint = hintParts.join(' ');
|
|
128
|
+
}
|
|
129
|
+
return { hint, prompt };
|
|
130
|
+
}
|
|
131
|
+
function getNameStatus() {
|
|
132
|
+
const map = new Map();
|
|
133
|
+
try {
|
|
134
|
+
const output = execSync('git diff --cached --name-status').toString();
|
|
135
|
+
output.split('\n').forEach((line) => {
|
|
136
|
+
if (!line.trim())
|
|
137
|
+
return;
|
|
138
|
+
const [status, ...rest] = line.split('\t');
|
|
139
|
+
const filePath = rest[rest.length - 1];
|
|
140
|
+
if (filePath)
|
|
141
|
+
map.set(filePath, status);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// ignore status errors; diff will still be used
|
|
146
|
+
}
|
|
147
|
+
return map;
|
|
148
|
+
}
|
|
149
|
+
function getChangedFiles() {
|
|
150
|
+
try {
|
|
151
|
+
const output = execSync('git status --porcelain -z').toString();
|
|
152
|
+
if (!output)
|
|
153
|
+
return [];
|
|
154
|
+
const parts = output.split('\0').filter(Boolean);
|
|
155
|
+
const entries = [];
|
|
156
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
157
|
+
const entry = parts[i];
|
|
158
|
+
const status = entry.slice(0, 2);
|
|
159
|
+
let path = entry.slice(3);
|
|
160
|
+
if (status.startsWith('R') || status.startsWith('C')) {
|
|
161
|
+
const newPath = parts[i + 1];
|
|
162
|
+
if (newPath) {
|
|
163
|
+
path = newPath;
|
|
164
|
+
i += 1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (path)
|
|
168
|
+
entries.push({ path, status: status.trim() });
|
|
169
|
+
}
|
|
170
|
+
const unique = new Map();
|
|
171
|
+
entries.forEach((entry) => unique.set(entry.path, entry));
|
|
172
|
+
return Array.from(unique.values());
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function promptStageFiles() {
|
|
179
|
+
const candidates = getChangedFiles();
|
|
180
|
+
if (candidates.length === 0) {
|
|
181
|
+
console.log('No local changes to stage.');
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
console.log('No staged changes detected.');
|
|
185
|
+
console.log('Choose how to stage changes:');
|
|
186
|
+
const options = [
|
|
187
|
+
{ label: 'Stage all files (git add .)', value: 'all' },
|
|
188
|
+
{ label: 'Stage selected files', value: 'select' },
|
|
189
|
+
{ label: 'Cancel', value: 'cancel' }
|
|
190
|
+
];
|
|
191
|
+
options.forEach((option, index) => {
|
|
192
|
+
console.log(`${index + 1}. ${option.label}`);
|
|
193
|
+
});
|
|
194
|
+
const selection = await prompts({
|
|
195
|
+
type: 'number',
|
|
196
|
+
name: 'choice',
|
|
197
|
+
message: 'Enter a number (1-3):',
|
|
198
|
+
initial: 1,
|
|
199
|
+
min: 1,
|
|
200
|
+
max: options.length
|
|
201
|
+
});
|
|
202
|
+
const choice = selection.choice;
|
|
203
|
+
if (!choice || choice < 1 || choice > options.length)
|
|
204
|
+
return false;
|
|
205
|
+
const action = options[choice - 1].value;
|
|
206
|
+
if (action === 'cancel')
|
|
207
|
+
return false;
|
|
208
|
+
if (action === 'all') {
|
|
209
|
+
spawnSync('git', ['add', '.'], { stdio: 'inherit' });
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
const fileChoices = candidates.map((entry) => ({
|
|
213
|
+
title: `${entry.status.padEnd(2, ' ')} ${entry.path}`,
|
|
214
|
+
value: entry.path
|
|
215
|
+
}));
|
|
216
|
+
const picked = await prompts({
|
|
217
|
+
type: 'multiselect',
|
|
218
|
+
name: 'files',
|
|
219
|
+
message: 'Select files to stage (space to toggle, enter to submit):',
|
|
220
|
+
choices: fileChoices,
|
|
221
|
+
min: 1
|
|
222
|
+
});
|
|
223
|
+
const files = picked.files;
|
|
224
|
+
if (!files || files.length === 0)
|
|
225
|
+
return false;
|
|
226
|
+
spawnSync('git', ['add', '--', ...files], { stdio: 'inherit' });
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
function getExtension(filePath) {
|
|
230
|
+
const lower = filePath.toLowerCase();
|
|
231
|
+
const idx = lower.lastIndexOf('.');
|
|
232
|
+
return idx >= 0 ? lower.slice(idx) : '';
|
|
233
|
+
}
|
|
234
|
+
function shouldOmitFile(path, block) {
|
|
235
|
+
const lowerPath = path.toLowerCase();
|
|
236
|
+
if (LOCK_FILES.has(lowerPath))
|
|
237
|
+
return { omit: true, reason: 'lockfile' };
|
|
238
|
+
if (MEDIA_EXTENSIONS.has(getExtension(lowerPath)))
|
|
239
|
+
return { omit: true, reason: 'media/binary asset' };
|
|
240
|
+
if (GENERATED_PREFIXES.some((prefix) => lowerPath.startsWith(prefix))) {
|
|
241
|
+
return { omit: true, reason: 'generated artifact' };
|
|
242
|
+
}
|
|
243
|
+
if (block.includes('GIT binary patch') || block.includes('Binary files')) {
|
|
244
|
+
return { omit: true, reason: 'binary diff' };
|
|
245
|
+
}
|
|
246
|
+
const lines = block.split('\n').length;
|
|
247
|
+
if (lines > MAX_BLOCK_LINES || block.length > MAX_BLOCK_CHARS) {
|
|
248
|
+
return { omit: true, reason: 'large diff' };
|
|
249
|
+
}
|
|
250
|
+
return { omit: false };
|
|
251
|
+
}
|
|
252
|
+
function summarizeDiff(diff) {
|
|
253
|
+
const statusMap = getNameStatus();
|
|
254
|
+
const blocks = diff.split(/^diff --git /m);
|
|
255
|
+
const keptBlocks = [];
|
|
256
|
+
const omitted = [];
|
|
257
|
+
for (const block of blocks) {
|
|
258
|
+
if (!block.trim())
|
|
259
|
+
continue;
|
|
260
|
+
const headerLine = block.split('\n')[0] ?? '';
|
|
261
|
+
const match = headerLine.match(/^a\/(.+?) b\/(.+?)$/);
|
|
262
|
+
if (!match) {
|
|
263
|
+
keptBlocks.push(`diff --git ${block}`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const aPath = match[1];
|
|
267
|
+
const bPath = match[2];
|
|
268
|
+
const filePath = bPath === '/dev/null' ? aPath : bPath;
|
|
269
|
+
const status = statusMap.get(filePath);
|
|
270
|
+
const originalBlock = `diff --git ${block}`;
|
|
271
|
+
const { omit, reason } = shouldOmitFile(filePath, originalBlock);
|
|
272
|
+
if (omit && reason) {
|
|
273
|
+
omitted.push({ path: filePath, reason, status });
|
|
274
|
+
keptBlocks.push(`diff --git a/${aPath} b/${bPath}\n# content omitted (${reason})\n`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
keptBlocks.push(originalBlock);
|
|
278
|
+
}
|
|
279
|
+
const summaryLines = omitted.map((item) => {
|
|
280
|
+
const statusLabel = item.status ? `${item.status} ` : '';
|
|
281
|
+
return `- ${statusLabel}${item.path} (${item.reason})`;
|
|
282
|
+
});
|
|
283
|
+
const summaryHeader = summaryLines.length
|
|
284
|
+
? `# Omitted file contents (summarized)\n${summaryLines.join('\n')}\n\n`
|
|
285
|
+
: '';
|
|
286
|
+
return `${summaryHeader}${keptBlocks.join('\n')}`.trim();
|
|
287
|
+
}
|
|
9
288
|
async function main() {
|
|
10
289
|
// 1. 사용자 힌트(Arguments) 가져오기
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
|
|
290
|
+
const rawArgs = process.argv.slice(2);
|
|
291
|
+
if (rawArgs.includes('--version') || rawArgs.includes('-v')) {
|
|
292
|
+
const version = getPackageVersion();
|
|
293
|
+
console.log(version ?? 'unknown');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (rawArgs[0] === 'config') {
|
|
297
|
+
await runConfigCommand(rawArgs.slice(1));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const parsed = parseMessageArgs(rawArgs.filter((arg) => arg !== '--version' && arg !== '-v'));
|
|
301
|
+
if (!parsed)
|
|
302
|
+
return;
|
|
303
|
+
const { hint, prompt } = parsed;
|
|
304
|
+
if (hint) {
|
|
305
|
+
console.log(`Hint detected: "${hint}"`);
|
|
14
306
|
}
|
|
15
307
|
// 2. 인증 토큰 확인 (없으면 물어봄)
|
|
16
308
|
let authToken = config.get('authToken');
|
|
17
309
|
if (!authToken) {
|
|
18
|
-
console.log(
|
|
19
|
-
console.log(
|
|
310
|
+
console.log('Initial authentication setup');
|
|
311
|
+
console.log('Enter the shared access token. It will be stored locally.');
|
|
20
312
|
const response = await prompts({
|
|
21
313
|
type: 'password',
|
|
22
314
|
name: 'token',
|
|
@@ -27,11 +319,11 @@ async function main() {
|
|
|
27
319
|
return;
|
|
28
320
|
authToken = response.token;
|
|
29
321
|
config.set('authToken', authToken);
|
|
30
|
-
console.log(
|
|
322
|
+
console.log('Token saved.\n');
|
|
31
323
|
}
|
|
32
324
|
if (!authToken)
|
|
33
325
|
return;
|
|
34
|
-
console.log(
|
|
326
|
+
console.log('Analyzing staged changes...');
|
|
35
327
|
try {
|
|
36
328
|
// 3. Git Diff 가져오기
|
|
37
329
|
let diff;
|
|
@@ -39,26 +331,33 @@ async function main() {
|
|
|
39
331
|
diff = execSync('git diff --cached').toString();
|
|
40
332
|
}
|
|
41
333
|
catch (e) {
|
|
42
|
-
console.log(
|
|
334
|
+
console.log('Error: not a git repository or git is unavailable.');
|
|
43
335
|
return;
|
|
44
336
|
}
|
|
45
337
|
if (!diff.trim()) {
|
|
46
|
-
|
|
47
|
-
|
|
338
|
+
const staged = await promptStageFiles();
|
|
339
|
+
if (!staged)
|
|
340
|
+
return;
|
|
341
|
+
diff = execSync('git diff --cached').toString();
|
|
342
|
+
if (!diff.trim()) {
|
|
343
|
+
console.log('No staged changes. Nothing to commit.');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
48
346
|
}
|
|
347
|
+
const filteredDiff = summarizeDiff(diff);
|
|
49
348
|
// 4. API 요청 (Diff + Hint + Auth)
|
|
50
|
-
const response = await fetch(
|
|
349
|
+
const response = await fetch(getWorkerUrl(), {
|
|
51
350
|
method: 'POST',
|
|
52
351
|
headers: {
|
|
53
352
|
'Content-Type': 'application/json',
|
|
54
353
|
'X-AUTH-TOKEN': authToken
|
|
55
354
|
},
|
|
56
|
-
body: JSON.stringify({ diff, hint
|
|
355
|
+
body: JSON.stringify({ diff: filteredDiff, hint, prompt }) // 힌트/프롬프트도 같이 전송
|
|
57
356
|
});
|
|
58
357
|
// 인증 실패 처리 (401)
|
|
59
358
|
if (response.status === 401) {
|
|
60
|
-
console.log(
|
|
61
|
-
console.log(
|
|
359
|
+
console.log('\nAuthentication failed. The token is invalid or expired.');
|
|
360
|
+
console.log('Stored token will be cleared. Run again to enter a new token.');
|
|
62
361
|
config.delete('authToken');
|
|
63
362
|
return;
|
|
64
363
|
}
|
|
@@ -67,44 +366,52 @@ async function main() {
|
|
|
67
366
|
}
|
|
68
367
|
const { message } = await response.json();
|
|
69
368
|
// 5. 결과 출력 및 액션 선택
|
|
70
|
-
console.log('\
|
|
71
|
-
console.log(
|
|
72
|
-
console.log('\n
|
|
369
|
+
console.log('\nCommit message:\n');
|
|
370
|
+
console.log(message);
|
|
371
|
+
console.log('\n-----------------------------------\n');
|
|
372
|
+
const actions = [
|
|
373
|
+
{ label: 'Commit with this message', value: 'commit' },
|
|
374
|
+
{ label: 'Edit in editor and commit', value: 'edit' },
|
|
375
|
+
{ label: 'Copy to clipboard', value: 'copy' },
|
|
376
|
+
{ label: 'Cancel', value: 'cancel' }
|
|
377
|
+
];
|
|
378
|
+
console.log('Select an action:');
|
|
379
|
+
actions.forEach((action, index) => {
|
|
380
|
+
console.log(`${index + 1}. ${action.label}`);
|
|
381
|
+
});
|
|
73
382
|
const result = await prompts({
|
|
74
|
-
type: '
|
|
75
|
-
name: '
|
|
76
|
-
message: '
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{ title: '📋 클립보드에 복사', value: 'copy' },
|
|
81
|
-
{ title: '❌ 취소', value: 'cancel' }
|
|
82
|
-
]
|
|
383
|
+
type: 'number',
|
|
384
|
+
name: 'choice',
|
|
385
|
+
message: 'Enter a number (1-4):',
|
|
386
|
+
initial: 1,
|
|
387
|
+
min: 1,
|
|
388
|
+
max: actions.length
|
|
83
389
|
});
|
|
84
|
-
const
|
|
85
|
-
if (!
|
|
390
|
+
const choice = result.choice;
|
|
391
|
+
if (!choice || choice < 1 || choice > actions.length)
|
|
86
392
|
return;
|
|
393
|
+
const action = actions[choice - 1].value;
|
|
87
394
|
switch (action) {
|
|
88
395
|
case 'commit':
|
|
89
396
|
spawnSync('git', ['commit', '-m', message], { stdio: 'inherit' });
|
|
90
|
-
console.log(
|
|
397
|
+
console.log('Commit complete.');
|
|
91
398
|
break;
|
|
92
399
|
case 'edit':
|
|
93
400
|
spawnSync('git', ['commit', '-e', '-m', message], { stdio: 'inherit' });
|
|
94
|
-
console.log(
|
|
401
|
+
console.log('Commit complete.');
|
|
95
402
|
break;
|
|
96
403
|
case 'copy':
|
|
97
404
|
clipboardy.writeSync(message);
|
|
98
|
-
console.log(
|
|
405
|
+
console.log('Copied to clipboard.');
|
|
99
406
|
break;
|
|
100
407
|
case 'cancel':
|
|
101
|
-
console.log(
|
|
408
|
+
console.log('Cancelled.');
|
|
102
409
|
break;
|
|
103
410
|
}
|
|
104
411
|
}
|
|
105
412
|
catch (error) {
|
|
106
413
|
const message = error instanceof Error ? error.message : String(error);
|
|
107
|
-
console.error(
|
|
414
|
+
console.error('Error:', message);
|
|
108
415
|
}
|
|
109
416
|
}
|
|
110
417
|
main();
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-cmg",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "AI Commit Message Generator",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ai-
|
|
7
|
+
"ai-cmg": "dist/index.js",
|
|
8
|
+
"ai-commit": "dist/index.js",
|
|
9
|
+
"d-commit": "dist/index.js"
|
|
8
10
|
},
|
|
9
11
|
"main": "./dist/index.js",
|
|
10
12
|
"types": "./dist/index.d.ts",
|
|
@@ -20,7 +22,6 @@
|
|
|
20
22
|
"dependencies": {
|
|
21
23
|
"clipboardy": "^5.0.2",
|
|
22
24
|
"conf": "^15.0.2",
|
|
23
|
-
"picocolors": "^1.1.1",
|
|
24
25
|
"prompts": "^2.4.2"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|