mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 23:41:12 +08:00
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:
@@ -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)
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user