atlasia-ghost 0.0.1 → 0.1.0

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.
Files changed (4) hide show
  1. package/README.md +54 -2
  2. package/ghost.js +113 -29
  3. package/package.json +5 -4
  4. package/test.js +36 -0
package/README.md CHANGED
@@ -1,2 +1,54 @@
1
- # ghost
2
- Utilitaire de verification, validation, et de proposition de commits basé sur l'IA
1
+ # 👻 Ghost CLI
2
+
3
+ Assistant Git Intelligent (Node.js Edition) - Zéro-dépendance, compatible Windows, Mac et Linux.
4
+
5
+ Ghost analyse vos changements Git (`staged`), vérifie l'absence de secrets (clés API, tokens) et propose un message de commit professionnel suivant la convention **Conventional Commits**.
6
+
7
+ ## 🚀 Installation
8
+
9
+ Vous pouvez installer Ghost globalement via npm :
10
+
11
+ ```bash
12
+ npm install -g atlasia-ghost
13
+ ```
14
+
15
+ Ou l'utiliser directement avec npx :
16
+
17
+ ```bash
18
+ npx atlasia-ghost
19
+ ```
20
+
21
+ ## ⚙️ Configuration
22
+
23
+ Au premier lancement, Ghost vous demandera une **clé API Groq** (gratuite).
24
+ Vous pouvez en obtenir une ici : [https://console.groq.com](https://console.groq.com)
25
+
26
+ La configuration est stockée dans `~/.ghost`.
27
+
28
+ ## 🛠️ Utilisation
29
+
30
+ Préparez vos fichiers comme d'habitude :
31
+
32
+ ```bash
33
+ git add .
34
+ ghost
35
+ ```
36
+
37
+ ### Options disponibles
38
+
39
+ | Option | Description |
40
+ | :--- | :--- |
41
+ | `--model <name>` | Utiliser un modèle spécifique (ex: `llama-3.1-8b-instant`) |
42
+ | `--no-security` | Désactiver l'audit de sécurité (scan de secrets) |
43
+ | `--dry-run` | Générer le message sans effectuer le commit |
44
+ | `--help`, `-h` | Afficher l'aide |
45
+
46
+ ## 🛡️ Sécurité
47
+
48
+ Ghost effectue un double audit :
49
+ 1. **Local** : Scan par expressions régulières (Regex) et analyse d'entropie de Shannon pour détecter des patterns suspects.
50
+ 2. **IA** : En cas de doute, les fragments suspects sont analysés par l'IA pour confirmer s'il s'agit d'une faille réelle ou d'un faux positif.
51
+
52
+ ## 📄 Licence
53
+
54
+ MIT - [Adel Lamallam](https://github.com/lamallamadel)
package/ghost.js CHANGED
@@ -158,9 +158,61 @@ class AIEngine {
158
158
  }
159
159
  }
160
160
 
161
+ // ==============================================================================
162
+ // 🛠️ UTILS & ARG PARSER
163
+ // ==============================================================================
164
+ function parseArgs() {
165
+ const args = process.argv.slice(2);
166
+ const flags = {
167
+ model: null,
168
+ noSecurity: false,
169
+ dryRun: false,
170
+ help: false
171
+ };
172
+
173
+ for (let i = 0; i < args.length; i++) {
174
+ if (args[i] === '--model' && args[i + 1]) {
175
+ flags.model = args[i + 1];
176
+ i++;
177
+ } else if (args[i] === '--no-security') {
178
+ flags.noSecurity = true;
179
+ } else if (args[i] === '--dry-run') {
180
+ flags.dryRun = true;
181
+ } else if (args[i] === '--help' || args[i] === '-h') {
182
+ flags.help = true;
183
+ }
184
+ }
185
+ return flags;
186
+ }
187
+
188
+ function showHelp() {
189
+ console.log(`
190
+ ${Colors.BOLD}${Colors.CYAN}GHOST CLI v0.1.0${Colors.ENDC}
191
+ Assistant Git Intelligent basé sur l'IA (Groq)
192
+
193
+ ${Colors.BOLD}USAGE:${Colors.ENDC}
194
+ ghost [options]
195
+
196
+ ${Colors.BOLD}OPTIONS:${Colors.ENDC}
197
+ --model <name> Utiliser un modèle spécifique (ex: llama-3.1-8b-instant)
198
+ --no-security Désactiver l'audit de sécurité
199
+ --dry-run Générer le message sans effectuer le commit
200
+ --help, -h Afficher cette aide
201
+ `);
202
+ }
203
+
161
204
  // ==============================================================================
162
205
  // 🛡️ SCANNER DE SECURITE
163
206
  // ==============================================================================
207
+ const SECRET_REGEXES = [
208
+ { name: 'Generic API Key', regex: /([a-z0-9_-]{20,})/gi },
209
+ { name: 'Groq API Key', regex: /gsk_[a-zA-Z0-9]{48}/g },
210
+ { name: 'GitHub Token', regex: /gh[pous]_[a-zA-Z0-9]{36}/g },
211
+ { name: 'Slack Token', regex: /xox[baprs]-[0-9a-zA-Z]{10,48}/g },
212
+ { name: 'AWS Access Key', regex: /AKIA[0-9A-Z]{16}/g },
213
+ { name: 'Private Key', regex: /-----BEGIN (RSA|EC|PGP|OPENSSH) PRIVATE KEY-----/g }
214
+ ];
215
+
164
216
  function calculateShannonEntropy(data) {
165
217
  if (!data) return 0;
166
218
  const frequencies = {};
@@ -180,7 +232,20 @@ function calculateShannonEntropy(data) {
180
232
  function scanForSecrets(content) {
181
233
  if (!content) return [];
182
234
  const suspicious = [];
183
- // Regex : Cherche ce qui est entre quotes ou après un signe égal
235
+
236
+ // 1. Recherche via Regex ciblées
237
+ for (const { name, regex } of SECRET_REGEXES) {
238
+ const matches = content.match(regex);
239
+ if (matches) {
240
+ matches.forEach(m => {
241
+ if (m.length > 8) {
242
+ suspicious.push(`${m.substring(0, 15)}... (${name})`);
243
+ }
244
+ });
245
+ }
246
+ }
247
+
248
+ // 2. Recherche via Entropie (pour les secrets non-standard)
184
249
  const regex = /(['"])(.*?)(\1)|=\s*([^\s]+)/g;
185
250
  let match;
186
251
 
@@ -192,7 +257,10 @@ function scanForSecrets(content) {
192
257
 
193
258
  // Analyse mathématique (Entropie > 4.8 est souvent un secret)
194
259
  if (calculateShannonEntropy(candidate) > 4.8) {
195
- suspicious.push(candidate.substring(0, 15) + "...");
260
+ const display = candidate.substring(0, 15) + "...";
261
+ if (!suspicious.some(s => s.startsWith(display))) {
262
+ suspicious.push(`${display} (High Entropy)`);
263
+ }
196
264
  }
197
265
  }
198
266
  return suspicious;
@@ -232,7 +300,7 @@ function getStagedDiff() {
232
300
 
233
301
  for (let f of files) {
234
302
  f = f.trim().replace(/^"|"$/g, '');
235
- if (!f || SAFE_FILES.has(path.basename(f)) || SAFE_EXTENSIONS.has(path.extname(f))) continue;
303
+ if (!f) continue;
236
304
 
237
305
  const content = gitExec(['diff', '--cached', `"${f}"`]);
238
306
  if (content) {
@@ -262,8 +330,15 @@ function promptUser(question) {
262
330
  // 🚀 MAIN LOOP
263
331
  // ==============================================================================
264
332
  async function main() {
333
+ const flags = parseArgs();
334
+
335
+ if (flags.help) {
336
+ showHelp();
337
+ process.exit(0);
338
+ }
339
+
265
340
  console.clear();
266
- console.log(`\n${Colors.BOLD}${Colors.CYAN} 👻 GHOST CLI ${Colors.ENDC}${Colors.DIM} v1.0.0${Colors.ENDC}`);
341
+ console.log(`\n${Colors.BOLD}${Colors.CYAN} 👻 GHOST CLI ${Colors.ENDC}${Colors.DIM} v0.1.0${Colors.ENDC}`);
267
342
  console.log(`${Colors.DIM} ─────────────────────────────────────${Colors.ENDC}\n`);
268
343
 
269
344
  // 1. Vérification Git
@@ -275,7 +350,7 @@ async function main() {
275
350
 
276
351
  const config = new ConfigManager();
277
352
  const apiKey = await config.getApiKey();
278
- const model = config.getModel(); // Récupère le modèle configuré
353
+ const model = flags.model || config.getModel(); // Priorité au flag --model
279
354
  const ai = new AIEngine(apiKey, model);
280
355
 
281
356
  // 2. Récupération du Diff
@@ -293,34 +368,38 @@ async function main() {
293
368
  console.log(""); // Saut de ligne
294
369
 
295
370
  // 3. Audit de Sécurité
296
- process.stdout.write(`${Colors.BLUE}🛡️ [1/2] Audit de Sécurité... ${Colors.ENDC}`);
297
-
298
- const potentialLeaks = {};
299
- for (const [fname, content] of Object.entries(diffMap)) {
300
- const suspects = scanForSecrets(content);
301
- if (suspects.length > 0) potentialLeaks[fname] = suspects;
302
- }
303
-
304
- if (Object.keys(potentialLeaks).length > 0) {
305
- console.log(`\n${Colors.WARNING}⚠️ Entropie élevée détectée ! Analyse approfondie par l'IA...${Colors.ENDC}`);
306
- const valPrompt = `Analyse ces secrets potentiels : ${JSON.stringify(potentialLeaks)}. Réponds JSON {'is_breach': bool, 'reason': str}. Vrais secrets (API Keys) seulement.`;
371
+ if (!flags.noSecurity) {
372
+ process.stdout.write(`${Colors.BLUE}🛡️ [1/2] Audit de Sécurité... ${Colors.ENDC}`);
307
373
 
308
- try {
309
- const res = await ai.call("Tu es un expert sécurité.", valPrompt, 0.3, true);
310
- const audit = JSON.parse(res);
374
+ const potentialLeaks = {};
375
+ for (const [fname, content] of Object.entries(diffMap)) {
376
+ const suspects = scanForSecrets(content);
377
+ if (suspects.length > 0) potentialLeaks[fname] = suspects;
378
+ }
379
+
380
+ if (Object.keys(potentialLeaks).length > 0) {
381
+ console.log(`\n${Colors.WARNING}⚠️ Entropie élevée ou patterns suspects détectés ! Analyse approfondie par l'IA...${Colors.ENDC}`);
382
+ const valPrompt = `Analyse ces secrets potentiels : ${JSON.stringify(potentialLeaks)}. Réponds JSON {'is_breach': bool, 'reason': str}. Vrais secrets (API Keys, tokens) seulement. Ignore les exemples ou les faux positifs.`;
311
383
 
312
- if (audit.is_breach) {
313
- console.log(`\n${Colors.FAIL}❌ [BLOCAGE SÉCURITÉ] Secret détecté !${Colors.ENDC}`);
314
- console.log(`${Colors.FAIL} Raison : ${audit.reason}${Colors.ENDC}\n`);
315
- process.exit(1);
316
- } else {
317
- console.log(`${Colors.GREEN} Faux positifs confirmés (Sûr).${Colors.ENDC}`);
384
+ try {
385
+ const res = await ai.call("Tu es un expert sécurité.", valPrompt, 0.3, true);
386
+ const audit = JSON.parse(res);
387
+
388
+ if (audit.is_breach) {
389
+ console.log(`\n${Colors.FAIL} [BLOCAGE SÉCURITÉ] Secret détecté !${Colors.ENDC}`);
390
+ console.log(`${Colors.FAIL} Raison : ${audit.reason}${Colors.ENDC}\n`);
391
+ process.exit(1);
392
+ } else {
393
+ console.log(`${Colors.GREEN}✅ Faux positifs confirmés (Sûr).${Colors.ENDC}`);
394
+ }
395
+ } catch (e) {
396
+ console.log(`${Colors.FAIL}Erreur audit IA: ${e.message}${Colors.ENDC}`);
318
397
  }
319
- } catch (e) {
320
- console.log(`${Colors.FAIL}Erreur audit IA: ${e.message}${Colors.ENDC}`);
398
+ } else {
399
+ console.log(`${Colors.GREEN}OK (Code sain)${Colors.ENDC}`);
321
400
  }
322
401
  } else {
323
- console.log(`${Colors.GREEN}OK (Code sain)${Colors.ENDC}`);
402
+ console.log(`${Colors.WARNING} Audit de sécurité ignoré (--no-security)${Colors.ENDC}`);
324
403
  }
325
404
 
326
405
  // 4. Génération
@@ -338,6 +417,11 @@ async function main() {
338
417
  console.log(`${Colors.BOLD}${commitMsg}${Colors.ENDC}`);
339
418
  console.log(`${Colors.CYAN}──────────────────────────────────────────────────${Colors.ENDC}\n`);
340
419
 
420
+ if (flags.dryRun) {
421
+ console.log(`${Colors.WARNING}✨ Mode --dry-run : Aucun commit effectué.${Colors.ENDC}\n`);
422
+ process.exit(0);
423
+ }
424
+
341
425
  const action = await promptUser(`${Colors.BOLD}[Enter]${Colors.ENDC} Valider | ${Colors.BOLD}[n]${Colors.ENDC} Annuler : `);
342
426
 
343
427
  if (action.toLowerCase() === 'n') {
@@ -358,4 +442,4 @@ async function main() {
358
442
 
359
443
  if (require.main === module) {
360
444
  main().catch(console.error);
361
- }
445
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "atlasia-ghost",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Utilitaire de verification, validation, et de proposition de commits basé sur l'IA",
5
5
  "main": "ghost.js",
6
6
  "bin": {
7
7
  "ghost": "./ghost.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "echo \"ghost raider\"",
10
+ "test": "node test.js",
11
11
  "start": "node ghost.js"
12
12
  },
13
13
  "repository": {
@@ -31,5 +31,6 @@
31
31
  "homepage": "https://github.com/lamallamadel/ghost#readme",
32
32
  "engines": {
33
33
  "node": ">=14.0.0"
34
- }
35
- }
34
+ },
35
+ "dependencies": {}
36
+ }
package/test.js ADDED
@@ -0,0 +1,36 @@
1
+ const assert = require('assert');
2
+ const { execSync } = require('child_process');
3
+ const path = require('path');
4
+
5
+ // Mock Colors if needed or import from ghost.js if it were exported
6
+ const Colors = {
7
+ CYAN: '', BOLD: '', ENDC: '', DIM: '', GREEN: '', FAIL: ''
8
+ };
9
+
10
+ console.log('🧪 Running Ghost CLI Tests...\n');
11
+
12
+ try {
13
+ // Test 1: Version check
14
+ const packageJson = require('./package.json');
15
+ assert.strictEqual(packageJson.version, '0.1.0', 'package.json version should be 0.1.0');
16
+ console.log('✅ Test 1: package.json version is correct');
17
+
18
+ // Test 2: Help flag
19
+ const helpOutput = execSync('node ghost.js --help', { encoding: 'utf8' });
20
+ assert.ok(helpOutput.includes('GHOST CLI v0.1.0'), 'Help output should contain version');
21
+ assert.ok(helpOutput.includes('--model'), 'Help output should contain --model flag');
22
+ assert.ok(helpOutput.includes('--no-security'), 'Help output should contain --no-security flag');
23
+ assert.ok(helpOutput.includes('--dry-run'), 'Help output should contain --dry-run flag');
24
+ console.log('✅ Test 2: Help flag output is correct');
25
+
26
+ // Test 3: Internal logic (Entropy)
27
+ // We can't easily import internal functions without exporting them
28
+ // But we can test if the file is valid JS
29
+ execSync('node --check ghost.js');
30
+ console.log('✅ Test 3: ghost.js syntax is valid');
31
+
32
+ console.log('\n🎉 All tests passed successfully!');
33
+ } catch (error) {
34
+ console.error(`\n❌ Test failed: ${error.message}`);
35
+ process.exit(1);
36
+ }