@nosserb/prmt 0.0.0-stage → 2.0.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/LICENSE +21 -0
- package/README.md +191 -2
- package/bin/prmt.mjs +174 -0
- package/dist/assets/AuditModule-BOO_iRy8.js +1 -0
- package/dist/assets/Audits-yUBm0EpW.js +1 -0
- package/dist/assets/BrainPage-FIWO_9Nc.js +3 -0
- package/dist/assets/BrainPage-XQCEGRc5.css +1 -0
- package/dist/assets/Carte-CPuiR1Ha.css +1 -0
- package/dist/assets/Carte-xycu1mQn.js +1 -0
- package/dist/assets/Dashboard-DKvCQcX8.js +2 -0
- package/dist/assets/Dashboard-XdhC6_hM.css +1 -0
- package/dist/assets/Field-C7c2mCbb.js +2 -0
- package/dist/assets/Journal-BLIIlh80.css +1 -0
- package/dist/assets/Journal-BaelFjwV.js +1 -0
- package/dist/assets/Landing-B2jxV3zs.css +1 -0
- package/dist/assets/Landing-Dr612Zdu.js +26 -0
- package/dist/assets/Register-DA_Aq5f8.js +1 -0
- package/dist/assets/Reports-8wfDNSxH.js +1 -0
- package/dist/assets/Reports-B8GXN95N.css +1 -0
- package/dist/assets/SavoirPage-BTxbH3LY.css +1 -0
- package/dist/assets/SavoirPage-Dp3eUCHp.js +1 -0
- package/dist/assets/SettingsPage-BElIP46c.css +1 -0
- package/dist/assets/SettingsPage-BiQbnw3o.js +1 -0
- package/dist/assets/Shell-BAyHVljp.css +1 -0
- package/dist/assets/Shell-CQRlx9-V.js +2 -0
- package/dist/assets/Table-DUmGel5e.js +1 -0
- package/dist/assets/Tabs-CfhbELZk.js +1 -0
- package/dist/assets/Terminal-Bv1IJfi9.js +3 -0
- package/dist/assets/Terminal-CEfj38NV.css +1 -0
- package/dist/assets/arrow-right-Cx3pAEhl.js +1 -0
- package/dist/assets/auditViews-BMza2Sdz.js +1 -0
- package/dist/assets/auditViews-BePWj26h.css +1 -0
- package/dist/assets/bricolage-grotesque-latin-ext-opsz-normal-IcJDqblK.woff2 +0 -0
- package/dist/assets/bricolage-grotesque-latin-opsz-normal-Cre6nC2_.woff2 +0 -0
- package/dist/assets/bricolage-grotesque-vietnamese-opsz-normal-D9N6E8K1.woff2 +0 -0
- package/dist/assets/charts-BcijSJ4J.js +1 -0
- package/dist/assets/circle-stop-C33BAcKW.js +1 -0
- package/dist/assets/command-ChA49Xk7.js +1 -0
- package/dist/assets/command-Ck4pdQCv.css +1 -0
- package/dist/assets/download-CeuKOenD.js +1 -0
- package/dist/assets/eye-CXXHBBpr.js +1 -0
- package/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/assets/habillage-B5mMBadn.js +1 -0
- package/dist/assets/index-BH1etemR.css +1 -0
- package/dist/assets/index-COSiM-R9.js +14 -0
- package/dist/assets/jetbrains-mono-cyrillic-ext-wght-normal-EocZY2iu.woff2 +0 -0
- package/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/assets/layers-2x-Bpkbi35X.png +0 -0
- package/dist/assets/layers-BWBAp2CZ.png +0 -0
- package/dist/assets/markdown-DF87LQh1.css +1 -0
- package/dist/assets/markdown-o5sMhPHN.js +4 -0
- package/dist/assets/marker-icon-hN30_KVU.png +0 -0
- package/dist/assets/play-Daw4K_73.js +1 -0
- package/dist/assets/plus-D_QHm8ud.js +1 -0
- package/dist/assets/trash-DLpHM-s8.js +1 -0
- package/dist/assets/web-6j1-aY67.js +1 -0
- package/dist/favicon.svg +1 -0
- package/dist/index.html +18 -0
- package/docs/ANALYSE.md +87 -0
- package/docs/SECURITE.md +188 -0
- package/lib/scripts/lier.js +15 -0
- package/lib/scripts/searxng.js +91 -0
- package/lib/server/app.js +444 -0
- package/lib/server/audits/modules/local.js +429 -0
- package/lib/server/audits/modules/network.js +331 -0
- package/lib/server/audits/modules/web.js +302 -0
- package/lib/server/audits/net.js +141 -0
- package/lib/server/audits/registry.js +202 -0
- package/lib/server/audits/types.js +58 -0
- package/lib/server/brain/brain.js +624 -0
- package/lib/server/brain/conversations.js +82 -0
- package/lib/server/brain/ollama.js +197 -0
- package/lib/server/brain/prompt.js +188 -0
- package/lib/server/brain/provider.js +1 -0
- package/lib/server/cli/bridge.js +292 -0
- package/lib/server/cli/pont.js +62 -0
- package/lib/server/config.js +68 -0
- package/lib/server/engine/analyzer.js +205 -0
- package/lib/server/engine/catalog.js +229 -0
- package/lib/server/engine/decoder.js +91 -0
- package/lib/server/engine/engine.js +194 -0
- package/lib/server/engine/paths.js +129 -0
- package/lib/server/engine/policy.js +397 -0
- package/lib/server/engine/relax.js +10 -0
- package/lib/server/engine/runner.js +193 -0
- package/lib/server/errors.js +23 -0
- package/lib/server/http/guard.js +75 -0
- package/lib/server/http/router.js +154 -0
- package/lib/server/http/static.js +76 -0
- package/lib/server/index.js +40 -0
- package/lib/server/journal.js +139 -0
- package/lib/server/lien.js +33 -0
- package/lib/server/savoir/base.js +284 -0
- package/lib/server/savoir/lecture.js +406 -0
- package/lib/server/savoir/memoire.js +115 -0
- package/lib/server/search/categories.js +179 -0
- package/lib/server/search/fiche.js +326 -0
- package/lib/server/search/lieux.js +134 -0
- package/lib/server/search/overpass.js +216 -0
- package/lib/server/search/searxng.js +223 -0
- package/lib/server/search/tiles.js +103 -0
- package/lib/server/status.js +157 -0
- package/lib/server/stores.js +141 -0
- package/lib/server/telephone/acces.js +457 -0
- package/lib/server/telephone/certificat.js +79 -0
- package/lib/server/telephone/qr.js +308 -0
- package/lib/shared/analysis.js +40 -0
- package/lib/shared/schemas.js +105 -0
- package/lib/shared/types.js +10 -0
- package/package.json +81 -4
- package/searxng/settings.yml +35 -0
- package/tui/Modelfile +23 -0
- package/tui/README.md +103 -0
- package/tui/prmt_tui.py +65 -0
- package/tui/promethee/__init__.py +1 -0
- package/tui/promethee/__main__.py +4 -0
- package/tui/promethee/accueil.py +66 -0
- package/tui/promethee/agents.py +220 -0
- package/tui/promethee/app/__init__.py +4 -0
- package/tui/promethee/app/application.py +299 -0
- package/tui/promethee/app/clavier.py +99 -0
- package/tui/promethee/app/commandes_slash.py +164 -0
- package/tui/promethee/app/contexte.py +188 -0
- package/tui/promethee/app/definitions.py +518 -0
- package/tui/promethee/app/ecran.py +177 -0
- package/tui/promethee/app/envoi.py +141 -0
- package/tui/promethee/app/historique.py +373 -0
- package/tui/promethee/app/modele.py +108 -0
- package/tui/promethee/app/notes.py +63 -0
- package/tui/promethee/app/outils.py +589 -0
- package/tui/promethee/app/plateforme.py +409 -0
- package/tui/promethee/app/saisie.py +100 -0
- package/tui/promethee/app/taches.py +92 -0
- package/tui/promethee/app/tour.py +539 -0
- package/tui/promethee/app/validation.py +210 -0
- package/tui/promethee/appels.py +35 -0
- package/tui/promethee/commandes.py +154 -0
- package/tui/promethee/completion.py +140 -0
- package/tui/promethee/config.py +12 -0
- package/tui/promethee/consignes.py +278 -0
- package/tui/promethee/conversation.py +94 -0
- package/tui/promethee/etat.py +253 -0
- package/tui/promethee/fichiers.py +354 -0
- package/tui/promethee/journal.py +116 -0
- package/tui/promethee/logo.py +132 -0
- package/tui/promethee/memoire.py +50 -0
- package/tui/promethee/ollama.py +52 -0
- package/tui/promethee/plan.py +91 -0
- package/tui/promethee/politique.py +429 -0
- package/tui/promethee/preferences.py +65 -0
- package/tui/promethee/presse_papiers.py +44 -0
- package/tui/promethee/recherche.py +581 -0
- package/tui/promethee/securite.py +204 -0
- package/tui/promethee/shell.py +190 -0
- package/tui/promethee/textes.py +190 -0
- package/tui/promethee/theme.py +92 -0
- package/tui/promethee/widgets.py +493 -0
- package/tui/requirements.txt +1 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Avant de montrer une commande : vérification par l'analyseur de PowerShell et repérage des commandes risquées."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .config import CREATE_NO_WINDOW
|
|
12
|
+
from .shell import decode_output, refreshed_env
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------- vérification avant validation
|
|
15
|
+
# Chaque commande est analysée par le vrai analyseur de PowerShell (sans l'exécuter) avant de te
|
|
16
|
+
# la montrer : erreur de syntaxe, programme absent, `curl` (alias trompeur d'Invoke-WebRequest).
|
|
17
|
+
# En cas de problème, le modèle la corrige seul : tu ne vois que des commandes plausibles.
|
|
18
|
+
PRECHECK_PS = r"""$ErrorActionPreference = 'Stop'
|
|
19
|
+
try {
|
|
20
|
+
$src = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('@B64@'))
|
|
21
|
+
$tokens = $null; $errs = $null
|
|
22
|
+
$ast = [System.Management.Automation.Language.Parser]::ParseInput($src, [ref]$tokens, [ref]$errs)
|
|
23
|
+
if ($errs.Count -gt 0) { 'SYNTAXE ' + $errs[0].Message; exit }
|
|
24
|
+
$defined = @($ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | ForEach-Object { $_.Name })
|
|
25
|
+
$names = @($ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true) | ForEach-Object { $_.GetCommandName() } | Where-Object { $_ } | Select-Object -Unique)
|
|
26
|
+
foreach ($n in $names) {
|
|
27
|
+
if ($defined -contains $n) { continue }
|
|
28
|
+
if ($n -eq 'curl' -or $n -eq 'wget') { 'ALIAS ' + $n; continue }
|
|
29
|
+
if (-not (Get-Command $n -ErrorAction SilentlyContinue)) { 'INTROUVABLE ' + $n }
|
|
30
|
+
}
|
|
31
|
+
# Paramètres inventés (« Get-NetFirewallProfile -Enabled ») : seulement pour les cmdlets et fonctions connues
|
|
32
|
+
$common = @('Verbose','Debug','ErrorAction','WarningAction','InformationAction','ErrorVariable','WarningVariable',
|
|
33
|
+
'InformationVariable','OutVariable','OutBuffer','PipelineVariable','WhatIf','Confirm')
|
|
34
|
+
foreach ($c in $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true)) {
|
|
35
|
+
$n = $c.GetCommandName()
|
|
36
|
+
if (-not $n -or $defined -contains $n) { continue }
|
|
37
|
+
# Lecteurs spéciaux (Cert:, HKLM:, Env:…) : leurs paramètres dynamiques ne sont pas connus ici
|
|
38
|
+
if ($c.Extent.Text -match '(?i)\b(Cert|HK\w+|Env|WSMan|Function|Variable|Alias|Registry)::?') { continue }
|
|
39
|
+
try {
|
|
40
|
+
$info = Get-Command $n -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
41
|
+
if ($info -and $info.CommandType -eq 'Alias') { $info = $info.ResolvedCommand }
|
|
42
|
+
if (-not $info -or ($info.CommandType -ne 'Cmdlet' -and $info.CommandType -ne 'Function')) { continue }
|
|
43
|
+
$params = $info.Parameters
|
|
44
|
+
if (-not $params -or $params.Count -eq 0) { continue }
|
|
45
|
+
$known = @($params.Keys) + @($params.Values | ForEach-Object { $_.Aliases }) + $common
|
|
46
|
+
foreach ($p in $c.CommandElements) {
|
|
47
|
+
if ($p -isnot [System.Management.Automation.Language.CommandParameterAst] -or -not $p.ParameterName) { continue }
|
|
48
|
+
$pn = $p.ParameterName
|
|
49
|
+
if (@($known | Where-Object { $_ -like "$pn*" }).Count -eq 0) {
|
|
50
|
+
'PARAMETRE ' + $info.Name + ' ' + $pn + ' ' + ((@($params.Keys | Where-Object { $common -notcontains $_ }) | Select-Object -First 25) -join ',')
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch { }
|
|
54
|
+
}
|
|
55
|
+
} catch { }
|
|
56
|
+
"""
|
|
57
|
+
PLACEHOLDER = re.compile(r"<[^<>|]{2,40}>")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def precheck(command: str, cwd: Path) -> list[tuple[str, str]]:
|
|
61
|
+
"""Problèmes détectés sans exécuter la commande : [(résumé pour l'écran, explication pour le modèle)]."""
|
|
62
|
+
script = PRECHECK_PS.replace("@B64@", base64.b64encode(command.encode("utf-8")).decode("ascii"))
|
|
63
|
+
path = ""
|
|
64
|
+
try:
|
|
65
|
+
fd, path = tempfile.mkstemp(prefix="prmt_chk_", suffix=".ps1")
|
|
66
|
+
with os.fdopen(fd, "w", encoding="utf-8-sig", newline="\r\n") as f:
|
|
67
|
+
f.write(script)
|
|
68
|
+
proc = subprocess.run(
|
|
69
|
+
["powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", path],
|
|
70
|
+
cwd=str(cwd), env=refreshed_env(), stdin=subprocess.DEVNULL, capture_output=True,
|
|
71
|
+
creationflags=CREATE_NO_WINDOW, timeout=20,
|
|
72
|
+
)
|
|
73
|
+
out = decode_output(proc.stdout)
|
|
74
|
+
except (OSError, subprocess.SubprocessError):
|
|
75
|
+
return [] # la vérification ne doit jamais bloquer l'utilisateur
|
|
76
|
+
finally:
|
|
77
|
+
if path:
|
|
78
|
+
try:
|
|
79
|
+
os.remove(path)
|
|
80
|
+
except OSError:
|
|
81
|
+
pass
|
|
82
|
+
problems: list[tuple[str, str]] = []
|
|
83
|
+
for line in out.split("\n"):
|
|
84
|
+
kind, _, arg = line.strip().partition(" ")
|
|
85
|
+
if kind == "SYNTAXE":
|
|
86
|
+
if PLACEHOLDER.search(command):
|
|
87
|
+
problems.append(("valeur manquante (" + PLACEHOLDER.search(command).group(0) + ")",
|
|
88
|
+
"la commande contient un espace réservé comme <adresse_ip> : remplace-le par la vraie valeur "
|
|
89
|
+
"(demande-la à l'utilisateur si tu ne la connais pas)"))
|
|
90
|
+
else:
|
|
91
|
+
problems.append(("erreur de syntaxe", f"erreur de syntaxe PowerShell : {arg}"))
|
|
92
|
+
elif kind == "ALIAS":
|
|
93
|
+
problems.append((f"`{arg}` à remplacer par curl.exe",
|
|
94
|
+
f"dans PowerShell 5.1, `{arg}` est un alias d'Invoke-WebRequest qui n'accepte pas les options "
|
|
95
|
+
f"de {arg} : écris `curl.exe` à la place"))
|
|
96
|
+
elif kind == "INTROUVABLE":
|
|
97
|
+
problems.append((f"« {arg} » n'est pas installé",
|
|
98
|
+
f"« {arg} » n'est ni installé sur cette machine ni une commande PowerShell : utilise une autre "
|
|
99
|
+
"commande disponible, ou propose à l'utilisateur de l'installer"))
|
|
100
|
+
elif kind == "PARAMETRE":
|
|
101
|
+
cmd, _, rest = arg.partition(" ")
|
|
102
|
+
param, _, known = rest.partition(" ")
|
|
103
|
+
listed = ", ".join(f"-{k}" for k in known.split(",") if k)
|
|
104
|
+
problems.append((f"{cmd} n'a pas de paramètre -{param}",
|
|
105
|
+
f"{cmd} n'a pas de paramètre -{param} (paramètre inventé). Ses paramètres : {listed}. "
|
|
106
|
+
"Utilise seulement ceux-là, ou filtre le résultat avec Where-Object / Select-Object"))
|
|
107
|
+
return problems
|
|
108
|
+
|
|
109
|
+
# Commandes qui modifient / suppriment / exécutent du code : jamais autorisées automatiquement.
|
|
110
|
+
DANGEROUS_ANYWHERE = {
|
|
111
|
+
"remove-item", "format-volume", "clear-disk", "initialize-disk", "stop-computer", "restart-computer",
|
|
112
|
+
"set-executionpolicy", "invoke-expression", "iex", "stop-process", "set-content", "out-file", "add-content",
|
|
113
|
+
"new-item", "move-item", "rename-item", "copy-item", "clear-content", "remove-itemproperty", "set-itemproperty",
|
|
114
|
+
"new-itemproperty", "start-process", "diskpart", "bcdedit", "vssadmin", "wbadmin", "takeown", "icacls",
|
|
115
|
+
"cipher", "taskkill", "shutdown", "set-mppreference", "add-mppreference", "remove-mppreference",
|
|
116
|
+
"set-netfirewallprofile", "new-netfirewallrule", "remove-netfirewallrule", "disable-netfirewallrule",
|
|
117
|
+
"set-service", "stop-service", "start-service", "restart-service", "new-service", "remove-service",
|
|
118
|
+
"new-localuser", "set-localuser", "remove-localuser", "add-localgroupmember", "remove-localgroupmember",
|
|
119
|
+
"uninstall-package", "install-package", "install-module", "clear-recyclebin", "set-acl", "invoke-command",
|
|
120
|
+
}
|
|
121
|
+
DANGEROUS_PREFIX = {
|
|
122
|
+
"rm", "ri", "del", "erase", "rd", "rmdir", "mv", "mi", "move", "ren", "rni", "cp", "cpi", "copy", "ni", "md",
|
|
123
|
+
"mkdir", "sc", "sc.exe", "ac", "clc", "sp", "rp", "spps", "kill", "saps", "start", "format", "reg", "reg.exe",
|
|
124
|
+
"net", "net.exe", "netsh", "schtasks", "attrib", "fsutil", "wmic", "powershell", "pwsh", "cmd", "winget",
|
|
125
|
+
"choco", "pip", "npm", "msiexec", "runas", "bitsadmin", "certutil", "mshta", "rundll32", "regsvr32",
|
|
126
|
+
}
|
|
127
|
+
SEGMENT_SPLIT = re.compile(r"\|\||&&|[|;\n]")
|
|
128
|
+
FILE_REDIRECT = re.compile(r"(?<!&)>{1,2}(?!&)\s*(?!\s*(?:\$null\b|nul\b))", re.I)
|
|
129
|
+
# Programme ou script désigné par son chemin (« .\\outil.exe », « C:\\x\\y.ps1 ») ou chargé avec « . script » :
|
|
130
|
+
# son nom ne dit rien de ce qu'il fait, il n'est donc jamais autorisé pour la session.
|
|
131
|
+
PATH_CALL = re.compile(r"""(?:^|[|;{(\n])\s*(?:\.\s|['"]?(?:\.{1,2}|~|[A-Za-z]:)?[\\/])""")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def segments(cmd: str) -> list[str]:
|
|
135
|
+
return [s.strip() for s in SEGMENT_SPLIT.split(cmd) if s.strip()]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def prefix_of(segment: str) -> str:
|
|
139
|
+
m = re.match(r"""^[&.(\s]*['"]?([^\s'"(){}]+)""", segment)
|
|
140
|
+
if not m:
|
|
141
|
+
return ""
|
|
142
|
+
token = m.group(1).replace("\\", "/").rsplit("/", 1)[-1].lower()
|
|
143
|
+
return token[:-4] if token.endswith(".exe") and token not in ("sc.exe", "reg.exe", "net.exe") else token
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
CMD_POSITION = re.compile(r"(?:^|[|;{(\n])\s*([^\s|;{}()]+)")
|
|
147
|
+
NEVER_AUTO = re.compile(r"`|\[[\w.]+\]::|&", re.I)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def command_words(cmd: str) -> list[str]:
|
|
151
|
+
"""Tous les noms de commande, y compris ceux cachés dans { } et ( ) (ex. Where-Object { notepad })."""
|
|
152
|
+
words = []
|
|
153
|
+
for m in CMD_POSITION.finditer(cmd):
|
|
154
|
+
token = m.group(1)
|
|
155
|
+
if token[0] in "$'\"-0123456789@[.":
|
|
156
|
+
continue # variable, texte, paramètre, nombre… : pas une commande
|
|
157
|
+
word = prefix_of(token)
|
|
158
|
+
if word and word not in words:
|
|
159
|
+
words.append(word)
|
|
160
|
+
return words
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def can_auto_allow(cmd: str) -> bool:
|
|
164
|
+
"""Une commande n'est autorisable automatiquement que si elle est simple et non risquée."""
|
|
165
|
+
cleaned = re.sub(r"[0-9*]?>&[0-9]", "", cmd) # 2>&1 n'est pas un opérateur d'appel
|
|
166
|
+
return (not is_dangerous(cmd) and not NEVER_AUTO.search(cleaned) and not PATH_CALL.search(cmd)
|
|
167
|
+
and bool(command_words(cmd)))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def is_dangerous(cmd: str) -> bool:
|
|
171
|
+
words = {w.lower() for w in re.findall(r"[A-Za-z][\w.-]*", cmd)}
|
|
172
|
+
if words & DANGEROUS_ANYWHERE:
|
|
173
|
+
return True
|
|
174
|
+
if any(prefix_of(s) in DANGEROUS_PREFIX for s in segments(cmd)):
|
|
175
|
+
return True
|
|
176
|
+
# Aussi dans les blocs : « Get-ChildItem | ForEach-Object { rm $_ } » supprime autant que « rm »
|
|
177
|
+
if any(w in DANGEROUS_PREFIX for w in command_words(cmd)):
|
|
178
|
+
return True
|
|
179
|
+
return bool(FILE_REDIRECT.search(cmd))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# Portée d'un « ne plus demander » : pour une lecture reconnue, il vaut pour le nom de la commande ; pour tout le
|
|
183
|
+
# reste (python, git, curl, nmap…), seulement pour la ligne exacte. Sinon, après « python --version », n'importe
|
|
184
|
+
# quel « python script.py » partait sans validation.
|
|
185
|
+
READ_VERBS = ("get-", "test-", "resolve-", "select-", "where-", "sort-", "measure-", "format-", "group-", "compare-",
|
|
186
|
+
"convertto-", "convertfrom-", "find-", "join-", "split-")
|
|
187
|
+
READ_NAMES = {
|
|
188
|
+
"foreach-object", "out-string", "out-host", "write-output", "write-host", "ipconfig", "whoami", "hostname", "ping",
|
|
189
|
+
"tracert", "pathping", "nslookup", "netstat", "arp", "getmac", "systeminfo", "tasklist", "ver", "where", "findstr",
|
|
190
|
+
"sort", "more", "type", "dir", "ls", "cat", "gc", "gci", "gi", "gp", "gps", "gsv", "gcm", "gm", "select", "echo",
|
|
191
|
+
"pwd", "tree", "ft", "fl", "fw", "measure", "group", "sls", "oh", "gl",
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def is_read_name(word: str) -> bool:
|
|
196
|
+
return word in READ_NAMES or word.startswith(READ_VERBS)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def session_scope(cmd: str) -> tuple[str, list[str]]:
|
|
200
|
+
"""Ce que couvrirait un « ne plus demander » : ("noms", [commandes]) ou ("ligne", [la commande exacte])."""
|
|
201
|
+
words = command_words(cmd)
|
|
202
|
+
if words and all(is_read_name(w) for w in words):
|
|
203
|
+
return "noms", words
|
|
204
|
+
return "ligne", [" ".join(cmd.split())]
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Exécution PowerShell : décodage des sorties, environnement, script lancé, résultat renvoyé au modèle."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from .config import MAX_TOOL_CHARS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _codepages() -> tuple[str, str]:
|
|
11
|
+
try:
|
|
12
|
+
import ctypes
|
|
13
|
+
k32 = ctypes.windll.kernel32
|
|
14
|
+
return f"cp{k32.GetOEMCP()}", f"cp{k32.GetACP()}"
|
|
15
|
+
except Exception:
|
|
16
|
+
return "cp850", "cp1252"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
OEM_CP, ANSI_CP = _codepages()
|
|
20
|
+
FRENCH_CHARS = set("àâäçéèêëîïôöùûüÿœÀÂÄÇÉÈÊËÎÏÔÖÙÛÜŒ«»’\xa0°")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _decode_line(raw: bytes) -> str:
|
|
24
|
+
# Une même sortie mélange les encodages : PowerShell et la plupart des outils écrivent en OEM
|
|
25
|
+
# (cp850), certains messages d'erreur (findstr…) en ANSI (cp1252), python/git en UTF-8.
|
|
26
|
+
try:
|
|
27
|
+
return raw.decode("utf-8")
|
|
28
|
+
except UnicodeDecodeError:
|
|
29
|
+
pass
|
|
30
|
+
candidates = [raw.decode(OEM_CP, "replace"), raw.decode(ANSI_CP, "replace")]
|
|
31
|
+
return max(candidates, key=lambda s: sum(c in FRENCH_CHARS for c in s))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def decode_output(data: bytes) -> str:
|
|
35
|
+
try:
|
|
36
|
+
text = data.decode("utf-8")
|
|
37
|
+
except UnicodeDecodeError:
|
|
38
|
+
text = "\n".join(_decode_line(line) for line in data.split(b"\n"))
|
|
39
|
+
text = text.replace("\r\n", "\n").replace("\xa0", " ")
|
|
40
|
+
if "\r" in text:
|
|
41
|
+
# Barres de progression (winget, curl…) : un \r seul réécrit la ligne, comme dans un terminal
|
|
42
|
+
text = "\n".join(_last_segment(line) for line in text.split("\n"))
|
|
43
|
+
return text
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _last_segment(line: str) -> str:
|
|
47
|
+
parts = [p for p in line.split("\r") if p.strip()]
|
|
48
|
+
return parts[-1] if parts else ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def refreshed_env() -> dict[str, str]:
|
|
52
|
+
"""Environnement avec le PATH relu dans le registre : un logiciel installé pendant la session
|
|
53
|
+
(winget, installateur…) est trouvé sans relancer prmt, comme dans un nouveau terminal."""
|
|
54
|
+
env = dict(os.environ)
|
|
55
|
+
try:
|
|
56
|
+
import winreg
|
|
57
|
+
found: list[str] = []
|
|
58
|
+
for root, key in ((winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"),
|
|
59
|
+
(winreg.HKEY_CURRENT_USER, "Environment")):
|
|
60
|
+
try:
|
|
61
|
+
with winreg.OpenKey(root, key) as k:
|
|
62
|
+
value, _ = winreg.QueryValueEx(k, "Path")
|
|
63
|
+
found += [os.path.expandvars(p) for p in value.split(";") if p.strip()]
|
|
64
|
+
except OSError:
|
|
65
|
+
pass
|
|
66
|
+
current = env.get("PATH", "").split(";")
|
|
67
|
+
known = {p.rstrip("\\").lower() for p in current}
|
|
68
|
+
extra = [p for p in found if p.rstrip("\\").lower() not in known]
|
|
69
|
+
if extra:
|
|
70
|
+
env["PATH"] = ";".join(current + extra)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
env["PATH"] = windows_first(env.get("PATH", ""), env.get("SystemRoot") or env.get("windir") or "C:\\Windows")
|
|
74
|
+
return env
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def windows_first(path: str, root: str) -> str:
|
|
78
|
+
"""PATH avec les dossiers de Windows en tête, comme dans le moteur de la console : « whoami » ou « ipconfig »
|
|
79
|
+
sont alors ceux de Windows, ceux que la politique a vérifiés, et pas un programme du même nom placé avant."""
|
|
80
|
+
head = [f"{root}\\System32", root, f"{root}\\System32\\Wbem", f"{root}\\System32\\WindowsPowerShell\\v1.0"]
|
|
81
|
+
lowered = {h.lower() for h in head}
|
|
82
|
+
return ";".join(head + [p for p in path.split(";") if p and p.rstrip("\\").lower() not in lowered])
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
CWD_MARK = "__PRMT_CWD__="
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
MODULE_NAME = re.compile(r"^[A-Za-z0-9._-]{1,80}$")
|
|
89
|
+
CONSTRAINED = "$ExecutionContext.SessionState.LanguageMode = 'ConstrainedLanguage'"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_script(cmd: str, modules: tuple[str, ...] | list[str] = (), constrained: bool = False) -> str:
|
|
93
|
+
# Le marqueur final permet de suivre les "cd" d'une commande à l'autre.
|
|
94
|
+
# $OutputEncoding = encodage console : sinon PowerShell 5.1 transmet les pipes natifs
|
|
95
|
+
# (ex. arp -a | findstr) en ASCII et les accents deviennent des « ? ».
|
|
96
|
+
# constrained : seconde barrière, comme dans la console. Les modules de fonctions sont chargés juste avant,
|
|
97
|
+
# sinon ils ne se chargeraient plus (mesuré : Get-NetTCPConnection échoue en mode contraint sans cela).
|
|
98
|
+
imports = "".join(f"Import-Module -Name '{m}' -ErrorAction SilentlyContinue\n" for m in modules if MODULE_NAME.match(m))
|
|
99
|
+
return (
|
|
100
|
+
"$ProgressPreference='SilentlyContinue'\n"
|
|
101
|
+
"$OutputEncoding = [Console]::OutputEncoding\n"
|
|
102
|
+
+ imports + (CONSTRAINED + "\n" if constrained else "") +
|
|
103
|
+
f"{cmd}\n"
|
|
104
|
+
"$__ok = $?; $__code = $LASTEXITCODE\n"
|
|
105
|
+
# Retour à la ligne avant le marqueur : certaines commandes (curl…) ne finissent pas par un \n
|
|
106
|
+
f"Write-Output (\"`n{CWD_MARK}\" + (Get-Location).ProviderPath)\n"
|
|
107
|
+
"if (-not $__ok) { if ($__code) { exit $__code } else { exit 1 } }\n"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Les erreurs PowerShell citent le fichier .ps1 temporaire et soulignent la ligne fautive :
|
|
112
|
+
# on ne garde que le message (plus lisible pour l'utilisateur, moins de bruit pour le modèle).
|
|
113
|
+
ERR_LOCATION = re.compile(r"^(?:Au caractère|At) .*prmt_\w+\.ps1:\d+\s*(?::|char:)\s*\d+\s*$")
|
|
114
|
+
ERR_DETAIL = re.compile(r"^\s+\+ (?:CategoryInfo|FullyQualifiedErrorId)\s*:")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def tidy_output(text: str) -> str:
|
|
118
|
+
lines: list[str] = []
|
|
119
|
+
in_location = in_detail = False
|
|
120
|
+
for line in text.split("\n"):
|
|
121
|
+
if ERR_LOCATION.match(line):
|
|
122
|
+
in_location = True
|
|
123
|
+
continue
|
|
124
|
+
if in_location and line.startswith("+ "):
|
|
125
|
+
continue
|
|
126
|
+
in_location = False
|
|
127
|
+
if ERR_DETAIL.match(line):
|
|
128
|
+
in_detail = True
|
|
129
|
+
continue
|
|
130
|
+
# PowerShell coupe les longues lignes de détail (~120 colonnes) : on saute leur suite
|
|
131
|
+
if in_detail and line.startswith(" ") and line.strip():
|
|
132
|
+
continue
|
|
133
|
+
in_detail = False
|
|
134
|
+
line = line.rstrip()
|
|
135
|
+
if not line and (not lines or not lines[-1]):
|
|
136
|
+
continue # pas de lignes vides en double
|
|
137
|
+
lines.append(line)
|
|
138
|
+
return "\n".join(lines).strip("\n")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def for_model(code: int | None, output: str, note: str = "") -> str:
|
|
142
|
+
out = output.strip() or "(aucune sortie)"
|
|
143
|
+
if len(out) > MAX_TOOL_CHARS:
|
|
144
|
+
cut = len(out) - 3300
|
|
145
|
+
out = out[:1800] + f"\n[… {cut} caractères coupés …]\n" + out[-1500:]
|
|
146
|
+
head = f"Code de sortie : {code}" if code is not None else "Commande non terminée"
|
|
147
|
+
return f"{head}{(' (' + note + ')') if note else ''}\n{out}{hint_for(code, out)}"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Indices ajoutés au résultat envoyé au modèle (pas à l'écran) : un modèle de 8B suit bien mieux
|
|
151
|
+
# une consigne placée dans la sortie d'une commande que dans le prompt système.
|
|
152
|
+
WINGET_HINT = ("\n\n(Indice : le nom n'a pas suffi. Lance maintenant `winget search <un seul mot-clé>`, "
|
|
153
|
+
"puis réessaie avec `winget install -e --id <Id exact copié depuis la colonne Id> "
|
|
154
|
+
"--accept-package-agreements --accept-source-agreements`.)")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Mesuré : après une commande fausse, le modèle relance l'outil 5/6 avec cet indice (2/6 sans),
|
|
158
|
+
# et répond toujours directement quand l'échec EST la réponse (processus absent, port fermé).
|
|
159
|
+
ERROR_HINT = ("\n\n(Indice : si cette sortie répond déjà à la question, réponds. Si la commande était fausse "
|
|
160
|
+
"(syntaxe, option, nom), rappelle directement l'outil avec une commande corrigée, sans demander la permission par écrit.)")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Smart App Control (Windows 11) bloque au hasard les programmes qu'on vient de compiler : pas une erreur du code.
|
|
164
|
+
APP_CONTROL = re.compile(r"os error 4551|contr[ôo]le d.application a bloqu|application control policy has blocked", re.I)
|
|
165
|
+
APP_CONTROL_HINT = ("\n\n(Indice : c'est Windows (Smart App Control) qui a bloqué l'exécution du programme compilé, pas une "
|
|
166
|
+
"erreur du code : la compilation a réussi. Ne réessaie pas de le lancer. Vérifie le code avec "
|
|
167
|
+
"`cargo check` et `cargo clippy`, et dis à l'utilisateur que Smart App Control a bloqué l'exécution.)")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def hint_for(code: int | None, out: str) -> str:
|
|
171
|
+
if "No package found matching input criteria" in out or "Multiple packages found matching input criteria" in out:
|
|
172
|
+
return WINGET_HINT
|
|
173
|
+
if APP_CONTROL.search(out):
|
|
174
|
+
return APP_CONTROL_HINT
|
|
175
|
+
if code not in (0, None):
|
|
176
|
+
return ERROR_HINT
|
|
177
|
+
return ""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# --------------------------------------------------------------------------- Mode « ! » (commande lancée par l'utilisateur)
|
|
181
|
+
SHELL_TAG = "[Commande lancée par l'utilisateur avec !]"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def shell_message(command: str, code: int | None, output: str, note: str) -> str:
|
|
185
|
+
"""Message ajouté à la conversation après « ! commande » (sans les indices destinés au modèle)."""
|
|
186
|
+
out = output.strip() or "(aucune sortie)"
|
|
187
|
+
if len(out) > MAX_TOOL_CHARS:
|
|
188
|
+
out = out[:1800] + f"\n[… {len(out) - 3300} caractères coupés …]\n" + out[-1500:]
|
|
189
|
+
head = f"Code de sortie : {code}" if code is not None else "Commande non terminée"
|
|
190
|
+
return f"{SHELL_TAG} `{command}`\n{head}{(' (' + note + ')') if note else ''}\n{out}"
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Textes fixes de l'interface : aide, spinner, feuille de style."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from .theme import AGENT_COLOR, DIM, MEMORY_COLOR, OR, OR_CLAIR, ORANGE, PLAN_COLOR, RED, SHELL_COLOR
|
|
5
|
+
|
|
6
|
+
# --------------------------------------------------------------------------- Application
|
|
7
|
+
SPINNER = ["·", "✢", "✳", "✶", "✻", "✽", "✻", "✶", "✳", "✢"]
|
|
8
|
+
VERBS = [
|
|
9
|
+
"Réflexion", "Analyse", "Décryptage", "Forge", "Attise les braises", "Allume le feu",
|
|
10
|
+
"Énumération", "Investigation", "Rétro-ingénierie", "Recherche de failles", "Déchiffrement",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
HELP = """\
|
|
14
|
+
**Commandes**
|
|
15
|
+
- `/aide` : afficher cette aide
|
|
16
|
+
- `/etat` : état de la plateforme, mesuré à l'instant (Ollama, modèle, PowerShell, politique, périmètre, activité)
|
|
17
|
+
- `/clear` : nouvelle conversation (efface la mémoire de Prométhée et les autorisations de la session)
|
|
18
|
+
- `/modele` : lister les modèles · `/modele deephat` : changer de modèle
|
|
19
|
+
- `/copier` : copier la dernière réponse · `/copier code` : seulement les blocs de code
|
|
20
|
+
- `/sortie` : afficher la sortie complète de la dernière commande exécutée
|
|
21
|
+
- `/politique` : politique d'exécution et liste blanche · `/politique stricte` · `/politique souple`
|
|
22
|
+
- `/permissions` : autorisations de la session · `/permissions reset` : tout redemander ·
|
|
23
|
+
`/permissions autoriser <commande>` · `/permissions retirer <commande>`
|
|
24
|
+
- `/perimetre` : cibles réseau déclarées · `/perimetre ajouter <hôte>` · `/perimetre retirer <hôte>`
|
|
25
|
+
- `/journal` : dernières actions (commandes exécutées ou refusées, fichiers écrits, recherches web) · `/journal refus`
|
|
26
|
+
- `/recherche` : recherche web, active ou non, et SearXNG répond-il · `/recherche on` · `/recherche off`
|
|
27
|
+
- `/theme terminal` : garder le fond de ton terminal · `/theme promethee` : le fond de Prométhée
|
|
28
|
+
- `/sauver` : enregistrer la conversation (dossier Documents\\Promethee)
|
|
29
|
+
- `/compact` : résumer la conversation pour libérer le contexte (automatique à 80 %)
|
|
30
|
+
- `/reprendre` : reprendre une conversation précédente (aussi : `prmt -c` la dernière, `prmt -r` pour choisir)
|
|
31
|
+
- `/memoire` : voir la mémoire · `/memoire ouvrir` : la modifier dans le Bloc-notes
|
|
32
|
+
- `/plan` : activer ou couper le mode plan (comme `Shift+Tab`)
|
|
33
|
+
- `/quitter` : quitter
|
|
34
|
+
- Tape `/` : la liste des commandes s'affiche (`↑↓` choisir, `Tab` compléter, `Entrée` lancer, `Esc` fermer).
|
|
35
|
+
|
|
36
|
+
**Mode commande `!`**
|
|
37
|
+
- `! commande` : exécute ta commande PowerShell. Une lecture part **directement** ; une commande sensible, risquée
|
|
38
|
+
ou hors politique te demande **une confirmation** ; un refus de la politique vaut aussi pour toi.
|
|
39
|
+
- Sa sortie est ajoutée à la conversation : Prométhée peut ensuite l'analyser (« explique ce résultat »).
|
|
40
|
+
|
|
41
|
+
**Mémoire `#` (fichiers PROMETHEE.md)**
|
|
42
|
+
- `# ma VM Kali est en 192.168.56.10` : enregistre l'information, dans la mémoire globale ou dans celle du dossier courant.
|
|
43
|
+
- Prométhée relit sa mémoire à chaque message : plus besoin de répéter ton contexte (niveau, machines, préférences).
|
|
44
|
+
- Mémoire globale : `Documents\\Promethee\\PROMETHEE.md` · mémoire d'un dossier : `PROMETHEE.md` dans ce dossier.
|
|
45
|
+
|
|
46
|
+
**Fichiers joints `@`**
|
|
47
|
+
- `analyse @auth.log` : le contenu du fichier est joint à ta question. En tapant `@`, la liste des fichiers du dossier
|
|
48
|
+
s'affiche (`Tab` pour compléter, `\\` pour entrer dans un sous-dossier). `@dossier` joint la liste de son contenu.
|
|
49
|
+
|
|
50
|
+
**Agents (`@agent-nom`, `/agents`)**
|
|
51
|
+
- Un agent est un assistant spécialisé qui travaille **dans son propre contexte** (ses longues sorties de commandes
|
|
52
|
+
n'encombrent pas la conversation) puis renvoie seulement son **rapport**. Tu valides toujours ses commandes.
|
|
53
|
+
- `@agent-analyste-logs analyse @auth.log` : confie directement la tâche à l'agent. Prométhée peut aussi déléguer
|
|
54
|
+
de lui-même si tu le lui demandes (« demande à l'explorateur de trouver… »).
|
|
55
|
+
- `/agents` : liste des agents, **créer** un agent (tu le décris, Prométhée l'écrit), le modifier, le supprimer.
|
|
56
|
+
Intégrés : `explorateur` (cherche dans les fichiers), `analyste-logs` (analyse de journaux).
|
|
57
|
+
- Fichiers : `Documents\\Promethee\\agents\\nom.md` (globaux) ou `.promethee\\agents\\nom.md` dans un dossier.
|
|
58
|
+
- `Ctrl+O` : afficher ou replier le détail du travail des agents.
|
|
59
|
+
|
|
60
|
+
**IA de code (`/code`)**
|
|
61
|
+
- Quand tu demandes d'écrire, corriger ou expliquer du code, Prométhée passe la main à une IA de code
|
|
62
|
+
(`qwen3.5:9b` par défaut) pour cette réponse, puis la reprend : même conversation, mêmes outils, tu valides
|
|
63
|
+
toujours. Elle vérifie son code (compilation, exécution) avant de dire que ça marche.
|
|
64
|
+
- `/code` : état · `/code off` / `/code on` : couper / rétablir la bascule · `/code <modèle>` : changer d'IA de code.
|
|
65
|
+
|
|
66
|
+
**Commandes personnalisées (`/commandes`)**
|
|
67
|
+
- Un fichier `nom.md` dans `Documents\\Promethee\\commandes` (ou `.promethee\\commandes` d'un dossier) devient la
|
|
68
|
+
commande `/nom` : son texte est envoyé à Prométhée, `$ARGUMENTS` remplacé par ce que tu tapes après (`$1`, `$2`…
|
|
69
|
+
pour chaque mot). Elles apparaissent dans le menu `/`.
|
|
70
|
+
- `/commandes` : les lister, en **créer** une (tu la décris, Prométhée l'écrit), la modifier, la supprimer.
|
|
71
|
+
|
|
72
|
+
**Retour en arrière, mémoire du dossier, contexte**
|
|
73
|
+
- `Esc` `Esc` (saisie vide) : choisir un de tes messages ; la conversation repart juste avant, et le message revient
|
|
74
|
+
dans la saisie pour le modifier (les commandes déjà exécutées ne sont pas annulées).
|
|
75
|
+
- `/init` : Prométhée explore le dossier courant et écrit sa mémoire `PROMETHEE.md` (tu valides le fichier).
|
|
76
|
+
- `/contexte` : ce qui remplit le contexte du modèle (consignes, mémoire, messages, sorties de commandes…).
|
|
77
|
+
|
|
78
|
+
**Recherche**
|
|
79
|
+
- Prométhée cherche des fichiers (`chercher_fichiers`, par nom) ou du texte dans des fichiers (`chercher_texte`)
|
|
80
|
+
**sans te demander** : c'est de la lecture seule, comme `lire_fichier`.
|
|
81
|
+
|
|
82
|
+
**Recherche web (`/recherche`)**
|
|
83
|
+
- Quand une question demande une information récente ou précise (dernière version, actualité, CVE, prix) ou un lieu,
|
|
84
|
+
Prométhée cherche de lui-même sur internet par SearXNG, installé chez toi. Tu vois la requête et les sources
|
|
85
|
+
numérotées, et pour un lieu son adresse, ses coordonnées et un lien OpenStreetMap. La réponse cite ses sources : [1].
|
|
86
|
+
- Rien ne s'exécute sur ta machine : la réponse qui suit une recherche ne peut proposer aucune commande.
|
|
87
|
+
- `/recherche` : état · `/recherche off` / `/recherche on` : couper / rétablir. Le réglage est partagé avec la console
|
|
88
|
+
quand elle est reliée. SearXNG absent : `npm run searxng` dans le dossier de la console.
|
|
89
|
+
|
|
90
|
+
**Mode plan (`Shift+Tab`)**
|
|
91
|
+
- Prométhée propose un plan numéroté **sans rien exécuter**. Tu le relis, tu l'affines, puis tu choisis de l'exécuter :
|
|
92
|
+
il repasse alors en mode normal, et tu valides toujours chaque commande.
|
|
93
|
+
- Pendant l'exécution d'un plan ou d'un audit, la **liste des tâches** s'affiche au-dessus de la saisie et se coche
|
|
94
|
+
d'après les commandes réellement exécutées.
|
|
95
|
+
|
|
96
|
+
**Exécution de commandes**
|
|
97
|
+
- Prométhée peut lancer des commandes PowerShell, mais **tu valides chacune** avant qu'elle s'exécute.
|
|
98
|
+
- `1` Oui · `2` Oui et ne plus demander (session) · `3` / `Esc` Non. « Ne plus demander » vaut pour le nom d'une
|
|
99
|
+
commande de lecture reconnue (`ipconfig`, `Get-Process`…) ; pour toute autre, seulement pour la ligne exacte.
|
|
100
|
+
- Les commandes qui modifient ou suppriment quelque chose sont signalées en rouge et **toujours** soumises à validation.
|
|
101
|
+
- `Esc` pendant une commande : l'arrêter. Délai maximum : 5 minutes.
|
|
102
|
+
|
|
103
|
+
**Politique d'exécution (`/politique`)**
|
|
104
|
+
- Avant de t'être montrée, chaque commande est analysée par le moteur de la console web (le vrai analyseur de
|
|
105
|
+
PowerShell, sans l'exécuter) : liste blanche, arguments, dossiers d'écriture, cibles réseau.
|
|
106
|
+
- `● lecture` · `▲ sensible` (écrit, contacte le réseau, commande débloquée) · `◆ hors politique` · `✕ refusée`.
|
|
107
|
+
- **stricte** (par défaut) : une commande hors liste blanche est refusée, sans t'être proposée. Prométhée reçoit
|
|
108
|
+
la raison et cherche une commande de lecture, ou te dit quoi autoriser.
|
|
109
|
+
- **souple** : ce que l'analyse ne sait pas vérifier (scripts, compilateurs, programmes lancés par leur chemin)
|
|
110
|
+
t'est montré comme hors politique et tu le valides. Restent refusés dans les deux modes : commande destructrice
|
|
111
|
+
non débloquée, cible hors périmètre, protections de Windows, chemin réseau.
|
|
112
|
+
- `/permissions autoriser cargo` ajoute une commande à ta liste ; `/permissions autoriser Remove-Item` débloque une
|
|
113
|
+
commande destructrice. La liste est partagée avec la console web.
|
|
114
|
+
- Les tests de ports et les scans ne sont permis que sur cette machine et sur les cibles de `/perimetre` : des
|
|
115
|
+
systèmes qui t'appartiennent ou pour lesquels tu as une autorisation écrite.
|
|
116
|
+
- Si la console n'est pas reliée, la barre d'état affiche « validation manuelle » : tu valides chaque commande, sans
|
|
117
|
+
liste blanche. Chaque action est inscrite dans `Documents\\Promethee\\journal.jsonl` (`/journal`).
|
|
118
|
+
|
|
119
|
+
**Fichiers**
|
|
120
|
+
- Prométhée lit les fichiers sans demander (lecture seule), mais te montre un **diff coloré** avant chaque création
|
|
121
|
+
ou modification : `1` Oui · `2` Oui pour toutes les modifications de la session · `3` / `Esc` Non.
|
|
122
|
+
- Les fichiers hors de ton dossier utilisateur sont signalés en rouge et toujours soumis à validation.
|
|
123
|
+
- Les fichiers relus ou exécutés plus tard aussi, même après « oui pour toutes » : mémoire `PROMETHEE.md`, agents et
|
|
124
|
+
commandes de Prométhée, profil PowerShell, dossier `AppData`, fichiers et dossiers cachés (`.ssh`, `.git`, `.env`).
|
|
125
|
+
|
|
126
|
+
**Contrôles automatiques (interpréteur)**
|
|
127
|
+
- Avant de répondre, ta demande est classée : *action* (Prométhée exécute) ou *explication* (il répond sans rien lancer).
|
|
128
|
+
- S'il écrit des commandes au lieu de les exécuter, ou annonce une étape sans la faire, il est relancé automatiquement.
|
|
129
|
+
- Chaque commande est vérifiée avant de t'être montrée (syntaxe, programme installé, `curl` → `curl.exe`) et corrigée si besoin.
|
|
130
|
+
- Après la réponse, les IP et ports cités qui n'apparaissent dans aucune sortie de commande te sont signalés.
|
|
131
|
+
- Pour un audit (« fais un audit de sécurité de mon site … »), il suit une méthode en 5 étapes puis fait un rapport.
|
|
132
|
+
|
|
133
|
+
**Raccourcis**
|
|
134
|
+
- `Entrée` : envoyer · `Ctrl+J` ou `\\` puis `Entrée` : nouvelle ligne
|
|
135
|
+
- `↑` / `↓` : historique de tes messages
|
|
136
|
+
- `Esc` : interrompre la réponse · `Esc` `Esc` (saisie vide) : revenir à un message précédent
|
|
137
|
+
- `Ctrl+C` : copier la sélection, sinon interrompre ; deux fois de suite pour quitter
|
|
138
|
+
- `PgUp` / `PgDn` ou molette : faire défiler la conversation
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
# Les couleurs viennent de theme.py. Le fond, les panneaux ($surface) et les ascenseurs suivent le thème Textual
|
|
142
|
+
# choisi par /theme : peints par l'application, ou laissés au terminal.
|
|
143
|
+
CSS = f"""
|
|
144
|
+
Screen {{ layout: vertical; }}
|
|
145
|
+
#chat {{ height: 1fr; padding: 0 1; scrollbar-size-vertical: 1; }}
|
|
146
|
+
Logo {{ height: auto; width: auto; margin: 1 0 0 1; }}
|
|
147
|
+
Accueil {{
|
|
148
|
+
border: round {OR}; padding: 0 1; margin: 1 0 0 0; width: auto; max-width: 100%; height: auto; background: $surface;
|
|
149
|
+
}}
|
|
150
|
+
.etat {{
|
|
151
|
+
border: round {OR}; border-title-color: {OR_CLAIR}; border-title-style: bold; border-subtitle-color: {DIM};
|
|
152
|
+
padding: 0 1; margin: 1 0 0 0; width: auto; max-width: 100%; height: auto; background: $surface;
|
|
153
|
+
}}
|
|
154
|
+
.user {{ margin: 1 0 0 0; height: auto; color: {DIM}; }}
|
|
155
|
+
AssistantMessage {{ margin: 1 0 0 0; height: auto; }}
|
|
156
|
+
AssistantMessage .bullet {{ width: 2; height: 1; color: {ORANGE}; }}
|
|
157
|
+
AssistantMessage .body {{ width: 1fr; height: auto; }}
|
|
158
|
+
AssistantMessage Markdown {{ margin: 0; padding: 0; height: auto; }}
|
|
159
|
+
ToolCallView {{ margin: 1 0 0 0; height: auto; }}
|
|
160
|
+
ToolCallView Static {{ height: auto; }}
|
|
161
|
+
SearchView {{ margin: 1 0 0 0; height: auto; }}
|
|
162
|
+
SearchView Static {{ height: auto; }}
|
|
163
|
+
AgentView {{ margin: 1 0 0 0; height: auto; }}
|
|
164
|
+
AgentView Static {{ height: auto; }}
|
|
165
|
+
AgentView .agent-body {{ height: auto; padding: 0 0 0 2; }}
|
|
166
|
+
AgentView .agent-body ToolCallView {{ margin: 0; }}
|
|
167
|
+
.notice {{ margin: 1 0 0 0; padding: 0 0 0 2; height: auto; }}
|
|
168
|
+
.notice-md {{ margin: 1 0 0 2; padding: 0; height: auto; }}
|
|
169
|
+
#bottom {{ height: auto; }}
|
|
170
|
+
#spinner {{ height: 1; padding: 0 2; display: none; }}
|
|
171
|
+
#spinner.-active {{ display: block; }}
|
|
172
|
+
#input-box {{ border: round {DIM}; height: auto; margin: 0 1; padding: 0 1; background: $surface; }}
|
|
173
|
+
#input-box:focus-within {{ border: round {ORANGE}; }}
|
|
174
|
+
#input-box.-plan, #input-box.-plan:focus-within {{ border: round {PLAN_COLOR}; }}
|
|
175
|
+
#input-box.-shell, #input-box.-shell:focus-within {{ border: round {SHELL_COLOR}; }}
|
|
176
|
+
#input-box.-shell #prompt-char {{ color: {SHELL_COLOR}; }}
|
|
177
|
+
#input-box.-memory, #input-box.-memory:focus-within {{ border: round {MEMORY_COLOR}; }}
|
|
178
|
+
#input-box.-memory #prompt-char {{ color: {MEMORY_COLOR}; }}
|
|
179
|
+
#input-box.-agent, #input-box.-agent:focus-within {{ border: round {AGENT_COLOR}; }}
|
|
180
|
+
#input-box.-agent #prompt-char {{ color: {AGENT_COLOR}; }}
|
|
181
|
+
#completions {{ height: auto; padding: 0 2; display: none; }}
|
|
182
|
+
#tasks {{ height: auto; padding: 0 2; margin: 1 0 0 0; display: none; }}
|
|
183
|
+
#prompt-char {{ width: 2; height: 1; color: {ORANGE}; text-style: bold; }}
|
|
184
|
+
PromptInput {{ height: auto; max-height: 12; width: 1fr; border: none; padding: 0; background: transparent; }}
|
|
185
|
+
ChoicePrompt {{ border: round {ORANGE}; height: auto; margin: 0 1; padding: 0 1; background: $surface; }}
|
|
186
|
+
ChoicePrompt.-danger {{ border: round {RED}; }}
|
|
187
|
+
#status-line {{ height: 1; padding: 0 2; }}
|
|
188
|
+
#hints {{ width: 1fr; color: {DIM}; }}
|
|
189
|
+
#status {{ width: auto; color: {DIM}; }}
|
|
190
|
+
"""
|