Date : 03/02/2026 Status : REPORTEE AU BACKLOG Projet : connectors-api + connectors-front Priorite : MOYENNE (amelioration UX, pas bloquant) Prerequis : Fix PowerShell wrapping (commit e9f5f2f) deploye
Quand une commande SSH echoue parce qu'un outil n'est pas installe :
installNouveau fichier src/services/packageDetector.ts :
interface MissingToolDetection {
detected: boolean;
tool_name: string | null;
install_commands: InstallCommand[];
}
interface InstallCommand {
package_manager: string; // 'apt' | 'yum' | 'dnf' | 'brew' | 'choco' | 'winget' | 'scoop'
command: string; // 'apt install -y curl'
requires_sudo: boolean;
}
Patterns de detection stderr (deterministe, regex) :
| Pattern | OS | Exemple |
|---|---|---|
'xxx' n'est pas reconnu en tant que commande |
Windows FR | 'tr' n'est pas reconnu... |
'xxx' is not recognized as an internal or external command |
Windows EN | 'curl' is not recognized... |
xxx: command not found |
Linux | jq: command not found |
xxx: not found |
Linux (sh) | htop: not found |
bash: xxx: No such file or directory |
Linux | bash: foo: No such file or directory |
The term 'xxx' is not recognized as the name of a cmdlet |
PowerShell | The term 'jq' is not recognized... |
Detection package manager (une commande which/where par instance, cachee) :
| OS | Commande | Package managers |
|---|---|---|
| Linux | which apt dnf yum brew 2>/dev/null |
apt, dnf, yum, brew |
| Windows | where choco winget scoop 2>nul |
choco, winget, scoop |
Le resultat est cache dans token_metadata de l'instance (comme os_type).
Mapping outil → package (table statique, extensible) :
const TOOL_PACKAGES: Record<string, Record<string, string>> = {
// tool_name → { package_manager: package_name }
'jq': { apt: 'jq', yum: 'jq', dnf: 'jq', brew: 'jq', choco: 'jq', winget: 'jqlang.jq', scoop: 'jq' },
'curl': { apt: 'curl', yum: 'curl', choco: 'curl', winget: 'cURL.cURL', scoop: 'curl' },
'htop': { apt: 'htop', yum: 'htop', dnf: 'htop', brew: 'htop' },
'tree': { apt: 'tree', yum: 'tree', choco: 'tree', scoop: 'tree' },
'git': { apt: 'git', yum: 'git', choco: 'git', winget: 'Git.Git', scoop: 'git' },
'wget': { apt: 'wget', yum: 'wget', choco: 'wget', winget: 'JernejSimoncic.Wget', scoop: 'wget' },
'nano': { apt: 'nano', yum: 'nano', choco: 'nano' },
'vim': { apt: 'vim', yum: 'vim', brew: 'vim', choco: 'vim' },
'python3':{ apt: 'python3', yum: 'python3', brew: 'python', choco: 'python', winget: 'Python.Python.3' },
'node': { apt: 'nodejs', yum: 'nodejs', brew: 'node', choco: 'nodejs', winget: 'OpenJS.NodeJS' },
'docker': { apt: 'docker.io', yum: 'docker', brew: 'docker', choco: 'docker-desktop', winget: 'Docker.DockerDesktop' },
'ffmpeg': { apt: 'ffmpeg', yum: 'ffmpeg', brew: 'ffmpeg', choco: 'ffmpeg', winget: 'Gyan.FFmpeg', scoop: 'ffmpeg' },
'7z': { apt: 'p7zip-full', yum: 'p7zip', choco: '7zip', winget: '7zip.7zip', scoop: '7zip' },
// ... extensible
};
Dans POST /api/ai/test-command, apres execution :
// Si la commande a echoue, detecter outil manquant
if (!result.success && result.stderr) {
const detection = packageDetector.detect(result.stderr, instanceId, userId);
if (detection.detected) {
return {
...result,
missing_tool: {
name: detection.tool_name,
install_commands: detection.install_commands
}
};
}
}
Le frontend recoit missing_tool dans la reponse et peut proposer l'installation.
Frontend : Quand missing_tool est present dans la reponse test :
┌──────────────────────────────────────────────────┐
│ ⚠ Outil manquant : jq │
│ │
│ Commande d'installation suggeree : │
│ ┌──────────────────────────────────────────────┐ │
│ │ sudo apt install -y jq │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ [Installer] [Ignorer] [Sauver comme action] │
│ │
└──────────────────────────────────────────────────┘
3 choix :
install autoBackend : L'action d'installation est une action SSH classique avec :
{
"name": "Install jq",
"action_type": "ssh",
"instance_id": "xxx",
"config": {
"type": "command",
"command": "sudo apt install -y jq",
"output_parser": "raw"
},
"tags": ["install", "package-management"],
"input_schema": null
}
Consultation : Page instance → onglet "Packages" ou section dans la page existante.
Filtre les actions par tags contains 'install' + instance_id → affiche :
| Package | Date | Status | Commande |
|---|---|---|---|
| jq | 03/02/2026 14:30 | OK | sudo apt install -y jq |
| htop | 03/02/2026 14:35 | OK | sudo apt install -y htop |
| tree | 03/02/2026 15:00 | ERREUR | sudo apt install -y tree |
On sait exactement ce qui a ete installe via notre systeme, avec l'historique d'execution complet (succes, erreur, stdout, stderr, duree).
Desinstallation : Meme principe — bouton "Desinstaller" genere la commande inverse (apt remove, choco uninstall, etc.), cree une action avec tag uninstall.
| Fichier | Action | Description |
|---|---|---|
src/services/packageDetector.ts |
CREER | Detection outil manquant + mapping packages |
src/index.ts |
MODIFIER | Enrichir reponse /api/ai/test-command avec missing_tool |
src/services/ssh.ts |
MODIFIER | Detecter et cacher le package manager dans token_metadata |
| Fichier | Action | Description |
|---|---|---|
src/routes/connectors/instance/[id]/index.tsx |
MODIFIER | Bandeau missing_tool dans le flow AI suggest |
src/routes/connectors/instance/[id]/index.tsx |
MODIFIER | Section "Packages installes" (ou onglet) |
On reutilise les tables existantes :
actions avec tags: ['install'] ou tags: ['uninstall']action_executions pour l'historiqueuser_connectors.token_metadata pour le cache du package manager| Phase | Contenu | Dependances |
|---|---|---|
| 1 | packageDetector.ts + enrichissement test-command |
Aucune |
| 2 | Bandeau frontend "outil manquant" + bouton Installer | Phase 1 |
| 3 | Section inventaire packages par instance | Phase 2 |
| 4 | Desinstallation + tags uninstall |
Phase 3 |
TOOL_PACKAGES couvre les outils courants, pas toutpython3 vs python)