feat: install.sh — arbre catégories, SPACE=action, TAB=plier, v=viewer bat
- Bug fix: {1} au lieu de {n} — action s'applique bien au skill sélectionné
- SPACE cycle l'action (local→global→ignorer), TAB plie/déplie la catégorie
- Arbre par catégorie : en-têtes ▼/▶, repliage auto si >3 skills
- Touche v : viewer avec coloration syntaxique (bat/batcat ou cat en fallback)
- Preview caché par défaut (v pour afficher), 50% droite
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+136
-56
@@ -37,6 +37,7 @@ REPO_URL="https://gitea.maison43.duckdns.org/gilles/mes_skills.git"
|
||||
REPO_DIR="/tmp/mes_skills_$$"
|
||||
_CLONED_REPO_DIR=""
|
||||
STATE_FILE="/tmp/skills_state_$$"
|
||||
COLLAPSED_FILE="/tmp/skills_collapsed_$$"
|
||||
|
||||
SKILLS_DEBUG="${SKILLS_DEBUG:-0}"
|
||||
SKILLS_DRY_RUN="${SKILLS_DRY_RUN:-0}"
|
||||
@@ -56,7 +57,8 @@ header() { echo -e "\n${GRV_PURPLE}╔══ $* ══╗${RESET}\n"; }
|
||||
cleanup() {
|
||||
debug "Nettoyage $_CLONED_REPO_DIR et $STATE_FILE"
|
||||
[[ -n "$_CLONED_REPO_DIR" && -d "$_CLONED_REPO_DIR" ]] && rm -rf "$_CLONED_REPO_DIR"
|
||||
[[ -f "$STATE_FILE" ]] && rm -f "$STATE_FILE"
|
||||
[[ -f "$STATE_FILE" ]] && rm -f "$STATE_FILE"
|
||||
[[ -f "$COLLAPSED_FILE" ]] && rm -f "$COLLAPSED_FILE"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
@@ -311,9 +313,9 @@ state_cycle() {
|
||||
sed -i "s|^${key}=.*|${key}=${next}|" "$STATE_FILE"
|
||||
}
|
||||
|
||||
# ── Formatage ligne menu ──────────────────────────────────────────
|
||||
# ── Formatage ligne skill (préfixe caché s:IDX\t pour fzf) ───────
|
||||
format_skill_line() {
|
||||
local entry="$1"
|
||||
local entry="$1" idx="$2"
|
||||
local cat skill agent etat repo_ver local_ver desc tags
|
||||
IFS='|' read -r cat skill agent etat repo_ver local_ver desc tags <<< "$entry"
|
||||
local key; key=$(make_key "$entry")
|
||||
@@ -340,41 +342,69 @@ format_skill_line() {
|
||||
[[ "$etat" == "upd" ]] && ver_info=" (${local_ver}→${repo_ver})"
|
||||
[[ "$etat" == "new" ]] && ver_info=" (v${repo_ver})"
|
||||
|
||||
printf "${color_etat}%s${RESET} %-28s ${GRV_GRAY}[%s]${RESET} ${color_action}%s${RESET}%s ${GRV_FG}%s${RESET} ${GRV_PURPLE}%s${RESET}\n" \
|
||||
"$ico_etat" "${cat}/${skill}" "$agent" "$ico_action" "$ver_info" "$desc" "$tags"
|
||||
printf "s:%d\t ${color_etat}%s${RESET} %-24s ${GRV_GRAY}[%s]${RESET} ${color_action}%s${RESET}%s ${GRV_FG}%s${RESET} ${GRV_PURPLE}%s${RESET}\n" \
|
||||
"$idx" "$ico_etat" "${skill}" "$agent" "$ico_action" "$ver_info" "$desc" "$tags"
|
||||
}
|
||||
|
||||
# ── Formatage en-tête catégorie (préfixe caché h:CAT\t) ──────────
|
||||
format_category_header() {
|
||||
local cat="$1" count="$2" collapsed="$3"
|
||||
local arrow color_arrow
|
||||
if [[ "$collapsed" == "1" ]]; then
|
||||
arrow="▶"; color_arrow="$GRV_YELLOW"
|
||||
else
|
||||
arrow="▼"; color_arrow="$GRV_BLUE"
|
||||
fi
|
||||
printf "h:%s\t${color_arrow}%s${RESET} ${GRV_BLUE}%s/${RESET} ${GRV_GRAY}(%d skill(s))${RESET}\n" \
|
||||
"$cat" "$arrow" "$cat" "$count"
|
||||
}
|
||||
|
||||
# ── Menu fzf principal ────────────────────────────────────────────
|
||||
run_menu() {
|
||||
header "Sélection des skills"
|
||||
|
||||
echo -e " ${GRV_FG}Navigation :${RESET} ${GRV_YELLOW}↑↓${RESET} se déplacer ${GRV_GREEN}TAB${RESET} changer l'action ${GRV_GREEN}ENTER${RESET} confirmer ${GRV_RED}ESC${RESET} annuler"
|
||||
echo -e " ${GRV_GRAY}Taper du texte filtre les skills par nom ou description.${RESET}\n"
|
||||
echo -e " ${GRV_FG}Navigation :${RESET} ${GRV_YELLOW}↑↓${RESET} déplacer ${GRV_GREEN}SPACE${RESET} changer action ${GRV_GREEN}TAB${RESET} plier/déplier ${GRV_GREEN}v${RESET} voir skill ${GRV_GREEN}ENTER${RESET} confirmer ${GRV_RED}ESC${RESET} annuler"
|
||||
echo -e " ${GRV_GRAY}Taper du texte filtre par nom ou description.${RESET}\n"
|
||||
|
||||
state_init
|
||||
|
||||
local cycle_script="/tmp/skills_cycle_$$.sh"
|
||||
# Catégories avec >3 skills repliées par défaut
|
||||
rm -f "$COLLAPSED_FILE"
|
||||
declare -A _cat_count
|
||||
for entry in "${SKILLS_LIST[@]}"; do
|
||||
local _cat; _cat="${entry%%|*}"
|
||||
_cat_count[$_cat]=$(( ${_cat_count[$_cat]:-0} + 1 ))
|
||||
done
|
||||
for _cat in "${!_cat_count[@]}"; do
|
||||
[[ ${_cat_count[$_cat]} -gt 3 ]] && echo "$_cat" >> "$COLLAPSED_FILE"
|
||||
done
|
||||
|
||||
local space_script="/tmp/skills_space_$$.sh"
|
||||
local tab_script="/tmp/skills_tab_$$.sh"
|
||||
local list_script="/tmp/skills_list_$$.sh"
|
||||
local fns_file="/tmp/skills_fns_$$.sh"
|
||||
local preview_script="/tmp/skills_preview_$$.sh"
|
||||
|
||||
# Script de cycle d'état (appelé par fzf via execute-silent)
|
||||
cat > "$cycle_script" << 'CYCLE_EOF'
|
||||
# Exporter fonctions et données dans un fichier source partagé
|
||||
{
|
||||
declare -f format_skill_line format_category_header state_get make_key
|
||||
declare -p GRV_GREEN GRV_YELLOW GRV_AQUA GRV_GRAY GRV_BLUE GRV_FG GRV_PURPLE RESET \
|
||||
ICO_OK ICO_UPD ICO_NEW ICO_NA ICO_LOCAL ICO_GLOBAL ICO_SKIP
|
||||
echo "STATE_FILE='$STATE_FILE'"
|
||||
echo "REPO_DIR='$REPO_DIR'"
|
||||
echo "COLLAPSED_FILE='$COLLAPSED_FILE'"
|
||||
echo "SKILLS_LIST=($(printf '"%s" ' "${SKILLS_LIST[@]}"))"
|
||||
} > "$fns_file"
|
||||
|
||||
# Script SPACE : cycle action sur une ligne skill (s:IDX)
|
||||
cat > "$space_script" << 'SPACE_EOF'
|
||||
#!/usr/bin/env bash
|
||||
STATE_FILE="$1"
|
||||
SKILLS_FNS="$2"
|
||||
LINE_NUM="$3" # numéro de ligne fzf (1-based)
|
||||
|
||||
source "$SKILLS_FNS"
|
||||
|
||||
# Récupère l'entrée correspondante (0-based dans SKILLS_LIST)
|
||||
idx=$(( LINE_NUM - 1 ))
|
||||
entry="${SKILLS_LIST[$idx]:-}"
|
||||
source "FNSFILE"
|
||||
td="$1"; type="${td%%:*}"; value="${td#*:}"
|
||||
[[ "$type" != "s" ]] && exit 0
|
||||
entry="${SKILLS_LIST[$value]:-}"
|
||||
[[ -z "$entry" ]] && exit 0
|
||||
|
||||
key=$(make_key "$entry")
|
||||
etat=$(echo "$entry" | cut -d'|' -f4)
|
||||
|
||||
current=$(grep "^${key}=" "$STATE_FILE" 2>/dev/null | cut -d'=' -f2)
|
||||
case "$current" in
|
||||
local) next="global" ;;
|
||||
@@ -384,56 +414,106 @@ case "$current" in
|
||||
*) next="local" ;;
|
||||
esac
|
||||
sed -i "s|^${key}=.*|${key}=${next}|" "$STATE_FILE"
|
||||
CYCLE_EOF
|
||||
chmod +x "$cycle_script"
|
||||
SPACE_EOF
|
||||
sed -i "s|FNSFILE|$fns_file|" "$space_script"
|
||||
chmod +x "$space_script"
|
||||
|
||||
# Exporter fonctions et variables dans un fichier source
|
||||
{
|
||||
declare -f format_skill_line state_get make_key
|
||||
declare -p GRV_GREEN GRV_YELLOW GRV_AQUA GRV_GRAY GRV_BLUE GRV_FG GRV_PURPLE RESET \
|
||||
ICO_OK ICO_UPD ICO_NEW ICO_NA ICO_LOCAL ICO_GLOBAL ICO_SKIP
|
||||
echo "STATE_FILE='$STATE_FILE'"
|
||||
echo "REPO_DIR='$REPO_DIR'"
|
||||
echo "SKILLS_LIST=($(printf '"%s" ' "${SKILLS_LIST[@]}"))"
|
||||
} > "$fns_file"
|
||||
|
||||
# Script de prévisualisation du skill (appelé par fzf --preview)
|
||||
cat > "$preview_script" << 'PREVIEW_EOF'
|
||||
# Script TAB : plier/déplier une catégorie
|
||||
cat > "$tab_script" << 'TAB_EOF'
|
||||
#!/usr/bin/env bash
|
||||
source "$1"
|
||||
idx=$(( $2 - 1 ))
|
||||
entry="${SKILLS_LIST[$idx]:-}"
|
||||
[[ -z "$entry" ]] && exit 0
|
||||
IFS='|' read -r cat skill agent _ <<< "$entry"
|
||||
skill_file="${REPO_DIR}/skills/${cat}/${skill}/${agent}.md"
|
||||
[[ -f "$skill_file" ]] && cat "$skill_file" || echo "Fichier introuvable : $skill_file"
|
||||
PREVIEW_EOF
|
||||
sed -i "s|\"\$1\"|\"$fns_file\"|" "$preview_script"
|
||||
chmod +x "$preview_script"
|
||||
source "FNSFILE"
|
||||
td="$1"; type="${td%%:*}"; value="${td#*:}"
|
||||
if [[ "$type" == "h" ]]; then
|
||||
cat_name="$value"
|
||||
elif [[ "$type" == "s" ]]; then
|
||||
entry="${SKILLS_LIST[$value]:-}"
|
||||
[[ -z "$entry" ]] && exit 0
|
||||
cat_name="${entry%%|*}"
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
if grep -qx "$cat_name" "$COLLAPSED_FILE" 2>/dev/null; then
|
||||
sed -i "/^${cat_name}$/d" "$COLLAPSED_FILE"
|
||||
else
|
||||
echo "$cat_name" >> "$COLLAPSED_FILE"
|
||||
fi
|
||||
TAB_EOF
|
||||
sed -i "s|FNSFILE|$fns_file|" "$tab_script"
|
||||
chmod +x "$tab_script"
|
||||
|
||||
# Script générateur de liste pour fzf --reload
|
||||
cat > "$list_script" << LIST_EOF
|
||||
# Script LIST : générateur d'arbre pour fzf --reload
|
||||
cat > "$list_script" << 'LIST_EOF'
|
||||
#!/usr/bin/env bash
|
||||
source "$fns_file"
|
||||
for entry in "\${SKILLS_LIST[@]}"; do
|
||||
format_skill_line "\$entry"
|
||||
source "FNSFILE"
|
||||
declare -A cat_map
|
||||
declare -a cat_order
|
||||
for i in "${!SKILLS_LIST[@]}"; do
|
||||
cat="${SKILLS_LIST[$i]%%|*}"
|
||||
[[ -z "${cat_map[$cat]+x}" ]] && { cat_order+=("$cat"); cat_map[$cat]=""; }
|
||||
cat_map[$cat]+=" $i"
|
||||
done
|
||||
for cat in "${cat_order[@]}"; do
|
||||
indices=(${cat_map[$cat]})
|
||||
collapsed=0
|
||||
grep -qx "$cat" "$COLLAPSED_FILE" 2>/dev/null && collapsed=1
|
||||
format_category_header "$cat" "${#indices[@]}" "$collapsed"
|
||||
[[ "$collapsed" == "1" ]] && continue
|
||||
for idx in "${indices[@]}"; do
|
||||
format_skill_line "${SKILLS_LIST[$idx]}" "$idx"
|
||||
done
|
||||
done
|
||||
LIST_EOF
|
||||
sed -i "s|FNSFILE|$fns_file|" "$list_script"
|
||||
chmod +x "$list_script"
|
||||
|
||||
# Script PREVIEW : affiche le skill avec coloration (bat si dispo)
|
||||
cat > "$preview_script" << 'PREVIEW_EOF'
|
||||
#!/usr/bin/env bash
|
||||
source "FNSFILE"
|
||||
td="$1"; type="${td%%:*}"; value="${td#*:}"
|
||||
if [[ "$type" == "s" ]]; then
|
||||
entry="${SKILLS_LIST[$value]:-}"
|
||||
[[ -z "$entry" ]] && exit 0
|
||||
IFS='|' read -r cat skill agent _ <<< "$entry"
|
||||
skill_file="${REPO_DIR}/skills/${cat}/${skill}/${agent}.md"
|
||||
if [[ ! -f "$skill_file" ]]; then echo "Fichier introuvable : $skill_file"; exit 0; fi
|
||||
if command -v bat &>/dev/null; then
|
||||
bat --style=numbers,header --color=always --language=markdown "$skill_file"
|
||||
elif command -v batcat &>/dev/null; then
|
||||
batcat --style=numbers,header --color=always --language=markdown "$skill_file"
|
||||
else
|
||||
cat "$skill_file"
|
||||
fi
|
||||
elif [[ "$type" == "h" ]]; then
|
||||
echo "=== Catégorie : ${value} ==="
|
||||
for entry in "${SKILLS_LIST[@]}"; do
|
||||
[[ "${entry%%|*}" == "$value" ]] || continue
|
||||
IFS='|' read -r _ skill agent _ _ _ desc tags <<< "$entry"
|
||||
echo " • ${skill} [${agent}] ${desc} ${tags}"
|
||||
done
|
||||
fi
|
||||
PREVIEW_EOF
|
||||
sed -i "s|FNSFILE|$fns_file|" "$preview_script"
|
||||
chmod +x "$preview_script"
|
||||
|
||||
local legend
|
||||
legend=$(echo -e "${GRV_GRAY}État: ${GRV_GREEN}✓ installé ${GRV_YELLOW}↑ MAJ ${GRV_AQUA}+ nouveau Action: ${GRV_GREEN}●L local ${GRV_BLUE}●G global ${GRV_GRAY}○ ignorer TAB=changer ENTER=confirmer ESC=quitter${RESET}")
|
||||
legend=$(echo -e "${GRV_GRAY}État: ${GRV_GREEN}✓ ${GRV_YELLOW}↑ ${GRV_AQUA}+ Action: ${GRV_GREEN}●L ${GRV_BLUE}●G ${GRV_GRAY}○ SPACE=action TAB=plier v=voir ENTER=ok ESC=quitter${RESET}")
|
||||
|
||||
fzf \
|
||||
--ansi \
|
||||
--delimiter='\t' \
|
||||
--with-nth=2.. \
|
||||
--nth=2.. \
|
||||
--prompt="Skills > " \
|
||||
--header="$legend" \
|
||||
--preview="bash $preview_script {n}" \
|
||||
--preview-window="right:45%:wrap" \
|
||||
--bind="tab:execute-silent($cycle_script '$STATE_FILE' '$fns_file' {n})+reload($list_script)" \
|
||||
--preview="bash $preview_script {1}" \
|
||||
--preview-window="right:50%:wrap:hidden" \
|
||||
--bind="space:execute-silent(bash $space_script {1})+reload(bash $list_script)" \
|
||||
--bind="tab:execute-silent(bash $tab_script {1})+reload(bash $list_script)" \
|
||||
--bind="v:toggle-preview" \
|
||||
< <(bash "$list_script") > /dev/null || true
|
||||
|
||||
rm -f "$cycle_script" "$list_script" "$fns_file" "$preview_script"
|
||||
rm -f "$space_script" "$tab_script" "$list_script" "$fns_file" "$preview_script"
|
||||
}
|
||||
|
||||
# ── Installation ──────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user