aico-ai 1.0.1 → 1.0.5
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 +2 -1
- package/index.js +81 -7
- package/lib/ai-service.js +105 -54
- package/lib/config-utils.js +30 -10
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Aico
|
|
1
|
+
# Aico AI - Gatekeeper for your code
|
|
2
2
|
|
|
3
3
|
**Aico** is an intelligent CLI tool that acts as a gatekeeper for your code. It uses AI (powered by Groq) to review your changes before you push them, ensuring high quality, security, and consistency across your projects.
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
- 🔍 **AI Code Review**: Semantic analysis of your git diffs to catch bugs, security issues, and code smells.
|
|
8
8
|
- ✨ **Apply Fixes**: Automatically apply suggested improvements with a single click.
|
|
9
9
|
- 📝 **AI Commit Messages**: Generate high-quality, Conventional Commit messages based on your changes.
|
|
10
|
+
- 🤖 **Multi-Provider Support**: Use Groq, OpenAI, Gemini, DeepSeek, or even local models via Ollama.
|
|
10
11
|
- 🛡️ **Git Hook Integration**: Seamlessly integrates with Husky to run reviews as a `pre-push` hook.
|
|
11
12
|
- 🤫 **Silent Mode**: Run reviews without blocking your workflow.
|
|
12
13
|
- 🌍 **Global Config**: Configure your API key once and use it across all your projects.
|
package/index.js
CHANGED
|
@@ -13,17 +13,50 @@ import { execSync } from 'child_process';
|
|
|
13
13
|
async function init() {
|
|
14
14
|
console.log(pc.bold(pc.blue('Aico: Initializing...')));
|
|
15
15
|
|
|
16
|
-
const {
|
|
17
|
-
type: '
|
|
18
|
-
name: '
|
|
19
|
-
message: '
|
|
16
|
+
const { provider } = await prompt({
|
|
17
|
+
type: 'select',
|
|
18
|
+
name: 'provider',
|
|
19
|
+
message: 'Which AI provider would you like to use?',
|
|
20
|
+
choices: [
|
|
21
|
+
{ name: 'groq', message: 'Groq (Fast & Free tier)' },
|
|
22
|
+
{ name: 'openai', message: 'OpenAI (GPT-4o, etc.)' },
|
|
23
|
+
{ name: 'deepseek', message: 'DeepSeek (Powerful & Cheap)' },
|
|
24
|
+
{ name: 'ollama', message: 'Ollama (Local & Private)' },
|
|
25
|
+
{ name: 'gemini', message: 'Google Gemini' }
|
|
26
|
+
]
|
|
20
27
|
});
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
let config = { provider, providers: {} };
|
|
30
|
+
config.providers[provider] = {};
|
|
31
|
+
|
|
32
|
+
if (provider === 'ollama') {
|
|
33
|
+
const { baseUrl } = await prompt({
|
|
34
|
+
type: 'input',
|
|
35
|
+
name: 'baseUrl',
|
|
36
|
+
message: 'Ollama Base URL:',
|
|
37
|
+
initial: 'http://localhost:11434'
|
|
38
|
+
});
|
|
39
|
+
config.providers[provider].baseUrl = baseUrl;
|
|
40
|
+
} else {
|
|
41
|
+
const { apiKey } = await prompt({
|
|
42
|
+
type: 'input',
|
|
43
|
+
name: 'apiKey',
|
|
44
|
+
message: `Enter your ${provider} API Key:`
|
|
45
|
+
});
|
|
46
|
+
config.providers[provider].apiKey = apiKey;
|
|
25
47
|
}
|
|
26
48
|
|
|
49
|
+
const { model } = await prompt({
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'model',
|
|
52
|
+
message: 'Model name (leave empty for default):',
|
|
53
|
+
initial: ''
|
|
54
|
+
});
|
|
55
|
+
if (model) config.providers[provider].model = model;
|
|
56
|
+
|
|
57
|
+
saveGlobalConfig(config);
|
|
58
|
+
console.log(pc.green(`\nConfiguration saved globally in ~/.aicorc for ${provider}!`));
|
|
59
|
+
|
|
27
60
|
const { setupHusky } = await prompt({
|
|
28
61
|
type: 'confirm',
|
|
29
62
|
name: 'setupHusky',
|
|
@@ -46,16 +79,57 @@ async function init() {
|
|
|
46
79
|
}
|
|
47
80
|
}
|
|
48
81
|
|
|
82
|
+
function displayHelp() {
|
|
83
|
+
console.log(`
|
|
84
|
+
${pc.bold(pc.blue('Aico AI - Gatekeeper for your code'))}
|
|
85
|
+
|
|
86
|
+
${pc.bold('Usage:')}
|
|
87
|
+
aico <command> [options]
|
|
88
|
+
|
|
89
|
+
${pc.bold('Commands:')}
|
|
90
|
+
${pc.cyan('review')} Analyze staged changes and suggest improvements (default)
|
|
91
|
+
${pc.cyan('commit')} Generate and apply an AI-suggested commit message
|
|
92
|
+
${pc.cyan('init')} Setup AI providers and Git hooks
|
|
93
|
+
${pc.cyan('help')} Display this help message
|
|
94
|
+
|
|
95
|
+
${pc.bold('Options:')}
|
|
96
|
+
${pc.cyan('--silent, -s')} Run review without blocking the push
|
|
97
|
+
${pc.cyan('--version, -v')} Display version number
|
|
98
|
+
${pc.cyan('--help, -h')} Display this help message
|
|
99
|
+
|
|
100
|
+
${pc.bold('Examples:')}
|
|
101
|
+
aico review --silent
|
|
102
|
+
aico commit
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
|
|
49
106
|
async function main() {
|
|
50
107
|
const args = process.argv.slice(2);
|
|
51
108
|
const command = args[0] || 'review';
|
|
52
109
|
const isSilent = args.includes('--silent') || args.includes('-s');
|
|
53
110
|
|
|
111
|
+
if (args.includes('--help') || args.includes('-h') || command === 'help') {
|
|
112
|
+
displayHelp();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
117
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'));
|
|
118
|
+
console.log(`aico-ai v${pkg.version}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
54
122
|
if (command === 'init') {
|
|
55
123
|
await init();
|
|
56
124
|
return;
|
|
57
125
|
}
|
|
58
126
|
|
|
127
|
+
if (command !== 'review' && command !== 'commit') {
|
|
128
|
+
console.error(pc.red(`Unknown command: ${command}`));
|
|
129
|
+
console.log(`Run ${pc.cyan('aico help')} to see available commands.`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
59
133
|
if (command === 'commit') {
|
|
60
134
|
console.log(pc.bold(pc.blue('Aico: Generating commit message...')));
|
|
61
135
|
try {
|
package/lib/ai-service.js
CHANGED
|
@@ -1,54 +1,118 @@
|
|
|
1
1
|
import Groq from 'groq-sdk';
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
|
-
|
|
4
|
-
import { getApiKey } from './config-utils.js';
|
|
3
|
+
import { getActiveProvider } from './config-utils.js';
|
|
5
4
|
|
|
6
5
|
dotenv.config();
|
|
7
6
|
|
|
8
|
-
const groq = new Groq({
|
|
9
|
-
apiKey: getApiKey(),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
7
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param {string} diff
|
|
15
|
-
* @returns {Promise<string>} AI review response.
|
|
8
|
+
* Generic AI Client Wrapper
|
|
16
9
|
*/
|
|
10
|
+
class AIClient {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.updateConfig();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
updateConfig() {
|
|
16
|
+
const { provider, apiKey, model, baseUrl } = getActiveProvider();
|
|
17
|
+
this.provider = provider;
|
|
18
|
+
this.apiKey = apiKey;
|
|
19
|
+
this.model = model || this.getDefaultModel(provider);
|
|
20
|
+
this.baseUrl = baseUrl;
|
|
21
|
+
|
|
22
|
+
if (provider === 'groq') {
|
|
23
|
+
this.client = new Groq({ apiKey });
|
|
24
|
+
} else if (provider === 'openai' || provider === 'deepseek' || provider === 'ollama') {
|
|
25
|
+
// OpenAI compatible APIs
|
|
26
|
+
const url = provider === 'ollama' ? (baseUrl || 'http://localhost:11434/v1') :
|
|
27
|
+
provider === 'deepseek' ? 'https://api.deepseek.com' : undefined;
|
|
28
|
+
|
|
29
|
+
this.client = {
|
|
30
|
+
chat: {
|
|
31
|
+
completions: {
|
|
32
|
+
create: async (params) => {
|
|
33
|
+
const res = await fetch(`${url || 'https://api.openai.com/v1'}/chat/completions`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'Authorization': `Bearer ${apiKey}`
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify(params)
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const err = await res.json();
|
|
43
|
+
throw new Error(err.error?.message || 'AI API Error');
|
|
44
|
+
}
|
|
45
|
+
return res.json();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getDefaultModel(provider) {
|
|
54
|
+
const models = {
|
|
55
|
+
groq: 'llama-3.3-70b-versatile',
|
|
56
|
+
openai: 'gpt-4o-mini',
|
|
57
|
+
deepseek: 'deepseek-chat',
|
|
58
|
+
ollama: 'llama3'
|
|
59
|
+
};
|
|
60
|
+
return models[provider] || 'gpt-3.5-turbo';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async createChatCompletion(messages) {
|
|
64
|
+
if (!this.client) this.updateConfig();
|
|
65
|
+
|
|
66
|
+
if (this.provider === 'groq') {
|
|
67
|
+
return this.client.chat.completions.create({
|
|
68
|
+
messages,
|
|
69
|
+
model: this.model,
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
// OpenAI compatible
|
|
73
|
+
return this.client.chat.completions.create({
|
|
74
|
+
messages,
|
|
75
|
+
model: this.model,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const ai = new AIClient();
|
|
82
|
+
|
|
17
83
|
export async function reviewCode(diff) {
|
|
18
|
-
const apiKey =
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
throw new Error(
|
|
84
|
+
const { apiKey, provider } = getActiveProvider();
|
|
85
|
+
if (!apiKey && provider !== 'ollama') {
|
|
86
|
+
throw new Error(`API Key for ${provider} is not set. Run "aico init" to configure it.`);
|
|
21
87
|
}
|
|
22
88
|
|
|
23
89
|
if (!diff || diff.trim() === '') {
|
|
24
|
-
|
|
90
|
+
throw new Error('No diff provided for review.');
|
|
25
91
|
}
|
|
26
92
|
|
|
27
93
|
const systemPrompt = `
|
|
28
|
-
You are
|
|
29
|
-
|
|
30
|
-
1.
|
|
31
|
-
2.
|
|
32
|
-
3.
|
|
33
|
-
4.
|
|
34
|
-
|
|
35
|
-
Format your response as a list of issues.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
94
|
+
You are a senior code reviewer. Analyze the git diff and identify potential bugs, security issues, or performance bottlenecks.
|
|
95
|
+
For each issue, provide:
|
|
96
|
+
1. File: The filename.
|
|
97
|
+
2. Issue: A concise description of the problem.
|
|
98
|
+
3. Suggestion: How to fix it.
|
|
99
|
+
4. CorrectedCode: The FULL corrected content of the file. This is CRITICAL.
|
|
100
|
+
|
|
101
|
+
Format your response as a list of issues. Use the following markers:
|
|
102
|
+
[FILE] filename
|
|
103
|
+
[ISSUE] description
|
|
104
|
+
[SUGGESTION] how to fix
|
|
105
|
+
[CORRECTED_CODE]
|
|
106
|
+
\`\`\`
|
|
107
|
+
full file content here
|
|
108
|
+
\`\`\`
|
|
42
109
|
`;
|
|
43
110
|
|
|
44
111
|
try {
|
|
45
|
-
const chatCompletion = await
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
],
|
|
50
|
-
model: 'llama-3.3-70b-versatile',
|
|
51
|
-
});
|
|
112
|
+
const chatCompletion = await ai.createChatCompletion([
|
|
113
|
+
{ role: 'system', content: systemPrompt },
|
|
114
|
+
{ role: 'user', content: `Review this diff:\n\n${diff}` },
|
|
115
|
+
]);
|
|
52
116
|
|
|
53
117
|
return chatCompletion.choices[0]?.message?.content || 'No feedback provided.';
|
|
54
118
|
} catch (error) {
|
|
@@ -57,19 +121,10 @@ Keep it concise and professional. Do not use emojis.
|
|
|
57
121
|
}
|
|
58
122
|
}
|
|
59
123
|
|
|
60
|
-
/**
|
|
61
|
-
* Generates a commit message based on the diff.
|
|
62
|
-
* @param {string} diff
|
|
63
|
-
* @returns {Promise<string>} Suggested commit message.
|
|
64
|
-
*/
|
|
65
124
|
export async function generateCommitMessage(diff) {
|
|
66
|
-
const apiKey =
|
|
67
|
-
if (!apiKey) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!diff || diff.trim() === '') {
|
|
72
|
-
return 'chore: update files';
|
|
125
|
+
const { apiKey, provider } = getActiveProvider();
|
|
126
|
+
if (!apiKey && provider !== 'ollama') {
|
|
127
|
+
throw new Error(`API Key for ${provider} is not set. Run "aico init" to configure it.`);
|
|
73
128
|
}
|
|
74
129
|
|
|
75
130
|
const systemPrompt = `
|
|
@@ -78,18 +133,14 @@ Analyze the diff and provide a single, concise commit message.
|
|
|
78
133
|
Format: <type>(<scope>): <description>
|
|
79
134
|
Types: feat, fix, docs, style, refactor, test, chore.
|
|
80
135
|
Description: Imperative, present tense, no period at the end.
|
|
81
|
-
|
|
82
136
|
Do not include any other text, only the commit message itself. Do not use emojis.
|
|
83
137
|
`;
|
|
84
138
|
|
|
85
139
|
try {
|
|
86
|
-
const chatCompletion = await
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
],
|
|
91
|
-
model: 'llama-3.3-70b-versatile',
|
|
92
|
-
});
|
|
140
|
+
const chatCompletion = await ai.createChatCompletion([
|
|
141
|
+
{ role: 'system', content: systemPrompt },
|
|
142
|
+
{ role: 'user', content: `Generate a commit message for this diff:\n\n${diff}` },
|
|
143
|
+
]);
|
|
93
144
|
|
|
94
145
|
return chatCompletion.choices[0]?.message?.content?.trim() || 'chore: update files';
|
|
95
146
|
} catch (error) {
|
package/lib/config-utils.js
CHANGED
|
@@ -5,11 +5,13 @@ import path from 'path';
|
|
|
5
5
|
const CONFIG_PATH = path.join(os.homedir(), '.aicorc');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Saves the
|
|
9
|
-
* @param {
|
|
8
|
+
* Saves the global config.
|
|
9
|
+
* @param {object} config
|
|
10
10
|
*/
|
|
11
11
|
export function saveGlobalConfig(config) {
|
|
12
|
-
|
|
12
|
+
const currentConfig = loadGlobalConfig() || {};
|
|
13
|
+
const newConfig = { ...currentConfig, ...config };
|
|
14
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(newConfig, null, 2));
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -28,13 +30,31 @@ export function loadGlobalConfig() {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
|
-
* Gets the
|
|
32
|
-
* @returns {
|
|
33
|
+
* Gets the active provider configuration.
|
|
34
|
+
* @returns {object} { provider, apiKey, model, baseUrl }
|
|
33
35
|
*/
|
|
34
|
-
export function
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
export function getActiveProvider() {
|
|
37
|
+
const config = loadGlobalConfig();
|
|
38
|
+
const provider = process.env.AICO_PROVIDER || config?.provider || 'groq';
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
// Priority: Process Env > Global Config
|
|
41
|
+
const providers = config?.providers || {};
|
|
42
|
+
const providerConfig = providers[provider] || {};
|
|
43
|
+
|
|
44
|
+
const apiKeyEnvMap = {
|
|
45
|
+
groq: 'GROQ_API_KEY',
|
|
46
|
+
openai: 'OPENAI_API_KEY',
|
|
47
|
+
gemini: 'GEMINI_API_KEY',
|
|
48
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
49
|
+
anthropic: 'ANTHROPIC_API_KEY'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const apiKey = process.env[apiKeyEnvMap[provider]] || providerConfig.apiKey;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
provider,
|
|
56
|
+
apiKey,
|
|
57
|
+
model: providerConfig.model,
|
|
58
|
+
baseUrl: providerConfig.baseUrl
|
|
59
|
+
};
|
|
40
60
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aico-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,10 +17,21 @@
|
|
|
17
17
|
"commit",
|
|
18
18
|
"groq",
|
|
19
19
|
"cli",
|
|
20
|
-
"developer-tools"
|
|
20
|
+
"developer-tools",
|
|
21
|
+
"chatgpt",
|
|
22
|
+
"gemini",
|
|
23
|
+
"artificial-intelligence"
|
|
21
24
|
],
|
|
22
25
|
"author": "Lucas Silva from CodeTech Software <projetos@codetechsoftware.com.br>",
|
|
23
26
|
"license": "ISC",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/LukasdeSouza/aico-ai.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/LukasdeSouza/aico-ai/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/LukasdeSouza/aico-ai#readme",
|
|
24
35
|
"dependencies": {
|
|
25
36
|
"dotenv": "^17.2.3",
|
|
26
37
|
"enquirer": "^2.4.1",
|