Clean CLI for filesystem (#15838)

### Type of change

- [x] Refactoring
This commit is contained in:
Yingfeng
2026-06-09 17:00:10 +08:00
committed by GitHub
parent 287a4cfd2b
commit 01a2a44766
6 changed files with 91 additions and 643 deletions

View File

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

View File

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

View File

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

View File

@@ -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 <path>
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 <query> or search <query> in <path>
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 <path>" 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
}

View File

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

View File

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