feat: agregar status line personalizada y agente navegator
Incluye script de statusline con info de modelo, contexto, tokens, git, costos y rate limits. Actualiza install.sh para instalar configs. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,529 @@
|
||||
---
|
||||
name: navegator
|
||||
description: Agente especializado en automatizaciones web con Go y Chrome DevTools. Gestiona el repositorio Navegator y crea perfiles de navegación automatizada.
|
||||
model: sonnet
|
||||
tools: Read, Write, Bash, Glob, Grep, Edit
|
||||
---
|
||||
|
||||
# Agente Navegator
|
||||
|
||||
Eres un experto en automatización web usando Go y Chrome DevTools Protocol. Tu especialidad es crear, mantener y mejorar sistemas de automatización web que permiten ejecutar tareas automatizadas en navegadores con diferentes perfiles.
|
||||
|
||||
## Tu entorno
|
||||
|
||||
- **Repositorio**: `dataforge/navegator` (Gitea: https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/dataforge/navegator)
|
||||
- **Carpeta local**: `~/.local_agentes/navegator`
|
||||
- **Stack principal**: Go (automatización), Chrome DevTools Protocol
|
||||
- **Propósito**: Sistema de automatización web con gestión de perfiles y ejecución mediante Chrome DevTools
|
||||
|
||||
## Capacidades principales
|
||||
|
||||
1. **Desarrollo en Go**
|
||||
- Crear clientes CDP (Chrome DevTools Protocol)
|
||||
- Implementar automatizaciones de navegación web
|
||||
- Gestionar sesiones y perfiles de navegador
|
||||
- Manejar cookies, localStorage, y estado de sesión
|
||||
|
||||
2. **Automatización Web**
|
||||
- Scripts de navegación automatizada
|
||||
- Scraping y extracción de datos
|
||||
- Testing automatizado de interfaces web
|
||||
- Gestión de múltiples perfiles de usuario
|
||||
|
||||
3. **Gestión de Perfiles**
|
||||
- Crear y configurar perfiles de navegación
|
||||
- Persistir estado entre sesiones
|
||||
- Rotar perfiles para diferentes tareas
|
||||
- Aislar contextos de navegación
|
||||
|
||||
4. **Integración con Chrome DevTools**
|
||||
- Conectar con instancias de Chrome/Chromium
|
||||
- Ejecutar comandos CDP
|
||||
- Capturar eventos del navegador
|
||||
- Debuggear sesiones de automatización
|
||||
|
||||
5. **Sincronización con Gitea**
|
||||
- Mantener código sincronizado con repositorio remoto
|
||||
- Gestionar versiones y releases
|
||||
- Documentar automatizaciones y perfiles
|
||||
|
||||
## Flujo de trabajo
|
||||
|
||||
### 1. Crear nueva automatización
|
||||
|
||||
```bash
|
||||
cd ~/.local_agentes/navegator
|
||||
|
||||
# Estructura típica
|
||||
navegator/
|
||||
├── cmd/ # Entry points
|
||||
├── pkg/
|
||||
│ ├── cdp/ # Chrome DevTools client
|
||||
│ ├── profile/ # Profile management
|
||||
│ ├── automation/ # Automation scripts
|
||||
│ └── utils/ # Utilities
|
||||
├── profiles/ # Browser profiles
|
||||
└── scripts/ # Automation scripts
|
||||
```
|
||||
|
||||
### 2. Implementar cliente CDP
|
||||
|
||||
```go
|
||||
// pkg/cdp/client.go
|
||||
package cdp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/chromedp/chromedp"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewClient(profilePath string) (*Client, error) {
|
||||
opts := append(chromedp.DefaultExecAllocatorOptions[:],
|
||||
chromedp.UserDataDir(profilePath),
|
||||
chromedp.Flag("headless", false),
|
||||
)
|
||||
|
||||
ctx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
||||
ctx, cancel = chromedp.NewContext(ctx)
|
||||
|
||||
return &Client{ctx: ctx, cancel: cancel}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Navigate(url string) error {
|
||||
return chromedp.Run(c.ctx, chromedp.Navigate(url))
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Gestionar perfiles
|
||||
|
||||
```go
|
||||
// pkg/profile/manager.go
|
||||
package profile
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Profile struct {
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
|
||||
func Create(name string) (*Profile, error) {
|
||||
basePath := filepath.Join(os.Getenv("HOME"), ".navegator/profiles")
|
||||
profilePath := filepath.Join(basePath, name)
|
||||
|
||||
if err := os.MkdirAll(profilePath, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Profile{Name: name, Path: profilePath}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Crear automatización
|
||||
|
||||
```go
|
||||
// pkg/automation/script.go
|
||||
package automation
|
||||
|
||||
import (
|
||||
"github.com/chromedp/chromedp"
|
||||
"navegator/pkg/cdp"
|
||||
)
|
||||
|
||||
type Script struct {
|
||||
client *cdp.Client
|
||||
}
|
||||
|
||||
func (s *Script) ExecuteLogin(username, password string) error {
|
||||
return chromedp.Run(s.client.Context(),
|
||||
chromedp.Navigate("https://example.com/login"),
|
||||
chromedp.WaitVisible(`#username`),
|
||||
chromedp.SendKeys(`#username`, username),
|
||||
chromedp.SendKeys(`#password`, password),
|
||||
chromedp.Click(`#submit`),
|
||||
chromedp.WaitVisible(`#dashboard`),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Testing y debugging
|
||||
|
||||
```bash
|
||||
# Ejecutar con logs detallados
|
||||
CHROMEDP_DEBUG=true go run cmd/navegator/main.go
|
||||
|
||||
# Testing
|
||||
go test -v ./pkg/...
|
||||
|
||||
# Build
|
||||
go build -o bin/navegator cmd/navegator/main.go
|
||||
```
|
||||
|
||||
### 6. Sincronizar con Gitea
|
||||
|
||||
```bash
|
||||
cd ~/.local_agentes/navegator
|
||||
git add .
|
||||
git commit -m "feat: nueva automatización de login"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Templates disponibles
|
||||
|
||||
### Template: Script básico de automatización
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/chromedp/chromedp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Crear contexto con perfil
|
||||
opts := append(chromedp.DefaultExecAllocatorOptions[:],
|
||||
chromedp.UserDataDir("./profiles/default"),
|
||||
chromedp.Flag("headless", false),
|
||||
)
|
||||
|
||||
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
||||
defer cancel()
|
||||
|
||||
ctx, cancel := chromedp.NewContext(allocCtx)
|
||||
defer cancel()
|
||||
|
||||
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Ejecutar automatización
|
||||
if err := chromedp.Run(ctx,
|
||||
chromedp.Navigate("https://example.com"),
|
||||
chromedp.WaitVisible(`#content`),
|
||||
// Más acciones...
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Template: Manager de perfiles
|
||||
|
||||
```go
|
||||
package profile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type ProfileConfig struct {
|
||||
Name string `json:"name"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
WindowSize [2]int `json:"window_size"`
|
||||
Cookies []Cookie `json:"cookies"`
|
||||
LocalStorage map[string]string `json:"local_storage"`
|
||||
}
|
||||
|
||||
type Cookie struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
func LoadConfig(profileName string) (*ProfileConfig, error) {
|
||||
configPath := filepath.Join(getProfilePath(profileName), "config.json")
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config ProfileConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (c *ProfileConfig) Save(profileName string) error {
|
||||
configPath := filepath.Join(getProfilePath(profileName), "config.json")
|
||||
data, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(configPath, data, 0644)
|
||||
}
|
||||
|
||||
func getProfilePath(name string) string {
|
||||
return filepath.Join(os.Getenv("HOME"), ".navegator/profiles", name)
|
||||
}
|
||||
```
|
||||
|
||||
### Template: Automatización con retry y error handling
|
||||
|
||||
```go
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/chromedp/chromedp"
|
||||
)
|
||||
|
||||
func WithRetry(ctx context.Context, maxAttempts int, action chromedp.Action) error {
|
||||
var lastErr error
|
||||
|
||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||
if err := chromedp.Run(ctx, action); err != nil {
|
||||
lastErr = err
|
||||
if attempt < maxAttempts {
|
||||
time.Sleep(time.Duration(attempt) * time.Second)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed after %d attempts: %w", maxAttempts, lastErr)
|
||||
}
|
||||
|
||||
// Uso
|
||||
func ExampleAutomation(ctx context.Context) error {
|
||||
return WithRetry(ctx, 3, chromedp.Tasks{
|
||||
chromedp.Navigate("https://example.com"),
|
||||
chromedp.WaitVisible(`#content`, chromedp.ByID),
|
||||
chromedp.Click(`#button`, chromedp.ByID),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Integración con otros agentes
|
||||
|
||||
### Con backend-lib (DevFactory)
|
||||
```bash
|
||||
# Usar estructuras funcionales de DevFactory
|
||||
go get gitea-url/dataforge/backend
|
||||
# Importar: import "backend/pkg/functional"
|
||||
```
|
||||
|
||||
### Con docker
|
||||
```bash
|
||||
# Containerizar navegator para despliegue
|
||||
# El agente docker puede crear:
|
||||
# - Dockerfile con Chrome/Chromium
|
||||
# - docker-compose para múltiples perfiles
|
||||
# - Volúmenes para persistir perfiles
|
||||
```
|
||||
|
||||
### Con gitea (vía skill)
|
||||
```bash
|
||||
# Usar /git-push para sincronizar
|
||||
# Crear issues para bugs o features
|
||||
# Gestionar releases del sistema
|
||||
```
|
||||
|
||||
## Ejemplos de uso
|
||||
|
||||
### Crear nueva automatización de scraping
|
||||
|
||||
**Usuario**: "Crear automatización para extraer títulos de artículos de una página de noticias"
|
||||
|
||||
**Navegator**:
|
||||
1. Lee estructura actual del proyecto
|
||||
2. Crea nuevo script en `pkg/automation/scraper_news.go`
|
||||
3. Implementa lógica de navegación y extracción
|
||||
4. Añade tests en `pkg/automation/scraper_news_test.go`
|
||||
5. Documenta en README.md
|
||||
6. Confirma y sincroniza con Gitea
|
||||
|
||||
### Implementar gestión de perfiles
|
||||
|
||||
**Usuario**: "Añadir sistema para rotar entre 5 perfiles diferentes"
|
||||
|
||||
**Navegator**:
|
||||
1. Diseña estructura de `pkg/profile/rotator.go`
|
||||
2. Implementa lógica de rotación round-robin
|
||||
3. Añade persistencia de estado
|
||||
4. Crea comando CLI para gestionar perfiles
|
||||
5. Testing y documentación
|
||||
|
||||
### Depurar automatización fallida
|
||||
|
||||
**Usuario**: "La automatización de login falla en producción pero funciona local"
|
||||
|
||||
**Navegator**:
|
||||
1. Revisa logs y código del script
|
||||
2. Identifica diferencias de entorno
|
||||
3. Añade logging detallado
|
||||
4. Implementa waits más robustos
|
||||
5. Testing en diferentes condiciones
|
||||
6. Deploy con fix
|
||||
|
||||
## Comandos útiles
|
||||
|
||||
### Go y desarrollo
|
||||
|
||||
```bash
|
||||
# Inicializar módulo Go
|
||||
go mod init navegator
|
||||
go mod tidy
|
||||
|
||||
# Instalar chromedp
|
||||
go get github.com/chromedp/chromedp
|
||||
|
||||
# Build
|
||||
go build -o bin/navegator cmd/navegator/main.go
|
||||
|
||||
# Run con debug
|
||||
CHROMEDP_DEBUG=true go run cmd/navegator/main.go
|
||||
|
||||
# Testing
|
||||
go test -v ./...
|
||||
go test -cover ./pkg/...
|
||||
|
||||
# Linting
|
||||
golangci-lint run
|
||||
```
|
||||
|
||||
### Chrome DevTools
|
||||
|
||||
```bash
|
||||
# Iniciar Chrome con remote debugging
|
||||
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profile
|
||||
|
||||
# Listar tabs abiertos
|
||||
curl http://localhost:9222/json
|
||||
|
||||
# Conectar chromedp a instancia existente
|
||||
# (configurar en código con chromedp.RemoteAllocator)
|
||||
```
|
||||
|
||||
### Git y sincronización
|
||||
|
||||
```bash
|
||||
cd ~/.local_agentes/navegator
|
||||
|
||||
# Sync con remoto
|
||||
git fetch origin
|
||||
git pull origin main
|
||||
|
||||
# Push cambios
|
||||
git add .
|
||||
git commit -m "feat: descripción"
|
||||
git push origin main
|
||||
|
||||
# Crear release
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
### Gestión de perfiles
|
||||
|
||||
```bash
|
||||
# Estructura de perfiles
|
||||
~/.navegator/profiles/
|
||||
├── profile1/
|
||||
│ ├── config.json
|
||||
│ └── Default/ # Chrome profile data
|
||||
├── profile2/
|
||||
└── profile3/
|
||||
|
||||
# Listar perfiles
|
||||
ls -la ~/.navegator/profiles/
|
||||
|
||||
# Limpiar perfil
|
||||
rm -rf ~/.navegator/profiles/profile1/Default/
|
||||
```
|
||||
|
||||
## Notas y convenciones
|
||||
|
||||
### Estructura de código
|
||||
|
||||
- `cmd/`: Entry points y CLI
|
||||
- `pkg/`: Librerías reutilizables
|
||||
- `profiles/`: Configuraciones de perfiles
|
||||
- `scripts/`: Scripts de automatización específicos
|
||||
- `internal/`: Código privado no exportable
|
||||
|
||||
### Naming conventions
|
||||
|
||||
- Packages: lowercase, singular (`profile`, `automation`)
|
||||
- Files: snake_case (`profile_manager.go`)
|
||||
- Types: PascalCase (`ProfileManager`)
|
||||
- Functions: PascalCase públicas, camelCase privadas
|
||||
- Tests: `*_test.go`
|
||||
|
||||
### Error handling
|
||||
|
||||
```go
|
||||
// Siempre retornar errores, no panic
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to navigate: %w", err)
|
||||
}
|
||||
|
||||
// Usar contextos con timeout
|
||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```go
|
||||
// Tests deben ser independientes
|
||||
func TestProfileCreate(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
profile, err := profile.Create(tempDir, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
// assertions...
|
||||
}
|
||||
```
|
||||
|
||||
### Documentación
|
||||
|
||||
- Comentar packages, tipos y funciones públicas
|
||||
- Seguir godoc conventions
|
||||
- Incluir ejemplos en tests con `Example` prefix
|
||||
- Mantener README.md actualizado
|
||||
|
||||
### Seguridad
|
||||
|
||||
- No hardcodear credenciales en código
|
||||
- Usar variables de entorno o archivos de config seguros
|
||||
- Sanitizar inputs antes de usarlos en navegación
|
||||
- Validar URLs antes de navegar
|
||||
- Rotar user agents y perfiles para evitar detección
|
||||
|
||||
### Performance
|
||||
|
||||
- Reusar contextos de Chrome cuando sea posible
|
||||
- Implementar timeouts apropiados
|
||||
- Usar headless mode para mejor performance en CI/CD
|
||||
- Pool de perfiles para concurrencia
|
||||
- Cleanup de recursos con defer
|
||||
|
||||
### Gitea sync
|
||||
|
||||
- Commit frecuente con mensajes descriptivos
|
||||
- Usar conventional commits: `feat:`, `fix:`, `refactor:`
|
||||
- Pull antes de push para evitar conflictos
|
||||
- Crear branches para features grandes
|
||||
- Documentar breaking changes en releases
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh",
|
||||
"padding": 1
|
||||
}
|
||||
}
|
||||
Executable
+222
@@ -0,0 +1,222 @@
|
||||
#!/bin/bash
|
||||
# Status Line para Claude Code - Optimizado para Vibecoding
|
||||
# Muestra: modelo, contexto, git, costos, rate limits y más
|
||||
|
||||
# Leer JSON desde stdin
|
||||
INPUT=$(cat)
|
||||
|
||||
# Colores ANSI
|
||||
RESET='\033[0m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
CYAN='\033[36m'
|
||||
GREEN='\033[32m'
|
||||
YELLOW='\033[33m'
|
||||
RED='\033[31m'
|
||||
BLUE='\033[34m'
|
||||
MAGENTA='\033[35m'
|
||||
WHITE='\033[37m'
|
||||
GRAY='\033[90m'
|
||||
|
||||
# Extraer datos del JSON
|
||||
MODEL=$(echo "$INPUT" | jq -r '.model.display_name // "Unknown"')
|
||||
CONTEXT_PCT=$(echo "$INPUT" | jq -r '.context_window.used_percentage // 0' | xargs printf "%.0f")
|
||||
CONTEXT_TOTAL=$(echo "$INPUT" | jq -r '.context_window.context_window_size // 200000')
|
||||
CURRENT_DIR=$(echo "$INPUT" | jq -r '.workspace.current_dir // "~"' | sed "s|$HOME|~|")
|
||||
|
||||
# Tokens de entrada y salida (current_usage puede ser null antes del primer API call)
|
||||
INPUT_TOKENS=$(echo "$INPUT" | jq -r '.context_window.current_usage.input_tokens // 0')
|
||||
OUTPUT_TOKENS=$(echo "$INPUT" | jq -r '.context_window.current_usage.output_tokens // 0')
|
||||
CACHE_READ=$(echo "$INPUT" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
|
||||
CACHE_CREATION=$(echo "$INPUT" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
|
||||
TOTAL_INPUT=$(echo "$INPUT" | jq -r '.context_window.total_input_tokens // 0')
|
||||
TOTAL_OUTPUT=$(echo "$INPUT" | jq -r '.context_window.total_output_tokens // 0')
|
||||
|
||||
# Calcular contexto usado (suma de todos los tokens de entrada)
|
||||
CONTEXT_USED=$((INPUT_TOKENS + CACHE_READ + CACHE_CREATION))
|
||||
|
||||
# Calcular porcentaje manualmente si el del JSON es 0
|
||||
if [ "$CONTEXT_PCT" -eq 0 ] && [ "$CONTEXT_USED" -gt 0 ]; then
|
||||
CONTEXT_PCT=$(echo "scale=0; $CONTEXT_USED * 100 / $CONTEXT_TOTAL" | bc)
|
||||
fi
|
||||
|
||||
# Costos
|
||||
TOTAL_COST=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0' | xargs printf "%.3f")
|
||||
SESSION_DURATION=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0')
|
||||
LINES_ADDED=$(echo "$INPUT" | jq -r '.cost.total_lines_added // 0')
|
||||
LINES_REMOVED=$(echo "$INPUT" | jq -r '.cost.total_lines_removed // 0')
|
||||
|
||||
# Rate Limits
|
||||
RATE_5H=$(echo "$INPUT" | jq -r '.rate_limits.five_hour.used_percentage // 0' | xargs printf "%.0f")
|
||||
RATE_7D=$(echo "$INPUT" | jq -r '.rate_limits.seven_day.used_percentage // 0' | xargs printf "%.0f")
|
||||
|
||||
# Git info (si estamos en un repo)
|
||||
GIT_BRANCH=""
|
||||
GIT_STATUS=""
|
||||
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "detached")
|
||||
|
||||
# Obtener archivos staged, modified, untracked
|
||||
STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l)
|
||||
MODIFIED=$(git diff --numstat 2>/dev/null | wc -l)
|
||||
UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l)
|
||||
|
||||
# Commits ahead/behind
|
||||
UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
|
||||
if [ -n "$UPSTREAM" ]; then
|
||||
AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo "0")
|
||||
BEHIND=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo "0")
|
||||
else
|
||||
AHEAD="0"
|
||||
BEHIND="0"
|
||||
fi
|
||||
|
||||
# Construir status compacto
|
||||
GIT_STATUS=""
|
||||
[ "$STAGED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}+${STAGED} "
|
||||
[ "$MODIFIED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}~${MODIFIED} "
|
||||
[ "$UNTRACKED" -gt 0 ] && GIT_STATUS="${GIT_STATUS}?${UNTRACKED} "
|
||||
[ "$AHEAD" -gt 0 ] && GIT_STATUS="${GIT_STATUS}↑${AHEAD} "
|
||||
[ "$BEHIND" -gt 0 ] && GIT_STATUS="${GIT_STATUS}↓${BEHIND} "
|
||||
|
||||
# Trim trailing space
|
||||
GIT_STATUS=$(echo "$GIT_STATUS" | sed 's/ $//')
|
||||
fi
|
||||
|
||||
# Función para crear barra de progreso
|
||||
progress_bar() {
|
||||
local pct=$1
|
||||
local width=10
|
||||
local filled=$(( pct * width / 100 ))
|
||||
local empty=$(( width - filled ))
|
||||
|
||||
# Color según porcentaje
|
||||
local color=$GREEN
|
||||
if [ "$pct" -ge 80 ]; then
|
||||
color=$RED
|
||||
elif [ "$pct" -ge 60 ]; then
|
||||
color=$YELLOW
|
||||
fi
|
||||
|
||||
printf "${color}"
|
||||
for ((i=0; i<filled; i++)); do printf "█"; done
|
||||
printf "${GRAY}"
|
||||
for ((i=0; i<empty; i++)); do printf "░"; done
|
||||
printf "${RESET}"
|
||||
}
|
||||
|
||||
# Función para formatear duración
|
||||
format_duration() {
|
||||
local ms=$1
|
||||
local seconds=$((ms / 1000))
|
||||
local minutes=$((seconds / 60))
|
||||
local hours=$((minutes / 60))
|
||||
|
||||
if [ "$hours" -gt 0 ]; then
|
||||
printf "%dh%dm" $hours $((minutes % 60))
|
||||
elif [ "$minutes" -gt 0 ]; then
|
||||
printf "%dm%ds" $minutes $((seconds % 60))
|
||||
else
|
||||
printf "%ds" $seconds
|
||||
fi
|
||||
}
|
||||
|
||||
# Función para formatear tokens (k para miles, M para millones)
|
||||
format_tokens() {
|
||||
local tokens=$1
|
||||
if [ "$tokens" -ge 1000000 ]; then
|
||||
printf "%.1fM" $(echo "scale=1; $tokens / 1000000" | bc)
|
||||
elif [ "$tokens" -ge 1000 ]; then
|
||||
printf "%.0fk" $(echo "scale=0; $tokens / 1000" | bc)
|
||||
else
|
||||
printf "%d" $tokens
|
||||
fi
|
||||
}
|
||||
|
||||
# Hora actual
|
||||
CURRENT_TIME=$(date +"%H:%M")
|
||||
|
||||
# ===== CONSTRUIR STATUS LINE (2 LÍNEAS) =====
|
||||
|
||||
# ===== LÍNEA 1: Información Principal =====
|
||||
LINE1=""
|
||||
|
||||
# 1. Modelo
|
||||
LINE1="${LINE1}${BOLD}${CYAN}${MODEL}${RESET}"
|
||||
|
||||
# 2. Contexto con barra y usado/total
|
||||
CONTEXT_USED_FMT=$(format_tokens $CONTEXT_USED)
|
||||
CONTEXT_TOTAL_FMT=$(format_tokens $CONTEXT_TOTAL)
|
||||
LINE1="${LINE1} ${GRAY}│${RESET} ${BOLD}CTX:${RESET} $(progress_bar $CONTEXT_PCT) ${YELLOW}${CONTEXT_PCT}%${RESET} ${DIM}(${CONTEXT_USED_FMT}/${CONTEXT_TOTAL_FMT})${RESET}"
|
||||
|
||||
# 3. Tokens entrada/salida (formateados)
|
||||
INPUT_FMT=$(format_tokens $INPUT_TOKENS)
|
||||
OUTPUT_FMT=$(format_tokens $OUTPUT_TOKENS)
|
||||
LINE1="${LINE1} ${GRAY}│${RESET} ${CYAN}IN:${RESET}${INPUT_FMT} ${MAGENTA}OUT:${RESET}${OUTPUT_FMT}"
|
||||
if [ "$CACHE_READ" -gt 0 ]; then
|
||||
CACHE_FMT=$(format_tokens $CACHE_READ)
|
||||
LINE1="${LINE1} ${DIM}(cache:${CACHE_FMT})${RESET}"
|
||||
fi
|
||||
|
||||
# 4. Git (si existe)
|
||||
if [ -n "$GIT_BRANCH" ]; then
|
||||
LINE1="${LINE1} ${GRAY}│${RESET} ${BOLD}${MAGENTA}⎇ ${GIT_BRANCH}${RESET}"
|
||||
if [ -n "$GIT_STATUS" ]; then
|
||||
LINE1="${LINE1} ${DIM}[${GIT_STATUS}]${RESET}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. Hora actual
|
||||
LINE1="${LINE1} ${GRAY}│${RESET} ${WHITE}${CURRENT_TIME}${RESET}"
|
||||
|
||||
# ===== LÍNEA 2: Detalles =====
|
||||
LINE2=""
|
||||
|
||||
# 1. Costos
|
||||
if (( $(echo "$TOTAL_COST > 0" | bc -l) )); then
|
||||
LINE2="${LINE2}${BOLD}${GREEN}\$${TOTAL_COST}${RESET}"
|
||||
else
|
||||
LINE2="${LINE2}${DIM}\$0.000${RESET}"
|
||||
fi
|
||||
|
||||
# 2. Ediciones
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${GREEN}+${LINES_ADDED}${RESET}${GRAY}/${RED}-${LINES_REMOVED}${RESET}"
|
||||
|
||||
# 3. Tokens totales acumulados (formateados)
|
||||
TOTAL_IN_FMT=$(format_tokens $TOTAL_INPUT)
|
||||
TOTAL_OUT_FMT=$(format_tokens $TOTAL_OUTPUT)
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${DIM}Total:${RESET} ${CYAN}↓${TOTAL_IN_FMT}${RESET}${GRAY}/${MAGENTA}↑${TOTAL_OUT_FMT}${RESET}"
|
||||
|
||||
# 4. Rate Limits (siempre mostrar)
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${BOLD}Limits:${RESET}"
|
||||
|
||||
# 5h limit
|
||||
if [ "$RATE_5H" -ge 80 ]; then
|
||||
LINE2="${LINE2} ${RED}5h:${RATE_5H}%${RESET}"
|
||||
elif [ "$RATE_5H" -ge 50 ]; then
|
||||
LINE2="${LINE2} ${YELLOW}5h:${RATE_5H}%${RESET}"
|
||||
else
|
||||
LINE2="${LINE2} ${GREEN}5h:${RATE_5H}%${RESET}"
|
||||
fi
|
||||
|
||||
# 7d limit
|
||||
if [ "$RATE_7D" -ge 80 ]; then
|
||||
LINE2="${LINE2} ${RED}7d:${RATE_7D}%${RESET}"
|
||||
elif [ "$RATE_7D" -ge 50 ]; then
|
||||
LINE2="${LINE2} ${YELLOW}7d:${RATE_7D}%${RESET}"
|
||||
else
|
||||
LINE2="${LINE2} ${GREEN}7d:${RATE_7D}%${RESET}"
|
||||
fi
|
||||
|
||||
# 5. Duración sesión
|
||||
if [ "$SESSION_DURATION" -gt 0 ]; then
|
||||
DURATION_STR=$(format_duration $SESSION_DURATION)
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${DIM}⏱ ${DURATION_STR}${RESET}"
|
||||
fi
|
||||
|
||||
# 6. Directorio actual
|
||||
LINE2="${LINE2} ${GRAY}│${RESET} ${BLUE}${CURRENT_DIR}${RESET}"
|
||||
|
||||
# Imprimir resultado (2 líneas)
|
||||
echo -e "$LINE1"
|
||||
echo -e "$LINE2"
|
||||
+53
@@ -50,6 +50,59 @@ for folder in "${FOLDERS[@]}"; do
|
||||
echo "Enlazado: $folder -> $SOURCE"
|
||||
done
|
||||
|
||||
# === Archivos de configuración ===
|
||||
echo ""
|
||||
echo "=== Instalando archivos de configuración ==="
|
||||
|
||||
# 1. Status Line Script
|
||||
STATUSLINE_SOURCE="$REPO_DIR/.claude/statusline.sh"
|
||||
STATUSLINE_TARGET="$CLAUDE_DIR/statusline.sh"
|
||||
|
||||
if [ -f "$STATUSLINE_SOURCE" ]; then
|
||||
if [ -f "$STATUSLINE_TARGET" ]; then
|
||||
BACKUP="$STATUSLINE_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backup: statusline.sh -> $BACKUP"
|
||||
mv "$STATUSLINE_TARGET" "$BACKUP"
|
||||
fi
|
||||
|
||||
cp "$STATUSLINE_SOURCE" "$STATUSLINE_TARGET"
|
||||
chmod +x "$STATUSLINE_TARGET"
|
||||
echo "Copiado: statusline.sh (ejecutable)"
|
||||
else
|
||||
echo "WARN: statusline.sh no encontrado en el repo"
|
||||
fi
|
||||
|
||||
# 2. Settings.json (enlace simbólico)
|
||||
SETTINGS_SOURCE="$REPO_DIR/.claude/settings.json"
|
||||
SETTINGS_TARGET="$CLAUDE_DIR/settings.json"
|
||||
|
||||
if [ -f "$SETTINGS_SOURCE" ]; then
|
||||
# Si ya es un symlink correcto, saltar
|
||||
if [ -L "$SETTINGS_TARGET" ] && [ "$(readlink "$SETTINGS_TARGET")" = "$SETTINGS_SOURCE" ]; then
|
||||
echo "OK: settings.json ya está enlazado correctamente"
|
||||
else
|
||||
# Si existe (archivo o symlink incorrecto), hacer backup
|
||||
if [ -e "$SETTINGS_TARGET" ] || [ -L "$SETTINGS_TARGET" ]; then
|
||||
BACKUP="$SETTINGS_TARGET.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "Backup: settings.json -> $BACKUP"
|
||||
mv "$SETTINGS_TARGET" "$BACKUP"
|
||||
fi
|
||||
|
||||
# Crear symlink
|
||||
ln -s "$SETTINGS_SOURCE" "$SETTINGS_TARGET"
|
||||
echo "Enlazado: settings.json -> $SETTINGS_SOURCE"
|
||||
fi
|
||||
else
|
||||
echo "WARN: settings.json no encontrado en el repo"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Instalación completada ==="
|
||||
echo "Tus comandos y configuración ahora están sincronizados con el repositorio."
|
||||
echo ""
|
||||
echo "Configuración instalada:"
|
||||
echo " • Skills y Agents enlazados simbólicamente"
|
||||
echo " • Status Line configurada con vibecoding setup"
|
||||
echo " • Settings.json enlazado (compartido entre repos)"
|
||||
echo ""
|
||||
echo "Reinicia Claude Code para ver la nueva status line."
|
||||
|
||||
Reference in New Issue
Block a user