package cli import ( "bufio" "context" "fmt" "io" "os" "strings" "email-mcp/mcpgen" frameworkcli "forge.lclr.dev/AI/mcp-framework/cli" "email-mcp/internal/secretstore" ) type InteractiveConfigPrompter struct { reader *bufio.Reader stdinFile *os.File output io.Writer } func NewInteractiveConfigPrompter(input io.Reader, output io.Writer) *InteractiveConfigPrompter { if input == nil { input = strings.NewReader("") } if output == nil { output = io.Discard } prompter := &InteractiveConfigPrompter{ reader: bufio.NewReader(input), output: output, } if file, ok := input.(*os.File); ok { prompter.stdinFile = file } return prompter } func (p *InteractiveConfigPrompter) PromptCredential(_ context.Context, existing secretstore.Credential, hasStoredPassword bool) (secretstore.Credential, error) { if p.stdinFile != nil { return p.promptCredentialWithSetupEngine(existing, hasStoredPassword) } host, err := frameworkcli.PromptLine(p.reader, p.output, "IMAP host", existing.Host) if err != nil { return secretstore.Credential{}, err } username, err := frameworkcli.PromptLine(p.reader, p.output, "Username", existing.Username) if err != nil { return secretstore.Credential{}, err } password, err := p.promptPassword(existing.Password, hasStoredPassword) if err != nil { return secretstore.Credential{}, err } cred := secretstore.Credential{ Host: host, Username: username, Password: password, } if err := cred.Validate(); err != nil { return secretstore.Credential{}, err } return cred, nil } func (p *InteractiveConfigPrompter) promptCredentialWithSetupEngine(existing secretstore.Credential, hasStoredPassword bool) (secretstore.Credential, error) { password := "" if hasStoredPassword { password = existing.Password } fields := mcpgen.SetupFields(map[string]string{"password": password}) for i := range fields { switch fields[i].Name { case "host": fields[i].Default = existing.Host case "username": fields[i].Default = existing.Username } } result, err := frameworkcli.RunSetup(frameworkcli.SetupOptions{ Stdin: p.stdinFile, Stdout: p.output, Fields: fields, }) if err != nil { return secretstore.Credential{}, err } host, ok := result.Get("host") if !ok { return secretstore.Credential{}, fmt.Errorf("setup result is missing host") } username, ok := result.Get("username") if !ok { return secretstore.Credential{}, fmt.Errorf("setup result is missing username") } secret, ok := result.Get("password") if !ok { return secretstore.Credential{}, fmt.Errorf("setup result is missing password") } cred := secretstore.Credential{ Host: host.String, Username: username.String, Password: secret.String, } if err := cred.Validate(); err != nil { return secretstore.Credential{}, err } return cred, nil } func (p *InteractiveConfigPrompter) promptPassword(storedPassword string, hasStoredPassword bool) (string, error) { if p.stdinFile != nil { return frameworkcli.PromptSecret(p.stdinFile, p.output, "Password", hasStoredPassword, storedPassword) } if hasStoredPassword { fmt.Fprint(p.output, "Password [stored, leave blank to keep]: ") } else { fmt.Fprint(p.output, "Password: ") } line, err := p.reader.ReadString('\n') if err != nil && err != io.EOF { return "", err } password := strings.TrimSpace(line) if password == "" && hasStoredPassword { return storedPassword, nil } return password, nil }