diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 5ae5eaa6f4..3f0213328f 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" //"os/signal" "path/filepath" @@ -177,7 +178,22 @@ func ParseArgs(args []string) (*CommandLineConfig, error) { for i := 0; i < len(args); i++ { arg := args[i] - // If we've found the command, collect remaining args as subcommand args + // Handle known global flags (already parsed in first pass). + // Intercept here regardless of position so they are never + // mistaken for command args or unknown flags downstream. + switch arg { + case "-o", "--output": + if i+1 < len(args) { + i++ + } + continue + case "-v", "--verbose", "--help", "-help": + continue + case "--admin", "-admin": + return nil, fmt.Errorf("unexpected parameter: --admin") + } + + // If we've found the command, collect remaining args if foundCommand { commandArgs = append(commandArgs, arg) continue @@ -222,17 +238,6 @@ func ParseArgs(args []string) (*CommandLineConfig, error) { } i++ } - case "-o", "--output": - // Already handled above - if i+1 < len(args) { - i++ - } - continue - case "-v", "--verbose", "--help", "-help": - // Already handled above - continue - case "--admin", "-admin": - return nil, fmt.Errorf("unexpected parameter: --admin") default: // Non-flag argument (command) if !strings.HasPrefix(arg, "-") { @@ -311,7 +316,22 @@ func ParseArgs(args []string) (*CommandLineConfig, error) { for i := 0; i < len(args); i++ { arg := args[i] - // If we've found the command, collect remaining args as subcommand args + // Handle known global flags regardless of position + switch arg { + case "-o", "--output": + if i+1 < len(args) { + i++ + } + continue + case "-v", "--verbose", "--admin", "-admin", "--help", "-help": + continue + case "-t", "--token": + return nil, fmt.Errorf("token is invalid in admin mode") + case "-f", "--config": + return nil, fmt.Errorf("config is invalid in admin mode") + } + + // If we've found the command, collect remaining args if foundCommand { commandArgs = append(commandArgs, arg) continue @@ -329,8 +349,6 @@ func ParseArgs(args []string) (*CommandLineConfig, error) { AdminConfig.AdminPort = port i++ } - case "-t", "--token": - return nil, fmt.Errorf("token is invalid in admin mode") case "-u", "--user": if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { AdminConfig.AdminName = &args[i+1] @@ -341,17 +359,6 @@ func ParseArgs(args []string) (*CommandLineConfig, error) { AdminConfig.AdminPassword = &args[i+1] i++ } - case "-f", "--config": - return nil, fmt.Errorf("config is invalid in admin mode") - case "-o", "--output": - // Already handled above - if i+1 < len(args) { - i++ - } - continue - case "-v", "--verbose", "--admin", "-admin", "--help", "-help": - // Already handled above - continue default: // Non-flag argument (command) if !strings.HasPrefix(arg, "-") { @@ -534,8 +541,9 @@ func NewCLIWithConfig(commandLineConfig *CommandLineConfig) (*CLI, error) { line := liner.NewLiner() cli := &CLI{ - line: line, - Config: commandLineConfig, + line: line, + Config: commandLineConfig, + outputFormat: commandLineConfig.OutputFormat, } if commandLineConfig.CLIMode == APIMode { @@ -789,11 +797,50 @@ func (c *CLI) execute(input string) error { } // Filesystem mode: execute filesystem command - return c.executeFilesystem(input) + 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 -func (c *CLI) executeFilesystem(input string) error { +// executeFilesystem executes a Filesystem command and returns a ResponseIf. +func (c *CLI) executeFilesystem(cmd *Command) (ResponseIf, error) { + rawInput, _ := cmd.Params["command"].(string) + + r, w, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("create stdout pipe: %w", err) + } + old := os.Stdout + os.Stdout = w + defer func() { + os.Stdout = old + _ = w.Close() + _ = r.Close() + }() + + var buf strings.Builder + copyErrCh := make(chan error, 1) + go func() { + _, copyErr := io.Copy(&buf, r) + copyErrCh <- copyErr + }() + + execErr := c.executeFilesystemInner(rawInput) + _ = w.Close() // signal EOF to reader goroutine + copyErr := <-copyErrCh + if copyErr != nil { + return nil, fmt.Errorf("capture filesystem output: %w", copyErr) + } + return &FileSystemResponse{Output: buf.String()}, execErr +} + +// executeFilesystemInner executes a Filesystem command and writes output to stdout. +// It is called by executeFilesystem which captures the stdout output. +func (c *CLI) executeFilesystemInner(input string) error { // Parse input into arguments var args []string if c.args != nil && len(c.args.CommandArgs) > 0 { @@ -947,36 +994,6 @@ func (c *CLI) executeFilesystem(input string) error { fileProv, _ := fileProvider.(*filesystem.FileProvider) cmd := filesystem.NewUninstallSkillCommand(httpAdapter, skillProvider, fileProv) return cmd.Execute(cmdArgs) - case "add-skill": - fmt.Println("⚠ Warning: 'add-skill' is deprecated. Use 'install-skill' instead.") - // Forward to install-skill - fileProvider, ok := c.ContextEngine.GetProvider("files").(*filesystem.FileProvider) - if !ok { - return fmt.Errorf("file provider not available") - } - skillProvider := c.ContextEngine.GetProvider("skills") - if skillProvider == nil { - return fmt.Errorf("skill provider not available") - } - httpAdapter := &httpClientAdapter{client: httpClient} - cmd := filesystem.NewInstallSkillCommand(httpAdapter, fileProvider, skillProvider) - return cmd.Execute(cmdArgs) - case "delete-skill": - fmt.Println("⚠ Warning: 'delete-skill' is deprecated. Use 'uninstall-skill' instead.") - // Forward to uninstall-skill - skillProvider := c.ContextEngine.GetProvider("skills") - if skillProvider == nil { - return fmt.Errorf("skill provider not available") - } - fileProvider := c.ContextEngine.GetProvider("files") - if fileProvider == nil { - return fmt.Errorf("file provider not available") - } - httpAdapter := &httpClientAdapter{client: httpClient} - fileProv, _ := fileProvider.(*filesystem.FileProvider) - cmd := filesystem.NewUninstallSkillCommand(httpAdapter, skillProvider, fileProv) - return cmd.Execute(cmdArgs) - default: return fmt.Errorf("unknown filesystem command: %s", cmdType) } diff --git a/internal/cli/cli_http.go b/internal/cli/cli_http.go index 03abbef220..6135a79a97 100644 --- a/internal/cli/cli_http.go +++ b/internal/cli/cli_http.go @@ -294,9 +294,8 @@ func (c *CLI) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { return c.DeleteAdminServer(cmd) case "save_config_command": return c.SaveServerConfig(cmd) - // ContextEngine commands - case "context_engine_command": - return nil, c.executeFilesystem(cmd.Params["command"].(string)) + case "file_system_command": + return c.executeFilesystem(cmd) default: return nil, fmt.Errorf("command '%s' would be executed with API", cmd.Type) } diff --git a/internal/cli/context_parser.go b/internal/cli/context_parser.go deleted file mode 100644 index 324ec39c07..0000000000 --- a/internal/cli/context_parser.go +++ /dev/null @@ -1,178 +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" - "strings" -) - -func (p *Parser) parseContextListCommand() (*Command, error) { - p.nextToken() // consume LS - - cmd := NewCommand("ce_ls") - - if p.curToken.Type == TokenEOF { - cmd.Params["path"] = "." - return cmd, nil - } - - for p.curToken.Type != TokenEOF { - if p.curToken.Type == TokenDash { - p.nextToken() // skip dash - if p.curToken.Type != TokenIdentifier { - return nil, fmt.Errorf("expect identifier") - } - if cmd.Params["parameter"] == nil { - cmd.Params["parameter"] = p.curToken.Value - } else { - cmd.Params["parameter"] = fmt.Sprintf("%s%s", cmd.Params["parameter"], p.curToken.Value) - } - p.nextToken() // skip parameter - } else if p.curToken.Type == TokenIdentifier { - if cmd.Params["path"] == nil { - cmd.Params["path"] = p.curToken.Value - } else { - err := fmt.Errorf("ls: cannot access '%s': No such file or directory", p.curToken.Value) - return nil, err - } - p.nextToken() // skip path - } else { - return nil, fmt.Errorf("syntax error") - } - } - - return cmd, nil -} - -func (p *Parser) parseContextCatCommand() (*Command, error) { - p.nextToken() // consume CAT - - if p.curToken.Type == TokenEOF { - return nil, fmt.Errorf("expect a filename") - } - - if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString { - return nil, fmt.Errorf("expect a filename") - } - - cmd := NewCommand("ce_cat") - if p.curToken.Type == TokenIdentifier { - for p.curToken.Type != TokenEOF { - if p.curToken.Type != TokenIdentifier { - return nil, fmt.Errorf("expect a identifier") - } - - if cmd.Params["filename"] == nil { - cmd.Params["filename"] = p.curToken.Value - } else { - cmd.Params["filename"] = fmt.Sprintf("%s/%s", cmd.Params["filename"], p.curToken.Value) - } - p.nextToken() - if p.curToken.Type == TokenEOF { - break - } - if p.curToken.Type != TokenSlash { - return nil, fmt.Errorf("expect a slash") - } - p.nextToken() - if p.curToken.Type == TokenEOF { - return nil, fmt.Errorf("error format") - } - } - - } else if p.curToken.Type == TokenQuotedString { - var err error - cmd.Params["filename"], err = p.parseQuotedString() - if err != nil { - return nil, err - } - } - p.nextToken() - - if p.curToken.Type != TokenEOF { - return nil, fmt.Errorf("syntax error") - } - - return cmd, nil -} - -func (p *Parser) parseContextSearchCommand() (*Command, error) { - p.nextToken() // consume SEARCH - - cmd := NewCommand("ce_search") - - for p.curToken.Type != TokenEOF { - if p.curToken.Type == TokenDash { - p.nextToken() // skip dash - if p.curToken.Type != TokenIdentifier { - return nil, fmt.Errorf("expect identifier") - } - - if strings.ToLower(p.curToken.Value) == "n" { - p.nextToken() - var err error - if p.curToken.Type != TokenInteger { - return nil, fmt.Errorf("expect number") - } - cmd.Params["number"], err = p.parseNumber() - if err != nil { - return nil, err - } - p.nextToken() - continue - } - - if strings.ToLower(p.curToken.Value) == "t" { - p.nextToken() - var err error - if p.curToken.Type != TokenInteger { - return nil, fmt.Errorf("expect number") - } - cmd.Params["threshold"], err = p.parseFloat() - if err != nil { - return nil, err - } - p.nextToken() - continue - } - - return nil, fmt.Errorf("unknow parameter: %s", p.curToken.Value) - } else if p.curToken.Type == TokenIdentifier { - if cmd.Params["path"] == nil { - cmd.Params["path"] = p.curToken.Value - } else { - cmd.Params["path"] = fmt.Sprintf("%s %s", cmd.Params["path"], p.curToken.Value) - } - p.nextToken() // skip path - } else if p.curToken.Type == TokenQuotedString { - if cmd.Params["query"] == nil { - var err error - cmd.Params["query"], err = p.parseQuotedString() - if err != nil { - return nil, err - } - p.nextToken() - } else { - return nil, fmt.Errorf("Query phrase exists") - } - } - return nil, fmt.Errorf("syntax error") - } - - return cmd, nil -} diff --git a/internal/cli/parser.go b/internal/cli/parser.go index 4c2fbcb7e1..04253b051a 100644 --- a/internal/cli/parser.go +++ b/internal/cli/parser.go @@ -227,7 +227,7 @@ func (p *Parser) parseUserCommand() (*Command, error) { case TokenLS, TokenCat, TokenSearch: // For context engine - return p.parseContextEngineCommand() + return p.parseFileSystemCommand() default: return nil, fmt.Errorf("unknown command: %s", p.curToken.Value) } @@ -390,241 +390,13 @@ func tokenTypeToString(t int) string { return fmt.Sprintf("token(%d)", t) } -func (p *Parser) parseContextEngineCommand() (*Command, error) { +func (p *Parser) parseFileSystemCommand() (*Command, error) { p.nextToken() // consume COMMAND - cmd := NewCommand("context_engine_command") + cmd := NewCommand("file_system_command") cmd.Params["command"] = p.original return cmd, nil } -// parseCEListCommand parses the ls command -// Syntax: ls [path] or ls datasets -func (p *Parser) parseCEListCommand() (*Command, error) { - p.nextToken() // consume LS/LIST - cmd := NewCommand("ce_ls") - - // Check if there's a path argument - // Also accept TokenDatasets since "datasets" is a keyword but can be a path - if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenQuotedString || - p.curToken.Type == TokenDatasets { - path := p.curToken.Value - // Remove quotes if present - if p.curToken.Type == TokenQuotedString { - path = strings.Trim(path, "\"'") - } - p.nextToken() - - // Handle path components separated by slashes (e.g., "skills/hub1") - for p.curToken.Type == TokenSlash { - p.nextToken() // consume slash - if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenDatasets || - p.curToken.Type == TokenAgents || p.curToken.Type == TokenChats { - path = path + "/" + p.curToken.Value - p.nextToken() - } else if p.curToken.Type == TokenNumber { - // Handle version numbers like 1.0.0 (parsed as number . number . number) - // OR filenames starting with numbers like 3_list_compressors.pdf - numberPart := p.curToken.Value - p.nextToken() - // Continue reading .number parts (version number format) - if p.curToken.Type == TokenIllegal && p.curToken.Value == "." { - versionPart := numberPart - for p.curToken.Type == TokenIllegal && p.curToken.Value == "." { - p.nextToken() // consume . - if p.curToken.Type == TokenNumber { - versionPart = versionPart + "." + p.curToken.Value - p.nextToken() - } else { - break - } - } - path = path + "/" + versionPart - } else if p.curToken.Type == TokenIdentifier { - // Filename starting with number: 3_list_compressors.pdf - path = path + "/" + numberPart + p.curToken.Value - p.nextToken() - } else { - // Just a number - path = path + "/" + numberPart - } - } else { - // Trailing slash, just append it - path = path + "/" - break - } - } - - cmd.Params["path"] = path - } else { - // Default to "datasets" root - cmd.Params["path"] = "datasets" - } - - // Optional semicolon - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - - return cmd, nil -} - -// parseCECatCommand parses the cat command -// Syntax: cat -func (p *Parser) parseCECatCommand() (*Command, error) { - p.nextToken() // consume CAT - - cmd := NewCommand("ce_cat") - - if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString { - return nil, fmt.Errorf("expected path after CAT") - } - - path := p.curToken.Value - if p.curToken.Type == TokenQuotedString { - path = strings.Trim(path, "\"'") - } - p.nextToken() - - // Handle path components separated by slashes (e.g., "skills/hub1/skill/README.md") - for p.curToken.Type == TokenSlash { - p.nextToken() // consume slash - if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenAgents || - p.curToken.Type == TokenChats || p.curToken.Type == TokenDatasets { - path = path + "/" + p.curToken.Value - p.nextToken() - } else if p.curToken.Type == TokenNumber { - // Handle version numbers like 1.0.0 (parsed as number . number . number) - // OR filenames starting with numbers like 3_list_compressors.pdf - numberPart := p.curToken.Value - p.nextToken() - // Continue reading .number parts (version number format) - if p.curToken.Type == TokenIllegal && p.curToken.Value == "." { - versionPart := numberPart - for p.curToken.Type == TokenIllegal && p.curToken.Value == "." { - p.nextToken() // consume . - if p.curToken.Type == TokenNumber { - versionPart = versionPart + "." + p.curToken.Value - p.nextToken() - } else { - break - } - } - path = path + "/" + versionPart - } else if p.curToken.Type == TokenIdentifier { - // Filename starting with number: 3_list_compressors.pdf - path = path + "/" + numberPart + p.curToken.Value - p.nextToken() - } else { - // Just a number - path = path + "/" + numberPart - } - } else if p.curToken.Type == TokenQuotedString { - path = path + "/" + strings.Trim(p.curToken.Value, "\"'") - p.nextToken() - } else { - // Trailing slash, just append it - path = path + "/" - break - } - } - - cmd.Params["path"] = path - - // Optional semicolon - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - - return cmd, nil -} - -// parseCESearchCommand parses the search command -// Syntax: search or search in -func (p *Parser) parseCESearchCommand() (*Command, error) { - p.nextToken() // consume SEARCH - - cmd := NewCommand("ce_search") - - if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString { - return nil, fmt.Errorf("expected query after SEARCH") - } - - query := p.curToken.Value - if p.curToken.Type == TokenQuotedString { - query = strings.Trim(query, "\"'") - } - cmd.Params["query"] = query - p.nextToken() - - // Check for optional "in " clause - if p.curToken.Type == TokenIdentifier && strings.ToUpper(p.curToken.Value) == "IN" { - p.nextToken() // consume IN - - if p.curToken.Type != TokenIdentifier && p.curToken.Type != TokenQuotedString { - return nil, fmt.Errorf("expected path after IN") - } - - path := p.curToken.Value - if p.curToken.Type == TokenQuotedString { - path = strings.Trim(path, "\"'") - } - p.nextToken() - - // Handle path components separated by slashes (e.g., "skills/hub1") - for p.curToken.Type == TokenSlash { - p.nextToken() // consume slash - if p.curToken.Type == TokenIdentifier || p.curToken.Type == TokenAgents || - p.curToken.Type == TokenChats || p.curToken.Type == TokenDatasets { - path = path + "/" + p.curToken.Value - p.nextToken() - } else if p.curToken.Type == TokenNumber { - // Handle version numbers like 1.0.0 (parsed as number . number . number) - // OR filenames starting with numbers like 3_list_compressors.pdf - numberPart := p.curToken.Value - p.nextToken() - // Continue reading .number parts (version number format) - if p.curToken.Type == TokenIllegal && p.curToken.Value == "." { - versionPart := numberPart - for p.curToken.Type == TokenIllegal && p.curToken.Value == "." { - p.nextToken() // consume . - if p.curToken.Type == TokenNumber { - versionPart = versionPart + "." + p.curToken.Value - p.nextToken() - } else { - break - } - } - path = path + "/" + versionPart - } else if p.curToken.Type == TokenIdentifier { - // Filename starting with number: 3_list_compressors.pdf - path = path + "/" + numberPart + p.curToken.Value - p.nextToken() - } else { - // Just a number - path = path + "/" + numberPart - } - } else if p.curToken.Type == TokenQuotedString { - path = path + "/" + strings.Trim(p.curToken.Value, "\"'") - p.nextToken() - } else { - // Trailing slash, just append it - path = path + "/" - break - } - } - - cmd.Params["path"] = path - } else { - cmd.Params["path"] = "." - } - - // Optional semicolon - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - - return cmd, nil -} diff --git a/internal/cli/response.go b/internal/cli/response.go index 7bf7150b6d..054ceb4430 100644 --- a/internal/cli/response.go +++ b/internal/cli/response.go @@ -529,69 +529,16 @@ func (r *TaskResponse) PrintOut() { } } -// ==================== ContextEngine Commands ==================== - -// ContextListResponse represents the response for ls command -type ContextListResponse struct { - Code int `json:"code"` - Data []map[string]interface{} `json:"data"` - Message string `json:"message"` +// FileSystemResponse wraps the raw text output from executeFilesystem(). +type FileSystemResponse struct { + Output string Duration float64 OutputFormat OutputFormat } -func (r *ContextListResponse) Type() string { return "ce_ls" } -func (r *ContextListResponse) TimeCost() float64 { return r.Duration } -func (r *ContextListResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format } -func (r *ContextListResponse) PrintOut() { - if r.Code == 0 { - PrintTableSimpleByFormat(r.Data, r.OutputFormat) - } else { - fmt.Println("ERROR") - fmt.Printf("%d, %s\n", r.Code, r.Message) - } -} - -// ContextSearchResponse represents the response for search command -type ContextSearchResponse struct { - Code int `json:"code"` - Data []map[string]interface{} `json:"data"` - Total int `json:"total"` - Message string `json:"message"` - Duration float64 - OutputFormat OutputFormat -} - -func (r *ContextSearchResponse) Type() string { return "ce_search" } -func (r *ContextSearchResponse) TimeCost() float64 { return r.Duration } -func (r *ContextSearchResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format } -func (r *ContextSearchResponse) PrintOut() { - if r.Code == 0 { - fmt.Printf("Found %d results:\n", r.Total) - PrintTableSimpleByFormat(r.Data, r.OutputFormat) - } else { - fmt.Println("ERROR") - fmt.Printf("%d, %s\n", r.Code, r.Message) - } -} - -// ContextCatResponse represents the response for cat command -type ContextCatResponse struct { - Code int `json:"code"` - Content string `json:"content"` - Message string `json:"message"` - Duration float64 - OutputFormat OutputFormat -} - -func (r *ContextCatResponse) Type() string { return "ce_cat" } -func (r *ContextCatResponse) TimeCost() float64 { return r.Duration } -func (r *ContextCatResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format } -func (r *ContextCatResponse) PrintOut() { - if r.Code == 0 { - fmt.Println(r.Content) - } else { - fmt.Println("ERROR") - fmt.Printf("%d, %s\n", r.Code, r.Message) - } +func (r *FileSystemResponse) Type() string { return "filesystem" } +func (r *FileSystemResponse) TimeCost() float64 { return r.Duration } +func (r *FileSystemResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format } +func (r *FileSystemResponse) PrintOut() { + fmt.Print(r.Output) } diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index ccf8611c39..b783ebc6d5 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -18,7 +18,6 @@ package cli import ( "bufio" - "context" "encoding/base64" "encoding/json" "errors" @@ -29,7 +28,6 @@ import ( "os" "os/exec" "path/filepath" - ce "ragflow/internal/cli/filesystem" "strings" "time" ) @@ -2736,113 +2734,6 @@ func (c *CLI) AddCustomModel(cmd *Command) (ResponseIf, error) { } -// Context related commands - -// CECat handles the cat command - shows content using Context Engine -func (c *CLI) CECat(cmd *Command) (ResponseIf, error) { - if c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].APIToken == nil && c.APIServerClientMap[c.Config.APIClientConfig.CurrentAPIServer].LoginToken == nil { - return nil, fmt.Errorf("API token not set. Please login first") - } - if c.Config.CLIMode != APIMode { - return nil, fmt.Errorf("this command is only allowed in USER mode") - } - - path, ok := cmd.Params["path"].(string) - if !ok { - return nil, fmt.Errorf("fail to convert 'path' to string") - } - - // Execute cat command through Filesystem Engine - ctx := context.Background() - content, err := c.ContextEngine.Cat(ctx, path) - if err != nil { - return nil, err - } - - // Convert to response - var response ContextCatResponse - response.OutputFormat = c.outputFormat - response.Code = 0 - response.Content = string(content) - - return &response, nil -} - -// CEList handles the ls command - lists nodes using Context Engine -func (c *CLI) CEList(cmd *Command) (ResponseIf, error) { - // Get path from command params, default to "datasets" - path, _ := cmd.Params["path"].(string) - if path == "" { - path = "datasets" - } - - // Parse options - opts := &ce.ListOptions{} - if recursive, ok := cmd.Params["recursive"].(bool); ok { - opts.Recursive = recursive - } - if limit, ok := cmd.Params["limit"].(int); ok { - opts.Limit = limit - } - if offset, ok := cmd.Params["offset"].(int); ok { - opts.Offset = offset - } - - // Execute list command through Filesystem Engine - ctx := context.Background() - result, err := c.ContextEngine.List(ctx, path, opts) - if err != nil { - return nil, err - } - - // Convert to response - var response ContextListResponse - response.OutputFormat = c.outputFormat - response.Code = 0 - response.Data = ce.FormatNodes(result.Nodes, string(c.outputFormat)) - - return &response, nil -} - -// CESearch handles the search command using Context Engine -func (c *CLI) CESearch(cmd *Command) (ResponseIf, error) { - // Get path and query from command params - path, _ := cmd.Params["path"].(string) - if path == "" { - path = "datasets" - } - query, _ := cmd.Params["query"].(string) - - // Parse options - opts := &ce.SearchOptions{ - Query: query, - } - if limit, ok := cmd.Params["limit"].(int); ok { - opts.Limit = limit - } - if offset, ok := cmd.Params["offset"].(int); ok { - opts.Offset = offset - } - if recursive, ok := cmd.Params["recursive"].(bool); ok { - opts.Recursive = recursive - } - - // Execute search command through Filesystem Engine - ctx := context.Background() - result, err := c.ContextEngine.Search(ctx, path, opts) - if err != nil { - return nil, err - } - - // Convert to response - var response ContextSearchResponse - response.OutputFormat = c.outputFormat - response.Code = 0 - response.Total = result.Total - response.Data = ce.FormatNodes(result.Nodes, string(c.outputFormat)) - - return &response, nil -} // InsertChunksFromFile inserts chunks from a JSON file func (c *CLI) InsertChunksFromFile(cmd *Command) (ResponseIf, error) {