// 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 }