package bootstrap import ( "context" "errors" "fmt" "io" "os" "strings" ) const ( CommandSetup = "setup" CommandMCP = "mcp" CommandConfig = "config" CommandUpdate = "update" CommandVersion = "version" ) var ( ErrBinaryNameRequired = errors.New("binary name is required") ErrUnknownCommand = errors.New("unknown command") ErrCommandNotConfigured = errors.New("command not configured") ErrVersionRequired = errors.New("version is required when no version hook is configured") ) type Handler func(context.Context, Invocation) error type Hooks struct { Setup Handler MCP Handler Config Handler Update Handler Version Handler } type Options struct { BinaryName string Description string Version string Args []string Stdin io.Reader Stdout io.Writer Stderr io.Writer Hooks Hooks } type Invocation struct { Command string Args []string Stdin io.Reader Stdout io.Writer Stderr io.Writer } type commandDef struct { Name string Description string Handler func(Hooks) Handler } var commands = []commandDef{ { Name: CommandSetup, Description: "Initialiser ou mettre a jour la configuration locale.", Handler: func(h Hooks) Handler { return h.Setup }, }, { Name: CommandMCP, Description: "Executer la logique MCP principale du binaire.", Handler: func(h Hooks) Handler { return h.MCP }, }, { Name: CommandConfig, Description: "Inspecter ou modifier la configuration.", Handler: func(h Hooks) Handler { return h.Config }, }, { Name: CommandUpdate, Description: "Declencher le flux d'auto-update.", Handler: func(h Hooks) Handler { return h.Update }, }, { Name: CommandVersion, Description: "Afficher la version du binaire.", Handler: func(h Hooks) Handler { return h.Version }, }, } func Run(ctx context.Context, opts Options) error { normalized := normalize(opts) if strings.TrimSpace(normalized.BinaryName) == "" { return ErrBinaryNameRequired } command, commandArgs, showHelp := parseArgs(normalized.Args) if showHelp { return printHelp(normalized, command) } if command == "" { return printHelp(normalized, "") } handler, known := resolveHandler(command, normalized.Hooks) if !known { return fmt.Errorf("%w: %s", ErrUnknownCommand, command) } if handler == nil { if command == CommandVersion { if strings.TrimSpace(normalized.Version) == "" { return ErrVersionRequired } _, err := fmt.Fprintln(normalized.Stdout, normalized.Version) return err } return fmt.Errorf("%w: %s", ErrCommandNotConfigured, command) } return handler(ctx, Invocation{ Command: command, Args: commandArgs, Stdin: normalized.Stdin, Stdout: normalized.Stdout, Stderr: normalized.Stderr, }) } func normalize(opts Options) Options { if opts.Stdin == nil { opts.Stdin = os.Stdin } if opts.Stdout == nil { opts.Stdout = os.Stdout } if opts.Stderr == nil { opts.Stderr = os.Stderr } if opts.Args == nil { opts.Args = os.Args[1:] } return opts } func parseArgs(args []string) (command string, commandArgs []string, showHelp bool) { if len(args) == 0 { return "", nil, false } first := strings.TrimSpace(args[0]) switch first { case "help", "-h", "--help": if len(args) > 1 { return strings.TrimSpace(args[1]), nil, true } return "", nil, true } command = first commandArgs = args[1:] if len(commandArgs) == 1 { helpArg := strings.TrimSpace(commandArgs[0]) if helpArg == "-h" || helpArg == "--help" { return command, nil, true } } return command, commandArgs, false } func resolveHandler(command string, hooks Hooks) (Handler, bool) { for _, def := range commands { if def.Name == command { return def.Handler(hooks), true } } return nil, false } func printHelp(opts Options, command string) error { if command == "" { return printGlobalHelp(opts) } for _, def := range commands { if def.Name != command { continue } _, err := fmt.Fprintf( opts.Stdout, "Usage:\n %s %s [args]\n\n%s\n", opts.BinaryName, def.Name, def.Description, ) return err } return fmt.Errorf("%w: %s", ErrUnknownCommand, command) } func printGlobalHelp(opts Options) error { if strings.TrimSpace(opts.Description) != "" { if _, err := fmt.Fprintf(opts.Stdout, "%s\n\n", opts.Description); err != nil { return err } } if _, err := fmt.Fprintf(opts.Stdout, "Usage:\n %s [args]\n\n", opts.BinaryName); err != nil { return err } if _, err := fmt.Fprintln(opts.Stdout, "Commandes communes:"); err != nil { return err } for _, def := range commands { if _, err := fmt.Fprintf(opts.Stdout, " %-7s %s\n", def.Name, def.Description); err != nil { return err } } _, err := fmt.Fprintf(opts.Stdout, "\nAide detaillee: %s help \n", opts.BinaryName) return err }