feat(browser): auto-commit con 178 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 18:22:23 +02:00
parent 7d100e7f3e
commit 763e06c127
178 changed files with 19917 additions and 317 deletions
@@ -0,0 +1,73 @@
---
name: focus_cdp_tab_window
id: focus_cdp_tab_window_bash_infra
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "focus_cdp_tab_window(port: int, [target_id: string]) -> void"
description: "Handoff humano de captcha: trae al frente la pestaña (via CDP /json/activate) y la ventana del SO de un Chrome con CDP, para que el humano resuelva el captcha a mano. Promocion del patron inline que acompaña a detect_captcha_go_browser."
tags: [browser, captcha, handoff, cdp, wmctrl, xdotool, infra, navegator]
params:
- name: "port"
desc: "Puerto CDP del Chrome (ej. 9333 = Chrome aislado del browser_mcp; 9222 = navegador diario). Obligatorio."
- name: "target_id"
desc: "Opcional. Target/tab id CDP de la pestaña del captcha. Si se pasa, se activa esa pestaña dentro del browser antes de levantar la ventana del SO. Si se omite, solo se levanta la ventana."
output: "Stdout una linea legible y JSON-parseable simple: 'focus_cdp_tab_window: focused win=<wid> pid=<pid> port=<port> tab=<target_id_o_->'. Exit 0 en exito; 2 sin puerto, 3 sin DISPLAY, 4 falta wmctrl/xdotool, 5 no hay chromium en el puerto, 6 sin ventana top-level."
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/focus_cdp_tab_window.sh"
---
## Ejemplo
```bash
# Activar la pestaña del captcha (por su target id CDP) y levantar la ventana del Chrome aislado
focus_cdp_tab_window 9333 20EF6E28AA792C53AF0D260F34A768B3
# -> focus_cdp_tab_window: focused win=0x03a00007 pid=48213 port=9333 tab=20EF6E28AA792C53AF0D260F34A768B3
# Solo levantar la ventana del Chrome (sin activar tab concreta)
focus_cdp_tab_window 9333
# -> focus_cdp_tab_window: focused win=0x03a00007 pid=48213 port=9333 tab=-
```
Invocacion canonica via el CLI del registry (despacho bash automatico):
```bash
./fn run focus_cdp_tab_window 9333 20EF6E28AA792C53AF0D260F34A768B3
```
## Cuando usarla
En el handoff humano de captcha: cuando el `browser_mcp` marca `⚠️ CAPTCHA-DETECTED`
(via `detect_captcha_go_browser`), usa esta funcion para traer la pestaña del captcha y la
ventana del Chrome al frente para que el humano lo resuelva a mano; luego se le notifica y se
para la automatizacion. Pasa el `target_id` de la tab donde se detecto el captcha para activar
esa pestaña exacta; omitelo si solo necesitas levantar la ventana del navegador.
## Gotchas
- **Impura, requiere X11**: necesita un entorno grafico (`$DISPLAY` no vacio) + `wmctrl` + `xdotool`
instalados. No sirve headless ni por SSH sin X forwarding — sale con error y exit != 0.
- **Match pid->ventana fragil**: resuelve la ventana cruzando el PID del browser principal con la
columna PID de `wmctrl -lp`. Puede fallar si el window manager agrupa ventanas o si chromium no
expone `_NET_WM_PID` en el main; de ahi el fallback a `xdotool search --pid <pid> --onlyvisible`.
- **No reposiciona entre monitores**: solo activa/levanta la ventana donde ya esta; no la mueve a
otra pantalla.
- **Varias ventanas del mismo Chrome**: si el browser tiene varias ventanas top-level, coge la
primera que matchea el PID.
- **Activate CDP best-effort**: `curl /json/activate/<target_id>` puede dar 404 si el `target_id`
caduco (la tab cambio de id o se cerro). La funcion NO aborta: sigue con el raise de la ventana
igualmente.
- **Reintento por XFCE**: xfwm pisa el primer `windowactivate`/`windowraise`, por eso se hace el
activate+raise dos veces con una espera corta entre medias.
- **Identifica el browser process por ausencia de `--type=`**: las lineas de `pgrep` con
`--type=renderer/gpu/utility/zygote` son procesos hijos; se descartan para quedarse con el main.
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# focus_cdp_tab_window — trae al frente la pestaña + la ventana del SO de un Chrome con CDP
#
# Handoff humano de captcha: activa la tab del captcha (opcional, via CDP) y levanta
# la ventana X11 del proceso browser principal de ese puerto para que un humano resuelva
# el captcha a mano. Best-effort y robusto: cada paso continua aunque uno falle.
focus_cdp_tab_window() {
set -uo pipefail
local port="${1:-}"
local target_id="${2:-}"
# 1. Validacion de entorno y dependencias.
if [[ -z "$port" ]]; then
echo "focus_cdp_tab_window: falta el puerto CDP (uso: focus_cdp_tab_window <port> [target_id])" >&2
return 2
fi
if [[ -z "${DISPLAY:-}" ]]; then
echo "focus_cdp_tab_window: sin entorno grafico (DISPLAY vacio)" >&2
return 3
fi
if ! command -v wmctrl >/dev/null 2>&1 || ! command -v xdotool >/dev/null 2>&1; then
echo "focus_cdp_tab_window: falta wmctrl/xdotool" >&2
return 4
fi
# 2. Activar la tab del captcha dentro del browser (best-effort, no aborta).
if [[ -n "$target_id" ]]; then
curl -sf "http://127.0.0.1:${port}/json/activate/${target_id}" >/dev/null 2>&1 || true
fi
# 3. Encontrar el PID del proceso BROWSER principal de ese puerto.
# De las lineas que matchean el flag de debugging, el browser process es el que
# NO lleva --type= (los renderers/gpu/utility/zygote son procesos hijos).
local browser_pid=""
local line
while IFS= read -r line; do
[[ -z "$line" ]] && continue
if [[ "$line" == *"--type="* ]]; then
continue
fi
# pgrep -af antepone el PID seguido de la cmdline.
browser_pid="${line%% *}"
break
done < <(pgrep -af -- "remote-debugging-port=${port}" 2>/dev/null)
if [[ -z "$browser_pid" ]]; then
echo "focus_cdp_tab_window: no hay chromium en el puerto ${port}" >&2
return 5
fi
# 4. Resolver el window id top-level.
# Primero por wmctrl -lp (columna 3 = PID). Fallback xdotool si el main no expone _NET_WM_PID.
local wid=""
while IFS= read -r line; do
[[ -z "$line" ]] && continue
# Formato: <wid> <desktop> <pid> <host> <title...>
local w_id w_pid
w_id="$(awk '{print $1}' <<<"$line")"
w_pid="$(awk '{print $3}' <<<"$line")"
if [[ "$w_pid" == "$browser_pid" ]]; then
wid="$w_id"
break
fi
done < <(wmctrl -lp 2>/dev/null)
if [[ -z "$wid" ]]; then
wid="$(xdotool search --pid "$browser_pid" --onlyvisible 2>/dev/null | head -n1)"
fi
if [[ -z "$wid" ]]; then
echo "focus_cdp_tab_window: no se encontro ventana top-level para pid ${browser_pid} (puerto ${port})" >&2
return 6
fi
# 5. Traer al frente con REINTENTO (xfwm de XFCE pisa el primer activate/raise).
# Espera no bloqueante con read -t en vez de sleep.
local attempt
for attempt in 1 2; do
xdotool windowactivate "$wid" >/dev/null 2>&1 || true
read -r -t 0.2 _ < /dev/zero 2>/dev/null || true
xdotool windowraise "$wid" >/dev/null 2>&1 || true
done
# 6. Salida legible y JSON-parseable simple.
echo "focus_cdp_tab_window: focused win=${wid} pid=${browser_pid} port=${port} tab=${target_id:--}"
return 0
}
# Permitir ejecucion directa: focus_cdp_tab_window <port> [target_id]
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
focus_cdp_tab_window "$@"
fi
+50 -19
View File
@@ -3,10 +3,10 @@ name: launch_fleetclaude
kind: function
lang: bash
domain: infra
version: "1.3.2"
version: "1.4.0"
purity: impure
signature: "launch_fleetclaude [--cwd <dir>] [--bin <path>] [--session <name>] [--cols <n>]"
description: "Entrypoint de FleetView: abre una ventana kitty con una sesion tmux (socket aislado -L fleet) de dos panes (TUI fleetview a la izquierda, claude --dangerously-skip-permissions a la derecha) para centralizar la flota de Claudes. Instala atajos alt+flechas/alt+enter/alt+n que controlan la TUI desde cualquier pane, y fija el ancho del sidebar con hooks."
signature: "launch_fleetclaude [--cwd <dir>] [--bin <path>] [--session <name>] [--reuse] [--cols <n>]"
description: "Entrypoint de FleetView: abre una ventana kitty con una sesion tmux (socket aislado por perfil) de dos panes (TUI fleetview a la izquierda, claude --dangerously-skip-permissions a la derecha) para centralizar la flota de Claudes. Soporta PERFILES multiples: sin --session/--reuse cada invocacion abre un perfil nuevo (fleet, fleet2, fleet3, ...) con su propia flota; inyecta FLEET_SOCKET/FLEET_SESSION a la TUI para que cada panel vea solo sus Claudes. Instala atajos alt+flechas/alt+enter/alt+n que controlan la TUI desde cualquier pane, y fija el ancho del sidebar con hooks."
tags: [claude-fleet, infra, kitty, tmux, claude, fleetview, launcher]
params:
- name: --cwd
@@ -14,7 +14,9 @@ params:
- name: --bin
desc: "Ruta al binario de la TUI fleetview que corre en el pane izquierdo. Opcional. Default: <repo>/apps/fleetview/fleetview. Si no es ejecutable, el pane izquierdo muestra un mensaje de como compilarla y deja una shell viva."
- name: --session
desc: "Nombre de la sesion tmux a crear o reutilizar. Opcional. Default: fleet. La funcion es idempotente sobre este nombre."
desc: "Fija el perfil (socket+sesion tmux comparten nombre) por nombre exacto; reutiliza el existente si ya vive (idempotente sobre ese nombre). Opcional. Sin esta opcion, el perfil se elige automaticamente (primer nombre libre de la secuencia fleet, fleet2, ...)."
- name: --reuse
desc: "Reattach al perfil principal 'fleet' en vez de abrir uno nuevo. Opcional. Recupera el comportamiento idempotente clasico (volver a invocar NO duplica la flota, reusa la existente)."
- name: --cols
desc: "Ancho en columnas del pane izquierdo (la TUI). Opcional. Default: 40."
output: "Crea/reutiliza una sesion tmux detached con dos panes y lanza una ventana kitty 'FleetView' adjunta a ella, desacoplada del shell padre (setsid). Imprime el estado por stdout. Sin valor de retorno; exit 0 en exito."
@@ -36,17 +38,22 @@ file_path: "bash/functions/infra/launch_fleetclaude.sh"
# Via fn run (resuelve por nombre o ID):
fn run launch_fleetclaude
# Directo, con cwd explicito:
launch_fleetclaude --cwd ~/fn_registry
# Perfil nuevo automatico (fleet la 1a vez; fleet2, fleet3, ... si ya hay uno):
launch_fleetclaude
# Sesion y ancho de pane personalizados:
launch_fleetclaude --session fleet --cols 50
# Reattach a la flota principal 'fleet' (comportamiento idempotente clasico):
launch_fleetclaude --reuse
# Perfil con nombre fijo y ancho de pane personalizado:
launch_fleetclaude --session trabajo --cols 50
```
Tras invocarlo aparece una ventana kitty titulada `FleetView` con dos panes
lado a lado: a la izquierda la TUI `fleetview`, a la derecha una sesion de
`claude --dangerously-skip-permissions`. Volver a invocarlo NO duplica la
sesion: reusa la existente y solo abre otra kitty adjunta.
Tras invocarlo aparece una ventana kitty titulada `FleetView (<perfil>)` con dos
panes lado a lado: a la izquierda la TUI `fleetview`, a la derecha una sesion de
`claude --dangerously-skip-permissions`. Cada perfil es un socket+sesion tmux
aislados con su propia flota: puedes tener varias FleetView abiertas a la vez.
Por defecto, volver a invocarlo abre un perfil NUEVO (no reusa); usa `--reuse`
o `--session <nombre>` para volver a una flota concreta.
## Cuando usarla
@@ -57,9 +64,23 @@ al retomar el trabajo en el repo `fn_registry`.
## Gotchas
- **Idempotencia tmux**: si la sesion `<session>` (default `fleet`) ya existe,
NO se recrea el layout; solo se abre una kitty nueva adjunta a la misma
sesion. Para empezar de cero: `tmux kill-session -t fleet` antes de invocar.
- **Perfiles multiples (default = perfil nuevo)**: sin `--session` ni `--reuse`,
cada invocacion abre un perfil NUEVO usando el primer nombre libre de la
secuencia `fleet`, `fleet2`, `fleet3`, ... (socket+sesion tmux comparten el
nombre del perfil). Asi puedes tener varias FleetView abiertas a la vez, cada
una con su flota independiente. Un perfil cerrado libera su nombre: tras matar
`fleet`, el siguiente lanzamiento vuelve a `fleet`. Para reattach a una flota
concreta: `--reuse` (principal `fleet`) o `--session <nombre>` (idempotente
sobre ese nombre, reusa el layout si ya vive).
- **Perfil ↔ TUI por entorno**: el launcher inyecta `FLEET_SOCKET`/`FLEET_SESSION`
al pane de la TUI (y los fija en el server con `set-environment -g`, para que
`respawn-pane` de alt+R y los Claude nuevos hereden el socket). `main.go` los
lee con fallback a `fleet`. Por eso cada panel ve SOLO los Claude de su perfil
(cruza la lista del sistema con los panes de su socket).
- **Dentro de tmux abre ventana nueva**: si invocas `fleetclaude` desde dentro de
una sesion tmux (`$TMUX` definido), NO hace `attach` anidado (rompe / avisa de
nesting); cae a la ruta kitty y abre una ventana nueva. Fuera de tmux y con
TTY, reutiliza la terminal actual con `exec tmux attach`.
- **kitty detached (setsid)**: la ventana se lanza con `setsid ... &` para
sobrevivir al cierre de la terminal que la invoco. No bloquea al shell padre.
- **`exec` en los panes**: tanto la TUI como `claude` se lanzan con `exec`, asi
@@ -70,10 +91,11 @@ al retomar el trabajo en el repo `fn_registry`.
`<repo>/apps/fleetview/fleetview`. Si ese binario no existe, el pane izquierdo
muestra `cd apps/fleetview && go build -o fleetview .` en lugar de fallar en
silencio. Compila la TUI antes para el flujo completo.
- **Socket tmux aislado (`-L fleet`)**: toda la sesion vive en un server tmux
propio, separado del tmux por defecto del usuario. Asi los atajos `bind -n`
NO afectan otras sesiones (ej. una sesion `mobile-1` del movil) y matar el
server fleet no toca nada mas: `tmux -L fleet kill-server`.
- **Socket tmux aislado por perfil (`-L <perfil>`)**: cada perfil vive en su
propio server tmux (socket = nombre del perfil), separado del tmux por defecto
del usuario y de los demas perfiles. Asi los atajos `bind -n` NO afectan otras
sesiones (ej. una sesion `mobile-1` del movil) y matar un perfil no toca los
otros: `tmux -L <perfil> kill-server` (o `alt+q` dentro de la TUI).
- **Atajos en el socket, NO en kitty.conf**: instala `bind -n` para
`alt+flechas` (mover el cursor de la TUI), `alt+enter` (conmutar al Claude
seleccionado) y `alt+n` (abrir Claude nuevo). Son bindings de tmux que
@@ -91,6 +113,15 @@ al retomar el trabajo en el repo `fn_registry`.
## Capability growth log
- v1.4.0 (2026-06-18) — **perfiles multiples**. Socket+sesion tmux ya no son el
fijo `fleet`: cada perfil tiene los suyos (mismo nombre). Sin `--session`/
`--reuse`, cada invocacion abre el primer perfil libre (`fleet`, `fleet2`, ...),
asi abrir FleetView con uno ya abierto arranca otra flota en vez de reusarla.
Nuevo flag `--reuse` para el reattach idempotente clasico. El launcher inyecta
`FLEET_SOCKET`/`FLEET_SESSION` (env + `set-environment -g`) y `main.go` de
`fleetview` los lee (fallback `fleet`), de modo que cada panel ve solo su flota.
Titulo de kitty `FleetView (<perfil>)`. Guard anti-nesting: invocado dentro de
tmux abre ventana kitty nueva en vez de `attach` anidado.
- v1.3.2 (2026-06-17) — targeting de panes por **pane ID** (`%0`/`%1`) en vez de
por indice (`console.0`). Antes fallaba con `can't find pane: 0` en hosts cuyo
`~/.tmux.conf` define `base-index 1`/`pane-base-index 1` (el socket `-L fleet`
+71 -17
View File
@@ -21,7 +21,9 @@ launch_fleetclaude() {
local bin=""
local session="fleet"
local cols=52
local T="tmux -L fleet" # socket tmux aislado: no toca el tmux normal del usuario
local explicit_session=0 # 1 si el usuario pasó --session <name> a mano
local reuse=0 # 1 si el usuario pidió --reuse (reattach al perfil principal)
local T="" # socket tmux aislado; se fija al resolver el perfil
# -----------------------------------------------------------------------
# Parseo de argumentos
@@ -39,6 +41,10 @@ launch_fleetclaude() {
--session)
shift
session="${1:-}"
explicit_session=1
;;
--reuse)
reuse=1
;;
--cols)
shift
@@ -51,19 +57,28 @@ Uso: launch_fleetclaude [opciones]
Abre una ventana kitty con una sesion tmux de dos panes: la TUI fleetview a la
izquierda y 'claude --dangerously-skip-permissions' a la derecha.
Cada PERFIL de FleetView es un socket+sesion tmux aislados (su propia flota de
Claudes). Sin --session ni --reuse, cada invocacion abre un perfil NUEVO: usa
el primer nombre libre de la secuencia fleet, fleet2, fleet3, ... Asi puedes
tener varias FleetView abiertas a la vez, cada una con su flota independiente.
Opciones:
--cwd <dir> Directorio de trabajo de los panes.
Default: raiz del repo fn_registry (derivada dinamicamente).
--bin <path> Ruta al binario de la TUI fleetview.
Default: <repo>/apps/fleetview/fleetview
--session <name> Nombre de la sesion tmux. Default: fleet.
--session <name> Fija el perfil (socket+sesion) por nombre exacto; reutiliza
el existente si ya esta vivo. Sin esta opcion, perfil auto.
--reuse Reattach al perfil principal 'fleet' en vez de abrir uno
nuevo (vuelve al comportamiento idempotente clasico).
--cols <n> Ancho (columnas) del pane izquierdo. Default: 40.
-h, --help Muestra esta ayuda.
Ejemplos:
launch_fleetclaude
launch_fleetclaude --cwd ~/fn_registry
launch_fleetclaude --session fleet --cols 50
launch_fleetclaude # perfil nuevo (fleet, luego fleet2, ...)
launch_fleetclaude --reuse # reattach a la flota principal 'fleet'
launch_fleetclaude --session trabajo # perfil con nombre fijo 'trabajo'
launch_fleetclaude --cwd ~/fn_registry --cols 50
USAGE
return 0
;;
@@ -111,6 +126,34 @@ USAGE
echo "launch_fleetclaude: tmux no esta instalado." >&2
return 1
fi
# -----------------------------------------------------------------------
# Resolver el PERFIL (socket+sesion tmux comparten nombre).
#
# - --session <name> -> usa ese nombre exacto (reutiliza si ya vive).
# - --reuse -> usa 'fleet' (el perfil principal), idempotente.
# - sin nada -> perfil NUEVO: primer nombre libre de la secuencia
# fleet, fleet2, fleet3, ... Asi abrir FleetView con
# uno ya abierto arranca otra flota, no la reusa.
#
# "Libre" = no hay un server tmux con esa sesion (has-session falla). Un
# perfil cerrado libera su nombre, asi que tras cerrar 'fleet' el siguiente
# lanzamiento vuelve a 'fleet'.
# -----------------------------------------------------------------------
if [[ "$explicit_session" -eq 0 && "$reuse" -eq 0 ]]; then
local base="$session" n=1 cand
while :; do
if [[ "$n" -eq 1 ]]; then cand="$base"; else cand="${base}${n}"; fi
if ! tmux -L "$cand" has-session -t "$cand" 2>/dev/null; then
session="$cand"
break
fi
n=$((n + 1))
done
echo "launch_fleetclaude: perfil nuevo '$session'."
fi
# A partir de aqui el socket aislado es el del perfil resuelto.
T="tmux -L $session"
# Nota: kitty NO se exige aqui. La ruta interactiva (TTY) reutiliza la
# terminal actual con `exec tmux attach` y no necesita kitty. Solo la
# ruta sin-TTY (abrir ventana nueva con setsid kitty) lo requiere, y ahi
@@ -121,9 +164,13 @@ USAGE
# - Si el binario fleetview existe -> ejecutarlo (exec, sin shell colgado).
# - Si NO existe -> mensaje claro + shell interactiva (no falla en silencio).
# -----------------------------------------------------------------------
# La TUI necesita saber a qué perfil pertenece: se lo pasamos por entorno
# (FLEET_SOCKET/FLEET_SESSION), que main.go lee con fallback a "fleet".
local envpfx
envpfx="FLEET_SOCKET=$(printf '%q' "$session") FLEET_SESSION=$(printf '%q' "$session")"
local left_cmd
if [[ -x "$bin" ]]; then
left_cmd="exec $(printf '%q' "$bin")"
left_cmd="$envpfx exec $(printf '%q' "$bin")"
else
# Fallback claro: instruye como compilar la TUI y deja una shell viva.
left_cmd="echo 'fleetview no compilado: cd apps/fleetview && go build -o fleetview .'; exec \"\$SHELL\""
@@ -181,8 +228,14 @@ USAGE
$T bind -n M-r send-keys -t "$left_pane" r
$T bind -n M-u send-keys -t "$left_pane" u
$T bind -n M-h send-keys -t "$left_pane" h
$T bind -n M-R send-keys -t "$left_pane" R
$T bind -n M-Left send-keys -t "$left_pane" Escape
$T bind -n M-q send-keys -t "$left_pane" Q
# Entorno del perfil en el server tmux: respawn-pane (alt+R, recompila la TUI)
# y los Claude nuevos heredan FLEET_SOCKET/FLEET_SESSION para apuntar al
# socket correcto aunque no sea el default "fleet".
$T set-environment -g FLEET_SOCKET "$session"
$T set-environment -g FLEET_SESSION "$session"
# Raton: enruta clicks/rueda al pane bajo el cursor; la TUI los interpreta.
$T set -g mouse on
# Al salir un Claude (exit / Ctrl-D / kill), cerrar su window en vez de
@@ -207,24 +260,25 @@ USAGE
# (Mismo patron que reboot_all_claudes para relanzar terminales.)
# -----------------------------------------------------------------------
# Adjuntar la sesion:
# - Si se invoca desde una terminal interactiva, convertir ESA terminal en
# el panel FleetView (exec reemplaza el proceso; al hacer detach vuelve la
# - Terminal interactiva y FUERA de tmux: convertir ESA terminal en el
# panel FleetView (exec reemplaza el proceso; al hacer detach vuelve la
# shell). Asi `fleetclaude` no abre otra ventana: usa la actual.
# - Si NO hay TTY (atajo de escritorio, cron, script), abrir una ventana
# kitty nueva desacoplada (setsid) como antes.
if [ -t 0 ] && [ -t 1 ]; then
exec tmux -L fleet attach -t "$session"
# - DENTRO de tmux (o sin TTY: atajo de escritorio, cron, script): abrir
# una ventana kitty nueva desacoplada (setsid). No hacemos `attach`
# anidado dentro de otra sesion tmux (rompe / da el warning de nesting).
if [ -t 0 ] && [ -t 1 ] && [ -z "${TMUX:-}" ]; then
exec tmux -L "$session" attach -t "$session"
fi
# Ruta sin-TTY: necesitamos kitty para abrir la ventana nueva.
# Ruta ventana-nueva: necesitamos kitty para abrirla.
if ! command -v kitty >/dev/null 2>&1; then
echo "launch_fleetclaude: kitty no esta instalado (necesario solo sin TTY)." >&2
echo "launch_fleetclaude: lanzalo desde una terminal interactiva, o instala kitty." >&2
echo "launch_fleetclaude: kitty no esta instalado (necesario para abrir ventana nueva)." >&2
echo "launch_fleetclaude: lanzalo desde una terminal interactiva fuera de tmux, o instala kitty." >&2
return 1
fi
setsid kitty --title "FleetView" -e tmux -L fleet attach -t "$session" </dev/null >/dev/null 2>&1 &
setsid kitty --title "FleetView ($session)" -e tmux -L "$session" attach -t "$session" </dev/null >/dev/null 2>&1 &
disown 2>/dev/null || true
echo "launch_fleetclaude: ventana kitty 'FleetView' adjunta a la sesion tmux '$session'."
echo "launch_fleetclaude: ventana kitty 'FleetView ($session)' adjunta al perfil '$session'."
return 0
}
@@ -0,0 +1,55 @@
---
name: open_doc_onlyoffice
kind: function
lang: bash
domain: infra
version: 1.0.0
purity: impure
signature: "open_doc_onlyoffice <ruta_archivo> [--restart]"
description: "Abre un documento ofimático (xlsx, docx, pptx, csv, ods, odt, ...) con OnlyOffice Desktop Editors, desacoplado del shell (setsid + background). Localiza el binario por PATH sin hardcodear rutas. Flag --restart cierra toda la app OnlyOffice y la relanza para forzar la recarga desde disco de un archivo regenerado (OnlyOffice cachea en memoria la versión vieja de los documentos abiertos)."
tags:
- onlyoffice
- desktop
- office
- open
uses_functions: []
uses_types: []
returns: []
error_type: error_go_core
params:
- name: ruta_archivo
desc: "Ruta (relativa o absoluta) del documento ofimático a abrir. Debe existir."
- name: --restart
desc: "Opcional. Si se pasa, cierra TODA la instancia de OnlyOffice (pkill -x DesktopEditors) antes de relanzar, forzando la recarga desde disco. Cierra cualquier otro documento abierto: usar solo si ninguno tiene cambios sin guardar."
output: "Imprime la ruta absoluta abierta. Exit 0 si lanza OnlyOffice; exit 1 si el archivo no existe o el binario no está en PATH; exit 2 en error de uso."
file_path: bash/functions/infra/open_doc_onlyoffice.sh
---
## Ejemplo
```bash
# Abrir un documento (lo enfoca si OnlyOffice ya está corriendo)
fn run open_doc_onlyoffice ~/Desktop/negocio_dashboards.xlsx
# Tras regenerar el archivo en disco, forzar que OnlyOffice lo recargue
fn run open_doc_onlyoffice ~/Desktop/negocio_dashboards.xlsx --restart
```
## Cuando usarla
Cuando necesites abrir o mostrar al usuario un documento ofimático (`.xlsx`, `.docx`, `.pptx`, `.csv`, `.ods`, `.odt`) en su escritorio. Es la forma canónica de abrir documentos en este equipo: el usuario usa OnlyOffice, nunca LibreOffice. Usa `--restart` cuando acabas de regenerar un archivo que probablemente ya está abierto y OnlyOffice muestra la versión cacheada en memoria.
## Gotchas
- OnlyOffice es **instancia única**: lanzarlo con un archivo ya abierto reenfoca la pestaña existente con la versión cacheada en memoria, NO recarga desde disco. Por eso existe `--restart`.
- `--restart` cierra **toda** la app (`pkill -x DesktopEditors`), no solo la pestaña del archivo. Cualquier otro documento abierto se cierra. No usar si hay documentos con cambios sin guardar.
- No hay forma por CLI de cerrar/recargar una sola pestaña: o se acepta la versión cacheada, o se reinicia la app entera.
- Usa `setsid` + `&` para que el editor sobreviva al proceso que lo invoca (no muere al cerrar la terminal/sesión).
- Localiza el binario con `command -v onlyoffice-desktopeditors`; el proceso real subyacente es `/opt/onlyoffice/desktopeditors/DesktopEditors`.
## example
```bash
open_doc_onlyoffice ~/Desktop/negocio_dashboards.xlsx
open_doc_onlyoffice ~/Desktop/negocio_dashboards.xlsx --restart # fuerza recarga desde disco
```
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# open_doc_onlyoffice — abre un documento ofimático con OnlyOffice Desktop Editors.
#
# Uso:
# open_doc_onlyoffice <ruta_archivo> [--restart]
#
# Lanza el editor desacoplado del shell (setsid + background) para que sobreviva
# al proceso que lo invoca. Localiza el binario por PATH, sin hardcodear rutas.
#
# --restart cierra toda la instancia de OnlyOffice antes de relanzar, para forzar
# la recarga desde disco de un archivo que se regeneró (OnlyOffice mantiene en
# memoria la versión vieja de los documentos ya abiertos).
set -euo pipefail
usage() {
echo "uso: open_doc_onlyoffice <ruta_archivo> [--restart]" >&2
exit 2
}
[ $# -ge 1 ] || usage
doc=""
restart=0
for arg in "$@"; do
case "$arg" in
--restart) restart=1 ;;
-h|--help) usage ;;
*) doc="$arg" ;;
esac
done
[ -n "$doc" ] || usage
if [ ! -f "$doc" ]; then
echo "error: archivo no encontrado: $doc" >&2
exit 1
fi
bin="$(command -v onlyoffice-desktopeditors || true)"
if [ -z "$bin" ]; then
echo "error: onlyoffice-desktopeditors no esta en PATH" >&2
exit 1
fi
# Ruta absoluta para que OnlyOffice no dependa del directorio de trabajo.
doc_abs="$(readlink -f "$doc")"
if [ "$restart" -eq 1 ]; then
# Cierra la app entera para descartar la copia en memoria de los documentos.
# pkill -x sobre el comm exacto del proceso real (no -f, para no auto-matar
# el propio script si su ruta contiene el patrón).
pkill -x DesktopEditors 2>/dev/null || true
# Espera (máx ~5s) a que el proceso principal termine antes de relanzar.
for _ in $(seq 1 25); do
pgrep -x DesktopEditors >/dev/null 2>&1 || break
sleep 0.2
done
fi
setsid "$bin" "$doc_abs" >/dev/null 2>&1 &
echo "abierto en OnlyOffice: $doc_abs"