...
This commit is contained in:
		
							
								
								
									
										86
									
								
								cmd/orpctest/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								cmd/orpctest/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/openrpc" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// Parse command line flags | ||||
| 	var ( | ||||
| 		specDir    = flag.String("dir", "pkg/openrpc/services", "Directory containing OpenRPC specifications") | ||||
| 		specName   = flag.String("spec", "", "Name of the specification to display (optional)") | ||||
| 		methodName = flag.String("method", "", "Name of the method to display (optional)") | ||||
| 	) | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	// Create a new OpenRPC Manager | ||||
| 	manager := openrpc.NewORPCManager() | ||||
|  | ||||
| 	// Ensure the specification directory exists | ||||
| 	if _, err := os.Stat(*specDir); os.IsNotExist(err) { | ||||
| 		log.Fatalf("Specification directory does not exist: %s", *specDir) | ||||
| 	} | ||||
|  | ||||
| 	// Load all specifications from the directory | ||||
| 	log.Printf("Loading specifications from %s...", *specDir) | ||||
| 	if err := manager.LoadSpecs(*specDir); err != nil { | ||||
| 		log.Fatalf("Failed to load specifications: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// List all loaded specifications | ||||
| 	specs := manager.ListSpecs() | ||||
| 	if len(specs) == 0 { | ||||
| 		log.Fatalf("No specifications found in %s", *specDir) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("Loaded specifications:") | ||||
| 	for _, spec := range specs { | ||||
| 		fmt.Printf("- %s\n", spec) | ||||
| 	} | ||||
|  | ||||
| 	// If a specification name is provided, display its methods | ||||
| 	if *specName != "" { | ||||
| 		spec := manager.GetSpec(*specName) | ||||
| 		if spec == nil { | ||||
| 			log.Fatalf("Specification not found: %s", *specName) | ||||
| 		} | ||||
|  | ||||
| 		fmt.Printf("\nMethods in %s specification:\n", *specName) | ||||
| 		methods := manager.ListMethods(*specName) | ||||
| 		for _, method := range methods { | ||||
| 			fmt.Printf("- %s\n", method) | ||||
| 		} | ||||
|  | ||||
| 		// If a method name is provided, display its details | ||||
| 		if *methodName != "" { | ||||
| 			method := manager.GetMethod(*specName, *methodName) | ||||
| 			if method == nil { | ||||
| 				log.Fatalf("Method not found: %s", *methodName) | ||||
| 			} | ||||
|  | ||||
| 			fmt.Printf("\nDetails for method '%s':\n", *methodName) | ||||
| 			fmt.Printf("Description: %s\n", method.Description) | ||||
| 			fmt.Printf("Parameters: %d\n", len(method.Params)) | ||||
|  | ||||
| 			if len(method.Params) > 0 { | ||||
| 				fmt.Println("Parameter list:") | ||||
| 				for _, param := range method.Params { | ||||
| 					required := "" | ||||
| 					if param.Required { | ||||
| 						required = " (required)" | ||||
| 					} | ||||
| 					fmt.Printf("  - %s%s: %s\n", param.Name, required, param.Description) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			fmt.Printf("Result: %s\n", method.Result.Name) | ||||
| 			fmt.Printf("Examples: %d\n", len(method.Examples)) | ||||
| 			fmt.Printf("Errors: %d\n", len(method.Errors)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										239
									
								
								openrpc_manager_plan.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								openrpc_manager_plan.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| # OpenRPC Manager Implementation Plan | ||||
|  | ||||
| ## 1. Understanding the Requirements | ||||
|  | ||||
| The task requires us to: | ||||
| - Create an OpenRPC Manager (ORPCManager) | ||||
| - Read JSON files from services in pkg/openrpc | ||||
| - Create a model for OpenRPC spec in a separate file | ||||
| - Read the OpenRPC specs into the model | ||||
| - Keep these models in memory in the ORPCManager | ||||
| - Create supporting methods like list_methods | ||||
| - Create a command in @cmd/ to test this behavior | ||||
|  | ||||
| ## 2. Project Structure | ||||
|  | ||||
| Here's the proposed file structure for our implementation: | ||||
|  | ||||
| ``` | ||||
| pkg/ | ||||
|   openrpc/ | ||||
|     models/ | ||||
|       spec.go       # OpenRPC specification model | ||||
|     manager.go      # ORPCManager implementation | ||||
| cmd/ | ||||
|   orpctest/ | ||||
|     main.go         # Test command for the ORPCManager | ||||
| ``` | ||||
|  | ||||
| ## 3. Implementation Details | ||||
|  | ||||
| ### 3.1 OpenRPC Specification Model (pkg/openrpc/models/spec.go) | ||||
|  | ||||
| We'll create a Go struct model that represents the OpenRPC specification based on the structure observed in zinit.json: | ||||
|  | ||||
| ```mermaid | ||||
| classDiagram | ||||
|     class OpenRPCSpec { | ||||
|         +string OpenRPC | ||||
|         +InfoObject Info | ||||
|         +Server[] Servers | ||||
|         +Method[] Methods | ||||
|     } | ||||
|      | ||||
|     class InfoObject { | ||||
|         +string Version | ||||
|         +string Title | ||||
|         +string Description | ||||
|         +LicenseObject License | ||||
|     } | ||||
|      | ||||
|     class LicenseObject { | ||||
|         +string Name | ||||
|     } | ||||
|      | ||||
|     class Server { | ||||
|         +string Name | ||||
|         +string URL | ||||
|     } | ||||
|      | ||||
|     class Method { | ||||
|         +string Name | ||||
|         +string Description | ||||
|         +Parameter[] Params | ||||
|         +ResultObject Result | ||||
|         +Example[] Examples | ||||
|         +ErrorObject[] Errors | ||||
|     } | ||||
|      | ||||
|     class Parameter { | ||||
|         +string Name | ||||
|         +string Description | ||||
|         +bool Required | ||||
|         +SchemaObject Schema | ||||
|     } | ||||
|      | ||||
|     class ResultObject { | ||||
|         +string Name | ||||
|         +string Description | ||||
|         +SchemaObject Schema | ||||
|     } | ||||
|      | ||||
|     class SchemaObject { | ||||
|         +string Type | ||||
|         +map[string]interface{} Properties | ||||
|         +SchemaObject Items | ||||
|         +map[string]SchemaObject AdditionalProperties | ||||
|     } | ||||
|      | ||||
|     class Example { | ||||
|         +string Name | ||||
|         +map[string]interface{}[] Params | ||||
|         +ExampleResultObject Result | ||||
|     } | ||||
|      | ||||
|     class ExampleResultObject { | ||||
|         +string Name | ||||
|         +interface{} Value | ||||
|     } | ||||
|      | ||||
|     class ErrorObject { | ||||
|         +int Code | ||||
|         +string Message | ||||
|         +string Data | ||||
|     } | ||||
|      | ||||
|     OpenRPCSpec --> InfoObject | ||||
|     OpenRPCSpec --> Server | ||||
|     OpenRPCSpec --> Method | ||||
|     Method --> Parameter | ||||
|     Method --> ResultObject | ||||
|     Method --> Example | ||||
|     Method --> ErrorObject | ||||
|     Parameter --> SchemaObject | ||||
|     ResultObject --> SchemaObject | ||||
|     Example --> ExampleResultObject | ||||
| ``` | ||||
|  | ||||
| ### 3.2 OpenRPC Manager (pkg/openrpc/manager.go) | ||||
|  | ||||
| The ORPCManager will be responsible for: | ||||
| - Loading OpenRPC specifications from JSON files | ||||
| - Storing and managing these specifications in memory | ||||
| - Providing methods to access and manipulate the specifications | ||||
|  | ||||
| ```mermaid | ||||
| classDiagram | ||||
|     class ORPCManager { | ||||
|         -map[string]*OpenRPCSpec specs | ||||
|         +NewORPCManager() *ORPCManager | ||||
|         +LoadSpecs(dir string) error | ||||
|         +LoadSpec(path string) error | ||||
|         +GetSpec(name string) *OpenRPCSpec | ||||
|         +ListSpecs() []string | ||||
|         +ListMethods(specName string) []string | ||||
|         +GetMethod(specName string, methodName string) *Method | ||||
|     } | ||||
|      | ||||
|     ORPCManager --> OpenRPCSpec | ||||
| ``` | ||||
|  | ||||
| ### 3.3 Test Command (cmd/orpctest/main.go) | ||||
|  | ||||
| We'll create a command-line tool to test the ORPCManager functionality: | ||||
| - Initialize the ORPCManager | ||||
| - Load specifications from the pkg/openrpc/services directory | ||||
| - List available specifications | ||||
| - List methods for each specification | ||||
| - Display details for specific methods | ||||
|  | ||||
| ## 4. Implementation Steps | ||||
|  | ||||
| 1. **Create the OpenRPC Specification Model**: | ||||
|    - Define the Go structs for the OpenRPC specification | ||||
|    - Implement JSON marshaling/unmarshaling | ||||
|    - Add validation functions | ||||
|  | ||||
| 2. **Implement the ORPCManager**: | ||||
|    - Create the manager struct with a map to store specifications | ||||
|    - Implement methods to load specifications from files | ||||
|    - Implement methods to access and manipulate specifications | ||||
|  | ||||
| 3. **Create the Test Command**: | ||||
|    - Implement a command-line interface to test the ORPCManager | ||||
|    - Add options to list specifications, methods, and display details | ||||
|  | ||||
| 4. **Write Tests**: | ||||
|    - Write unit tests for the OpenRPC model | ||||
|    - Write unit tests for the ORPCManager | ||||
|    - Write integration tests for the entire system | ||||
|  | ||||
| ## 5. Detailed Method Specifications | ||||
|  | ||||
| ### 5.1 ORPCManager Methods | ||||
|  | ||||
| #### NewORPCManager() | ||||
| - Creates a new instance of the ORPCManager | ||||
| - Initializes the specs map | ||||
|  | ||||
| #### LoadSpecs(dir string) error | ||||
| - Reads all JSON files in the specified directory | ||||
| - For each file, calls LoadSpec() | ||||
| - Returns an error if any file fails to load | ||||
|  | ||||
| #### LoadSpec(path string) error | ||||
| - Reads the JSON file at the specified path | ||||
| - Parses the JSON into an OpenRPCSpec struct | ||||
| - Validates the specification | ||||
| - Stores the specification in the specs map using the filename (without extension) as the key | ||||
| - Returns an error if any step fails | ||||
|  | ||||
| #### GetSpec(name string) *OpenRPCSpec | ||||
| - Returns the OpenRPCSpec with the specified name | ||||
| - Returns nil if the specification doesn't exist | ||||
|  | ||||
| #### ListSpecs() []string | ||||
| - Returns a list of all loaded specification names | ||||
|  | ||||
| #### ListMethods(specName string) []string | ||||
| - Returns a list of all method names in the specified specification | ||||
| - Returns an empty list if the specification doesn't exist | ||||
|  | ||||
| #### GetMethod(specName string, methodName string) *Method | ||||
| - Returns the Method with the specified name from the specified specification | ||||
| - Returns nil if the specification or method doesn't exist | ||||
|  | ||||
| ## 6. Example Usage | ||||
|  | ||||
| ```go | ||||
| // Initialize the ORPCManager | ||||
| manager := openrpc.NewORPCManager() | ||||
|  | ||||
| // Load all specifications from the services directory | ||||
| err := manager.LoadSpecs("pkg/openrpc/services") | ||||
| if err != nil { | ||||
|     log.Fatalf("Failed to load specifications: %v", err) | ||||
| } | ||||
|  | ||||
| // List all loaded specifications | ||||
| specs := manager.ListSpecs() | ||||
| fmt.Println("Loaded specifications:") | ||||
| for _, spec := range specs { | ||||
|     fmt.Printf("- %s\n", spec) | ||||
| } | ||||
|  | ||||
| // List all methods in the zinit specification | ||||
| methods := manager.ListMethods("zinit") | ||||
| fmt.Println("\nMethods in zinit specification:") | ||||
| for _, method := range methods { | ||||
|     fmt.Printf("- %s\n", method) | ||||
| } | ||||
|  | ||||
| // Get details for a specific method | ||||
| method := manager.GetMethod("zinit", "service_list") | ||||
| if method != nil { | ||||
|     fmt.Printf("\nDetails for method 'service_list':\n") | ||||
|     fmt.Printf("Description: %s\n", method.Description) | ||||
|     fmt.Printf("Parameters: %d\n", len(method.Params)) | ||||
|     fmt.Printf("Examples: %d\n", len(method.Examples)) | ||||
| } | ||||
							
								
								
									
										117
									
								
								pkg/openrpc/manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								pkg/openrpc/manager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| package openrpc | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/openrpc/models" | ||||
| ) | ||||
|  | ||||
| // ORPCManager manages OpenRPC specifications | ||||
| type ORPCManager struct { | ||||
| 	specs map[string]*models.OpenRPCSpec | ||||
| } | ||||
|  | ||||
| // NewORPCManager creates a new OpenRPC Manager | ||||
| func NewORPCManager() *ORPCManager { | ||||
| 	return &ORPCManager{ | ||||
| 		specs: make(map[string]*models.OpenRPCSpec), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LoadSpecs loads all OpenRPC specifications from a directory | ||||
| func (m *ORPCManager) LoadSpecs(dir string) error { | ||||
| 	files, err := ioutil.ReadDir(dir) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to read directory: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, file := range files { | ||||
| 		if file.IsDir() { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if !strings.HasSuffix(file.Name(), ".json") { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		path := filepath.Join(dir, file.Name()) | ||||
| 		if err := m.LoadSpec(path); err != nil { | ||||
| 			return fmt.Errorf("failed to load spec %s: %w", file.Name(), err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // LoadSpec loads an OpenRPC specification from a file | ||||
| func (m *ORPCManager) LoadSpec(path string) error { | ||||
| 	// Read the file | ||||
| 	data, err := ioutil.ReadFile(path) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to read file: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Parse the JSON | ||||
| 	var spec models.OpenRPCSpec | ||||
| 	if err := json.Unmarshal(data, &spec); err != nil { | ||||
| 		return fmt.Errorf("failed to parse JSON: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Validate the specification | ||||
| 	if err := spec.Validate(); err != nil { | ||||
| 		return fmt.Errorf("invalid specification: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Store the specification | ||||
| 	name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) | ||||
| 	m.specs[name] = &spec | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetSpec returns an OpenRPC specification by name | ||||
| func (m *ORPCManager) GetSpec(name string) *models.OpenRPCSpec { | ||||
| 	return m.specs[name] | ||||
| } | ||||
|  | ||||
| // ListSpecs returns a list of all loaded specification names | ||||
| func (m *ORPCManager) ListSpecs() []string { | ||||
| 	var names []string | ||||
| 	for name := range m.specs { | ||||
| 		names = append(names, name) | ||||
| 	} | ||||
| 	return names | ||||
| } | ||||
|  | ||||
| // ListMethods returns a list of all method names in a specification | ||||
| func (m *ORPCManager) ListMethods(specName string) []string { | ||||
| 	spec := m.GetSpec(specName) | ||||
| 	if spec == nil { | ||||
| 		return []string{} | ||||
| 	} | ||||
|  | ||||
| 	var methods []string | ||||
| 	for _, method := range spec.Methods { | ||||
| 		methods = append(methods, method.Name) | ||||
| 	} | ||||
| 	return methods | ||||
| } | ||||
|  | ||||
| // GetMethod returns a method from a specification | ||||
| func (m *ORPCManager) GetMethod(specName, methodName string) *models.Method { | ||||
| 	spec := m.GetSpec(specName) | ||||
| 	if spec == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for _, method := range spec.Methods { | ||||
| 		if method.Name == methodName { | ||||
| 			return &method | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										89
									
								
								pkg/openrpc/models/spec.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								pkg/openrpc/models/spec.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| package models | ||||
|  | ||||
| // OpenRPCSpec represents an OpenRPC specification document | ||||
| type OpenRPCSpec struct { | ||||
| 	OpenRPC string     `json:"openrpc"` | ||||
| 	Info    InfoObject `json:"info"` | ||||
| 	Servers []Server   `json:"servers"` | ||||
| 	Methods []Method   `json:"methods"` | ||||
| } | ||||
|  | ||||
| // InfoObject contains metadata about the API | ||||
| type InfoObject struct { | ||||
| 	Version     string         `json:"version"` | ||||
| 	Title       string         `json:"title"` | ||||
| 	Description string         `json:"description,omitempty"` | ||||
| 	License     *LicenseObject `json:"license,omitempty"` | ||||
| } | ||||
|  | ||||
| // LicenseObject contains license information for the API | ||||
| type LicenseObject struct { | ||||
| 	Name string `json:"name"` | ||||
| } | ||||
|  | ||||
| // Server represents a server that provides the API | ||||
| type Server struct { | ||||
| 	Name string `json:"name"` | ||||
| 	URL  string `json:"url"` | ||||
| } | ||||
|  | ||||
| // Method represents a method in the API | ||||
| type Method struct { | ||||
| 	Name        string        `json:"name"` | ||||
| 	Description string        `json:"description,omitempty"` | ||||
| 	Params      []Parameter   `json:"params"` | ||||
| 	Result      ResultObject  `json:"result"` | ||||
| 	Examples    []Example     `json:"examples,omitempty"` | ||||
| 	Errors      []ErrorObject `json:"errors,omitempty"` | ||||
| } | ||||
|  | ||||
| // Parameter represents a parameter for a method | ||||
| type Parameter struct { | ||||
| 	Name        string       `json:"name"` | ||||
| 	Description string       `json:"description,omitempty"` | ||||
| 	Required    bool         `json:"required"` | ||||
| 	Schema      SchemaObject `json:"schema"` | ||||
| } | ||||
|  | ||||
| // ResultObject represents the result of a method | ||||
| type ResultObject struct { | ||||
| 	Name        string       `json:"name"` | ||||
| 	Description string       `json:"description,omitempty"` | ||||
| 	Schema      SchemaObject `json:"schema"` | ||||
| } | ||||
|  | ||||
| // SchemaObject represents a JSON Schema object | ||||
| type SchemaObject struct { | ||||
| 	Type                 string                  `json:"type,omitempty"` | ||||
| 	Properties           map[string]SchemaObject `json:"properties,omitempty"` | ||||
| 	Items                *SchemaObject           `json:"items,omitempty"` | ||||
| 	AdditionalProperties *SchemaObject           `json:"additionalProperties,omitempty"` | ||||
| 	Description          string                  `json:"description,omitempty"` | ||||
| 	Enum                 []string                `json:"enum,omitempty"` | ||||
| } | ||||
|  | ||||
| // Example represents an example for a method | ||||
| type Example struct { | ||||
| 	Name   string                   `json:"name"` | ||||
| 	Params []map[string]interface{} `json:"params"` | ||||
| 	Result ExampleResultObject      `json:"result"` | ||||
| } | ||||
|  | ||||
| // ExampleResultObject represents the result of an example | ||||
| type ExampleResultObject struct { | ||||
| 	Name  string      `json:"name"` | ||||
| 	Value interface{} `json:"value"` | ||||
| } | ||||
|  | ||||
| // ErrorObject represents an error that can be returned by a method | ||||
| type ErrorObject struct { | ||||
| 	Code    int    `json:"code"` | ||||
| 	Message string `json:"message"` | ||||
| 	Data    string `json:"data,omitempty"` | ||||
| } | ||||
|  | ||||
| // Validate validates the OpenRPC specification | ||||
| func (spec *OpenRPCSpec) Validate() error { | ||||
| 	// TODO: Implement validation logic | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										873
									
								
								pkg/openrpc/services/zinit.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										873
									
								
								pkg/openrpc/services/zinit.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,873 @@ | ||||
| { | ||||
|   "openrpc": "1.2.6", | ||||
|   "info": { | ||||
|     "version": "1.0.0", | ||||
|     "title": "Zinit JSON-RPC API", | ||||
|     "description": "JSON-RPC 2.0 API for controlling and querying Zinit services", | ||||
|     "license": { | ||||
|       "name": "MIT" | ||||
|     } | ||||
|   }, | ||||
|   "servers": [ | ||||
|     { | ||||
|       "name": "Unix Socket", | ||||
|       "url": "unix:///tmp/zinit.sock" | ||||
|     } | ||||
|   ], | ||||
|   "methods": [ | ||||
|     { | ||||
|       "name": "rpc_discover", | ||||
|       "description": "Returns the OpenRPC specification for the API", | ||||
|       "params": [], | ||||
|       "result": { | ||||
|         "name": "OpenRPCSpec", | ||||
|         "description": "The OpenRPC specification", | ||||
|         "schema": { | ||||
|           "type": "object" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Get API specification", | ||||
|           "params": [], | ||||
|           "result": { | ||||
|             "name": "OpenRPCSpecResult", | ||||
|             "value": { | ||||
|               "openrpc": "1.2.6", | ||||
|               "info": { | ||||
|                 "version": "1.0.0", | ||||
|                 "title": "Zinit JSON-RPC API" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_list", | ||||
|       "description": "Lists all services managed by Zinit", | ||||
|       "params": [], | ||||
|       "result": { | ||||
|         "name": "ServiceList", | ||||
|         "description": "A map of service names to their current states", | ||||
|         "schema": { | ||||
|           "type": "object", | ||||
|           "additionalProperties": { | ||||
|             "type": "string", | ||||
|             "description": "Service state (Running, Success, Error, etc.)" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "List all services", | ||||
|           "params": [], | ||||
|           "result": { | ||||
|             "name": "ServiceListResult", | ||||
|             "value": { | ||||
|               "service1": "Running", | ||||
|               "service2": "Success", | ||||
|               "service3": "Error" | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_status", | ||||
|       "description": "Shows detailed status information for a specific service", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "ServiceStatus", | ||||
|         "description": "Detailed status information for the service", | ||||
|         "schema": { | ||||
|           "type": "object", | ||||
|           "properties": { | ||||
|             "name": { | ||||
|               "type": "string", | ||||
|               "description": "Service name" | ||||
|             }, | ||||
|             "pid": { | ||||
|               "type": "integer", | ||||
|               "description": "Process ID of the running service (if running)" | ||||
|             }, | ||||
|             "state": { | ||||
|               "type": "string", | ||||
|               "description": "Current state of the service (Running, Success, Error, etc.)" | ||||
|             }, | ||||
|             "target": { | ||||
|               "type": "string", | ||||
|               "description": "Target state of the service (Up, Down)" | ||||
|             }, | ||||
|             "after": { | ||||
|               "type": "object", | ||||
|               "description": "Dependencies of the service and their states", | ||||
|               "additionalProperties": { | ||||
|                 "type": "string", | ||||
|                 "description": "State of the dependency" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Get status of redis service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "ServiceStatusResult", | ||||
|             "value": { | ||||
|               "name": "redis", | ||||
|               "pid": 1234, | ||||
|               "state": "Running", | ||||
|               "target": "Up", | ||||
|               "after": { | ||||
|                 "dependency1": "Success", | ||||
|                 "dependency2": "Running" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "service name \"unknown\" unknown" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_start", | ||||
|       "description": "Starts a service", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to start", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "StartResult", | ||||
|         "description": "Result of the start operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Start redis service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "StartResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "service name \"unknown\" unknown" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_stop", | ||||
|       "description": "Stops a service", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to stop", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "StopResult", | ||||
|         "description": "Result of the stop operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Stop redis service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "StopResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "service name \"unknown\" unknown" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32003, | ||||
|           "message": "Service is down", | ||||
|           "data": "service \"redis\" is down" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_monitor", | ||||
|       "description": "Starts monitoring a service. The service configuration is loaded from the config directory.", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to monitor", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "MonitorResult", | ||||
|         "description": "Result of the monitor operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Monitor redis service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "MonitorResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32001, | ||||
|           "message": "Service already monitored", | ||||
|           "data": "service \"redis\" already monitored" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32005, | ||||
|           "message": "Config error", | ||||
|           "data": "failed to load service configuration" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_forget", | ||||
|       "description": "Stops monitoring a service. You can only forget a stopped service.", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to forget", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "ForgetResult", | ||||
|         "description": "Result of the forget operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Forget redis service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "ForgetResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "service name \"unknown\" unknown" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32002, | ||||
|           "message": "Service is up", | ||||
|           "data": "service \"redis\" is up" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_kill", | ||||
|       "description": "Sends a signal to a running service", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to send the signal to", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "name": "signal", | ||||
|           "description": "The signal to send (e.g., SIGTERM, SIGKILL)", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "KillResult", | ||||
|         "description": "Result of the kill operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Send SIGTERM to redis service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             }, | ||||
|             { | ||||
|               "name": "signal", | ||||
|               "value": "SIGTERM" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "KillResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "service name \"unknown\" unknown" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32003, | ||||
|           "message": "Service is down", | ||||
|           "data": "service \"redis\" is down" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32004, | ||||
|           "message": "Invalid signal", | ||||
|           "data": "invalid signal: INVALID" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "system_shutdown", | ||||
|       "description": "Stops all services and powers off the system", | ||||
|       "params": [], | ||||
|       "result": { | ||||
|         "name": "ShutdownResult", | ||||
|         "description": "Result of the shutdown operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Shutdown the system", | ||||
|           "params": [], | ||||
|           "result": { | ||||
|             "name": "ShutdownResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32006, | ||||
|           "message": "Shutting down", | ||||
|           "data": "system is already shutting down" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "system_reboot", | ||||
|       "description": "Stops all services and reboots the system", | ||||
|       "params": [], | ||||
|       "result": { | ||||
|         "name": "RebootResult", | ||||
|         "description": "Result of the reboot operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Reboot the system", | ||||
|           "params": [], | ||||
|           "result": { | ||||
|             "name": "RebootResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32006, | ||||
|           "message": "Shutting down", | ||||
|           "data": "system is already shutting down" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_create", | ||||
|       "description": "Creates a new service configuration file", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to create", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "name": "content", | ||||
|           "description": "The service configuration content", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|               "exec": { | ||||
|                 "type": "string", | ||||
|                 "description": "Command to run" | ||||
|               }, | ||||
|               "oneshot": { | ||||
|                 "type": "boolean", | ||||
|                 "description": "Whether the service should be restarted" | ||||
|               }, | ||||
|               "after": { | ||||
|                 "type": "array", | ||||
|                 "items": { | ||||
|                   "type": "string" | ||||
|                 }, | ||||
|                 "description": "Services that must be running before this one starts" | ||||
|               }, | ||||
|               "log": { | ||||
|                 "type": "string", | ||||
|                 "enum": ["null", "ring", "stdout"], | ||||
|                 "description": "How to handle service output" | ||||
|               }, | ||||
|               "env": { | ||||
|                 "type": "object", | ||||
|                 "additionalProperties": { | ||||
|                   "type": "string" | ||||
|                 }, | ||||
|                 "description": "Environment variables for the service" | ||||
|               }, | ||||
|               "shutdown_timeout": { | ||||
|                 "type": "integer", | ||||
|                 "description": "Maximum time to wait for service to stop during shutdown" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "CreateServiceResult", | ||||
|         "description": "Result of the create operation", | ||||
|         "schema": { | ||||
|           "type": "string" | ||||
|         } | ||||
|       }, | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32007, | ||||
|           "message": "Service already exists", | ||||
|           "data": "Service 'name' already exists" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32008, | ||||
|           "message": "Service file error", | ||||
|           "data": "Failed to create service file" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_delete", | ||||
|       "description": "Deletes a service configuration file", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to delete", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "DeleteServiceResult", | ||||
|         "description": "Result of the delete operation", | ||||
|         "schema": { | ||||
|           "type": "string" | ||||
|         } | ||||
|       }, | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "Service 'name' not found" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32008, | ||||
|           "message": "Service file error", | ||||
|           "data": "Failed to delete service file" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_get", | ||||
|       "description": "Gets a service configuration file", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to get", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "GetServiceResult", | ||||
|         "description": "The service configuration", | ||||
|         "schema": { | ||||
|           "type": "object" | ||||
|         } | ||||
|       }, | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "Service 'name' not found" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32008, | ||||
|           "message": "Service file error", | ||||
|           "data": "Failed to read service file" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "service_stats", | ||||
|       "description": "Get memory and CPU usage statistics for a service", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "The name of the service to get stats for", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "ServiceStats", | ||||
|         "description": "Memory and CPU usage statistics for the service", | ||||
|         "schema": { | ||||
|           "type": "object", | ||||
|           "properties": { | ||||
|             "name": { | ||||
|               "type": "string", | ||||
|               "description": "Service name" | ||||
|             }, | ||||
|             "pid": { | ||||
|               "type": "integer", | ||||
|               "description": "Process ID of the service" | ||||
|             }, | ||||
|             "memory_usage": { | ||||
|               "type": "integer", | ||||
|               "description": "Memory usage in bytes" | ||||
|             }, | ||||
|             "cpu_usage": { | ||||
|               "type": "number", | ||||
|               "description": "CPU usage as a percentage (0-100)" | ||||
|             }, | ||||
|             "children": { | ||||
|               "type": "array", | ||||
|               "description": "Stats for child processes", | ||||
|               "items": { | ||||
|                 "type": "object", | ||||
|                 "properties": { | ||||
|                   "pid": { | ||||
|                     "type": "integer", | ||||
|                     "description": "Process ID of the child process" | ||||
|                   }, | ||||
|                   "memory_usage": { | ||||
|                     "type": "integer", | ||||
|                     "description": "Memory usage in bytes" | ||||
|                   }, | ||||
|                   "cpu_usage": { | ||||
|                     "type": "number", | ||||
|                     "description": "CPU usage as a percentage (0-100)" | ||||
|                   } | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Get stats for redis service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "ServiceStatsResult", | ||||
|             "value": { | ||||
|               "name": "redis", | ||||
|               "pid": 1234, | ||||
|               "memory_usage": 10485760, | ||||
|               "cpu_usage": 2.5, | ||||
|               "children": [ | ||||
|                 { | ||||
|                   "pid": 1235, | ||||
|                   "memory_usage": 5242880, | ||||
|                   "cpu_usage": 1.2 | ||||
|                 } | ||||
|               ] | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32000, | ||||
|           "message": "Service not found", | ||||
|           "data": "service name \"unknown\" unknown" | ||||
|         }, | ||||
|         { | ||||
|           "code": -32003, | ||||
|           "message": "Service is down", | ||||
|           "data": "service \"redis\" is down" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "system_start_http_server", | ||||
|       "description": "Start an HTTP/RPC server at the specified address", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "address", | ||||
|           "description": "The network address to bind the server to (e.g., '127.0.0.1:8080')", | ||||
|           "required": true, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "StartHttpServerResult", | ||||
|         "description": "Result of the start HTTP server operation", | ||||
|         "schema": { | ||||
|           "type": "string" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Start HTTP server on localhost:8080", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "address", | ||||
|               "value": "127.0.0.1:8080" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "StartHttpServerResult", | ||||
|             "value": "HTTP server started at 127.0.0.1:8080" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32602, | ||||
|           "message": "Invalid address", | ||||
|           "data": "Invalid network address format" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "system_stop_http_server", | ||||
|       "description": "Stop the HTTP/RPC server if running", | ||||
|       "params": [], | ||||
|       "result": { | ||||
|         "name": "StopHttpServerResult", | ||||
|         "description": "Result of the stop HTTP server operation", | ||||
|         "schema": { | ||||
|           "type": "null" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Stop the HTTP server", | ||||
|           "params": [], | ||||
|           "result": { | ||||
|             "name": "StopHttpServerResult", | ||||
|             "value": null | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "errors": [ | ||||
|         { | ||||
|           "code": -32602, | ||||
|           "message": "Server not running", | ||||
|           "data": "No HTTP server is currently running" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "stream_currentLogs", | ||||
|       "description": "Get current logs from zinit and monitored services", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "Optional service name filter. If provided, only logs from this service will be returned", | ||||
|           "required": false, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "LogsResult", | ||||
|         "description": "Array of log strings", | ||||
|         "schema": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Get all logs", | ||||
|           "params": [], | ||||
|           "result": { | ||||
|             "name": "LogsResult", | ||||
|             "value": [ | ||||
|               "2023-01-01T12:00:00 redis: Starting service", | ||||
|               "2023-01-01T12:00:01 nginx: Starting service" | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "name": "Get logs for a specific service", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "LogsResult", | ||||
|             "value": [ | ||||
|               "2023-01-01T12:00:00 redis: Starting service", | ||||
|               "2023-01-01T12:00:02 redis: Service started" | ||||
|             ] | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "name": "stream_subscribeLogs", | ||||
|       "description": "Subscribe to log messages generated by zinit and monitored services", | ||||
|       "params": [ | ||||
|         { | ||||
|           "name": "name", | ||||
|           "description": "Optional service name filter. If provided, only logs from this service will be returned", | ||||
|           "required": false, | ||||
|           "schema": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "result": { | ||||
|         "name": "LogSubscription", | ||||
|         "description": "A subscription to log messages", | ||||
|         "schema": { | ||||
|           "type": "string" | ||||
|         } | ||||
|       }, | ||||
|       "examples": [ | ||||
|         { | ||||
|           "name": "Subscribe to all logs", | ||||
|           "params": [], | ||||
|           "result": { | ||||
|             "name": "LogSubscription", | ||||
|             "value": "2023-01-01T12:00:00 redis: Service started" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "name": "Subscribe to filtered logs", | ||||
|           "params": [ | ||||
|             { | ||||
|               "name": "name", | ||||
|               "value": "redis" | ||||
|             } | ||||
|           ], | ||||
|           "result": { | ||||
|             "name": "LogSubscription", | ||||
|             "value": "2023-01-01T12:00:00 redis: Service started" | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user