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.
- package/README.md +54 -2
- package/ghost.js +113 -29
- package/package.json +5 -4
- package/test.js +36 -0
package/README.md
CHANGED
|
@@ -1,2 +1,54 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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}
|
|
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(); //
|
|
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
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
}
|
|
320
|
-
console.log(`${Colors.
|
|
398
|
+
} else {
|
|
399
|
+
console.log(`${Colors.GREEN}OK (Code sain)${Colors.ENDC}`);
|
|
321
400
|
}
|
|
322
401
|
} else {
|
|
323
|
-
console.log(`${Colors.
|
|
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
|
|
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": "
|
|
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
|
+
}
|