Skip to content

Commit 3f9f61a

Browse files
Copilotwarengonzaga
andcommitted
📦 new: implement multi-convention support and Copilot SDK integration
Co-authored-by: warengonzaga <[email protected]>
1 parent 6595812 commit 3f9f61a

8 files changed

Lines changed: 379 additions & 136 deletions

File tree

source/cli.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
logout,
1212
} from './commands/auth.js';
1313
import {showConfig, resetConfig} from './commands/config.js';
14+
import {setConvention, getConvention} from './utils/config-manager.js';
15+
import {showSuccess, showError} from './utils/ui.js';
1416

1517
const program = new Command();
1618

@@ -44,6 +46,10 @@ program
4446
.option('--all', 'Process all files at once')
4547
.option('--file <path>', 'Process specific file')
4648
.option('--model <model>', 'Specify AI model (e.g., gpt-4, gpt-3.5-turbo)')
49+
.option(
50+
'--convention <type>',
51+
'Commit convention (clean, conventional, gitmoji, simple)',
52+
)
4753
.action(options => {
4854
commitCommand(options);
4955
});
@@ -83,6 +89,21 @@ authCmd
8389
// Config command
8490
const configCmd = program.command('config').description('Manage configuration');
8591

92+
configCmd
93+
.command('set-convention <type>')
94+
.description('Set default commit convention')
95+
.action(type => {
96+
const validConventions = ['clean', 'conventional', 'gitmoji', 'simple'];
97+
if (!validConventions.includes(type)) {
98+
showError(`Invalid convention: ${type}`);
99+
console.log(`Available: ${validConventions.join(', ')}`);
100+
process.exit(1);
101+
}
102+
103+
setConvention(type);
104+
showSuccess(`Default convention set to: ${type}`);
105+
});
106+
86107
configCmd
87108
.option('--show', 'Show current configuration')
88109
.option('--reset', 'Reset configuration to defaults')

source/commands/auth.js

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {execa} from 'execa';
12
import process from 'node:process';
23
import {
34
setAuthMode,
@@ -20,44 +21,43 @@ import {
2021

2122
export async function authenticateWithCopilot(token = null) {
2223
try {
23-
// Check for token in arguments first
24-
let authToken = token;
24+
console.log('🔐 Setting up GitHub Copilot authentication...\n');
2525

26-
// If no token provided, check environment variables
27-
if (!authToken) {
28-
authToken =
29-
process.env.COPILOT_GITHUB_TOKEN ||
30-
process.env.GH_TOKEN ||
31-
process.env.GITHUB_TOKEN;
26+
// Check if gh CLI is installed
27+
try {
28+
await execa('gh', ['--version']);
29+
} catch {
30+
showError('GitHub CLI (gh) is not installed.');
31+
console.log('');
32+
console.log('Install it from: https://cli.github.com/');
33+
console.log('Then run: gh auth login');
34+
process.exit(1);
3235
}
3336

34-
if (!authToken) {
35-
showError('No GitHub token found.');
37+
// Check if gh is authenticated
38+
try {
39+
await execa('gh', ['auth', 'status']);
40+
} catch {
41+
showError('GitHub CLI is not authenticated.');
3642
console.log('');
37-
console.log('Please provide a token using one of these methods:');
38-
console.log(' 1. Pass token: magicc auth copilot --token <your-token>');
39-
console.log(' 2. Set environment variable: GITHUB_TOKEN or GH_TOKEN');
40-
console.log(
41-
' 3. Use gh CLI: gh auth login (then use: magicc auth copilot)',
42-
);
43+
console.log('Please authenticate first:');
44+
console.log(' gh auth login');
4345
console.log('');
44-
console.log('To create a token:');
45-
console.log(' Visit: https://github.com/settings/tokens');
46-
console.log(' Scopes needed: repo, read:user');
46+
console.log('Make sure you have GitHub Copilot enabled on your account.');
4747
process.exit(1);
4848
}
4949

50-
// Store token and set auth mode
51-
setToken('github', authToken);
50+
// Store auth mode
5251
setAuthMode('copilot');
5352

54-
showSuccess('GitHub Copilot authentication successful!');
53+
showSuccess('GitHub Copilot ready!');
5554
console.log('');
56-
console.log(`📁 Config stored at: ${getConfigPath()}`);
55+
console.log('✅ GitHub CLI authenticated');
56+
console.log('✅ Copilot SDK will use your CLI session');
5757
console.log('');
5858
console.log('You can now use: magicc commit');
5959
} catch (error) {
60-
showError(`Authentication failed: ${error.message}`);
60+
showError(`Setup failed: ${error.message}`);
6161
process.exit(1);
6262
}
6363
}

source/commands/commit.js

Lines changed: 78 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
showInfo,
1818
confirmCommit,
1919
} from '../utils/ui.js';
20-
import {getAuthMode} from '../utils/config-manager.js';
20+
import {getAuthMode, getConvention} from '../utils/config-manager.js';
2121

2222
/**
2323
* Main commit command logic
@@ -137,37 +137,47 @@ export async function processFilesInteractively(aiProvider, options = {}) {
137137
continue;
138138
}
139139

140-
// Generate commit message
141-
try {
142-
const message = await aiProvider.generateCommitMessage(
143-
diff,
144-
file,
145-
options,
146-
);
140+
let currentConvention = options.convention || getConvention();
141+
let fileProcessed = false;
147142

148-
// Confirm and commit
149-
const result = await confirmCommit(message, file);
143+
while (!fileProcessed) {
144+
try {
145+
const message = await aiProvider.generateCommitMessage(
146+
diff,
147+
file,
148+
{...options, convention: currentConvention},
149+
);
150150

151-
if (result.action === 'accept') {
152-
const success = await commit(result.message);
153-
if (success) {
154-
showSuccess(`Committed: ${file}`);
155-
console.log(`📝 ${result.message}`);
156-
committed++;
157-
} else {
158-
showError(`Failed to commit ${file}`);
151+
const result = await confirmCommit(message, file, currentConvention);
152+
153+
if (result.action === 'accept') {
154+
const success = await commit(result.message);
155+
if (success) {
156+
showSuccess(`Committed: ${file}`);
157+
console.log(`📝 ${result.message}`);
158+
committed++;
159+
} else {
160+
showError(`Failed to commit ${file}`);
161+
await unstageFile(file);
162+
skipped++;
163+
}
164+
165+
fileProcessed = true;
166+
} else if (result.action === 'regenerate') {
167+
currentConvention = result.convention;
168+
// Loop continues to regenerate
169+
} else if (result.action === 'skip') {
170+
showInfo(`Skipped: ${file}`);
159171
await unstageFile(file);
160172
skipped++;
173+
fileProcessed = true;
161174
}
162-
} else if (result.action === 'skip') {
163-
showInfo(`Skipped: ${file}`);
175+
} catch (error) {
176+
showError(`Error processing ${file}: ${error.message}`);
164177
await unstageFile(file);
165178
skipped++;
179+
fileProcessed = true;
166180
}
167-
} catch (error) {
168-
showError(`Error processing ${file}: ${error.message}`);
169-
await unstageFile(file);
170-
skipped++;
171181
}
172182
}
173183

@@ -199,27 +209,52 @@ export async function processFile(filePath, aiProvider, options = {}) {
199209
return;
200210
}
201211

202-
// Generate commit message
203-
showInfo('Generating commit message...');
204-
const message = await aiProvider.generateCommitMessage(
205-
diff,
206-
filePath,
207-
options,
208-
);
212+
let currentConvention = options.convention || getConvention();
213+
let attempts = 0;
214+
const maxAttempts = 5;
209215

210-
// Confirm and commit
211-
const result = await confirmCommit(message, filePath);
216+
while (attempts < maxAttempts) {
217+
showInfo('Generating commit message...');
212218

213-
if (result.action === 'accept') {
214-
const success = await commit(result.message);
215-
if (success) {
216-
showSuccess('Changes committed successfully!');
217-
console.log(`📝 ${result.message}`);
218-
} else {
219-
showError('Failed to commit changes.');
219+
try {
220+
const message = await aiProvider.generateCommitMessage(
221+
diff,
222+
filePath,
223+
{...options, convention: currentConvention},
224+
);
225+
226+
const result = await confirmCommit(message, filePath, currentConvention);
227+
228+
if (result.action === 'accept') {
229+
const success = await commit(result.message);
230+
if (success) {
231+
showSuccess('Changes committed successfully!');
232+
console.log(`📝 ${result.message}`);
233+
} else {
234+
showError('Failed to commit changes.');
235+
}
236+
237+
return;
238+
}
239+
240+
if (result.action === 'regenerate') {
241+
currentConvention = result.convention;
242+
attempts++;
243+
continue;
244+
}
245+
246+
if (result.action === 'skip') {
247+
showInfo('Commit cancelled.');
248+
await unstageFile(filePath);
249+
return;
250+
}
251+
} catch (error) {
252+
showError(`Error: ${error.message}`);
253+
await unstageFile(filePath);
254+
return;
220255
}
221-
} else if (result.action === 'skip') {
222-
showInfo('Commit cancelled.');
223-
await unstageFile(filePath);
224256
}
257+
258+
showWarning('Maximum regeneration attempts reached.');
259+
await unstageFile(filePath);
225260
}

source/commands/config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getConfigPath,
66
} from '../utils/config-manager.js';
77
import {showSuccess, showInfo} from '../utils/ui.js';
8+
import {getConvention} from '../utils/commit-conventions.js';
89

910
/**
1011
* Configuration commands
@@ -31,6 +32,12 @@ export function showConfig() {
3132
displayValue = value.slice(0, 10) + '...';
3233
}
3334

35+
// Show convention name nicely
36+
if (key === 'convention') {
37+
const conv = getConvention(value);
38+
displayValue = `${value} (${conv.name})`;
39+
}
40+
3441
console.log(` ${chalk.yellow(key)}: ${chalk.white(displayValue)}`);
3542
}
3643
}

0 commit comments

Comments
 (0)