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 <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-06-10 16:06:30 +08:00
committed by GitHub
parent 357cb84cd4
commit 139f4515e8
5 changed files with 97 additions and 318 deletions

View File

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

View File

@@ -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")
}

View File

@@ -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:

View File

@@ -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)
}