🔑 Feat: Gestion interactive des clés API avec stockage sécurisé
Nouvelles fonctionnalités: - Demande interactive des clés API au premier lancement - Commandes pour gérer les clés: key set/remove, setup, keys - Stockage sécurisé des clés dans ~/.neuraterm/keys.json - Support variables d'environnement et rechargement à chaud - Gestion intelligente du provider par défaut selon les clés disponibles Commandes ajoutées: - `keys` - Afficher le statut des clés API - `key set <provider>` - Configurer une clé (openai/mistral) - `key remove <provider>` - Supprimer une clé - `setup` - Configuration interactive complète Plus besoin de configurer manuellement les clés avant le lancement! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0b9bab45a8
commit
b8369b89e6
@ -2,7 +2,8 @@
|
|||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(mkdir:*)",
|
"Bash(mkdir:*)",
|
||||||
"Bash(git add:*)"
|
"Bash(git add:*)",
|
||||||
|
"Bash(git reset:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,5 @@
|
|||||||
.divers
|
.divers
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
|
.env
|
53
README.md
53
README.md
@ -28,26 +28,50 @@ npm start
|
|||||||
|
|
||||||
## ⚙️ Configuration
|
## ⚙️ Configuration
|
||||||
|
|
||||||
### Variables d'environnement
|
### 🚀 Configuration automatique (recommandée)
|
||||||
|
|
||||||
|
Au premier lancement, NeuraTerm vous demandera vos clés API de manière interactive :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
neuraterm
|
||||||
|
# Suivez les instructions pour configurer vos clés API
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔑 Gestion des clés API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Afficher le statut des clés
|
||||||
|
keys
|
||||||
|
|
||||||
|
# Configurer une nouvelle clé
|
||||||
|
key set openai
|
||||||
|
key set mistral
|
||||||
|
|
||||||
|
# Supprimer une clé
|
||||||
|
key remove openai
|
||||||
|
|
||||||
|
# Reconfiguration complète
|
||||||
|
setup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables d'environnement (optionnel)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export OPENAI_API_KEY="votre_clé_openai"
|
export OPENAI_API_KEY="votre_clé_openai"
|
||||||
export MISTRAL_API_KEY="votre_clé_mistral"
|
export MISTRAL_API_KEY="votre_clé_mistral"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fichier de configuration
|
### Fichier de configuration avancée
|
||||||
|
|
||||||
Créez `~/.neuraterm/config.json` :
|
Créez `~/.neuraterm/config.json` pour une configuration avancée :
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ai": {
|
"ai": {
|
||||||
"openai": {
|
"openai": {
|
||||||
"apiKey": "votre_clé_openai",
|
|
||||||
"model": "gpt-4o-mini"
|
"model": "gpt-4o-mini"
|
||||||
},
|
},
|
||||||
"mistral": {
|
"mistral": {
|
||||||
"apiKey": "votre_clé_mistral",
|
|
||||||
"model": "mistral-large-latest"
|
"model": "mistral-large-latest"
|
||||||
},
|
},
|
||||||
"defaultProvider": "openai"
|
"defaultProvider": "openai"
|
||||||
@ -61,6 +85,8 @@ Créez `~/.neuraterm/config.json` :
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Note**: Les clés API sont stockées séparément dans `~/.neuraterm/keys.json` pour plus de sécurité.
|
||||||
|
|
||||||
## 🎯 Utilisation
|
## 🎯 Utilisation
|
||||||
|
|
||||||
### Commandes de base
|
### Commandes de base
|
||||||
@ -72,20 +98,27 @@ neuraterm
|
|||||||
# Aide
|
# Aide
|
||||||
help
|
help
|
||||||
|
|
||||||
|
# Gestion des clés API
|
||||||
|
keys # Statut des clés
|
||||||
|
key set openai # Configurer OpenAI
|
||||||
|
key set mistral # Configurer Mistral
|
||||||
|
setup # Configuration interactive
|
||||||
|
|
||||||
# Poser une question à l'IA
|
# Poser une question à l'IA
|
||||||
Comment optimiser mon code Python ?
|
Comment optimiser mon code Python ?
|
||||||
|
|
||||||
# Changer de provider
|
# Changer de provider
|
||||||
provider mistral
|
provider mistral
|
||||||
|
provider openai
|
||||||
|
|
||||||
# Voir les statistiques
|
# Voir les statistiques
|
||||||
stats
|
stats # Toutes les stats
|
||||||
stats openai
|
stats openai # Stats OpenAI uniquement
|
||||||
cost
|
cost # Coût total
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
config
|
config # Voir la configuration
|
||||||
providers
|
providers # Lister les providers
|
||||||
```
|
```
|
||||||
|
|
||||||
### Exemples d'usage professionnel
|
### Exemples d'usage professionnel
|
||||||
|
6974
package-lock.json
generated
Normal file
6974
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,20 +6,28 @@ import { AIClient } from './client.js';
|
|||||||
import { ProviderConfig } from './types.js';
|
import { ProviderConfig } from './types.js';
|
||||||
|
|
||||||
export async function initAI(config: any, auth: any): Promise<AIClient> {
|
export async function initAI(config: any, auth: any): Promise<AIClient> {
|
||||||
|
const keyManager = auth.getKeyManager();
|
||||||
|
const keys = keyManager.loadKeys();
|
||||||
|
|
||||||
const providerConfig: ProviderConfig = {
|
const providerConfig: ProviderConfig = {
|
||||||
openai: {
|
openai: {
|
||||||
apiKey: config.ai.openai?.apiKey || process.env.OPENAI_API_KEY || '',
|
apiKey: keys.openai || '',
|
||||||
model: config.ai.openai?.model || 'gpt-4o-mini',
|
model: config.ai.openai?.model || 'gpt-4o-mini',
|
||||||
baseUrl: config.ai.openai?.baseUrl
|
baseUrl: config.ai.openai?.baseUrl
|
||||||
},
|
},
|
||||||
mistral: {
|
mistral: {
|
||||||
apiKey: config.ai.mistral?.apiKey || process.env.MISTRAL_API_KEY || '',
|
apiKey: keys.mistral || '',
|
||||||
model: config.ai.mistral?.model || 'mistral-large-latest',
|
model: config.ai.mistral?.model || 'mistral-large-latest',
|
||||||
baseUrl: config.ai.mistral?.baseUrl
|
baseUrl: config.ai.mistral?.baseUrl
|
||||||
},
|
},
|
||||||
defaultProvider: config.ai.defaultProvider || 'openai'
|
defaultProvider: keys.openai ? 'openai' : 'mistral'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ajuster le provider par défaut selon les clés disponibles
|
||||||
|
if (config.ai.defaultProvider && keys[config.ai.defaultProvider as keyof typeof keys]) {
|
||||||
|
providerConfig.defaultProvider = config.ai.defaultProvider;
|
||||||
|
}
|
||||||
|
|
||||||
return new AIClient(providerConfig);
|
return new AIClient(providerConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,40 @@
|
|||||||
/**
|
/**
|
||||||
* Gestion de l'authentification
|
* Gestion de l'authentification et des clés API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { KeyManager } from './keyManager.js';
|
||||||
|
|
||||||
export class AuthManager {
|
export class AuthManager {
|
||||||
constructor(private config: any) {}
|
private keyManager: KeyManager;
|
||||||
|
|
||||||
|
constructor(private config: any) {
|
||||||
|
this.keyManager = new KeyManager();
|
||||||
|
}
|
||||||
|
|
||||||
isAuthenticated(): boolean {
|
isAuthenticated(): boolean {
|
||||||
return true;
|
return this.keyManager.hasValidKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(): Promise<void> {
|
async authenticate(): Promise<void> {
|
||||||
return;
|
if (!this.keyManager.hasValidKeys()) {
|
||||||
|
await this.keyManager.promptForMissingKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyManager(): KeyManager {
|
||||||
|
return this.keyManager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initAuthentication(config: any): Promise<AuthManager> {
|
export async function initAuthentication(config: any): Promise<AuthManager> {
|
||||||
return new AuthManager(config);
|
const authManager = new AuthManager(config);
|
||||||
|
|
||||||
|
// Vérifier les clés au démarrage
|
||||||
|
if (!authManager.isAuthenticated()) {
|
||||||
|
await authManager.authenticate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return authManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { KeyManager } from './keyManager.js';
|
264
src/auth/keyManager.ts
Normal file
264
src/auth/keyManager.ts
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
/**
|
||||||
|
* Gestionnaire des clés API avec stockage sécurisé
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
||||||
|
import { homedir } from 'os';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
import * as readline from 'readline';
|
||||||
|
import { logger } from '../utils/logger.js';
|
||||||
|
|
||||||
|
export interface APIKeys {
|
||||||
|
openai?: string;
|
||||||
|
mistral?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KeyManager {
|
||||||
|
private configPath: string;
|
||||||
|
private keysPath: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const configDir = join(homedir(), '.neuraterm');
|
||||||
|
this.configPath = join(configDir, 'config.json');
|
||||||
|
this.keysPath = join(configDir, 'keys.json');
|
||||||
|
|
||||||
|
// Créer le dossier de configuration s'il n'existe pas
|
||||||
|
if (!existsSync(configDir)) {
|
||||||
|
mkdirSync(configDir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charge les clés API depuis le fichier de configuration
|
||||||
|
*/
|
||||||
|
loadKeys(): APIKeys {
|
||||||
|
const keys: APIKeys = {};
|
||||||
|
|
||||||
|
// Priorité 1: Variables d'environnement
|
||||||
|
if (process.env.OPENAI_API_KEY) {
|
||||||
|
keys.openai = process.env.OPENAI_API_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.MISTRAL_API_KEY) {
|
||||||
|
keys.mistral = process.env.MISTRAL_API_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priorité 2: Fichier de clés
|
||||||
|
if (existsSync(this.keysPath)) {
|
||||||
|
try {
|
||||||
|
const fileKeys = JSON.parse(readFileSync(this.keysPath, 'utf8'));
|
||||||
|
if (!keys.openai && fileKeys.openai) {
|
||||||
|
keys.openai = fileKeys.openai;
|
||||||
|
}
|
||||||
|
if (!keys.mistral && fileKeys.mistral) {
|
||||||
|
keys.mistral = fileKeys.mistral;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Erreur lors du chargement des clés:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sauvegarde les clés API dans le fichier de configuration
|
||||||
|
*/
|
||||||
|
saveKeys(keys: APIKeys): void {
|
||||||
|
try {
|
||||||
|
// Ne sauvegarder que les clés non définies en variable d'environnement
|
||||||
|
const keysToSave: APIKeys = {};
|
||||||
|
|
||||||
|
if (keys.openai && !process.env.OPENAI_API_KEY) {
|
||||||
|
keysToSave.openai = keys.openai;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.mistral && !process.env.MISTRAL_API_KEY) {
|
||||||
|
keysToSave.mistral = keys.mistral;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(this.keysPath, JSON.stringify(keysToSave, null, 2));
|
||||||
|
logger.info('Clés API sauvegardées avec succès');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Erreur lors de la sauvegarde des clés:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si au moins une clé API est disponible
|
||||||
|
*/
|
||||||
|
hasValidKeys(): boolean {
|
||||||
|
const keys = this.loadKeys();
|
||||||
|
return !!(keys.openai || keys.mistral);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demande interactivement les clés API manquantes
|
||||||
|
*/
|
||||||
|
async promptForMissingKeys(): Promise<APIKeys> {
|
||||||
|
const keys = this.loadKeys();
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
const question = (prompt: string): Promise<string> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(prompt, resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('\n🔑 Configuration des clés API');
|
||||||
|
console.log('─'.repeat(40));
|
||||||
|
|
||||||
|
if (!keys.openai) {
|
||||||
|
console.log('\nOpenAI (ChatGPT) non configuré.');
|
||||||
|
const wantOpenAI = await question('Voulez-vous configurer OpenAI ? (o/n): ');
|
||||||
|
|
||||||
|
if (wantOpenAI.toLowerCase() === 'o' || wantOpenAI.toLowerCase() === 'oui') {
|
||||||
|
const openaiKey = await question('Entrez votre clé API OpenAI: ');
|
||||||
|
if (openaiKey.trim()) {
|
||||||
|
keys.openai = openaiKey.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('✅ OpenAI configuré');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keys.mistral) {
|
||||||
|
console.log('\nMistral AI non configuré.');
|
||||||
|
const wantMistral = await question('Voulez-vous configurer Mistral AI ? (o/n): ');
|
||||||
|
|
||||||
|
if (wantMistral.toLowerCase() === 'o' || wantMistral.toLowerCase() === 'oui') {
|
||||||
|
const mistralKey = await question('Entrez votre clé API Mistral: ');
|
||||||
|
if (mistralKey.trim()) {
|
||||||
|
keys.mistral = mistralKey.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('✅ Mistral AI configuré');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier qu'au moins une clé est disponible
|
||||||
|
if (!keys.openai && !keys.mistral) {
|
||||||
|
console.log('\n❌ Erreur: Au moins une clé API est requise pour utiliser NeuraTerm.');
|
||||||
|
console.log('Vous pouvez aussi définir les variables d\'environnement:');
|
||||||
|
console.log(' export OPENAI_API_KEY="votre_clé"');
|
||||||
|
console.log(' export MISTRAL_API_KEY="votre_clé"');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder les nouvelles clés
|
||||||
|
this.saveKeys(keys);
|
||||||
|
|
||||||
|
console.log('\n✅ Configuration terminée !');
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour une clé API spécifique
|
||||||
|
*/
|
||||||
|
async updateKey(provider: 'openai' | 'mistral'): Promise<void> {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
const question = (prompt: string): Promise<string> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(prompt, resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`\n🔑 Mise à jour de la clé ${provider.toUpperCase()}`);
|
||||||
|
|
||||||
|
if (process.env[`${provider.toUpperCase()}_API_KEY`]) {
|
||||||
|
console.log(`⚠️ Attention: La clé ${provider.toUpperCase()} est définie en variable d'environnement.`);
|
||||||
|
console.log('Cette modification ne sera effective que pour cette session.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const newKey = await question(`Entrez la nouvelle clé API ${provider}: `);
|
||||||
|
|
||||||
|
if (!newKey.trim()) {
|
||||||
|
console.log('❌ Clé vide, annulation.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = this.loadKeys();
|
||||||
|
keys[provider] = newKey.trim();
|
||||||
|
this.saveKeys(keys);
|
||||||
|
|
||||||
|
console.log(`✅ Clé ${provider.toUpperCase()} mise à jour avec succès !`);
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime une clé API
|
||||||
|
*/
|
||||||
|
async removeKey(provider: 'openai' | 'mistral'): Promise<void> {
|
||||||
|
const keys = this.loadKeys();
|
||||||
|
|
||||||
|
if (process.env[`${provider.toUpperCase()}_API_KEY`]) {
|
||||||
|
console.log(`⚠️ La clé ${provider.toUpperCase()} est définie en variable d'environnement et ne peut pas être supprimée.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(this.keysPath)) {
|
||||||
|
try {
|
||||||
|
const fileKeys = JSON.parse(readFileSync(this.keysPath, 'utf8'));
|
||||||
|
delete fileKeys[provider];
|
||||||
|
writeFileSync(this.keysPath, JSON.stringify(fileKeys, null, 2));
|
||||||
|
console.log(`✅ Clé ${provider.toUpperCase()} supprimée avec succès !`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Erreur lors de la suppression de la clé:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche le statut des clés API
|
||||||
|
*/
|
||||||
|
displayKeysStatus(): void {
|
||||||
|
const keys = this.loadKeys();
|
||||||
|
|
||||||
|
console.log('\n🔑 Statut des clés API:');
|
||||||
|
console.log('─'.repeat(30));
|
||||||
|
|
||||||
|
// OpenAI
|
||||||
|
if (keys.openai) {
|
||||||
|
const source = process.env.OPENAI_API_KEY ? '(var. env.)' : '(fichier)';
|
||||||
|
const masked = this.maskKey(keys.openai);
|
||||||
|
console.log(`✅ OpenAI: ${masked} ${source}`);
|
||||||
|
} else {
|
||||||
|
console.log('❌ OpenAI: Non configuré');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mistral
|
||||||
|
if (keys.mistral) {
|
||||||
|
const source = process.env.MISTRAL_API_KEY ? '(var. env.)' : '(fichier)';
|
||||||
|
const masked = this.maskKey(keys.mistral);
|
||||||
|
console.log(`✅ Mistral: ${masked} ${source}`);
|
||||||
|
} else {
|
||||||
|
console.log('❌ Mistral: Non configuré');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Masque une clé API pour l'affichage
|
||||||
|
*/
|
||||||
|
private maskKey(key: string): string {
|
||||||
|
if (key.length <= 8) return '*'.repeat(key.length);
|
||||||
|
return key.substring(0, 4) + '*'.repeat(key.length - 8) + key.substring(key.length - 4);
|
||||||
|
}
|
||||||
|
}
|
@ -98,6 +98,19 @@ export class CommandProcessor {
|
|||||||
this.showConfig();
|
this.showConfig();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Nouvelles commandes pour les clés API
|
||||||
|
case 'keys':
|
||||||
|
this.showKeys();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'key':
|
||||||
|
await this.manageKey(args);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'setup':
|
||||||
|
await this.setupKeys();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Traiter comme une question à l'IA
|
// Traiter comme une question à l'IA
|
||||||
await this.handleAIQuery(command);
|
await this.handleAIQuery(command);
|
||||||
@ -157,6 +170,100 @@ export class CommandProcessor {
|
|||||||
console.log(JSON.stringify(this.config, null, 2));
|
console.log(JSON.stringify(this.config, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showKeys(): void {
|
||||||
|
const keyManager = this.dependencies.auth.getKeyManager();
|
||||||
|
keyManager.displayKeysStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async manageKey(args: string[]): Promise<void> {
|
||||||
|
const keyManager = this.dependencies.auth.getKeyManager();
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.log('\n🔑 Gestion des clés API:');
|
||||||
|
console.log('Usage:');
|
||||||
|
console.log(' key set <provider> - Définir/mettre à jour une clé (openai, mistral)');
|
||||||
|
console.log(' key remove <provider> - Supprimer une clé');
|
||||||
|
console.log(' key status - Afficher le statut des clés');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [action, provider] = args;
|
||||||
|
|
||||||
|
switch (action.toLowerCase()) {
|
||||||
|
case 'set':
|
||||||
|
case 'update':
|
||||||
|
if (!provider || !['openai', 'mistral'].includes(provider.toLowerCase())) {
|
||||||
|
console.log('❌ Provider invalide. Utilisez: openai ou mistral');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await keyManager.updateKey(provider.toLowerCase() as 'openai' | 'mistral');
|
||||||
|
// Redémarrer le client IA avec les nouvelles clés
|
||||||
|
await this.reloadAIClient();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'remove':
|
||||||
|
case 'delete':
|
||||||
|
if (!provider || !['openai', 'mistral'].includes(provider.toLowerCase())) {
|
||||||
|
console.log('❌ Provider invalide. Utilisez: openai ou mistral');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await keyManager.removeKey(provider.toLowerCase() as 'openai' | 'mistral');
|
||||||
|
await this.reloadAIClient();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
keyManager.displayKeysStatus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log('❌ Action invalide. Utilisez: set, remove, ou status');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setupKeys(): Promise<void> {
|
||||||
|
const keyManager = this.dependencies.auth.getKeyManager();
|
||||||
|
await keyManager.promptForMissingKeys();
|
||||||
|
await this.reloadAIClient();
|
||||||
|
console.log('\n✅ Configuration terminée! Vous pouvez maintenant utiliser NeuraTerm.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async reloadAIClient(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Recréer le client IA avec les nouvelles clés
|
||||||
|
const keyManager = this.dependencies.auth.getKeyManager();
|
||||||
|
const keys = keyManager.loadKeys();
|
||||||
|
|
||||||
|
// Vérifier qu'au moins une clé est disponible
|
||||||
|
if (!keys.openai && !keys.mistral) {
|
||||||
|
console.log('⚠️ Aucune clé API disponible. Le client IA ne peut pas être rechargé.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerConfig = {
|
||||||
|
openai: {
|
||||||
|
apiKey: keys.openai || '',
|
||||||
|
model: this.config.ai.openai?.model || 'gpt-4o-mini',
|
||||||
|
baseUrl: this.config.ai.openai?.baseUrl
|
||||||
|
},
|
||||||
|
mistral: {
|
||||||
|
apiKey: keys.mistral || '',
|
||||||
|
model: this.config.ai.mistral?.model || 'mistral-large-latest',
|
||||||
|
baseUrl: this.config.ai.mistral?.baseUrl
|
||||||
|
},
|
||||||
|
defaultProvider: keys.openai ? 'openai' : 'mistral'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recréer le client IA
|
||||||
|
const { AIClient } = await import('../ai/client.js');
|
||||||
|
this.dependencies.ai = new AIClient(providerConfig);
|
||||||
|
|
||||||
|
console.log('✅ Client IA rechargé avec les nouvelles clés');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('❌ Erreur lors du rechargement:', error instanceof Error ? error.message : String(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async handleAIQuery(query: string): Promise<void> {
|
private async handleAIQuery(query: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const response = await this.dependencies.ai.generateResponse({
|
const response = await this.dependencies.ai.generateResponse({
|
||||||
|
@ -62,27 +62,12 @@ export async function loadConfig(options: any = {}): Promise<Config> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charger depuis les variables d'environnement
|
// Les clés API seront gérées par KeyManager
|
||||||
if (process.env.OPENAI_API_KEY) {
|
// On ne fait plus la validation ici car elle sera faite par AuthManager
|
||||||
config.ai.openai = {
|
|
||||||
...config.ai.openai,
|
|
||||||
apiKey: process.env.OPENAI_API_KEY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.MISTRAL_API_KEY) {
|
|
||||||
config.ai.mistral = {
|
|
||||||
...config.ai.mistral,
|
|
||||||
apiKey: process.env.MISTRAL_API_KEY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fusionner avec les options passées en paramètre
|
// Fusionner avec les options passées en paramètre
|
||||||
config = { ...config, ...options };
|
config = { ...config, ...options };
|
||||||
|
|
||||||
// Validation
|
|
||||||
validateConfig(config);
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,10 +71,16 @@ Commandes générales:
|
|||||||
exit, quit - Quitter NeuraTerm
|
exit, quit - Quitter NeuraTerm
|
||||||
clear - Effacer l'écran
|
clear - Effacer l'écran
|
||||||
|
|
||||||
|
Gestion des clés API:
|
||||||
|
keys - Afficher le statut des clés API
|
||||||
|
key set <provider> - Définir/mettre à jour une clé (openai, mistral)
|
||||||
|
key remove <provider> - Supprimer une clé
|
||||||
|
key status - Afficher le statut des clés
|
||||||
|
setup - Configuration interactive des clés
|
||||||
|
|
||||||
Gestion des providers:
|
Gestion des providers:
|
||||||
providers - Lister les providers disponibles
|
providers - Lister les providers disponibles
|
||||||
provider <nom> - Changer de provider (openai, mistral)
|
provider <nom> - Changer de provider (openai, mistral)
|
||||||
models - Lister les modèles disponibles
|
|
||||||
|
|
||||||
Statistiques:
|
Statistiques:
|
||||||
stats - Afficher les statistiques d'utilisation
|
stats - Afficher les statistiques d'utilisation
|
||||||
@ -84,9 +90,13 @@ Statistiques:
|
|||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
config - Afficher la configuration actuelle
|
config - Afficher la configuration actuelle
|
||||||
set <param> <value> - Modifier un paramètre
|
|
||||||
|
|
||||||
Pour poser une question à l'IA, tapez simplement votre message.
|
Pour poser une question à l'IA, tapez simplement votre message.
|
||||||
|
|
||||||
|
Exemples:
|
||||||
|
key set openai - Configurer votre clé OpenAI
|
||||||
|
provider mistral - Basculer vers Mistral AI
|
||||||
|
Comment optimiser ce code Python ?
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user