[IMP] todo: add Claude configs submenu and command listing
Refactor single commit setup into a generic deployment mechanism for Claude commands, allowing easy addition of new commands. Add todo_add_command template and an option to list installed custom commands with their dates. Generated by Claude Code 2.1.74 model claude-opus-4-6 Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
This commit is contained in:
parent
c2e08f1de0
commit
91c835d57e
3 changed files with 287 additions and 22 deletions
143
conf/template_claude_commands_todo_add_command.md
Normal file
143
conf/template_claude_commands_todo_add_command.md
Normal 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
|
||||
|
|
@ -802,7 +802,7 @@ class TODO:
|
|||
def prompt_execute_gpt_code(self):
|
||||
print(f"🤖 {t('gpt_code_manage')}")
|
||||
choices = [
|
||||
{"prompt_description": t("gpt_code_claude_commit")},
|
||||
{"prompt_description": t("gpt_code_claude_configs")},
|
||||
{"prompt_description": t("gpt_code_claude_add_automation")},
|
||||
{"prompt_description": t("menu_rtk")},
|
||||
]
|
||||
|
|
@ -814,7 +814,7 @@ class TODO:
|
|||
if status == "0":
|
||||
return False
|
||||
elif status == "1":
|
||||
self._setup_claude_commit()
|
||||
self._prompt_claude_configs()
|
||||
elif status == "2":
|
||||
self._claude_add_automation()
|
||||
elif status == "3":
|
||||
|
|
@ -822,44 +822,118 @@ class TODO:
|
|||
else:
|
||||
print(t("cmd_not_found"))
|
||||
|
||||
def _setup_claude_commit(self):
|
||||
def _prompt_claude_configs(self):
|
||||
print(f"🤖 {t('gpt_code_claude_configs_manage')}")
|
||||
choices = [
|
||||
{"prompt_description": t("gpt_code_claude_commit")},
|
||||
{
|
||||
"prompt_description": t(
|
||||
"gpt_code_claude_todo_add_command"
|
||||
)
|
||||
},
|
||||
{
|
||||
"prompt_description": t(
|
||||
"gpt_code_claude_list_commands"
|
||||
)
|
||||
},
|
||||
]
|
||||
help_info = self.fill_help_info(choices)
|
||||
|
||||
while True:
|
||||
status = click.prompt(help_info)
|
||||
print()
|
||||
if status == "0":
|
||||
return False
|
||||
elif status == "1":
|
||||
self._setup_claude_command(
|
||||
"commit",
|
||||
"template_claude_commands_commit.md",
|
||||
personalize=True,
|
||||
)
|
||||
elif status == "2":
|
||||
self._setup_claude_command(
|
||||
"todo_add_command",
|
||||
"template_claude_commands_todo_add_command.md",
|
||||
)
|
||||
elif status == "3":
|
||||
self._list_claude_commands()
|
||||
else:
|
||||
print(t("cmd_not_found"))
|
||||
|
||||
def _list_claude_commands(self):
|
||||
commands_dir = os.path.expanduser("~/.claude/commands")
|
||||
if not os.path.isdir(commands_dir):
|
||||
print(t("gpt_code_claude_no_commands"))
|
||||
return
|
||||
files = sorted(
|
||||
f
|
||||
for f in os.listdir(commands_dir)
|
||||
if f.endswith(".md")
|
||||
)
|
||||
if not files:
|
||||
print(t("gpt_code_claude_no_commands"))
|
||||
return
|
||||
print(t("gpt_code_claude_list_header"))
|
||||
print("-" * 50)
|
||||
for f in files:
|
||||
filepath = os.path.join(commands_dir, f)
|
||||
mtime = os.path.getmtime(filepath)
|
||||
date_str = datetime.datetime.fromtimestamp(mtime).strftime(
|
||||
"%Y-%m-%d %H:%M"
|
||||
)
|
||||
name = f[:-3] # remove .md
|
||||
print(f" /{name:<30} {date_str}")
|
||||
print("-" * 50)
|
||||
print(
|
||||
f"{t('gpt_code_claude_list_total')}"
|
||||
f" {len(files)}"
|
||||
)
|
||||
|
||||
def _setup_claude_command(
|
||||
self, command_name, template_filename, personalize=False
|
||||
):
|
||||
dest_dir = os.path.expanduser("~/.claude/commands")
|
||||
dest_file = os.path.join(dest_dir, "commit.md")
|
||||
dest_file = os.path.join(dest_dir, f"{command_name}.md")
|
||||
|
||||
if os.path.exists(dest_file):
|
||||
print(t("gpt_code_commit_exists"))
|
||||
return
|
||||
|
||||
name = input(t("gpt_code_enter_name")).strip()
|
||||
email = input(t("gpt_code_enter_email")).strip()
|
||||
print(f"{t('gpt_code_cmd_exists')}{dest_file}")
|
||||
overwrite = input(
|
||||
t("gpt_code_cmd_overwrite")
|
||||
).strip()
|
||||
if overwrite not in ("y", "Y"):
|
||||
print(t("gpt_code_cmd_nothing_to_do"))
|
||||
return
|
||||
|
||||
template_path = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"..",
|
||||
"..",
|
||||
"conf",
|
||||
"template_claude_commands_commit.md",
|
||||
template_filename,
|
||||
)
|
||||
try:
|
||||
with open(template_path) as f:
|
||||
content = f.read()
|
||||
|
||||
content = content.replace(
|
||||
"Your Name <your@email.com>",
|
||||
f"{name} <{email}>",
|
||||
)
|
||||
content = content.replace(
|
||||
"Your Name ",
|
||||
f"{name} ",
|
||||
)
|
||||
if personalize:
|
||||
name = input(t("gpt_code_enter_name")).strip()
|
||||
email = input(t("gpt_code_enter_email")).strip()
|
||||
content = content.replace(
|
||||
"Your Name <your@email.com>",
|
||||
f"{name} <{email}>",
|
||||
)
|
||||
content = content.replace(
|
||||
"Your Name ",
|
||||
f"{name} ",
|
||||
)
|
||||
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
with open(dest_file, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
print(t("gpt_code_commit_created"))
|
||||
print(f"{t('gpt_code_cmd_created')}{dest_file}")
|
||||
except Exception as e:
|
||||
print(f"{t('gpt_code_commit_error')}{e}")
|
||||
print(f"{t('gpt_code_cmd_error')}{e}")
|
||||
|
||||
def _claude_add_automation(self):
|
||||
description = input(
|
||||
|
|
|
|||
|
|
@ -655,9 +655,17 @@ TRANSLATIONS = {
|
|||
"fr": "Outils d'assistant IA pour le développement!",
|
||||
"en": "AI assistant tools for development!",
|
||||
},
|
||||
"gpt_code_claude_configs": {
|
||||
"fr": "Configurer les configurations Claude Code",
|
||||
"en": "Configure Claude Code configurations",
|
||||
},
|
||||
"gpt_code_claude_commit": {
|
||||
"fr": "Configurer le commit Claude Code",
|
||||
"en": "Configure Claude Code commit",
|
||||
"fr": "Commit - Commande de commit OCA/Odoo",
|
||||
"en": "Commit - OCA/Odoo commit command",
|
||||
},
|
||||
"gpt_code_claude_todo_add_command": {
|
||||
"fr": "Todo Add Command - Ajouter une commande au menu todo.py",
|
||||
"en": "Todo Add Command - Add a command to todo.py menu",
|
||||
},
|
||||
"gpt_code_enter_name": {
|
||||
"fr": "Entrez votre nom complet : ",
|
||||
|
|
@ -667,6 +675,46 @@ TRANSLATIONS = {
|
|||
"fr": "Entrez votre courriel : ",
|
||||
"en": "Enter your email: ",
|
||||
},
|
||||
"gpt_code_claude_configs_manage": {
|
||||
"fr": "Déployer les commandes Claude Code!",
|
||||
"en": "Deploy Claude Code commands!",
|
||||
},
|
||||
"gpt_code_claude_list_commands": {
|
||||
"fr": "Afficher les commandes personnalisées installées",
|
||||
"en": "Show installed custom commands",
|
||||
},
|
||||
"gpt_code_claude_no_commands": {
|
||||
"fr": "Aucune commande personnalisée trouvée dans ~/.claude/commands/",
|
||||
"en": "No custom commands found in ~/.claude/commands/",
|
||||
},
|
||||
"gpt_code_claude_list_header": {
|
||||
"fr": "Commandes personnalisées Claude Code :",
|
||||
"en": "Claude Code custom commands:",
|
||||
},
|
||||
"gpt_code_claude_list_total": {
|
||||
"fr": "Total :",
|
||||
"en": "Total:",
|
||||
},
|
||||
"gpt_code_cmd_exists": {
|
||||
"fr": "Le fichier existe déjà : ",
|
||||
"en": "File already exists: ",
|
||||
},
|
||||
"gpt_code_cmd_overwrite": {
|
||||
"fr": "Voulez-vous écraser le fichier? (y/Y) : ",
|
||||
"en": "Do you want to overwrite the file? (y/Y): ",
|
||||
},
|
||||
"gpt_code_cmd_nothing_to_do": {
|
||||
"fr": "Rien à faire.",
|
||||
"en": "Nothing to do.",
|
||||
},
|
||||
"gpt_code_cmd_created": {
|
||||
"fr": "Fichier créé avec succès : ",
|
||||
"en": "File created successfully: ",
|
||||
},
|
||||
"gpt_code_cmd_error": {
|
||||
"fr": "Erreur lors de la création du fichier : ",
|
||||
"en": "Error creating file: ",
|
||||
},
|
||||
"gpt_code_commit_exists": {
|
||||
"fr": "Le fichier ~/.claude/commands/commit.md existe déjà. Aucune action effectuée.",
|
||||
"en": "File ~/.claude/commands/commit.md already exists. No action taken.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue