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 CHANGED
@@ -18,7 +18,7 @@
18
18
  <img src="https://img.shields.io/badge/D%C3%A9pendances-Zero%20External-22c55e?logo=python&amp;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&amp;logoColor=white" alt="Watchdog Temps Réel">
20
20
  <img src="https://img.shields.io/badge/Safe%20Restore-Gestionnaire%20Backups-7C3AED?logo=securityscorecard&amp;logoColor=white" alt="Safe Restore">
21
- <img src="https://img.shields.io/badge/Plateforme-Linux%20%7C%20WSL%20(uniquement)-E95420?logo=linux&amp;logoColor=white" alt="Linux | WSL (uniquement)">
21
+ <img src="https://img.shields.io/badge/Plateforme-Linux%20%7C%20Windows%20%7C%20WSL-0078D6?logo=windows&amp;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`, `termios`, `tty`, `select`, `unicodedata`, `shutil`, `argparse`, `subprocess`).
506
- * **Zéro Dépendance Externe** : Aucun package tiers `pip`. Conçu pour la bibliothèque standard Python 3.
507
- * **Systèmes Supportés** : **Linux** (natif Wayland / X11) et **WSL / WSL2** sous Windows uniquement pour le moment (macOS et Windows natif non supportés).
508
- * **Notification System** : Détection automatique de `notify-send` pour alertes desktop natives Wayland / X11.
509
- * **Emplacement du code** : `/home/dev_redious/Documents/Dev/tools/ai-secret-scout/ai_secret_scout.py`
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&amp;logoColor=white" alt="Zero Dependencies">
19
19
  <img src="https://img.shields.io/badge/Watchdog-Real--Time%20%26%20Desktop%20Alerts-F59E0B?logo=airplayaudio&amp;logoColor=white" alt="Real-Time Watchdog">
20
20
  <img src="https://img.shields.io/badge/Safe%20Restore-Backup%20Manager-7C3AED?logo=securityscorecard&amp;logoColor=white" alt="Safe Restore">
21
- <img src="https://img.shields.io/badge/Platform-Linux%20%7C%20WSL%20(only)-E95420?logo=linux&amp;logoColor=white" alt="Linux | WSL (only)">
21
+ <img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows%20%7C%20WSL-0078D6?logo=windows&amp;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
- > [!IMPORTANT]
111
- > **Platform Support**: `aiscout` currently supports **Linux** (Wayland & X11) and **WSL / WSL2** (Windows Subsystem for Linux). Native Windows and macOS are not supported at this stage.
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`, `termios`, `tty`, `select`, `unicodedata`, `shutil`, `argparse`, `subprocess`).
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) and **WSL / WSL2** on Windows (macOS and native Windows not supported).
511
- * **Notification System**: Auto-detects `notify-send` for native desktop banners on Wayland & X11.
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
  ---
@@ -16,9 +16,7 @@ import time
16
16
  import json
17
17
  import math
18
18
  import shutil
19
- import select
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
- VERSION = "2.3.0"
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 (~/.config/aiscout/rules.json) ---
558
- CONFIG_DIR = Path.home() / ".config" / "aiscout"
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 ~/.config/aiscout/rules.json."""
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 Plasma Wayland."""
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 sans déconnexion intempestive sur les flèches."""
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", "user")
1207
- host = os.uname().nodename
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** : `{os.getenv('USER')}`\n")
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** : `{os.getenv('USER')}`\n")
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
- fd = sys.stdin.fileno()
2151
- old_term = termios.tcgetattr(fd)
2152
- try:
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
- r, _, _ = select.select([fd], [], [], 1.0)
2211
- if r:
2212
- key_bytes = os.read(fd, 32)
2213
- if key_bytes in (b"q", b"Q", b"\x1b", b"\x03"):
2214
- break
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
- termios.tcsetattr(fd, termios.TCSADRAIN, old_term)
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('python3', [pyScript, ...process.argv.slice(2)], {
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.0",
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": [