ai-secret-scout 2.3.0 → 2.4.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.fr.md +6 -10
- package/README.md +7 -7
- package/ai_secret_scout.py +132 -25
- package/bin/aiscout.js +7 -2
- package/bin/check-environment.js +189 -0
- package/package.json +2 -1
package/README.fr.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<img src="https://img.shields.io/badge/D%C3%A9pendances-Zero%20External-22c55e?logo=python&logoColor=white" alt="Zéro Dépendance">
|
|
19
19
|
<img src="https://img.shields.io/badge/Watchdog-Temps%20R%C3%A9el%20%26%20Desktop%20Alerts-F59E0B?logo=airplayaudio&logoColor=white" alt="Watchdog Temps Réel">
|
|
20
20
|
<img src="https://img.shields.io/badge/Safe%20Restore-Gestionnaire%20Backups-7C3AED?logo=securityscorecard&logoColor=white" alt="Safe Restore">
|
|
21
|
-
<img src="https://img.shields.io/badge/Plateforme-Linux%20%7C%
|
|
21
|
+
<img src="https://img.shields.io/badge/Plateforme-Linux%20%7C%20Windows%20%7C%20WSL-0078D6?logo=windows&logoColor=white" alt="Linux | Windows | WSL">
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
@@ -502,13 +502,9 @@ aiscout --home-dir /home/autre_utilisateur
|
|
|
502
502
|
|
|
503
503
|
## 🏗️ Architecture Technique
|
|
504
504
|
|
|
505
|
-
* **Langage** : Python 3.10+ standard pur (`os`, `sys`, `re`, `json`, `
|
|
506
|
-
* **Zéro Dépendance Externe** : Aucun package tiers `pip`. Conçu
|
|
507
|
-
* **Systèmes Supportés** : **Linux** (natif Wayland / X11)
|
|
508
|
-
* **Notification
|
|
509
|
-
* **
|
|
510
|
-
* **Configuration** : `~/.config/aiscout/rules.json`
|
|
511
|
-
* **Liens symboliques exécutables** :
|
|
512
|
-
- `~/.local/bin/aiscout`
|
|
513
|
-
- `~/.local/bin/ai-secret-scout`
|
|
505
|
+
* **Langage** : Python 3.10+ standard pur (`os`, `sys`, `re`, `json`, `unicodedata`, `shutil`, `argparse`, `subprocess`, avec `msvcrt` sous Windows et `termios`/`tty`/`select` sous POSIX).
|
|
506
|
+
* **Zéro Dépendance Externe** : Aucun package tiers `pip`. Conçu exclusivement avec la bibliothèque standard Python 3.
|
|
507
|
+
* **Systèmes Supportés** : **Linux** (natif Wayland / X11), **Windows 10/11** (natif PowerShell, CMD, Windows Terminal) et **WSL / WSL2**.
|
|
508
|
+
* **Système de Notification** : Détection automatique de `notify-send` sous Linux et toasts natifs PowerShell sous Windows.
|
|
509
|
+
* **Configuration** : `~/.config/aiscout/rules.json` (ou `%APPDATA%\aiscout\rules.json` sous Windows).
|
|
514
510
|
* **Confidentialité absolue** : 100% local, aucun flux réseau sortant, zéro télémétrie.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<img src="https://img.shields.io/badge/Dependencies-Zero%20External-22c55e?logo=python&logoColor=white" alt="Zero Dependencies">
|
|
19
19
|
<img src="https://img.shields.io/badge/Watchdog-Real--Time%20%26%20Desktop%20Alerts-F59E0B?logo=airplayaudio&logoColor=white" alt="Real-Time Watchdog">
|
|
20
20
|
<img src="https://img.shields.io/badge/Safe%20Restore-Backup%20Manager-7C3AED?logo=securityscorecard&logoColor=white" alt="Safe Restore">
|
|
21
|
-
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%
|
|
21
|
+
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows%20%7C%20WSL-0078D6?logo=windows&logoColor=white" alt="Linux | Windows | WSL">
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
@@ -107,8 +107,8 @@ When using autonomous coding assistants (**Claude Code**, **Google Antigravity /
|
|
|
107
107
|
|
|
108
108
|
## 🚀 Quickstart
|
|
109
109
|
|
|
110
|
-
> [!
|
|
111
|
-
> **Platform Support**: `aiscout`
|
|
110
|
+
> [!TIP]
|
|
111
|
+
> **Cross-Platform Support**: `aiscout` runs natively on **Linux** (Wayland & X11), **Windows 10/11** (PowerShell, Command Prompt, Windows Terminal), and **WSL / WSL2**. Zero external dependencies required.
|
|
112
112
|
|
|
113
113
|
`aiscout` can be executed instantly without installation or installed globally:
|
|
114
114
|
|
|
@@ -505,11 +505,11 @@ aiscout --home-dir /home/other_user
|
|
|
505
505
|
|
|
506
506
|
## 🏗️ Technical Architecture
|
|
507
507
|
|
|
508
|
-
* **Core Engine**: Pure Python 3.10+ standard library (`os`, `sys`, `re`, `json`, `
|
|
508
|
+
* **Core Engine**: Pure Python 3.10+ standard library (`os`, `sys`, `re`, `json`, `unicodedata`, `shutil`, `argparse`, `subprocess`, with `msvcrt` on Windows and `termios`/`tty`/`select` on POSIX).
|
|
509
509
|
* **Zero External Dependencies**: Zero `pip` dependencies required.
|
|
510
|
-
* **Supported Platforms**: **Linux** (native Wayland / X11)
|
|
511
|
-
* **Notification System**: Auto-detects `notify-send`
|
|
512
|
-
* **Configuration Path**: `~/.config/aiscout/rules.json`
|
|
510
|
+
* **Supported Platforms**: **Linux** (native Wayland / X11), **Windows 10/11** (native PowerShell, CMD, Windows Terminal), and **WSL / WSL2**.
|
|
511
|
+
* **Notification System**: Auto-detects `notify-send` on Linux and native PowerShell toast notifications on Windows.
|
|
512
|
+
* **Configuration Path**: `~/.config/aiscout/rules.json` (or `%APPDATA%\aiscout\rules.json` on Windows).
|
|
513
513
|
* **Zero Telemetry**: 100% local execution, zero outbound network traffic, complete privacy guarantee.
|
|
514
514
|
|
|
515
515
|
---
|
package/ai_secret_scout.py
CHANGED
|
@@ -16,9 +16,7 @@ import time
|
|
|
16
16
|
import json
|
|
17
17
|
import math
|
|
18
18
|
import shutil
|
|
19
|
-
import
|
|
20
|
-
import termios
|
|
21
|
-
import tty
|
|
19
|
+
import platform
|
|
22
20
|
import argparse
|
|
23
21
|
import subprocess
|
|
24
22
|
import unicodedata
|
|
@@ -27,7 +25,23 @@ from datetime import datetime
|
|
|
27
25
|
from collections import Counter
|
|
28
26
|
from typing import List, Dict, Any, Optional, Tuple
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
IS_WINDOWS = sys.platform == "win32"
|
|
29
|
+
|
|
30
|
+
if IS_WINDOWS:
|
|
31
|
+
import msvcrt
|
|
32
|
+
# Active les séquences d'échappement VT100 / ANSI sous console Windows
|
|
33
|
+
os.system("")
|
|
34
|
+
try:
|
|
35
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
36
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
else:
|
|
40
|
+
import select
|
|
41
|
+
import termios
|
|
42
|
+
import tty
|
|
43
|
+
|
|
44
|
+
VERSION = "2.4.0"
|
|
31
45
|
|
|
32
46
|
# --- INTERNATIONALISATION (I18N) ---
|
|
33
47
|
def detect_default_lang() -> str:
|
|
@@ -554,12 +568,15 @@ PATTERNS = {
|
|
|
554
568
|
}
|
|
555
569
|
}
|
|
556
570
|
|
|
557
|
-
# --- CONFIGURATION ET RÈGLES PERSONNALISÉES (
|
|
558
|
-
|
|
571
|
+
# --- CONFIGURATION ET RÈGLES PERSONNALISÉES (rules.json) ---
|
|
572
|
+
if IS_WINDOWS and os.getenv("APPDATA"):
|
|
573
|
+
CONFIG_DIR = Path(os.getenv("APPDATA")) / "aiscout"
|
|
574
|
+
else:
|
|
575
|
+
CONFIG_DIR = Path.home() / ".config" / "aiscout"
|
|
559
576
|
CUSTOM_RULES_FILE = CONFIG_DIR / "rules.json"
|
|
560
577
|
|
|
561
578
|
def load_custom_rules() -> Dict[str, Dict[str, str]]:
|
|
562
|
-
"""Charge les règles personnalisées de l'utilisateur depuis
|
|
579
|
+
"""Charge les règles personnalisées de l'utilisateur depuis rules.json."""
|
|
563
580
|
if not CUSTOM_RULES_FILE.exists():
|
|
564
581
|
try:
|
|
565
582
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
@@ -596,7 +613,33 @@ def load_custom_rules() -> Dict[str, Dict[str, str]]:
|
|
|
596
613
|
|
|
597
614
|
|
|
598
615
|
def send_desktop_notification(title: str, message: str, severity: str = "normal"):
|
|
599
|
-
"""Envoie une notification de bureau native sous Linux / KDE
|
|
616
|
+
"""Envoie une notification de bureau native sous Linux / KDE / GNOME ou Windows 10/11."""
|
|
617
|
+
if IS_WINDOWS:
|
|
618
|
+
clean_title = title.replace('"', '`"').replace("'", "''")
|
|
619
|
+
clean_msg = message.replace('"', '`"').replace("'", "''").replace("\n", " ")
|
|
620
|
+
ps_script = (
|
|
621
|
+
f'[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms"); '
|
|
622
|
+
f'$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon; '
|
|
623
|
+
f'$objNotifyIcon.Icon = [System.Drawing.SystemIcons]::Shield; '
|
|
624
|
+
f'$objNotifyIcon.BalloonTipIcon = "Warning"; '
|
|
625
|
+
f'$objNotifyIcon.BalloonTipTitle = "{clean_title}"; '
|
|
626
|
+
f'$objNotifyIcon.BalloonTipText = "{clean_msg}"; '
|
|
627
|
+
f'$objNotifyIcon.Visible = $True; '
|
|
628
|
+
f'$objNotifyIcon.ShowBalloonTip(5000); '
|
|
629
|
+
f'Start-Sleep -Seconds 1; '
|
|
630
|
+
f'$objNotifyIcon.Dispose()'
|
|
631
|
+
)
|
|
632
|
+
try:
|
|
633
|
+
subprocess.Popen(
|
|
634
|
+
["powershell", "-NoProfile", "-NonInteractive", "-Command", ps_script],
|
|
635
|
+
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0,
|
|
636
|
+
stdout=subprocess.DEVNULL,
|
|
637
|
+
stderr=subprocess.DEVNULL
|
|
638
|
+
)
|
|
639
|
+
except Exception:
|
|
640
|
+
pass
|
|
641
|
+
return
|
|
642
|
+
|
|
600
643
|
if shutil.which("notify-send"):
|
|
601
644
|
urgency = "critical" if severity in ("CRITIQUE", "ÉLEVÉ") else "normal"
|
|
602
645
|
try:
|
|
@@ -667,7 +710,40 @@ def display_len(s: str) -> int:
|
|
|
667
710
|
|
|
668
711
|
|
|
669
712
|
def read_key() -> str:
|
|
670
|
-
"""Lecture robuste des frappes clavier
|
|
713
|
+
"""Lecture robuste des frappes clavier (Linux / macOS / Windows natif)."""
|
|
714
|
+
if IS_WINDOWS:
|
|
715
|
+
while not msvcrt.kbhit():
|
|
716
|
+
time.sleep(0.02)
|
|
717
|
+
ch = msvcrt.getwch()
|
|
718
|
+
if ch in ("\x00", "\xe0"):
|
|
719
|
+
# Touche spéciale sous Windows (flèches, pagination, etc.)
|
|
720
|
+
code = msvcrt.getwch()
|
|
721
|
+
arrows = {
|
|
722
|
+
"H": "UP",
|
|
723
|
+
"P": "DOWN",
|
|
724
|
+
"K": "LEFT",
|
|
725
|
+
"M": "RIGHT",
|
|
726
|
+
"I": "PAGE_UP",
|
|
727
|
+
"Q": "PAGE_DOWN",
|
|
728
|
+
"G": "HOME",
|
|
729
|
+
"O": "END",
|
|
730
|
+
"S": "DELETE"
|
|
731
|
+
}
|
|
732
|
+
return arrows.get(code, "")
|
|
733
|
+
if ch in ("\r", "\n"):
|
|
734
|
+
return "ENTER"
|
|
735
|
+
if ch == " ":
|
|
736
|
+
return "SPACE"
|
|
737
|
+
if ch == "\t":
|
|
738
|
+
return "TAB"
|
|
739
|
+
if ch in ("\x08", "\x7f"):
|
|
740
|
+
return "BACKSPACE"
|
|
741
|
+
if ch == "\x1b":
|
|
742
|
+
return "ESC"
|
|
743
|
+
if ch == "\x03":
|
|
744
|
+
return "CTRL_C"
|
|
745
|
+
return ch
|
|
746
|
+
|
|
671
747
|
fd = sys.stdin.fileno()
|
|
672
748
|
old = termios.tcgetattr(fd)
|
|
673
749
|
try:
|
|
@@ -813,11 +889,26 @@ class ScoutEngine:
|
|
|
813
889
|
self.home / ".continue" / "sessions",
|
|
814
890
|
self.home / ".aider.chat.history.md",
|
|
815
891
|
]
|
|
892
|
+
# Emplacements Windows spécifiques (%APPDATA% et %USERPROFILE%)
|
|
893
|
+
appdata = os.getenv("APPDATA")
|
|
894
|
+
if appdata:
|
|
895
|
+
ad = Path(appdata)
|
|
896
|
+
targets.extend([
|
|
897
|
+
ad / "Cursor" / "User" / "workspaceStorage",
|
|
898
|
+
ad / "Claude" / "projects",
|
|
899
|
+
ad / "Code" / "User" / "workspaceStorage"
|
|
900
|
+
])
|
|
901
|
+
localappdata = os.getenv("LOCALAPPDATA")
|
|
902
|
+
if localappdata:
|
|
903
|
+
lad = Path(localappdata)
|
|
904
|
+
targets.extend([
|
|
905
|
+
lad / "Programs" / "cursor" / "resources"
|
|
906
|
+
])
|
|
816
907
|
return [p for p in targets if p.exists()]
|
|
817
908
|
|
|
818
909
|
def identify_tool(self, file_path: str) -> str:
|
|
819
|
-
fp = str(file_path)
|
|
820
|
-
if ".claude" in fp:
|
|
910
|
+
fp = str(file_path).lower().replace("\\", "/")
|
|
911
|
+
if ".claude" in fp or "/claude/" in fp:
|
|
821
912
|
return "Claude Code"
|
|
822
913
|
elif ".gemini" in fp or "antigravity" in fp:
|
|
823
914
|
return "Antigravity (Gemini)"
|
|
@@ -825,7 +916,7 @@ class ScoutEngine:
|
|
|
825
916
|
return "Codex"
|
|
826
917
|
elif ".copilot" in fp:
|
|
827
918
|
return "GitHub Copilot"
|
|
828
|
-
elif ".cursor" in fp:
|
|
919
|
+
elif ".cursor" in fp or "/cursor/" in fp:
|
|
829
920
|
return "Cursor"
|
|
830
921
|
elif ".continue" in fp:
|
|
831
922
|
return "Continue.dev"
|
|
@@ -1203,8 +1294,8 @@ class ScoutTUI:
|
|
|
1203
1294
|
return " " * pad + text
|
|
1204
1295
|
|
|
1205
1296
|
def build_top_bar(self, width: int) -> str:
|
|
1206
|
-
user = os.getenv("USER"
|
|
1207
|
-
host =
|
|
1297
|
+
user = os.getenv("USER") or os.getenv("USERNAME") or "user"
|
|
1298
|
+
host = platform.node() or "localhost"
|
|
1208
1299
|
date_str = datetime.now().strftime("%d/%m/%Y %H:%M")
|
|
1209
1300
|
u_lbl = t("user_label")
|
|
1210
1301
|
h_lbl = t("host_label")
|
|
@@ -1899,11 +1990,12 @@ class ScoutTUI:
|
|
|
1899
1990
|
|
|
1900
1991
|
def export_markdown(self, path: Path, items: List[Finding]):
|
|
1901
1992
|
is_fr = (get_lang() == "fr")
|
|
1993
|
+
user_name = os.getenv("USER") or os.getenv("USERNAME") or "user"
|
|
1902
1994
|
with open(path, "w", encoding="utf-8") as f:
|
|
1903
1995
|
if is_fr:
|
|
1904
1996
|
f.write(f"# Rapport d'Audit de Sécurité — Transcriptions d'IA\n\n")
|
|
1905
1997
|
f.write(f"- **Généré le** : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
1906
|
-
f.write(f"- **Utilisateur** : `{
|
|
1998
|
+
f.write(f"- **Utilisateur** : `{user_name}`\n")
|
|
1907
1999
|
f.write(f"- **Total secrets identifiés** : {len(items)}\n\n")
|
|
1908
2000
|
f.write("## Synthèse par Niveau de Sévérité\n\n")
|
|
1909
2001
|
f.write(f"- **Critique** : {sum(1 for x in items if x.severity in ('CRITIQUE', 'CRITICAL'))}\n")
|
|
@@ -1917,7 +2009,7 @@ class ScoutTUI:
|
|
|
1917
2009
|
else:
|
|
1918
2010
|
f.write(f"# Security Audit Report — AI Assistant Histories\n\n")
|
|
1919
2011
|
f.write(f"- **Generated on** : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
1920
|
-
f.write(f"- **User** : `{
|
|
2012
|
+
f.write(f"- **User** : `{user_name}`\n")
|
|
1921
2013
|
f.write(f"- **Total exposed secrets** : {len(items)}\n\n")
|
|
1922
2014
|
f.write("## Summary by Severity Level\n\n")
|
|
1923
2015
|
f.write(f"- **Critical** : {sum(1 for x in items if x.severity in ('CRITIQUE', 'CRITICAL'))}\n")
|
|
@@ -2147,10 +2239,12 @@ class ScoutTUI:
|
|
|
2147
2239
|
scans_count = 0
|
|
2148
2240
|
pulse = False
|
|
2149
2241
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2242
|
+
if not IS_WINDOWS:
|
|
2243
|
+
fd = sys.stdin.fileno()
|
|
2244
|
+
old_term = termios.tcgetattr(fd)
|
|
2153
2245
|
tty.setraw(fd)
|
|
2246
|
+
|
|
2247
|
+
try:
|
|
2154
2248
|
while True:
|
|
2155
2249
|
pulse = not pulse
|
|
2156
2250
|
width = self.get_width()
|
|
@@ -2207,11 +2301,23 @@ class ScoutTUI:
|
|
|
2207
2301
|
sys.stdout.flush()
|
|
2208
2302
|
|
|
2209
2303
|
# Attente non-bloquante d'une frappe clavier (1.0 seconde)
|
|
2210
|
-
|
|
2211
|
-
if
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2304
|
+
interrupted = False
|
|
2305
|
+
if IS_WINDOWS:
|
|
2306
|
+
for _ in range(10):
|
|
2307
|
+
if msvcrt.kbhit():
|
|
2308
|
+
ch = msvcrt.getwch()
|
|
2309
|
+
if ch in ("q", "Q", "\x1b", "\x03"):
|
|
2310
|
+
interrupted = True
|
|
2311
|
+
break
|
|
2312
|
+
time.sleep(0.1)
|
|
2313
|
+
else:
|
|
2314
|
+
r, _, _ = select.select([fd], [], [], 1.0)
|
|
2315
|
+
if r:
|
|
2316
|
+
key_bytes = os.read(fd, 32)
|
|
2317
|
+
if key_bytes in (b"q", b"Q", b"\x1b", b"\x03"):
|
|
2318
|
+
interrupted = True
|
|
2319
|
+
if interrupted:
|
|
2320
|
+
break
|
|
2215
2321
|
|
|
2216
2322
|
# Analyse des répertoires pour détecter fichiers nouveaux ou modifiés
|
|
2217
2323
|
current_files = []
|
|
@@ -2262,7 +2368,8 @@ class ScoutTUI:
|
|
|
2262
2368
|
events_log = events_log[-50:]
|
|
2263
2369
|
|
|
2264
2370
|
finally:
|
|
2265
|
-
|
|
2371
|
+
if not IS_WINDOWS:
|
|
2372
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_term)
|
|
2266
2373
|
|
|
2267
2374
|
|
|
2268
2375
|
# --- POINT D'ENTRÉE ---
|
package/bin/aiscout.js
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const { spawn } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { runCheck } = require('./check-environment');
|
|
6
|
+
|
|
7
|
+
// Valide la présence et la version de Node (>=16) et Python (>=3.10)
|
|
8
|
+
const pyCmd = runCheck(false);
|
|
5
9
|
|
|
6
10
|
const pyScript = path.join(__dirname, '..', 'ai_secret_scout.py');
|
|
7
11
|
|
|
8
|
-
const child = spawn(
|
|
12
|
+
const child = spawn(pyCmd, [pyScript, ...process.argv.slice(2)], {
|
|
9
13
|
stdio: 'inherit',
|
|
10
|
-
env: process.env
|
|
14
|
+
env: process.env,
|
|
15
|
+
shell: process.platform === 'win32'
|
|
11
16
|
});
|
|
12
17
|
|
|
13
18
|
child.on('error', (err) => {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
function isFrenchLocale() {
|
|
7
|
+
const envLang = (process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || '').toLowerCase();
|
|
8
|
+
return envLang.includes('fr');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function checkNodeVersion() {
|
|
12
|
+
const nodeVer = process.version;
|
|
13
|
+
const major = parseInt(process.versions.node.split('.')[0], 10);
|
|
14
|
+
return {
|
|
15
|
+
ok: major >= 16,
|
|
16
|
+
version: nodeVer,
|
|
17
|
+
major
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkPythonVersion() {
|
|
22
|
+
const candidates = process.platform === 'win32'
|
|
23
|
+
? ['python', 'py', 'python3']
|
|
24
|
+
: ['python3', 'python'];
|
|
25
|
+
|
|
26
|
+
for (const cmd of candidates) {
|
|
27
|
+
try {
|
|
28
|
+
const output = execSync(`${cmd} -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"`, {
|
|
29
|
+
encoding: 'utf-8',
|
|
30
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
31
|
+
timeout: 3000
|
|
32
|
+
}).trim();
|
|
33
|
+
|
|
34
|
+
const parts = output.split('.').map(n => parseInt(n, 10));
|
|
35
|
+
const major = parts[0];
|
|
36
|
+
const minor = parts[1];
|
|
37
|
+
|
|
38
|
+
if (major === 3 && minor >= 10) {
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
cmd,
|
|
42
|
+
version: output,
|
|
43
|
+
tooOld: false
|
|
44
|
+
};
|
|
45
|
+
} else if (major === 3 || major === 2) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
cmd,
|
|
49
|
+
version: output,
|
|
50
|
+
tooOld: true
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Ignore and try next candidate
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
cmd: null,
|
|
61
|
+
version: null,
|
|
62
|
+
tooOld: false
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getInstallSuggestion(isFr) {
|
|
67
|
+
if (process.platform === 'win32') {
|
|
68
|
+
if (isFr) {
|
|
69
|
+
return '👉 Sous Windows, vous pouvez installer Python 3.10+ avec winget ou depuis le site officiel :\n winget install Python.Python.3.12\n ou téléchargez l\'installeur : https://www.python.org/downloads/';
|
|
70
|
+
}
|
|
71
|
+
return '👉 On Windows, you can install Python 3.10+ via winget or from the official website:\n winget install Python.Python.3.12\n or download the installer from: https://www.python.org/downloads/';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let pkgManager = 'apt';
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync('/etc/os-release')) {
|
|
77
|
+
const osRelease = fs.readFileSync('/etc/os-release', 'utf-8');
|
|
78
|
+
if (/ID(_LIKE)?=.*(fedora|rhel|centos|nobara)/i.test(osRelease)) {
|
|
79
|
+
pkgManager = 'dnf';
|
|
80
|
+
} else if (/ID(_LIKE)?=.*(arch|manjaro)/i.test(osRelease)) {
|
|
81
|
+
pkgManager = 'pacman';
|
|
82
|
+
} else if (/ID(_LIKE)?=.*(suse)/i.test(osRelease)) {
|
|
83
|
+
pkgManager = 'zypper';
|
|
84
|
+
} else if (/ID(_LIKE)?=.*(alpine)/i.test(osRelease)) {
|
|
85
|
+
pkgManager = 'apk';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Standard fallback
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const commands = {
|
|
93
|
+
dnf: 'sudo dnf install -y python3',
|
|
94
|
+
apt: 'sudo apt update && sudo apt install -y python3',
|
|
95
|
+
pacman: 'sudo pacman -S python',
|
|
96
|
+
zypper: 'sudo zypper install python3',
|
|
97
|
+
apk: 'apk add python3'
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const cmd = commands[pkgManager] || 'sudo apt install python3';
|
|
101
|
+
|
|
102
|
+
if (isFr) {
|
|
103
|
+
return `👉 Commande d'installation recommandée pour votre distribution :\n ${cmd}`;
|
|
104
|
+
}
|
|
105
|
+
return `👉 Recommended installation command for your distribution:\n ${cmd}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function runCheck(isPostinstall = false) {
|
|
109
|
+
const isFr = isFrenchLocale();
|
|
110
|
+
const nodeStatus = checkNodeVersion();
|
|
111
|
+
const pyStatus = checkPythonVersion();
|
|
112
|
+
|
|
113
|
+
if (isPostinstall) {
|
|
114
|
+
// Mode postinstall lors du npm install -g
|
|
115
|
+
if (nodeStatus.ok && pyStatus.ok) {
|
|
116
|
+
console.log('\x1b[32m✔ AI Secret Scout - Environment check passed:\x1b[0m');
|
|
117
|
+
console.log(` • Node.js : ${nodeStatus.version} (OK)`);
|
|
118
|
+
console.log(` • Python : ${pyStatus.version} (${pyStatus.cmd} - OK)`);
|
|
119
|
+
console.log('\x1b[36m🚀 AI Secret Scout is ready to run! Type: aiscout\x1b[0m\n');
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!nodeStatus.ok) {
|
|
124
|
+
console.warn(`\x1b[33m⚠️ Warning: Node.js version ${nodeStatus.version} detected. AI Secret Scout recommends Node.js >= 16.0.0.\x1b[0m`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!pyStatus.ok) {
|
|
128
|
+
console.warn('\x1b[33m──────────────────────────────────────────────────────────────────\x1b[0m');
|
|
129
|
+
if (pyStatus.tooOld) {
|
|
130
|
+
console.warn(`\x1b[33m⚠️ Warning: Python ${pyStatus.version} found via '${pyStatus.cmd}', but AI Secret Scout requires Python >= 3.10.\x1b[0m`);
|
|
131
|
+
} else {
|
|
132
|
+
console.warn('\x1b[33m⚠️ Warning: Python 3 was not detected on your system.\x1b[0m');
|
|
133
|
+
}
|
|
134
|
+
console.warn(isFr
|
|
135
|
+
? 'AI Secret Scout nécessite Python 3.10+ pour exécuter le moteur d\'analyse TUI.'
|
|
136
|
+
: 'AI Secret Scout requires Python 3.10+ to run the core TUI security engine.'
|
|
137
|
+
);
|
|
138
|
+
console.warn(getInstallSuggestion(isFr));
|
|
139
|
+
console.warn('\x1b[33m──────────────────────────────────────────────────────────────────\x1b[0m\n');
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Mode runtime direct (aiscout execution)
|
|
145
|
+
if (!nodeStatus.ok) {
|
|
146
|
+
console.error(isFr
|
|
147
|
+
? `❌ Erreur : Version de Node.js obsolète (${nodeStatus.version}). Node.js >= 16.0.0 est requis.`
|
|
148
|
+
: `❌ Error: Outdated Node.js version (${nodeStatus.version}). Node.js >= 16.0.0 is required.`
|
|
149
|
+
);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!pyStatus.ok) {
|
|
154
|
+
console.error('\x1b[31m──────────────────────────────────────────────────────────────────\x1b[0m');
|
|
155
|
+
if (pyStatus.tooOld) {
|
|
156
|
+
console.error(isFr
|
|
157
|
+
? `❌ Version de Python trop ancienne : ${pyStatus.version} détecté (${pyStatus.cmd}).\n AI Secret Scout requiert Python 3.10 ou supérieur.`
|
|
158
|
+
: `❌ Python version too old: ${pyStatus.version} detected (${pyStatus.cmd}).\n AI Secret Scout requires Python 3.10 or higher.`
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
console.error(isFr
|
|
162
|
+
? '❌ Python 3 (python3) est introuvable sur votre système.'
|
|
163
|
+
: '❌ Python 3 (python3) could not be found on your system.'
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
console.error('');
|
|
167
|
+
console.error(getInstallSuggestion(isFr));
|
|
168
|
+
console.error(isFr
|
|
169
|
+
? '💡 Note : Aucun paquet pip externe n\'est requis (seule la bibliothèque standard Python est utilisée).'
|
|
170
|
+
: '💡 Note: No pip packages required (uses Python standard library only).'
|
|
171
|
+
);
|
|
172
|
+
console.error('\x1b[31m──────────────────────────────────────────────────────────────────\x1b[0m');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return pyStatus.cmd;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (require.main === module) {
|
|
180
|
+
const isPostinstall = process.argv.includes('--postinstall');
|
|
181
|
+
runCheck(isPostinstall);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
checkNodeVersion,
|
|
186
|
+
checkPythonVersion,
|
|
187
|
+
getInstallSuggestion,
|
|
188
|
+
runCheck
|
|
189
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-secret-scout",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Zero-dependency heuristic secret scanner & redaction tool for AI coding assistants (Claude Code, Antigravity, Codex, Copilot, Cursor)",
|
|
5
5
|
"main": "bin/aiscout.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"README.fr.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
+
"postinstall": "node ./bin/check-environment.js --postinstall",
|
|
17
18
|
"start": "node ./bin/aiscout.js"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|