f5b857dbc6
Implementa un dashboard interactivo con bubbletea siguiendo el patrón pure core / impure shell del proyecto: - pkg/tui/ (PURE): Model, Update, View — solo fmt y strings, cero I/O. Update produce Intent[] (datos puros) en vez de side effects. - shell/tui/ (IMPURE): Adapter convierte Intent[] en tea.Cmd[] con I/O real (process management, /proc stats, log tail). - cmd/dashboard/ (composición): Bridge conecta pure Update con shell Adapter usando la Elm Architecture de bubbletea. Pantallas: Main Menu → Agent List → Agent Actions (start/stop/restart/kill) → Logs. Navegación: flechas ↑↓, Enter seleccionar, 0 volver, q salir. Dependencias añadidas: bubbletea, lipgloss. Actualiza .gitignore para anclar binarios a raíz (/agentctl, /dashboard). Documenta nuevos scripts en CLAUDE.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
86 lines
1.9 KiB
Go
86 lines
1.9 KiB
Go
// Command dashboard provides an interactive TUI for managing bot agents.
|
|
//
|
|
// Usage:
|
|
//
|
|
// dashboard # launch the interactive TUI
|
|
// go run ./cmd/dashboard
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
puretui "github.com/enmanuel/agents/pkg/tui"
|
|
"github.com/enmanuel/agents/shell/process"
|
|
shelltui "github.com/enmanuel/agents/shell/tui"
|
|
)
|
|
|
|
const (
|
|
runDir = "run"
|
|
agentsGlob = "agents/*/config.yaml"
|
|
)
|
|
|
|
func main() {
|
|
_ = os.MkdirAll(runDir, 0o755)
|
|
|
|
mgr := process.NewManager(runDir, agentsGlob, "")
|
|
adapter := shelltui.NewAdapter(mgr)
|
|
|
|
p := tea.NewProgram(newBridge(adapter), tea.WithAltScreen())
|
|
if _, err := p.Run(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// bridge implements tea.Model and connects the pure Update with the impure Adapter.
|
|
type bridge struct {
|
|
model puretui.Model
|
|
adapter *shelltui.Adapter
|
|
}
|
|
|
|
func newBridge(adapter *shelltui.Adapter) bridge {
|
|
return bridge{
|
|
model: puretui.InitialModel(),
|
|
adapter: adapter,
|
|
}
|
|
}
|
|
|
|
func (b bridge) Init() tea.Cmd {
|
|
return b.adapter.RunIntent(puretui.Intent{Kind: puretui.IntentLoadAgents})
|
|
}
|
|
|
|
func (b bridge) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
// Convert tea messages to pure messages.
|
|
var pureMsg interface{}
|
|
switch m := msg.(type) {
|
|
case tea.KeyMsg:
|
|
pureMsg = puretui.KeyMsg{Str: m.String()}
|
|
case tea.WindowSizeMsg:
|
|
pureMsg = puretui.WindowSizeMsg{Width: m.Width, Height: m.Height}
|
|
default:
|
|
// MsgAgentsLoaded, MsgActionDone, MsgLogsLoaded, MsgTick pass through.
|
|
pureMsg = msg
|
|
}
|
|
|
|
// Pure update: no side effects.
|
|
newModel, intents := puretui.Update(b.model, pureMsg)
|
|
b.model = newModel
|
|
|
|
// Convert pure intents to impure tea.Cmds.
|
|
cmds := make([]tea.Cmd, 0, len(intents))
|
|
for _, intent := range intents {
|
|
if cmd := b.adapter.RunIntent(intent); cmd != nil {
|
|
cmds = append(cmds, cmd)
|
|
}
|
|
}
|
|
|
|
return b, tea.Batch(cmds...)
|
|
}
|
|
|
|
func (b bridge) View() string {
|
|
return puretui.View(b.model)
|
|
}
|