// ajean-remote — client LÉGER « poste distant ». Binaire séparé et minuscule // (protocole partagé + WebSocket, sans UI ni moteur) : on l'installe sur un PC // pour que l'IA d'un serveur AJEAN puisse agir dessus. // // ajean-remote install --code CODE [--allow shell,read,write,list] [--root DIR] // appaire ce PC et l'installe en service (démarrage auto) // ajean-remote connect --code CODE [...] même chose mais en avant-plan (test) // ajean-remote run boucle client (utilisé par le service) // ajean-remote status | uninstall | logout package main import ( "context" "fmt" "os" "runtime" "strings" "github.com/nathaninline/ajean/internal/nodeclient" ) func main() { args := os.Args[1:] cmd := "" if len(args) > 0 { cmd = args[0] args = args[1:] } var err error switch cmd { case "install": err = doConnect(args, true) case "connect": err = doConnect(args, false) case "run": err = doRun() case "uninstall": err = nodeclient.Uninstall() if err == nil { fmt.Println("[ok] service retiré") } case "logout": _ = nodeclient.Uninstall() err = os.Remove(nodeclient.ConfigPath()) if err == nil || os.IsNotExist(err) { err = nil fmt.Println("[ok] configuration oubliée") } case "status": err = doStatus() default: usage() } if err != nil { fmt.Fprintln(os.Stderr, "[err]", err) os.Exit(1) } } func usage() { fmt.Print(`ajean-remote — client léger « poste distant » pour AJEAN ajean-remote install --code CODE [options] appaire + installe en service (démarrage auto, invisible) ajean-remote connect --code CODE [options] idem en avant-plan (pour tester) ajean-remote status état de la configuration ajean-remote uninstall retire le service (garde la config) ajean-remote logout retire le service ET oublie la clé Options : --code CODE code d'appairage (généré dans l'UI AJEAN → Postes distants) --allow LISTE capacités acceptées : shell,read,write,list (défaut : ce que le serveur autorise) --root DIR dossier autorisé (confine lecture/écriture) --yes auto-approuve les actions (obligatoire pour le service, sans terminal) `) } // doConnect appaire au besoin, sauvegarde, puis installe (service) ou lance // (avant-plan) selon install. func doConnect(args []string, install bool) error { url, code, allow, root, yes := parseFlags(args) cfg, lerr := nodeclient.LoadConfig() needEnroll := lerr != nil || cfg.Key == "" if url != "" { cfg.ServerURL = url } if needEnroll { if cfg.ServerURL == "" { return fmt.Errorf("url du serveur requise (ex: http://192.168.1.127:8090)") } if code == "" { return fmt.Errorf("code d'appairage requis : --code CODE") } if err := nodeclient.Enroll(&cfg, code); err != nil { return err } fmt.Printf("[ok] appairé (id %s)\n", cfg.ID) } if len(allow) > 0 { cfg.Caps = nodeclient.SanitizeCaps(allow) } if root != "" { cfg.Root = root } if cfg.Root == "" { cfg.Root = nodeclient.DataDir() + string(os.PathSeparator) + "workspace" } if yes || install { cfg.AutoYes = true // un service n'a pas de terminal pour confirmer } if cfg.Name == "" { cfg.Name, _ = os.Hostname() } if err := nodeclient.SaveConfig(cfg); err != nil { return err } fmt.Printf("[ok] poste « %s » → %s\n", cfg.Name, cfg.ServerURL) fmt.Printf("[ok] capacités : %s | dossier : %s\n", strings.Join(cfg.Caps, ", "), cfg.Root) if install { if err := nodeclient.Install(); err != nil { return err } fmt.Println("[ok] service installé et démarré — il tournera en tâche de fond et au redémarrage") return nil } nodeclient.Run(context.Background(), cfg, false) return nil } func doRun() error { cfg, err := nodeclient.LoadConfig() if err != nil { return fmt.Errorf("aucune configuration — lance d'abord « ajean-remote install … »") } return nodeclient.RunService(cfg) } func doStatus() error { cfg, err := nodeclient.LoadConfig() if err != nil { fmt.Println("aucun poste configuré") return nil } fmt.Printf("serveur : %s\n", cfg.ServerURL) fmt.Printf("nom : %s\n", cfg.Name) fmt.Printf("capacités : %s\n", strings.Join(cfg.Caps, ", ")) fmt.Printf("dossier : %s\n", cfg.Root) fmt.Printf("auto-oui : %v\n", cfg.AutoYes) fmt.Printf("os : %s/%s\n", runtime.GOOS, runtime.GOARCH) return nil } func parseFlags(args []string) (url, code string, allow []string, root string, yes bool) { for i := 0; i < len(args); i++ { a := args[i] next := func() string { if i+1 < len(args) { i++ return args[i] } return "" } switch { case a == "--code": code = next() case a == "--allow": for _, p := range strings.Split(next(), ",") { if p = strings.TrimSpace(p); p != "" { allow = append(allow, p) } } case a == "--root": root = next() case a == "--yes" || a == "-y": yes = true case !strings.HasPrefix(a, "-") && url == "": url = a } } return }