From 139f4515e8a9e9dcb250df5ea27bd3d23a39e708 Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Wed, 10 Jun 2026 16:06:30 +0800 Subject: [PATCH] Go: refactor CLI (#15898) ### What problem does this PR solve? 1. remove unused code 2. fix login issue ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) - [x] Refactoring Signed-off-by: Jin Hai --- cmd/ragflow_cli.go | 62 +---------- internal/cli/cli.go | 187 +++++++++------------------------ internal/cli/client.go | 118 --------------------- internal/cli/common_command.go | 4 +- internal/cli/http_client.go | 44 ++++++++ 5 files changed, 97 insertions(+), 318 deletions(-) delete mode 100644 internal/cli/client.go diff --git a/cmd/ragflow_cli.go b/cmd/ragflow_cli.go index 1ac3c84e86..006cbdfb23 100644 --- a/cmd/ragflow_cli.go +++ b/cmd/ragflow_cli.go @@ -26,7 +26,8 @@ import ( "ragflow/internal/cli" ) -func newMain() { +func main() { + parseArgs, err := cli.ParseArgs(os.Args[1:]) if err != nil { return @@ -63,7 +64,7 @@ func newMain() { if parseArgs.Command != nil { if err = client.RunSingleCommand(parseArgs.Command); err != nil { - fmt.Printf("Error: %v\n", err) + fmt.Printf("Command execution failed: %v\n", err) os.Exit(1) } } else { @@ -75,60 +76,3 @@ func newMain() { return } - -func main() { - - newMain() - //// Parse command line arguments (skip program name) - //args, err := cli.ParseConnectionArgs(os.Args[1:]) - //if err != nil { - // fmt.Printf("Error: %v\n", err) - // os.Exit(1) - //} - // - //// Initialize logger with appropriate level - //logLevel := "warn" // Default to warn (quiet mode) - //if args.Verbose { - // logLevel = "info" - //} - //if err = common.Init(logLevel); err != nil { - // fmt.Printf("Warning: Failed to initialize logger: %v\n", err) - //} - // - //// Show help and exit - //if args.ShowHelp { - // cli.PrintUsage() - // os.Exit(0) - //} - // - //// Create CLI instance with parsed arguments - //cliApp, err := cli.NewCLIWithArgs(args) - //if err != nil { - // fmt.Printf("Failed to create CLI: %v\n", err) - // os.Exit(1) - //} - // - //// Handle interrupt signal - //sigChan := make(chan os.Signal, 1) - //signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - //go func() { - // <-sigChan - // cliApp.Cleanup() - // os.Exit(0) - //}() - // - //// Check if we have a single command to execute - //if args.Command != nil { - // // Single command mode - // if err = cliApp.RunSingleCommand(args.Command); err != nil { - // fmt.Printf("Error: %v\n", err) - // os.Exit(1) - // } - //} else { - // // Interactive mode - // if err = cliApp.Run(); err != nil { - // fmt.Printf("CLI error: %v\n", err) - // os.Exit(1) - // } - //} -} diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 3f0213328f..3295459a6e 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -64,23 +64,6 @@ const ( OutputFormatJSON OutputFormat = "json" // JSON format (reserved for future use) ) -// ConnectionArgs holds the parsed command line arguments -type ConnectionArgs struct { - Host string - Port int - Password string - APIToken string - UserName string - ConfigFilePath string // Path to the config file (e.g., rf.yml) - Command *string // Original command string (for SQL mode) - CommandArgs []string // Split command arguments (for ContextEngine mode) - IsSQLMode bool // true=SQL mode (quoted), false= ContextEngine mode (unquoted) - ShowHelp bool - AdminMode bool - OutputFormat OutputFormat // Output format: table, plain, json - Verbose bool // Enable verbose logging -} - type CommandLineMode string const ( @@ -516,12 +499,8 @@ const historyFileName = ".ragflow_cli_history" // CLI represents the command line interface type CLI struct { - //client *RAGFlowClient - //contextEngine *filesystem.Engine - running bool - line *liner.State - args *ConnectionArgs - outputFormat OutputFormat // Output format + running bool + line *liner.State APIServerClientMap map[string]*HTTPClient AdminServerClient *HTTPClient @@ -541,9 +520,8 @@ func NewCLIWithConfig(commandLineConfig *CommandLineConfig) (*CLI, error) { line := liner.NewLiner() cli := &CLI{ - line: line, - Config: commandLineConfig, - outputFormat: commandLineConfig.OutputFormat, + line: line, + Config: commandLineConfig, } if commandLineConfig.CLIMode == APIMode { @@ -608,7 +586,7 @@ func (c *CLI) NewRun() error { // provider username but no password or api token maxAttempts := 3 for attempt := 1; attempt <= maxAttempts; attempt++ { - fmt.Printf("Please input your password for '%s': ", *apiConfig.UserName) + fmt.Printf("Please input your password: ") password, err := ReadPassword() @@ -622,12 +600,12 @@ func (c *CLI) NewRun() error { apiConfig.UserPassword = &password - if err = c.VerifyAuth(); err != nil { + if err = c.VerifyAuth(*apiConfig.UserName, *apiConfig.UserPassword); err != nil { if attempt < maxAttempts { - fmt.Printf("Authentication failed: %v (%d/%d attempts)\n", err, attempt, maxAttempts) + fmt.Printf("Authentication failed (%d/%d attempts)\n", attempt, maxAttempts) continue } - return fmt.Errorf("authentication failed after %d attempts: %v", maxAttempts, err) + return fmt.Errorf("authentication failed after %d attempts", maxAttempts) } break @@ -635,39 +613,41 @@ func (c *CLI) NewRun() error { } case AdminMode: + adminConfig := c.Config.AdminClientConfig + if adminConfig.AdminName != nil && adminConfig.AdminPassword == nil { + // provider username but no password or api token + maxAttempts := 3 + for attempt := 1; attempt <= maxAttempts; attempt++ { + fmt.Printf("Please input your password: ") + + password, err := ReadPassword() + + if password == "" { + if attempt < maxAttempts { + fmt.Println("Password cannot be empty, please try again") + continue + } + return errors.New("no password provided after 3 attempts") + } + + adminConfig.AdminPassword = &password + + if err = c.VerifyAuth(*adminConfig.AdminName, *adminConfig.AdminPassword); err != nil { + if attempt < maxAttempts { + fmt.Printf("Authentication failed (%d/%d attempts)\n", attempt, maxAttempts) + continue + } + return fmt.Errorf("authentication failed after %d attempts", maxAttempts) + } + + break + } + } + default: return fmt.Errorf("unexpected CLI mode: %s", cliConfig.CLIMode) } - if c.args != nil && c.args.UserName != "" && c.args.Password == "" && c.args.APIToken == "" { - maxAttempts := 3 - for attempt := 1; attempt <= maxAttempts; attempt++ { - fmt.Print("Please input your password: ") - - password, err := ReadPassword() - - if password == "" { - if attempt < maxAttempts { - fmt.Println("Password cannot be empty, please try again") - continue - } - return errors.New("no password provided after 3 attempts") - } - - c.args.Password = password - - if err = c.VerifyAuth(); err != nil { - if attempt < maxAttempts { - fmt.Printf("Authentication failed: %v (%d/%d attempts)\n", err, attempt, maxAttempts) - continue - } - return fmt.Errorf("authentication failed after %d attempts: %v", maxAttempts, err) - } - - break - } - } - c.running = true // Load history from file @@ -750,62 +730,6 @@ func (c *CLI) executeNew(input string) error { return err } -func (c *CLI) execute(input string) error { - // Determine execution mode based on input and args - input = strings.TrimSpace(input) - - // Handle meta commands (start with \) - if strings.HasPrefix(input, "\\") { - p := NewParser(input) - cmd, err := p.Parse(c.Config.CLIMode) - if err != nil { - return err - } - if cmd != nil && cmd.Type == "meta" { - return c.handleMetaCommand(cmd) - } - } - - // Check if we should use SQL mode or Filesystem mode - isSQLMode := false - if c.args != nil && len(c.args.CommandArgs) > 0 { - // Non-interactive mode: use pre-determined mode from args - isSQLMode = c.args.IsSQLMode - } else { - // Interactive mode: determine based on input - isSQLMode = looksLikeSQL(input) - } - - if isSQLMode { - // SQL mode: use parser - p := NewParser(input) - cmd, err := p.Parse(c.Config.CLIMode) - if err != nil { - return err - } - if cmd == nil { - return nil - } - // Execute SQL command using the client - var result ResponseIf - result, err = c.ExecuteCommand(cmd) - if result != nil { - result.SetOutputFormat(c.outputFormat) - result.PrintOut() - } - return err - } - - // Filesystem mode: execute filesystem command - cmd := NewCommand("file_system_command") - cmd.Params["command"] = input - resp, ceErr := c.executeFilesystem(cmd) - if resp != nil { - resp.PrintOut() - } - return ceErr -} - // executeFilesystem executes a Filesystem command and returns a ResponseIf. func (c *CLI) executeFilesystem(cmd *Command) (ResponseIf, error) { rawInput, _ := cmd.Params["command"].(string) @@ -843,13 +767,8 @@ func (c *CLI) executeFilesystem(cmd *Command) (ResponseIf, error) { func (c *CLI) executeFilesystemInner(input string) error { // Parse input into arguments var args []string - if c.args != nil && len(c.args.CommandArgs) > 0 { - // Non-interactive mode: use pre-parsed args - args = c.args.CommandArgs - } else { - // Interactive mode: parse input - args = parseFilesystemArgs(input) - } + // Interactive mode: parse input + args = parseFilesystemArgs(input) if len(args) == 0 { return fmt.Errorf("no command provided") @@ -936,7 +855,7 @@ func (c *CLI) executeFilesystemInner(input string) error { return err } // Print skill search results with full details - c.printSkillSearchResults(result, c.outputFormat) + c.printSkillSearchResults(result, c.Config.OutputFormat) return nil } ceCmd = &filesystem.Command{ @@ -1006,7 +925,7 @@ func (c *CLI) executeFilesystemInner(input string) error { // Print result // For search command, default to JSON format if not explicitly set to plain/table - format := c.outputFormat + format := c.Config.OutputFormat if ceCmd.Type == filesystem.CommandSearch && format != OutputFormatPlain && format != OutputFormatTable { format = OutputFormatJSON } @@ -1427,30 +1346,20 @@ func (c *CLI) NewVerifyAuth(username, password *string) error { } // VerifyAuth verifies authentication if needed -func (c *CLI) VerifyAuth() error { - if c.args == nil { - return nil - } - - // If API token is provided, use it for authentication - if c.args.APIToken != "" { - // TODO: Implement API token authentication - return nil - } - +func (c *CLI) VerifyAuth(username, password string) error { // Otherwise, use username/password authentication - if c.args.UserName == "" { + if username == "" { return fmt.Errorf("username is required") } - if c.args.Password == "" { + if password == "" { return fmt.Errorf("password is required") } // Create login command with username and password cmd := NewCommand("login_user") - cmd.Params["email"] = c.args.UserName - cmd.Params["password"] = c.args.Password + cmd.Params["email"] = username + cmd.Params["password"] = password _, err := c.ExecuteCommand(cmd) return err } diff --git a/internal/cli/client.go b/internal/cli/client.go deleted file mode 100644 index 806394a053..0000000000 --- a/internal/cli/client.go +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cli - -import ( - "fmt" - "io" - - ce "ragflow/internal/cli/filesystem" -) - -// PasswordPromptFunc is a function type for password input -type PasswordPromptFunc func(prompt string) (string, error) - -// CurrentModel holds the current model configuration -type CurrentModel struct { - Provider string - Instance string - Model string -} - -// RAGFlowClient handles API interactions with the RAGFlow server -type RAGFlowClient struct { - HTTPClient *HTTPClient - ServerType string // "admin" or "user" - PasswordPrompt PasswordPromptFunc // Function for password input - OutputFormat OutputFormat // Output format: table, plain, json - ContextEngine *ce.Engine // Context Engine for virtual filesystem - CurrentModel *CurrentModel // Current model configuration -} - -func NewRAGFlowClient(serverType string) *RAGFlowClient { - httpClient := NewHTTPClient() - // Set port from configuration file based on server type - if serverType == "admin" { - httpClient.Port = 9381 - } else { - httpClient.Port = 9380 - } - - client := &RAGFlowClient{ - HTTPClient: httpClient, - ServerType: serverType, - } - - // Initialize Context Engine - client.initContextEngine() - - return client -} - -// initContextEngine initializes the Context Engine with all providers -func (c *RAGFlowClient) initContextEngine() { - engine := ce.NewEngine() - - // Register providers - engine.RegisterProvider(ce.NewDatasetProvider(&httpClientAdapter{c.HTTPClient})) - engine.RegisterProvider(ce.NewFileProvider(&httpClientAdapter{c.HTTPClient})) - engine.RegisterProvider(ce.NewSkillProvider(&httpClientAdapter{c.HTTPClient})) - - c.ContextEngine = engine -} - -// httpClientAdapter adapts HTTPClient to ce.HTTPClientInterface -type httpClientAdapter struct { - client *HTTPClient -} - -func (a *httpClientAdapter) Request(method, path string, authKind string, headers map[string]string, jsonBody map[string]interface{}) (*ce.HTTPResponse, error) { - // Auto-detect auth kind based on available tokens - // If authKind is "auto" or empty, determine based on token availability - if authKind == "auto" || authKind == "" { - if a.client.useAPIToken && a.client.APIToken != nil { - authKind = "api" - } else if a.client.LoginToken != nil { - authKind = "web" - } else { - authKind = "web" // default - } - } - resp, err := a.client.Request(method, path, authKind, headers, jsonBody) - if err != nil { - return nil, err - } - return &ce.HTTPResponse{ - StatusCode: resp.StatusCode, - Body: resp.Body, - Headers: resp.Headers, - Duration: resp.Duration, - }, nil -} - -func (a *httpClientAdapter) UploadMultipart(path string, contentType string, body io.Reader) error { - return a.client.UploadMultipart(path, contentType, body) -} - -// ShowCurrentUser shows the current logged-in user information -// TODO: Implement showing current user information when API is available -func (c *RAGFlowClient) ShowCurrentUser(cmd *Command) (map[string]interface{}, error) { - // TODO: Call the appropriate API to get current user information - // Currently there is no /admin/user/info or /user/info API available - // The /admin/auth API only verifies authorization, does not return user info - return nil, fmt.Errorf("command 'SHOW CURRENT USER' is not yet implemented") -} diff --git a/internal/cli/common_command.go b/internal/cli/common_command.go index fd1de23d8e..e753424d6b 100644 --- a/internal/cli/common_command.go +++ b/internal/cli/common_command.go @@ -46,7 +46,7 @@ func (c *CLI) LoginUserByCommand(cmd *Command) (ResponseIf, error) { var result SimpleResponse result.Code = 0 - result.SetOutputFormat(c.outputFormat) + result.SetOutputFormat(c.Config.OutputFormat) result.Message = "Login successful" return &result, nil @@ -78,7 +78,7 @@ func (c *CLI) LoginUserInteractive(email, password string) error { return err } - fmt.Printf("Login user %s successfully\n", email) + fmt.Printf("Login successfully\n") switch c.Config.CLIMode { case AdminMode: diff --git a/internal/cli/http_client.go b/internal/cli/http_client.go index a8ce5e64a9..9bb63f46a3 100644 --- a/internal/cli/http_client.go +++ b/internal/cli/http_client.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "net/http" + ce "ragflow/internal/cli/filesystem" "time" ) @@ -364,3 +365,46 @@ func (c *HTTPClient) RequestStream(method, path string, authKind string, headers return resp.Body, nil } + +// PasswordPromptFunc is a function type for password input +type PasswordPromptFunc func(prompt string) (string, error) + +// CurrentModel holds the current model configuration +type CurrentModel struct { + Provider string + Instance string + Model string +} + +// httpClientAdapter adapts HTTPClient to ce.HTTPClientInterface +type httpClientAdapter struct { + client *HTTPClient +} + +func (a *httpClientAdapter) Request(method, path string, authKind string, headers map[string]string, jsonBody map[string]interface{}) (*ce.HTTPResponse, error) { + // Auto-detect auth kind based on available tokens + // If authKind is "auto" or empty, determine based on token availability + if authKind == "auto" || authKind == "" { + if a.client.useAPIToken && a.client.APIToken != nil { + authKind = "api" + } else if a.client.LoginToken != nil { + authKind = "web" + } else { + authKind = "web" // default + } + } + resp, err := a.client.Request(method, path, authKind, headers, jsonBody) + if err != nil { + return nil, err + } + return &ce.HTTPResponse{ + StatusCode: resp.StatusCode, + Body: resp.Body, + Headers: resp.Headers, + Duration: resp.Duration, + }, nil +} + +func (a *httpClientAdapter) UploadMultipart(path string, contentType string, body io.Reader) error { + return a.client.UploadMultipart(path, contentType, body) +}