Merge branch 'develop'

- fix todo for server without tk
- split claude.md into rules
- integrate rtk for optimisation agent IA
- todo to add command for claude
- todo i18n use english key
- more space when print command into execute.py
- todo integrate deploy local clone
- script format
This commit is contained in:
Mathieu Benoit 2026-03-14 23:26:50 -04:00
commit b1eeeb6053
22 changed files with 1387 additions and 785 deletions

View file

@ -0,0 +1,14 @@
# Versions supportées
| Odoo | Python | Poetry | Statut |
|-------|----------|--------|------------|
| 18.0 | 3.12.10 | 2.1.3 | **Défaut** |
| 17.0 | 3.10.18 | 1.8.3 | Actif |
| 16.0 | 3.10.18 | 1.8.3 | Actif |
| 15.0 | 3.8.20 | 1.8.3 | Déprécié |
| 14.0 | 3.8.20 | 1.5.0 | Déprécié |
| 13.0 | 3.7.17 | 1.5.0 | Déprécié |
| 12.0 | 3.7.17 | 1.5.0 | Déprécié |
Configuration dans `conf/supported_version_erplibre.json`.
Fichiers de version : `.odoo-version`, `.erplibre-version`, `.poetry-version`, `.python-odoo-version`.

View file

@ -0,0 +1,45 @@
# Structure du projet
```
erplibre/
├── Makefile # Orchestrateur principal (inclut conf/make.*.Makefile)
├── run.sh / odoo_bin.sh # Lanceurs Odoo (venv + PYTHONPATH)
├── env_var.sh # Variables d'environnement globales
├── conf/ # Configuration : Makefiles modulaires, versions, manifests CSV
│ ├── make.installation.Makefile
│ ├── make.test.Makefile
│ ├── make.database.Makefile
│ ├── make.docker.Makefile
│ ├── make.code_generator.Makefile
│ ├── make.installation.poetry.Makefile
│ └── supported_version_erplibre.json
├── manifest/ # Manifests Google Repo (XML) par version Odoo
│ └── git_manifest_odoo{12..18}.0.xml
├── requirement/ # Dépendances par version
│ ├── pyproject.odooXX.0_pythonY.Z.toml
│ ├── poetry.odooXX.0_pythonY.Z.lock
│ └── requirements.odooXX.0_pythonY.Z.txt
├── script/ # Scripts utilitaires (32+ catégories)
│ ├── todo/ # CLI interactif principal (todo.py)
│ ├── database/ # Opérations DB (restore, migrate, image_db)
│ ├── addons/ # Gestion des modules (install, update, uninstall)
│ ├── code_generator/ # Génération de modules Odoo
│ ├── version/ # Changement de version
│ ├── git/ # Opérations Git et Google Repo
│ ├── maintenance/ # Formatage (black, isort, prettier)
│ ├── test/ # Tests parallèles + coverage
│ ├── docker/ # Build/run Docker
│ ├── poetry/ # Gestion Poetry
│ ├── deployment/ # Déploiement production
│ └── selenium/ # Tests web automatisés
├── docker/ # Dockerfiles + docker-compose par version
├── addons/ # Répertoire des addons (géré par Google Repo)
│ ├── OCA_*/ # Modules OCA
│ ├── ERPLibre_*/ # Modules ERPLibre
│ ├── TechnoLibre_*/ # Code generator + templates
│ └── MathBenTech_*/ # Modules spécialisés
├── odoo{12..18}.0/ # Sources Odoo par version
├── doc/ # Documentation (DEVELOPMENT, PRODUCTION, MIGRATION, etc.)
├── test/ # Framework de test
└── private/ # Fichiers privés (non versionné)
```

View file

@ -0,0 +1,63 @@
# Commandes essentielles
## Changement de version Odoo
```bash
make switch_odoo_18 # Passer à Odoo 18
make switch_odoo_17 # Passer à Odoo 17
make switch_odoo_16 # Passer à Odoo 16 (défaut)
make switch_odoo_15 # Passer à Odoo 15
make switch_odoo_14 # Passer à Odoo 14
make switch_odoo_13 # Passer à Odoo 13
make switch_odoo_12 # Passer à Odoo 12
make switch_odoo_16_update # Passer + mettre à jour les dépendances
```
## Exécution
```bash
make run # Lancer Odoo (port 8069)
make run_test # Lancer avec la DB "test"
./run.sh -d ma_base # Lancer avec une DB spécifique
./odoo_bin.sh [args] # Exécuter odoo-bin directement
```
## Base de données
```bash
make db_create_db_test # Créer la DB "test"
make db_clone_test_to_test2 # Cloner test -> test2
./script/database/db_restore.py --database NOM --image IMAGE
./script/addons/install_addons_dev.sh DB module1,module2
./script/addons/update_addons_all.sh DB
```
## Tests
```bash
make test # Tests de base + format
make test_full_fast # Tests complets en parallèle
make test_full_fast_coverage # Avec couverture de code
```
## Formatage du code
```bash
make format # Fichiers à commiter uniquement
make format_all # Tout formater (parallèle)
```
## Docker
```bash
make docker_run_daemon # Lancer en production
make docker_build_odoo_18 # Builder l'image Odoo 18
```
## Google Repo (gestion multi-dépôts)
```bash
make repo_show_status # Statut de tous les dépôts addons
make repo_configure_all # Configurer tous les dépôts
make repo_do_stash # Stash tous les dépôts
.venv.erplibre/bin/repo sync # Synchroniser les dépôts
```
## Installation
```bash
make install_os # Dépendances système
make install_odoo_18 # Installer Odoo 18 complet
```

View file

@ -0,0 +1,24 @@
# Conventions de code
## Python
- Formateur : **Black** (profil par défaut, ligne max 79 pour les modules Odoo)
- Imports : **isort** avec profil `black`, longueur de ligne 79
- Linting : **Flake8** avec bugbear, max-line-length 80, max-complexity 16
- Ignorer : E203, E501, W503 (compatibilité Black)
## XML / JSON / YAML
- Formateur : **Prettier** (via npm)
- Indentation : 4 espaces (XML/CSS/JS), 2 espaces (JSON/YAML)
## Fichiers
- Encodage : UTF-8
- Fins de ligne : LF (Unix)
- Indentation : 4 espaces (Python, XML, CSS, JS), 2 espaces (JSON, YAML, RST, MD)
- Retour à la ligne final : oui
- Espaces en fin de ligne : supprimés
## Git
- Branches : `develop` (développement), `master` (production)
- Pas de submodules Git — utilise **Google Repo** pour les addons
- Manifests XML dans `manifest/` pour chaque version Odoo
- Format de commit : `[TYPE] description` (ex: `[FIX]`, `[UPD]`, `[ADD]`, `[REM]`)

View file

@ -0,0 +1,22 @@
# Architecture des environnements virtuels
```
.venv.erplibre/ # Venv ERPLibre (outils : repo, poetry, coverage)
.venv.odoo18/ # Venv Odoo 18 (Python 3.12)
.venv.odoo17/ # Venv Odoo 17 (Python 3.10)
.venv.odoo16/ # Venv Odoo 16 (Python 3.10)
.venv.odoo14/ # Venv Odoo 14 (Python 3.8)
.venv.odoo12/ # Venv Odoo 12 (Python 3.7)
```
Géré via **pyenv** pour les multiples versions de Python.
## Système de dépendances
Chaque version Odoo a son propre ensemble dans `requirement/` :
- `pyproject.odooXX.0_pythonY.Z.toml` — Configuration Poetry
- `poetry.odooXX.0_pythonY.Z.lock` — Lock file Poetry
- `requirements.odooXX.0_pythonY.Z.txt` — Requirements pip (fallback)
- `ignore_requirements.odooXX.0.txt` — Paquets à ignorer
Mise à jour : `./script/poetry/poetry_update.py`

View file

@ -0,0 +1,9 @@
# Code Generator
ERPLibre inclut un système de génération de modules Odoo :
- `script/code_generator/new_project.py` — Créer un nouveau module
- `script/code_generator/create_from_existing_module.py` — Cloner un module existant
- `addons/TechnoLibre_odoo-code-generator/` — Moteur de génération
- `addons/TechnoLibre_odoo-code-generator-template/` — Templates
Documentation : `doc/CODE_GENERATOR.md`

View file

@ -0,0 +1,55 @@
# Documentation multilingue
La documentation est bilingue (anglais/français) via **mmg** (Multilingual Markdown Generator).
## Fonctionnement
- Les fichiers sources sont les `.base.md` (contiennent les deux langues)
- `mmg` génère : `FICHIER.md` (anglais) et `FICHIER.fr.md` (français)
- Marqueurs : `<!-- [en] -->`, `<!-- [fr] -->`, `<!-- [common] -->` (blocs de code partagés)
## Commandes
```bash
make doc_markdown # Regénérer toute la doc multilingue
```
## Convention
- **Ne jamais modifier directement** les fichiers `.md` ou `.fr.md` générés
- Toujours modifier le fichier `.base.md` correspondant, puis exécuter `make doc_markdown`
- Les blocs de code vont dans `<!-- [common] -->`, le texte dans `<!-- [en] -->` et `<!-- [fr] -->`
- En-tête obligatoire dans chaque `.base.md` :
```
<!---------------------------->
<!-- multilingual suffix: en, fr -->
<!-- no suffix: en -->
<!---------------------------->
```
## Fichiers concernés (30 fichiers)
- Racine : `README`, `CHANGELOG`, `TODO`
- `doc/` : DEVELOPMENT, PRODUCTION, DISCOVER, RUN, MIGRATION, WINDOWS_INSTALLATION, FAQ, GIT_REPO, POETRY, RELEASE, UPDATE, CONTRIBUTION, HOWTO, TODO, CODE_GENERATOR
- `docker/` : README
- `script/*/` : database, deployment, fork_github_repo, nginx, restful, selenium (2), todo, odoo/migration
- `.github/ISSUE_TEMPLATE/` : bug_report, feature_request
## Internationalisation du CLI TODO (i18n)
Le CLI interactif `script/todo/todo.py` supporte le français et l'anglais.
### Architecture
- **`script/todo/todo_i18n.py`** — Module de traduction (dictionnaire `TRANSLATIONS`, fonctions `t()`, `get_lang()`, `set_lang()`)
- Les chaînes traduisibles utilisent `t("clé")` au lieu de texte en dur
- Les entrées de `todo.json` peuvent avoir un champ `prompt_description_key` résolu via `t()` (fallback sur `prompt_description`)
### Résolution de la langue (priorité)
1. Variable d'environnement `EL_LANG` (définie dans `env_var.sh`, défaut `"fr"`)
2. Défaut : `"fr"`
### Comportement
- Première exécution : prompt bilingue demande à l'utilisateur de choisir sa langue
- Le choix est persisté dans `env_var.sh`
- Changement de langue possible via le menu Execute > Langue/Language
### Ajouter une traduction
1. Ajouter la clé dans `TRANSLATIONS` de `todo_i18n.py` avec les valeurs `"fr"` et `"en"`
2. Remplacer la chaîne en dur par `t("ma_clé")` dans `todo.py`
3. Pour les entrées JSON : ajouter `"prompt_description_key": "ma_clé"` dans `todo.json`

View file

@ -0,0 +1,9 @@
# Déploiement
- **Docker** : `docker-compose.yml` (PostgreSQL 18 + PostGIS 3.6)
- **Systemd** : `script/systemd/` pour les services
- **Nginx** : `script/nginx/` pour le reverse proxy
- **SSL** : Certbot pour les certificats
- **DNS** : `script/deployment/update_dns_cloudflare.py`
Plateformes supportées : Ubuntu 20.04-25.04, Linux Mint 22.3, Debian 12, Arch Linux, macOS (pyenv), Windows (WSL/Docker).

View file

@ -0,0 +1,49 @@
# Workflow Orchestration
## 1. Plan Mode Default
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
- If something goes sideways, STOP and re-plan immediately
- Don't keep pushing.
- Use plan mode for verification steps, not just building
- Write detailed specs upfront to reduce ambiguity
## 2. Subagent Strategy
- Use subagents liberally to keep the main context window clean.
- Offload research, exploration, and parallel analysis to subagents
- For complex problems, throw more compute at it via subagents
- One task per subagent for focused execution
## 3. Self-Improvement Loop
- Capture Lessons: Update AGENT.md with any change in approach the user has asked you to make.
- Write rules for yourself that prevent the same mistake
- Ruthlessly iterate on these lessons until the mistake rate drops.
- Review lessons at session start for relevant project
## 4. Verification Before Done
- Never mark a task complete without proving it works
- Diff behavior between main and your changes when relevant
- Ask yourself, "Would a staff engineer approve this?"
- Run tests, check logs, and demonstrate correctness.
## 5. Demand Elegance (Balanced)
- For non-trivial changes, pause and ask, "Is there a more elegant way?"
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
- Skip this for simple, obvious fixes
- Don't over-engineer.
- Challenge your own work before presenting it
## 6. Autonomous Bug Fixing
- When given a bug report, just fix it. Don't ask for hand-holding
- Point at logs, errors, and failing tests.
- Then resolve them.
- Zero context switching required from the user
- Go fix failing CI tests without being told how
## Task Management
1. **Plan First**: Write a plan to `tasks/todo.md` with checkable items.
2. **Verify Plan**: Check in before starting implementation.
3. **Track Progress**: Mark items complete as you go.
4. **Explain Changes**: High-level summary at each step.
5. **Document Results**: Add review section to `tasks/todo.md`
6. **Capture Lessons**: Update `tasks/lessons.md` after corrections

316
CLAUDE.md
View file

@ -6,256 +6,6 @@ ERPLibre est un fork communautaire d'Odoo Community Edition (OCE) supportant les
Version actuelle : **1.6.0** | Licence : **AGPL-3.0+** Version actuelle : **1.6.0** | Licence : **AGPL-3.0+**
Version Odoo par défaut : **18.0** (support officiel ERPLibre 1.6.0) Version Odoo par défaut : **18.0** (support officiel ERPLibre 1.6.0)
## Versions supportées
| Odoo | Python | Poetry | Statut |
|-------|----------|--------|------------|
| 18.0 | 3.12.10 | 2.1.3 | **Défaut** |
| 17.0 | 3.10.18 | 1.8.3 | Actif |
| 16.0 | 3.10.18 | 1.8.3 | Actif |
| 15.0 | 3.8.20 | 1.8.3 | Déprécié |
| 14.0 | 3.8.20 | 1.5.0 | Déprécié |
| 13.0 | 3.7.17 | 1.5.0 | Déprécié |
| 12.0 | 3.7.17 | 1.5.0 | Déprécié |
Configuration dans `conf/supported_version_erplibre.json`.
Fichiers de version : `.odoo-version`, `.erplibre-version`, `.poetry-version`, `.python-odoo-version`.
## Structure du projet
```
erplibre/
├── Makefile # Orchestrateur principal (inclut conf/make.*.Makefile)
├── run.sh / odoo_bin.sh # Lanceurs Odoo (venv + PYTHONPATH)
├── env_var.sh # Variables d'environnement globales
├── conf/ # Configuration : Makefiles modulaires, versions, manifests CSV
│ ├── make.installation.Makefile
│ ├── make.test.Makefile
│ ├── make.database.Makefile
│ ├── make.docker.Makefile
│ ├── make.code_generator.Makefile
│ ├── make.installation.poetry.Makefile
│ └── supported_version_erplibre.json
├── manifest/ # Manifests Google Repo (XML) par version Odoo
│ └── git_manifest_odoo{12..18}.0.xml
├── requirement/ # Dépendances par version
│ ├── pyproject.odooXX.0_pythonY.Z.toml
│ ├── poetry.odooXX.0_pythonY.Z.lock
│ └── requirements.odooXX.0_pythonY.Z.txt
├── script/ # Scripts utilitaires (32+ catégories)
│ ├── todo/ # CLI interactif principal (todo.py)
│ ├── database/ # Opérations DB (restore, migrate, image_db)
│ ├── addons/ # Gestion des modules (install, update, uninstall)
│ ├── code_generator/ # Génération de modules Odoo
│ ├── version/ # Changement de version
│ ├── git/ # Opérations Git et Google Repo
│ ├── maintenance/ # Formatage (black, isort, prettier)
│ ├── test/ # Tests parallèles + coverage
│ ├── docker/ # Build/run Docker
│ ├── poetry/ # Gestion Poetry
│ ├── deployment/ # Déploiement production
│ └── selenium/ # Tests web automatisés
├── docker/ # Dockerfiles + docker-compose par version
├── addons/ # Répertoire des addons (géré par Google Repo)
│ ├── OCA_*/ # Modules OCA
│ ├── ERPLibre_*/ # Modules ERPLibre
│ ├── TechnoLibre_*/ # Code generator + templates
│ └── MathBenTech_*/ # Modules spécialisés
├── odoo{12..18}.0/ # Sources Odoo par version
├── doc/ # Documentation (DEVELOPMENT, PRODUCTION, MIGRATION, etc.)
├── test/ # Framework de test
└── private/ # Fichiers privés (non versionné)
```
## Commandes essentielles
### Changement de version Odoo
```bash
make switch_odoo_18 # Passer à Odoo 18
make switch_odoo_17 # Passer à Odoo 17
make switch_odoo_16 # Passer à Odoo 16 (défaut)
make switch_odoo_15 # Passer à Odoo 15
make switch_odoo_14 # Passer à Odoo 14
make switch_odoo_13 # Passer à Odoo 13
make switch_odoo_12 # Passer à Odoo 12
make switch_odoo_16_update # Passer + mettre à jour les dépendances
```
### Exécution
```bash
make run # Lancer Odoo (port 8069)
make run_test # Lancer avec la DB "test"
./run.sh -d ma_base # Lancer avec une DB spécifique
./odoo_bin.sh [args] # Exécuter odoo-bin directement
```
### Base de données
```bash
make db_create_db_test # Créer la DB "test"
make db_clone_test_to_test2 # Cloner test -> test2
./script/database/db_restore.py --database NOM --image IMAGE
./script/addons/install_addons_dev.sh DB module1,module2
./script/addons/update_addons_all.sh DB
```
### Tests
```bash
make test # Tests de base + format
make test_full_fast # Tests complets en parallèle
make test_full_fast_coverage # Avec couverture de code
```
### Formatage du code
```bash
make format # Fichiers à commiter uniquement
make format_all # Tout formater (parallèle)
```
Outils : **Black** (Python, ligne max 79), **isort** (profil black, ligne max 79), **Prettier** (XML/JSON), **Flake8** (ligne max 80, complexité max 16).
### Docker
```bash
make docker_run_daemon # Lancer en production
make docker_build_odoo_18 # Builder l'image Odoo 18
```
### Google Repo (gestion multi-dépôts)
```bash
make repo_show_status # Statut de tous les dépôts addons
make repo_configure_all # Configurer tous les dépôts
make repo_do_stash # Stash tous les dépôts
.venv.erplibre/bin/repo sync # Synchroniser les dépôts
```
### Installation
```bash
make install_os # Dépendances système
make install_odoo_18 # Installer Odoo 18 complet
```
## Conventions de code
### Python
- Formateur : **Black** (profil par défaut, ligne max 79 pour les modules Odoo)
- Imports : **isort** avec profil `black`, longueur de ligne 79
- Linting : **Flake8** avec bugbear, max-line-length 80, max-complexity 16
- Ignorer : E203, E501, W503 (compatibilité Black)
### XML / JSON / YAML
- Formateur : **Prettier** (via npm)
- Indentation : 4 espaces (XML/CSS/JS), 2 espaces (JSON/YAML)
### Fichiers
- Encodage : UTF-8
- Fins de ligne : LF (Unix)
- Indentation : 4 espaces (Python, XML, CSS, JS), 2 espaces (JSON, YAML, RST, MD)
- Retour à la ligne final : oui
- Espaces en fin de ligne : supprimés
### Git
- Branches : `develop` (développement), `master` (production)
- Pas de submodules Git — utilise **Google Repo** pour les addons
- Manifests XML dans `manifest/` pour chaque version Odoo
## Architecture des environnements virtuels
```
.venv.erplibre/ # Venv ERPLibre (outils : repo, poetry, coverage)
.venv.odoo18/ # Venv Odoo 18 (Python 3.12)
.venv.odoo17/ # Venv Odoo 17 (Python 3.10)
.venv.odoo16/ # Venv Odoo 16 (Python 3.10)
.venv.odoo14/ # Venv Odoo 14 (Python 3.8)
.venv.odoo12/ # Venv Odoo 12 (Python 3.7)
```
Géré via **pyenv** pour les multiples versions de Python.
## Système de dépendances
Chaque version Odoo a son propre ensemble dans `requirement/` :
- `pyproject.odooXX.0_pythonY.Z.toml` — Configuration Poetry
- `poetry.odooXX.0_pythonY.Z.lock` — Lock file Poetry
- `requirements.odooXX.0_pythonY.Z.txt` — Requirements pip (fallback)
- `ignore_requirements.odooXX.0.txt` — Paquets à ignorer
Mise à jour : `./script/poetry/poetry_update.py`
## Code Generator
ERPLibre inclut un système de génération de modules Odoo :
- `script/code_generator/new_project.py` — Créer un nouveau module
- `script/code_generator/create_from_existing_module.py` — Cloner un module existant
- `addons/TechnoLibre_odoo-code-generator/` — Moteur de génération
- `addons/TechnoLibre_odoo-code-generator-template/` — Templates
Documentation : `doc/CODE_GENERATOR.md`
## Documentation multilingue
La documentation est bilingue (anglais/français) via **mmg** (Multilingual Markdown Generator).
### Fonctionnement
- Les fichiers sources sont les `.base.md` (contiennent les deux langues)
- `mmg` génère : `FICHIER.md` (anglais) et `FICHIER.fr.md` (français)
- Marqueurs : `<!-- [en] -->`, `<!-- [fr] -->`, `<!-- [common] -->` (blocs de code partagés)
### Commandes
```bash
make doc_markdown # Regénérer toute la doc multilingue
```
### Convention
- **Ne jamais modifier directement** les fichiers `.md` ou `.fr.md` générés
- Toujours modifier le fichier `.base.md` correspondant, puis exécuter `make doc_markdown`
- Les blocs de code vont dans `<!-- [common] -->`, le texte dans `<!-- [en] -->` et `<!-- [fr] -->`
- En-tête obligatoire dans chaque `.base.md` :
```
<!---------------------------->
<!-- multilingual suffix: en, fr -->
<!-- no suffix: en -->
<!---------------------------->
```
### Fichiers concernés (30 fichiers)
- Racine : `README`, `CHANGELOG`, `TODO`
- `doc/` : DEVELOPMENT, PRODUCTION, DISCOVER, RUN, MIGRATION, WINDOWS_INSTALLATION, FAQ, GIT_REPO, POETRY, RELEASE, UPDATE, CONTRIBUTION, HOWTO, TODO, CODE_GENERATOR
- `docker/` : README
- `script/*/` : database, deployment, fork_github_repo, nginx, restful, selenium (2), todo, odoo/migration
- `.github/ISSUE_TEMPLATE/` : bug_report, feature_request
## Internationalisation du CLI TODO (i18n)
Le CLI interactif `script/todo/todo.py` supporte le français et l'anglais.
### Architecture
- **`script/todo/todo_i18n.py`** — Module de traduction (dictionnaire `TRANSLATIONS`, fonctions `t()`, `get_lang()`, `set_lang()`)
- Les chaînes traduisibles utilisent `t("clé")` au lieu de texte en dur
- Les entrées de `todo.json` peuvent avoir un champ `prompt_description_key` résolu via `t()` (fallback sur `prompt_description`)
### Résolution de la langue (priorité)
1. Variable d'environnement `EL_LANG` (définie dans `env_var.sh`, défaut `"fr"`)
2. Défaut : `"fr"`
### Comportement
- Première exécution : prompt bilingue demande à l'utilisateur de choisir sa langue
- Le choix est persisté dans `env_var.sh`
- Changement de langue possible via le menu Execute > Langue/Language
### Ajouter une traduction
1. Ajouter la clé dans `TRANSLATIONS` de `todo_i18n.py` avec les valeurs `"fr"` et `"en"`
2. Remplacer la chaîne en dur par `t("ma_clé")` dans `todo.py`
3. Pour les entrées JSON : ajouter `"prompt_description_key": "ma_clé"` dans `todo.json`
## Déploiement
- **Docker** : `docker-compose.yml` (PostgreSQL 18 + PostGIS 3.6)
- **Systemd** : `script/systemd/` pour les services
- **Nginx** : `script/nginx/` pour le reverse proxy
- **SSL** : Certbot pour les certificats
- **DNS** : `script/deployment/update_dns_cloudflare.py`
Plateformes supportées : Ubuntu 20.04-25.04, Linux Mint 22.3, Debian 12, Arch Linux, macOS (pyenv), Windows (WSL/Docker).
## Points d'attention pour Claude ## Points d'attention pour Claude
- Toujours vérifier la version Odoo active avant de modifier du code (`cat .odoo-version`) - Toujours vérifier la version Odoo active avant de modifier du code (`cat .odoo-version`)
@ -270,58 +20,24 @@ Plateformes supportées : Ubuntu 20.04-25.04, Linux Mint 22.3, Debian 12, Arch L
- Pour la documentation : modifier les `.base.md`, jamais les `.md` ou `.fr.md` directement - Pour la documentation : modifier les `.base.md`, jamais les `.md` ou `.fr.md` directement
- Outil mmg disponible via `source .venv.erplibre/bin/activate && mmg` - Outil mmg disponible via `source .venv.erplibre/bin/activate && mmg`
## Workflow Orchestration
### 1. Plan Mode Default
- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
- If something goes sideways, STOP and re-plan immediately
- Don't keep pushing.
- Use plan mode for verification steps, not just building
- Write detailed specs upfront to reduce ambiguity
### 2. Subagent Strategy
- Use subagents liberally to keep the main context window clean.
- Offload research, exploration, and parallel analysis to subagents
- For complex problems, throw more compute at it via subagents
- One task per subagent for focused execution
### 3. Self-Improvement Loop
- Capture Lessons: Update AGENT.md with any change in approach the user has asked you to make.
- Write rules for yourself that prevent the same mistake
- Ruthlessly iterate on these lessons until the mistake rate drops.
- Review lessons at session start for relevant project
### 4. Verification Before Done
- Never mark a task complete without proving it works
- Diff behavior between main and your changes when relevant
- Ask yourself, "Would a staff engineer approve this?"
- Run tests, check logs, and demonstrate correctness.
### 5. Demand Elegance (Balanced)
- For non-trivial changes, pause and ask, "Is there a more elegant way?"
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
- Skip this for simple, obvious fixes
- Don't over-engineer.
- Challenge your own work before presenting it
### 6. Autonomous Bug Fixing
- When given a bug report, just fix it. Don't ask for hand-holding
- Point at logs, errors, and failing tests.
- Then resolve them.
- Zero context switching required from the user
- Go fix failing CI tests without being told how
## Task Management
1. **Plan First**: Write a plan to `tasks/todo.md` with checkable items.
2. **Verify Plan**: Check in before starting implementation.
3. **Track Progress**: Mark items complete as you go.
4. **Explain Changes**: High-level summary at each step.
5. **Document Results**: Add review section to `tasks/todo.md`
6. **Capture Lessons**: Update `tasks/lessons.md` after corrections
## Core Principles ## Core Principles
- **Simplicity First**: Make every change as simple as possible. Impact minimal code. - **Simplicity First**: Make every change as simple as possible. Impact minimal code.
- **No Laziness**: Find root causes. No temporary fixes. Senior developer standards. - **No Laziness**: Find root causes. No temporary fixes. Senior developer standards.
- **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs. - **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs.
## Règles détaillées
Les instructions détaillées sont dans `.claude/rules/` :
| Fichier | Contenu |
|---------|---------|
| `01-versions.md` | Versions Odoo/Python/Poetry supportées |
| `02-project-structure.md` | Arborescence du projet |
| `03-commands.md` | Commandes essentielles (make, scripts) |
| `04-code-conventions.md` | Conventions Python, XML, fichiers, Git |
| `05-environments.md` | Venvs, pyenv, système de dépendances |
| `06-code-generator.md` | Génération de modules Odoo |
| `07-documentation.md` | Documentation multilingue (mmg) + i18n CLI |
| `08-deployment.md` | Docker, systemd, nginx, SSL, DNS |
| `09-workflow.md` | Workflow orchestration + task management |

View file

@ -0,0 +1,143 @@
---
name: todo_add_command
description: "Add a new menu command to script/todo/todo.py with i18n support and optional todo.json entry."
allowed-tools:
- Read
- Edit
- Write
- Grep
- Glob
- Bash(python3 -m py_compile:*)
- Bash(python3 -c:*)
---
## Context
- Current todo.py menu structure: !`grep -n "def prompt_execute" script/todo/todo.py | head -20`
- Current todo.json sections: !`python3 -c "import json; d=json.load(open('script/todo/todo.json')); print('\n'.join(d.keys()))"`
- Current i18n keys count: !`grep -c fr.: script/todo/todo_i18n.py`
## Architecture Reference
### Files to modify
| File | Role |
|------|------|
| `script/todo/todo.py` | Main CLI — menu methods, business logic |
| `script/todo/todo_i18n.py` | Translations dict `TRANSLATIONS` with `"fr"` and `"en"` keys |
| `script/todo/todo.json` | Config-driven entries (optional, for `bash_command` or `makefile_cmd`) |
### Pattern A — Hardcoded menu entry (interactive logic)
Used when the command needs Python logic (user prompts, conditionals, API calls).
1. **Add i18n keys** in `todo_i18n.py` `TRANSLATIONS` dict:
```python
"my_feature_description": {
"fr": "Description en français",
"en": "English description",
},
```
2. **Add choice** in the parent menu method (e.g. `prompt_execute_git`):
```python
choices = [
...,
{"prompt_description": t("my_feature_description")},
]
```
3. **Add elif branch** in the same menu's while loop:
```python
elif status == "N":
self._my_feature()
```
4. **Add method** to the `TODO` class:
```python
def _my_feature(self):
# Implementation here
pass
```
### Pattern B — Config-driven entry (simple bash command)
Used when the command just runs a bash command or a make target.
1. **Add i18n key** in `todo_i18n.py` (use `prompt_description_key`).
2. **Add entry** in `todo.json` under the appropriate `*_from_makefile` section:
```json
{
"prompt_description_key": "my_i18n_key",
"bash_command": "my-command --flag"
}
```
Or for make targets:
```json
{
"prompt_description_key": "my_i18n_key",
"makefile_cmd": "my_make_target"
}
```
These are automatically picked up by `execute_from_configuration()`.
### Available menu sections
| Menu | Method | JSON key |
|------|--------|----------|
| Automation | `prompt_execute_function` | `function` |
| Code | `prompt_execute_code` | `code_from_makefile` |
| Config | `prompt_execute_config` | — |
| Database | `prompt_execute_database` | — |
| Doc | `prompt_execute_doc` | — |
| Git | `prompt_execute_git` | `git_from_makefile` |
| GPT code | `prompt_execute_gpt_code` | — |
| Network | `prompt_execute_network` | — |
| Process | `prompt_execute_process` | — |
| Run | `prompt_execute_instance` | `instance` |
| Security | `prompt_execute_security` | — |
| Test | `prompt_execute_test` | — |
| Update | `prompt_execute_update` | `update_from_makefile` |
### Key conventions
- i18n keys: `snake_case`, grouped by section with a comment header
- Method names: `_private_method` for actions, `prompt_execute_*` for submenus
- All user-facing strings must use `t("key")` — never hardcoded text
- Use `self.execute.exec_command_live(cmd, source_erplibre=False)` to run bash
- Use `input(t("prompt_key")).strip()` for user input
- Use `click.prompt(help_info)` for menu navigation
- Menu methods return `False` on back, use `self.fill_help_info(choices)` for formatting
## Task
Add a new command to the todo.py menu system. The user will describe what they want.
### Steps
1. **Determine the target menu section** from the user's description
2. **Choose Pattern A or B** based on complexity:
- Simple bash/make command → Pattern B (todo.json entry)
- Needs user interaction or logic → Pattern A (hardcoded method)
3. **Add i18n translations** in `todo_i18n.py` — always both `"fr"` and `"en"`
4. **Implement the feature** following the appropriate pattern
5. **Validate syntax**:
```bash
python3 -m py_compile script/todo/todo.py
python3 -m py_compile script/todo/todo_i18n.py
python3 -c "import json; json.load(open('script/todo/todo.json'))"
```
### Rules
- Keep menu numbering sequential — update all `elif` branches if inserting
- Group i18n keys with a `# Section name` comment
- Match existing code style (Black, 79 char lines for Odoo modules)
- Do not break existing menu entries or their numbering
- Test that `fill_help_info` and `execute_from_configuration` handle the new entry
## User Request
$ARGUMENTS

View file

@ -96,9 +96,7 @@ def commit_by_directory():
run_git_command(f'cd "{config.path}" && git rm -r {directory}') run_git_command(f'cd "{config.path}" && git rm -r {directory}')
# Crée le commit # Crée le commit
commit_message = ( commit_message = f"[{mig_prefix_msg}] {directory}: Migration to {config.odoo_version}"
f"[{mig_prefix_msg}] {directory}: Migration to {config.odoo_version}"
)
commit_result = run_git_command( commit_result = run_git_command(
f'cd "{config.path}" && git commit -m "{commit_message}"' f'cd "{config.path}" && git commit -m "{commit_message}"'
) )

View file

@ -10,28 +10,31 @@ def get_db_list_xmlrpc(odoo_url):
Retrieves the list of Odoo databases using the XML-RPC API. Retrieves the list of Odoo databases using the XML-RPC API.
""" """
try: try:
common = xmlrpc.client.ServerProxy(f'{odoo_url}/xmlrpc/db') common = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/db")
db_list = common.list() db_list = common.list()
return db_list return db_list
except xmlrpc.client.Fault as e: except xmlrpc.client.Fault as e:
print(f"XML-RPC Error: {e.faultCode} - {e.faultString}", file=sys.stderr) print(
f"XML-RPC Error: {e.faultCode} - {e.faultString}", file=sys.stderr
)
return [] return []
except Exception as e: except Exception as e:
print(f"Connection Error: {e}", file=sys.stderr) print(f"Connection Error: {e}", file=sys.stderr)
return [] return []
# --- CLI using Click --- # --- CLI using Click ---
@click.command() @click.command()
@click.option( @click.option(
'--odoo-url', "--odoo-url",
default='http://localhost:8069', default="http://localhost:8069",
help='URL of the Odoo server.', help="URL of the Odoo server.",
show_default=True show_default=True,
) )
@click.option( @click.option(
'--raw', "--raw",
is_flag=True, is_flag=True,
help='Output one database per line, without extra formatting. Useful for scripting.' help="Output one database per line, without extra formatting. Useful for scripting.",
) )
def list_databases(odoo_url, raw=False): def list_databases(odoo_url, raw=False):
""" """
@ -56,5 +59,5 @@ def list_databases(odoo_url, raw=False):
# --- Script Execution --- # --- Script Execution ---
if __name__ == '__main__': if __name__ == "__main__":
list_databases() list_databases()

View file

@ -115,7 +115,7 @@ class Execute:
command = self.cmd_source_default % command command = self.cmd_source_default % command
if not quiet: if not quiet:
print("🏠 ⬇ Execute command :") print("🏠 ⬇ Execute command :\n")
print(command) print(command)
output_lines = [] output_lines = []
@ -172,10 +172,10 @@ class Execute:
duration_delta = datetime.timedelta(seconds=duration_sec) duration_delta = datetime.timedelta(seconds=duration_sec)
human_time = humanize.precisedelta(duration_delta) human_time = humanize.precisedelta(duration_delta)
if not quiet: if not quiet:
print(f"🏠 ⬆ Executed ({human_time}) :") print(f"🏠 ⬆ Executed ({human_time}) :\n")
else: else:
if not quiet: if not quiet:
print(f"🏠 ⬆ Executed ({duration_sec:.2f} sec.) :") print(f"🏠 ⬆ Executed ({duration_sec:.2f} sec.) :\n")
if not quiet: if not quiet:
print(command) print(command)
print() print()

View file

@ -151,8 +151,7 @@ Use --production-ready for /srv/git (requires root).
type=int, type=int,
default=DEFAULT_JOBS, default=DEFAULT_JOBS,
help=( help=(
"Parallel jobs for init/remote/push" "Parallel jobs for init/remote/push" f" (default: {DEFAULT_JOBS})"
f" (default: {DEFAULT_JOBS})"
), ),
) )
parser.add_argument( parser.add_argument(
@ -247,11 +246,7 @@ def get_erplibre_root_project(erplibre_root):
capture_output=True, capture_output=True,
text=True, text=True,
) )
revision = ( revision = result.stdout.strip() if result.returncode == 0 else "master"
result.stdout.strip()
if result.returncode == 0
else "master"
)
return { return {
"name": ERPLIBRE_REPO_NAME, "name": ERPLIBRE_REPO_NAME,
@ -278,19 +273,13 @@ async def _init_single_bare_repo(git_path, project, semaphore):
return "skipped" return "skipped"
_logger.info(f" Creating: {bare_path}") _logger.info(f" Creating: {bare_path}")
_, err, rc = await _run_git( _, err, rc = await _run_git("git", "init", "--bare", bare_path)
"git", "init", "--bare", bare_path
)
if rc != 0: if rc != 0:
_logger.warning( _logger.warning(f" Init failed: {bare_path}: {err.strip()}")
f" Init failed: {bare_path}: {err.strip()}"
)
return "error" return "error"
# Enable git daemon export # Enable git daemon export
export_file = os.path.join( export_file = os.path.join(bare_path, "git-daemon-export-ok")
bare_path, "git-daemon-export-ok"
)
open(export_file, "w").close() open(export_file, "w").close()
# Allow pushing from shallow clones # Allow pushing from shallow clones
@ -312,10 +301,7 @@ async def init_bare_repos(git_path, projects, jobs):
semaphore = asyncio.Semaphore(jobs) semaphore = asyncio.Semaphore(jobs)
results = await asyncio.gather( results = await asyncio.gather(
*[ *[_init_single_bare_repo(git_path, p, semaphore) for p in projects]
_init_single_bare_repo(git_path, p, semaphore)
for p in projects
]
) )
created = results.count("created") created = results.count("created")
@ -352,9 +338,7 @@ async def _add_single_remote(
): ):
"""Add or update remote for a single repo (async).""" """Add or update remote for a single repo (async)."""
async with semaphore: async with semaphore:
repo_path = os.path.join( repo_path = os.path.join(erplibre_root, project["path"])
erplibre_root, project["path"]
)
if not os.path.isdir(repo_path): if not os.path.isdir(repo_path):
_logger.warning(f" Not found: {repo_path}") _logger.warning(f" Not found: {repo_path}")
return "error" return "error"
@ -368,9 +352,7 @@ async def _add_single_remote(
) )
# Check if remote already exists # Check if remote already exists
stdout, _, _ = await _run_git( stdout, _, _ = await _run_git("git", "-C", repo_path, "remote")
"git", "-C", repo_path, "remote"
)
existing_remotes = stdout.strip().split("\n") existing_remotes = stdout.strip().split("\n")
if remote_name in existing_remotes: if remote_name in existing_remotes:
@ -403,8 +385,7 @@ async def _add_single_remote(
) )
if rc != 0: if rc != 0:
_logger.warning( _logger.warning(
f" add failed for" f" add failed for" f" {project['path']}: {err.strip()}"
f" {project['path']}: {err.strip()}"
) )
return "error" return "error"
_logger.info(f" Added: {project['path']}") _logger.info(f" Added: {project['path']}")
@ -447,10 +428,7 @@ async def add_remotes(
added = results.count("added") added = results.count("added")
updated = results.count("updated") updated = results.count("updated")
errors = results.count("error") errors = results.count("error")
print( print(f"Remotes: {added} added, {updated} updated," f" {errors} errors")
f"Remotes: {added} added, {updated} updated,"
f" {errors} errors"
)
# --- Async workers for push --- # --- Async workers for push ---
@ -458,9 +436,7 @@ async def add_remotes(
async def _is_detached_head(repo_path): async def _is_detached_head(repo_path):
"""Check if a repo is in detached HEAD state.""" """Check if a repo is in detached HEAD state."""
_, _, rc = await _run_git( _, _, rc = await _run_git("git", "-C", repo_path, "symbolic-ref", "HEAD")
"git", "-C", repo_path, "symbolic-ref", "HEAD"
)
return rc != 0 return rc != 0
@ -471,9 +447,7 @@ async def _checkout_manifest_branch(repo_path, revision):
on the exact commit. We create/checkout a local branch on the exact commit. We create/checkout a local branch
matching the manifest revision so git push works. matching the manifest revision so git push works.
""" """
branch = ( branch = revision.split("/")[-1] if "/" in revision else revision
revision.split("/")[-1] if "/" in revision else revision
)
# Check if local branch already exists # Check if local branch already exists
_, _, rc = await _run_git( _, _, rc = await _run_git(
@ -485,9 +459,7 @@ async def _checkout_manifest_branch(repo_path, revision):
f"refs/heads/{branch}", f"refs/heads/{branch}",
) )
if rc == 0: if rc == 0:
await _run_git( await _run_git("git", "-C", repo_path, "checkout", branch)
"git", "-C", repo_path, "checkout", branch
)
else: else:
await _run_git( await _run_git(
"git", "git",
@ -514,9 +486,7 @@ async def _update_bare_head(git_path, project):
revision = project.get("revision", "") revision = project.get("revision", "")
if not revision: if not revision:
return return
branch = ( branch = revision.split("/")[-1] if "/" in revision else revision
revision.split("/")[-1] if "/" in revision else revision
)
await _run_git( await _run_git(
"git", "git",
"-C", "-C",
@ -529,20 +499,10 @@ async def _update_bare_head(git_path, project):
async def _try_unshallow(repo_path, project, remote_name): async def _try_unshallow(repo_path, project, remote_name):
"""Try to unshallow a repo by fetching full history.""" """Try to unshallow a repo by fetching full history."""
shallow_file = os.path.join( shallow_file = os.path.join(repo_path, ".git", "shallow")
repo_path, ".git", "shallow" _logger.info(f" Unshallowing {project['path']}...")
) stdout, _, _ = await _run_git("git", "-C", repo_path, "remote")
_logger.info( remotes = [r for r in stdout.strip().split("\n") if r and r != remote_name]
f" Unshallowing {project['path']}..."
)
stdout, _, _ = await _run_git(
"git", "-C", repo_path, "remote"
)
remotes = [
r
for r in stdout.strip().split("\n")
if r and r != remote_name
]
manifest_remote = project.get("remote", "") manifest_remote = project.get("remote", "")
if manifest_remote in remotes: if manifest_remote in remotes:
remotes.remove(manifest_remote) remotes.remove(manifest_remote)
@ -562,9 +522,7 @@ async def _try_unshallow(repo_path, project, remote_name):
except asyncio.TimeoutError: except asyncio.TimeoutError:
continue continue
if not os.path.exists(shallow_file): if not os.path.exists(shallow_file):
_logger.info( _logger.info(f" Unshallowed via {try_remote}")
f" Unshallowed via {try_remote}"
)
return return
if os.path.exists(shallow_file): if os.path.exists(shallow_file):
_logger.warning( _logger.warning(
@ -585,30 +543,22 @@ async def _push_single_repo(
): ):
"""Push a single repo to local remote (async).""" """Push a single repo to local remote (async)."""
async with semaphore: async with semaphore:
repo_path = os.path.join( repo_path = os.path.join(erplibre_root, project["path"])
erplibre_root, project["path"]
)
if not os.path.isdir(repo_path): if not os.path.isdir(repo_path):
_logger.warning(f" Not found: {repo_path}") _logger.warning(f" Not found: {repo_path}")
return "error", False return "error", False
# Handle shallow repos # Handle shallow repos
shallow_file = os.path.join( shallow_file = os.path.join(repo_path, ".git", "shallow")
repo_path, ".git", "shallow"
)
if os.path.exists(shallow_file): if os.path.exists(shallow_file):
if unshallow: if unshallow:
await _try_unshallow( await _try_unshallow(repo_path, project, remote_name)
repo_path, project, remote_name
)
else: else:
# Enable shallow push on bare repo # Enable shallow push on bare repo
repo_name = project["name"] repo_name = project["name"]
if not repo_name.endswith(".git"): if not repo_name.endswith(".git"):
repo_name += ".git" repo_name += ".git"
bare_path = os.path.join( bare_path = os.path.join(git_path, repo_name)
git_path, repo_name
)
if os.path.isdir(bare_path): if os.path.isdir(bare_path):
await _run_git( await _run_git(
"git", "git",
@ -618,19 +568,14 @@ async def _push_single_repo(
"receive.shallowUpdate", "receive.shallowUpdate",
"true", "true",
) )
_logger.info( _logger.info(f" Shallow push for" f" {project['path']}")
f" Shallow push for"
f" {project['path']}"
)
# Handle detached HEAD: checkout manifest branch # Handle detached HEAD: checkout manifest branch
did_checkout = False did_checkout = False
if await _is_detached_head(repo_path): if await _is_detached_head(repo_path):
revision = project.get("revision", "") revision = project.get("revision", "")
if revision: if revision:
branch = await _checkout_manifest_branch( branch = await _checkout_manifest_branch(repo_path, revision)
repo_path, revision
)
_logger.info( _logger.info(
f" Checkout {branch} for" f" Checkout {branch} for"
f" {project['path']}" f" {project['path']}"
@ -663,9 +608,7 @@ async def _push_single_repo(
"push", "push",
remote_name, remote_name,
] ]
_, err, rc = await _run_git( _, err, rc = await _run_git(*cmd, timeout=120)
*cmd, timeout=120
)
if rc != 0: if rc != 0:
_logger.warning( _logger.warning(
f" Push failed for" f" Push failed for"
@ -675,14 +618,10 @@ async def _push_single_repo(
return "error", did_checkout return "error", did_checkout
else: else:
await _update_bare_head(git_path, project) await _update_bare_head(git_path, project)
_logger.info( _logger.info(f" Pushed: {project['path']}")
f" Pushed: {project['path']}"
)
return "pushed", did_checkout return "pushed", did_checkout
except asyncio.TimeoutError: except asyncio.TimeoutError:
_logger.warning( _logger.warning(f" Timeout pushing {project['path']}")
f" Timeout pushing {project['path']}"
)
return "error", did_checkout return "error", did_checkout
@ -744,8 +683,7 @@ def print_clone_commands(git_path, projects, port):
if clone_path == ".": if clone_path == ".":
clone_path = "erplibre" clone_path = "erplibre"
lines.append( lines.append(
f" git clone {base_url}/{repo_name}" f" git clone {base_url}/{repo_name}" f" {clone_path}"
f" {clone_path}"
) )
lines.sort() lines.sort()
for line in lines: for line in lines:

View file

@ -26,17 +26,10 @@ def get_pull_request_repo(
parsed_url = parse(upstream_url) parsed_url = parse(upstream_url)
status, user = gh.user.get() status, user = gh.user.get()
user_name = ( user_name = user["login"] if not organization_name else organization_name
user["login"] if not organization_name else organization_name status, lst_pull = gh.repos[user_name][parsed_url.repo].pulls.get()
)
status, lst_pull = (
gh.repos[user_name][parsed_url.repo].pulls.get()
)
if type(lst_pull) is dict: if type(lst_pull) is dict:
print( print(f"For url {upstream_url}," f" got {lst_pull.get('message')}")
f"For url {upstream_url},"
f" got {lst_pull.get('message')}"
)
return False return False
else: else:
for pull in lst_pull: for pull in lst_pull:
@ -53,26 +46,21 @@ def fork_repo(
parsed_url = parse(upstream_url) parsed_url = parse(upstream_url)
status, user = gh.user.get() status, user = gh.user.get()
user_name = ( user_name = user["login"] if not organization_name else organization_name
user["login"] if not organization_name else organization_name status, forked_repo = gh.repos[user_name][parsed_url.repo].get()
)
status, forked_repo = (
gh.repos[user_name][parsed_url.repo].get()
)
if status == 404: if status == 404:
status, upstream_repo = ( status, upstream_repo = gh.repos[parsed_url.owner][
gh.repos[parsed_url.owner][parsed_url.repo].get() parsed_url.repo
) ].get()
if status == 404: if status == 404:
print("Unable to find repo %s" % upstream_url) print("Unable to find repo %s" % upstream_url)
exit(1) exit(1)
args = {} args = {}
if organization_name: if organization_name:
args["organization"] = organization_name args["organization"] = organization_name
status, forked_repo = ( status, forked_repo = gh.repos[parsed_url.owner][
gh.repos[parsed_url.owner][parsed_url.repo] parsed_url.repo
.forks.post(**args) ].forks.post(**args)
)
if status == 404: if status == 404:
print( print(
f"{Fore.RED}Error{Style.RESET_ALL} when forking" f"{Fore.RED}Error{Style.RESET_ALL} when forking"
@ -82,23 +70,16 @@ def fork_repo(
else: else:
try: try:
print( print(
"Forked %s to %s" "Forked %s to %s" % (upstream_url, forked_repo["html_url"])
% (upstream_url, forked_repo["html_url"])
) )
except Exception as e: except Exception as e:
print(e) print(e)
print(forked_repo) print(forked_repo)
print(upstream_url) print(upstream_url)
elif status == 202: elif status == 202:
print( print("Forked repo %s already exists" % forked_repo["full_name"])
"Forked repo %s already exists"
% forked_repo["full_name"]
)
elif status != 200: elif status != 200:
print( print("Status not supported: %s - %s" % (status, forked_repo))
"Status not supported: %s - %s"
% (status, forked_repo)
)
exit(1) exit(1)
@ -110,9 +91,7 @@ def add_and_fetch_remote(
""" """
try: try:
working_repo = Repo(repo_info.relative_path) working_repo = Repo(repo_info.relative_path)
if repo_info.organization in [ if repo_info.organization in [a.name for a in working_repo.remotes]:
a.name for a in working_repo.remotes
]:
print( print(
f'Remote "{repo_info.organization}" already exist' f'Remote "{repo_info.organization}" already exist'
f" in {repo_info.relative_path}" f" in {repo_info.relative_path}"
@ -122,8 +101,7 @@ def add_and_fetch_remote(
print(f"New repo {repo_info.relative_path}") print(f"New repo {repo_info.relative_path}")
if not root_repo: if not root_repo:
print( print(
"Missing git repository to root for repo" "Missing git repository to root for repo" f" {repo_info.path}"
f" {repo_info.path}"
) )
return return
if branch_name: if branch_name:
@ -149,16 +127,14 @@ def add_and_fetch_remote(
# Add remote # Add remote
upstream_remote = retry( upstream_remote = retry(
wait_exponential_multiplier=1000, stop_max_delay=15000 wait_exponential_multiplier=1000, stop_max_delay=15000
)(working_repo.create_remote)( )(working_repo.create_remote)(repo_info.organization, repo_info.url_https)
repo_info.organization, repo_info.url_https
)
print( print(
'Remote "%s" created for %s' 'Remote "%s" created for %s'
% (repo_info.organization, repo_info.url_https) % (repo_info.organization, repo_info.url_https)
) )
# Fetch the remote # Fetch the remote
retry( retry(wait_exponential_multiplier=1000, stop_max_delay=15000)(
wait_exponential_multiplier=1000, stop_max_delay=15000 upstream_remote.fetch
)(upstream_remote.fetch)() )()
print('Remote "%s" fetched' % repo_info.organization) print('Remote "%s" fetched' % repo_info.organization)

View file

@ -26,7 +26,9 @@ _logger = logging.getLogger(__name__)
PROJECT_NAME = os.path.basename(os.getcwd()) PROJECT_NAME = os.path.basename(os.getcwd())
IDEA_PATH = "./.idea" IDEA_PATH = "./.idea"
DEFAULT_ODOO_BIN = "./odoo/odoo-bin" # Need this to replace static configuration DEFAULT_ODOO_BIN = (
"./odoo/odoo-bin" # Need this to replace static configuration
)
IDEA_MISC = os.path.join(IDEA_PATH, "misc.xml") IDEA_MISC = os.path.join(IDEA_PATH, "misc.xml")
IDEA_WORKSPACE = os.path.join(IDEA_PATH, "workspace.xml") IDEA_WORKSPACE = os.path.join(IDEA_PATH, "workspace.xml")
VCS_WORKSPACE = os.path.join(IDEA_PATH, "vcs.xml") VCS_WORKSPACE = os.path.join(IDEA_PATH, "vcs.xml")

View file

@ -2,7 +2,9 @@
# © 2021-2026 TechnoLibre (http://www.technolibre.ca) # © 2021-2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
installed_modules = env["ir.module.module"].search([('state', '=', 'installed')]) installed_modules = env["ir.module.module"].search(
[("state", "=", "installed")]
)
print("Installed modules:") print("Installed modules:")

View file

@ -10,18 +10,20 @@ print("Running a script in the Odoo shell!")
# ERROR: insert or update on table "discuss_channel_member" violates foreign key constraint "discuss_channel_member_channel_id_fkey" # ERROR: insert or update on table "discuss_channel_member" violates foreign key constraint "discuss_channel_member_channel_id_fkey"
# DÉTAIL : Key (channel_id)=(3) is not present in table "discuss_channel". # DÉTAIL : Key (channel_id)=(3) is not present in table "discuss_channel".
env.cr.execute(""" env.cr.execute(
"""
ALTER TABLE discuss_channel ALTER TABLE discuss_channel
DROP CONSTRAINT discuss_channel_channel_type_not_null; DROP CONSTRAINT discuss_channel_channel_type_not_null;
""") """
)
env.cr.commit() env.cr.commit()
#env.cr.execute("SELECT to_regclass('public.discuss_channel');") # env.cr.execute("SELECT to_regclass('public.discuss_channel');")
#print("table:", env.cr.fetchone()) # print("table:", env.cr.fetchone())
#print( # print(
# "model discuss.channel:", # "model discuss.channel:",
# env["ir.model"].search([("model", "=", "discuss.channel")]), # env["ir.model"].search([("model", "=", "discuss.channel")]),
#) # )
print("End fix migration with update mail postgresql 17 to postgresql 18.") print("End fix migration with update mail postgresql 17 to postgresql 18.")

View file

@ -10,47 +10,52 @@
}, },
"instance": [ "instance": [
{ {
"prompt_description_key": "json_instance_test", "prompt_description_key": "Test - Minimal base instance",
"makefile_cmd": "db_restore_erplibre_base_db_test", "makefile_cmd": "db_restore_erplibre_base_db_test",
"database": "test" "database": "test"
}, },
{ {
"prompt_description_key": "json_robot_minimal", "prompt_description_key": "Open RobotLibre 🤖 minimal",
"makefile_cmd": "robot_libre", "makefile_cmd": "robot_libre",
"database": "robotlibre" "database": "robotlibre"
}, },
{ {
"prompt_description_key": "json_robot_search", "prompt_description_key": "Open RobotLibre 🤖 with search enabled",
"makefile_cmd": "robot_libre_all", "makefile_cmd": "robot_libre_all",
"database": "robotlibre" "database": "robotlibre"
} }
], ],
"function": [ "function": [
{ {
"prompt_description_key": "json_open_erplibre_todo", "prompt_description_key": "Open ERPLibre with TODO 🤖",
"command": "./.venv.erplibre/bin/python ./script/selenium/web_login.py" "command": "./.venv.erplibre/bin/python ./script/selenium/web_login.py"
} }
], ],
"update_from_makefile": [ "update_from_makefile": [
{ {
"prompt_description_key": "json_update_erplibre_base_test", "prompt_description_key": "Update all erplibre_base on database test",
"makefile_cmd": "db_erplibre_base_db_test_update_all", "makefile_cmd": "db_erplibre_base_db_test_update_all",
"database": "test" "database": "test"
} }
], ],
"code_from_makefile": [ "code_from_makefile": [
{ {
"prompt_description_key": "json_show_code_status", "prompt_description_key": "Show code status",
"makefile_cmd": "repo_show_status" "makefile_cmd": "repo_show_status"
}, },
{ {
"prompt_description_key": "json_stash_all_code", "prompt_description_key": "Stash all code",
"makefile_cmd": "repo_do_stash" "makefile_cmd": "repo_do_stash"
}, },
{ {
"prompt_description_key": "json_format_modified_code", "prompt_description_key": "Format modified code",
"makefile_cmd": "format" "makefile_cmd": "format"
} }
], ],
"git_from_makefile": [] "git_from_makefile": [
{
"prompt_description_key": "Configure git local editor to vim",
"bash_command": "git config --global core.editor \"vim\""
}
]
} }

File diff suppressed because it is too large Load diff

View file

@ -15,539 +15,732 @@ TRANSLATIONS = {
"fr": "L'importation est un succès!", "fr": "L'importation est un succès!",
"en": "Importation success!", "en": "Importation success!",
}, },
"opening": { "Opening TODO ...": {
"fr": "Ouverture de TODO en cours ...", "fr": "Ouverture de TODO en cours ...",
"en": "Opening TODO ...", "en": "Opening TODO ...",
}, },
"enter_directives": { "=> Enter your choice by number and press Enter!": {
"fr": "=> Entre tes directives par son chiffre et fait Entrée!", "fr": "=> Entre tes directives par son chiffre et fait Entrée!",
"en": "=> Enter your choice by number and press Enter!", "en": "=> Enter your choice by number and press Enter!",
}, },
"command": { "Command:": {
"fr": "Commande :", "fr": "Commande :",
"en": "Command:", "en": "Command:",
}, },
"menu_execute": { "Execute": {
"fr": "Exécution", "fr": "Exécution",
"en": "Execute", "en": "Execute",
}, },
"menu_install": { "Install": {
"fr": "Installation", "fr": "Installation",
"en": "Install", "en": "Install",
}, },
"menu_question": { "Question": {
"fr": "Question", "fr": "Question",
"en": "Question", "en": "Question",
}, },
"menu_fork": { "Fork - Open TODO in a new tab": {
"fr": "Fork - Ouvre TODO dans une nouvelle tabulation", "fr": "Fork - Ouvre TODO dans une nouvelle tabulation",
"en": "Fork - Open TODO in a new tab", "en": "Fork - Open TODO in a new tab",
}, },
"menu_quit": { "Quit": {
"fr": "Quitter", "fr": "Quitter",
"en": "Quit", "en": "Quit",
}, },
"cmd_not_found": { "Command not found !": {
"fr": "Commande non trouvée !", "fr": "Commande non trouvée !",
"en": "Command not found !", "en": "Command not found !",
}, },
"back": { "Back": {
"fr": "Retour", "fr": "Retour",
"en": "Back", "en": "Back",
}, },
# Execute submenu # Execute submenu
"menu_run": { "Run - Execute and install an instance": {
"fr": "Run - Exécuter et installer une instance", "fr": "Run - Exécuter et installer une instance",
"en": "Run - Execute and install an instance", "en": "Run - Execute and install an instance",
}, },
"menu_automation": { "Automation - Demonstration of developed features": {
"fr": "Automatisation - Demonstration des fonctions développées", "fr": "Automatisation - Demonstration des fonctions développées",
"en": "Automation - Demonstration of developed features", "en": "Automation - Demonstration of developed features",
}, },
"menu_update": { "Update - Update all developed staging source code": {
"fr": "Mise à jour - Update all developed staging source code", "fr": "Mise à jour - Update all developed staging source code",
"en": "Update - Update all developed staging source code", "en": "Update - Update all developed staging source code",
}, },
"menu_code": { "Code - Developer tools": {
"fr": "Code - Outil pour développeur", "fr": "Code - Outil pour développeur",
"en": "Code - Developer tools", "en": "Code - Developer tools",
}, },
"menu_doc": { "Doc - Documentation search": {
"fr": "Doc - Recherche de documentation", "fr": "Doc - Recherche de documentation",
"en": "Doc - Documentation search", "en": "Doc - Documentation search",
}, },
"menu_database": { "Database - Database tools": {
"fr": "Database - Outils sur les bases de données", "fr": "Database - Outils sur les bases de données",
"en": "Database - Database tools", "en": "Database - Database tools",
}, },
"menu_process": { "Process - Execution tools": {
"fr": "Process - Outils sur les executions", "fr": "Process - Outils sur les executions",
"en": "Process - Execution tools", "en": "Process - Execution tools",
}, },
"menu_config": { "Config - Configuration file management": {
"fr": "Config - Traitement du fichier de configuration", "fr": "Config - Traitement du fichier de configuration",
"en": "Config - Configuration file management", "en": "Config - Configuration file management",
}, },
"menu_network": { "Network - Network tools": {
"fr": "Réseau - Outil réseautique", "fr": "Réseau - Outil réseautique",
"en": "Network - Network tools", "en": "Network - Network tools",
}, },
"menu_security": { "Security - Dependency security audit": {
"fr": "Sécurité - Audit de sécurité des dépendances", "fr": "Sécurité - Audit de sécurité des dépendances",
"en": "Security - Dependency security audit", "en": "Security - Dependency security audit",
}, },
"menu_lang": { "RTK - CLI proxy to reduce LLM token consumption": {
"fr": "RTK - Proxy CLI pour réduire la consommation de tokens LLM",
"en": "RTK - CLI proxy to reduce LLM token consumption",
},
"Language - Change language / Changer la langue": {
"fr": "Langue - Changer la langue / Change language", "fr": "Langue - Changer la langue / Change language",
"en": "Language - Change language / Changer la langue", "en": "Language - Change language / Changer la langue",
}, },
# Deploy section
"Deploy - Deploy ERPLibre locally": {
"fr": "Déploiement - Déployer ERPLibre localement",
"en": "Deploy - Deploy ERPLibre locally",
},
"Deploy ERPLibre to a local directory!": {
"fr": "Déployer ERPLibre dans un répertoire local!",
"en": "Deploy ERPLibre to a local directory!",
},
"Clone ERPLibre locally (git clone)": {
"fr": "Cloner ERPLibre localement (git clone)",
"en": "Clone ERPLibre locally (git clone)",
},
"Target directory path (default: ~/erplibre): ": {
"fr": "Chemin du répertoire cible (défaut: ~/erplibre) : ",
"en": "Target directory path (default: ~/erplibre): ",
},
"Directory already exists: ": {
"fr": "Le répertoire existe déjà : ",
"en": "Directory already exists: ",
},
"Cloning ERPLibre...": {
"fr": "Clonage d'ERPLibre en cours...",
"en": "Cloning ERPLibre...",
},
"ERPLibre cloned successfully to: ": {
"fr": "ERPLibre cloné avec succès dans : ",
"en": "ERPLibre cloned successfully to: ",
},
"Error cloning ERPLibre: ": {
"fr": "Erreur lors du clonage d'ERPLibre : ",
"en": "Error cloning ERPLibre: ",
},
# RTK (Rust Token Killer)
"Manage RTK (Rust Token Killer) for token optimization!": {
"fr": "Gérer RTK (Rust Token Killer) pour optimiser les tokens!",
"en": "Manage RTK (Rust Token Killer) for token optimization!",
},
"Install RTK": {
"fr": "Installer RTK",
"en": "Install RTK",
},
"Check RTK version": {
"fr": "Vérifier la version de RTK",
"en": "Check RTK version",
},
"Show cumulative token savings": {
"fr": "Afficher les économies de tokens cumulées",
"en": "Show cumulative token savings",
},
"Discover optimization opportunities": {
"fr": "Identifier les opportunités d'optimisation",
"en": "Discover optimization opportunities",
},
"Initialize global auto-rewrite hook": {
"fr": "Initialiser le hook auto-rewrite global",
"en": "Initialize global auto-rewrite hook",
},
"Check RTK status": {
"fr": "Vérifier le statut de RTK",
"en": "Check RTK status",
},
"RTK is not installed. Use option 1 to install it.": {
"fr": "RTK n'est pas installé. Utilisez l'option 1 pour l'installer.",
"en": "RTK is not installed. Use option 1 to install it.",
},
"RTK is installed, version: ": {
"fr": "RTK est installé, version : ",
"en": "RTK is installed, version: ",
},
"Global auto-rewrite hook: active": {
"fr": "Hook auto-rewrite global : actif",
"en": "Global auto-rewrite hook: active",
},
"Global auto-rewrite hook: inactive": {
"fr": "Hook auto-rewrite global : inactif",
"en": "Global auto-rewrite hook: inactive",
},
"Installation method:": {
"fr": "Méthode d'installation :",
"en": "Installation method:",
},
"curl - Automatic install script": {
"fr": "curl - Script d'installation automatique",
"en": "curl - Automatic install script",
},
"brew - Homebrew (macOS/Linux)": {
"fr": "brew - Homebrew (macOS/Linux)",
"en": "brew - Homebrew (macOS/Linux)",
},
"cargo - Build from source (Rust required)": {
"fr": "cargo - Compilation depuis les sources (Rust requis)",
"en": "cargo - Build from source (Rust required)",
},
# Prompts and messages # Prompts and messages
"enter_password": { "Enter your password: ": {
"fr": "Entrez votre mot de passe : ", "fr": "Entrez votre mot de passe : ",
"en": "Enter your password: ", "en": "Enter your password: ",
}, },
"ia_prompt": { "Write your question ": {
"fr": "Écrit moi ta question ", "fr": "Écrit moi ta question ",
"en": "Write your question ", "en": "Write your question ",
}, },
"new_instance_confirm": { "Do you want a new instance?": {
"fr": "Voulez-vous une nouvelle instance?", "fr": "Voulez-vous une nouvelle instance?",
"en": "Do you want a new instance?", "en": "Do you want a new instance?",
}, },
"ssh_port_forwarding": { "SSH port-forwarding": {
"fr": "SSH port-forwarding", "fr": "SSH port-forwarding",
"en": "SSH port-forwarding", "en": "SSH port-forwarding",
}, },
"network_performance_request_per_second": { "Network performance request per second": {
"fr": "Performance réseau en requêtes par seconde", "fr": "Performance réseau en requêtes par seconde",
"en": "Network performance request per second", "en": "Network performance request per second",
}, },
"setup_queue_job_for_parallelism": { "Setup queue job for parallelism": {
"fr": "Configurer la file d'attente pour l'exécution parallèle", "fr": "Configurer la file d'attente pour l'exécution parallèle",
"en": "Setup queue job for parallelism", "en": "Setup queue job for parallelism",
}, },
"choose_database": { "Choose your database": {
"fr": "Choisir sa base de données", "fr": "Choisir sa base de données",
"en": "Choose your database", "en": "Choose your database",
}, },
"update_dev": { "Development update": {
"fr": "Mise à jour du développement", "fr": "Mise à jour du développement",
"en": "Development update", "en": "Development update",
}, },
"code_need": { "What do you need for development?": {
"fr": "Qu'avez-vous de besoin pour développer?", "fr": "Qu'avez-vous de besoin pour développer?",
"en": "What do you need for development?", "en": "What do you need for development?",
}, },
"doc_search": { "Looking for documentation?": {
"fr": "Vous cherchez de la documentation?", "fr": "Vous cherchez de la documentation?",
"en": "Looking for documentation?", "en": "Looking for documentation?",
}, },
"db_modify": { "Make changes to databases!": {
"fr": "Faites des modifications sur les bases de données!", "fr": "Faites des modifications sur les bases de données!",
"en": "Make changes to databases!", "en": "Make changes to databases!",
}, },
"process_manage": { "Manage execution processes!": {
"fr": "Manipuler les processus d'exécution!", "fr": "Manipuler les processus d'exécution!",
"en": "Manage execution processes!", "en": "Manage execution processes!",
}, },
"config_manage": { "Manage ERPLibre and Odoo configuration!": {
"fr": "Manipuler la configuration ERPLibre et Odoo!", "fr": "Manipuler la configuration ERPLibre et Odoo!",
"en": "Manage ERPLibre and Odoo configuration!", "en": "Manage ERPLibre and Odoo configuration!",
}, },
"network_tools": { "Network tools!": {
"fr": "Outil réseautique!", "fr": "Outil réseautique!",
"en": "Network tools!", "en": "Network tools!",
}, },
"security_audit": { "Dependency security audit!": {
"fr": "Audit de securite des dépendances!", "fr": "Audit de securite des dépendances!",
"en": "Dependency security audit!", "en": "Dependency security audit!",
}, },
"script_failed": { "The Bash script failed with return code": {
"fr": "Le script Bash a échoué avec le code de retour", "fr": "Le script Bash a échoué avec le code de retour",
"en": "The Bash script failed with return code", "en": "The Bash script failed with return code",
}, },
"no_env_installed": { "No installed environment found. Install an Odoo version first.": {
"fr": "Aucun environnement installe trouve. Installez d'abord une version d'Odoo.", "fr": "Aucun environnement installe trouve. Installez d'abord une version d'Odoo.",
"en": "No installed environment found. Install an Odoo version first.", "en": "No installed environment found. Install an Odoo version first.",
}, },
"choose_env_audit": { "Choose an environment for the audit:": {
"fr": "Choisir un environnement pour l'audit :", "fr": "Choisir un environnement pour l'audit :",
"en": "Choose an environment for the audit:", "en": "Choose an environment for the audit:",
}, },
"selection": { "Select: ": {
"fr": "Sélection : ", "fr": "Sélection : ",
"en": "Select: ", "en": "Select: ",
}, },
"error_value": { "Error, cannot understand value": {
"fr": "Erreur, impossible de comprendre la valeur", "fr": "Erreur, impossible de comprendre la valeur",
"en": "Error, cannot understand value", "en": "Error, cannot understand value",
}, },
"dep_file_not_found": { "Dependencies file not found: ": {
"fr": "Fichier de dépendances introuvable : ", "fr": "Fichier de dépendances introuvable : ",
"en": "Dependencies file not found: ", "en": "Dependencies file not found: ",
}, },
"execution": { "Execution: ": {
"fr": "Execution : ", "fr": "Execution : ",
"en": "Execution: ", "en": "Execution: ",
}, },
"current": { "Current": {
"fr": "Actuel", "fr": "Actuel",
"en": "Current", "en": "Current",
}, },
"default": { "Default": {
"fr": "Défaut", "fr": "Défaut",
"en": "Default", "en": "Default",
}, },
"reboot_todo": { "Reboot TODO ...": {
"fr": "Reboot TODO ...", "fr": "Reboot TODO ...",
"en": "Reboot TODO ...", "en": "Reboot TODO ...",
}, },
"pip_audit_desc": { "pip-audit - Check vulnerabilities on Python environments": {
"fr": "pip-audit - Verifier les vulnérabilités sur les environnements Python", "fr": "pip-audit - Verifier les vulnérabilités sur les environnements Python",
"en": "pip-audit - Check vulnerabilities on Python environments", "en": "pip-audit - Check vulnerabilities on Python environments",
}, },
"will_execute": { "Will execute:": {
"fr": "Va exécuter :", "fr": "Va exécuter :",
"en": "Will execute:", "en": "Will execute:",
}, },
"choose_version": { "Choose a version:": {
"fr": "Choisir une version :", "fr": "Choisir une version :",
"en": "Choose a version:", "en": "Choose a version:",
}, },
"error_cannot_understand": {
"fr": "Erreur, impossible de comprendre la valeur",
"en": "Error, cannot understand value",
},
# todo.json translatable prompt_descriptions # todo.json translatable prompt_descriptions
"json_instance_test": { "Test - Minimal base instance": {
"fr": "Test - Instance de base minimale", "fr": "Test - Instance de base minimale",
"en": "Test - Minimal base instance", "en": "Test - Minimal base instance",
}, },
"json_robot_minimal": { "Open RobotLibre 🤖 minimal": {
"fr": "Ouvrir RobotLibre 🤖 minimal", "fr": "Ouvrir RobotLibre 🤖 minimal",
"en": "Open RobotLibre 🤖 minimal", "en": "Open RobotLibre 🤖 minimal",
}, },
"json_robot_search": { "Open RobotLibre 🤖 with search enabled": {
"fr": "Ouvrir RobotLibre 🤖 en activant la recherche", "fr": "Ouvrir RobotLibre 🤖 en activant la recherche",
"en": "Open RobotLibre 🤖 with search enabled", "en": "Open RobotLibre 🤖 with search enabled",
}, },
"json_open_erplibre_todo": { "Open ERPLibre with TODO 🤖": {
"fr": "Ouvrir ERPLibre avec TODO 🤖", "fr": "Ouvrir ERPLibre avec TODO 🤖",
"en": "Open ERPLibre with TODO 🤖", "en": "Open ERPLibre with TODO 🤖",
}, },
"json_update_erplibre_base_test": { "Update all erplibre_base on database test": {
"fr": "Mise à jour de tous les erplibre_base sur la base de données test", "fr": "Mise à jour de tous les erplibre_base sur la base de données test",
"en": "Update all erplibre_base on database test", "en": "Update all erplibre_base on database test",
}, },
"json_show_code_status": { "Show code status": {
"fr": "Afficher le statut du code", "fr": "Afficher le statut du code",
"en": "Show code status", "en": "Show code status",
}, },
"json_stash_all_code": { "Stash all code": {
"fr": "Remiser tout le code", "fr": "Remiser tout le code",
"en": "Stash all code", "en": "Stash all code",
}, },
"json_format_modified_code": { "Format modified code": {
"fr": "Formater le code modifié", "fr": "Formater le code modifié",
"en": "Format modified code", "en": "Format modified code",
}, },
# todo.py hardcoded prompt_descriptions # todo.py hardcoded prompt_descriptions
"mobile_compile_run": { "Mobile - Compile and run software": {
"fr": "Mobile - Compiler et exécuter le logiciel", "fr": "Mobile - Compiler et exécuter le logiciel",
"en": "Mobile - Compile and run software", "en": "Mobile - Compile and run software",
}, },
"upgrade_odoo_migration": { "Upgrade Odoo - Migration Database": {
"fr": "Mise à jour Odoo - Migration de base de données", "fr": "Mise à jour Odoo - Migration de base de données",
"en": "Upgrade Odoo - Migration Database", "en": "Upgrade Odoo - Migration Database",
}, },
"upgrade_poetry_dependency": { "Upgrade Poetry - Dependency of Odoo": {
"fr": "Mise à jour Poetry - Dépendances d'Odoo", "fr": "Mise à jour Poetry - Dépendances d'Odoo",
"en": "Upgrade Poetry - Dependency of Odoo", "en": "Upgrade Poetry - Dependency of Odoo",
}, },
"open_shell": { "Open SHELL": {
"fr": "Ouvrir le SHELL", "fr": "Ouvrir le SHELL",
"en": "Open SHELL", "en": "Open SHELL",
}, },
"upgrade_module": { "Upgrade Module": {
"fr": "Mise à jour de module", "fr": "Mise à jour de module",
"en": "Upgrade Module", "en": "Upgrade Module",
}, },
"debug": { "Debug": {
"fr": "Débogage", "fr": "Débogage",
"en": "Debug", "en": "Debug",
}, },
"migration_module_coverage": { "Migration module coverage": {
"fr": "Couverture de migration des modules", "fr": "Couverture de migration des modules",
"en": "Migration module coverage", "en": "Migration module coverage",
}, },
"what_change_between_version": { "What change between version": {
"fr": "Quels changements entre les versions", "fr": "Quels changements entre les versions",
"en": "What change between version", "en": "What change between version",
}, },
"oca_guidelines": { "OCA guidelines": {
"fr": "Directives OCA", "fr": "Directives OCA",
"en": "OCA guidelines", "en": "OCA guidelines",
}, },
"oca_migration_odoo_19": { "OCA migration Odoo 19 milestone": {
"fr": "Migration OCA Odoo 19 - Jalons", "fr": "Migration OCA Odoo 19 - Jalons",
"en": "OCA migration Odoo 19 milestone", "en": "OCA migration Odoo 19 milestone",
}, },
"download_db_backup": { "Download database to create backup (.zip)": {
"fr": "Télécharger une base de données pour créer une sauvegarde (.zip)", "fr": "Télécharger une base de données pour créer une sauvegarde (.zip)",
"en": "Download database to create backup (.zip)", "en": "Download database to create backup (.zip)",
}, },
"restore_from_backup": { "Restore from backup (.zip)": {
"fr": "Restaurer a partir d'une sauvegarde (.zip)", "fr": "Restaurer a partir d'une sauvegarde (.zip)",
"en": "Restore from backup (.zip)", "en": "Restore from backup (.zip)",
}, },
"create_backup": { "Create backup (.zip)": {
"fr": "Créer une sauvegarde (.zip)", "fr": "Créer une sauvegarde (.zip)",
"en": "Create backup (.zip)", "en": "Create backup (.zip)",
}, },
"kill_process_port": { "Kill Odoo process from actual port": {
"fr": "Terminer le processus Odoo du port actuel", "fr": "Terminer le processus Odoo du port actuel",
"en": "Kill Odoo process from actual port", "en": "Kill Odoo process from actual port",
}, },
"kill_git_daemon": { "Kill git daemon server process": {
"fr": "Terminer le processus du serveur git daemon", "fr": "Terminer le processus du serveur git daemon",
"en": "Kill git daemon server process", "en": "Kill git daemon server process",
}, },
"kill_git_daemon_done": { "Git daemon process killed.": {
"fr": "Processus git daemon terminé.", "fr": "Processus git daemon terminé.",
"en": "Git daemon process killed.", "en": "Git daemon process killed.",
}, },
"generate_all_config": { "Generate all configuration": {
"fr": "Générer toute la configuration", "fr": "Générer toute la configuration",
"en": "Generate all configuration", "en": "Generate all configuration",
}, },
"generate_from_preconfig": { "Generate from pre-configuration": {
"fr": "Générer a partir de la pre-configuration", "fr": "Générer a partir de la pre-configuration",
"en": "Generate from pre-configuration", "en": "Generate from pre-configuration",
}, },
"generate_from_backup": { "Generate from backup file": {
"fr": "Générer a partir d'un fichier de sauvegarde", "fr": "Générer a partir d'un fichier de sauvegarde",
"en": "Generate from backup file", "en": "Generate from backup file",
}, },
"generate_from_database": { "Generate from database": {
"fr": "Générer a partir de la base de données", "fr": "Générer a partir de la base de données",
"en": "Generate from database", "en": "Generate from database",
}, },
"preconfig_base": { "base": {
"fr": "base", "fr": "base",
"en": "base", "en": "base",
}, },
"preconfig_base_code_generator": { "base + code_generator": {
"fr": "base + code_generator", "fr": "base + code_generator",
"en": "base + code_generator", "en": "base + code_generator",
}, },
"preconfig_base_image_db": { "base + image_db": {
"fr": "base + image_db", "fr": "base + image_db",
"en": "base + image_db", "en": "base + image_db",
}, },
"preconfig_all": { "all": {
"fr": "tout", "fr": "tout",
"en": "all", "en": "all",
}, },
"debug_todo_py": { "Debug todo.py": {
"fr": "Débogage todo.py", "fr": "Débogage todo.py",
"en": "Debug todo.py", "en": "Debug todo.py",
}, },
# Test section # Test section
"menu_test": { "Test - Test an Odoo module": {
"fr": "Test - Tester un module Odoo", "fr": "Test - Tester un module Odoo",
"en": "Test - Test an Odoo module", "en": "Test - Test an Odoo module",
}, },
"test_description": { "Test an Odoo module on a temporary database!": {
"fr": "Tester un module Odoo sur une base de données temporaire!", "fr": "Tester un module Odoo sur une base de données temporaire!",
"en": "Test an Odoo module on a temporary database!", "en": "Test an Odoo module on a temporary database!",
}, },
"test_run_module": { "Test a module": {
"fr": "Tester un module", "fr": "Tester un module",
"en": "Test a module", "en": "Test a module",
}, },
"test_run_module_coverage": { "Test a module with code coverage": {
"fr": "Tester un module avec couverture de code", "fr": "Tester un module avec couverture de code",
"en": "Test a module with code coverage", "en": "Test a module with code coverage",
}, },
"test_run_unit_tests": { "ERPLibre unit tests": {
"fr": "Tests unitaires ERPLibre", "fr": "Tests unitaires ERPLibre",
"en": "ERPLibre unit tests", "en": "ERPLibre unit tests",
}, },
"test_unit_running": { "Running unit tests": {
"fr": "Exécution des tests unitaires", "fr": "Exécution des tests unitaires",
"en": "Running unit tests", "en": "Running unit tests",
}, },
"test_unit_success": { "All unit tests passed": {
"fr": "Tous les tests unitaires ont réussi", "fr": "Tous les tests unitaires ont réussi",
"en": "All unit tests passed", "en": "All unit tests passed",
}, },
"test_unit_failed": { "Some unit tests failed, exit code": {
"fr": "Des tests unitaires ont échoué, code de sortie", "fr": "Des tests unitaires ont échoué, code de sortie",
"en": "Some unit tests failed, exit code", "en": "Some unit tests failed, exit code",
}, },
"test_enter_module_name": { "Module name to test: ": {
"fr": "Nom du module à tester : ", "fr": "Nom du module à tester : ",
"en": "Module name to test: ", "en": "Module name to test: ",
}, },
"test_db_name": { "Temporary database name (default: test_todo_tmp): ": {
"fr": "Nom de la base de données temporaire (défaut: test_todo_tmp) : ", "fr": "Nom de la base de données temporaire (défaut: test_todo_tmp) : ",
"en": "Temporary database name (default: test_todo_tmp): ", "en": "Temporary database name (default: test_todo_tmp): ",
}, },
"test_install_extra_modules": { "Extra modules to install (comma-separated, empty for none): ": {
"fr": "Modules supplémentaires à installer (séparés par des virgules, vide pour aucun) : ", "fr": "Modules supplémentaires à installer (séparés par des virgules, vide pour aucun) : ",
"en": "Extra modules to install (comma-separated, empty for none): ", "en": "Extra modules to install (comma-separated, empty for none): ",
}, },
"test_log_level": { "Log level (default: test): ": {
"fr": "Niveau de log (défaut: test) : ", "fr": "Niveau de log (défaut: test) : ",
"en": "Log level (default: test): ", "en": "Log level (default: test): ",
}, },
"test_creating_db": { "Creating temporary database": {
"fr": "Création de la base de données temporaire", "fr": "Création de la base de données temporaire",
"en": "Creating temporary database", "en": "Creating temporary database",
}, },
"test_installing_modules": { "Installing modules": {
"fr": "Installation des modules", "fr": "Installation des modules",
"en": "Installing modules", "en": "Installing modules",
}, },
"test_running": { "Running tests": {
"fr": "Exécution des tests", "fr": "Exécution des tests",
"en": "Running tests", "en": "Running tests",
}, },
"test_cleaning_db": { "Cleaning up temporary database": {
"fr": "Suppression de la base de données temporaire", "fr": "Suppression de la base de données temporaire",
"en": "Cleaning up temporary database", "en": "Cleaning up temporary database",
}, },
"test_keep_db": { "Keep the temporary database? (y/N): ": {
"fr": "Conserver la base de données temporaire? (o/N) : ", "fr": "Conserver la base de données temporaire? (o/N) : ",
"en": "Keep the temporary database? (y/N): ", "en": "Keep the temporary database? (y/N): ",
}, },
"test_db_kept": { "Database kept": {
"fr": "Base de données conservée", "fr": "Base de données conservée",
"en": "Database kept", "en": "Database kept",
}, },
"test_success": { "Tests completed successfully!": {
"fr": "Tests terminés avec succès!", "fr": "Tests terminés avec succès!",
"en": "Tests completed successfully!", "en": "Tests completed successfully!",
}, },
"test_failed": { "Tests failed with return code": {
"fr": "Les tests ont échoué avec le code de retour", "fr": "Les tests ont échoué avec le code de retour",
"en": "Tests failed with return code", "en": "Tests failed with return code",
}, },
"test_module_required": { "Module name is required!": {
"fr": "Le nom du module est requis!", "fr": "Le nom du module est requis!",
"en": "Module name is required!", "en": "Module name is required!",
}, },
# Git section # Git section
"menu_git": { "Git - Git tools": {
"fr": "Git - Outils Git", "fr": "Git - Outils Git",
"en": "Git - Git tools", "en": "Git - Git tools",
}, },
"git_manage": { "Git management tools!": {
"fr": "Outils de gestion Git!", "fr": "Outils de gestion Git!",
"en": "Git management tools!", "en": "Git management tools!",
}, },
"git_local_server": { "Local git server": {
"fr": "Serveur git local", "fr": "Serveur git local",
"en": "Local git server", "en": "Local git server",
}, },
"git_repo_manage": { "Manage local git repository server!": {
"fr": "Gérer le serveur de dépôts git local!", "fr": "Gérer le serveur de dépôts git local!",
"en": "Manage local git repository server!", "en": "Manage local git repository server!",
}, },
"git_repo_deploy_local": { "Deploy a local git server (~/.git-server)": {
"fr": "Déployer un serveur git local (~/.git-server)", "fr": "Déployer un serveur git local (~/.git-server)",
"en": "Deploy a local git server (~/.git-server)", "en": "Deploy a local git server (~/.git-server)",
}, },
"git_repo_deploy_production": { "Deploy a production git server (/srv/git, root required)": {
"fr": "Déployer un serveur git production (/srv/git, root requis)", "fr": "Déployer un serveur git production (/srv/git, root requis)",
"en": "Deploy a production git server (/srv/git, root required)", "en": "Deploy a production git server (/srv/git, root required)",
}, },
"git_repo_deploy_starting": { "Starting git server deployment...": {
"fr": "Démarrage du déploiement du serveur git...", "fr": "Démarrage du déploiement du serveur git...",
"en": "Starting git server deployment...", "en": "Starting git server deployment...",
}, },
"git_mode_local": { "Local mode (~/.git-server)": {
"fr": "Mode local (~/.git-server)", "fr": "Mode local (~/.git-server)",
"en": "Local mode (~/.git-server)", "en": "Local mode (~/.git-server)",
}, },
"git_mode_production": { "Production mode (/srv/git, root required)": {
"fr": "Mode production (/srv/git, root requis)", "fr": "Mode production (/srv/git, root requis)",
"en": "Production mode (/srv/git, root required)", "en": "Production mode (/srv/git, root required)",
}, },
"git_action_all": { "Run all (init + remote + push + serve)": {
"fr": "Tout exécuter (init + remote + push + serve)", "fr": "Tout exécuter (init + remote + push + serve)",
"en": "Run all (init + remote + push + serve)", "en": "Run all (init + remote + push + serve)",
}, },
"git_action_init": { "Init - Create bare repos": {
"fr": "Init - Créer les bare repos", "fr": "Init - Créer les bare repos",
"en": "Init - Create bare repos", "en": "Init - Create bare repos",
}, },
"git_action_remote": { "Remote - Add local remotes": {
"fr": "Remote - Ajouter les remotes locaux", "fr": "Remote - Ajouter les remotes locaux",
"en": "Remote - Add local remotes", "en": "Remote - Add local remotes",
}, },
"git_action_push": { "Push - Push to local server": {
"fr": "Push - Pousser vers le serveur local", "fr": "Push - Pousser vers le serveur local",
"en": "Push - Push to local server", "en": "Push - Push to local server",
}, },
"git_action_serve": { "Serve - Start git daemon": {
"fr": "Serve - Démarrer le daemon git", "fr": "Serve - Démarrer le daemon git",
"en": "Serve - Start git daemon", "en": "Serve - Start git daemon",
}, },
# Git remote add
"Add a remote to a local repository": {
"fr": "Ajouter un remote vers un dépôt local",
"en": "Add a remote to a local repository",
},
"Remote name (default: localhost): ": {
"fr": "Nom du remote (défaut: localhost) : ",
"en": "Remote name (default: localhost): ",
},
"Repository address (e.g.: git://192.168.1.100/my-repo.git): ": {
"fr": "Adresse du dépôt (ex: git://192.168.1.100/mon-repo.git) : ",
"en": "Repository address (e.g.: git://192.168.1.100/my-repo.git): ",
},
"Repository address is required!": {
"fr": "L'adresse du dépôt est requise!",
"en": "Repository address is required!",
},
"Remote added successfully!": {
"fr": "Remote ajouté avec succès!",
"en": "Remote added successfully!",
},
"Error adding remote: ": {
"fr": "Erreur lors de l'ajout du remote : ",
"en": "Error adding remote: ",
},
# Git config vim
"Configure git local editor to vim": {
"fr": "Configuration git local par vim",
"en": "Configure git local editor to vim",
},
"Git editor configured to vim successfully!": {
"fr": "Éditeur git configuré sur vim avec succès!",
"en": "Git editor configured to vim successfully!",
},
"Error during configuration: ": {
"fr": "Erreur lors de la configuration : ",
"en": "Error during configuration: ",
},
# GPT code - Claude automation
"Add an automation with Claude in todo.py": {
"fr": "Ajouter une automatisation avec Claude dans todo.py",
"en": "Add an automation with Claude in todo.py",
},
"Description of the command to add: ": {
"fr": "Description de la commande à ajouter : ",
"en": "Description of the command to add: ",
},
"Bash command to execute: ": {
"fr": "Commande bash à exécuter : ",
"en": "Bash command to execute: ",
},
"Menu section (git/code/config/network/process): ": {
"fr": "Section du menu (git/code/config/network/process) : ",
"en": "Menu section (git/code/config/network/process): ",
},
"Automation added successfully in todo.json!": {
"fr": "Automatisation ajoutée avec succès dans todo.json!",
"en": "Automation added successfully in todo.json!",
},
"Error adding automation: ": {
"fr": "Erreur lors de l'ajout de l'automatisation : ",
"en": "Error adding automation: ",
},
# Language selection # Language selection
"lang_prompt": { "Choose language / Choisir la langue": {
"fr": "Choisir la langue / Choose language", "fr": "Choisir la langue / Choose language",
"en": "Choose language / Choisir la langue", "en": "Choose language / Choisir la langue",
}, },
"lang_french": { "French": {
"fr": "Francais", "fr": "Francais",
"en": "French", "en": "French",
}, },
"lang_english": { "English": {
"fr": "Anglais", "fr": "Anglais",
"en": "English", "en": "English",
}, },
"lang_changed": { "Language changed to: English": {
"fr": "Langue changée pour : Francais", "fr": "Langue changée pour : Francais",
"en": "Language changed to: English", "en": "Language changed to: English",
}, },
"execution_time": { "TODO execution time": {
"fr": "Temps d'exécution TODO", "fr": "Temps d'exécution TODO",
"en": "TODO execution time", "en": "TODO execution time",
}, },
"keyboard_interrupt": { "Keyboard interrupt": {
"fr": "Interruption clavier", "fr": "Interruption clavier",
"en": "Keyboard interrupt", "en": "Keyboard interrupt",
}, },
# GPT code section # GPT code section
"menu_gpt_code": { "GPT code - AI assistant tools": {
"fr": "GPT code - Outils d'assistant IA", "fr": "GPT code - Outils d'assistant IA",
"en": "GPT code - AI assistant tools", "en": "GPT code - AI assistant tools",
}, },
"gpt_code_manage": { "AI assistant tools for development!": {
"fr": "Outils d'assistant IA pour le développement!", "fr": "Outils d'assistant IA pour le développement!",
"en": "AI assistant tools for development!", "en": "AI assistant tools for development!",
}, },
"gpt_code_claude_commit": { "Configure Claude Code configurations": {
"fr": "Configurer le commit Claude Code", "fr": "Configurer les configurations Claude Code",
"en": "Configure Claude Code commit", "en": "Configure Claude Code configurations",
}, },
"gpt_code_enter_name": { "Commit - OCA/Odoo commit command": {
"fr": "Commit - Commande de commit OCA/Odoo",
"en": "Commit - OCA/Odoo commit command",
},
"Todo Add Command - Add a command to todo.py menu": {
"fr": "Todo Add Command - Ajouter une commande au menu todo.py",
"en": "Todo Add Command - Add a command to todo.py menu",
},
"Enter your full name: ": {
"fr": "Entrez votre nom complet : ", "fr": "Entrez votre nom complet : ",
"en": "Enter your full name: ", "en": "Enter your full name: ",
}, },
"gpt_code_enter_email": { "Enter your email: ": {
"fr": "Entrez votre courriel : ", "fr": "Entrez votre courriel : ",
"en": "Enter your email: ", "en": "Enter your email: ",
}, },
"gpt_code_commit_exists": { "Deploy Claude Code commands!": {
"fr": "Le fichier ~/.claude/commands/commit.md existe déjà. Aucune action effectuée.", "fr": "Déployer les commandes Claude Code!",
"en": "File ~/.claude/commands/commit.md already exists. No action taken.", "en": "Deploy Claude Code commands!",
}, },
"gpt_code_commit_created": { "Show installed custom commands": {
"fr": "Fichier ~/.claude/commands/commit.md créé avec succès!", "fr": "Afficher les commandes personnalisées installées",
"en": "File ~/.claude/commands/commit.md created successfully!", "en": "Show installed custom commands",
}, },
"gpt_code_commit_error": { "No custom commands found in ~/.claude/commands/": {
"fr": "Aucune commande personnalisée trouvée dans ~/.claude/commands/",
"en": "No custom commands found in ~/.claude/commands/",
},
"Claude Code custom commands:": {
"fr": "Commandes personnalisées Claude Code :",
"en": "Claude Code custom commands:",
},
"Total:": {
"fr": "Total :",
"en": "Total:",
},
"File already exists: ": {
"fr": "Le fichier existe déjà : ",
"en": "File already exists: ",
},
"Do you want to overwrite the file? (y/Y): ": {
"fr": "Voulez-vous écraser le fichier? (y/Y) : ",
"en": "Do you want to overwrite the file? (y/Y): ",
},
"Nothing to do.": {
"fr": "Rien à faire.",
"en": "Nothing to do.",
},
"File created successfully: ": {
"fr": "Fichier créé avec succès : ",
"en": "File created successfully: ",
},
"Error creating file: ": {
"fr": "Erreur lors de la création du fichier : ", "fr": "Erreur lors de la création du fichier : ",
"en": "Error creating file: ", "en": "Error creating file: ",
}, },