diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a07c148a63..4845e58a51 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -181,18 +181,18 @@ PY=python3 NGINX_CONF_DIR="/etc/nginx/conf.d" if [ -n "$API_PROXY_SCHEME" ]; then if [[ "${API_PROXY_SCHEME}" == "hybrid" ]]; then - mv -f "$NGINX_CONF_DIR/ragflow.conf.hybrid" "$NGINX_CONF_DIR/ragflow.conf" + cp -f "$NGINX_CONF_DIR/ragflow.conf.hybrid" "$NGINX_CONF_DIR/ragflow.conf" echo "Applied nginx config: ragflow.conf.hybrid" elif [[ "${API_PROXY_SCHEME}" == "go" ]]; then - mv -f "$NGINX_CONF_DIR/ragflow.conf.golang" "$NGINX_CONF_DIR/ragflow.conf" + cp -f "$NGINX_CONF_DIR/ragflow.conf.golang" "$NGINX_CONF_DIR/ragflow.conf" echo "Applied nginx config: ragflow.conf.golang (default)" else - mv -f "$NGINX_CONF_DIR/ragflow.conf.python" "$NGINX_CONF_DIR/ragflow.conf" + cp -f "$NGINX_CONF_DIR/ragflow.conf.python" "$NGINX_CONF_DIR/ragflow.conf" echo "Applied nginx config: ragflow.conf.python" fi else # Default to python backend - mv -f "$NGINX_CONF_DIR/ragflow.conf.python" "$NGINX_CONF_DIR/ragflow.conf" + cp -f "$NGINX_CONF_DIR/ragflow.conf.python" "$NGINX_CONF_DIR/ragflow.conf" echo "Default: applied nginx config: ragflow.conf.python" fi diff --git a/internal/admin/service.go b/internal/admin/service.go index de20805bc0..e8fb31129a 100644 --- a/internal/admin/service.go +++ b/internal/admin/service.go @@ -870,19 +870,117 @@ func (s *Service) GetUserAgents(username string) ([]map[string]interface{}, erro // ListUserAPITokens get user API keys func (s *Service) ListUserAPITokens(username string) ([]map[string]interface{}, error) { - // TODO: Implement get API keys - return []map[string]interface{}{}, nil + // 1. Get user details + user, err := s.userDAO.GetByEmail(username) + if err != nil { + return nil, fmt.Errorf("user not found: %w", err) + } + + // 2. Get user's tenants + userTenants, err := s.userTenantDAO.GetByUserID(user.ID) + if err != nil || len(userTenants) == 0 { + return nil, fmt.Errorf("tenant not found") + } + + tenantID := userTenants[0].TenantID + + // 3. Get API tokens by tenant ID + tokens, err := s.apiTokenDAO.GetByTenantID(tenantID) + if err != nil { + return nil, fmt.Errorf("failed to get API tokens: %w", err) + } + + // 4. Convert to map slice + result := make([]map[string]interface{}, 0, len(tokens)) + for _, token := range tokens { + result = append(result, map[string]interface{}{ + "tenant_id": token.TenantID, + "token": token.Token, + "beta": token.Beta, + "dialog_id": token.DialogID, + "source": token.Source, + "create_time": token.CreateTime, + "create_date": token.CreateDate, + "update_time": token.UpdateTime, + "update_date": token.UpdateDate, + }) + } + + return result, nil } // GenerateUserAPIToken generate API key for user func (s *Service) GenerateUserAPIToken(username string) (map[string]interface{}, error) { - // TODO: Implement generate API key - return map[string]interface{}{}, nil + // 1. Get user details + user, err := s.userDAO.GetByEmail(username) + if err != nil { + return nil, fmt.Errorf("user not found: %w", err) + } + + // 2. Get user's tenants + userTenants, err := s.userTenantDAO.GetByUserID(user.ID) + if err != nil || len(userTenants) == 0 { + return nil, fmt.Errorf("tenant not found") + } + + tenantID := userTenants[0].TenantID + + // 3. Generate API token + key := utility.GenerateAPIToken() + beta := utility.GenerateBetaAPIToken(key) + now := time.Now() + nowUnix := now.Unix() + + apiToken := &model.APIToken{ + TenantID: tenantID, + Token: key, + Beta: &beta, + } + apiToken.CreateTime = &nowUnix + apiToken.CreateDate = &now + + // 4. Save API token + if err := s.apiTokenDAO.Create(apiToken); err != nil { + return nil, fmt.Errorf("failed to generate API key: %w", err) + } + + return map[string]interface{}{ + "tenant_id": tenantID, + "token": key, + "beta": beta, + "create_time": apiToken.CreateTime, + "create_date": apiToken.CreateDate, + "update_time": apiToken.UpdateTime, + "update_date": apiToken.UpdateDate, + }, nil } // DeleteUserAPIToken delete user API key func (s *Service) DeleteUserAPIToken(username, key string) error { - // TODO: Implement delete API key + // 1. Get user details + user, err := s.userDAO.GetByEmail(username) + if err != nil { + return fmt.Errorf("user not found: %w", err) + } + + // 2. Get user's tenants + userTenants, err := s.userTenantDAO.GetByUserID(user.ID) + if err != nil || len(userTenants) == 0 { + return fmt.Errorf("tenant not found") + } + + tenantID := userTenants[0].TenantID + + // 3. Delete API token + rowsAffected, err := s.apiTokenDAO.DeleteByTenantIDAndToken(tenantID, key) + if err != nil { + return fmt.Errorf("failed to delete API key: %w", err) + } + + if rowsAffected == 0 { + return fmt.Errorf("API key not found or could not be deleted") + } + return nil } diff --git a/internal/cli/admin_command.go b/internal/cli/admin_command.go index ed6dac4583..001ef6758e 100644 --- a/internal/cli/admin_command.go +++ b/internal/cli/admin_command.go @@ -983,8 +983,8 @@ func (c *RAGFlowClient) ShowUserPermission(cmd *Command) (ResponseIf, error) { return &result, nil } -// CreateAdminToken generates an API token for a user (admin mode only) -func (c *RAGFlowClient) CreateAdminToken(cmd *Command) (ResponseIf, error) { +// GenerateAdminToken generates an API token for a user (admin mode only) +func (c *RAGFlowClient) GenerateAdminToken(cmd *Command) (ResponseIf, error) { if c.ServerType != "admin" { return nil, fmt.Errorf("this command is only allowed in ADMIN mode") } @@ -1003,7 +1003,7 @@ func (c *RAGFlowClient) CreateAdminToken(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("failed to generate token: HTTP %d, body: %s", resp.StatusCode, string(resp.Body)) } - var result CommonResponse + var result CommonDataResponse if err = json.Unmarshal(resp.Body, &result); err != nil { return nil, fmt.Errorf("generate token failed: invalid JSON (%w)", err) } @@ -1012,10 +1012,9 @@ func (c *RAGFlowClient) CreateAdminToken(cmd *Command) (ResponseIf, error) { return nil, fmt.Errorf("%s", result.Message) } - // Remove extra field from data - for _, item := range result.Data { - delete(item, "extra") - } + delete(result.Data, "update_date") + delete(result.Data, "update_time") + delete(result.Data, "create_time") result.Duration = resp.Duration return &result, nil @@ -1052,7 +1051,11 @@ func (c *RAGFlowClient) ListAdminTokens(cmd *Command) (ResponseIf, error) { // Remove extra field from data for _, item := range result.Data { - delete(item, "extra") + delete(item, "dialog_id") + delete(item, "source") + delete(item, "update_date") + delete(item, "update_time") + delete(item, "create_time") } result.Duration = resp.Duration diff --git a/internal/cli/admin_parser.go b/internal/cli/admin_parser.go new file mode 100644 index 0000000000..1cfd96a277 --- /dev/null +++ b/internal/cli/admin_parser.go @@ -0,0 +1,1494 @@ +package cli + +import "fmt" + +// Command parsers +func (p *Parser) parseAdminLoginUser() (*Command, error) { + cmd := NewCommand("login_user") + + p.nextToken() // consume LOGIN + if p.curToken.Type != TokenUser { + return nil, fmt.Errorf("expected USER after LOGIN") + } + + p.nextToken() + email, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["email"] = email + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +func (p *Parser) parseAdminPingServer() (*Command, error) { + cmd := NewCommand("ping") + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminRegisterCommand() (*Command, error) { + cmd := NewCommand("register_user") + + if err := p.expectPeek(TokenUser); err != nil { + return nil, err + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["user_name"] = userName + + p.nextToken() + if p.curToken.Type != TokenAs { + return nil, fmt.Errorf("expected AS") + } + + p.nextToken() + nickname, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["nickname"] = nickname + + p.nextToken() + if p.curToken.Type != TokenPassword { + return nil, fmt.Errorf("expected PASSWORD") + } + + p.nextToken() + password, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["password"] = password + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +func (p *Parser) parseAdminListCommand() (*Command, error) { + p.nextToken() // consume LIST + + switch p.curToken.Type { + case TokenServices: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_services"), nil + case TokenUsers: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_users"), nil + case TokenRoles: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_roles"), nil + case TokenVars: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_variables"), nil + case TokenConfigs: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_configs"), nil + case TokenEnvs: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_environments"), nil + case TokenDatasets: + return p.parseAdminListDatasets() + case TokenAgents: + return p.parseAdminListAgents() + case TokenTokens: + return p.parseAdminListTokens() + case TokenModel: + return p.parseAdminListModelProviders() + case TokenDefault: + return p.parseAdminListDefaultModels() + case TokenChats: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_user_chats"), nil + case TokenFiles: + return p.parseAdminListFiles() + default: + return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminListDatasets() (*Command, error) { + cmd := NewCommand("list_user_datasets") + p.nextToken() // consume DATASETS + + if p.curToken.Type == TokenSemicolon { + return cmd, nil + } + + if p.curToken.Type == TokenOf { + p.nextToken() + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd = NewCommand("list_datasets") + cmd.Params["user_name"] = userName + p.nextToken() + } + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminListAgents() (*Command, error) { + p.nextToken() // consume AGENTS + + if p.curToken.Type == TokenSemicolon { + return NewCommand("list_user_agents"), nil + } + + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("list_agents") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminListTokens() (*Command, error) { + p.nextToken() // consume TOKENS + cmd := NewCommand("list_tokens") + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminListModelProviders() (*Command, error) { + p.nextToken() // consume MODEL + if p.curToken.Type != TokenProviders { + return nil, fmt.Errorf("expected PROVIDERS") + } + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_user_model_providers"), nil +} + +func (p *Parser) parseAdminListDefaultModels() (*Command, error) { + p.nextToken() // consume DEFAULT + if p.curToken.Type != TokenModels { + return nil, fmt.Errorf("expected MODELS") + } + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_user_default_models"), nil +} + +func (p *Parser) parseAdminListFiles() (*Command, error) { + p.nextToken() // consume FILES + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + if p.curToken.Type != TokenDataset { + return nil, fmt.Errorf("expected DATASET") + } + p.nextToken() + + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("list_user_dataset_files") + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminShowCommand() (*Command, error) { + p.nextToken() // consume SHOW + + switch p.curToken.Type { + case TokenVersion: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("show_version"), nil + case TokenToken: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("show_token"), nil + case TokenCurrent: + p.nextToken() + if p.curToken.Type != TokenUser { + return nil, fmt.Errorf("expected USER after CURRENT") + } + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("show_current_user"), nil + case TokenUser: + return p.parseShowUser() + case TokenRole: + return p.parseShowRole() + case TokenVar: + return p.parseShowVariable() + case TokenService: + return p.parseShowService() + default: + return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminShowUser() (*Command, error) { + p.nextToken() // consume USER + + // Check for PERMISSION + if p.curToken.Type == TokenPermission { + p.nextToken() + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd := NewCommand("show_user_permission") + cmd.Params["user_name"] = userName + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil + } + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_user") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminShowRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_role") + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminShowVariable() (*Command, error) { + p.nextToken() // consume VAR + varName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_variable") + cmd.Params["var_name"] = varName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminShowService() (*Command, error) { + p.nextToken() // consume SERVICE + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminCreateCommand() (*Command, error) { + p.nextToken() // consume CREATE + + switch p.curToken.Type { + case TokenUser: + return p.parseCreateUser() + case TokenRole: + return p.parseCreateRole() + case TokenModel: + return p.parseCreateModelProvider() + case TokenDataset: + return p.parseCreateDataset() + case TokenChat: + return p.parseCreateChat() + case TokenToken: + return p.parseCreateToken() + default: + return nil, fmt.Errorf("unknown CREATE target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminCreateToken() (*Command, error) { + p.nextToken() // consume TOKEN + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return NewCommand("create_token"), nil +} + +func (p *Parser) parseAdminCreateUser() (*Command, error) { + p.nextToken() // consume USER + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + password, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_user") + cmd.Params["user_name"] = userName + cmd.Params["password"] = password + cmd.Params["role"] = "user" + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminCreateRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_role") + cmd.Params["role_name"] = roleName + + p.nextToken() + if p.curToken.Type == TokenDescription { + p.nextToken() + description, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["description"] = description + p.nextToken() + } + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminCreateModelProvider() (*Command, error) { + p.nextToken() // consume MODEL + if p.curToken.Type != TokenProvider { + return nil, fmt.Errorf("expected PROVIDER") + } + p.nextToken() + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + providerKey, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_model_provider") + cmd.Params["provider_name"] = providerName + cmd.Params["provider_key"] = providerKey + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminCreateDataset() (*Command, error) { + p.nextToken() // consume DATASET + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenWith { + return nil, fmt.Errorf("expected WITH") + } + p.nextToken() + if p.curToken.Type != TokenEmbedding { + return nil, fmt.Errorf("expected EMBEDDING") + } + p.nextToken() + + embedding, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + cmd := NewCommand("create_user_dataset") + cmd.Params["dataset_name"] = datasetName + cmd.Params["embedding"] = embedding + + if p.curToken.Type == TokenParser { + p.nextToken() + parserType, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["parser_type"] = parserType + p.nextToken() + } else if p.curToken.Type == TokenPipeline { + p.nextToken() + pipeline, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["pipeline"] = pipeline + p.nextToken() + } else { + return nil, fmt.Errorf("expected PARSER or PIPELINE") + } + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminCreateChat() (*Command, error) { + p.nextToken() // consume CHAT + chatName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_user_chat") + cmd.Params["chat_name"] = chatName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminDropCommand() (*Command, error) { + p.nextToken() // consume DROP + + switch p.curToken.Type { + case TokenUser: + return p.parseDropUser() + case TokenRole: + return p.parseDropRole() + case TokenModel: + return p.parseDropModelProvider() + case TokenDataset: + return p.parseDropDataset() + case TokenChat: + return p.parseDropChat() + case TokenToken: + return p.parseDropToken() + default: + return nil, fmt.Errorf("unknown DROP target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminDropToken() (*Command, error) { + p.nextToken() // consume TOKEN + + tokenValue, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_token") + cmd.Params["token"] = tokenValue + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminDropUser() (*Command, error) { + p.nextToken() // consume USER + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_user") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminDropRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_role") + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminDropModelProvider() (*Command, error) { + p.nextToken() // consume MODEL + if p.curToken.Type != TokenProvider { + return nil, fmt.Errorf("expected PROVIDER") + } + p.nextToken() + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_model_provider") + cmd.Params["provider_name"] = providerName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminDropDataset() (*Command, error) { + p.nextToken() // consume DATASET + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_user_dataset") + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminDropChat() (*Command, error) { + p.nextToken() // consume CHAT + chatName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_user_chat") + cmd.Params["chat_name"] = chatName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminAlterCommand() (*Command, error) { + p.nextToken() // consume ALTER + + switch p.curToken.Type { + case TokenUser: + return p.parseAlterUser() + case TokenRole: + return p.parseAlterRole() + default: + return nil, fmt.Errorf("unknown ALTER target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminAlterUser() (*Command, error) { + p.nextToken() // consume USER + + if p.curToken.Type == TokenActive { + return p.parseActivateUser() + } + + if p.curToken.Type == TokenPassword { + p.nextToken() + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + password, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("alter_user") + cmd.Params["user_name"] = userName + cmd.Params["password"] = password + + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil + } + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenSet { + return nil, fmt.Errorf("expected SET") + } + p.nextToken() + if p.curToken.Type != TokenRole { + return nil, fmt.Errorf("expected ROLE") + } + p.nextToken() + + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("alter_user_role") + cmd.Params["user_name"] = userName + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminActivateUser() (*Command, error) { + p.nextToken() // consume ACTIVE + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + // Accept 'on' or 'off' as identifier + status := p.curToken.Value + if status != "on" && status != "off" { + return nil, fmt.Errorf("expected 'on' or 'off', got %s", p.curToken.Value) + } + + cmd := NewCommand("activate_user") + cmd.Params["user_name"] = userName + cmd.Params["activate_status"] = status + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminAlterRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenSet { + return nil, fmt.Errorf("expected SET") + } + p.nextToken() + if p.curToken.Type != TokenDescription { + return nil, fmt.Errorf("expected DESCRIPTION") + } + p.nextToken() + + description, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("alter_role") + cmd.Params["role_name"] = roleName + cmd.Params["description"] = description + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminGrantCommand() (*Command, error) { + p.nextToken() // consume GRANT + + if p.curToken.Type == TokenAdmin { + return p.parseGrantAdmin() + } + + return p.parseGrantPermission() +} + +func (p *Parser) parseAdminGrantAdmin() (*Command, error) { + p.nextToken() // consume ADMIN + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("grant_admin") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminGrantPermission() (*Command, error) { + actions, err := p.parseIdentifierList() + if err != nil { + return nil, err + } + + if p.curToken.Type != TokenOn { + return nil, fmt.Errorf("expected ON") + } + p.nextToken() + + resource, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenTo { + return nil, fmt.Errorf("expected TO") + } + p.nextToken() + if p.curToken.Type != TokenRole { + return nil, fmt.Errorf("expected ROLE") + } + p.nextToken() + + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("grant_permission") + cmd.Params["actions"] = actions + cmd.Params["resource"] = resource + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminRevokeCommand() (*Command, error) { + p.nextToken() // consume REVOKE + + if p.curToken.Type == TokenAdmin { + return p.parseRevokeAdmin() + } + + return p.parseRevokePermission() +} + +func (p *Parser) parseAdminRevokeAdmin() (*Command, error) { + p.nextToken() // consume ADMIN + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("revoke_admin") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminRevokePermission() (*Command, error) { + actions, err := p.parseIdentifierList() + if err != nil { + return nil, err + } + + if p.curToken.Type != TokenOn { + return nil, fmt.Errorf("expected ON") + } + p.nextToken() + + resource, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenFrom { + return nil, fmt.Errorf("expected FROM") + } + p.nextToken() + if p.curToken.Type != TokenRole { + return nil, fmt.Errorf("expected ROLE") + } + p.nextToken() + + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("revoke_permission") + cmd.Params["actions"] = actions + cmd.Params["resource"] = resource + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminIdentifierList() ([]string, error) { + var list []string + + ident, err := p.parseIdentifier() + if err != nil { + return nil, err + } + list = append(list, ident) + p.nextToken() + + for p.curToken.Type == TokenComma { + p.nextToken() + ident, err := p.parseIdentifier() + if err != nil { + return nil, err + } + list = append(list, ident) + p.nextToken() + } + + return list, nil +} + +func (p *Parser) parseAdminSetCommand() (*Command, error) { + p.nextToken() // consume SET + + if p.curToken.Type == TokenVar { + return p.parseSetVariable() + } + if p.curToken.Type == TokenDefault { + return p.parseSetDefault() + } + if p.curToken.Type == TokenToken { + return p.parseSetToken() + } + + return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value) +} + +func (p *Parser) parseAdminSetVariable() (*Command, error) { + p.nextToken() // consume VAR + varName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + varValue, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("set_variable") + cmd.Params["var_name"] = varName + cmd.Params["var_value"] = varValue + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminSetDefault() (*Command, error) { + p.nextToken() // consume DEFAULT + + var modelType, modelID string + + switch p.curToken.Type { + case TokenLLM: + modelType = "llm_id" + case TokenVLM: + modelType = "img2txt_id" + case TokenEmbedding: + modelType = "embd_id" + case TokenReranker: + modelType = "reranker_id" + case TokenASR: + modelType = "asr_id" + case TokenTTS: + modelType = "tts_id" + default: + return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value) + } + + p.nextToken() + id, err := p.parseQuotedString() + if err != nil { + return nil, err + } + modelID = id + + cmd := NewCommand("set_default_model") + cmd.Params["model_type"] = modelType + cmd.Params["model_id"] = modelID + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminSetToken() (*Command, error) { + p.nextToken() // consume TOKEN + + tokenValue, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("set_token") + cmd.Params["token"] = tokenValue + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminResetCommand() (*Command, error) { + p.nextToken() // consume RESET + + if p.curToken.Type != TokenDefault { + return nil, fmt.Errorf("expected DEFAULT") + } + p.nextToken() + + var modelType string + switch p.curToken.Type { + case TokenLLM: + modelType = "llm_id" + case TokenVLM: + modelType = "img2txt_id" + case TokenEmbedding: + modelType = "embd_id" + case TokenReranker: + modelType = "reranker_id" + case TokenASR: + modelType = "asr_id" + case TokenTTS: + modelType = "tts_id" + default: + return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value) + } + + cmd := NewCommand("reset_default_model") + cmd.Params["model_type"] = modelType + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminGenerateCommand() (*Command, error) { + p.nextToken() // consume GENERATE + if p.curToken.Type != TokenToken { + return nil, fmt.Errorf("expected TOKEN") + } + p.nextToken() + if p.curToken.Type != TokenFor { + return nil, fmt.Errorf("expected FOR") + } + p.nextToken() + if p.curToken.Type != TokenUser { + return nil, fmt.Errorf("expected USER") + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("generate_token") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminImportCommand() (*Command, error) { + p.nextToken() // consume IMPORT + documentPaths, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenInto { + return nil, fmt.Errorf("expected INTO") + } + p.nextToken() + if p.curToken.Type != TokenDataset { + return nil, fmt.Errorf("expected DATASET") + } + p.nextToken() + + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("import_docs_into_dataset") + cmd.Params["document_paths"] = documentPaths + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminSearchCommand() (*Command, error) { + p.nextToken() // consume SEARCH + question, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenOn { + return nil, fmt.Errorf("expected ON") + } + p.nextToken() + if p.curToken.Type != TokenDatasets { + return nil, fmt.Errorf("expected DATASETS") + } + p.nextToken() + + datasets, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("search_on_datasets") + cmd.Params["question"] = question + cmd.Params["datasets"] = datasets + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminParseCommand() (*Command, error) { + p.nextToken() // consume PARSE + + if p.curToken.Type == TokenDataset { + return p.parseParseDataset() + } + + return p.parseParseDocs() +} + +func (p *Parser) parseAdminParseDataset() (*Command, error) { + p.nextToken() // consume DATASET + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + var method string + if p.curToken.Type == TokenSync { + method = "sync" + } else if p.curToken.Type == TokenAsync { + method = "async" + } else { + return nil, fmt.Errorf("expected SYNC or ASYNC") + } + + cmd := NewCommand("parse_dataset") + cmd.Params["dataset_name"] = datasetName + cmd.Params["method"] = method + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminParseDocs() (*Command, error) { + documentNames, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + if p.curToken.Type != TokenDataset { + return nil, fmt.Errorf("expected DATASET") + } + p.nextToken() + + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("parse_dataset_docs") + cmd.Params["document_names"] = documentNames + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminBenchmarkCommand() (*Command, error) { + cmd := NewCommand("benchmark") + + p.nextToken() // consume BENCHMARK + concurrency, err := p.parseNumber() + if err != nil { + return nil, err + } + cmd.Params["concurrency"] = concurrency + + p.nextToken() + iterations, err := p.parseNumber() + if err != nil { + return nil, err + } + cmd.Params["iterations"] = iterations + + p.nextToken() + // Parse user_statement + nestedCmd, err := p.parseUserStatement() // Not only user statement + if err != nil { + return nil, err + } + cmd.Params["command"] = nestedCmd + + return cmd, nil +} + +func (p *Parser) parseAdminUserStatement() (*Command, error) { + switch p.curToken.Type { + case TokenPing: + return p.parsePingServer() + case TokenShow: + return p.parseShowCommand() + case TokenCreate: + return p.parseCreateCommand() + case TokenDrop: + return p.parseDropCommand() + case TokenSet: + return p.parseSetCommand() + case TokenUnset: + return p.parseUnsetCommand() + case TokenReset: + return p.parseResetCommand() + case TokenList: + return p.parseListCommand() + case TokenParse: + return p.parseParseCommand() + case TokenImport: + return p.parseImportCommand() + case TokenSearch: + return p.parseSearchCommand() + default: + return nil, fmt.Errorf("invalid user statement: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAdminStartupCommand() (*Command, error) { + p.nextToken() // consume STARTUP + if p.curToken.Type != TokenService { + return nil, fmt.Errorf("expected SERVICE") + } + p.nextToken() + + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("startup_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminShutdownCommand() (*Command, error) { + p.nextToken() // consume SHUTDOWN + if p.curToken.Type != TokenService { + return nil, fmt.Errorf("expected SERVICE") + } + p.nextToken() + + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("shutdown_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminRestartCommand() (*Command, error) { + p.nextToken() // consume RESTART + if p.curToken.Type != TokenService { + return nil, fmt.Errorf("expected SERVICE") + } + p.nextToken() + + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("restart_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAdminUnsetCommand() (*Command, error) { + p.nextToken() // consume UNSET + + if p.curToken.Type != TokenToken { + return nil, fmt.Errorf("expected TOKEN after UNSET") + } + p.nextToken() + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("unset_token"), nil +} diff --git a/internal/cli/cli.go b/internal/cli/cli.go index fbedb8f0fb..8ca48fb6dd 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -18,7 +18,6 @@ package cli import ( "errors" - "flag" "fmt" "os" "os/signal" @@ -34,21 +33,21 @@ import ( // ConfigFile represents the rf.yml configuration file structure type ConfigFile struct { Host string `yaml:"host"` - User string `yaml:"user"` - Password string `yaml:"password"` APIToken string `yaml:"api_token"` + UserName string `yaml:"user_name"` + Password string `yaml:"password"` } // ConnectionArgs holds the parsed command line arguments type ConnectionArgs struct { - Host string - Port int - Password string - Key string - Type string - Username string - Command string - ShowHelp bool + Host string + Port int + Password string + APIToken string + UserName string + Command string + ShowHelp bool + AdminMode bool } // LoadDefaultConfigFile reads the rf.yml file from current directory if it exists @@ -109,23 +108,16 @@ func parseHostPort(hostPort string) (string, int, error) { // ParseConnectionArgs parses command line arguments similar to Python's parse_connection_args func ParseConnectionArgs(args []string) (*ConnectionArgs, error) { - // First, scan args to check for help and config file + // First, scan args to check for help, config file, and admin mode var configFilePath string - var hasOtherFlags bool for i := 0; i < len(args); i++ { arg := args[i] - if arg == "--help" { + if arg == "--help" || arg == "-help" { return &ConnectionArgs{ShowHelp: true}, nil - } else if arg == "-f" && i+1 < len(args) { + } else if (arg == "-f" || arg == "--config") && i+1 < len(args) { configFilePath = args[i+1] i++ - } else if strings.HasPrefix(arg, "-") && arg != "-f" { - // Check if it's a flag (not a command) - if arg == "-h" || arg == "-p" || arg == "-w" || arg == "-k" || - arg == "-u" || arg == "-admin" || arg == "-user" { - hasOtherFlags = true - } } } @@ -147,115 +139,116 @@ func ParseConnectionArgs(args []string) (*ConnectionArgs, error) { } } - if config != nil { - if hasOtherFlags { - return nil, fmt.Errorf("cannot use command line flags (-h, -p, -w, -k, -u, -admin, -user) when using config file. Please use config file or command line flags, not both") - } + // Parse arguments manually to support both short and long forms + // and to handle priority: command line > config file > defaults - return buildArgsFromConfig(config, args) - } - // Create a new flag set - fs := flag.NewFlagSet("ragflow_cli", flag.ContinueOnError) - - // Define flags - host := fs.String("h", "127.0.0.1", "Admin or RAGFlow service host") - port := fs.Int("p", -1, "Admin or RAGFlow service port (default: 9381 for admin, 9380 for user)") - password := fs.String("w", "", "Superuser password") - key := fs.String("k", "", "API key for authentication") - _ = fs.String("f", "", "Path to config file (YAML format)") // Already parsed above - _ = fs.Bool("admin", false, "Run in admin mode (default)") - userMode := fs.Bool("user", false, "Run in user mode") - username := fs.String("u", "", "Username (email). In admin mode defaults to admin@ragflow.io, in user mode required") - - // Parse the arguments - if err = fs.Parse(args); err != nil { - return nil, fmt.Errorf("failed to parse arguments: %v", err) - } - - // Otherwise, use command line flags - return buildArgsFromFlags(host, port, password, key, userMode, username, fs.Args()) -} - -// buildArgsFromConfig builds ConnectionArgs from config file -func buildArgsFromConfig(config *ConfigFile, remainingArgs []string) (*ConnectionArgs, error) { + // Build result from config file first (if exists), then override with command line flags result := &ConnectionArgs{} - // Parse host:port from config file - if config.Host != "" { - host, port, err := parseHostPort(config.Host) - if err != nil { - return nil, fmt.Errorf("invalid host in config file: %v", err) + // Get non-flag arguments (command to execute) + var nonFlagArgs []string + + // Apply config file values first (lower priority) + if config != nil { + // Parse host:port from config file + if config.Host != "" { + h, port, err := parseHostPort(config.Host) + if err != nil { + return nil, fmt.Errorf("invalid host in config file: %v", err) + } + result.Host = h + result.Port = port } - result.Host = host - result.Port = port - } else { + result.UserName = config.UserName + result.Password = config.Password + result.APIToken = config.APIToken + } + + // Override with command line flags (higher priority) + // Handle both short and long forms manually + for i := 0; i < len(args); i++ { + arg := args[i] + switch arg { + case "-h", "--host": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + hostVal := args[i+1] + h, port, err := parseHostPort(hostVal) + if err != nil { + return nil, fmt.Errorf("invalid host format: %v", err) + } + result.Host = h + result.Port = port + i++ + } + case "-t", "--token": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + result.APIToken = args[i+1] + i++ + } + case "-u", "--user": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + result.UserName = args[i+1] + i++ + } + case "-p", "--password": + if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + result.Password = args[i+1] + i++ + } + case "-f", "--config": + // Skip config file path (already parsed) + if i+1 < len(args) { + i++ + } + case "--admin", "-admin": + result.AdminMode = true + case "--help", "-help": + // Already handled above + continue + default: + // Non-flag argument (command) + if !strings.HasPrefix(arg, "-") { + nonFlagArgs = append(nonFlagArgs, arg) + } + } + } + + // Set defaults if not provided + if result.Host == "" { result.Host = "127.0.0.1" } - - // Apply auth info from config - result.Username = config.User - result.Password = config.Password - result.Key = config.APIToken - - // Determine mode: if config has auth info, use user mode - if config.User != "" || config.APIToken != "" { - result.Type = "user" - } else { - result.Type = "admin" - result.Username = "admin@ragflow.io" - } - - // Set default port if not specified in config - if result.Port == -1 { - if result.Type == "admin" { - result.Port = 9381 - } else { - result.Port = 9380 - } - } - - // Get command from remaining args (no need for quotes or semicolon) - if len(remainingArgs) > 0 { - result.Command = strings.Join(remainingArgs, " ") + ";" - } - - return result, nil -} - -// buildArgsFromFlags builds ConnectionArgs from command line flags -func buildArgsFromFlags(host *string, port *int, password *string, key *string, userMode *bool, username *string, remainingArgs []string) (*ConnectionArgs, error) { - result := &ConnectionArgs{ - Host: *host, - Port: *port, - Password: *password, - Key: *key, - Username: *username, - } - - // Determine mode - if *userMode { - result.Type = "user" - } else { - result.Type = "admin" - } - - // Set default port based on type if not specified - if result.Port == -1 { - if result.Type == "admin" { + if result.Port == -1 || result.Port == 0 { + if result.AdminMode { result.Port = 9383 } else { result.Port = 9384 } } - // Determine username based on mode - if result.Type == "admin" && result.Username == "" { - result.Username = "admin@ragflow.io" + if result.UserName == "" && result.Password != "" { + return nil, fmt.Errorf("username (-u/--user) is required when using password (-p/--password)") } - // Get command from remaining args (no need for quotes or semicolon) - if len(remainingArgs) > 0 { - result.Command = strings.Join(remainingArgs, " ") + ";" + if result.AdminMode { + result.APIToken = "" + if result.UserName == "" { + result.UserName = "admin@ragflow.io" + result.Password = "" + } + } else { + // For user mode + // Validate mutual exclusivity: -t and (-u, -p) are mutually exclusive + hasToken := result.APIToken != "" + hasUserPass := result.UserName != "" || result.Password != "" + + if hasToken && hasUserPass { + return nil, fmt.Errorf("cannot use both API token (-t/--token) and username/password (-u/--user, -p/--password). Please use one authentication method") + } + } + + // Get command from remaining args (non-flag arguments) + if len(nonFlagArgs) > 0 { + result.Command = strings.Join(nonFlagArgs, " ") } return result, nil @@ -268,31 +261,40 @@ func PrintUsage() { Usage: ragflow_cli [options] [command] Options: - -h string Admin or RAGFlow service host (default "127.0.0.1") - -p int Admin or RAGFlow service port (default 9381 for admin, 9380 for user) - -w string Superuser password - -k string API key for authentication - -f string Path to config file (YAML format) - -admin Run in admin mode (default) - -user Run in user mode - -u string Username (email). In admin mode defaults to admin@ragflow.io - --help Show this help message + -h, --host string RAGFlow service address (host:port, default "127.0.0.1:9380") + -t, --token string API token for authentication + -u, --user string Username for authentication + -p, --password string Password for authentication + -f, --config string Path to config file (YAML format) + --admin, -admin Run in admin mode + --help Show this help message + +Mode: + --admin, -admin Run in admin mode (prompt: RAGFlow(admin)>) + Default is user mode (prompt: RAGFlow(user)>). + +Authentication: + You can authenticate using either: + 1. API token: -t or --token + 2. Username and password: -u/--user and -p/--password + Note: These two methods are mutually exclusive. Configuration File: The CLI will automatically read rf.yml from the current directory if it exists. - Use -f to specify a custom config file path. + Use -f or --config to specify a custom config file path. + Command line options override config file values. Config file format: host: 127.0.0.1:9380 - user: your-email@example.com - password: your-password api_token: your-api-token + user_name: your-username + password: your-password - When using a config file, you cannot use other command line flags except -help. - The command line is only for the SQL command. + Note: api_token and user_name/password are mutually exclusive in config file. Commands: SQL commands like: LOGIN USER 'email'; LIST USERS; etc. + If no command is provided, CLI runs in interactive mode. `) } @@ -322,10 +324,11 @@ func NewCLIWithArgs(args *ConnectionArgs) (*CLI, error) { // Create liner first line := liner.NewLiner() - // Determine server type + // Determine server type based on --admin or --user flag + // Default to "user" mode if not specified serverType := "user" - if args != nil && args.Type != "" { - serverType = args.Type + if args != nil && args.AdminMode { + serverType = "admin" } // Create client with password prompt using liner @@ -333,11 +336,19 @@ func NewCLIWithArgs(args *ConnectionArgs) (*CLI, error) { client.PasswordPrompt = line.PasswordPrompt // Apply connection arguments if provided - client.HTTPClient.Host = args.Host - client.HTTPClient.Port = args.Port + if args != nil { + client.HTTPClient.Host = args.Host + if args.Port > 0 { + client.HTTPClient.Port = args.Port + } + + if args.APIToken != "" { + client.HTTPClient.APIToken = args.APIToken + } + } // Set prompt based on server type - prompt := "RAGFlow> " + prompt := "RAGFlow(user)> " if serverType == "admin" { prompt = "RAGFlow(admin)> " } @@ -352,7 +363,8 @@ func NewCLIWithArgs(args *ConnectionArgs) (*CLI, error) { // Run starts the interactive CLI func (c *CLI) Run() error { - if c.args.Type == "admin" { + // If username is provided without password, prompt for password + if c.args != nil && c.args.UserName != "" && c.args.Password == "" && c.args.APIToken == "" { // Allow 3 attempts for password verification maxAttempts := 3 for attempt := 1; attempt <= maxAttempts; attempt++ { @@ -448,7 +460,7 @@ func (c *CLI) Run() error { func (c *CLI) execute(input string) error { p := NewParser(input) - cmd, err := p.Parse() + cmd, err := p.Parse(c.args.AdminMode) if err != nil { return err } @@ -490,7 +502,7 @@ func (c *CLI) handleMetaCommand(cmd *Command) error { fmt.Println("Switched to ADMIN mode") case "user": c.client.ServerType = "user" - c.prompt = "RAGFlow> " + c.prompt = "RAGFlow(user)> " fmt.Println("Switched to USER mode") case "host": if len(args) == 0 { @@ -616,7 +628,14 @@ func (c *CLI) VerifyAuth() error { return nil } - if c.args.Username == "" { + // If API token is provided, use it for authentication + if c.args.APIToken != "" { + // TODO: Implement API token authentication + return nil + } + + // Otherwise, use username/password authentication + if c.args.UserName == "" { return fmt.Errorf("username is required") } @@ -626,7 +645,7 @@ func (c *CLI) VerifyAuth() error { // Create login command with username and password cmd := NewCommand("login_user") - cmd.Params["email"] = c.args.Username + cmd.Params["email"] = c.args.UserName cmd.Params["password"] = c.args.Password _, err := c.client.ExecuteCommand(cmd) return err diff --git a/internal/cli/client.go b/internal/cli/client.go index 4801feacd1..e550453689 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -333,8 +333,8 @@ func (c *RAGFlowClient) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) { return c.ListDatasets(cmd) case "list_agents": return c.ListAgents(cmd) - case "create_token": - return c.CreateAdminToken(cmd) + case "generate_token": + return c.GenerateAdminToken(cmd) case "list_tokens": return c.ListAdminTokens(cmd) case "drop_token": diff --git a/internal/cli/http_client.go b/internal/cli/http_client.go index 0b8b78afdc..eb5ab1a804 100644 --- a/internal/cli/http_client.go +++ b/internal/cli/http_client.go @@ -84,16 +84,12 @@ func (c *HTTPClient) BuildURL(path string, useAPIBase bool) string { // Headers builds the request headers func (c *HTTPClient) Headers(authKind string, extra map[string]string) map[string]string { headers := make(map[string]string) - switch authKind { - case "api": - if c.APIToken != "" { - headers["Authorization"] = fmt.Sprintf("Bearer %s", c.APIToken) - } - case "web", "admin": - if c.LoginToken != "" { - headers["Authorization"] = c.LoginToken - } + if c.APIToken != "" { + headers["Authorization"] = fmt.Sprintf("Bearer %s", c.APIToken) + } else if c.LoginToken != "" { + headers["Authorization"] = c.LoginToken } + for k, v := range extra { headers[k] = v } diff --git a/internal/cli/parser.go b/internal/cli/parser.go index 397cf2d699..730d8892e2 100644 --- a/internal/cli/parser.go +++ b/internal/cli/parser.go @@ -45,7 +45,7 @@ func (p *Parser) nextToken() { } // Parse parses the input and returns a Command -func (p *Parser) Parse() (*Command, error) { +func (p *Parser) Parse(adminCommand bool) (*Command, error) { if p.curToken.Type == TokenEOF { return nil, nil } @@ -56,7 +56,7 @@ func (p *Parser) Parse() (*Command, error) { } // Parse SQL-like command - return p.parseSQLCommand() + return p.parseSQLCommand(adminCommand) } func (p *Parser) parseMetaCommand() (*Command, error) { @@ -76,10 +76,57 @@ func (p *Parser) parseMetaCommand() (*Command, error) { return cmd, nil } -func (p *Parser) parseSQLCommand() (*Command, error) { - if p.curToken.Type != TokenIdentifier && !isKeyword(p.curToken.Type) { - return nil, fmt.Errorf("expected command, got %s", p.curToken.Value) +func (p *Parser) parseAdminCommand() (*Command, error) { + + switch p.curToken.Type { + case TokenLogin: + return p.parseAdminLoginUser() + case TokenPing: + return p.parseAdminPingServer() + case TokenList: + return p.parseAdminListCommand() + case TokenShow: + return p.parseAdminShowCommand() + case TokenCreate: + return p.parseAdminCreateCommand() + case TokenDrop: + return p.parseAdminDropCommand() + case TokenAlter: + return p.parseAdminAlterCommand() + case TokenGrant: + return p.parseAdminGrantCommand() + case TokenRevoke: + return p.parseAdminRevokeCommand() + case TokenSet: + return p.parseAdminSetCommand() + case TokenUnset: + return p.parseAdminUnsetCommand() + case TokenReset: + return p.parseAdminResetCommand() + case TokenGenerate: + return p.parseAdminGenerateCommand() + case TokenImport: + return p.parseAdminImportCommand() + case TokenSearch: + return p.parseAdminSearchCommand() + case TokenParse: + return p.parseAdminParseCommand() + case TokenBenchmark: + return p.parseAdminBenchmarkCommand() + case TokenRegister: + return p.parseAdminRegisterCommand() + case TokenStartup: + return p.parseAdminStartupCommand() + case TokenShutdown: + return p.parseAdminShutdownCommand() + case TokenRestart: + return p.parseAdminRestartCommand() + default: + return nil, fmt.Errorf("unknown command: %s", p.curToken.Value) } +} + +func (p *Parser) parseUserCommand() (*Command, error) { switch p.curToken.Type { case TokenLogin: @@ -129,6 +176,18 @@ func (p *Parser) parseSQLCommand() (*Command, error) { } } +func (p *Parser) parseSQLCommand(adminCommand bool) (*Command, error) { + if p.curToken.Type != TokenIdentifier && !isKeyword(p.curToken.Type) { + return nil, fmt.Errorf("expected command, got %s", p.curToken.Value) + } + + if adminCommand { + return p.parseAdminCommand() + } + + return p.parseUserCommand() +} + func (p *Parser) expectPeek(tokenType int) error { if p.peekToken.Type != tokenType { return fmt.Errorf("expected %s, got %s", tokenTypeToString(tokenType), p.peekToken.Value) @@ -174,1536 +233,6 @@ func (p *Parser) parseNumber() (int, error) { return strconv.Atoi(p.curToken.Value) } -// Command parsers -func (p *Parser) parseLoginUser() (*Command, error) { - cmd := NewCommand("login_user") - - p.nextToken() // consume LOGIN - if p.curToken.Type != TokenUser { - return nil, fmt.Errorf("expected USER after LOGIN") - } - - p.nextToken() - email, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["email"] = email - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - - return cmd, nil -} - -func (p *Parser) parsePingServer() (*Command, error) { - cmd := NewCommand("ping") - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseRegisterCommand() (*Command, error) { - cmd := NewCommand("register_user") - - if err := p.expectPeek(TokenUser); err != nil { - return nil, err - } - p.nextToken() - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["user_name"] = userName - - p.nextToken() - if p.curToken.Type != TokenAs { - return nil, fmt.Errorf("expected AS") - } - - p.nextToken() - nickname, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["nickname"] = nickname - - p.nextToken() - if p.curToken.Type != TokenPassword { - return nil, fmt.Errorf("expected PASSWORD") - } - - p.nextToken() - password, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["password"] = password - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - - return cmd, nil -} - -func (p *Parser) parseListCommand() (*Command, error) { - p.nextToken() // consume LIST - - switch p.curToken.Type { - case TokenServices: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_services"), nil - case TokenUsers: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_users"), nil - case TokenRoles: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_roles"), nil - case TokenVars: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_variables"), nil - case TokenConfigs: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_configs"), nil - case TokenTokens: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_tokens"), nil - case TokenEnvs: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_environments"), nil - case TokenDatasets: - return p.parseListDatasets() - case TokenAgents: - return p.parseListAgents() - case TokenKeys: - return p.parseListKeys() - case TokenModel: - return p.parseListModelProviders() - case TokenDefault: - return p.parseListDefaultModels() - case TokenChats: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_user_chats"), nil - case TokenFiles: - return p.parseListFiles() - default: - return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) - } -} - -func (p *Parser) parseListDatasets() (*Command, error) { - cmd := NewCommand("list_user_datasets") - p.nextToken() // consume DATASETS - - if p.curToken.Type == TokenSemicolon { - return cmd, nil - } - - if p.curToken.Type == TokenOf { - p.nextToken() - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd = NewCommand("list_datasets") - cmd.Params["user_name"] = userName - p.nextToken() - } - - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseListAgents() (*Command, error) { - p.nextToken() // consume AGENTS - - if p.curToken.Type == TokenSemicolon { - return NewCommand("list_user_agents"), nil - } - - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } - p.nextToken() - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("list_agents") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseListKeys() (*Command, error) { - p.nextToken() // consume KEYS - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } - p.nextToken() - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("list_tokens") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseListModelProviders() (*Command, error) { - p.nextToken() // consume MODEL - if p.curToken.Type != TokenProviders { - return nil, fmt.Errorf("expected PROVIDERS") - } - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_user_model_providers"), nil -} - -func (p *Parser) parseListDefaultModels() (*Command, error) { - p.nextToken() // consume DEFAULT - if p.curToken.Type != TokenModels { - return nil, fmt.Errorf("expected MODELS") - } - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("list_user_default_models"), nil -} - -func (p *Parser) parseListFiles() (*Command, error) { - p.nextToken() // consume FILES - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } - p.nextToken() - if p.curToken.Type != TokenDataset { - return nil, fmt.Errorf("expected DATASET") - } - p.nextToken() - - datasetName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("list_user_dataset_files") - cmd.Params["dataset_name"] = datasetName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseShowCommand() (*Command, error) { - p.nextToken() // consume SHOW - - switch p.curToken.Type { - case TokenVersion: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("show_version"), nil - case TokenToken: - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("show_token"), nil - case TokenCurrent: - p.nextToken() - if p.curToken.Type != TokenUser { - return nil, fmt.Errorf("expected USER after CURRENT") - } - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("show_current_user"), nil - case TokenUser: - return p.parseShowUser() - case TokenRole: - return p.parseShowRole() - case TokenVar: - return p.parseShowVariable() - case TokenService: - return p.parseShowService() - default: - return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) - } -} - -func (p *Parser) parseShowUser() (*Command, error) { - p.nextToken() // consume USER - - // Check for PERMISSION - if p.curToken.Type == TokenPermission { - p.nextToken() - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd := NewCommand("show_user_permission") - cmd.Params["user_name"] = userName - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil - } - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("show_user") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseShowRole() (*Command, error) { - p.nextToken() // consume ROLE - roleName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("show_role") - cmd.Params["role_name"] = roleName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseShowVariable() (*Command, error) { - p.nextToken() // consume VAR - varName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("show_variable") - cmd.Params["var_name"] = varName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseShowService() (*Command, error) { - p.nextToken() // consume SERVICE - serviceNum, err := p.parseNumber() - if err != nil { - return nil, err - } - - cmd := NewCommand("show_service") - cmd.Params["number"] = serviceNum - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseCreateCommand() (*Command, error) { - p.nextToken() // consume CREATE - - switch p.curToken.Type { - case TokenUser: - return p.parseCreateUser() - case TokenRole: - return p.parseCreateRole() - case TokenModel: - return p.parseCreateModelProvider() - case TokenDataset: - return p.parseCreateDataset() - case TokenChat: - return p.parseCreateChat() - case TokenToken: - return p.parseCreateToken() - default: - return nil, fmt.Errorf("unknown CREATE target: %s", p.curToken.Value) - } -} - -func (p *Parser) parseCreateToken() (*Command, error) { - p.nextToken() // consume TOKEN - - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - - return NewCommand("create_token"), nil -} - -func (p *Parser) parseCreateUser() (*Command, error) { - p.nextToken() // consume USER - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - password, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("create_user") - cmd.Params["user_name"] = userName - cmd.Params["password"] = password - cmd.Params["role"] = "user" - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseCreateRole() (*Command, error) { - p.nextToken() // consume ROLE - roleName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("create_role") - cmd.Params["role_name"] = roleName - - p.nextToken() - if p.curToken.Type == TokenDescription { - p.nextToken() - description, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["description"] = description - p.nextToken() - } - - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseCreateModelProvider() (*Command, error) { - p.nextToken() // consume MODEL - if p.curToken.Type != TokenProvider { - return nil, fmt.Errorf("expected PROVIDER") - } - p.nextToken() - - providerName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - providerKey, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("create_model_provider") - cmd.Params["provider_name"] = providerName - cmd.Params["provider_key"] = providerKey - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseCreateDataset() (*Command, error) { - p.nextToken() // consume DATASET - datasetName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenWith { - return nil, fmt.Errorf("expected WITH") - } - p.nextToken() - if p.curToken.Type != TokenEmbedding { - return nil, fmt.Errorf("expected EMBEDDING") - } - p.nextToken() - - embedding, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - cmd := NewCommand("create_user_dataset") - cmd.Params["dataset_name"] = datasetName - cmd.Params["embedding"] = embedding - - if p.curToken.Type == TokenParser { - p.nextToken() - parserType, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["parser_type"] = parserType - p.nextToken() - } else if p.curToken.Type == TokenPipeline { - p.nextToken() - pipeline, err := p.parseQuotedString() - if err != nil { - return nil, err - } - cmd.Params["pipeline"] = pipeline - p.nextToken() - } else { - return nil, fmt.Errorf("expected PARSER or PIPELINE") - } - - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseCreateChat() (*Command, error) { - p.nextToken() // consume CHAT - chatName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("create_user_chat") - cmd.Params["chat_name"] = chatName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseDropCommand() (*Command, error) { - p.nextToken() // consume DROP - - switch p.curToken.Type { - case TokenUser: - return p.parseDropUser() - case TokenRole: - return p.parseDropRole() - case TokenModel: - return p.parseDropModelProvider() - case TokenDataset: - return p.parseDropDataset() - case TokenChat: - return p.parseDropChat() - case TokenKey: - return p.parseDropKey() - case TokenToken: - return p.parseDropToken() - default: - return nil, fmt.Errorf("unknown DROP target: %s", p.curToken.Value) - } -} - -func (p *Parser) parseDropToken() (*Command, error) { - p.nextToken() // consume TOKEN - - tokenValue, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_token") - cmd.Params["token"] = tokenValue - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseDropUser() (*Command, error) { - p.nextToken() // consume USER - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_user") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseDropRole() (*Command, error) { - p.nextToken() // consume ROLE - roleName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_role") - cmd.Params["role_name"] = roleName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseDropModelProvider() (*Command, error) { - p.nextToken() // consume MODEL - if p.curToken.Type != TokenProvider { - return nil, fmt.Errorf("expected PROVIDER") - } - p.nextToken() - - providerName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_model_provider") - cmd.Params["provider_name"] = providerName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseDropDataset() (*Command, error) { - p.nextToken() // consume DATASET - datasetName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_user_dataset") - cmd.Params["dataset_name"] = datasetName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseDropChat() (*Command, error) { - p.nextToken() // consume CHAT - chatName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_user_chat") - cmd.Params["chat_name"] = chatName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseDropKey() (*Command, error) { - p.nextToken() // consume KEY - token, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } - p.nextToken() - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("drop_token") - cmd.Params["token"] = token - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseAlterCommand() (*Command, error) { - p.nextToken() // consume ALTER - - switch p.curToken.Type { - case TokenUser: - return p.parseAlterUser() - case TokenRole: - return p.parseAlterRole() - default: - return nil, fmt.Errorf("unknown ALTER target: %s", p.curToken.Value) - } -} - -func (p *Parser) parseAlterUser() (*Command, error) { - p.nextToken() // consume USER - - if p.curToken.Type == TokenActive { - return p.parseActivateUser() - } - - if p.curToken.Type == TokenPassword { - p.nextToken() - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - password, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("alter_user") - cmd.Params["user_name"] = userName - cmd.Params["password"] = password - - p.nextToken() - // Semicolon is optional for SHOW TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil - } - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenSet { - return nil, fmt.Errorf("expected SET") - } - p.nextToken() - if p.curToken.Type != TokenRole { - return nil, fmt.Errorf("expected ROLE") - } - p.nextToken() - - roleName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("alter_user_role") - cmd.Params["user_name"] = userName - cmd.Params["role_name"] = roleName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseActivateUser() (*Command, error) { - p.nextToken() // consume ACTIVE - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - // Accept 'on' or 'off' as identifier - status := p.curToken.Value - if status != "on" && status != "off" { - return nil, fmt.Errorf("expected 'on' or 'off', got %s", p.curToken.Value) - } - - cmd := NewCommand("activate_user") - cmd.Params["user_name"] = userName - cmd.Params["activate_status"] = status - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseAlterRole() (*Command, error) { - p.nextToken() // consume ROLE - roleName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenSet { - return nil, fmt.Errorf("expected SET") - } - p.nextToken() - if p.curToken.Type != TokenDescription { - return nil, fmt.Errorf("expected DESCRIPTION") - } - p.nextToken() - - description, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("alter_role") - cmd.Params["role_name"] = roleName - cmd.Params["description"] = description - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseGrantCommand() (*Command, error) { - p.nextToken() // consume GRANT - - if p.curToken.Type == TokenAdmin { - return p.parseGrantAdmin() - } - - return p.parseGrantPermission() -} - -func (p *Parser) parseGrantAdmin() (*Command, error) { - p.nextToken() // consume ADMIN - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("grant_admin") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseGrantPermission() (*Command, error) { - actions, err := p.parseIdentifierList() - if err != nil { - return nil, err - } - - if p.curToken.Type != TokenOn { - return nil, fmt.Errorf("expected ON") - } - p.nextToken() - - resource, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenTo { - return nil, fmt.Errorf("expected TO") - } - p.nextToken() - if p.curToken.Type != TokenRole { - return nil, fmt.Errorf("expected ROLE") - } - p.nextToken() - - roleName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("grant_permission") - cmd.Params["actions"] = actions - cmd.Params["resource"] = resource - cmd.Params["role_name"] = roleName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseRevokeCommand() (*Command, error) { - p.nextToken() // consume REVOKE - - if p.curToken.Type == TokenAdmin { - return p.parseRevokeAdmin() - } - - return p.parseRevokePermission() -} - -func (p *Parser) parseRevokeAdmin() (*Command, error) { - p.nextToken() // consume ADMIN - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("revoke_admin") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseRevokePermission() (*Command, error) { - actions, err := p.parseIdentifierList() - if err != nil { - return nil, err - } - - if p.curToken.Type != TokenOn { - return nil, fmt.Errorf("expected ON") - } - p.nextToken() - - resource, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenFrom { - return nil, fmt.Errorf("expected FROM") - } - p.nextToken() - if p.curToken.Type != TokenRole { - return nil, fmt.Errorf("expected ROLE") - } - p.nextToken() - - roleName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("revoke_permission") - cmd.Params["actions"] = actions - cmd.Params["resource"] = resource - cmd.Params["role_name"] = roleName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseIdentifierList() ([]string, error) { - var list []string - - ident, err := p.parseIdentifier() - if err != nil { - return nil, err - } - list = append(list, ident) - p.nextToken() - - for p.curToken.Type == TokenComma { - p.nextToken() - ident, err := p.parseIdentifier() - if err != nil { - return nil, err - } - list = append(list, ident) - p.nextToken() - } - - return list, nil -} - -func (p *Parser) parseSetCommand() (*Command, error) { - p.nextToken() // consume SET - - if p.curToken.Type == TokenVar { - return p.parseSetVariable() - } - if p.curToken.Type == TokenDefault { - return p.parseSetDefault() - } - if p.curToken.Type == TokenToken { - return p.parseSetToken() - } - - return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value) -} - -func (p *Parser) parseSetVariable() (*Command, error) { - p.nextToken() // consume VAR - varName, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - p.nextToken() - varValue, err := p.parseIdentifier() - if err != nil { - return nil, err - } - - cmd := NewCommand("set_variable") - cmd.Params["var_name"] = varName - cmd.Params["var_value"] = varValue - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseSetDefault() (*Command, error) { - p.nextToken() // consume DEFAULT - - var modelType, modelID string - - switch p.curToken.Type { - case TokenLLM: - modelType = "llm_id" - case TokenVLM: - modelType = "img2txt_id" - case TokenEmbedding: - modelType = "embd_id" - case TokenReranker: - modelType = "reranker_id" - case TokenASR: - modelType = "asr_id" - case TokenTTS: - modelType = "tts_id" - default: - return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value) - } - - p.nextToken() - id, err := p.parseQuotedString() - if err != nil { - return nil, err - } - modelID = id - - cmd := NewCommand("set_default_model") - cmd.Params["model_type"] = modelType - cmd.Params["model_id"] = modelID - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseSetToken() (*Command, error) { - p.nextToken() // consume TOKEN - - tokenValue, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("set_token") - cmd.Params["token"] = tokenValue - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseResetCommand() (*Command, error) { - p.nextToken() // consume RESET - - if p.curToken.Type != TokenDefault { - return nil, fmt.Errorf("expected DEFAULT") - } - p.nextToken() - - var modelType string - switch p.curToken.Type { - case TokenLLM: - modelType = "llm_id" - case TokenVLM: - modelType = "img2txt_id" - case TokenEmbedding: - modelType = "embd_id" - case TokenReranker: - modelType = "reranker_id" - case TokenASR: - modelType = "asr_id" - case TokenTTS: - modelType = "tts_id" - default: - return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value) - } - - cmd := NewCommand("reset_default_model") - cmd.Params["model_type"] = modelType - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseGenerateCommand() (*Command, error) { - p.nextToken() // consume GENERATE - if p.curToken.Type != TokenKey { - return nil, fmt.Errorf("expected KEY") - } - p.nextToken() - if p.curToken.Type != TokenFor { - return nil, fmt.Errorf("expected FOR") - } - p.nextToken() - if p.curToken.Type != TokenUser { - return nil, fmt.Errorf("expected USER") - } - p.nextToken() - - userName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("generate_token") - cmd.Params["user_name"] = userName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseImportCommand() (*Command, error) { - p.nextToken() // consume IMPORT - documentPaths, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenInto { - return nil, fmt.Errorf("expected INTO") - } - p.nextToken() - if p.curToken.Type != TokenDataset { - return nil, fmt.Errorf("expected DATASET") - } - p.nextToken() - - datasetName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("import_docs_into_dataset") - cmd.Params["document_paths"] = documentPaths - cmd.Params["dataset_name"] = datasetName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseSearchCommand() (*Command, error) { - p.nextToken() // consume SEARCH - question, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenOn { - return nil, fmt.Errorf("expected ON") - } - p.nextToken() - if p.curToken.Type != TokenDatasets { - return nil, fmt.Errorf("expected DATASETS") - } - p.nextToken() - - datasets, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("search_on_datasets") - cmd.Params["question"] = question - cmd.Params["datasets"] = datasets - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseParseCommand() (*Command, error) { - p.nextToken() // consume PARSE - - if p.curToken.Type == TokenDataset { - return p.parseParseDataset() - } - - return p.parseParseDocs() -} - -func (p *Parser) parseParseDataset() (*Command, error) { - p.nextToken() // consume DATASET - datasetName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - var method string - if p.curToken.Type == TokenSync { - method = "sync" - } else if p.curToken.Type == TokenAsync { - method = "async" - } else { - return nil, fmt.Errorf("expected SYNC or ASYNC") - } - - cmd := NewCommand("parse_dataset") - cmd.Params["dataset_name"] = datasetName - cmd.Params["method"] = method - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseParseDocs() (*Command, error) { - documentNames, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - p.nextToken() - if p.curToken.Type != TokenOf { - return nil, fmt.Errorf("expected OF") - } - p.nextToken() - if p.curToken.Type != TokenDataset { - return nil, fmt.Errorf("expected DATASET") - } - p.nextToken() - - datasetName, err := p.parseQuotedString() - if err != nil { - return nil, err - } - - cmd := NewCommand("parse_dataset_docs") - cmd.Params["document_names"] = documentNames - cmd.Params["dataset_name"] = datasetName - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseBenchmarkCommand() (*Command, error) { - cmd := NewCommand("benchmark") - - p.nextToken() // consume BENCHMARK - concurrency, err := p.parseNumber() - if err != nil { - return nil, err - } - cmd.Params["concurrency"] = concurrency - - p.nextToken() - iterations, err := p.parseNumber() - if err != nil { - return nil, err - } - cmd.Params["iterations"] = iterations - - p.nextToken() - // Parse user_statement - nestedCmd, err := p.parseUserStatement() - if err != nil { - return nil, err - } - cmd.Params["command"] = nestedCmd - - return cmd, nil -} - -func (p *Parser) parseUserStatement() (*Command, error) { - switch p.curToken.Type { - case TokenPing: - return p.parsePingServer() - case TokenShow: - return p.parseShowCommand() - case TokenCreate: - return p.parseCreateCommand() - case TokenDrop: - return p.parseDropCommand() - case TokenSet: - return p.parseSetCommand() - case TokenUnset: - return p.parseUnsetCommand() - case TokenReset: - return p.parseResetCommand() - case TokenList: - return p.parseListCommand() - case TokenParse: - return p.parseParseCommand() - case TokenImport: - return p.parseImportCommand() - case TokenSearch: - return p.parseSearchCommand() - default: - return nil, fmt.Errorf("invalid user statement: %s", p.curToken.Value) - } -} - -func (p *Parser) parseStartupCommand() (*Command, error) { - p.nextToken() // consume STARTUP - if p.curToken.Type != TokenService { - return nil, fmt.Errorf("expected SERVICE") - } - p.nextToken() - - serviceNum, err := p.parseNumber() - if err != nil { - return nil, err - } - - cmd := NewCommand("startup_service") - cmd.Params["number"] = serviceNum - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseShutdownCommand() (*Command, error) { - p.nextToken() // consume SHUTDOWN - if p.curToken.Type != TokenService { - return nil, fmt.Errorf("expected SERVICE") - } - p.nextToken() - - serviceNum, err := p.parseNumber() - if err != nil { - return nil, err - } - - cmd := NewCommand("shutdown_service") - cmd.Params["number"] = serviceNum - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseRestartCommand() (*Command, error) { - p.nextToken() // consume RESTART - if p.curToken.Type != TokenService { - return nil, fmt.Errorf("expected SERVICE") - } - p.nextToken() - - serviceNum, err := p.parseNumber() - if err != nil { - return nil, err - } - - cmd := NewCommand("restart_service") - cmd.Params["number"] = serviceNum - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return cmd, nil -} - -func (p *Parser) parseUnsetCommand() (*Command, error) { - p.nextToken() // consume UNSET - - if p.curToken.Type != TokenToken { - return nil, fmt.Errorf("expected TOKEN after UNSET") - } - p.nextToken() - - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { - p.nextToken() - } - return NewCommand("unset_token"), nil -} - func tokenTypeToString(t int) string { // Simplified for error messages return fmt.Sprintf("token(%d)", t) diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go new file mode 100644 index 0000000000..cae5b6e575 --- /dev/null +++ b/internal/cli/user_parser.go @@ -0,0 +1,1506 @@ +package cli + +import "fmt" + +// Command parsers +func (p *Parser) parseLoginUser() (*Command, error) { + cmd := NewCommand("login_user") + + p.nextToken() // consume LOGIN + if p.curToken.Type != TokenUser { + return nil, fmt.Errorf("expected USER after LOGIN") + } + + p.nextToken() + email, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["email"] = email + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +func (p *Parser) parsePingServer() (*Command, error) { + cmd := NewCommand("ping") + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseRegisterCommand() (*Command, error) { + cmd := NewCommand("register_user") + + if err := p.expectPeek(TokenUser); err != nil { + return nil, err + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["user_name"] = userName + + p.nextToken() + if p.curToken.Type != TokenAs { + return nil, fmt.Errorf("expected AS") + } + + p.nextToken() + nickname, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["nickname"] = nickname + + p.nextToken() + if p.curToken.Type != TokenPassword { + return nil, fmt.Errorf("expected PASSWORD") + } + + p.nextToken() + password, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["password"] = password + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return cmd, nil +} + +func (p *Parser) parseListCommand() (*Command, error) { + p.nextToken() // consume LIST + + switch p.curToken.Type { + case TokenServices: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_services"), nil + case TokenUsers: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_users"), nil + case TokenRoles: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_roles"), nil + case TokenVars: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_variables"), nil + case TokenConfigs: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_configs"), nil + case TokenEnvs: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_environments"), nil + case TokenDatasets: + return p.parseListDatasets() + case TokenAgents: + return p.parseListAgents() + case TokenTokens: + return p.parseListTokens() + case TokenModel: + return p.parseListModelProviders() + case TokenDefault: + return p.parseListDefaultModels() + case TokenChats: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_user_chats"), nil + case TokenFiles: + return p.parseListFiles() + default: + return nil, fmt.Errorf("unknown LIST target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseListDatasets() (*Command, error) { + cmd := NewCommand("list_user_datasets") + p.nextToken() // consume DATASETS + + if p.curToken.Type == TokenSemicolon { + return cmd, nil + } + + if p.curToken.Type == TokenOf { + p.nextToken() + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd = NewCommand("list_datasets") + cmd.Params["user_name"] = userName + p.nextToken() + } + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseListAgents() (*Command, error) { + p.nextToken() // consume AGENTS + + if p.curToken.Type == TokenSemicolon { + return NewCommand("list_user_agents"), nil + } + + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("list_agents") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseListTokens() (*Command, error) { + p.nextToken() // consume TOKENS + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("list_tokens") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseListModelProviders() (*Command, error) { + p.nextToken() // consume MODEL + if p.curToken.Type != TokenProviders { + return nil, fmt.Errorf("expected PROVIDERS") + } + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_user_model_providers"), nil +} + +func (p *Parser) parseListDefaultModels() (*Command, error) { + p.nextToken() // consume DEFAULT + if p.curToken.Type != TokenModels { + return nil, fmt.Errorf("expected MODELS") + } + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("list_user_default_models"), nil +} + +func (p *Parser) parseListFiles() (*Command, error) { + p.nextToken() // consume FILES + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + if p.curToken.Type != TokenDataset { + return nil, fmt.Errorf("expected DATASET") + } + p.nextToken() + + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("list_user_dataset_files") + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseShowCommand() (*Command, error) { + p.nextToken() // consume SHOW + + switch p.curToken.Type { + case TokenVersion: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("show_version"), nil + case TokenToken: + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("show_token"), nil + case TokenCurrent: + p.nextToken() + if p.curToken.Type != TokenUser { + return nil, fmt.Errorf("expected USER after CURRENT") + } + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("show_current_user"), nil + case TokenUser: + return p.parseShowUser() + case TokenRole: + return p.parseShowRole() + case TokenVar: + return p.parseShowVariable() + case TokenService: + return p.parseShowService() + default: + return nil, fmt.Errorf("unknown SHOW target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseShowUser() (*Command, error) { + p.nextToken() // consume USER + + // Check for PERMISSION + if p.curToken.Type == TokenPermission { + p.nextToken() + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd := NewCommand("show_user_permission") + cmd.Params["user_name"] = userName + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil + } + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_user") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseShowRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_role") + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseShowVariable() (*Command, error) { + p.nextToken() // consume VAR + varName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_variable") + cmd.Params["var_name"] = varName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseShowService() (*Command, error) { + p.nextToken() // consume SERVICE + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("show_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseCreateCommand() (*Command, error) { + p.nextToken() // consume CREATE + + switch p.curToken.Type { + case TokenUser: + return p.parseCreateUser() + case TokenRole: + return p.parseCreateRole() + case TokenModel: + return p.parseCreateModelProvider() + case TokenDataset: + return p.parseCreateDataset() + case TokenChat: + return p.parseCreateChat() + case TokenToken: + return p.parseCreateToken() + default: + return nil, fmt.Errorf("unknown CREATE target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseCreateToken() (*Command, error) { + p.nextToken() // consume TOKEN + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + + return NewCommand("create_token"), nil +} + +func (p *Parser) parseCreateUser() (*Command, error) { + p.nextToken() // consume USER + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + password, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_user") + cmd.Params["user_name"] = userName + cmd.Params["password"] = password + cmd.Params["role"] = "user" + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseCreateRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_role") + cmd.Params["role_name"] = roleName + + p.nextToken() + if p.curToken.Type == TokenDescription { + p.nextToken() + description, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["description"] = description + p.nextToken() + } + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseCreateModelProvider() (*Command, error) { + p.nextToken() // consume MODEL + if p.curToken.Type != TokenProvider { + return nil, fmt.Errorf("expected PROVIDER") + } + p.nextToken() + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + providerKey, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_model_provider") + cmd.Params["provider_name"] = providerName + cmd.Params["provider_key"] = providerKey + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseCreateDataset() (*Command, error) { + p.nextToken() // consume DATASET + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenWith { + return nil, fmt.Errorf("expected WITH") + } + p.nextToken() + if p.curToken.Type != TokenEmbedding { + return nil, fmt.Errorf("expected EMBEDDING") + } + p.nextToken() + + embedding, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + cmd := NewCommand("create_user_dataset") + cmd.Params["dataset_name"] = datasetName + cmd.Params["embedding"] = embedding + + if p.curToken.Type == TokenParser { + p.nextToken() + parserType, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["parser_type"] = parserType + p.nextToken() + } else if p.curToken.Type == TokenPipeline { + p.nextToken() + pipeline, err := p.parseQuotedString() + if err != nil { + return nil, err + } + cmd.Params["pipeline"] = pipeline + p.nextToken() + } else { + return nil, fmt.Errorf("expected PARSER or PIPELINE") + } + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseCreateChat() (*Command, error) { + p.nextToken() // consume CHAT + chatName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("create_user_chat") + cmd.Params["chat_name"] = chatName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseDropCommand() (*Command, error) { + p.nextToken() // consume DROP + + switch p.curToken.Type { + case TokenUser: + return p.parseDropUser() + case TokenRole: + return p.parseDropRole() + case TokenModel: + return p.parseDropModelProvider() + case TokenDataset: + return p.parseDropDataset() + case TokenChat: + return p.parseDropChat() + case TokenToken: + return p.parseDropToken() + default: + return nil, fmt.Errorf("unknown DROP target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseDropToken() (*Command, error) { + p.nextToken() // consume TOKEN + + tokenValue, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_token") + cmd.Params["token"] = tokenValue + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseDropUser() (*Command, error) { + p.nextToken() // consume USER + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_user") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseDropRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_role") + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseDropModelProvider() (*Command, error) { + p.nextToken() // consume MODEL + if p.curToken.Type != TokenProvider { + return nil, fmt.Errorf("expected PROVIDER") + } + p.nextToken() + + providerName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_model_provider") + cmd.Params["provider_name"] = providerName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseDropDataset() (*Command, error) { + p.nextToken() // consume DATASET + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_user_dataset") + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseDropChat() (*Command, error) { + p.nextToken() // consume CHAT + chatName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("drop_user_chat") + cmd.Params["chat_name"] = chatName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAlterCommand() (*Command, error) { + p.nextToken() // consume ALTER + + switch p.curToken.Type { + case TokenUser: + return p.parseAlterUser() + case TokenRole: + return p.parseAlterRole() + default: + return nil, fmt.Errorf("unknown ALTER target: %s", p.curToken.Value) + } +} + +func (p *Parser) parseAlterUser() (*Command, error) { + p.nextToken() // consume USER + + if p.curToken.Type == TokenActive { + return p.parseActivateUser() + } + + if p.curToken.Type == TokenPassword { + p.nextToken() + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + password, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("alter_user") + cmd.Params["user_name"] = userName + cmd.Params["password"] = password + + p.nextToken() + // Semicolon is optional for SHOW TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil + } + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenSet { + return nil, fmt.Errorf("expected SET") + } + p.nextToken() + if p.curToken.Type != TokenRole { + return nil, fmt.Errorf("expected ROLE") + } + p.nextToken() + + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("alter_user_role") + cmd.Params["user_name"] = userName + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseActivateUser() (*Command, error) { + p.nextToken() // consume ACTIVE + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + // Accept 'on' or 'off' as identifier + status := p.curToken.Value + if status != "on" && status != "off" { + return nil, fmt.Errorf("expected 'on' or 'off', got %s", p.curToken.Value) + } + + cmd := NewCommand("activate_user") + cmd.Params["user_name"] = userName + cmd.Params["activate_status"] = status + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseAlterRole() (*Command, error) { + p.nextToken() // consume ROLE + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenSet { + return nil, fmt.Errorf("expected SET") + } + p.nextToken() + if p.curToken.Type != TokenDescription { + return nil, fmt.Errorf("expected DESCRIPTION") + } + p.nextToken() + + description, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("alter_role") + cmd.Params["role_name"] = roleName + cmd.Params["description"] = description + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseGrantCommand() (*Command, error) { + p.nextToken() // consume GRANT + + if p.curToken.Type == TokenAdmin { + return p.parseGrantAdmin() + } + + return p.parseGrantPermission() +} + +func (p *Parser) parseGrantAdmin() (*Command, error) { + p.nextToken() // consume ADMIN + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("grant_admin") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseGrantPermission() (*Command, error) { + actions, err := p.parseIdentifierList() + if err != nil { + return nil, err + } + + if p.curToken.Type != TokenOn { + return nil, fmt.Errorf("expected ON") + } + p.nextToken() + + resource, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenTo { + return nil, fmt.Errorf("expected TO") + } + p.nextToken() + if p.curToken.Type != TokenRole { + return nil, fmt.Errorf("expected ROLE") + } + p.nextToken() + + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("grant_permission") + cmd.Params["actions"] = actions + cmd.Params["resource"] = resource + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseRevokeCommand() (*Command, error) { + p.nextToken() // consume REVOKE + + if p.curToken.Type == TokenAdmin { + return p.parseRevokeAdmin() + } + + return p.parseRevokePermission() +} + +func (p *Parser) parseRevokeAdmin() (*Command, error) { + p.nextToken() // consume ADMIN + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("revoke_admin") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseRevokePermission() (*Command, error) { + actions, err := p.parseIdentifierList() + if err != nil { + return nil, err + } + + if p.curToken.Type != TokenOn { + return nil, fmt.Errorf("expected ON") + } + p.nextToken() + + resource, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenFrom { + return nil, fmt.Errorf("expected FROM") + } + p.nextToken() + if p.curToken.Type != TokenRole { + return nil, fmt.Errorf("expected ROLE") + } + p.nextToken() + + roleName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("revoke_permission") + cmd.Params["actions"] = actions + cmd.Params["resource"] = resource + cmd.Params["role_name"] = roleName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseIdentifierList() ([]string, error) { + var list []string + + ident, err := p.parseIdentifier() + if err != nil { + return nil, err + } + list = append(list, ident) + p.nextToken() + + for p.curToken.Type == TokenComma { + p.nextToken() + ident, err := p.parseIdentifier() + if err != nil { + return nil, err + } + list = append(list, ident) + p.nextToken() + } + + return list, nil +} + +func (p *Parser) parseSetCommand() (*Command, error) { + p.nextToken() // consume SET + + if p.curToken.Type == TokenVar { + return p.parseSetVariable() + } + if p.curToken.Type == TokenDefault { + return p.parseSetDefault() + } + if p.curToken.Type == TokenToken { + return p.parseSetToken() + } + + return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value) +} + +func (p *Parser) parseSetVariable() (*Command, error) { + p.nextToken() // consume VAR + varName, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + p.nextToken() + varValue, err := p.parseIdentifier() + if err != nil { + return nil, err + } + + cmd := NewCommand("set_variable") + cmd.Params["var_name"] = varName + cmd.Params["var_value"] = varValue + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseSetDefault() (*Command, error) { + p.nextToken() // consume DEFAULT + + var modelType, modelID string + + switch p.curToken.Type { + case TokenLLM: + modelType = "llm_id" + case TokenVLM: + modelType = "img2txt_id" + case TokenEmbedding: + modelType = "embd_id" + case TokenReranker: + modelType = "reranker_id" + case TokenASR: + modelType = "asr_id" + case TokenTTS: + modelType = "tts_id" + default: + return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value) + } + + p.nextToken() + id, err := p.parseQuotedString() + if err != nil { + return nil, err + } + modelID = id + + cmd := NewCommand("set_default_model") + cmd.Params["model_type"] = modelType + cmd.Params["model_id"] = modelID + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseSetToken() (*Command, error) { + p.nextToken() // consume TOKEN + + tokenValue, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("set_token") + cmd.Params["token"] = tokenValue + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseResetCommand() (*Command, error) { + p.nextToken() // consume RESET + + if p.curToken.Type != TokenDefault { + return nil, fmt.Errorf("expected DEFAULT") + } + p.nextToken() + + var modelType string + switch p.curToken.Type { + case TokenLLM: + modelType = "llm_id" + case TokenVLM: + modelType = "img2txt_id" + case TokenEmbedding: + modelType = "embd_id" + case TokenReranker: + modelType = "reranker_id" + case TokenASR: + modelType = "asr_id" + case TokenTTS: + modelType = "tts_id" + default: + return nil, fmt.Errorf("unknown model type: %s", p.curToken.Value) + } + + cmd := NewCommand("reset_default_model") + cmd.Params["model_type"] = modelType + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseGenerateCommand() (*Command, error) { + p.nextToken() // consume GENERATE + if p.curToken.Type != TokenToken { + return nil, fmt.Errorf("expected TOKEN") + } + p.nextToken() + if p.curToken.Type != TokenFor { + return nil, fmt.Errorf("expected FOR") + } + p.nextToken() + if p.curToken.Type != TokenUser { + return nil, fmt.Errorf("expected USER") + } + p.nextToken() + + userName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("generate_token") + cmd.Params["user_name"] = userName + + p.nextToken() + // Semicolon is optional + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseImportCommand() (*Command, error) { + p.nextToken() // consume IMPORT + documentPaths, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenInto { + return nil, fmt.Errorf("expected INTO") + } + p.nextToken() + if p.curToken.Type != TokenDataset { + return nil, fmt.Errorf("expected DATASET") + } + p.nextToken() + + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("import_docs_into_dataset") + cmd.Params["document_paths"] = documentPaths + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseSearchCommand() (*Command, error) { + p.nextToken() // consume SEARCH + question, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenOn { + return nil, fmt.Errorf("expected ON") + } + p.nextToken() + if p.curToken.Type != TokenDatasets { + return nil, fmt.Errorf("expected DATASETS") + } + p.nextToken() + + datasets, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("search_on_datasets") + cmd.Params["question"] = question + cmd.Params["datasets"] = datasets + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseParseCommand() (*Command, error) { + p.nextToken() // consume PARSE + + if p.curToken.Type == TokenDataset { + return p.parseParseDataset() + } + + return p.parseParseDocs() +} + +func (p *Parser) parseParseDataset() (*Command, error) { + p.nextToken() // consume DATASET + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + var method string + if p.curToken.Type == TokenSync { + method = "sync" + } else if p.curToken.Type == TokenAsync { + method = "async" + } else { + return nil, fmt.Errorf("expected SYNC or ASYNC") + } + + cmd := NewCommand("parse_dataset") + cmd.Params["dataset_name"] = datasetName + cmd.Params["method"] = method + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseParseDocs() (*Command, error) { + documentNames, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + p.nextToken() + if p.curToken.Type != TokenOf { + return nil, fmt.Errorf("expected OF") + } + p.nextToken() + if p.curToken.Type != TokenDataset { + return nil, fmt.Errorf("expected DATASET") + } + p.nextToken() + + datasetName, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("parse_dataset_docs") + cmd.Params["document_names"] = documentNames + cmd.Params["dataset_name"] = datasetName + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseBenchmarkCommand() (*Command, error) { + cmd := NewCommand("benchmark") + + p.nextToken() // consume BENCHMARK + concurrency, err := p.parseNumber() + if err != nil { + return nil, err + } + cmd.Params["concurrency"] = concurrency + + p.nextToken() + iterations, err := p.parseNumber() + if err != nil { + return nil, err + } + cmd.Params["iterations"] = iterations + + p.nextToken() + // Parse user_statement + nestedCmd, err := p.parseUserStatement() // Not only user statement + if err != nil { + return nil, err + } + cmd.Params["command"] = nestedCmd + + return cmd, nil +} + +func (p *Parser) parseUserStatement() (*Command, error) { + switch p.curToken.Type { + case TokenPing: + return p.parsePingServer() + case TokenShow: + return p.parseShowCommand() + case TokenCreate: + return p.parseCreateCommand() + case TokenDrop: + return p.parseDropCommand() + case TokenSet: + return p.parseSetCommand() + case TokenUnset: + return p.parseUnsetCommand() + case TokenReset: + return p.parseResetCommand() + case TokenList: + return p.parseListCommand() + case TokenParse: + return p.parseParseCommand() + case TokenImport: + return p.parseImportCommand() + case TokenSearch: + return p.parseSearchCommand() + default: + return nil, fmt.Errorf("invalid user statement: %s", p.curToken.Value) + } +} + +func (p *Parser) parseStartupCommand() (*Command, error) { + p.nextToken() // consume STARTUP + if p.curToken.Type != TokenService { + return nil, fmt.Errorf("expected SERVICE") + } + p.nextToken() + + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("startup_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseShutdownCommand() (*Command, error) { + p.nextToken() // consume SHUTDOWN + if p.curToken.Type != TokenService { + return nil, fmt.Errorf("expected SERVICE") + } + p.nextToken() + + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("shutdown_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseRestartCommand() (*Command, error) { + p.nextToken() // consume RESTART + if p.curToken.Type != TokenService { + return nil, fmt.Errorf("expected SERVICE") + } + p.nextToken() + + serviceNum, err := p.parseNumber() + if err != nil { + return nil, err + } + + cmd := NewCommand("restart_service") + cmd.Params["number"] = serviceNum + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil +} + +func (p *Parser) parseUnsetCommand() (*Command, error) { + p.nextToken() // consume UNSET + + if p.curToken.Type != TokenToken { + return nil, fmt.Errorf("expected TOKEN after UNSET") + } + p.nextToken() + + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return NewCommand("unset_token"), nil +} diff --git a/internal/dao/user.go b/internal/dao/user.go index d36b4f9bef..95a86b7196 100644 --- a/internal/dao/user.go +++ b/internal/dao/user.go @@ -52,16 +52,6 @@ func (dao *UserDAO) GetByTenantID(tenantID string) (*model.User, error) { return &user, nil } -// GetByUsername get user by username -func (dao *UserDAO) GetByUsername(username string) (*model.User, error) { - var user model.User - err := DB.Where("username = ?", username).First(&user).Error - if err != nil { - return nil, err - } - return &user, nil -} - // GetByEmail get user by email func (dao *UserDAO) GetByEmail(email string) (*model.User, error) { var user model.User