Merge quick/orquestador-command: /orquestador + grupo orchestration (launch_claude_agent_kitty, list_claude_agents)

This commit is contained in:
2026-06-08 21:15:16 +02:00
5 changed files with 800 additions and 0 deletions
@@ -0,0 +1,66 @@
---
name: launch_claude_agent_kitty
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "launch_claude_agent_kitty(title: string, directory: string, prompt_file: string) -> string"
description: "Lanza un Claude Code secundario interactivo y persistente en su propia terminal kitty, con un prompt autonomo inyectado desde un archivo y --dangerously-skip-permissions. Mecanica del modo orquestador: un Claude principal descompone una tarea y lanza N secundarios, cada uno en su kitty, que el humano ve y puede retomar. La ventana sobrevive al cierre de la terminal padre (setsid nohup ... disown) y deja una shell interactiva viva cuando el claude termina (exec zsh)."
tags: [orchestration, agents, claude, kitty, agent, terminal, infra]
params:
- name: title
desc: "Titulo de la ventana kitty. Ej: 'fn_registry · subtarea X'. Tambien se sanitiza (minusculas, no-alfanumerico -> '_') para derivar el slug del archivo de log."
- name: directory
desc: "Directorio de trabajo AISLADO donde arranca el claude secundario (worktree git, sub-repo, o dir cualquiera). Debe existir; si no -> error exit 2. Usar un dir aislado: dos claudes en el mismo working tree comparten HEAD y dispersan commits."
- name: prompt_file
desc: "Ruta a un archivo .md con el prompt autonomo a inyectar (ej. /tmp/orq_<slug>.md). Debe existir y ser legible; si no -> error exit 2."
output: "Imprime en stdout el title, directory, prompt_file y la ruta del log (/tmp/orq_<slug>_kitty.log) donde se ve el arranque. Exit 0 = lanzamiento disparado; exit 2 = argumentos invalidos; exit 1 = kitty no instalado."
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/launch_claude_agent_kitty.sh"
---
## Ejemplo
```bash
source bash/functions/infra/launch_claude_agent_kitty.sh
# El orquestador prepara un worktree aislado y un archivo de prompt...
git worktree add /tmp/orq_docs_wt -b orq/docs
cat > /tmp/orq_docs.md <<'PROMPT'
Eres un agente secundario. Tu tarea: revisar y mejorar la documentacion del
dominio infra del registry. Trabaja SOLO en este worktree. Reporta al terminar.
PROMPT
# ...y lanza un claude secundario en su propia kitty:
launch_claude_agent_kitty "fn_registry · docs" /tmp/orq_docs_wt /tmp/orq_docs.md
# -> abre una ventana kitty titulada "fn_registry · docs", arranca claude con
# el prompt inyectado, y deja /tmp/orq_fn_registry_docs_kitty.log con el arranque.
```
O directo via `fn run`:
```bash
./fn run launch_claude_agent_kitty "fn_registry · docs" /tmp/orq_docs_wt /tmp/orq_docs.md
```
## Cuando usarla
Cuando el orquestador quiere lanzar un Claude secundario **interactivo** en su propia terminal kitty para una sub-tarea que el humano quiere **ver y poder retomar**. A diferencia del `Agent` tool (sub-agente no interactivo, headless, cuyo output vuelve al padre y no deja terminal abierta), aqui cada secundario corre en una ventana visible que persiste: el humano observa el progreso en vivo y, cuando el claude termina, la shell sigue ahi para continuar manualmente o relanzar.
## Gotchas
- **kitty debe estar instalado.** Si `command -v kitty` falla -> exit 1 con mensaje claro. No hay fallback a otra terminal.
- **El `directory` debe ser AISLADO** (worktree git o sub-repo propio). Dos claudes apuntando al mismo working tree **comparten HEAD** y dispersan/cruzan los commits (memoria `multi-agent-git-race-same-repo`). El orquestador debe crear un worktree/clon por agente antes de llamar.
- **`--dangerously-skip-permissions` corre sin pedir confirmacion** a ninguna accion (memoria `lanzar-agentes-skip-permissions`). Es a proposito para agentes autonomos desatendidos, pero es un riesgo asumido: el secundario puede tocar el sistema sin gates. No lanzar sobre directorios sensibles.
- **El log de `/tmp/orq_<slug>_kitty.log` es donde se ve el arranque** (errores de kitty/claude al iniciar). El `<slug>` deriva del `title` sanitizado; titulos distintos que colapsen al mismo slug sobrescriben el mismo log.
- **El PID reportado no es el de kitty.** Con `setsid` el `$!` es el del proceso setsid, no el de la ventana; por eso la funcion reporta el log en vez de un PID. Para encontrar la ventana despues: `pgrep -af kitty | grep <title>`.
- **El prompt se inyecta con `"$(cat <prompt_file>)"` evaluado DENTRO de la kitty.** Si editas el `prompt_file` despues de lanzar pero antes de que la kitty arranque, el claude vera la version editada (se lee en el momento del arranque, no del lanzamiento).
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
# launch_claude_agent_kitty — Lanza un Claude Code secundario interactivo y
# persistente en su propia terminal kitty, con un prompt autonomo inyectado
# desde un archivo. Es la mecanica de lanzamiento del "modo orquestador": un
# Claude principal descompone una tarea y lanza N secundarios, cada uno en su
# kitty, que el humano ve y puede retomar.
#
# Mecanismo:
# - setsid nohup kitty ... & disown -> la ventana sobrevive al cierre de la
# terminal padre (igual que reboot_all_claudes con setsid).
# - zsh -ic 'claude ...; exec zsh' -> al terminar el claude queda una shell
# interactiva viva para que el humano siga en esa terminal.
# - --dangerously-skip-permissions -> agente autonomo desatendido (sin
# confirmaciones). Riesgo asumido a proposito.
# - El prompt se inyecta con "$(cat <prompt_file>)" para no expandir nada en
# el shell del orquestador.
# - Log de arranque en /tmp/orq_<slug>_kitty.log, donde <slug> deriva del
# title (minusculas, no-alfanumerico -> '_').
set -euo pipefail
IFS=$' \t\n'
launch_claude_agent_kitty() {
# -----------------------------------------------------------------------
# Ayuda / sin argumentos.
# -----------------------------------------------------------------------
if [[ $# -eq 0 || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
cat <<'USAGE'
Uso: launch_claude_agent_kitty <title> <directory> <prompt_file>
Lanza un Claude Code secundario interactivo y persistente en su propia
terminal kitty, con el prompt del archivo <prompt_file> inyectado y
--dangerously-skip-permissions (agente autonomo desatendido).
Argumentos (los 3 obligatorios):
title Titulo de la ventana kitty. Ej: "fn_registry · subtarea X".
directory Directorio de trabajo AISLADO donde arranca el claude
secundario (worktree git, sub-repo, o dir cualquiera). Debe
existir. Usa un dir aislado: dos claudes en el mismo working
tree comparten HEAD y dispersan commits.
prompt_file Ruta a un archivo .md con el prompt autonomo a inyectar.
Debe existir y ser legible.
Ejemplo:
launch_claude_agent_kitty "fn_registry · docs" /tmp/orq_docs_wt /tmp/orq_docs.md
El log de arranque va a /tmp/orq_<slug>_kitty.log (slug derivado del title).
USAGE
return 0
fi
# -----------------------------------------------------------------------
# Validacion de argumentos.
# -----------------------------------------------------------------------
if [[ $# -ne 3 ]]; then
echo "launch_claude_agent_kitty: se requieren 3 argumentos <title> <directory> <prompt_file> (recibidos: $#). Usa -h." >&2
return 2
fi
local title="$1"
local directory="$2"
local prompt_file="$3"
if [[ -z "$title" ]]; then
echo "launch_claude_agent_kitty: <title> no puede estar vacio." >&2
return 2
fi
if [[ ! -d "$directory" ]]; then
echo "launch_claude_agent_kitty: el directorio de trabajo no existe: '$directory'." >&2
return 2
fi
if [[ ! -f "$prompt_file" ]]; then
echo "launch_claude_agent_kitty: el prompt_file no existe: '$prompt_file'." >&2
return 2
fi
if [[ ! -r "$prompt_file" ]]; then
echo "launch_claude_agent_kitty: el prompt_file no es legible: '$prompt_file'." >&2
return 2
fi
# -----------------------------------------------------------------------
# Comprobar que kitty esta instalado.
# -----------------------------------------------------------------------
if ! command -v kitty >/dev/null 2>&1; then
echo "launch_claude_agent_kitty: 'kitty' no esta instalado o no esta en el PATH." >&2
return 1
fi
# -----------------------------------------------------------------------
# Derivar el slug del title para el nombre del log.
# minusculas, todo no-alfanumerico -> '_', colapsar/recortar '_'.
# -----------------------------------------------------------------------
local slug
slug="$(printf '%s' "$title" \
| tr '[:upper:]' '[:lower:]' \
| tr -c 'a-z0-9' '_' \
| sed -E 's/_+/_/g; s/^_//; s/_$//')"
[[ -z "$slug" ]] && slug="agent"
local log="/tmp/orq_${slug}_kitty.log"
# -----------------------------------------------------------------------
# Lanzar la kitty detached. El prompt se inyecta con "$(cat <prompt_file>)"
# ya escapado para que se evalue DENTRO de la kitty, no aqui.
# exec zsh deja una shell viva cuando el claude termina.
# -----------------------------------------------------------------------
local inner
inner="claude --dangerously-skip-permissions \"\$(cat $(printf '%q' "$prompt_file"))\"; exec zsh"
setsid nohup kitty \
--title "$title" \
--directory "$directory" \
zsh -ic "$inner" \
>"$log" 2>&1 &
disown 2>/dev/null || true
# -----------------------------------------------------------------------
# Reportar. Con setsid el $! es el PID de setsid, no el de kitty; basta
# con confirmar el lanzamiento y apuntar al log donde se ve el arranque.
# -----------------------------------------------------------------------
echo "launch_claude_agent_kitty: claude secundario lanzado."
echo " title: $title"
echo " directory: $directory"
echo " prompt_file: $prompt_file"
echo " log: $log"
echo " (sigue el arranque con: tail -f $(printf '%q' "$log"))"
return 0
}
# Permitir ejecutar el archivo directamente (no solo como funcion sourced).
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
launch_claude_agent_kitty "$@"
fi
@@ -0,0 +1,55 @@
---
name: list_claude_agents
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "list_claude_agents([--json] [--exclude-current] [-h|--help])"
description: "Lista todas las instancias de Claude Code VIVAS cruzando pgrep -x claude con los archivos de estado por proceso ~/.claude/sessions/<PID>.json. Para cada claude vivo y validado devuelve PID, status (idle/busy), etime (tiempo de vida), KITTY_PID de su ventana kitty, sessionId y cwd. Es la herramienta de seguimiento de la flota del modo orquestador: el Claude principal ve que agentes secundarios siguen vivos, en que directorio trabajan y su sessionId para retomarlos con claude --resume."
tags: [orchestration, claude, session, fleet, kitty, infra, terminal-capture]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: "--json"
desc: "Imprime un array JSON (un objeto por agente con pid, session_id, cwd, status, etime, kitty_pid, self) en vez de la tabla legible. Pensado para que el agente parsee y decida cual retomar/parar."
- name: "--exclude-current"
desc: "Omite la propia sesion del listado. Detecta el claude propio subiendo por la cadena de ancestros de $$ hasta hallar un proceso con comm=claude. Sin esta opcion, la sesion actual se marca (columna SELF en tabla / self=true en JSON)."
- name: "-h|--help"
desc: "Muestra el uso y termina con exit 0."
output: "En modo tabla: una fila por claude vivo y validado con columnas PID, STATUS, ETIME, KITTY, SELF, SESSION_ID, CWD. En modo --json: array JSON con pid, session_id, cwd, status, etime, kitty_pid (null si no corre en kitty) y self. Si no hay claudes vivos imprime aviso (tabla) o [] (json) y exit 0. Exit 0 normal; exit 2 si flag invalido."
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/infra/list_claude_agents.sh"
notes: "Mecanismo (Claude Code 2.1.x sobre Linux + kitty): pgrep -x claude -> PIDs vivos; ~/.claude/sessions/<PID>.json -> sessionId/cwd/status/procStart (parseado con python3); validacion en tres capas: kill -0 <PID> exito, el JSON existe, y anti-PID-reciclado comparando procStart del JSON con el campo 22 de /proc/<PID>/stat (si difieren el JSON es huerfano de un PID reusado y se omite). KITTY_PID se saca del environ del proceso (tr '\\0' '\\n' < /proc/<PID>/environ | sed -n 's/^KITTY_PID=//p'). etime via ps -o etime= -p <PID>. Reusa la misma logica de descubrimiento y validacion que reboot_all_claudes_bash_infra. El codigo JSON va en python3 -c con los datos por stdin TSV (no heredoc) para no colisionar el stdin del pipe."
---
## Ejemplo
```bash
# Tabla legible de la flota de Claudes vivos (PID, status, etime, kitty, sessionId, cwd).
./fn run list_claude_agents
# Array JSON para parsear (decidir cual retomar con claude --resume <session_id>).
./fn run list_claude_agents --json
# Omitir la propia sesion (ver solo los agentes secundarios).
./fn run list_claude_agents --exclude-current
```
## Cuando usarla
Cuando el orquestador necesita ver la flota de Claudes secundarios vivos (PID, cwd, sessionId, status) para seguir su progreso o decidir cual retomar/parar. Lanzala al inicio de un ciclo de seguimiento para saber que agentes siguen activos y en que directorio trabaja cada uno; usa `--json` cuando vayas a programar la decision (filtrar por `status`, extraer `session_id` para un `claude --resume`).
## Gotchas
- **Requiere Claude Code >= 2.1.x.** Depende de que cada sesion escriba `~/.claude/sessions/<PID>.json` con los campos `sessionId`, `cwd`, `status`, `procStart`. Si una version futura cambia el formato, la funcion deja de mapear PID -> sessionId y omitira las sesiones.
- **Un JSON puede ser huerfano por PID reciclado.** El sistema operativo reusa PIDs; un `<PID>.json` viejo puede apuntar a un proceso `claude` distinto. Por eso se valida `procStart` del JSON contra el campo 22 de `/proc/<PID>/stat`; si no coincide la entrada se descarta. Sin esa validacion se reportarian agentes fantasma.
- **El titulo exacto de la ventana kitty no se recupera sin `kitty @`.** Se reporta el `KITTY_PID` (suficiente para identificar la ventana); mapearlo al titulo requeriria `kitty @ ls`, que solo funciona si el control remoto de kitty esta habilitado. KISS: se omite por defecto. Un claude que corra fuera de kitty (terminal integrado de un editor, etc.) sale con `KITTY` vacio `(none)` / `kitty_pid: null`.
- **Solo ve procesos del usuario actual.** `pgrep -x claude` y la lectura de `/proc/<PID>/{environ,stat}` solo cubren los claudes del propio usuario; no lista sesiones de otros usuarios del sistema.
- **`status` refleja el ultimo estado guardado en el JSON**, no necesariamente el instante exacto de la consulta (Claude actualiza el archivo al cambiar de estado). Pueden aparecer valores como `idle`, `busy` o `waiting`.
+265
View File
@@ -0,0 +1,265 @@
#!/usr/bin/env bash
# list_claude_agents — Lista todas las instancias de Claude Code VIVAS cruzando
# pgrep -x claude con los archivos de estado por proceso ~/.claude/sessions/<PID>.json.
# Para cada claude vivo y validado reporta: PID, status (idle/busy), etime (tiempo de
# vida del proceso), KITTY_PID de la ventana kitty si corre en una, sessionId y cwd.
# Es la herramienta de "seguimiento de la flota" del modo orquestador: el Claude
# principal la usa para ver que agentes secundarios siguen vivos, en que directorio
# trabajan y su sessionId (para poder retomarlos con claude --resume <sessionId>).
#
# Mecanismo (Claude Code 2.1.x sobre Linux + kitty):
# - pgrep -x claude -> PIDs de las sesiones interactivas vivas.
# - ~/.claude/sessions/<PID>.json -> mapea PID a {sessionId, cwd, status, procStart}.
# - Anti-PID-reciclado: procStart del JSON debe coincidir con el campo 22 de
# /proc/<PID>/stat; ademas kill -0 <PID> debe tener exito y el JSON debe existir.
# - KITTY_PID del environ del proceso -> ventana kitty (titulo exacto requeriria
# 'kitty @ ls'; aqui se reporta el KITTY_PID, suficiente para identificarla).
# - etime via ps -o etime= -p <PID>.
set -euo pipefail
IFS=$' \t\n'
list_claude_agents() {
local output="table" # table | json
local exclude_current=0
# -----------------------------------------------------------------------
# Parseo de argumentos
# -----------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--json)
output="json"
;;
--exclude-current)
exclude_current=1
;;
-h|--help)
cat <<'USAGE'
Uso: list_claude_agents [--json] [--exclude-current]
Lista las instancias de Claude Code vivas y validas, una fila por agente, con su
PID, status, etime (tiempo de vida), KITTY_PID, sessionId y cwd. Pensada para el
modo orquestador: ver la flota de Claudes secundarios y su sessionId para retomar
(claude --resume <sessionId>) o decidir cual parar.
Opciones:
--json Imprime un array JSON (pid, session_id, cwd, status, etime,
kitty_pid) en vez de la tabla. Util para parsear.
--exclude-current Omite la propia sesion (sube por la cadena de ancestros de
$$ hasta hallar un proceso con comm=claude). Sin esta opcion,
la sesion actual se marca con self=true / SELF en la tabla.
-h, --help Muestra esta ayuda.
Ejemplos:
list_claude_agents
list_claude_agents --json
list_claude_agents --exclude-current
USAGE
return 0
;;
*)
echo "list_claude_agents: opcion desconocida: '$1' (usa -h)" >&2
return 2
;;
esac
shift
done
# -----------------------------------------------------------------------
# Detectar el PID de la sesion actual subiendo por la cadena de ancestros
# hasta encontrar un proceso cuyo comm sea exactamente "claude".
# Se usa tanto para --exclude-current como para marcar la fila propia.
# -----------------------------------------------------------------------
local current_claude_pid=""
local walk="$$"
local guard=0
while [[ -n "$walk" && "$walk" != "0" && "$walk" != "1" ]]; do
local comm=""
comm="$(cat "/proc/$walk/comm" 2>/dev/null || true)"
if [[ "$comm" == "claude" ]]; then
current_claude_pid="$walk"
break
fi
# campo 4 de /proc/<pid>/stat es el PPID
walk="$(awk '{print $4}' "/proc/$walk/stat" 2>/dev/null || true)"
guard=$((guard + 1))
[[ "$guard" -gt 64 ]] && break
done
# -----------------------------------------------------------------------
# Recolectar las sesiones vivas y validarlas.
# -----------------------------------------------------------------------
local sessions_dir="$HOME/.claude/sessions"
local pids=""
pids="$(pgrep -x claude 2>/dev/null || true)"
if [[ -z "$pids" ]]; then
if [[ "$output" == "json" ]]; then
echo "[]"
else
echo "list_claude_agents: no hay sesiones de Claude Code vivas (pgrep -x claude vacio)."
fi
return 0
fi
# Arrays paralelos con la flota validada.
local -a a_pid a_status a_etime a_kitty a_sid a_cwd a_self
local pid
for pid in $pids; do
# Validacion 1: el proceso debe seguir vivo.
if ! kill -0 "$pid" 2>/dev/null; then
continue
fi
# Validacion 2: debe existir su JSON de sesion.
local json="$sessions_dir/$pid.json"
if [[ ! -f "$json" ]]; then
continue
fi
# Parsear el JSON con python3 (campos sessionId, cwd, status, procStart).
# Salida: lineas "clave=valor" en orden fijo.
local parsed=""
parsed="$(python3 - "$json" <<'PY' 2>/dev/null || true
import json, sys
try:
with open(sys.argv[1]) as fh:
d = json.load(fh)
except Exception:
sys.exit(0)
print("sessionId=" + str(d.get("sessionId", "")))
print("cwd=" + str(d.get("cwd", "")))
print("status=" + str(d.get("status", "")))
print("procStart=" + str(d.get("procStart", "")))
PY
)"
[[ -z "$parsed" ]] && continue
local sid cwd status proc_start_json
sid="$(printf '%s\n' "$parsed" | sed -n 's/^sessionId=//p')"
cwd="$(printf '%s\n' "$parsed" | sed -n 's/^cwd=//p')"
status="$(printf '%s\n' "$parsed" | sed -n 's/^status=//p')"
proc_start_json="$(printf '%s\n' "$parsed" | sed -n 's/^procStart=//p')"
[[ -z "$sid" ]] && continue
# Validacion 3 (anti-PID-reciclado): procStart del JSON debe coincidir
# con el campo 22 de /proc/<PID>/stat.
local proc_start_real=""
proc_start_real="$(awk '{print $22}' "/proc/$pid/stat" 2>/dev/null || true)"
if [[ -n "$proc_start_json" && "$proc_start_json" != "$proc_start_real" ]]; then
# JSON huerfano de un PID reciclado: omitir.
continue
fi
# Omitir la propia sesion si se pidio --exclude-current.
if [[ "$exclude_current" -eq 1 && -n "$current_claude_pid" && "$pid" == "$current_claude_pid" ]]; then
continue
fi
# KITTY_PID de la ventana kitty (vacio si claude no corre en kitty).
local kitty_pid=""
kitty_pid="$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | sed -n 's/^KITTY_PID=//p' | head -n1)"
# etime: tiempo transcurrido desde que arranco el proceso.
local etime=""
etime="$(ps -o etime= -p "$pid" 2>/dev/null | tr -d ' ' || true)"
# Marca de sesion propia (solo relevante cuando NO se excluye).
local self="false"
if [[ -n "$current_claude_pid" && "$pid" == "$current_claude_pid" ]]; then
self="true"
fi
a_pid+=("$pid")
a_status+=("${status:-?}")
a_etime+=("${etime:-?}")
a_kitty+=("${kitty_pid:-}")
a_sid+=("$sid")
a_cwd+=("${cwd:-?}")
a_self+=("$self")
done
local total="${#a_pid[@]}"
if [[ "$total" -eq 0 ]]; then
if [[ "$output" == "json" ]]; then
echo "[]"
else
echo "list_claude_agents: ninguna sesion valida encontrada (PIDs huerfanos, reciclados, o sin JSON)."
fi
return 0
fi
# -----------------------------------------------------------------------
# Salida JSON.
# -----------------------------------------------------------------------
if [[ "$output" == "json" ]]; then
# Delegar el escaping correcto de strings (cwd con espacios, etc.) a python3.
# El codigo python va en -c y los datos por stdin (TSV), para no colisionar
# el heredoc con el stdin del pipe.
local i
{
for ((i = 0; i < total; i++)); do
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"${a_pid[$i]}" \
"${a_sid[$i]}" \
"${a_cwd[$i]}" \
"${a_status[$i]}" \
"${a_etime[$i]}" \
"${a_kitty[$i]}" \
"${a_self[$i]}"
done
} | python3 -c '
import json, sys
out = []
for line in sys.stdin:
line = line.rstrip("\n")
if not line:
continue
pid, sid, cwd, status, etime, kitty, self_ = line.split("\t")
out.append({
"pid": int(pid) if pid.isdigit() else pid,
"session_id": sid,
"cwd": cwd,
"status": status,
"etime": etime,
"kitty_pid": (int(kitty) if kitty.isdigit() else (kitty or None)),
"self": (self_ == "true"),
})
print(json.dumps(out, indent=2))
'
return 0
fi
# -----------------------------------------------------------------------
# Salida tabla legible.
# -----------------------------------------------------------------------
echo "list_claude_agents — claudes vivos: ${total}"
echo
printf '%-8s %-7s %-12s %-9s %-6s %-38s %s\n' \
"PID" "STATUS" "ETIME" "KITTY" "SELF" "SESSION_ID" "CWD"
printf '%-8s %-7s %-12s %-9s %-6s %-38s %s\n' \
"--------" "-------" "------------" "---------" "------" \
"--------------------------------------" "---"
local i
for ((i = 0; i < total; i++)); do
local self_mark=""
[[ "${a_self[$i]}" == "true" ]] && self_mark="SELF"
printf '%-8s %-7s %-12s %-9s %-6s %-38s %s\n' \
"${a_pid[$i]}" \
"${a_status[$i]}" \
"${a_etime[$i]}" \
"${a_kitty[$i]:-(none)}" \
"${self_mark:--}" \
"${a_sid[$i]}" \
"${a_cwd[$i]}"
done
return 0
}
# Permitir ejecutar el archivo directamente (no solo como funcion sourced).
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
list_claude_agents "$@"
fi