110 lines
2.8 KiB
Go
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
|
|
}
|