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 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 { apiKey } = await prompt({
17
- type: 'input',
18
- name: 'apiKey',
19
- message: 'Enter your Groq API Key:'
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
- if (apiKey) {
23
- saveGlobalConfig({ GROQ_API_KEY: apiKey });
24
- console.log(pc.green('API Key saved globally in ~/.aicorc'));
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
- * Sends code diff to Groq for review.
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 = getApiKey();
19
- if (!apiKey) {
20
- throw new Error('GROQ_API_KEY is not set. Run "aico init" to configure it.');
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
- return 'No changes detected to review.';
90
+ throw new Error('No diff provided for review.');
25
91
  }
26
92
 
27
93
  const systemPrompt = `
28
- You are an expert code reviewer. Analyze the following git diff and provide constructive feedback.
29
- Focus on:
30
- 1. Security vulnerabilities.
31
- 2. Performance bottlenecks.
32
- 3. Code readability and clean code principles.
33
- 4. Potential bugs.
34
-
35
- Format your response as a list of issues. For each issue, provide:
36
- - File: [filename]
37
- - Issue: [description]
38
- - Suggestion: [how to fix it]
39
- - CorrectedCode: [the FULL corrected content of the file, wrapped in triple backticks]
40
-
41
- Keep it concise and professional. Do not use emojis.
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 groq.chat.completions.create({
46
- messages: [
47
- { role: 'system', content: systemPrompt },
48
- { role: 'user', content: `Review this diff:\n\n${diff}` },
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 = getApiKey();
67
- if (!apiKey) {
68
- throw new Error('GROQ_API_KEY is not set. Run "aico init" to configure it.');
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 groq.chat.completions.create({
87
- messages: [
88
- { role: 'system', content: systemPrompt },
89
- { role: 'user', content: `Generate a commit message for this diff:\n\n${diff}` },
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) {
@@ -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 API key to a global config file in the user's home directory.
9
- * @param {string} apiKey
8
+ * Saves the global config.
9
+ * @param {object} config
10
10
  */
11
11
  export function saveGlobalConfig(config) {
12
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
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 API key from environment variables or global config.
32
- * @returns {string|null}
33
+ * Gets the active provider configuration.
34
+ * @returns {object} { provider, apiKey, model, baseUrl }
33
35
  */
34
- export function getApiKey() {
35
- // Priority: Process Env > Local .env (handled by dotenv) > Global Config
36
- if (process.env.GROQ_API_KEY) return process.env.GROQ_API_KEY;
36
+ export function getActiveProvider() {
37
+ const config = loadGlobalConfig();
38
+ const provider = process.env.AICO_PROVIDER || config?.provider || 'groq';
37
39
 
38
- const globalConfig = loadGlobalConfig();
39
- return globalConfig?.GROQ_API_KEY || null;
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.1",
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",