Files
iasysadmin/cmd/sysagent/main.go
T
2026-09-27 16:04:02 -06:00

110 lines
2.8 KiB
Go

// Comando sysagent: agente autónomo de administración de sistemas.
//
// Uso:
//
// sysagent procesa las tareas *.md de PROJECT_ROOT
// sysagent --cli sesión interactiva (ignora las tareas)
// sysagent --version
//
// PROJECT_ROOT es siempre el directorio actual.
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"sysagent/internal/agent"
"sysagent/internal/project"
)
// version se inyecta al compilar:
//
// go build -ldflags "-X main.version=0.1.0" ./cmd/sysagent
var version = "0.1.0-dev"
func main() {
os.Exit(run())
}
// run contiene la lógica de main y devuelve el código de salida, para que
// los defer se ejecuten antes de os.Exit.
func run() int {
var (
cli = flag.Bool("cli", false, "start an interactive session (task *.md files are ignored)")
dryRun = flag.Bool("dry-run", false, "describe actions without modifying the system")
verbose = flag.Bool("verbose", false, "show DEBUG/INFO logs on the console")
rerun = flag.Bool("rerun", false, "execute again tasks that contexto.mem records as DONE")
showVersion = flag.Bool("version", false, "print version and exit")
)
flag.Parse()
if *showVersion {
fmt.Printf("sysagent %s\n", version)
return 0
}
if flag.NArg() > 0 {
fmt.Fprintf(os.Stderr, "ERROR: unexpected arguments: %v\n", flag.Args())
return 2
}
root, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: cannot determine current directory: %v\n", err)
return 1
}
// Las señales se capturan desde el principio. En modo tareas cancelan el
// contexto raíz; en --cli las gestiona el propio CLI (ver RunCLI).
interrupts := make(chan os.Signal, 1)
signal.Notify(interrupts, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
a, err := agent.Bootstrap(ctx, agent.Options{
Root: root,
CLI: *cli,
DryRun: *dryRun,
Verbose: *verbose,
Rerun: *rerun,
Version: version,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
})
if err != nil {
if errors.Is(err, project.ErrAgentNotFound) {
fmt.Fprintln(os.Stderr, "ERROR: agent.md not found in PROJECT_ROOT")
} else {
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
}
return 1
}
defer a.Close()
if *cli {
err = a.RunCLI(ctx, interrupts)
} else {
go func() {
// Primera señal: cancelar de forma ordenada (se matan los procesos
// hijos y se guarda contexto.mem al volver de RunTasks).
// Segunda señal: salida inmediata, por si algo no responde.
sig := <-interrupts
fmt.Fprintf(os.Stderr, "\nreceived %s, stopping (press Ctrl+C again to force)...\n", sig)
cancel()
<-interrupts
fmt.Fprintln(os.Stderr, "forced exit")
os.Exit(130)
}()
err = a.RunTasks(ctx)
}
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
return 1
}
return 0
}