diff --git a/workflow/plugins/go/convert/convert_parse_json/convert_parse_json.go b/workflow/plugins/go/convert/convert_parse_json/convert_parse_json.go index e580febfd..822b415cd 100644 --- a/workflow/plugins/go/convert/convert_parse_json/convert_parse_json.go +++ b/workflow/plugins/go/convert/convert_parse_json/convert_parse_json.go @@ -1,23 +1,37 @@ -// Package convert_parse_json provides the parse JSON plugin. +// Package convert_parse_json provides a workflow plugin for parsing JSON strings. package convert_parse_json import ( "encoding/json" - - plugin "metabuilder/workflow/plugins/go" ) -// Run parses a JSON string to object. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// ConvertParseJson implements the NodeExecutor interface for parsing JSON strings. +type ConvertParseJson struct { + NodeType string + Category string + Description string +} + +// NewConvertParseJson creates a new ConvertParseJson instance. +func NewConvertParseJson() *ConvertParseJson { + return &ConvertParseJson{ + NodeType: "convert.parse_json", + Category: "convert", + Description: "Parse a JSON string to object", + } +} + +// Execute runs the plugin logic. +func (p *ConvertParseJson) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { str, ok := inputs["string"].(string) if !ok { - return map[string]interface{}{"result": nil, "error": "string is required"}, nil + return map[string]interface{}{"result": nil, "error": "string is required"} } var result interface{} if err := json.Unmarshal([]byte(str), &result); err != nil { - return map[string]interface{}{"result": nil, "error": err.Error()}, nil + return map[string]interface{}{"result": nil, "error": err.Error()} } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/convert/convert_parse_json/factory.go b/workflow/plugins/go/convert/convert_parse_json/factory.go new file mode 100644 index 000000000..97cb40032 --- /dev/null +++ b/workflow/plugins/go/convert/convert_parse_json/factory.go @@ -0,0 +1,7 @@ +// Package convert_parse_json provides factory for ConvertParseJson plugin. +package convert_parse_json + +// Create returns a new ConvertParseJson instance. +func Create() *ConvertParseJson { + return NewConvertParseJson() +} diff --git a/workflow/plugins/go/convert/convert_parse_json/package.json b/workflow/plugins/go/convert/convert_parse_json/package.json index 1c84b411a..74b62fbe3 100644 --- a/workflow/plugins/go/convert/convert_parse_json/package.json +++ b/workflow/plugins/go/convert/convert_parse_json/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_parse_json.go", + "files": ["convert_parse_json.go", "factory.go"], "metadata": { "plugin_type": "convert.parse_json", - "category": "convert" + "category": "convert", + "struct": "ConvertParseJson", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/convert/convert_to_boolean/convert_to_boolean.go b/workflow/plugins/go/convert/convert_to_boolean/convert_to_boolean.go index 658a71a8a..c8c0c87eb 100644 --- a/workflow/plugins/go/convert/convert_to_boolean/convert_to_boolean.go +++ b/workflow/plugins/go/convert/convert_to_boolean/convert_to_boolean.go @@ -1,14 +1,28 @@ -// Package convert_to_boolean provides the convert to boolean plugin. +// Package convert_to_boolean provides a workflow plugin for converting values to booleans. package convert_to_boolean import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run converts a value to boolean. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// ConvertToBoolean implements the NodeExecutor interface for converting values to booleans. +type ConvertToBoolean struct { + NodeType string + Category string + Description string +} + +// NewConvertToBoolean creates a new ConvertToBoolean instance. +func NewConvertToBoolean() *ConvertToBoolean { + return &ConvertToBoolean{ + NodeType: "convert.to_boolean", + Category: "convert", + Description: "Convert a value to boolean", + } +} + +// Execute runs the plugin logic. +func (p *ConvertToBoolean) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { value := inputs["value"] var result bool @@ -31,5 +45,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int result = true // Non-nil values are truthy } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/convert/convert_to_boolean/factory.go b/workflow/plugins/go/convert/convert_to_boolean/factory.go new file mode 100644 index 000000000..8f8bfac8c --- /dev/null +++ b/workflow/plugins/go/convert/convert_to_boolean/factory.go @@ -0,0 +1,7 @@ +// Package convert_to_boolean provides factory for ConvertToBoolean plugin. +package convert_to_boolean + +// Create returns a new ConvertToBoolean instance. +func Create() *ConvertToBoolean { + return NewConvertToBoolean() +} diff --git a/workflow/plugins/go/convert/convert_to_boolean/package.json b/workflow/plugins/go/convert/convert_to_boolean/package.json index 5bbe99ecb..d49ea9f5f 100644 --- a/workflow/plugins/go/convert/convert_to_boolean/package.json +++ b/workflow/plugins/go/convert/convert_to_boolean/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_boolean.go", + "files": ["convert_to_boolean.go", "factory.go"], "metadata": { "plugin_type": "convert.to_boolean", - "category": "convert" + "category": "convert", + "struct": "ConvertToBoolean", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/convert/convert_to_json/convert_to_json.go b/workflow/plugins/go/convert/convert_to_json/convert_to_json.go index 544595e9f..ec04e17fc 100644 --- a/workflow/plugins/go/convert/convert_to_json/convert_to_json.go +++ b/workflow/plugins/go/convert/convert_to_json/convert_to_json.go @@ -1,14 +1,28 @@ -// Package convert_to_json provides the convert to JSON plugin. +// Package convert_to_json provides a workflow plugin for converting values to JSON. package convert_to_json import ( "encoding/json" - - plugin "metabuilder/workflow/plugins/go" ) -// Run converts a value to JSON string. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// ConvertToJson implements the NodeExecutor interface for converting values to JSON. +type ConvertToJson struct { + NodeType string + Category string + Description string +} + +// NewConvertToJson creates a new ConvertToJson instance. +func NewConvertToJson() *ConvertToJson { + return &ConvertToJson{ + NodeType: "convert.to_json", + Category: "convert", + Description: "Convert a value to JSON string", + } +} + +// Execute runs the plugin logic. +func (p *ConvertToJson) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { value := inputs["value"] pretty := false if p, ok := inputs["pretty"].(bool); ok { @@ -25,8 +39,8 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } if err != nil { - return map[string]interface{}{"result": "", "error": err.Error()}, nil + return map[string]interface{}{"result": "", "error": err.Error()} } - return map[string]interface{}{"result": string(bytes)}, nil + return map[string]interface{}{"result": string(bytes)} } diff --git a/workflow/plugins/go/convert/convert_to_json/factory.go b/workflow/plugins/go/convert/convert_to_json/factory.go new file mode 100644 index 000000000..3acc097db --- /dev/null +++ b/workflow/plugins/go/convert/convert_to_json/factory.go @@ -0,0 +1,7 @@ +// Package convert_to_json provides factory for ConvertToJson plugin. +package convert_to_json + +// Create returns a new ConvertToJson instance. +func Create() *ConvertToJson { + return NewConvertToJson() +} diff --git a/workflow/plugins/go/convert/convert_to_json/package.json b/workflow/plugins/go/convert/convert_to_json/package.json index 238a59659..3cdf8de35 100644 --- a/workflow/plugins/go/convert/convert_to_json/package.json +++ b/workflow/plugins/go/convert/convert_to_json/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_json.go", + "files": ["convert_to_json.go", "factory.go"], "metadata": { "plugin_type": "convert.to_json", - "category": "convert" + "category": "convert", + "struct": "ConvertToJson", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/convert/convert_to_number/convert_to_number.go b/workflow/plugins/go/convert/convert_to_number/convert_to_number.go index 54069179c..93e3fb2fa 100644 --- a/workflow/plugins/go/convert/convert_to_number/convert_to_number.go +++ b/workflow/plugins/go/convert/convert_to_number/convert_to_number.go @@ -1,14 +1,28 @@ -// Package convert_to_number provides the convert to number plugin. +// Package convert_to_number provides a workflow plugin for converting values to numbers. package convert_to_number import ( "strconv" - - plugin "metabuilder/workflow/plugins/go" ) -// Run converts a value to number. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// ConvertToNumber implements the NodeExecutor interface for converting values to numbers. +type ConvertToNumber struct { + NodeType string + Category string + Description string +} + +// NewConvertToNumber creates a new ConvertToNumber instance. +func NewConvertToNumber() *ConvertToNumber { + return &ConvertToNumber{ + NodeType: "convert.to_number", + Category: "convert", + Description: "Convert a value to number", + } +} + +// Execute runs the plugin logic. +func (p *ConvertToNumber) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { value := inputs["value"] var result float64 @@ -24,7 +38,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int case string: result, err = strconv.ParseFloat(v, 64) if err != nil { - return map[string]interface{}{"result": 0, "error": "invalid number string"}, nil + return map[string]interface{}{"result": 0, "error": "invalid number string"} } case bool: if v { @@ -33,8 +47,8 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int result = 0 } default: - return map[string]interface{}{"result": 0, "error": "cannot convert to number"}, nil + return map[string]interface{}{"result": 0, "error": "cannot convert to number"} } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/convert/convert_to_number/factory.go b/workflow/plugins/go/convert/convert_to_number/factory.go new file mode 100644 index 000000000..299d35fee --- /dev/null +++ b/workflow/plugins/go/convert/convert_to_number/factory.go @@ -0,0 +1,7 @@ +// Package convert_to_number provides factory for ConvertToNumber plugin. +package convert_to_number + +// Create returns a new ConvertToNumber instance. +func Create() *ConvertToNumber { + return NewConvertToNumber() +} diff --git a/workflow/plugins/go/convert/convert_to_number/package.json b/workflow/plugins/go/convert/convert_to_number/package.json index 62512df79..f6eac92ec 100644 --- a/workflow/plugins/go/convert/convert_to_number/package.json +++ b/workflow/plugins/go/convert/convert_to_number/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_number.go", + "files": ["convert_to_number.go", "factory.go"], "metadata": { "plugin_type": "convert.to_number", - "category": "convert" + "category": "convert", + "struct": "ConvertToNumber", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/convert/convert_to_string/convert_to_string.go b/workflow/plugins/go/convert/convert_to_string/convert_to_string.go index 8faf85b4d..118739be6 100644 --- a/workflow/plugins/go/convert/convert_to_string/convert_to_string.go +++ b/workflow/plugins/go/convert/convert_to_string/convert_to_string.go @@ -1,15 +1,29 @@ -// Package convert_to_string provides the convert to string plugin. +// Package convert_to_string provides a workflow plugin for converting values to strings. package convert_to_string import ( "encoding/json" "fmt" - - plugin "metabuilder/workflow/plugins/go" ) -// Run converts a value to string. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// ConvertToString implements the NodeExecutor interface for converting values to strings. +type ConvertToString struct { + NodeType string + Category string + Description string +} + +// NewConvertToString creates a new ConvertToString instance. +func NewConvertToString() *ConvertToString { + return &ConvertToString{ + NodeType: "convert.to_string", + Category: "convert", + Description: "Convert a value to string", + } +} + +// Execute runs the plugin logic. +func (p *ConvertToString) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { value := inputs["value"] var result string @@ -29,5 +43,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/convert/convert_to_string/factory.go b/workflow/plugins/go/convert/convert_to_string/factory.go new file mode 100644 index 000000000..b5a58ed65 --- /dev/null +++ b/workflow/plugins/go/convert/convert_to_string/factory.go @@ -0,0 +1,7 @@ +// Package convert_to_string provides factory for ConvertToString plugin. +package convert_to_string + +// Create returns a new ConvertToString instance. +func Create() *ConvertToString { + return NewConvertToString() +} diff --git a/workflow/plugins/go/convert/convert_to_string/package.json b/workflow/plugins/go/convert/convert_to_string/package.json index c09d0f54e..a00f37483 100644 --- a/workflow/plugins/go/convert/convert_to_string/package.json +++ b/workflow/plugins/go/convert/convert_to_string/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_string.go", + "files": ["convert_to_string.go", "factory.go"], "metadata": { "plugin_type": "convert.to_string", - "category": "convert" + "category": "convert", + "struct": "ConvertToString", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/dict/dict_delete/dict_delete.go b/workflow/plugins/go/dict/dict_delete/dict_delete.go index 26653caa1..d4753a610 100644 --- a/workflow/plugins/go/dict/dict_delete/dict_delete.go +++ b/workflow/plugins/go/dict/dict_delete/dict_delete.go @@ -1,13 +1,28 @@ -// Package dict_delete provides the dictionary delete plugin. +// Package dict_delete provides a workflow plugin for deleting dictionary keys. package dict_delete import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run removes a key from a dictionary. +// DictDelete implements the NodeExecutor interface for deleting dictionary keys. +type DictDelete struct { + NodeType string + Category string + Description string +} + +// NewDictDelete creates a new DictDelete instance. +func NewDictDelete() *DictDelete { + return &DictDelete{ + NodeType: "dict.delete", + Category: "dict", + Description: "Delete a key from a dictionary", + } +} + +// Execute runs the plugin logic. +// Removes a key from a dictionary. // Supports dot notation for nested keys (e.g., "user.name"). // Inputs: // - dict: the dictionary to modify @@ -16,10 +31,10 @@ import ( // Returns: // - result: the modified dictionary // - deleted: whether the key was found and deleted -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *DictDelete) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { dict, ok := inputs["dict"].(map[string]interface{}) if !ok { - return map[string]interface{}{"result": map[string]interface{}{}, "deleted": false}, nil + return map[string]interface{}{"result": map[string]interface{}{}, "deleted": false} } // Make a shallow copy to avoid mutating the original @@ -27,7 +42,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int key, ok := inputs["key"].(string) if !ok { - return map[string]interface{}{"result": dict, "deleted": false}, nil + return map[string]interface{}{"result": dict, "deleted": false} } // Handle dot notation for nested keys @@ -37,9 +52,9 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int // Simple key if _, exists := dict[key]; exists { delete(dict, key) - return map[string]interface{}{"result": dict, "deleted": true}, nil + return map[string]interface{}{"result": dict, "deleted": true} } - return map[string]interface{}{"result": dict, "deleted": false}, nil + return map[string]interface{}{"result": dict, "deleted": false} } // Nested key - navigate to parent and delete @@ -54,11 +69,11 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int current = copied } else { // Cannot descend further - return map[string]interface{}{"result": dict, "deleted": false}, nil + return map[string]interface{}{"result": dict, "deleted": false} } } else { // Path does not exist - return map[string]interface{}{"result": dict, "deleted": false}, nil + return map[string]interface{}{"result": dict, "deleted": false} } } @@ -66,10 +81,10 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int finalKey := parts[len(parts)-1] if _, exists := current[finalKey]; exists { delete(current, finalKey) - return map[string]interface{}{"result": dict, "deleted": true}, nil + return map[string]interface{}{"result": dict, "deleted": true} } - return map[string]interface{}{"result": dict, "deleted": false}, nil + return map[string]interface{}{"result": dict, "deleted": false} } // copyDict creates a shallow copy of a dictionary. diff --git a/workflow/plugins/go/dict/dict_delete/factory.go b/workflow/plugins/go/dict/dict_delete/factory.go new file mode 100644 index 000000000..f6eb3b123 --- /dev/null +++ b/workflow/plugins/go/dict/dict_delete/factory.go @@ -0,0 +1,7 @@ +// Package dict_delete provides factory for DictDelete plugin. +package dict_delete + +// Create returns a new DictDelete instance. +func Create() *DictDelete { + return NewDictDelete() +} diff --git a/workflow/plugins/go/dict/dict_delete/package.json b/workflow/plugins/go/dict/dict_delete/package.json index f662db07f..5245f657c 100644 --- a/workflow/plugins/go/dict/dict_delete/package.json +++ b/workflow/plugins/go/dict/dict_delete/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_delete.go", + "files": ["dict_delete.go", "factory.go"], "metadata": { "plugin_type": "dict.delete", - "category": "dict" + "category": "dict", + "struct": "DictDelete", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/dict/dict_get/dict_get.go b/workflow/plugins/go/dict/dict_get/dict_get.go index 1e3173ab9..6472b4cd4 100644 --- a/workflow/plugins/go/dict/dict_get/dict_get.go +++ b/workflow/plugins/go/dict/dict_get/dict_get.go @@ -1,13 +1,28 @@ -// Package dict_get provides the dictionary get plugin. +// Package dict_get provides a workflow plugin for getting dictionary values. package dict_get import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run retrieves a value from a dictionary by key. +// DictGet implements the NodeExecutor interface for getting dictionary values. +type DictGet struct { + NodeType string + Category string + Description string +} + +// NewDictGet creates a new DictGet instance. +func NewDictGet() *DictGet { + return &DictGet{ + NodeType: "dict.get", + Category: "dict", + Description: "Get a value from a dictionary by key", + } +} + +// Execute runs the plugin logic. +// Retrieves a value from a dictionary by key. // Supports dot notation for nested keys (e.g., "user.name"). // Inputs: // - dict: the dictionary to read from @@ -17,17 +32,17 @@ import ( // Returns: // - result: the value at the key or default // - found: whether the key was found -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *DictGet) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { dict, ok := inputs["dict"].(map[string]interface{}) if !ok { defaultVal := inputs["default"] - return map[string]interface{}{"result": defaultVal, "found": false}, nil + return map[string]interface{}{"result": defaultVal, "found": false} } key, ok := inputs["key"].(string) if !ok { defaultVal := inputs["default"] - return map[string]interface{}{"result": defaultVal, "found": false}, nil + return map[string]interface{}{"result": defaultVal, "found": false} } // Handle dot notation for nested keys @@ -38,7 +53,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int if val, exists := current[part]; exists { if i == len(parts)-1 { // Final key, return the value - return map[string]interface{}{"result": val, "found": true}, nil + return map[string]interface{}{"result": val, "found": true} } // Not the final key, try to descend if nested, ok := val.(map[string]interface{}); ok { @@ -46,15 +61,15 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } else { // Cannot descend further defaultVal := inputs["default"] - return map[string]interface{}{"result": defaultVal, "found": false}, nil + return map[string]interface{}{"result": defaultVal, "found": false} } } else { // Key not found defaultVal := inputs["default"] - return map[string]interface{}{"result": defaultVal, "found": false}, nil + return map[string]interface{}{"result": defaultVal, "found": false} } } defaultVal := inputs["default"] - return map[string]interface{}{"result": defaultVal, "found": false}, nil + return map[string]interface{}{"result": defaultVal, "found": false} } diff --git a/workflow/plugins/go/dict/dict_get/factory.go b/workflow/plugins/go/dict/dict_get/factory.go new file mode 100644 index 000000000..44ee51441 --- /dev/null +++ b/workflow/plugins/go/dict/dict_get/factory.go @@ -0,0 +1,7 @@ +// Package dict_get provides factory for DictGet plugin. +package dict_get + +// Create returns a new DictGet instance. +func Create() *DictGet { + return NewDictGet() +} diff --git a/workflow/plugins/go/dict/dict_get/package.json b/workflow/plugins/go/dict/dict_get/package.json index c8761925e..ae136f1d1 100644 --- a/workflow/plugins/go/dict/dict_get/package.json +++ b/workflow/plugins/go/dict/dict_get/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_get.go", + "files": ["dict_get.go", "factory.go"], "metadata": { "plugin_type": "dict.get", - "category": "dict" + "category": "dict", + "struct": "DictGet", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/dict/dict_keys/dict_keys.go b/workflow/plugins/go/dict/dict_keys/dict_keys.go index 39863b5e1..4b5255bd5 100644 --- a/workflow/plugins/go/dict/dict_keys/dict_keys.go +++ b/workflow/plugins/go/dict/dict_keys/dict_keys.go @@ -1,23 +1,38 @@ -// Package dict_keys provides the dictionary keys plugin. +// Package dict_keys provides a workflow plugin for getting dictionary keys. package dict_keys import ( "sort" - - plugin "metabuilder/workflow/plugins/go" ) -// Run returns all keys from a dictionary. +// DictKeys implements the NodeExecutor interface for getting dictionary keys. +type DictKeys struct { + NodeType string + Category string + Description string +} + +// NewDictKeys creates a new DictKeys instance. +func NewDictKeys() *DictKeys { + return &DictKeys{ + NodeType: "dict.keys", + Category: "dict", + Description: "Get all keys from a dictionary", + } +} + +// Execute runs the plugin logic. +// Returns all keys from a dictionary. // Inputs: // - dict: the dictionary to get keys from // - sorted: (optional) whether to sort keys alphabetically (default: false) // // Returns: // - result: list of keys -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *DictKeys) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { dict, ok := inputs["dict"].(map[string]interface{}) if !ok { - return map[string]interface{}{"result": []interface{}{}}, nil + return map[string]interface{}{"result": []interface{}{}} } keys := make([]interface{}, 0, len(dict)) @@ -34,5 +49,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int }) } - return map[string]interface{}{"result": keys}, nil + return map[string]interface{}{"result": keys} } diff --git a/workflow/plugins/go/dict/dict_keys/factory.go b/workflow/plugins/go/dict/dict_keys/factory.go new file mode 100644 index 000000000..682669800 --- /dev/null +++ b/workflow/plugins/go/dict/dict_keys/factory.go @@ -0,0 +1,7 @@ +// Package dict_keys provides factory for DictKeys plugin. +package dict_keys + +// Create returns a new DictKeys instance. +func Create() *DictKeys { + return NewDictKeys() +} diff --git a/workflow/plugins/go/dict/dict_keys/package.json b/workflow/plugins/go/dict/dict_keys/package.json index 867999f72..b2508564a 100644 --- a/workflow/plugins/go/dict/dict_keys/package.json +++ b/workflow/plugins/go/dict/dict_keys/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_keys.go", + "files": ["dict_keys.go", "factory.go"], "metadata": { "plugin_type": "dict.keys", - "category": "dict" + "category": "dict", + "struct": "DictKeys", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/dict/dict_merge/dict_merge.go b/workflow/plugins/go/dict/dict_merge/dict_merge.go index d2c6ce041..7b63b518f 100644 --- a/workflow/plugins/go/dict/dict_merge/dict_merge.go +++ b/workflow/plugins/go/dict/dict_merge/dict_merge.go @@ -1,11 +1,24 @@ -// Package dict_merge provides the dictionary merge plugin. +// Package dict_merge provides a workflow plugin for merging dictionaries. package dict_merge -import ( - plugin "metabuilder/workflow/plugins/go" -) +// DictMerge implements the NodeExecutor interface for merging dictionaries. +type DictMerge struct { + NodeType string + Category string + Description string +} -// Run combines multiple dictionaries into one. +// NewDictMerge creates a new DictMerge instance. +func NewDictMerge() *DictMerge { + return &DictMerge{ + NodeType: "dict.merge", + Category: "dict", + Description: "Merge multiple dictionaries into one", + } +} + +// Execute runs the plugin logic. +// Combines multiple dictionaries into one. // Later dictionaries override earlier ones for duplicate keys. // Inputs: // - dicts: list of dictionaries to merge @@ -13,10 +26,10 @@ import ( // // Returns: // - result: the merged dictionary -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *DictMerge) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { dicts, ok := inputs["dicts"].([]interface{}) if !ok { - return map[string]interface{}{"result": map[string]interface{}{}}, nil + return map[string]interface{}{"result": map[string]interface{}{}} } deep := false @@ -39,7 +52,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } // shallowMerge copies all keys from src to dst, overwriting existing keys. diff --git a/workflow/plugins/go/dict/dict_merge/factory.go b/workflow/plugins/go/dict/dict_merge/factory.go new file mode 100644 index 000000000..48df509be --- /dev/null +++ b/workflow/plugins/go/dict/dict_merge/factory.go @@ -0,0 +1,7 @@ +// Package dict_merge provides factory for DictMerge plugin. +package dict_merge + +// Create returns a new DictMerge instance. +func Create() *DictMerge { + return NewDictMerge() +} diff --git a/workflow/plugins/go/dict/dict_merge/package.json b/workflow/plugins/go/dict/dict_merge/package.json index 97fe5a5a8..f2e0fa1e7 100644 --- a/workflow/plugins/go/dict/dict_merge/package.json +++ b/workflow/plugins/go/dict/dict_merge/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_merge.go", + "files": ["dict_merge.go", "factory.go"], "metadata": { "plugin_type": "dict.merge", - "category": "dict" + "category": "dict", + "struct": "DictMerge", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/dict/dict_set/dict_set.go b/workflow/plugins/go/dict/dict_set/dict_set.go index ae37aefb2..5dec25db8 100644 --- a/workflow/plugins/go/dict/dict_set/dict_set.go +++ b/workflow/plugins/go/dict/dict_set/dict_set.go @@ -1,13 +1,28 @@ -// Package dict_set provides the dictionary set plugin. +// Package dict_set provides a workflow plugin for setting dictionary values. package dict_set import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run sets a value in a dictionary by key. +// DictSet implements the NodeExecutor interface for setting dictionary values. +type DictSet struct { + NodeType string + Category string + Description string +} + +// NewDictSet creates a new DictSet instance. +func NewDictSet() *DictSet { + return &DictSet{ + NodeType: "dict.set", + Category: "dict", + Description: "Set a value in a dictionary by key", + } +} + +// Execute runs the plugin logic. +// Sets a value in a dictionary by key. // Supports dot notation for nested keys (e.g., "user.name"). // Creates intermediate objects as needed. // Inputs: @@ -17,7 +32,7 @@ import ( // // Returns: // - result: the modified dictionary -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *DictSet) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { dict, ok := inputs["dict"].(map[string]interface{}) if !ok { dict = make(map[string]interface{}) @@ -28,7 +43,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int key, ok := inputs["key"].(string) if !ok { - return map[string]interface{}{"result": dict}, nil + return map[string]interface{}{"result": dict} } value := inputs["value"] @@ -39,7 +54,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int if len(parts) == 1 { // Simple key dict[key] = value - return map[string]interface{}{"result": dict}, nil + return map[string]interface{}{"result": dict} } // Nested key - navigate/create intermediate objects @@ -69,7 +84,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int // Set the final value current[parts[len(parts)-1]] = value - return map[string]interface{}{"result": dict}, nil + return map[string]interface{}{"result": dict} } // copyDict creates a shallow copy of a dictionary. diff --git a/workflow/plugins/go/dict/dict_set/factory.go b/workflow/plugins/go/dict/dict_set/factory.go new file mode 100644 index 000000000..b36706f19 --- /dev/null +++ b/workflow/plugins/go/dict/dict_set/factory.go @@ -0,0 +1,7 @@ +// Package dict_set provides factory for DictSet plugin. +package dict_set + +// Create returns a new DictSet instance. +func Create() *DictSet { + return NewDictSet() +} diff --git a/workflow/plugins/go/dict/dict_set/package.json b/workflow/plugins/go/dict/dict_set/package.json index bbf558153..d008b7cca 100644 --- a/workflow/plugins/go/dict/dict_set/package.json +++ b/workflow/plugins/go/dict/dict_set/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_set.go", + "files": ["dict_set.go", "factory.go"], "metadata": { "plugin_type": "dict.set", - "category": "dict" + "category": "dict", + "struct": "DictSet", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/dict/dict_values/dict_values.go b/workflow/plugins/go/dict/dict_values/dict_values.go index 6353a1214..9439fb739 100644 --- a/workflow/plugins/go/dict/dict_values/dict_values.go +++ b/workflow/plugins/go/dict/dict_values/dict_values.go @@ -1,23 +1,38 @@ -// Package dict_values provides the dictionary values plugin. +// Package dict_values provides a workflow plugin for getting dictionary values. package dict_values import ( "sort" - - plugin "metabuilder/workflow/plugins/go" ) -// Run returns all values from a dictionary. +// DictValues implements the NodeExecutor interface for getting dictionary values. +type DictValues struct { + NodeType string + Category string + Description string +} + +// NewDictValues creates a new DictValues instance. +func NewDictValues() *DictValues { + return &DictValues{ + NodeType: "dict.values", + Category: "dict", + Description: "Get all values from a dictionary", + } +} + +// Execute runs the plugin logic. +// Returns all values from a dictionary. // Inputs: // - dict: the dictionary to get values from // - sorted_by_key: (optional) return values sorted by their keys (default: false) // // Returns: // - result: list of values -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *DictValues) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { dict, ok := inputs["dict"].(map[string]interface{}) if !ok { - return map[string]interface{}{"result": []interface{}{}}, nil + return map[string]interface{}{"result": []interface{}{}} } sortedByKey := false @@ -38,7 +53,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int for _, k := range keys { values = append(values, dict[k]) } - return map[string]interface{}{"result": values}, nil + return map[string]interface{}{"result": values} } // Return values in arbitrary order @@ -47,5 +62,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int values = append(values, v) } - return map[string]interface{}{"result": values}, nil + return map[string]interface{}{"result": values} } diff --git a/workflow/plugins/go/dict/dict_values/factory.go b/workflow/plugins/go/dict/dict_values/factory.go new file mode 100644 index 000000000..729df866d --- /dev/null +++ b/workflow/plugins/go/dict/dict_values/factory.go @@ -0,0 +1,7 @@ +// Package dict_values provides factory for DictValues plugin. +package dict_values + +// Create returns a new DictValues instance. +func Create() *DictValues { + return NewDictValues() +} diff --git a/workflow/plugins/go/dict/dict_values/package.json b/workflow/plugins/go/dict/dict_values/package.json index 39a59b4ce..dad788315 100644 --- a/workflow/plugins/go/dict/dict_values/package.json +++ b/workflow/plugins/go/dict/dict_values/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_values.go", + "files": ["dict_values.go", "factory.go"], "metadata": { "plugin_type": "dict.values", - "category": "dict" + "category": "dict", + "struct": "DictValues", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/list/list_concat/factory.go b/workflow/plugins/go/list/list_concat/factory.go new file mode 100644 index 000000000..512f24172 --- /dev/null +++ b/workflow/plugins/go/list/list_concat/factory.go @@ -0,0 +1,7 @@ +// Package list_concat provides factory for ListConcat plugin. +package list_concat + +// Create returns a new ListConcat instance. +func Create() *ListConcat { + return NewListConcat() +} diff --git a/workflow/plugins/go/list/list_concat/list_concat.go b/workflow/plugins/go/list/list_concat/list_concat.go index 0c2881325..4dc23771b 100644 --- a/workflow/plugins/go/list/list_concat/list_concat.go +++ b/workflow/plugins/go/list/list_concat/list_concat.go @@ -1,15 +1,27 @@ -// Package list_concat provides the concatenate lists plugin. +// Package list_concat provides a workflow plugin for concatenating lists. package list_concat -import ( - plugin "metabuilder/workflow/plugins/go" -) +// ListConcat implements the NodeExecutor interface for concatenating lists. +type ListConcat struct { + NodeType string + Category string + Description string +} -// Run concatenates multiple lists. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewListConcat creates a new ListConcat instance. +func NewListConcat() *ListConcat { + return &ListConcat{ + NodeType: "list.concat", + Category: "list", + Description: "Concatenate multiple lists", + } +} + +// Execute runs the plugin logic. +func (p *ListConcat) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { lists, ok := inputs["lists"].([]interface{}) if !ok { - return map[string]interface{}{"result": []interface{}{}}, nil + return map[string]interface{}{"result": []interface{}{}} } var result []interface{} @@ -19,5 +31,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/list/list_concat/package.json b/workflow/plugins/go/list/list_concat/package.json index 7a0ce61c8..1bd7663c0 100644 --- a/workflow/plugins/go/list/list_concat/package.json +++ b/workflow/plugins/go/list/list_concat/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_concat.go", + "files": ["list_concat.go", "factory.go"], "metadata": { "plugin_type": "list.concat", - "category": "list" + "category": "list", + "struct": "ListConcat", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/list/list_find/factory.go b/workflow/plugins/go/list/list_find/factory.go new file mode 100644 index 000000000..64aada2ed --- /dev/null +++ b/workflow/plugins/go/list/list_find/factory.go @@ -0,0 +1,7 @@ +// Package list_find provides factory for ListFind plugin. +package list_find + +// Create returns a new ListFind instance. +func Create() *ListFind { + return NewListFind() +} diff --git a/workflow/plugins/go/list/list_find/list_find.go b/workflow/plugins/go/list/list_find/list_find.go index 20133a861..c3cc6cfc2 100644 --- a/workflow/plugins/go/list/list_find/list_find.go +++ b/workflow/plugins/go/list/list_find/list_find.go @@ -1,11 +1,23 @@ -// Package list_find provides the list find plugin. +// Package list_find provides a workflow plugin for finding elements in lists. package list_find -import ( - plugin "metabuilder/workflow/plugins/go" -) +// ListFind implements the NodeExecutor interface for finding elements in lists. +type ListFind struct { + NodeType string + Category string + Description string +} -// Run returns the first element matching a condition or key/value pair. +// NewListFind creates a new ListFind instance. +func NewListFind() *ListFind { + return &ListFind{ + NodeType: "list.find", + Category: "list", + Description: "Find first element matching a condition", + } +} + +// Execute runs the plugin logic. // Inputs: // - list: the list to search // - key: (optional) the key to match in objects @@ -14,10 +26,10 @@ import ( // Returns: // - result: the first matching element or nil // - index: the index of the match or -1 -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *ListFind) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { list, ok := inputs["list"].([]interface{}) if !ok { - return map[string]interface{}{"result": nil, "index": -1}, nil + return map[string]interface{}{"result": nil, "index": -1} } value := inputs["value"] @@ -28,16 +40,16 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int // Search by key/value in objects if obj, ok := item.(map[string]interface{}); ok { if objVal, exists := obj[key]; exists && objVal == value { - return map[string]interface{}{"result": item, "index": i}, nil + return map[string]interface{}{"result": item, "index": i} } } } else { // Direct value comparison if item == value { - return map[string]interface{}{"result": item, "index": i}, nil + return map[string]interface{}{"result": item, "index": i} } } } - return map[string]interface{}{"result": nil, "index": -1}, nil + return map[string]interface{}{"result": nil, "index": -1} } diff --git a/workflow/plugins/go/list/list_find/package.json b/workflow/plugins/go/list/list_find/package.json index ae3c23cbe..1f7166332 100644 --- a/workflow/plugins/go/list/list_find/package.json +++ b/workflow/plugins/go/list/list_find/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_find.go", + "files": ["list_find.go", "factory.go"], "metadata": { "plugin_type": "list.find", - "category": "list" + "category": "list", + "struct": "ListFind", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/list/list_length/factory.go b/workflow/plugins/go/list/list_length/factory.go new file mode 100644 index 000000000..94f3204f8 --- /dev/null +++ b/workflow/plugins/go/list/list_length/factory.go @@ -0,0 +1,7 @@ +// Package list_length provides factory for ListLength plugin. +package list_length + +// Create returns a new ListLength instance. +func Create() *ListLength { + return NewListLength() +} diff --git a/workflow/plugins/go/list/list_length/list_length.go b/workflow/plugins/go/list/list_length/list_length.go index 2985bd332..843c8b071 100644 --- a/workflow/plugins/go/list/list_length/list_length.go +++ b/workflow/plugins/go/list/list_length/list_length.go @@ -1,16 +1,28 @@ -// Package list_length provides the list length plugin. +// Package list_length provides a workflow plugin for getting list length. package list_length -import ( - plugin "metabuilder/workflow/plugins/go" -) +// ListLength implements the NodeExecutor interface for getting list length. +type ListLength struct { + NodeType string + Category string + Description string +} -// Run returns the length of a list. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewListLength creates a new ListLength instance. +func NewListLength() *ListLength { + return &ListLength{ + NodeType: "list.length", + Category: "list", + Description: "Get the length of a list", + } +} + +// Execute runs the plugin logic. +func (p *ListLength) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { list, ok := inputs["list"].([]interface{}) if !ok { - return map[string]interface{}{"result": 0}, nil + return map[string]interface{}{"result": 0} } - return map[string]interface{}{"result": len(list)}, nil + return map[string]interface{}{"result": len(list)} } diff --git a/workflow/plugins/go/list/list_length/package.json b/workflow/plugins/go/list/list_length/package.json index 1108ad759..13abe31f3 100644 --- a/workflow/plugins/go/list/list_length/package.json +++ b/workflow/plugins/go/list/list_length/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_length.go", + "files": ["list_length.go", "factory.go"], "metadata": { "plugin_type": "list.length", - "category": "list" + "category": "list", + "struct": "ListLength", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/list/list_reverse/factory.go b/workflow/plugins/go/list/list_reverse/factory.go new file mode 100644 index 000000000..fe7c4b118 --- /dev/null +++ b/workflow/plugins/go/list/list_reverse/factory.go @@ -0,0 +1,7 @@ +// Package list_reverse provides factory for ListReverse plugin. +package list_reverse + +// Create returns a new ListReverse instance. +func Create() *ListReverse { + return NewListReverse() +} diff --git a/workflow/plugins/go/list/list_reverse/list_reverse.go b/workflow/plugins/go/list/list_reverse/list_reverse.go index 744399cf3..0748c6252 100644 --- a/workflow/plugins/go/list/list_reverse/list_reverse.go +++ b/workflow/plugins/go/list/list_reverse/list_reverse.go @@ -1,15 +1,27 @@ -// Package list_reverse provides the list reverse plugin. +// Package list_reverse provides a workflow plugin for reversing lists. package list_reverse -import ( - plugin "metabuilder/workflow/plugins/go" -) +// ListReverse implements the NodeExecutor interface for reversing lists. +type ListReverse struct { + NodeType string + Category string + Description string +} -// Run reverses a list. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewListReverse creates a new ListReverse instance. +func NewListReverse() *ListReverse { + return &ListReverse{ + NodeType: "list.reverse", + Category: "list", + Description: "Reverse a list", + } +} + +// Execute runs the plugin logic. +func (p *ListReverse) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { list, ok := inputs["list"].([]interface{}) if !ok { - return map[string]interface{}{"result": []interface{}{}}, nil + return map[string]interface{}{"result": []interface{}{}} } result := make([]interface{}, len(list)) @@ -17,5 +29,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int result[len(list)-1-i] = v } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/list/list_reverse/package.json b/workflow/plugins/go/list/list_reverse/package.json index 622ac1cf3..f7679345d 100644 --- a/workflow/plugins/go/list/list_reverse/package.json +++ b/workflow/plugins/go/list/list_reverse/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_reverse.go", + "files": ["list_reverse.go", "factory.go"], "metadata": { "plugin_type": "list.reverse", - "category": "list" + "category": "list", + "struct": "ListReverse", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/list/list_slice/factory.go b/workflow/plugins/go/list/list_slice/factory.go new file mode 100644 index 000000000..abb148e23 --- /dev/null +++ b/workflow/plugins/go/list/list_slice/factory.go @@ -0,0 +1,7 @@ +// Package list_slice provides factory for ListSlice plugin. +package list_slice + +// Create returns a new ListSlice instance. +func Create() *ListSlice { + return NewListSlice() +} diff --git a/workflow/plugins/go/list/list_slice/list_slice.go b/workflow/plugins/go/list/list_slice/list_slice.go index 721192a2b..183d26f5e 100644 --- a/workflow/plugins/go/list/list_slice/list_slice.go +++ b/workflow/plugins/go/list/list_slice/list_slice.go @@ -1,15 +1,27 @@ -// Package list_slice provides the list slice plugin. +// Package list_slice provides a workflow plugin for slicing lists. package list_slice -import ( - plugin "metabuilder/workflow/plugins/go" -) +// ListSlice implements the NodeExecutor interface for slicing lists. +type ListSlice struct { + NodeType string + Category string + Description string +} -// Run extracts a portion of a list. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewListSlice creates a new ListSlice instance. +func NewListSlice() *ListSlice { + return &ListSlice{ + NodeType: "list.slice", + Category: "list", + Description: "Extract a portion of a list", + } +} + +// Execute runs the plugin logic. +func (p *ListSlice) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { list, ok := inputs["list"].([]interface{}) if !ok { - return map[string]interface{}{"result": []interface{}{}}, nil + return map[string]interface{}{"result": []interface{}{}} } start := 0 @@ -41,5 +53,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int start = end } - return map[string]interface{}{"result": list[start:end]}, nil + return map[string]interface{}{"result": list[start:end]} } diff --git a/workflow/plugins/go/list/list_slice/package.json b/workflow/plugins/go/list/list_slice/package.json index 98729c4c6..e6e67a023 100644 --- a/workflow/plugins/go/list/list_slice/package.json +++ b/workflow/plugins/go/list/list_slice/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_slice.go", + "files": ["list_slice.go", "factory.go"], "metadata": { "plugin_type": "list.slice", - "category": "list" + "category": "list", + "struct": "ListSlice", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/list/list_sort/factory.go b/workflow/plugins/go/list/list_sort/factory.go new file mode 100644 index 000000000..4fc4007ed --- /dev/null +++ b/workflow/plugins/go/list/list_sort/factory.go @@ -0,0 +1,7 @@ +// Package list_sort provides factory for ListSort plugin. +package list_sort + +// Create returns a new ListSort instance. +func Create() *ListSort { + return NewListSort() +} diff --git a/workflow/plugins/go/list/list_sort/list_sort.go b/workflow/plugins/go/list/list_sort/list_sort.go index e5344c958..6411f6138 100644 --- a/workflow/plugins/go/list/list_sort/list_sort.go +++ b/workflow/plugins/go/list/list_sort/list_sort.go @@ -1,13 +1,27 @@ -// Package list_sort provides the list sort plugin. +// Package list_sort provides a workflow plugin for sorting lists. package list_sort import ( "sort" - - plugin "metabuilder/workflow/plugins/go" ) -// Run sorts a list of values. +// ListSort implements the NodeExecutor interface for sorting lists. +type ListSort struct { + NodeType string + Category string + Description string +} + +// NewListSort creates a new ListSort instance. +func NewListSort() *ListSort { + return &ListSort{ + NodeType: "list.sort", + Category: "list", + Description: "Sort a list of values", + } +} + +// Execute runs the plugin logic. // Inputs: // - list: the list to sort // - key: (optional) the key to sort by for objects @@ -15,10 +29,10 @@ import ( // // Returns: // - result: the sorted list -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *ListSort) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { list, ok := inputs["list"].([]interface{}) if !ok { - return map[string]interface{}{"result": []interface{}{}}, nil + return map[string]interface{}{"result": []interface{}{}} } // Make a copy to avoid mutating the original @@ -55,7 +69,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int return less }) - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } // compareLess compares two values and returns true if a < b. diff --git a/workflow/plugins/go/list/list_sort/package.json b/workflow/plugins/go/list/list_sort/package.json index 94a4651e2..464545d99 100644 --- a/workflow/plugins/go/list/list_sort/package.json +++ b/workflow/plugins/go/list/list_sort/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_sort.go", + "files": ["list_sort.go", "factory.go"], "metadata": { "plugin_type": "list.sort", - "category": "list" + "category": "list", + "struct": "ListSort", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/list/list_unique/factory.go b/workflow/plugins/go/list/list_unique/factory.go new file mode 100644 index 000000000..7e7e379cd --- /dev/null +++ b/workflow/plugins/go/list/list_unique/factory.go @@ -0,0 +1,7 @@ +// Package list_unique provides factory for ListUnique plugin. +package list_unique + +// Create returns a new ListUnique instance. +func Create() *ListUnique { + return NewListUnique() +} diff --git a/workflow/plugins/go/list/list_unique/list_unique.go b/workflow/plugins/go/list/list_unique/list_unique.go index 15b2e3b04..31346a92f 100644 --- a/workflow/plugins/go/list/list_unique/list_unique.go +++ b/workflow/plugins/go/list/list_unique/list_unique.go @@ -1,23 +1,37 @@ -// Package list_unique provides the list unique plugin. +// Package list_unique provides a workflow plugin for removing duplicates from lists. package list_unique import ( "encoding/json" - - plugin "metabuilder/workflow/plugins/go" ) -// Run removes duplicate elements from a list. +// ListUnique implements the NodeExecutor interface for removing duplicates from lists. +type ListUnique struct { + NodeType string + Category string + Description string +} + +// NewListUnique creates a new ListUnique instance. +func NewListUnique() *ListUnique { + return &ListUnique{ + NodeType: "list.unique", + Category: "list", + Description: "Remove duplicate elements from a list", + } +} + +// Execute runs the plugin logic. // Inputs: // - list: the list to deduplicate // - key: (optional) the key to use for uniqueness in objects // // Returns: // - result: the list with duplicates removed -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +func (p *ListUnique) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { list, ok := inputs["list"].([]interface{}) if !ok { - return map[string]interface{}{"result": []interface{}{}}, nil + return map[string]interface{}{"result": []interface{}{}} } key, hasKey := inputs["key"].(string) @@ -48,7 +62,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } // toHashKey converts a value to a string suitable for use as a map key. diff --git a/workflow/plugins/go/list/list_unique/package.json b/workflow/plugins/go/list/list_unique/package.json index a2077fed0..5bc53ec6b 100644 --- a/workflow/plugins/go/list/list_unique/package.json +++ b/workflow/plugins/go/list/list_unique/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_unique.go", + "files": ["list_unique.go", "factory.go"], "metadata": { "plugin_type": "list.unique", - "category": "list" + "category": "list", + "struct": "ListUnique", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/logic/logic_and/factory.go b/workflow/plugins/go/logic/logic_and/factory.go new file mode 100644 index 000000000..57b2673b5 --- /dev/null +++ b/workflow/plugins/go/logic/logic_and/factory.go @@ -0,0 +1,7 @@ +// Package logic_and provides factory for LogicAnd plugin. +package logic_and + +// Create returns a new LogicAnd instance. +func Create() *LogicAnd { + return NewLogicAnd() +} diff --git a/workflow/plugins/go/logic/logic_and/logic_and.go b/workflow/plugins/go/logic/logic_and/logic_and.go index 3fd488440..28b30c382 100644 --- a/workflow/plugins/go/logic/logic_and/logic_and.go +++ b/workflow/plugins/go/logic/logic_and/logic_and.go @@ -1,24 +1,36 @@ -// Package logic_and provides the logical AND plugin. +// Package logic_and provides a workflow plugin for logical AND operations. package logic_and -import ( - plugin "metabuilder/workflow/plugins/go" -) +// LogicAnd implements the NodeExecutor interface for logical AND operations. +type LogicAnd struct { + NodeType string + Category string + Description string +} -// Run performs logical AND on boolean values. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewLogicAnd creates a new LogicAnd instance. +func NewLogicAnd() *LogicAnd { + return &LogicAnd{ + NodeType: "logic.and", + Category: "logic", + Description: "Perform logical AND on boolean values", + } +} + +// Execute runs the plugin logic. +func (p *LogicAnd) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { values, ok := inputs["values"].([]interface{}) if !ok || len(values) == 0 { - return map[string]interface{}{"result": false}, nil + return map[string]interface{}{"result": false} } for _, v := range values { if !toBool(v) { - return map[string]interface{}{"result": false}, nil + return map[string]interface{}{"result": false} } } - return map[string]interface{}{"result": true}, nil + return map[string]interface{}{"result": true} } func toBool(v interface{}) bool { diff --git a/workflow/plugins/go/logic/logic_and/package.json b/workflow/plugins/go/logic/logic_and/package.json index 9620443a0..435ca320c 100644 --- a/workflow/plugins/go/logic/logic_and/package.json +++ b/workflow/plugins/go/logic/logic_and/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_and.go", + "files": ["logic_and.go", "factory.go"], "metadata": { "plugin_type": "logic.and", - "category": "logic" + "category": "logic", + "struct": "LogicAnd", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/logic/logic_equals/factory.go b/workflow/plugins/go/logic/logic_equals/factory.go new file mode 100644 index 000000000..5b6585cd1 --- /dev/null +++ b/workflow/plugins/go/logic/logic_equals/factory.go @@ -0,0 +1,7 @@ +// Package logic_equals provides factory for LogicEquals plugin. +package logic_equals + +// Create returns a new LogicEquals instance. +func Create() *LogicEquals { + return NewLogicEquals() +} diff --git a/workflow/plugins/go/logic/logic_equals/logic_equals.go b/workflow/plugins/go/logic/logic_equals/logic_equals.go index 63783ecf4..71c54987e 100644 --- a/workflow/plugins/go/logic/logic_equals/logic_equals.go +++ b/workflow/plugins/go/logic/logic_equals/logic_equals.go @@ -1,17 +1,31 @@ -// Package logic_equals provides the equality check plugin. +// Package logic_equals provides a workflow plugin for equality checks. package logic_equals import ( "reflect" - - plugin "metabuilder/workflow/plugins/go" ) -// Run checks if two values are equal. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// LogicEquals implements the NodeExecutor interface for equality checks. +type LogicEquals struct { + NodeType string + Category string + Description string +} + +// NewLogicEquals creates a new LogicEquals instance. +func NewLogicEquals() *LogicEquals { + return &LogicEquals{ + NodeType: "logic.equals", + Category: "logic", + Description: "Check if two values are equal", + } +} + +// Execute runs the plugin logic. +func (p *LogicEquals) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { a := inputs["a"] b := inputs["b"] result := reflect.DeepEqual(a, b) - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/logic/logic_equals/package.json b/workflow/plugins/go/logic/logic_equals/package.json index e716af215..433a16e9c 100644 --- a/workflow/plugins/go/logic/logic_equals/package.json +++ b/workflow/plugins/go/logic/logic_equals/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_equals.go", + "files": ["logic_equals.go", "factory.go"], "metadata": { "plugin_type": "logic.equals", - "category": "logic" + "category": "logic", + "struct": "LogicEquals", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/logic/logic_gt/factory.go b/workflow/plugins/go/logic/logic_gt/factory.go new file mode 100644 index 000000000..f6cc5ea6b --- /dev/null +++ b/workflow/plugins/go/logic/logic_gt/factory.go @@ -0,0 +1,7 @@ +// Package logic_gt provides factory for LogicGt plugin. +package logic_gt + +// Create returns a new LogicGt instance. +func Create() *LogicGt { + return NewLogicGt() +} diff --git a/workflow/plugins/go/logic/logic_gt/logic_gt.go b/workflow/plugins/go/logic/logic_gt/logic_gt.go index 58699654b..dffb6f7de 100644 --- a/workflow/plugins/go/logic/logic_gt/logic_gt.go +++ b/workflow/plugins/go/logic/logic_gt/logic_gt.go @@ -1,16 +1,28 @@ -// Package logic_gt provides the greater than comparison plugin. +// Package logic_gt provides a workflow plugin for greater than comparisons. package logic_gt -import ( - plugin "metabuilder/workflow/plugins/go" -) +// LogicGt implements the NodeExecutor interface for greater than comparisons. +type LogicGt struct { + NodeType string + Category string + Description string +} -// Run checks if a > b. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewLogicGt creates a new LogicGt instance. +func NewLogicGt() *LogicGt { + return &LogicGt{ + NodeType: "logic.gt", + Category: "logic", + Description: "Check if a is greater than b", + } +} + +// Execute runs the plugin logic. +func (p *LogicGt) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { a := toFloat(inputs["a"]) b := toFloat(inputs["b"]) - return map[string]interface{}{"result": a > b}, nil + return map[string]interface{}{"result": a > b} } func toFloat(v interface{}) float64 { diff --git a/workflow/plugins/go/logic/logic_gt/package.json b/workflow/plugins/go/logic/logic_gt/package.json index f535930ca..f1c90351a 100644 --- a/workflow/plugins/go/logic/logic_gt/package.json +++ b/workflow/plugins/go/logic/logic_gt/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_gt.go", + "files": ["logic_gt.go", "factory.go"], "metadata": { "plugin_type": "logic.gt", - "category": "logic" + "category": "logic", + "struct": "LogicGt", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/logic/logic_lt/factory.go b/workflow/plugins/go/logic/logic_lt/factory.go new file mode 100644 index 000000000..514fa7088 --- /dev/null +++ b/workflow/plugins/go/logic/logic_lt/factory.go @@ -0,0 +1,7 @@ +// Package logic_lt provides factory for LogicLt plugin. +package logic_lt + +// Create returns a new LogicLt instance. +func Create() *LogicLt { + return NewLogicLt() +} diff --git a/workflow/plugins/go/logic/logic_lt/logic_lt.go b/workflow/plugins/go/logic/logic_lt/logic_lt.go index f5d0d73d0..602313f02 100644 --- a/workflow/plugins/go/logic/logic_lt/logic_lt.go +++ b/workflow/plugins/go/logic/logic_lt/logic_lt.go @@ -1,16 +1,28 @@ -// Package logic_lt provides the less than comparison plugin. +// Package logic_lt provides a workflow plugin for less than comparisons. package logic_lt -import ( - plugin "metabuilder/workflow/plugins/go" -) +// LogicLt implements the NodeExecutor interface for less than comparisons. +type LogicLt struct { + NodeType string + Category string + Description string +} -// Run checks if a < b. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewLogicLt creates a new LogicLt instance. +func NewLogicLt() *LogicLt { + return &LogicLt{ + NodeType: "logic.lt", + Category: "logic", + Description: "Check if a is less than b", + } +} + +// Execute runs the plugin logic. +func (p *LogicLt) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { a := toFloat(inputs["a"]) b := toFloat(inputs["b"]) - return map[string]interface{}{"result": a < b}, nil + return map[string]interface{}{"result": a < b} } func toFloat(v interface{}) float64 { diff --git a/workflow/plugins/go/logic/logic_lt/package.json b/workflow/plugins/go/logic/logic_lt/package.json index 82667f2f5..ccbe948e5 100644 --- a/workflow/plugins/go/logic/logic_lt/package.json +++ b/workflow/plugins/go/logic/logic_lt/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_lt.go", + "files": ["logic_lt.go", "factory.go"], "metadata": { "plugin_type": "logic.lt", - "category": "logic" + "category": "logic", + "struct": "LogicLt", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/logic/logic_not/factory.go b/workflow/plugins/go/logic/logic_not/factory.go new file mode 100644 index 000000000..8d1c52933 --- /dev/null +++ b/workflow/plugins/go/logic/logic_not/factory.go @@ -0,0 +1,7 @@ +// Package logic_not provides factory for LogicNot plugin. +package logic_not + +// Create returns a new LogicNot instance. +func Create() *LogicNot { + return NewLogicNot() +} diff --git a/workflow/plugins/go/logic/logic_not/logic_not.go b/workflow/plugins/go/logic/logic_not/logic_not.go index 2a6c9591d..0ece53ed0 100644 --- a/workflow/plugins/go/logic/logic_not/logic_not.go +++ b/workflow/plugins/go/logic/logic_not/logic_not.go @@ -1,14 +1,26 @@ -// Package logic_not provides the logical NOT plugin. +// Package logic_not provides a workflow plugin for logical NOT operations. package logic_not -import ( - plugin "metabuilder/workflow/plugins/go" -) +// LogicNot implements the NodeExecutor interface for logical NOT operations. +type LogicNot struct { + NodeType string + Category string + Description string +} -// Run performs logical NOT on a boolean value. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewLogicNot creates a new LogicNot instance. +func NewLogicNot() *LogicNot { + return &LogicNot{ + NodeType: "logic.not", + Category: "logic", + Description: "Perform logical NOT on a boolean value", + } +} + +// Execute runs the plugin logic. +func (p *LogicNot) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { value := inputs["value"] - return map[string]interface{}{"result": !toBool(value)}, nil + return map[string]interface{}{"result": !toBool(value)} } func toBool(v interface{}) bool { diff --git a/workflow/plugins/go/logic/logic_not/package.json b/workflow/plugins/go/logic/logic_not/package.json index ad540d566..a3a5180b4 100644 --- a/workflow/plugins/go/logic/logic_not/package.json +++ b/workflow/plugins/go/logic/logic_not/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_not.go", + "files": ["logic_not.go", "factory.go"], "metadata": { "plugin_type": "logic.not", - "category": "logic" + "category": "logic", + "struct": "LogicNot", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/logic/logic_or/factory.go b/workflow/plugins/go/logic/logic_or/factory.go new file mode 100644 index 000000000..7aefc4f19 --- /dev/null +++ b/workflow/plugins/go/logic/logic_or/factory.go @@ -0,0 +1,7 @@ +// Package logic_or provides factory for LogicOr plugin. +package logic_or + +// Create returns a new LogicOr instance. +func Create() *LogicOr { + return NewLogicOr() +} diff --git a/workflow/plugins/go/logic/logic_or/logic_or.go b/workflow/plugins/go/logic/logic_or/logic_or.go index 07b006a77..6a7ed4f3a 100644 --- a/workflow/plugins/go/logic/logic_or/logic_or.go +++ b/workflow/plugins/go/logic/logic_or/logic_or.go @@ -1,24 +1,36 @@ -// Package logic_or provides the logical OR plugin. +// Package logic_or provides a workflow plugin for logical OR operations. package logic_or -import ( - plugin "metabuilder/workflow/plugins/go" -) +// LogicOr implements the NodeExecutor interface for logical OR operations. +type LogicOr struct { + NodeType string + Category string + Description string +} -// Run performs logical OR on boolean values. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewLogicOr creates a new LogicOr instance. +func NewLogicOr() *LogicOr { + return &LogicOr{ + NodeType: "logic.or", + Category: "logic", + Description: "Perform logical OR on boolean values", + } +} + +// Execute runs the plugin logic. +func (p *LogicOr) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { values, ok := inputs["values"].([]interface{}) if !ok || len(values) == 0 { - return map[string]interface{}{"result": false}, nil + return map[string]interface{}{"result": false} } for _, v := range values { if toBool(v) { - return map[string]interface{}{"result": true}, nil + return map[string]interface{}{"result": true} } } - return map[string]interface{}{"result": false}, nil + return map[string]interface{}{"result": false} } func toBool(v interface{}) bool { diff --git a/workflow/plugins/go/logic/logic_or/package.json b/workflow/plugins/go/logic/logic_or/package.json index 03b577216..d34510859 100644 --- a/workflow/plugins/go/logic/logic_or/package.json +++ b/workflow/plugins/go/logic/logic_or/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_or.go", + "files": ["logic_or.go", "factory.go"], "metadata": { "plugin_type": "logic.or", - "category": "logic" + "category": "logic", + "struct": "LogicOr", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/math/math_add/factory.go b/workflow/plugins/go/math/math_add/factory.go new file mode 100644 index 000000000..e0ed6c238 --- /dev/null +++ b/workflow/plugins/go/math/math_add/factory.go @@ -0,0 +1,7 @@ +// Package math_add provides factory for MathAdd plugin. +package math_add + +// Create returns a new MathAdd instance. +func Create() *MathAdd { + return NewMathAdd() +} diff --git a/workflow/plugins/go/math/math_add/math_add.go b/workflow/plugins/go/math/math_add/math_add.go index cfdad2a7e..c6005939b 100644 --- a/workflow/plugins/go/math/math_add/math_add.go +++ b/workflow/plugins/go/math/math_add/math_add.go @@ -1,15 +1,27 @@ -// Package math_add provides the add numbers plugin. +// Package math_add provides a workflow plugin for adding numbers. package math_add -import ( - plugin "metabuilder/workflow/plugins/go" -) +// MathAdd implements the NodeExecutor interface for adding numbers. +type MathAdd struct { + NodeType string + Category string + Description string +} -// Run adds two or more numbers. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewMathAdd creates a new MathAdd instance. +func NewMathAdd() *MathAdd { + return &MathAdd{ + NodeType: "math.add", + Category: "math", + Description: "Add two or more numbers", + } +} + +// Execute runs the plugin logic. +func (p *MathAdd) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { numbers, ok := inputs["numbers"].([]interface{}) if !ok { - return map[string]interface{}{"result": 0, "error": "numbers must be an array"}, nil + return map[string]interface{}{"result": 0, "error": "numbers must be an array"} } var sum float64 @@ -24,5 +36,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } } - return map[string]interface{}{"result": sum}, nil + return map[string]interface{}{"result": sum} } diff --git a/workflow/plugins/go/math/math_add/package.json b/workflow/plugins/go/math/math_add/package.json index dd311f655..926a80737 100644 --- a/workflow/plugins/go/math/math_add/package.json +++ b/workflow/plugins/go/math/math_add/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_add.go", + "files": ["math_add.go", "factory.go"], "metadata": { "plugin_type": "math.add", - "category": "math" + "category": "math", + "struct": "MathAdd", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/math/math_divide/factory.go b/workflow/plugins/go/math/math_divide/factory.go new file mode 100644 index 000000000..7802f0292 --- /dev/null +++ b/workflow/plugins/go/math/math_divide/factory.go @@ -0,0 +1,7 @@ +// Package math_divide provides factory for MathDivide plugin. +package math_divide + +// Create returns a new MathDivide instance. +func Create() *MathDivide { + return NewMathDivide() +} diff --git a/workflow/plugins/go/math/math_divide/math_divide.go b/workflow/plugins/go/math/math_divide/math_divide.go index 03b3d7028..6063ca131 100644 --- a/workflow/plugins/go/math/math_divide/math_divide.go +++ b/workflow/plugins/go/math/math_divide/math_divide.go @@ -1,29 +1,43 @@ -// Package math_divide provides the divide numbers plugin. +// Package math_divide provides a workflow plugin for dividing numbers. package math_divide import ( "errors" - - plugin "metabuilder/workflow/plugins/go" ) -// Run divides the first number by subsequent numbers. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// MathDivide implements the NodeExecutor interface for dividing numbers. +type MathDivide struct { + NodeType string + Category string + Description string +} + +// NewMathDivide creates a new MathDivide instance. +func NewMathDivide() *MathDivide { + return &MathDivide{ + NodeType: "math.divide", + Category: "math", + Description: "Divide the first number by subsequent numbers", + } +} + +// Execute runs the plugin logic. +func (p *MathDivide) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { numbers, ok := inputs["numbers"].([]interface{}) if !ok || len(numbers) < 2 { - return map[string]interface{}{"result": 0, "error": "numbers must have at least 2 elements"}, nil + return map[string]interface{}{"result": 0, "error": "numbers must have at least 2 elements"} } result := toFloat64(numbers[0]) for i := 1; i < len(numbers); i++ { divisor := toFloat64(numbers[i]) if divisor == 0 { - return nil, errors.New("division by zero") + return map[string]interface{}{"result": 0, "error": errors.New("division by zero").Error()} } result /= divisor } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } func toFloat64(v interface{}) float64 { diff --git a/workflow/plugins/go/math/math_divide/package.json b/workflow/plugins/go/math/math_divide/package.json index c4b5a4545..ca144672e 100644 --- a/workflow/plugins/go/math/math_divide/package.json +++ b/workflow/plugins/go/math/math_divide/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_divide.go", + "files": ["math_divide.go", "factory.go"], "metadata": { "plugin_type": "math.divide", - "category": "math" + "category": "math", + "struct": "MathDivide", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/math/math_multiply/factory.go b/workflow/plugins/go/math/math_multiply/factory.go new file mode 100644 index 000000000..696348802 --- /dev/null +++ b/workflow/plugins/go/math/math_multiply/factory.go @@ -0,0 +1,7 @@ +// Package math_multiply provides factory for MathMultiply plugin. +package math_multiply + +// Create returns a new MathMultiply instance. +func Create() *MathMultiply { + return NewMathMultiply() +} diff --git a/workflow/plugins/go/math/math_multiply/math_multiply.go b/workflow/plugins/go/math/math_multiply/math_multiply.go index 935512f2b..dfebc7e45 100644 --- a/workflow/plugins/go/math/math_multiply/math_multiply.go +++ b/workflow/plugins/go/math/math_multiply/math_multiply.go @@ -1,15 +1,27 @@ -// Package math_multiply provides the multiply numbers plugin. +// Package math_multiply provides a workflow plugin for multiplying numbers. package math_multiply -import ( - plugin "metabuilder/workflow/plugins/go" -) +// MathMultiply implements the NodeExecutor interface for multiplying numbers. +type MathMultiply struct { + NodeType string + Category string + Description string +} -// Run multiplies two or more numbers. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewMathMultiply creates a new MathMultiply instance. +func NewMathMultiply() *MathMultiply { + return &MathMultiply{ + NodeType: "math.multiply", + Category: "math", + Description: "Multiply two or more numbers", + } +} + +// Execute runs the plugin logic. +func (p *MathMultiply) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { numbers, ok := inputs["numbers"].([]interface{}) if !ok || len(numbers) == 0 { - return map[string]interface{}{"result": 0, "error": "numbers must be a non-empty array"}, nil + return map[string]interface{}{"result": 0, "error": "numbers must be a non-empty array"} } result := 1.0 @@ -17,7 +29,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int result *= toFloat64(n) } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } func toFloat64(v interface{}) float64 { diff --git a/workflow/plugins/go/math/math_multiply/package.json b/workflow/plugins/go/math/math_multiply/package.json index bcb47dfed..e99fb5012 100644 --- a/workflow/plugins/go/math/math_multiply/package.json +++ b/workflow/plugins/go/math/math_multiply/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_multiply.go", + "files": ["math_multiply.go", "factory.go"], "metadata": { "plugin_type": "math.multiply", - "category": "math" + "category": "math", + "struct": "MathMultiply", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/math/math_subtract/factory.go b/workflow/plugins/go/math/math_subtract/factory.go new file mode 100644 index 000000000..a3606fe8d --- /dev/null +++ b/workflow/plugins/go/math/math_subtract/factory.go @@ -0,0 +1,7 @@ +// Package math_subtract provides factory for MathSubtract plugin. +package math_subtract + +// Create returns a new MathSubtract instance. +func Create() *MathSubtract { + return NewMathSubtract() +} diff --git a/workflow/plugins/go/math/math_subtract/math_subtract.go b/workflow/plugins/go/math/math_subtract/math_subtract.go index 9fb0f58d0..f82910805 100644 --- a/workflow/plugins/go/math/math_subtract/math_subtract.go +++ b/workflow/plugins/go/math/math_subtract/math_subtract.go @@ -1,15 +1,27 @@ -// Package math_subtract provides the subtract numbers plugin. +// Package math_subtract provides a workflow plugin for subtracting numbers. package math_subtract -import ( - plugin "metabuilder/workflow/plugins/go" -) +// MathSubtract implements the NodeExecutor interface for subtracting numbers. +type MathSubtract struct { + NodeType string + Category string + Description string +} -// Run subtracts numbers from the first number. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewMathSubtract creates a new MathSubtract instance. +func NewMathSubtract() *MathSubtract { + return &MathSubtract{ + NodeType: "math.subtract", + Category: "math", + Description: "Subtract numbers from the first number", + } +} + +// Execute runs the plugin logic. +func (p *MathSubtract) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { numbers, ok := inputs["numbers"].([]interface{}) if !ok || len(numbers) == 0 { - return map[string]interface{}{"result": 0, "error": "numbers must be a non-empty array"}, nil + return map[string]interface{}{"result": 0, "error": "numbers must be a non-empty array"} } result := toFloat64(numbers[0]) @@ -17,7 +29,7 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int result -= toFloat64(numbers[i]) } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } func toFloat64(v interface{}) float64 { diff --git a/workflow/plugins/go/math/math_subtract/package.json b/workflow/plugins/go/math/math_subtract/package.json index 37f561e3f..220a5fbe3 100644 --- a/workflow/plugins/go/math/math_subtract/package.json +++ b/workflow/plugins/go/math/math_subtract/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_subtract.go", + "files": ["math_subtract.go", "factory.go"], "metadata": { "plugin_type": "math.subtract", - "category": "math" + "category": "math", + "struct": "MathSubtract", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/string/string_concat/factory.go b/workflow/plugins/go/string/string_concat/factory.go new file mode 100644 index 000000000..2a08ab44c --- /dev/null +++ b/workflow/plugins/go/string/string_concat/factory.go @@ -0,0 +1,7 @@ +// Package string_concat provides factory for StringConcat plugin. +package string_concat + +// Create returns a new StringConcat instance. +func Create() *StringConcat { + return NewStringConcat() +} diff --git a/workflow/plugins/go/string/string_concat/package.json b/workflow/plugins/go/string/string_concat/package.json index e9d1afb0e..581d540bd 100644 --- a/workflow/plugins/go/string/string_concat/package.json +++ b/workflow/plugins/go/string/string_concat/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_concat.go", + "files": ["string_concat.go", "factory.go"], "metadata": { "plugin_type": "string.concat", - "category": "string" + "category": "string", + "struct": "StringConcat", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/string/string_concat/string_concat.go b/workflow/plugins/go/string/string_concat/string_concat.go index 1f95b5436..34462608d 100644 --- a/workflow/plugins/go/string/string_concat/string_concat.go +++ b/workflow/plugins/go/string/string_concat/string_concat.go @@ -1,18 +1,32 @@ -// Package string_concat provides the concatenate strings plugin. +// Package string_concat provides a workflow plugin for concatenating strings. package string_concat import ( "fmt" "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run concatenates multiple strings with optional separator. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// StringConcat implements the NodeExecutor interface for concatenating strings. +type StringConcat struct { + NodeType string + Category string + Description string +} + +// NewStringConcat creates a new StringConcat instance. +func NewStringConcat() *StringConcat { + return &StringConcat{ + NodeType: "string.concat", + Category: "string", + Description: "Concatenate multiple strings with optional separator", + } +} + +// Execute runs the plugin logic. +func (p *StringConcat) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { strs, ok := inputs["strings"].([]interface{}) if !ok { - return map[string]interface{}{"result": "", "error": "strings must be an array"}, nil + return map[string]interface{}{"result": "", "error": "strings must be an array"} } separator := "" @@ -25,5 +39,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int strList[i] = fmt.Sprintf("%v", s) } - return map[string]interface{}{"result": strings.Join(strList, separator)}, nil + return map[string]interface{}{"result": strings.Join(strList, separator)} } diff --git a/workflow/plugins/go/string/string_lower/factory.go b/workflow/plugins/go/string/string_lower/factory.go new file mode 100644 index 000000000..05f2c5537 --- /dev/null +++ b/workflow/plugins/go/string/string_lower/factory.go @@ -0,0 +1,7 @@ +// Package string_lower provides factory for StringLower plugin. +package string_lower + +// Create returns a new StringLower instance. +func Create() *StringLower { + return NewStringLower() +} diff --git a/workflow/plugins/go/string/string_lower/package.json b/workflow/plugins/go/string/string_lower/package.json index 9328f82c5..2bbc86b96 100644 --- a/workflow/plugins/go/string/string_lower/package.json +++ b/workflow/plugins/go/string/string_lower/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_lower.go", + "files": ["string_lower.go", "factory.go"], "metadata": { "plugin_type": "string.lower", - "category": "string" + "category": "string", + "struct": "StringLower", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/string/string_lower/string_lower.go b/workflow/plugins/go/string/string_lower/string_lower.go index edddad22c..d4e0b1eef 100644 --- a/workflow/plugins/go/string/string_lower/string_lower.go +++ b/workflow/plugins/go/string/string_lower/string_lower.go @@ -1,18 +1,32 @@ -// Package string_lower provides the lowercase string plugin. +// Package string_lower provides a workflow plugin for lowercasing strings. package string_lower import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run converts string to lowercase. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// StringLower implements the NodeExecutor interface for lowercasing strings. +type StringLower struct { + NodeType string + Category string + Description string +} + +// NewStringLower creates a new StringLower instance. +func NewStringLower() *StringLower { + return &StringLower{ + NodeType: "string.lower", + Category: "string", + Description: "Convert string to lowercase", + } +} + +// Execute runs the plugin logic. +func (p *StringLower) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { str, ok := inputs["string"].(string) if !ok { - return map[string]interface{}{"result": "", "error": "string is required"}, nil + return map[string]interface{}{"result": "", "error": "string is required"} } - return map[string]interface{}{"result": strings.ToLower(str)}, nil + return map[string]interface{}{"result": strings.ToLower(str)} } diff --git a/workflow/plugins/go/string/string_replace/factory.go b/workflow/plugins/go/string/string_replace/factory.go new file mode 100644 index 000000000..05f4f4e3b --- /dev/null +++ b/workflow/plugins/go/string/string_replace/factory.go @@ -0,0 +1,7 @@ +// Package string_replace provides factory for StringReplace plugin. +package string_replace + +// Create returns a new StringReplace instance. +func Create() *StringReplace { + return NewStringReplace() +} diff --git a/workflow/plugins/go/string/string_replace/package.json b/workflow/plugins/go/string/string_replace/package.json index 6e0b296ea..0aa132645 100644 --- a/workflow/plugins/go/string/string_replace/package.json +++ b/workflow/plugins/go/string/string_replace/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_replace.go", + "files": ["string_replace.go", "factory.go"], "metadata": { "plugin_type": "string.replace", - "category": "string" + "category": "string", + "struct": "StringReplace", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/string/string_replace/string_replace.go b/workflow/plugins/go/string/string_replace/string_replace.go index 143e9200f..49f1e8254 100644 --- a/workflow/plugins/go/string/string_replace/string_replace.go +++ b/workflow/plugins/go/string/string_replace/string_replace.go @@ -1,17 +1,31 @@ -// Package string_replace provides the replace string plugin. +// Package string_replace provides a workflow plugin for replacing strings. package string_replace import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run replaces occurrences in a string. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// StringReplace implements the NodeExecutor interface for replacing strings. +type StringReplace struct { + NodeType string + Category string + Description string +} + +// NewStringReplace creates a new StringReplace instance. +func NewStringReplace() *StringReplace { + return &StringReplace{ + NodeType: "string.replace", + Category: "string", + Description: "Replace occurrences in a string", + } +} + +// Execute runs the plugin logic. +func (p *StringReplace) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { str, ok := inputs["string"].(string) if !ok { - return map[string]interface{}{"result": "", "error": "string is required"}, nil + return map[string]interface{}{"result": "", "error": "string is required"} } old, _ := inputs["old"].(string) @@ -24,5 +38,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int } result := strings.Replace(str, old, new, count) - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/string/string_split/factory.go b/workflow/plugins/go/string/string_split/factory.go new file mode 100644 index 000000000..4353d5dc5 --- /dev/null +++ b/workflow/plugins/go/string/string_split/factory.go @@ -0,0 +1,7 @@ +// Package string_split provides factory for StringSplit plugin. +package string_split + +// Create returns a new StringSplit instance. +func Create() *StringSplit { + return NewStringSplit() +} diff --git a/workflow/plugins/go/string/string_split/package.json b/workflow/plugins/go/string/string_split/package.json index dfe34cff5..342751b54 100644 --- a/workflow/plugins/go/string/string_split/package.json +++ b/workflow/plugins/go/string/string_split/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_split.go", + "files": ["string_split.go", "factory.go"], "metadata": { "plugin_type": "string.split", - "category": "string" + "category": "string", + "struct": "StringSplit", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/string/string_split/string_split.go b/workflow/plugins/go/string/string_split/string_split.go index b4f56e11c..f387bbcba 100644 --- a/workflow/plugins/go/string/string_split/string_split.go +++ b/workflow/plugins/go/string/string_split/string_split.go @@ -1,17 +1,31 @@ -// Package string_split provides the split string plugin. +// Package string_split provides a workflow plugin for splitting strings. package string_split import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run splits a string by separator. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// StringSplit implements the NodeExecutor interface for splitting strings. +type StringSplit struct { + NodeType string + Category string + Description string +} + +// NewStringSplit creates a new StringSplit instance. +func NewStringSplit() *StringSplit { + return &StringSplit{ + NodeType: "string.split", + Category: "string", + Description: "Split a string by separator", + } +} + +// Execute runs the plugin logic. +func (p *StringSplit) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { str, ok := inputs["string"].(string) if !ok { - return map[string]interface{}{"result": []string{}, "error": "string is required"}, nil + return map[string]interface{}{"result": []string{}, "error": "string is required"} } separator := "" @@ -29,5 +43,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int result = strings.Split(str, separator) } - return map[string]interface{}{"result": result}, nil + return map[string]interface{}{"result": result} } diff --git a/workflow/plugins/go/string/string_upper/factory.go b/workflow/plugins/go/string/string_upper/factory.go new file mode 100644 index 000000000..171ffe3b5 --- /dev/null +++ b/workflow/plugins/go/string/string_upper/factory.go @@ -0,0 +1,7 @@ +// Package string_upper provides factory for StringUpper plugin. +package string_upper + +// Create returns a new StringUpper instance. +func Create() *StringUpper { + return NewStringUpper() +} diff --git a/workflow/plugins/go/string/string_upper/package.json b/workflow/plugins/go/string/string_upper/package.json index 3eb959e26..f2491e3e6 100644 --- a/workflow/plugins/go/string/string_upper/package.json +++ b/workflow/plugins/go/string/string_upper/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_upper.go", + "files": ["string_upper.go", "factory.go"], "metadata": { "plugin_type": "string.upper", - "category": "string" + "category": "string", + "struct": "StringUpper", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/string/string_upper/string_upper.go b/workflow/plugins/go/string/string_upper/string_upper.go index 8193021a2..f2e795302 100644 --- a/workflow/plugins/go/string/string_upper/string_upper.go +++ b/workflow/plugins/go/string/string_upper/string_upper.go @@ -1,18 +1,32 @@ -// Package string_upper provides the uppercase string plugin. +// Package string_upper provides a workflow plugin for uppercasing strings. package string_upper import ( "strings" - - plugin "metabuilder/workflow/plugins/go" ) -// Run converts string to uppercase. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// StringUpper implements the NodeExecutor interface for uppercasing strings. +type StringUpper struct { + NodeType string + Category string + Description string +} + +// NewStringUpper creates a new StringUpper instance. +func NewStringUpper() *StringUpper { + return &StringUpper{ + NodeType: "string.upper", + Category: "string", + Description: "Convert string to uppercase", + } +} + +// Execute runs the plugin logic. +func (p *StringUpper) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { str, ok := inputs["string"].(string) if !ok { - return map[string]interface{}{"result": "", "error": "string is required"}, nil + return map[string]interface{}{"result": "", "error": "string is required"} } - return map[string]interface{}{"result": strings.ToUpper(str)}, nil + return map[string]interface{}{"result": strings.ToUpper(str)} } diff --git a/workflow/plugins/go/var/var_delete/factory.go b/workflow/plugins/go/var/var_delete/factory.go new file mode 100644 index 000000000..560ef2765 --- /dev/null +++ b/workflow/plugins/go/var/var_delete/factory.go @@ -0,0 +1,7 @@ +// Package var_delete provides factory for VarDelete plugin. +package var_delete + +// Create returns a new VarDelete instance. +func Create() *VarDelete { + return NewVarDelete() +} diff --git a/workflow/plugins/go/var/var_delete/package.json b/workflow/plugins/go/var/var_delete/package.json index 81f963798..e84fe3344 100644 --- a/workflow/plugins/go/var/var_delete/package.json +++ b/workflow/plugins/go/var/var_delete/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["var", "workflow", "plugin"], "main": "var_delete.go", + "files": ["var_delete.go", "factory.go"], "metadata": { "plugin_type": "var.delete", - "category": "var" + "category": "var", + "struct": "VarDelete", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/var/var_delete/var_delete.go b/workflow/plugins/go/var/var_delete/var_delete.go index ea4f5b029..a53103095 100644 --- a/workflow/plugins/go/var/var_delete/var_delete.go +++ b/workflow/plugins/go/var/var_delete/var_delete.go @@ -1,19 +1,51 @@ -// Package var_delete provides the variable delete plugin. +// Package var_delete provides a workflow plugin for deleting workflow variables. package var_delete -import ( - plugin "metabuilder/workflow/plugins/go" -) +// VarDelete implements the NodeExecutor interface for deleting workflow variables. +type VarDelete struct { + NodeType string + Category string + Description string +} -// Run removes a variable from the workflow store. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewVarDelete creates a new VarDelete instance. +func NewVarDelete() *VarDelete { + return &VarDelete{ + NodeType: "var.delete", + Category: "var", + Description: "Delete a variable from the workflow store", + } +} + +// Runtime interface for accessing workflow store. +type Runtime interface { + GetStore() map[string]interface{} +} + +// Execute runs the plugin logic. +// Removes a variable from the workflow store. +func (p *VarDelete) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { key, ok := inputs["key"].(string) if !ok { - return map[string]interface{}{"success": false, "error": "key is required"}, nil + return map[string]interface{}{"success": false, "error": "key is required"} } - _, existed := runtime.Store[key] - delete(runtime.Store, key) + // Try to access the runtime store + var store map[string]interface{} + if r, ok := runtime.(Runtime); ok { + store = r.GetStore() + } else if r, ok := runtime.(map[string]interface{}); ok { + if s, ok := r["Store"].(map[string]interface{}); ok { + store = s + } + } - return map[string]interface{}{"success": true, "existed": existed}, nil + if store == nil { + return map[string]interface{}{"success": false, "error": "runtime store not available"} + } + + _, existed := store[key] + delete(store, key) + + return map[string]interface{}{"success": true, "existed": existed} } diff --git a/workflow/plugins/go/var/var_get/factory.go b/workflow/plugins/go/var/var_get/factory.go new file mode 100644 index 000000000..894ed094b --- /dev/null +++ b/workflow/plugins/go/var/var_get/factory.go @@ -0,0 +1,7 @@ +// Package var_get provides factory for VarGet plugin. +package var_get + +// Create returns a new VarGet instance. +func Create() *VarGet { + return NewVarGet() +} diff --git a/workflow/plugins/go/var/var_get/package.json b/workflow/plugins/go/var/var_get/package.json index 6fd279a61..88e19d027 100644 --- a/workflow/plugins/go/var/var_get/package.json +++ b/workflow/plugins/go/var/var_get/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["var", "workflow", "plugin"], "main": "var_get.go", + "files": ["var_get.go", "factory.go"], "metadata": { "plugin_type": "var.get", - "category": "var" + "category": "var", + "struct": "VarGet", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/var/var_get/var_get.go b/workflow/plugins/go/var/var_get/var_get.go index 1d63f0fd3..c64eef680 100644 --- a/workflow/plugins/go/var/var_get/var_get.go +++ b/workflow/plugins/go/var/var_get/var_get.go @@ -1,24 +1,59 @@ -// Package var_get provides the variable get plugin. +// Package var_get provides a workflow plugin for getting workflow variables. package var_get -import ( - plugin "metabuilder/workflow/plugins/go" -) +// VarGet implements the NodeExecutor interface for getting workflow variables. +type VarGet struct { + NodeType string + Category string + Description string +} -// Run retrieves a variable from the workflow store. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewVarGet creates a new VarGet instance. +func NewVarGet() *VarGet { + return &VarGet{ + NodeType: "var.get", + Category: "var", + Description: "Get a variable from the workflow store", + } +} + +// Runtime interface for accessing workflow store. +type Runtime interface { + GetStore() map[string]interface{} +} + +// Execute runs the plugin logic. +// Retrieves a variable from the workflow store. +func (p *VarGet) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { key, ok := inputs["key"].(string) if !ok { return map[string]interface{}{ "result": nil, "exists": false, "error": "key is required", - }, nil + } } defaultVal := inputs["default"] - value, exists := runtime.Store[key] + // Try to access the runtime store + var store map[string]interface{} + if r, ok := runtime.(Runtime); ok { + store = r.GetStore() + } else if r, ok := runtime.(map[string]interface{}); ok { + if s, ok := r["Store"].(map[string]interface{}); ok { + store = s + } + } + + if store == nil { + return map[string]interface{}{ + "result": defaultVal, + "exists": false, + } + } + + value, exists := store[key] if !exists { value = defaultVal } @@ -26,5 +61,5 @@ func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]int return map[string]interface{}{ "result": value, "exists": exists, - }, nil + } } diff --git a/workflow/plugins/go/var/var_set/factory.go b/workflow/plugins/go/var/var_set/factory.go new file mode 100644 index 000000000..855f54d37 --- /dev/null +++ b/workflow/plugins/go/var/var_set/factory.go @@ -0,0 +1,7 @@ +// Package var_set provides factory for VarSet plugin. +package var_set + +// Create returns a new VarSet instance. +func Create() *VarSet { + return NewVarSet() +} diff --git a/workflow/plugins/go/var/var_set/package.json b/workflow/plugins/go/var/var_set/package.json index debe2bfc1..2733e18b9 100644 --- a/workflow/plugins/go/var/var_set/package.json +++ b/workflow/plugins/go/var/var_set/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["var", "workflow", "plugin"], "main": "var_set.go", + "files": ["var_set.go", "factory.go"], "metadata": { "plugin_type": "var.set", - "category": "var" + "category": "var", + "struct": "VarSet", + "entrypoint": "Execute" } } diff --git a/workflow/plugins/go/var/var_set/var_set.go b/workflow/plugins/go/var/var_set/var_set.go index d892f4ba6..b9fb535c0 100644 --- a/workflow/plugins/go/var/var_set/var_set.go +++ b/workflow/plugins/go/var/var_set/var_set.go @@ -1,19 +1,52 @@ -// Package var_set provides the variable set plugin. +// Package var_set provides a workflow plugin for setting workflow variables. package var_set -import ( - plugin "metabuilder/workflow/plugins/go" -) +// VarSet implements the NodeExecutor interface for setting workflow variables. +type VarSet struct { + NodeType string + Category string + Description string +} -// Run stores a variable in the workflow store. -func Run(runtime *plugin.Runtime, inputs map[string]interface{}) (map[string]interface{}, error) { +// NewVarSet creates a new VarSet instance. +func NewVarSet() *VarSet { + return &VarSet{ + NodeType: "var.set", + Category: "var", + Description: "Set a variable in the workflow store", + } +} + +// Runtime interface for accessing workflow store. +type Runtime interface { + GetStore() map[string]interface{} +} + +// Execute runs the plugin logic. +// Stores a variable in the workflow store. +func (p *VarSet) Execute(inputs map[string]interface{}, runtime interface{}) map[string]interface{} { key, ok := inputs["key"].(string) if !ok { - return map[string]interface{}{"success": false, "error": "key is required"}, nil + return map[string]interface{}{"success": false, "error": "key is required"} } value := inputs["value"] - runtime.Store[key] = value - return map[string]interface{}{"success": true, "key": key}, nil + // Try to access the runtime store + var store map[string]interface{} + if r, ok := runtime.(Runtime); ok { + store = r.GetStore() + } else if r, ok := runtime.(map[string]interface{}); ok { + if s, ok := r["Store"].(map[string]interface{}); ok { + store = s + } + } + + if store == nil { + return map[string]interface{}{"success": false, "error": "runtime store not available"} + } + + store[key] = value + + return map[string]interface{}{"success": true, "key": key} } diff --git a/workflow/plugins/mojo/list/list_concat/factory.mojo b/workflow/plugins/mojo/list/list_concat/factory.mojo new file mode 100644 index 000000000..47851b0bc --- /dev/null +++ b/workflow/plugins/mojo/list/list_concat/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for ListConcat plugin.""" + +from .list_concat import ListConcat + + +fn create() -> ListConcat: + """Create a new ListConcat plugin instance. + + Returns: + A new ListConcat plugin instance. + """ + return ListConcat() diff --git a/workflow/plugins/mojo/list/list_concat/list_concat.mojo b/workflow/plugins/mojo/list/list_concat/list_concat.mojo index e69a15b5a..2b959227a 100644 --- a/workflow/plugins/mojo/list/list_concat/list_concat.mojo +++ b/workflow/plugins/mojo/list/list_concat/list_concat.mojo @@ -1,44 +1,61 @@ -# Workflow plugin: concatenate lists -# -# Concatenates multiple lists into a single list. -# Input: {"lists": [[1, 2], [3, 4], [5]]} -# Output: {"result": [1, 2, 3, 4, 5]} +"""Workflow plugin: concatenate lists. + +Concatenates multiple lists into a single list. +Input: {"lists": [[1, 2], [3, 4], [5]]} +Output: {"result": [1, 2, 3, 4, 5]} +""" from collections import Dict from python import PythonObject, Python -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Concatenate multiple lists into one. +struct ListConcat: + """Plugin that concatenates multiple lists into one.""" - Args: - inputs: Dictionary containing "lists" key with a list of lists. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the concatenated list. - """ - var lists = inputs.get("lists", PythonObject([])) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the ListConcat plugin.""" + self.node_type = "list.concat" + self.category = "list" + self.description = "Concatenate multiple lists into one" - # Use Python to concatenate lists for flexibility with mixed types - var py = Python.import_module("builtins") - var result = py.list() + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Concatenate multiple lists into one. - var num_lists = len(lists) - for i in range(num_lists): - var current_list = lists[i] - var list_len = len(current_list) - for j in range(list_len): - result.append(current_list[j]) + Args: + inputs: Dictionary containing "lists" key with a list of lists. + runtime: Optional runtime context (unused). - output["result"] = result - return output + Returns: + Dictionary with "result" key containing the concatenated list. + """ + var lists = inputs.get("lists", PythonObject([])) + var output = Dict[String, PythonObject]() + + # Use Python to concatenate lists for flexibility with mixed types + var py = Python.import_module("builtins") + var result = py.list() + + var num_lists = len(lists) + for i in range(num_lists): + var current_list = lists[i] + var list_len = len(current_list) + for j in range(list_len): + result.append(current_list[j]) + + output["result"] = result + return output fn main(): """Test the concat plugin.""" + var plugin = ListConcat() + var inputs = Dict[String, PythonObject]() inputs["lists"] = PythonObject([[1, 2], [3, 4], [5]]) - var result = run(inputs) + var result = plugin.execute(inputs) print("Concatenated:", result["result"]) # Expected: [1, 2, 3, 4, 5] diff --git a/workflow/plugins/mojo/list/list_concat/package.json b/workflow/plugins/mojo/list/list_concat/package.json index 04eed0fd2..b785031c3 100644 --- a/workflow/plugins/mojo/list/list_concat/package.json +++ b/workflow/plugins/mojo/list/list_concat/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_concat.mojo", + "files": ["list_concat.mojo", "factory.mojo"], "metadata": { "plugin_type": "list.concat", - "category": "list" + "category": "list", + "struct": "ListConcat", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/list/list_length/factory.mojo b/workflow/plugins/mojo/list/list_length/factory.mojo new file mode 100644 index 000000000..7de30c11d --- /dev/null +++ b/workflow/plugins/mojo/list/list_length/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for ListLength plugin.""" + +from .list_length import ListLength + + +fn create() -> ListLength: + """Create a new ListLength plugin instance. + + Returns: + A new ListLength plugin instance. + """ + return ListLength() diff --git a/workflow/plugins/mojo/list/list_length/list_length.mojo b/workflow/plugins/mojo/list/list_length/list_length.mojo index 6a137934e..a63db9c1a 100644 --- a/workflow/plugins/mojo/list/list_length/list_length.mojo +++ b/workflow/plugins/mojo/list/list_length/list_length.mojo @@ -1,35 +1,52 @@ -# Workflow plugin: get list length -# -# Returns the number of items in a list. -# Input: {"list": [1, 2, 3, 4, 5]} -# Output: {"result": 5} +"""Workflow plugin: get list length. + +Returns the number of items in a list. +Input: {"list": [1, 2, 3, 4, 5]} +Output: {"result": 5} +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Get the length of a list. +struct ListLength: + """Plugin that gets the length of a list.""" - Args: - inputs: Dictionary containing "list" key with the list to measure. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the list length as integer. - """ - var list_input = inputs.get("list", PythonObject([])) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the ListLength plugin.""" + self.node_type = "list.length" + self.category = "list" + self.description = "Get the number of items in a list" - var length = len(list_input) + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Get the length of a list. - output["result"] = PythonObject(length) - return output + Args: + inputs: Dictionary containing "list" key with the list to measure. + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the list length as integer. + """ + var list_input = inputs.get("list", PythonObject([])) + var output = Dict[String, PythonObject]() + + var length = len(list_input) + + output["result"] = PythonObject(length) + return output fn main(): """Test the length plugin.""" + var plugin = ListLength() + var inputs = Dict[String, PythonObject]() inputs["list"] = PythonObject([1, 2, 3, 4, 5]) - var result = run(inputs) + var result = plugin.execute(inputs) print("Length:", result["result"]) # Expected: 5 diff --git a/workflow/plugins/mojo/list/list_length/package.json b/workflow/plugins/mojo/list/list_length/package.json index 11edfb61e..3f5e5f161 100644 --- a/workflow/plugins/mojo/list/list_length/package.json +++ b/workflow/plugins/mojo/list/list_length/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_length.mojo", + "files": ["list_length.mojo", "factory.mojo"], "metadata": { "plugin_type": "list.length", - "category": "list" + "category": "list", + "struct": "ListLength", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/list/list_reverse/factory.mojo b/workflow/plugins/mojo/list/list_reverse/factory.mojo new file mode 100644 index 000000000..f98b94562 --- /dev/null +++ b/workflow/plugins/mojo/list/list_reverse/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for ListReverse plugin.""" + +from .list_reverse import ListReverse + + +fn create() -> ListReverse: + """Create a new ListReverse plugin instance. + + Returns: + A new ListReverse plugin instance. + """ + return ListReverse() diff --git a/workflow/plugins/mojo/list/list_reverse/list_reverse.mojo b/workflow/plugins/mojo/list/list_reverse/list_reverse.mojo index 9bdc5ba8d..fd568d53c 100644 --- a/workflow/plugins/mojo/list/list_reverse/list_reverse.mojo +++ b/workflow/plugins/mojo/list/list_reverse/list_reverse.mojo @@ -1,43 +1,60 @@ -# Workflow plugin: reverse a list -# -# Reverses the order of items in a list. -# Input: {"list": [1, 2, 3, 4, 5]} -# Output: {"result": [5, 4, 3, 2, 1]} +"""Workflow plugin: reverse a list. + +Reverses the order of items in a list. +Input: {"list": [1, 2, 3, 4, 5]} +Output: {"result": [5, 4, 3, 2, 1]} +""" from collections import Dict from python import PythonObject, Python -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Reverse the order of items in a list. +struct ListReverse: + """Plugin that reverses the order of items in a list.""" - Args: - inputs: Dictionary containing "list" key with the list to reverse. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the reversed list. - """ - var list_input = inputs.get("list", PythonObject([])) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the ListReverse plugin.""" + self.node_type = "list.reverse" + self.category = "list" + self.description = "Reverse the order of items in a list" - # Use Python to create reversed list for flexibility with mixed types - var py = Python.import_module("builtins") - var result = py.list() + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Reverse the order of items in a list. - var length = len(list_input) + Args: + inputs: Dictionary containing "list" key with the list to reverse. + runtime: Optional runtime context (unused). - # Build reversed list - for i in range(length - 1, -1, -1): - result.append(list_input[i]) + Returns: + Dictionary with "result" key containing the reversed list. + """ + var list_input = inputs.get("list", PythonObject([])) + var output = Dict[String, PythonObject]() - output["result"] = result - return output + # Use Python to create reversed list for flexibility with mixed types + var py = Python.import_module("builtins") + var result = py.list() + + var length = len(list_input) + + # Build reversed list + for i in range(length - 1, -1, -1): + result.append(list_input[i]) + + output["result"] = result + return output fn main(): """Test the reverse plugin.""" + var plugin = ListReverse() + var inputs = Dict[String, PythonObject]() inputs["list"] = PythonObject([1, 2, 3, 4, 5]) - var result = run(inputs) + var result = plugin.execute(inputs) print("Reversed:", result["result"]) # Expected: [5, 4, 3, 2, 1] diff --git a/workflow/plugins/mojo/list/list_reverse/package.json b/workflow/plugins/mojo/list/list_reverse/package.json index 857aab109..489c743f5 100644 --- a/workflow/plugins/mojo/list/list_reverse/package.json +++ b/workflow/plugins/mojo/list/list_reverse/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_reverse.mojo", + "files": ["list_reverse.mojo", "factory.mojo"], "metadata": { "plugin_type": "list.reverse", - "category": "list" + "category": "list", + "struct": "ListReverse", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/math/math_add/factory.mojo b/workflow/plugins/mojo/math/math_add/factory.mojo new file mode 100644 index 000000000..68821c2a1 --- /dev/null +++ b/workflow/plugins/mojo/math/math_add/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for MathAdd plugin.""" + +from .math_add import MathAdd + + +fn create() -> MathAdd: + """Create a new MathAdd plugin instance. + + Returns: + A new MathAdd plugin instance. + """ + return MathAdd() diff --git a/workflow/plugins/mojo/math/math_add/math_add.mojo b/workflow/plugins/mojo/math/math_add/math_add.mojo index ef5be5279..2f7e3b0b6 100644 --- a/workflow/plugins/mojo/math/math_add/math_add.mojo +++ b/workflow/plugins/mojo/math/math_add/math_add.mojo @@ -1,37 +1,54 @@ -# Workflow plugin: add numbers -# -# Adds two or more numbers together and returns the sum. -# Input: {"numbers": [1, 2, 3, ...]} -# Output: {"result": 6.0} +"""Workflow plugin: add numbers. + +Adds two or more numbers together and returns the sum. +Input: {"numbers": [1, 2, 3, ...]} +Output: {"result": 6.0} +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Add two or more numbers. +struct MathAdd: + """Plugin that adds two or more numbers together.""" - Args: - inputs: Dictionary containing "numbers" key with a list of numbers. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the sum as Float64. - """ - var numbers = inputs.get("numbers", PythonObject([])) - var result: Float64 = 0.0 + fn __init__(inout self): + """Initialize the MathAdd plugin.""" + self.node_type = "math.add" + self.category = "math" + self.description = "Add two or more numbers together" - for i in range(len(numbers)): - result += Float64(numbers[i]) + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Add two or more numbers. - var output = Dict[String, PythonObject]() - output["result"] = PythonObject(result) - return output + Args: + inputs: Dictionary containing "numbers" key with a list of numbers. + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the sum as Float64. + """ + var numbers = inputs.get("numbers", PythonObject([])) + var result: Float64 = 0.0 + + for i in range(len(numbers)): + result += Float64(numbers[i]) + + var output = Dict[String, PythonObject]() + output["result"] = PythonObject(result) + return output fn main(): """Test the add plugin.""" + var plugin = MathAdd() + var inputs = Dict[String, PythonObject]() inputs["numbers"] = PythonObject([1, 2, 3, 4, 5]) - var result = run(inputs) + var result = plugin.execute(inputs) print("Sum:", result["result"]) # Expected: 15.0 diff --git a/workflow/plugins/mojo/math/math_add/package.json b/workflow/plugins/mojo/math/math_add/package.json index 4e2b26ab5..96738122a 100644 --- a/workflow/plugins/mojo/math/math_add/package.json +++ b/workflow/plugins/mojo/math/math_add/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_add.mojo", + "files": ["math_add.mojo", "factory.mojo"], "metadata": { "plugin_type": "math.add", - "category": "math" + "category": "math", + "struct": "MathAdd", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/math/math_divide/factory.mojo b/workflow/plugins/mojo/math/math_divide/factory.mojo new file mode 100644 index 000000000..205984a01 --- /dev/null +++ b/workflow/plugins/mojo/math/math_divide/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for MathDivide plugin.""" + +from .math_divide import MathDivide + + +fn create() -> MathDivide: + """Create a new MathDivide plugin instance. + + Returns: + A new MathDivide plugin instance. + """ + return MathDivide() diff --git a/workflow/plugins/mojo/math/math_divide/math_divide.mojo b/workflow/plugins/mojo/math/math_divide/math_divide.mojo index 2118f1443..e31af9560 100644 --- a/workflow/plugins/mojo/math/math_divide/math_divide.mojo +++ b/workflow/plugins/mojo/math/math_divide/math_divide.mojo @@ -1,51 +1,68 @@ -# Workflow plugin: divide numbers -# -# Divides numbers sequentially from the first number. -# Input: {"numbers": [100, 2, 5]} -# Output: {"result": 10.0} (100 / 2 / 5 = 10) +"""Workflow plugin: divide numbers. + +Divides numbers sequentially from the first number. +Input: {"numbers": [100, 2, 5]} +Output: {"result": 10.0} (100 / 2 / 5 = 10) +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Divide numbers sequentially from the first number. +struct MathDivide: + """Plugin that divides numbers sequentially from the first number.""" - Args: - inputs: Dictionary containing "numbers" key with a list of numbers. - The first number is the dividend, subsequent numbers are divisors. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the quotient as Float64, - or "error" key if division by zero is attempted. - """ - var numbers = inputs.get("numbers", PythonObject([])) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the MathDivide plugin.""" + self.node_type = "math.divide" + self.category = "math" + self.description = "Divide numbers sequentially from the first number" - var length = len(numbers) - if length == 0: - output["result"] = PythonObject(0.0) - return output + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Divide numbers sequentially from the first number. - var result: Float64 = Float64(numbers[0]) + Args: + inputs: Dictionary containing "numbers" key with a list of numbers. + The first number is the dividend, subsequent numbers are divisors. + runtime: Optional runtime context (unused). - for i in range(1, length): - var divisor = Float64(numbers[i]) - if divisor == 0.0: - output["error"] = PythonObject("Division by zero") + Returns: + Dictionary with "result" key containing the quotient as Float64, + or "error" key if division by zero is attempted. + """ + var numbers = inputs.get("numbers", PythonObject([])) + var output = Dict[String, PythonObject]() + + var length = len(numbers) + if length == 0: + output["result"] = PythonObject(0.0) return output - result /= divisor - output["result"] = PythonObject(result) - return output + var result: Float64 = Float64(numbers[0]) + + for i in range(1, length): + var divisor = Float64(numbers[i]) + if divisor == 0.0: + output["error"] = PythonObject("Division by zero") + return output + result /= divisor + + output["result"] = PythonObject(result) + return output fn main(): """Test the divide plugin.""" + var plugin = MathDivide() + var inputs = Dict[String, PythonObject]() inputs["numbers"] = PythonObject([100, 2, 5]) - var result = run(inputs) + var result = plugin.execute(inputs) if "error" in result: print("Error:", result["error"]) else: diff --git a/workflow/plugins/mojo/math/math_divide/package.json b/workflow/plugins/mojo/math/math_divide/package.json index 8ff766361..a341c80f7 100644 --- a/workflow/plugins/mojo/math/math_divide/package.json +++ b/workflow/plugins/mojo/math/math_divide/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_divide.mojo", + "files": ["math_divide.mojo", "factory.mojo"], "metadata": { "plugin_type": "math.divide", - "category": "math" + "category": "math", + "struct": "MathDivide", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/math/math_multiply/factory.mojo b/workflow/plugins/mojo/math/math_multiply/factory.mojo new file mode 100644 index 000000000..6c7885001 --- /dev/null +++ b/workflow/plugins/mojo/math/math_multiply/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for MathMultiply plugin.""" + +from .math_multiply import MathMultiply + + +fn create() -> MathMultiply: + """Create a new MathMultiply plugin instance. + + Returns: + A new MathMultiply plugin instance. + """ + return MathMultiply() diff --git a/workflow/plugins/mojo/math/math_multiply/math_multiply.mojo b/workflow/plugins/mojo/math/math_multiply/math_multiply.mojo index ee9e1c2a2..8219c6aec 100644 --- a/workflow/plugins/mojo/math/math_multiply/math_multiply.mojo +++ b/workflow/plugins/mojo/math/math_multiply/math_multiply.mojo @@ -1,43 +1,60 @@ -# Workflow plugin: multiply numbers -# -# Multiplies two or more numbers together and returns the product. -# Input: {"numbers": [2, 3, 4]} -# Output: {"result": 24.0} +"""Workflow plugin: multiply numbers. + +Multiplies two or more numbers together and returns the product. +Input: {"numbers": [2, 3, 4]} +Output: {"result": 24.0} +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Multiply two or more numbers. +struct MathMultiply: + """Plugin that multiplies two or more numbers together.""" - Args: - inputs: Dictionary containing "numbers" key with a list of numbers. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the product as Float64. - """ - var numbers = inputs.get("numbers", PythonObject([])) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the MathMultiply plugin.""" + self.node_type = "math.multiply" + self.category = "math" + self.description = "Multiply two or more numbers together" - var length = len(numbers) - if length == 0: - output["result"] = PythonObject(0.0) + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Multiply two or more numbers. + + Args: + inputs: Dictionary containing "numbers" key with a list of numbers. + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the product as Float64. + """ + var numbers = inputs.get("numbers", PythonObject([])) + var output = Dict[String, PythonObject]() + + var length = len(numbers) + if length == 0: + output["result"] = PythonObject(0.0) + return output + + var result: Float64 = 1.0 + + for i in range(length): + result *= Float64(numbers[i]) + + output["result"] = PythonObject(result) return output - var result: Float64 = 1.0 - - for i in range(length): - result *= Float64(numbers[i]) - - output["result"] = PythonObject(result) - return output - fn main(): """Test the multiply plugin.""" + var plugin = MathMultiply() + var inputs = Dict[String, PythonObject]() inputs["numbers"] = PythonObject([2, 3, 4]) - var result = run(inputs) + var result = plugin.execute(inputs) print("Product:", result["result"]) # Expected: 24.0 diff --git a/workflow/plugins/mojo/math/math_multiply/package.json b/workflow/plugins/mojo/math/math_multiply/package.json index 3ab54aa73..9d668fed1 100644 --- a/workflow/plugins/mojo/math/math_multiply/package.json +++ b/workflow/plugins/mojo/math/math_multiply/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_multiply.mojo", + "files": ["math_multiply.mojo", "factory.mojo"], "metadata": { "plugin_type": "math.multiply", - "category": "math" + "category": "math", + "struct": "MathMultiply", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/math/math_subtract/factory.mojo b/workflow/plugins/mojo/math/math_subtract/factory.mojo new file mode 100644 index 000000000..4842d8332 --- /dev/null +++ b/workflow/plugins/mojo/math/math_subtract/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for MathSubtract plugin.""" + +from .math_subtract import MathSubtract + + +fn create() -> MathSubtract: + """Create a new MathSubtract plugin instance. + + Returns: + A new MathSubtract plugin instance. + """ + return MathSubtract() diff --git a/workflow/plugins/mojo/math/math_subtract/math_subtract.mojo b/workflow/plugins/mojo/math/math_subtract/math_subtract.mojo index 744762975..5ac3aabbb 100644 --- a/workflow/plugins/mojo/math/math_subtract/math_subtract.mojo +++ b/workflow/plugins/mojo/math/math_subtract/math_subtract.mojo @@ -1,45 +1,62 @@ -# Workflow plugin: subtract numbers -# -# Subtracts numbers sequentially from the first number. -# Input: {"numbers": [10, 3, 2]} -# Output: {"result": 5.0} (10 - 3 - 2 = 5) +"""Workflow plugin: subtract numbers. + +Subtracts numbers sequentially from the first number. +Input: {"numbers": [10, 3, 2]} +Output: {"result": 5.0} (10 - 3 - 2 = 5) +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Subtract numbers sequentially from the first number. +struct MathSubtract: + """Plugin that subtracts numbers sequentially from the first number.""" - Args: - inputs: Dictionary containing "numbers" key with a list of numbers. - The first number is the starting value, subsequent numbers - are subtracted from it. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the difference as Float64. - """ - var numbers = inputs.get("numbers", PythonObject([])) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the MathSubtract plugin.""" + self.node_type = "math.subtract" + self.category = "math" + self.description = "Subtract numbers sequentially from the first number" - var length = len(numbers) - if length == 0: - output["result"] = PythonObject(0.0) + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Subtract numbers sequentially from the first number. + + Args: + inputs: Dictionary containing "numbers" key with a list of numbers. + The first number is the starting value, subsequent numbers + are subtracted from it. + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the difference as Float64. + """ + var numbers = inputs.get("numbers", PythonObject([])) + var output = Dict[String, PythonObject]() + + var length = len(numbers) + if length == 0: + output["result"] = PythonObject(0.0) + return output + + var result: Float64 = Float64(numbers[0]) + + for i in range(1, length): + result -= Float64(numbers[i]) + + output["result"] = PythonObject(result) return output - var result: Float64 = Float64(numbers[0]) - - for i in range(1, length): - result -= Float64(numbers[i]) - - output["result"] = PythonObject(result) - return output - fn main(): """Test the subtract plugin.""" + var plugin = MathSubtract() + var inputs = Dict[String, PythonObject]() inputs["numbers"] = PythonObject([10, 3, 2]) - var result = run(inputs) + var result = plugin.execute(inputs) print("Difference:", result["result"]) # Expected: 5.0 diff --git a/workflow/plugins/mojo/math/math_subtract/package.json b/workflow/plugins/mojo/math/math_subtract/package.json index 4c9eadd5e..1224f9e77 100644 --- a/workflow/plugins/mojo/math/math_subtract/package.json +++ b/workflow/plugins/mojo/math/math_subtract/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_subtract.mojo", + "files": ["math_subtract.mojo", "factory.mojo"], "metadata": { "plugin_type": "math.subtract", - "category": "math" + "category": "math", + "struct": "MathSubtract", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/string/string_concat/factory.mojo b/workflow/plugins/mojo/string/string_concat/factory.mojo new file mode 100644 index 000000000..3ec8e93db --- /dev/null +++ b/workflow/plugins/mojo/string/string_concat/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for StringConcat plugin.""" + +from .string_concat import StringConcat + + +fn create() -> StringConcat: + """Create a new StringConcat plugin instance. + + Returns: + A new StringConcat plugin instance. + """ + return StringConcat() diff --git a/workflow/plugins/mojo/string/string_concat/package.json b/workflow/plugins/mojo/string/string_concat/package.json index ed97295e3..eeb4fc12a 100644 --- a/workflow/plugins/mojo/string/string_concat/package.json +++ b/workflow/plugins/mojo/string/string_concat/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_concat.mojo", + "files": ["string_concat.mojo", "factory.mojo"], "metadata": { "plugin_type": "string.concat", - "category": "string" + "category": "string", + "struct": "StringConcat", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/string/string_concat/string_concat.mojo b/workflow/plugins/mojo/string/string_concat/string_concat.mojo index ac4371e3d..0b0ee014a 100644 --- a/workflow/plugins/mojo/string/string_concat/string_concat.mojo +++ b/workflow/plugins/mojo/string/string_concat/string_concat.mojo @@ -1,47 +1,64 @@ -# Workflow plugin: concatenate strings -# -# Concatenates multiple strings together with an optional separator. -# Input: {"strings": ["Hello", "World"], "separator": " "} -# Output: {"result": "Hello World"} +"""Workflow plugin: concatenate strings. + +Concatenates multiple strings together with an optional separator. +Input: {"strings": ["Hello", "World"], "separator": " "} +Output: {"result": "Hello World"} +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Concatenate multiple strings with an optional separator. +struct StringConcat: + """Plugin that concatenates multiple strings with an optional separator.""" - Args: - inputs: Dictionary containing: - - "strings": List of strings to concatenate - - "separator": Optional separator string (default: "") + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the concatenated string. - """ - var strings = inputs.get("strings", PythonObject([])) - var separator = String(inputs.get("separator", PythonObject(""))) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the StringConcat plugin.""" + self.node_type = "string.concat" + self.category = "string" + self.description = "Concatenate multiple strings with optional separator" - var length = len(strings) - if length == 0: - output["result"] = PythonObject("") + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Concatenate multiple strings with an optional separator. + + Args: + inputs: Dictionary containing: + - "strings": List of strings to concatenate + - "separator": Optional separator string (default: "") + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the concatenated string. + """ + var strings = inputs.get("strings", PythonObject([])) + var separator = String(inputs.get("separator", PythonObject(""))) + var output = Dict[String, PythonObject]() + + var length = len(strings) + if length == 0: + output["result"] = PythonObject("") + return output + + var result = String(strings[0]) + + for i in range(1, length): + result += separator + String(strings[i]) + + output["result"] = PythonObject(result) return output - var result = String(strings[0]) - - for i in range(1, length): - result += separator + String(strings[i]) - - output["result"] = PythonObject(result) - return output - fn main(): """Test the concat plugin.""" + var plugin = StringConcat() + var inputs = Dict[String, PythonObject]() inputs["strings"] = PythonObject(["Hello", "World"]) inputs["separator"] = PythonObject(" ") - var result = run(inputs) + var result = plugin.execute(inputs) print("Concatenated:", result["result"]) # Expected: "Hello World" diff --git a/workflow/plugins/mojo/string/string_length/factory.mojo b/workflow/plugins/mojo/string/string_length/factory.mojo new file mode 100644 index 000000000..30f8f4309 --- /dev/null +++ b/workflow/plugins/mojo/string/string_length/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for StringLength plugin.""" + +from .string_length import StringLength + + +fn create() -> StringLength: + """Create a new StringLength plugin instance. + + Returns: + A new StringLength plugin instance. + """ + return StringLength() diff --git a/workflow/plugins/mojo/string/string_length/package.json b/workflow/plugins/mojo/string/string_length/package.json index 5065ea54b..30f1eeaf0 100644 --- a/workflow/plugins/mojo/string/string_length/package.json +++ b/workflow/plugins/mojo/string/string_length/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_length.mojo", + "files": ["string_length.mojo", "factory.mojo"], "metadata": { "plugin_type": "string.length", - "category": "string" + "category": "string", + "struct": "StringLength", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/string/string_length/string_length.mojo b/workflow/plugins/mojo/string/string_length/string_length.mojo index 80329c374..fb6293301 100644 --- a/workflow/plugins/mojo/string/string_length/string_length.mojo +++ b/workflow/plugins/mojo/string/string_length/string_length.mojo @@ -1,35 +1,52 @@ -# Workflow plugin: get string length -# -# Returns the length of a string. -# Input: {"text": "hello"} -# Output: {"result": 5} +"""Workflow plugin: get string length. + +Returns the length of a string. +Input: {"text": "hello"} +Output: {"result": 5} +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Get the length of a string. +struct StringLength: + """Plugin that gets the length of a string.""" - Args: - inputs: Dictionary containing "text" key with the string to measure. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the string length as integer. - """ - var text = String(inputs.get("text", PythonObject(""))) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the StringLength plugin.""" + self.node_type = "string.length" + self.category = "string" + self.description = "Get the length of a string" - var length = len(text) + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Get the length of a string. - output["result"] = PythonObject(length) - return output + Args: + inputs: Dictionary containing "text" key with the string to measure. + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the string length as integer. + """ + var text = String(inputs.get("text", PythonObject(""))) + var output = Dict[String, PythonObject]() + + var length = len(text) + + output["result"] = PythonObject(length) + return output fn main(): """Test the length plugin.""" + var plugin = StringLength() + var inputs = Dict[String, PythonObject]() inputs["text"] = PythonObject("hello") - var result = run(inputs) + var result = plugin.execute(inputs) print("Length:", result["result"]) # Expected: 5 diff --git a/workflow/plugins/mojo/string/string_lower/factory.mojo b/workflow/plugins/mojo/string/string_lower/factory.mojo new file mode 100644 index 000000000..61a2db604 --- /dev/null +++ b/workflow/plugins/mojo/string/string_lower/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for StringLower plugin.""" + +from .string_lower import StringLower + + +fn create() -> StringLower: + """Create a new StringLower plugin instance. + + Returns: + A new StringLower plugin instance. + """ + return StringLower() diff --git a/workflow/plugins/mojo/string/string_lower/package.json b/workflow/plugins/mojo/string/string_lower/package.json index 96e52de7b..a05613b3d 100644 --- a/workflow/plugins/mojo/string/string_lower/package.json +++ b/workflow/plugins/mojo/string/string_lower/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_lower.mojo", + "files": ["string_lower.mojo", "factory.mojo"], "metadata": { "plugin_type": "string.lower", - "category": "string" + "category": "string", + "struct": "StringLower", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/string/string_lower/string_lower.mojo b/workflow/plugins/mojo/string/string_lower/string_lower.mojo index 8b556400c..1f18f548b 100644 --- a/workflow/plugins/mojo/string/string_lower/string_lower.mojo +++ b/workflow/plugins/mojo/string/string_lower/string_lower.mojo @@ -1,37 +1,54 @@ -# Workflow plugin: convert string to lowercase -# -# Converts a string to all lowercase characters. -# Input: {"text": "HELLO WORLD"} -# Output: {"result": "hello world"} +"""Workflow plugin: convert string to lowercase. + +Converts a string to all lowercase characters. +Input: {"text": "HELLO WORLD"} +Output: {"result": "hello world"} +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Convert a string to lowercase. +struct StringLower: + """Plugin that converts a string to lowercase.""" - Args: - inputs: Dictionary containing "text" key with the string to convert. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the lowercase string. - """ - var text = String(inputs.get("text", PythonObject(""))) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the StringLower plugin.""" + self.node_type = "string.lower" + self.category = "string" + self.description = "Convert a string to lowercase" - # Convert to lowercase using Python interop for full Unicode support - var py_text = PythonObject(text) - var lower_text = py_text.lower() + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Convert a string to lowercase. - output["result"] = lower_text - return output + Args: + inputs: Dictionary containing "text" key with the string to convert. + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the lowercase string. + """ + var text = String(inputs.get("text", PythonObject(""))) + var output = Dict[String, PythonObject]() + + # Convert to lowercase using Python interop for full Unicode support + var py_text = PythonObject(text) + var lower_text = py_text.lower() + + output["result"] = lower_text + return output fn main(): """Test the lower plugin.""" + var plugin = StringLower() + var inputs = Dict[String, PythonObject]() inputs["text"] = PythonObject("HELLO WORLD") - var result = run(inputs) + var result = plugin.execute(inputs) print("Lowercase:", result["result"]) # Expected: "hello world" diff --git a/workflow/plugins/mojo/string/string_upper/factory.mojo b/workflow/plugins/mojo/string/string_upper/factory.mojo new file mode 100644 index 000000000..f56617428 --- /dev/null +++ b/workflow/plugins/mojo/string/string_upper/factory.mojo @@ -0,0 +1,12 @@ +"""Factory for StringUpper plugin.""" + +from .string_upper import StringUpper + + +fn create() -> StringUpper: + """Create a new StringUpper plugin instance. + + Returns: + A new StringUpper plugin instance. + """ + return StringUpper() diff --git a/workflow/plugins/mojo/string/string_upper/package.json b/workflow/plugins/mojo/string/string_upper/package.json index 26b67403a..0262b6abc 100644 --- a/workflow/plugins/mojo/string/string_upper/package.json +++ b/workflow/plugins/mojo/string/string_upper/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_upper.mojo", + "files": ["string_upper.mojo", "factory.mojo"], "metadata": { "plugin_type": "string.upper", - "category": "string" + "category": "string", + "struct": "StringUpper", + "entrypoint": "execute" } } diff --git a/workflow/plugins/mojo/string/string_upper/string_upper.mojo b/workflow/plugins/mojo/string/string_upper/string_upper.mojo index b8d0a3507..e7767f89b 100644 --- a/workflow/plugins/mojo/string/string_upper/string_upper.mojo +++ b/workflow/plugins/mojo/string/string_upper/string_upper.mojo @@ -1,37 +1,54 @@ -# Workflow plugin: convert string to uppercase -# -# Converts a string to all uppercase characters. -# Input: {"text": "hello world"} -# Output: {"result": "HELLO WORLD"} +"""Workflow plugin: convert string to uppercase. + +Converts a string to all uppercase characters. +Input: {"text": "hello world"} +Output: {"result": "HELLO WORLD"} +""" from collections import Dict from python import PythonObject -fn run(inputs: Dict[String, PythonObject]) -> Dict[String, PythonObject]: - """Convert a string to uppercase. +struct StringUpper: + """Plugin that converts a string to uppercase.""" - Args: - inputs: Dictionary containing "text" key with the string to convert. + var node_type: String + var category: String + var description: String - Returns: - Dictionary with "result" key containing the uppercase string. - """ - var text = String(inputs.get("text", PythonObject(""))) - var output = Dict[String, PythonObject]() + fn __init__(inout self): + """Initialize the StringUpper plugin.""" + self.node_type = "string.upper" + self.category = "string" + self.description = "Convert a string to uppercase" - # Convert to uppercase using Python interop for full Unicode support - var py_text = PythonObject(text) - var upper_text = py_text.upper() + fn execute(self, inputs: Dict[String, PythonObject], runtime: PythonObject = PythonObject(None)) -> Dict[String, PythonObject]: + """Convert a string to uppercase. - output["result"] = upper_text - return output + Args: + inputs: Dictionary containing "text" key with the string to convert. + runtime: Optional runtime context (unused). + + Returns: + Dictionary with "result" key containing the uppercase string. + """ + var text = String(inputs.get("text", PythonObject(""))) + var output = Dict[String, PythonObject]() + + # Convert to uppercase using Python interop for full Unicode support + var py_text = PythonObject(text) + var upper_text = py_text.upper() + + output["result"] = upper_text + return output fn main(): """Test the upper plugin.""" + var plugin = StringUpper() + var inputs = Dict[String, PythonObject]() inputs["text"] = PythonObject("hello world") - var result = run(inputs) + var result = plugin.execute(inputs) print("Uppercase:", result["result"]) # Expected: "HELLO WORLD" diff --git a/workflow/plugins/python/backend/backend_build_tool_map/backend_build_tool_map.py b/workflow/plugins/python/backend/backend_build_tool_map/backend_build_tool_map.py index 59d4c250d..0d74afb95 100644 --- a/workflow/plugins/python/backend/backend_build_tool_map/backend_build_tool_map.py +++ b/workflow/plugins/python/backend/backend_build_tool_map/backend_build_tool_map.py @@ -1,18 +1,27 @@ """Workflow plugin: build tool map for function dispatch.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Build a map from tool names to their handlers. - This reads plugins from context and builds a dispatch map. - """ - plugins = runtime.context.get("plugins", {}) +class BuildToolMap(NodeExecutor): + """Build a map from tool names to their handlers.""" - tool_map = {} - for plugin_type, plugin_info in plugins.items(): - # Map plugin_type (e.g., "math.add") to handler info - tool_map[plugin_type] = plugin_info + node_type = "backend.build_tool_map" + category = "backend" + description = "Build tool map for function dispatch" - runtime.context["tool_map"] = tool_map + def execute(self, inputs, runtime=None): + """Build a map from tool names to their handlers. - return {"success": True, "tool_count": len(tool_map)} + This reads plugins from context and builds a dispatch map. + """ + plugins = runtime.context.get("plugins", {}) + + tool_map = {} + for plugin_type, plugin_info in plugins.items(): + # Map plugin_type (e.g., "math.add") to handler info + tool_map[plugin_type] = plugin_info + + runtime.context["tool_map"] = tool_map + + return {"success": True, "tool_count": len(tool_map)} diff --git a/workflow/plugins/python/backend/backend_build_tool_map/factory.py b/workflow/plugins/python/backend/backend_build_tool_map/factory.py new file mode 100644 index 000000000..3c1b2bd7d --- /dev/null +++ b/workflow/plugins/python/backend/backend_build_tool_map/factory.py @@ -0,0 +1,7 @@ +"""Factory for BuildToolMap plugin.""" + +from .backend_build_tool_map import BuildToolMap + + +def create(): + return BuildToolMap() diff --git a/workflow/plugins/python/backend/backend_build_tool_map/package.json b/workflow/plugins/python/backend/backend_build_tool_map/package.json index 47865ec4e..994d1ac75 100644 --- a/workflow/plugins/python/backend/backend_build_tool_map/package.json +++ b/workflow/plugins/python/backend/backend_build_tool_map/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "tools"], "main": "backend_build_tool_map.py", + "files": ["backend_build_tool_map.py", "factory.py"], "metadata": { "plugin_type": "backend.build_tool_map", - "category": "backend" + "category": "backend", + "class": "BuildToolMap", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_configure_logging/backend_configure_logging.py b/workflow/plugins/python/backend/backend_configure_logging/backend_configure_logging.py index 100d1bee0..17b479b17 100644 --- a/workflow/plugins/python/backend/backend_configure_logging/backend_configure_logging.py +++ b/workflow/plugins/python/backend/backend_configure_logging/backend_configure_logging.py @@ -1,35 +1,45 @@ """Workflow plugin: configure logging.""" + import logging import sys +from ...base import NodeExecutor -def run(runtime, inputs): - """Configure logging for the workflow runtime. - Inputs: - level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) - format: Log format string - file: Optional file path for log output - """ - level_str = inputs.get("level", "INFO").upper() - log_format = inputs.get("format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s") - log_file = inputs.get("file") +class ConfigureLogging(NodeExecutor): + """Configure logging for the workflow runtime.""" - level = getattr(logging, level_str, logging.INFO) + node_type = "backend.configure_logging" + category = "backend" + description = "Configure logging for workflow runtime" - handlers = [logging.StreamHandler(sys.stdout)] - if log_file: - handlers.append(logging.FileHandler(log_file)) + def execute(self, inputs, runtime=None): + """Configure logging for the workflow runtime. - logging.basicConfig( - level=level, - format=log_format, - handlers=handlers - ) + Inputs: + level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) + format: Log format string + file: Optional file path for log output + """ + level_str = inputs.get("level", "INFO").upper() + log_format = inputs.get("format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s") + log_file = inputs.get("file") - logger = logging.getLogger("metabuilder") - logger.setLevel(level) + level = getattr(logging, level_str, logging.INFO) - runtime.context["logger"] = logger + handlers = [logging.StreamHandler(sys.stdout)] + if log_file: + handlers.append(logging.FileHandler(log_file)) - return {"success": True, "level": level_str} + logging.basicConfig( + level=level, + format=log_format, + handlers=handlers + ) + + logger = logging.getLogger("metabuilder") + logger.setLevel(level) + + runtime.context["logger"] = logger + + return {"success": True, "level": level_str} diff --git a/workflow/plugins/python/backend/backend_configure_logging/factory.py b/workflow/plugins/python/backend/backend_configure_logging/factory.py new file mode 100644 index 000000000..be0c41002 --- /dev/null +++ b/workflow/plugins/python/backend/backend_configure_logging/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConfigureLogging plugin.""" + +from .backend_configure_logging import ConfigureLogging + + +def create(): + return ConfigureLogging() diff --git a/workflow/plugins/python/backend/backend_configure_logging/package.json b/workflow/plugins/python/backend/backend_configure_logging/package.json index be2d88e4f..6572e4e90 100644 --- a/workflow/plugins/python/backend/backend_configure_logging/package.json +++ b/workflow/plugins/python/backend/backend_configure_logging/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "logging"], "main": "backend_configure_logging.py", + "files": ["backend_configure_logging.py", "factory.py"], "metadata": { "plugin_type": "backend.configure_logging", - "category": "backend" + "category": "backend", + "class": "ConfigureLogging", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_create_discord/backend_create_discord.py b/workflow/plugins/python/backend/backend_create_discord/backend_create_discord.py index 42a0a9087..2f5c048a8 100644 --- a/workflow/plugins/python/backend/backend_create_discord/backend_create_discord.py +++ b/workflow/plugins/python/backend/backend_create_discord/backend_create_discord.py @@ -1,18 +1,28 @@ """Workflow plugin: create Discord client.""" + import os +from ...base import NodeExecutor -def run(runtime, inputs): - """Create a Discord webhook client and store in runtime context. - Inputs: - webhook_url: Discord webhook URL (defaults to DISCORD_WEBHOOK_URL env var) - """ - webhook_url = inputs.get("webhook_url") or os.getenv("DISCORD_WEBHOOK_URL") +class CreateDiscord(NodeExecutor): + """Create a Discord webhook client and store in runtime context.""" - if not webhook_url: - return {"success": False, "error": "No webhook URL provided"} + node_type = "backend.create_discord" + category = "backend" + description = "Create Discord webhook client for notifications" - runtime.context["discord_webhook"] = webhook_url + def execute(self, inputs, runtime=None): + """Create a Discord webhook client and store in runtime context. - return {"success": True} + Inputs: + webhook_url: Discord webhook URL (defaults to DISCORD_WEBHOOK_URL env var) + """ + webhook_url = inputs.get("webhook_url") or os.getenv("DISCORD_WEBHOOK_URL") + + if not webhook_url: + return {"success": False, "error": "No webhook URL provided"} + + runtime.context["discord_webhook"] = webhook_url + + return {"success": True} diff --git a/workflow/plugins/python/backend/backend_create_discord/factory.py b/workflow/plugins/python/backend/backend_create_discord/factory.py new file mode 100644 index 000000000..e9758d05e --- /dev/null +++ b/workflow/plugins/python/backend/backend_create_discord/factory.py @@ -0,0 +1,7 @@ +"""Factory for CreateDiscord plugin.""" + +from .backend_create_discord import CreateDiscord + + +def create(): + return CreateDiscord() diff --git a/workflow/plugins/python/backend/backend_create_discord/package.json b/workflow/plugins/python/backend/backend_create_discord/package.json index 7c365d9e2..d3368f2a0 100644 --- a/workflow/plugins/python/backend/backend_create_discord/package.json +++ b/workflow/plugins/python/backend/backend_create_discord/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "discord"], "main": "backend_create_discord.py", + "files": ["backend_create_discord.py", "factory.py"], "metadata": { "plugin_type": "backend.create_discord", - "category": "backend" + "category": "backend", + "class": "CreateDiscord", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_create_github/backend_create_github.py b/workflow/plugins/python/backend/backend_create_github/backend_create_github.py index 2a500c2c4..7257045c3 100644 --- a/workflow/plugins/python/backend/backend_create_github/backend_create_github.py +++ b/workflow/plugins/python/backend/backend_create_github/backend_create_github.py @@ -1,25 +1,35 @@ """Workflow plugin: create GitHub client.""" + import os +from ...base import NodeExecutor -def run(runtime, inputs): - """Create a GitHub client and store in runtime context. - Inputs: - token: GitHub token (defaults to GITHUB_TOKEN env var) - """ - try: - from github import Github - except ImportError: - return {"success": False, "error": "PyGithub package not installed"} +class CreateGitHub(NodeExecutor): + """Create a GitHub client and store in runtime context.""" - token = inputs.get("token") or os.getenv("GITHUB_TOKEN") + node_type = "backend.create_github" + category = "backend" + description = "Create GitHub client for repository operations" - if not token: - return {"success": False, "error": "No token provided"} + def execute(self, inputs, runtime=None): + """Create a GitHub client and store in runtime context. - client = Github(token) + Inputs: + token: GitHub token (defaults to GITHUB_TOKEN env var) + """ + try: + from github import Github + except ImportError: + return {"success": False, "error": "PyGithub package not installed"} - runtime.context["github"] = client + token = inputs.get("token") or os.getenv("GITHUB_TOKEN") - return {"success": True} + if not token: + return {"success": False, "error": "No token provided"} + + client = Github(token) + + runtime.context["github"] = client + + return {"success": True} diff --git a/workflow/plugins/python/backend/backend_create_github/factory.py b/workflow/plugins/python/backend/backend_create_github/factory.py new file mode 100644 index 000000000..c7fa2aa0f --- /dev/null +++ b/workflow/plugins/python/backend/backend_create_github/factory.py @@ -0,0 +1,7 @@ +"""Factory for CreateGitHub plugin.""" + +from .backend_create_github import CreateGitHub + + +def create(): + return CreateGitHub() diff --git a/workflow/plugins/python/backend/backend_create_github/package.json b/workflow/plugins/python/backend/backend_create_github/package.json index f3f4f87fe..a16b6a474 100644 --- a/workflow/plugins/python/backend/backend_create_github/package.json +++ b/workflow/plugins/python/backend/backend_create_github/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "github"], "main": "backend_create_github.py", + "files": ["backend_create_github.py", "factory.py"], "metadata": { "plugin_type": "backend.create_github", - "category": "backend" + "category": "backend", + "class": "CreateGitHub", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_create_openai/backend_create_openai.py b/workflow/plugins/python/backend/backend_create_openai/backend_create_openai.py index 4e981f541..bd1e51bf4 100644 --- a/workflow/plugins/python/backend/backend_create_openai/backend_create_openai.py +++ b/workflow/plugins/python/backend/backend_create_openai/backend_create_openai.py @@ -1,28 +1,38 @@ """Workflow plugin: create OpenAI client.""" + import os +from ...base import NodeExecutor -def run(runtime, inputs): - """Create an OpenAI client and store in runtime context. - Inputs: - api_key: OpenAI API key (defaults to OPENAI_API_KEY env var) - model: Model name (default: gpt-4) - """ - try: - from openai import OpenAI - except ImportError: - return {"success": False, "error": "openai package not installed"} +class CreateOpenAI(NodeExecutor): + """Create an OpenAI client and store in runtime context.""" - api_key = inputs.get("api_key") or os.getenv("OPENAI_API_KEY") - model = inputs.get("model", "gpt-4") + node_type = "backend.create_openai" + category = "backend" + description = "Create OpenAI client for AI operations" - if not api_key: - return {"success": False, "error": "No API key provided"} + def execute(self, inputs, runtime=None): + """Create an OpenAI client and store in runtime context. - client = OpenAI(api_key=api_key) + Inputs: + api_key: OpenAI API key (defaults to OPENAI_API_KEY env var) + model: Model name (default: gpt-4) + """ + try: + from openai import OpenAI + except ImportError: + return {"success": False, "error": "openai package not installed"} - runtime.context["client"] = client - runtime.context["model_name"] = model + api_key = inputs.get("api_key") or os.getenv("OPENAI_API_KEY") + model = inputs.get("model", "gpt-4") - return {"success": True, "model": model} + if not api_key: + return {"success": False, "error": "No API key provided"} + + client = OpenAI(api_key=api_key) + + runtime.context["client"] = client + runtime.context["model_name"] = model + + return {"success": True, "model": model} diff --git a/workflow/plugins/python/backend/backend_create_openai/factory.py b/workflow/plugins/python/backend/backend_create_openai/factory.py new file mode 100644 index 000000000..e71f53a20 --- /dev/null +++ b/workflow/plugins/python/backend/backend_create_openai/factory.py @@ -0,0 +1,7 @@ +"""Factory for CreateOpenAI plugin.""" + +from .backend_create_openai import CreateOpenAI + + +def create(): + return CreateOpenAI() diff --git a/workflow/plugins/python/backend/backend_create_openai/package.json b/workflow/plugins/python/backend/backend_create_openai/package.json index c0d31310a..822046cde 100644 --- a/workflow/plugins/python/backend/backend_create_openai/package.json +++ b/workflow/plugins/python/backend/backend_create_openai/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "openai", "ai"], "main": "backend_create_openai.py", + "files": ["backend_create_openai.py", "factory.py"], "metadata": { "plugin_type": "backend.create_openai", - "category": "backend" + "category": "backend", + "class": "CreateOpenAI", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_create_slack/backend_create_slack.py b/workflow/plugins/python/backend/backend_create_slack/backend_create_slack.py index fe32b17ab..3506c3316 100644 --- a/workflow/plugins/python/backend/backend_create_slack/backend_create_slack.py +++ b/workflow/plugins/python/backend/backend_create_slack/backend_create_slack.py @@ -1,25 +1,35 @@ """Workflow plugin: create Slack client.""" + import os +from ...base import NodeExecutor -def run(runtime, inputs): - """Create a Slack client and store in runtime context. - Inputs: - token: Slack bot token (defaults to SLACK_BOT_TOKEN env var) - """ - try: - from slack_sdk import WebClient - except ImportError: - return {"success": False, "error": "slack_sdk package not installed"} +class CreateSlack(NodeExecutor): + """Create a Slack client and store in runtime context.""" - token = inputs.get("token") or os.getenv("SLACK_BOT_TOKEN") + node_type = "backend.create_slack" + category = "backend" + description = "Create Slack client for messaging" - if not token: - return {"success": False, "error": "No token provided"} + def execute(self, inputs, runtime=None): + """Create a Slack client and store in runtime context. - client = WebClient(token=token) + Inputs: + token: Slack bot token (defaults to SLACK_BOT_TOKEN env var) + """ + try: + from slack_sdk import WebClient + except ImportError: + return {"success": False, "error": "slack_sdk package not installed"} - runtime.context["slack"] = client + token = inputs.get("token") or os.getenv("SLACK_BOT_TOKEN") - return {"success": True} + if not token: + return {"success": False, "error": "No token provided"} + + client = WebClient(token=token) + + runtime.context["slack"] = client + + return {"success": True} diff --git a/workflow/plugins/python/backend/backend_create_slack/factory.py b/workflow/plugins/python/backend/backend_create_slack/factory.py new file mode 100644 index 000000000..d174c68a2 --- /dev/null +++ b/workflow/plugins/python/backend/backend_create_slack/factory.py @@ -0,0 +1,7 @@ +"""Factory for CreateSlack plugin.""" + +from .backend_create_slack import CreateSlack + + +def create(): + return CreateSlack() diff --git a/workflow/plugins/python/backend/backend_create_slack/package.json b/workflow/plugins/python/backend/backend_create_slack/package.json index fc49572a0..36cabf36d 100644 --- a/workflow/plugins/python/backend/backend_create_slack/package.json +++ b/workflow/plugins/python/backend/backend_create_slack/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "slack"], "main": "backend_create_slack.py", + "files": ["backend_create_slack.py", "factory.py"], "metadata": { "plugin_type": "backend.create_slack", - "category": "backend" + "category": "backend", + "class": "CreateSlack", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_env/backend_load_env.py b/workflow/plugins/python/backend/backend_load_env/backend_load_env.py index 899177fbd..ac3c6af12 100644 --- a/workflow/plugins/python/backend/backend_load_env/backend_load_env.py +++ b/workflow/plugins/python/backend/backend_load_env/backend_load_env.py @@ -1,20 +1,31 @@ """Workflow plugin: load environment variables.""" + import os + from dotenv import load_dotenv +from ...base import NodeExecutor -def run(_runtime, inputs): - """Load environment variables from .env file. - Inputs: - path: Optional path to .env file (default: .env) - override: Whether to override existing env vars (default: False) - """ - path = inputs.get("path", ".env") - override = inputs.get("override", False) +class LoadEnv(NodeExecutor): + """Load environment variables from .env file.""" - if os.path.exists(path): - load_dotenv(path, override=override) - return {"success": True, "path": path} + node_type = "backend.load_env" + category = "backend" + description = "Load environment variables from .env file" - return {"success": False, "error": f"File not found: {path}"} + def execute(self, inputs, runtime=None): + """Load environment variables from .env file. + + Inputs: + path: Optional path to .env file (default: .env) + override: Whether to override existing env vars (default: False) + """ + path = inputs.get("path", ".env") + override = inputs.get("override", False) + + if os.path.exists(path): + load_dotenv(path, override=override) + return {"success": True, "path": path} + + return {"success": False, "error": f"File not found: {path}"} diff --git a/workflow/plugins/python/backend/backend_load_env/factory.py b/workflow/plugins/python/backend/backend_load_env/factory.py new file mode 100644 index 000000000..6cae37f4f --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_env/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadEnv plugin.""" + +from .backend_load_env import LoadEnv + + +def create(): + return LoadEnv() diff --git a/workflow/plugins/python/backend/backend_load_env/package.json b/workflow/plugins/python/backend/backend_load_env/package.json index ac02fe2e1..379ccfc7b 100644 --- a/workflow/plugins/python/backend/backend_load_env/package.json +++ b/workflow/plugins/python/backend/backend_load_env/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "env"], "main": "backend_load_env.py", + "files": ["backend_load_env.py", "factory.py"], "metadata": { "plugin_type": "backend.load_env", - "category": "backend" + "category": "backend", + "class": "LoadEnv", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_messages/backend_load_messages.py b/workflow/plugins/python/backend/backend_load_messages/backend_load_messages.py index 0ed89e95a..d38f9dd1b 100644 --- a/workflow/plugins/python/backend/backend_load_messages/backend_load_messages.py +++ b/workflow/plugins/python/backend/backend_load_messages/backend_load_messages.py @@ -1,29 +1,39 @@ """Workflow plugin: load UI/CLI messages.""" + import os import json +from ...base import NodeExecutor -def run(runtime, inputs): - """Load UI/CLI messages for localization. - Inputs: - path: Path to messages file - locale: Locale code (default: en) - """ - path = inputs.get("path", "config/messages") - locale = inputs.get("locale", "en") +class LoadMessages(NodeExecutor): + """Load UI/CLI messages for localization.""" - messages_file = os.path.join(path, f"{locale}.json") + node_type = "backend.load_messages" + category = "backend" + description = "Load UI/CLI messages for localization" - if not os.path.exists(messages_file): - messages_file = os.path.join(path, "en.json") # Fallback + def execute(self, inputs, runtime=None): + """Load UI/CLI messages for localization. - if not os.path.exists(messages_file): - return {"success": False, "error": "No messages file found"} + Inputs: + path: Path to messages file + locale: Locale code (default: en) + """ + path = inputs.get("path", "config/messages") + locale = inputs.get("locale", "en") - with open(messages_file) as f: - messages = json.load(f) + messages_file = os.path.join(path, f"{locale}.json") - runtime.context["msgs"] = messages + if not os.path.exists(messages_file): + messages_file = os.path.join(path, "en.json") # Fallback - return {"success": True, "locale": locale, "message_count": len(messages)} + if not os.path.exists(messages_file): + return {"success": False, "error": "No messages file found"} + + with open(messages_file) as f: + messages = json.load(f) + + runtime.context["msgs"] = messages + + return {"success": True, "locale": locale, "message_count": len(messages)} diff --git a/workflow/plugins/python/backend/backend_load_messages/factory.py b/workflow/plugins/python/backend/backend_load_messages/factory.py new file mode 100644 index 000000000..65aa02e84 --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_messages/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadMessages plugin.""" + +from .backend_load_messages import LoadMessages + + +def create(): + return LoadMessages() diff --git a/workflow/plugins/python/backend/backend_load_messages/package.json b/workflow/plugins/python/backend/backend_load_messages/package.json index 707eb5d44..c754641db 100644 --- a/workflow/plugins/python/backend/backend_load_messages/package.json +++ b/workflow/plugins/python/backend/backend_load_messages/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "messages", "i18n"], "main": "backend_load_messages.py", + "files": ["backend_load_messages.py", "factory.py"], "metadata": { "plugin_type": "backend.load_messages", - "category": "backend" + "category": "backend", + "class": "LoadMessages", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_metadata/backend_load_metadata.py b/workflow/plugins/python/backend/backend_load_metadata/backend_load_metadata.py index 383525b69..96c4883f6 100644 --- a/workflow/plugins/python/backend/backend_load_metadata/backend_load_metadata.py +++ b/workflow/plugins/python/backend/backend_load_metadata/backend_load_metadata.py @@ -1,26 +1,36 @@ """Workflow plugin: load workflow metadata.""" + import os import json +from ...base import NodeExecutor -def run(runtime, inputs): - """Load workflow metadata from package.json or config. - Inputs: - path: Path to metadata file - """ - path = inputs.get("path", "package.json") +class LoadMetadata(NodeExecutor): + """Load workflow metadata from package.json or config.""" - if not os.path.exists(path): - return {"success": False, "error": f"File not found: {path}"} + node_type = "backend.load_metadata" + category = "backend" + description = "Load workflow metadata from config" - with open(path) as f: - metadata = json.load(f) + def execute(self, inputs, runtime=None): + """Load workflow metadata from package.json or config. - runtime.context["metadata"] = metadata + Inputs: + path: Path to metadata file + """ + path = inputs.get("path", "package.json") - return { - "success": True, - "name": metadata.get("name"), - "version": metadata.get("version") - } + if not os.path.exists(path): + return {"success": False, "error": f"File not found: {path}"} + + with open(path) as f: + metadata = json.load(f) + + runtime.context["metadata"] = metadata + + return { + "success": True, + "name": metadata.get("name"), + "version": metadata.get("version") + } diff --git a/workflow/plugins/python/backend/backend_load_metadata/factory.py b/workflow/plugins/python/backend/backend_load_metadata/factory.py new file mode 100644 index 000000000..feb2a9673 --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_metadata/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadMetadata plugin.""" + +from .backend_load_metadata import LoadMetadata + + +def create(): + return LoadMetadata() diff --git a/workflow/plugins/python/backend/backend_load_metadata/package.json b/workflow/plugins/python/backend/backend_load_metadata/package.json index 93b439919..a4b45fec8 100644 --- a/workflow/plugins/python/backend/backend_load_metadata/package.json +++ b/workflow/plugins/python/backend/backend_load_metadata/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "metadata"], "main": "backend_load_metadata.py", + "files": ["backend_load_metadata.py", "factory.py"], "metadata": { "plugin_type": "backend.load_metadata", - "category": "backend" + "category": "backend", + "class": "LoadMetadata", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_plugins/backend_load_plugins.py b/workflow/plugins/python/backend/backend_load_plugins/backend_load_plugins.py index 1a3bf2bdf..c7e6e12f3 100644 --- a/workflow/plugins/python/backend/backend_load_plugins/backend_load_plugins.py +++ b/workflow/plugins/python/backend/backend_load_plugins/backend_load_plugins.py @@ -1,50 +1,59 @@ """Workflow plugin: load workflow plugins.""" + import os import json -import importlib.util + +from ...base import NodeExecutor -def run(runtime, inputs): - """Load workflow plugins from directory. +class LoadPlugins(NodeExecutor): + """Load workflow plugins from directory.""" - Inputs: - path: Path to plugins directory - """ - path = inputs.get("path", "workflow/plugins/python") + node_type = "backend.load_plugins" + category = "backend" + description = "Load workflow plugins from directory" - if not os.path.exists(path): - return {"success": False, "error": f"Path not found: {path}"} + def execute(self, inputs, runtime=None): + """Load workflow plugins from directory. - plugins = {} - categories = [] + Inputs: + path: Path to plugins directory + """ + path = inputs.get("path", "workflow/plugins/python") - for category in os.listdir(path): - category_path = os.path.join(path, category) - if not os.path.isdir(category_path) or category.startswith("_"): - continue + if not os.path.exists(path): + return {"success": False, "error": f"Path not found: {path}"} - categories.append(category) + plugins = {} + categories = [] - for plugin_name in os.listdir(category_path): - plugin_path = os.path.join(category_path, plugin_name) - if not os.path.isdir(plugin_path): + for category in os.listdir(path): + category_path = os.path.join(path, category) + if not os.path.isdir(category_path) or category.startswith("_"): continue - package_json = os.path.join(plugin_path, "package.json") - if os.path.exists(package_json): - with open(package_json) as f: - metadata = json.load(f) - plugin_type = metadata.get("metadata", {}).get("plugin_type") - if plugin_type: - plugins[plugin_type] = { - "path": plugin_path, - "metadata": metadata - } + categories.append(category) - runtime.context["plugins"] = plugins + for plugin_name in os.listdir(category_path): + plugin_path = os.path.join(category_path, plugin_name) + if not os.path.isdir(plugin_path): + continue - return { - "success": True, - "categories": categories, - "plugin_count": len(plugins) - } + package_json = os.path.join(plugin_path, "package.json") + if os.path.exists(package_json): + with open(package_json) as f: + metadata = json.load(f) + plugin_type = metadata.get("metadata", {}).get("plugin_type") + if plugin_type: + plugins[plugin_type] = { + "path": plugin_path, + "metadata": metadata + } + + runtime.context["plugins"] = plugins + + return { + "success": True, + "categories": categories, + "plugin_count": len(plugins) + } diff --git a/workflow/plugins/python/backend/backend_load_plugins/factory.py b/workflow/plugins/python/backend/backend_load_plugins/factory.py new file mode 100644 index 000000000..68bb457f5 --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_plugins/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadPlugins plugin.""" + +from .backend_load_plugins import LoadPlugins + + +def create(): + return LoadPlugins() diff --git a/workflow/plugins/python/backend/backend_load_plugins/package.json b/workflow/plugins/python/backend/backend_load_plugins/package.json index 050641805..f79a300e6 100644 --- a/workflow/plugins/python/backend/backend_load_plugins/package.json +++ b/workflow/plugins/python/backend/backend_load_plugins/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "loader"], "main": "backend_load_plugins.py", + "files": ["backend_load_plugins.py", "factory.py"], "metadata": { "plugin_type": "backend.load_plugins", - "category": "backend" + "category": "backend", + "class": "LoadPlugins", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_prompt/backend_load_prompt.py b/workflow/plugins/python/backend/backend_load_prompt/backend_load_prompt.py index 5c57b1893..d73735b16 100644 --- a/workflow/plugins/python/backend/backend_load_prompt/backend_load_prompt.py +++ b/workflow/plugins/python/backend/backend_load_prompt/backend_load_prompt.py @@ -1,21 +1,31 @@ """Workflow plugin: load system prompt.""" + import os +from ...base import NodeExecutor -def run(runtime, inputs): - """Load system prompt from file. - Inputs: - path: Path to prompt file - """ - path = inputs.get("path", "config/system_prompt.txt") +class LoadPrompt(NodeExecutor): + """Load system prompt from file.""" - if not os.path.exists(path): - return {"success": False, "error": f"File not found: {path}"} + node_type = "backend.load_prompt" + category = "backend" + description = "Load system prompt from file" - with open(path) as f: - prompt = f.read() + def execute(self, inputs, runtime=None): + """Load system prompt from file. - runtime.context["system_prompt"] = prompt + Inputs: + path: Path to prompt file + """ + path = inputs.get("path", "config/system_prompt.txt") - return {"success": True, "length": len(prompt)} + if not os.path.exists(path): + return {"success": False, "error": f"File not found: {path}"} + + with open(path) as f: + prompt = f.read() + + runtime.context["system_prompt"] = prompt + + return {"success": True, "length": len(prompt)} diff --git a/workflow/plugins/python/backend/backend_load_prompt/factory.py b/workflow/plugins/python/backend/backend_load_prompt/factory.py new file mode 100644 index 000000000..adbfd084d --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_prompt/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadPrompt plugin.""" + +from .backend_load_prompt import LoadPrompt + + +def create(): + return LoadPrompt() diff --git a/workflow/plugins/python/backend/backend_load_prompt/package.json b/workflow/plugins/python/backend/backend_load_prompt/package.json index b5977db45..656b93092 100644 --- a/workflow/plugins/python/backend/backend_load_prompt/package.json +++ b/workflow/plugins/python/backend/backend_load_prompt/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "prompt", "ai"], "main": "backend_load_prompt.py", + "files": ["backend_load_prompt.py", "factory.py"], "metadata": { "plugin_type": "backend.load_prompt", - "category": "backend" + "category": "backend", + "class": "LoadPrompt", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_tool_policies/backend_load_tool_policies.py b/workflow/plugins/python/backend/backend_load_tool_policies/backend_load_tool_policies.py index a6f56714c..7083c2d42 100644 --- a/workflow/plugins/python/backend/backend_load_tool_policies/backend_load_tool_policies.py +++ b/workflow/plugins/python/backend/backend_load_tool_policies/backend_load_tool_policies.py @@ -1,24 +1,34 @@ """Workflow plugin: load tool policies.""" + import os import json +from ...base import NodeExecutor -def run(runtime, inputs): - """Load tool policies for access control. - Inputs: - path: Path to tool policies file - """ - path = inputs.get("path", "config/tool_policies.json") +class LoadToolPolicies(NodeExecutor): + """Load tool policies for access control.""" - if not os.path.exists(path): - # Default to permissive if no policies file - runtime.context["tool_policies"] = {} - return {"success": True, "policy_count": 0} + node_type = "backend.load_tool_policies" + category = "backend" + description = "Load tool policies for access control" - with open(path) as f: - policies = json.load(f) + def execute(self, inputs, runtime=None): + """Load tool policies for access control. - runtime.context["tool_policies"] = policies + Inputs: + path: Path to tool policies file + """ + path = inputs.get("path", "config/tool_policies.json") - return {"success": True, "policy_count": len(policies)} + if not os.path.exists(path): + # Default to permissive if no policies file + runtime.context["tool_policies"] = {} + return {"success": True, "policy_count": 0} + + with open(path) as f: + policies = json.load(f) + + runtime.context["tool_policies"] = policies + + return {"success": True, "policy_count": len(policies)} diff --git a/workflow/plugins/python/backend/backend_load_tool_policies/factory.py b/workflow/plugins/python/backend/backend_load_tool_policies/factory.py new file mode 100644 index 000000000..d1b625ed4 --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_tool_policies/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadToolPolicies plugin.""" + +from .backend_load_tool_policies import LoadToolPolicies + + +def create(): + return LoadToolPolicies() diff --git a/workflow/plugins/python/backend/backend_load_tool_policies/package.json b/workflow/plugins/python/backend/backend_load_tool_policies/package.json index 767169b34..6ce20619c 100644 --- a/workflow/plugins/python/backend/backend_load_tool_policies/package.json +++ b/workflow/plugins/python/backend/backend_load_tool_policies/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "tools", "policy"], "main": "backend_load_tool_policies.py", + "files": ["backend_load_tool_policies.py", "factory.py"], "metadata": { "plugin_type": "backend.load_tool_policies", - "category": "backend" + "category": "backend", + "class": "LoadToolPolicies", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_tool_registry/backend_load_tool_registry.py b/workflow/plugins/python/backend/backend_load_tool_registry/backend_load_tool_registry.py index dd6efd1c9..b4f0b99fa 100644 --- a/workflow/plugins/python/backend/backend_load_tool_registry/backend_load_tool_registry.py +++ b/workflow/plugins/python/backend/backend_load_tool_registry/backend_load_tool_registry.py @@ -1,22 +1,32 @@ """Workflow plugin: load tool registry.""" + import os import json +from ...base import NodeExecutor -def run(runtime, inputs): - """Load tool registry defining available AI tools. - Inputs: - path: Path to tool registry file - """ - path = inputs.get("path", "config/tool_registry.json") +class LoadToolRegistry(NodeExecutor): + """Load tool registry defining available AI tools.""" - if not os.path.exists(path): - return {"success": False, "error": f"File not found: {path}"} + node_type = "backend.load_tool_registry" + category = "backend" + description = "Load tool registry for AI function calling" - with open(path) as f: - registry = json.load(f) + def execute(self, inputs, runtime=None): + """Load tool registry defining available AI tools. - runtime.context["tool_registry"] = registry + Inputs: + path: Path to tool registry file + """ + path = inputs.get("path", "config/tool_registry.json") - return {"success": True, "tool_count": len(registry)} + if not os.path.exists(path): + return {"success": False, "error": f"File not found: {path}"} + + with open(path) as f: + registry = json.load(f) + + runtime.context["tool_registry"] = registry + + return {"success": True, "tool_count": len(registry)} diff --git a/workflow/plugins/python/backend/backend_load_tool_registry/factory.py b/workflow/plugins/python/backend/backend_load_tool_registry/factory.py new file mode 100644 index 000000000..f7bf1bc30 --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_tool_registry/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadToolRegistry plugin.""" + +from .backend_load_tool_registry import LoadToolRegistry + + +def create(): + return LoadToolRegistry() diff --git a/workflow/plugins/python/backend/backend_load_tool_registry/package.json b/workflow/plugins/python/backend/backend_load_tool_registry/package.json index 6a66a60a3..a0b2990fb 100644 --- a/workflow/plugins/python/backend/backend_load_tool_registry/package.json +++ b/workflow/plugins/python/backend/backend_load_tool_registry/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "tools", "registry"], "main": "backend_load_tool_registry.py", + "files": ["backend_load_tool_registry.py", "factory.py"], "metadata": { "plugin_type": "backend.load_tool_registry", - "category": "backend" + "category": "backend", + "class": "LoadToolRegistry", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_load_tools/backend_load_tools.py b/workflow/plugins/python/backend/backend_load_tools/backend_load_tools.py index c62a5c016..0d79eebf0 100644 --- a/workflow/plugins/python/backend/backend_load_tools/backend_load_tools.py +++ b/workflow/plugins/python/backend/backend_load_tools/backend_load_tools.py @@ -1,27 +1,37 @@ """Workflow plugin: load tools for AI function calling.""" + import os import json +from ...base import NodeExecutor -def run(runtime, inputs): - """Load tool definitions for AI function calling. - Inputs: - path: Path to tools definition file or directory - """ - path = inputs.get("path", "config/tools.json") +class LoadTools(NodeExecutor): + """Load tool definitions for AI function calling.""" - tools = [] + node_type = "backend.load_tools" + category = "backend" + description = "Load tool definitions for AI function calling" - if os.path.isfile(path): - with open(path) as f: - tools = json.load(f) - elif os.path.isdir(path): - for filename in os.listdir(path): - if filename.endswith(".json"): - with open(os.path.join(path, filename)) as f: - tools.extend(json.load(f)) + def execute(self, inputs, runtime=None): + """Load tool definitions for AI function calling. - runtime.context["tools"] = tools + Inputs: + path: Path to tools definition file or directory + """ + path = inputs.get("path", "config/tools.json") - return {"success": True, "tool_count": len(tools)} + tools = [] + + if os.path.isfile(path): + with open(path) as f: + tools = json.load(f) + elif os.path.isdir(path): + for filename in os.listdir(path): + if filename.endswith(".json"): + with open(os.path.join(path, filename)) as f: + tools.extend(json.load(f)) + + runtime.context["tools"] = tools + + return {"success": True, "tool_count": len(tools)} diff --git a/workflow/plugins/python/backend/backend_load_tools/factory.py b/workflow/plugins/python/backend/backend_load_tools/factory.py new file mode 100644 index 000000000..409516eea --- /dev/null +++ b/workflow/plugins/python/backend/backend_load_tools/factory.py @@ -0,0 +1,7 @@ +"""Factory for LoadTools plugin.""" + +from .backend_load_tools import LoadTools + + +def create(): + return LoadTools() diff --git a/workflow/plugins/python/backend/backend_load_tools/package.json b/workflow/plugins/python/backend/backend_load_tools/package.json index 0fb2fd3c5..15701c9ce 100644 --- a/workflow/plugins/python/backend/backend_load_tools/package.json +++ b/workflow/plugins/python/backend/backend_load_tools/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "tools", "ai"], "main": "backend_load_tools.py", + "files": ["backend_load_tools.py", "factory.py"], "metadata": { "plugin_type": "backend.load_tools", - "category": "backend" + "category": "backend", + "class": "LoadTools", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/backend/backend_parse_cli_args/backend_parse_cli_args.py b/workflow/plugins/python/backend/backend_parse_cli_args/backend_parse_cli_args.py index dafb86532..9587beff9 100644 --- a/workflow/plugins/python/backend/backend_parse_cli_args/backend_parse_cli_args.py +++ b/workflow/plugins/python/backend/backend_parse_cli_args/backend_parse_cli_args.py @@ -1,27 +1,37 @@ """Workflow plugin: parse CLI arguments.""" + import argparse +from ...base import NodeExecutor -def run(runtime, inputs): - """Parse command line arguments. - Inputs: - args: Optional list of arguments (defaults to sys.argv) - """ - parser = argparse.ArgumentParser(description="MetaBuilder Workflow") +class ParseCliArgs(NodeExecutor): + """Parse command line arguments.""" - parser.add_argument("--config", "-c", default="config.json", - help="Path to configuration file") - parser.add_argument("--workflow", "-w", - help="Path to workflow file") - parser.add_argument("--verbose", "-v", action="store_true", - help="Enable verbose output") - parser.add_argument("--dry-run", action="store_true", - help="Simulate workflow execution") + node_type = "backend.parse_cli_args" + category = "backend" + description = "Parse command line arguments" - args_list = inputs.get("args") - parsed = parser.parse_args(args_list) + def execute(self, inputs, runtime=None): + """Parse command line arguments. - runtime.context["cli_args"] = vars(parsed) + Inputs: + args: Optional list of arguments (defaults to sys.argv) + """ + parser = argparse.ArgumentParser(description="MetaBuilder Workflow") - return {"success": True, "args": vars(parsed)} + parser.add_argument("--config", "-c", default="config.json", + help="Path to configuration file") + parser.add_argument("--workflow", "-w", + help="Path to workflow file") + parser.add_argument("--verbose", "-v", action="store_true", + help="Enable verbose output") + parser.add_argument("--dry-run", action="store_true", + help="Simulate workflow execution") + + args_list = inputs.get("args") + parsed = parser.parse_args(args_list) + + runtime.context["cli_args"] = vars(parsed) + + return {"success": True, "args": vars(parsed)} diff --git a/workflow/plugins/python/backend/backend_parse_cli_args/factory.py b/workflow/plugins/python/backend/backend_parse_cli_args/factory.py new file mode 100644 index 000000000..886fc5062 --- /dev/null +++ b/workflow/plugins/python/backend/backend_parse_cli_args/factory.py @@ -0,0 +1,7 @@ +"""Factory for ParseCliArgs plugin.""" + +from .backend_parse_cli_args import ParseCliArgs + + +def create(): + return ParseCliArgs() diff --git a/workflow/plugins/python/backend/backend_parse_cli_args/package.json b/workflow/plugins/python/backend/backend_parse_cli_args/package.json index 31fff7cd1..deae65b15 100644 --- a/workflow/plugins/python/backend/backend_parse_cli_args/package.json +++ b/workflow/plugins/python/backend/backend_parse_cli_args/package.json @@ -6,8 +6,11 @@ "license": "MIT", "keywords": ["backend", "workflow", "plugin", "cli", "args"], "main": "backend_parse_cli_args.py", + "files": ["backend_parse_cli_args.py", "factory.py"], "metadata": { "plugin_type": "backend.parse_cli_args", - "category": "backend" + "category": "backend", + "class": "ParseCliArgs", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/base.py b/workflow/plugins/python/base.py new file mode 100644 index 000000000..8eb0a6301 --- /dev/null +++ b/workflow/plugins/python/base.py @@ -0,0 +1,202 @@ +""" +Base classes and types for workflow plugins. + +This module provides the class-based plugin architecture that mirrors +the TypeScript implementation for consistency across languages. +""" + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Callable +from enum import Enum +import time + + +class NodeStatus(Enum): + """Status of node execution.""" + SUCCESS = "success" + ERROR = "error" + SKIPPED = "skipped" + PENDING = "pending" + + +@dataclass +class NodeResult: + """Result of a node execution.""" + status: NodeStatus + output: Optional[Any] = None + error: Optional[str] = None + error_code: Optional[str] = None + timestamp: int = field(default_factory=lambda: int(time.time() * 1000)) + duration: Optional[int] = None + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for serialization.""" + result = { + "status": self.status.value, + "timestamp": self.timestamp, + } + if self.output is not None: + result["output"] = self.output + if self.error is not None: + result["error"] = self.error + if self.error_code is not None: + result["errorCode"] = self.error_code + if self.duration is not None: + result["duration"] = self.duration + return result + + +@dataclass +class ValidationResult: + """Result of node validation.""" + valid: bool + errors: List[str] = field(default_factory=list) + warnings: List[str] = field(default_factory=list) + + +@dataclass +class WorkflowNode: + """Workflow node definition.""" + id: str + name: str + node_type: str + parameters: Dict[str, Any] = field(default_factory=dict) + metadata: Dict[str, Any] = field(default_factory=dict) + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "WorkflowNode": + """Create from dictionary.""" + return cls( + id=data.get("id", ""), + name=data.get("name", ""), + node_type=data.get("nodeType", data.get("node_type", "")), + parameters=data.get("parameters", {}), + metadata=data.get("metadata", {}), + ) + + +@dataclass +class WorkflowContext: + """Context for workflow execution.""" + execution_id: str + tenant_id: str + user_id: str + trigger_data: Dict[str, Any] = field(default_factory=dict) + variables: Dict[str, Any] = field(default_factory=dict) + secrets: Dict[str, str] = field(default_factory=dict) + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "WorkflowContext": + """Create from dictionary.""" + return cls( + execution_id=data.get("executionId", data.get("execution_id", "")), + tenant_id=data.get("tenantId", data.get("tenant_id", "")), + user_id=data.get("userId", data.get("user_id", "")), + trigger_data=data.get("triggerData", data.get("trigger_data", {})), + variables=data.get("variables", {}), + secrets=data.get("secrets", {}), + ) + + +class ExecutionState(dict): + """State dictionary for workflow execution with variable storage.""" + + @property + def variables(self) -> Dict[str, Any]: + """Get or create variables dict.""" + if "variables" not in self: + self["variables"] = {} + return self["variables"] + + @variables.setter + def variables(self, value: Dict[str, Any]) -> None: + self["variables"] = value + + +class NodeExecutor(ABC): + """ + Abstract base class for node executors. + + All workflow plugins should inherit from this class and implement + the execute() method. The run() method provides legacy compatibility. + """ + + node_type: str = "" + category: str = "" + description: str = "" + + @abstractmethod + def execute(self, inputs: Dict[str, Any], runtime: Any = None) -> Dict[str, Any]: + """ + Execute the node logic. + + Args: + inputs: Input parameters dictionary + runtime: Optional runtime context (for advanced use) + + Returns: + Dict with 'result' key on success, or 'error' key on failure + """ + pass + + def validate(self, inputs: Dict[str, Any]) -> ValidationResult: + """ + Validate inputs. + + Override this method to add custom validation logic. + Default implementation returns valid=True. + + Args: + inputs: Input parameters to validate + + Returns: + ValidationResult with valid flag and any errors/warnings + """ + return ValidationResult(valid=True) + + def run(self, runtime: Any, inputs: Dict[str, Any]) -> Dict[str, Any]: + """ + Legacy interface - calls execute(). + + Args: + runtime: Runtime context (passed through) + inputs: Input parameters + + Returns: + Dict with result or error + """ + return self.execute(inputs, runtime) + + def _resolve(self, value: Any, inputs: Dict[str, Any]) -> Any: + """ + Resolve template expressions in values. + + Handles {{variable}} syntax for dynamic values. + """ + if isinstance(value, str) and value.startswith("{{") and value.endswith("}}"): + expr = value[2:-2].strip() + return self._get_nested(inputs, expr) + return value + + def _get_nested(self, data: Dict[str, Any], path: str) -> Any: + """Get nested value from dict using dot notation.""" + parts = path.split(".") + current = data + + for part in parts: + if isinstance(current, dict): + current = current.get(part) + elif hasattr(current, part): + current = getattr(current, part) + else: + return None + + if current is None: + return None + + return current + + + + diff --git a/workflow/plugins/python/control/control_get_bot_status/control_get_bot_status.py b/workflow/plugins/python/control/control_get_bot_status/control_get_bot_status.py index 986bbf976..10fb9a81f 100644 --- a/workflow/plugins/python/control/control_get_bot_status/control_get_bot_status.py +++ b/workflow/plugins/python/control/control_get_bot_status/control_get_bot_status.py @@ -1,5 +1,7 @@ """Workflow plugin: get current bot execution status.""" +from ...base import NodeExecutor + # Global state for bot process _bot_process = None _mock_running = False @@ -27,13 +29,20 @@ def reset_bot_state(): _mock_running = False -def run(_runtime, _inputs): - """Get current bot execution status. +class ControlGetBotStatus(NodeExecutor): + """Get current bot execution status.""" - Returns: - Dictionary with: - - is_running: bool - Whether the bot is currently running - - config: dict - Current run configuration (empty if not running) - - process: object - Bot process object (or None if not running) - """ - return get_bot_state() + node_type = "control.get_bot_status" + category = "control" + description = "Get current bot execution status" + + def execute(self, inputs, runtime=None): + """Get current bot execution status. + + Returns: + Dictionary with: + - is_running: bool - Whether the bot is currently running + - config: dict - Current run configuration (empty if not running) + - process: object - Bot process object (or None if not running) + """ + return get_bot_state() diff --git a/workflow/plugins/python/control/control_get_bot_status/factory.py b/workflow/plugins/python/control/control_get_bot_status/factory.py new file mode 100644 index 000000000..826f83fe2 --- /dev/null +++ b/workflow/plugins/python/control/control_get_bot_status/factory.py @@ -0,0 +1,7 @@ +"""Factory for ControlGetBotStatus plugin.""" + +from .control_get_bot_status import ControlGetBotStatus + + +def create(): + return ControlGetBotStatus() diff --git a/workflow/plugins/python/control/control_get_bot_status/package.json b/workflow/plugins/python/control/control_get_bot_status/package.json index 488b493d8..a59375189 100644 --- a/workflow/plugins/python/control/control_get_bot_status/package.json +++ b/workflow/plugins/python/control/control_get_bot_status/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/control_get_bot_status", "version": "1.0.0", - "description": "control_get_bot_status plugin", + "description": "Get current bot execution status", "author": "MetaBuilder", "license": "MIT", "keywords": ["control", "workflow", "plugin"], "main": "control_get_bot_status.py", + "files": ["control_get_bot_status.py", "factory.py"], "metadata": { "plugin_type": "control.get_bot_status", - "category": "control" + "category": "control", + "class": "ControlGetBotStatus", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/control/control_reset_bot_state/control_reset_bot_state.py b/workflow/plugins/python/control/control_reset_bot_state/control_reset_bot_state.py index b989155bf..e856bf3e7 100644 --- a/workflow/plugins/python/control/control_reset_bot_state/control_reset_bot_state.py +++ b/workflow/plugins/python/control/control_reset_bot_state/control_reset_bot_state.py @@ -1,13 +1,22 @@ """Workflow plugin: reset bot execution state.""" -from .control_get_bot_status import reset_bot_state + +from ...base import NodeExecutor +from ..control_get_bot_status.control_get_bot_status import reset_bot_state -def run(_runtime, _inputs): - """Reset bot execution state. +class ControlResetBotState(NodeExecutor): + """Reset bot execution state.""" - Returns: - Dictionary with: - - reset: bool - Always True to indicate state was reset - """ - reset_bot_state() - return {"reset": True} + node_type = "control.reset_bot_state" + category = "control" + description = "Reset bot execution state" + + def execute(self, inputs, runtime=None): + """Reset bot execution state. + + Returns: + Dictionary with: + - reset: bool - Always True to indicate state was reset + """ + reset_bot_state() + return {"reset": True} diff --git a/workflow/plugins/python/control/control_reset_bot_state/factory.py b/workflow/plugins/python/control/control_reset_bot_state/factory.py new file mode 100644 index 000000000..f787a1984 --- /dev/null +++ b/workflow/plugins/python/control/control_reset_bot_state/factory.py @@ -0,0 +1,7 @@ +"""Factory for ControlResetBotState plugin.""" + +from .control_reset_bot_state import ControlResetBotState + + +def create(): + return ControlResetBotState() diff --git a/workflow/plugins/python/control/control_reset_bot_state/package.json b/workflow/plugins/python/control/control_reset_bot_state/package.json index cb4727105..f1dba0eb9 100644 --- a/workflow/plugins/python/control/control_reset_bot_state/package.json +++ b/workflow/plugins/python/control/control_reset_bot_state/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/control_reset_bot_state", "version": "1.0.0", - "description": "control_reset_bot_state plugin", + "description": "Reset bot execution state", "author": "MetaBuilder", "license": "MIT", "keywords": ["control", "workflow", "plugin"], "main": "control_reset_bot_state.py", + "files": ["control_reset_bot_state.py", "factory.py"], "metadata": { "plugin_type": "control.reset_bot_state", - "category": "control" + "category": "control", + "class": "ControlResetBotState", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/control/control_start_bot/control_start_bot.py b/workflow/plugins/python/control/control_start_bot/control_start_bot.py index 06ebfcf0d..d0ec7f46e 100644 --- a/workflow/plugins/python/control/control_start_bot/control_start_bot.py +++ b/workflow/plugins/python/control/control_start_bot/control_start_bot.py @@ -1,20 +1,19 @@ """Workflow plugin: start bot execution in background thread.""" + import os import subprocess import sys import threading import time -from .control_get_bot_status import ( +from ...base import NodeExecutor +from ..control_get_bot_status.control_get_bot_status import ( get_bot_state, reset_bot_state, - _bot_process, - _mock_running, - _current_run_config ) # Import global state -import workflow.plugins.python.control.control_get_bot_status as bot_status +import workflow.plugins.python.control.control_get_bot_status.control_get_bot_status as bot_status def _run_bot_task(mode: str, iterations: int, yolo: bool, stop_at_mvp: bool) -> None: @@ -61,37 +60,44 @@ def _run_bot_task(mode: str, iterations: int, yolo: bool, stop_at_mvp: bool) -> reset_bot_state() -def run(_runtime, inputs): - """Start bot execution in background thread. +class ControlStartBot(NodeExecutor): + """Start bot execution in background thread.""" - Args: - inputs: Dictionary with keys: - - mode: str (default: "once") - Execution mode ("once", "iterations", etc.) - - iterations: int (default: 1) - Number of iterations for "iterations" mode - - yolo: bool (default: True) - Run in YOLO mode - - stop_at_mvp: bool (default: False) - Stop when MVP is reached + node_type = "control.start_bot" + category = "control" + description = "Start bot execution in background thread" - Returns: - Dictionary with: - - started: bool - Whether the bot was started successfully - - error: str (optional) - Error message if bot is already running - """ - mode = inputs.get("mode", "once") - iterations = inputs.get("iterations", 1) - yolo = inputs.get("yolo", True) - stop_at_mvp = inputs.get("stop_at_mvp", False) + def execute(self, inputs, runtime=None): + """Start bot execution in background thread. - # Check if bot is already running - state = get_bot_state() - if state["is_running"]: - return {"started": False, "error": "Bot already running"} + Args: + inputs: Dictionary with keys: + - mode: str (default: "once") - Execution mode ("once", "iterations", etc.) + - iterations: int (default: 1) - Number of iterations for "iterations" mode + - yolo: bool (default: True) - Run in YOLO mode + - stop_at_mvp: bool (default: False) - Stop when MVP is reached - # Start bot in background thread - thread = threading.Thread( - target=_run_bot_task, - args=(mode, iterations, yolo, stop_at_mvp), - daemon=True - ) - thread.start() + Returns: + Dictionary with: + - started: bool - Whether the bot was started successfully + - error: str (optional) - Error message if bot is already running + """ + mode = inputs.get("mode", "once") + iterations = inputs.get("iterations", 1) + yolo = inputs.get("yolo", True) + stop_at_mvp = inputs.get("stop_at_mvp", False) - return {"started": True} + # Check if bot is already running + state = get_bot_state() + if state["is_running"]: + return {"started": False, "error": "Bot already running"} + + # Start bot in background thread + thread = threading.Thread( + target=_run_bot_task, + args=(mode, iterations, yolo, stop_at_mvp), + daemon=True + ) + thread.start() + + return {"started": True} diff --git a/workflow/plugins/python/control/control_start_bot/factory.py b/workflow/plugins/python/control/control_start_bot/factory.py new file mode 100644 index 000000000..27563d89c --- /dev/null +++ b/workflow/plugins/python/control/control_start_bot/factory.py @@ -0,0 +1,7 @@ +"""Factory for ControlStartBot plugin.""" + +from .control_start_bot import ControlStartBot + + +def create(): + return ControlStartBot() diff --git a/workflow/plugins/python/control/control_start_bot/package.json b/workflow/plugins/python/control/control_start_bot/package.json index a7c40c7a1..e4d5f61b1 100644 --- a/workflow/plugins/python/control/control_start_bot/package.json +++ b/workflow/plugins/python/control/control_start_bot/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/control_start_bot", "version": "1.0.0", - "description": "control_start_bot plugin", + "description": "Start bot execution in background thread", "author": "MetaBuilder", "license": "MIT", "keywords": ["control", "workflow", "plugin"], "main": "control_start_bot.py", + "files": ["control_start_bot.py", "factory.py"], "metadata": { "plugin_type": "control.start_bot", - "category": "control" + "category": "control", + "class": "ControlStartBot", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/control/control_switch/control_switch.py b/workflow/plugins/python/control/control_switch/control_switch.py index de1a4ae6e..fd557e6f5 100644 --- a/workflow/plugins/python/control/control_switch/control_switch.py +++ b/workflow/plugins/python/control/control_switch/control_switch.py @@ -1,11 +1,32 @@ """Workflow plugin: switch/case control flow.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ControlSwitch(NodeExecutor): """Switch on value and return matching case.""" - value = inputs.get("value") - cases = inputs.get("cases", {}) - default = inputs.get("default") - result = cases.get(str(value), default) - return {"result": result, "matched": str(value) in cases} + node_type = "control.switch" + category = "control" + description = "Switch/case control flow" + + def execute(self, inputs, runtime=None): + """Switch on value and return matching case. + + Args: + inputs: Dictionary with keys: + - value: The value to switch on + - cases: dict - Map of case values to results + - default: Default value if no case matches + + Returns: + Dictionary with: + - result: The matched case value or default + - matched: bool - Whether a case was matched + """ + value = inputs.get("value") + cases = inputs.get("cases", {}) + default = inputs.get("default") + + result = cases.get(str(value), default) + return {"result": result, "matched": str(value) in cases} diff --git a/workflow/plugins/python/control/control_switch/factory.py b/workflow/plugins/python/control/control_switch/factory.py new file mode 100644 index 000000000..4fa16765b --- /dev/null +++ b/workflow/plugins/python/control/control_switch/factory.py @@ -0,0 +1,7 @@ +"""Factory for ControlSwitch plugin.""" + +from .control_switch import ControlSwitch + + +def create(): + return ControlSwitch() diff --git a/workflow/plugins/python/control/control_switch/package.json b/workflow/plugins/python/control/control_switch/package.json index 471faa761..4bf8b3718 100644 --- a/workflow/plugins/python/control/control_switch/package.json +++ b/workflow/plugins/python/control/control_switch/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/control_switch", "version": "1.0.0", - "description": "control_switch plugin", + "description": "Switch/case control flow", "author": "MetaBuilder", "license": "MIT", "keywords": ["control", "workflow", "plugin"], "main": "control_switch.py", + "files": ["control_switch.py", "factory.py"], "metadata": { "plugin_type": "control.switch", - "category": "control" + "category": "control", + "class": "ControlSwitch", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/convert/convert_parse_json/convert_parse_json.py b/workflow/plugins/python/convert/convert_parse_json/convert_parse_json.py index 1065af5e5..6fd368e63 100644 --- a/workflow/plugins/python/convert/convert_parse_json/convert_parse_json.py +++ b/workflow/plugins/python/convert/convert_parse_json/convert_parse_json.py @@ -1,13 +1,21 @@ """Workflow plugin: parse JSON string.""" + import json +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ConvertParseJson(NodeExecutor): """Parse JSON string to object.""" - text = inputs.get("text", "") - try: - result = json.loads(text) - return {"result": result} - except json.JSONDecodeError as e: - return {"result": None, "error": str(e)} + node_type = "convert.parseJson" + category = "convert" + description = "Parse JSON string to object" + + def execute(self, inputs, runtime=None): + text = inputs.get("text", "") + try: + result = json.loads(text) + return {"result": result} + except json.JSONDecodeError as e: + return {"result": None, "error": str(e)} diff --git a/workflow/plugins/python/convert/convert_parse_json/factory.py b/workflow/plugins/python/convert/convert_parse_json/factory.py new file mode 100644 index 000000000..2c3a9eba8 --- /dev/null +++ b/workflow/plugins/python/convert/convert_parse_json/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConvertParseJson plugin.""" + +from .convert_parse_json import ConvertParseJson + + +def create(): + return ConvertParseJson() diff --git a/workflow/plugins/python/convert/convert_parse_json/package.json b/workflow/plugins/python/convert/convert_parse_json/package.json index 6bd4c3219..782e6ed7b 100644 --- a/workflow/plugins/python/convert/convert_parse_json/package.json +++ b/workflow/plugins/python/convert/convert_parse_json/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/convert_parse_json", "version": "1.0.0", - "description": "convert_parse_json plugin", + "description": "Parse JSON string to object", "author": "MetaBuilder", "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_parse_json.py", + "files": ["convert_parse_json.py", "factory.py"], "metadata": { - "plugin_type": "convert.parse_json", - "category": "convert" + "plugin_type": "convert.parseJson", + "category": "convert", + "class": "ConvertParseJson", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/convert/convert_to_boolean/convert_to_boolean.py b/workflow/plugins/python/convert/convert_to_boolean/convert_to_boolean.py index f6ca9e374..8a1667122 100644 --- a/workflow/plugins/python/convert/convert_to_boolean/convert_to_boolean.py +++ b/workflow/plugins/python/convert/convert_to_boolean/convert_to_boolean.py @@ -1,11 +1,17 @@ """Workflow plugin: convert to boolean.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ConvertToBoolean(NodeExecutor): """Convert value to boolean.""" - value = inputs.get("value") - if isinstance(value, str): - return {"result": value.lower() not in ("false", "0", "", "none", "null")} + node_type = "convert.toBoolean" + category = "convert" + description = "Convert value to boolean" - return {"result": bool(value)} + def execute(self, inputs, runtime=None): + value = inputs.get("value") + if isinstance(value, str): + return {"result": value.lower() not in ("false", "0", "", "none", "null")} + return {"result": bool(value)} diff --git a/workflow/plugins/python/convert/convert_to_boolean/factory.py b/workflow/plugins/python/convert/convert_to_boolean/factory.py new file mode 100644 index 000000000..ec5d77d86 --- /dev/null +++ b/workflow/plugins/python/convert/convert_to_boolean/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConvertToBoolean plugin.""" + +from .convert_to_boolean import ConvertToBoolean + + +def create(): + return ConvertToBoolean() diff --git a/workflow/plugins/python/convert/convert_to_boolean/package.json b/workflow/plugins/python/convert/convert_to_boolean/package.json index dd93b3c8e..480e5171e 100644 --- a/workflow/plugins/python/convert/convert_to_boolean/package.json +++ b/workflow/plugins/python/convert/convert_to_boolean/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/convert_to_boolean", "version": "1.0.0", - "description": "convert_to_boolean plugin", + "description": "Convert value to boolean", "author": "MetaBuilder", "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_boolean.py", + "files": ["convert_to_boolean.py", "factory.py"], "metadata": { - "plugin_type": "convert.to_boolean", - "category": "convert" + "plugin_type": "convert.toBoolean", + "category": "convert", + "class": "ConvertToBoolean", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/convert/convert_to_dict/convert_to_dict.py b/workflow/plugins/python/convert/convert_to_dict/convert_to_dict.py index d62399cec..b5e4be91e 100644 --- a/workflow/plugins/python/convert/convert_to_dict/convert_to_dict.py +++ b/workflow/plugins/python/convert/convert_to_dict/convert_to_dict.py @@ -1,17 +1,25 @@ """Workflow plugin: convert to dictionary.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ConvertToDict(NodeExecutor): """Convert value to dictionary.""" - value = inputs.get("value") - if isinstance(value, dict): - return {"result": value} - elif isinstance(value, list): - # Convert list of [key, value] pairs to dict - try: - return {"result": dict(value)} - except (TypeError, ValueError): - return {"result": {}, "error": "Cannot convert list to dict"} - else: - return {"result": {}} + node_type = "convert.toDict" + category = "convert" + description = "Convert value to dictionary" + + def execute(self, inputs, runtime=None): + value = inputs.get("value") + + if isinstance(value, dict): + return {"result": value} + elif isinstance(value, list): + # Convert list of [key, value] pairs to dict + try: + return {"result": dict(value)} + except (TypeError, ValueError): + return {"result": {}, "error": "Cannot convert list to dict"} + else: + return {"result": {}} diff --git a/workflow/plugins/python/convert/convert_to_dict/factory.py b/workflow/plugins/python/convert/convert_to_dict/factory.py new file mode 100644 index 000000000..12c163cd2 --- /dev/null +++ b/workflow/plugins/python/convert/convert_to_dict/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConvertToDict plugin.""" + +from .convert_to_dict import ConvertToDict + + +def create(): + return ConvertToDict() diff --git a/workflow/plugins/python/convert/convert_to_dict/package.json b/workflow/plugins/python/convert/convert_to_dict/package.json index 3f15a412b..bd2a71f9f 100644 --- a/workflow/plugins/python/convert/convert_to_dict/package.json +++ b/workflow/plugins/python/convert/convert_to_dict/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/convert_to_dict", "version": "1.0.0", - "description": "convert_to_dict plugin", + "description": "Convert value to dictionary", "author": "MetaBuilder", "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_dict.py", + "files": ["convert_to_dict.py", "factory.py"], "metadata": { - "plugin_type": "convert.to_dict", - "category": "convert" + "plugin_type": "convert.toDict", + "category": "convert", + "class": "ConvertToDict", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/convert/convert_to_json/convert_to_json.py b/workflow/plugins/python/convert/convert_to_json/convert_to_json.py index 77bb87a2f..e252b4104 100644 --- a/workflow/plugins/python/convert/convert_to_json/convert_to_json.py +++ b/workflow/plugins/python/convert/convert_to_json/convert_to_json.py @@ -1,14 +1,23 @@ """Workflow plugin: convert to JSON string.""" + import json +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ConvertToJson(NodeExecutor): """Convert value to JSON string.""" - value = inputs.get("value") - indent = inputs.get("indent") - try: - result = json.dumps(value, indent=indent) - return {"result": result} - except (TypeError, ValueError) as e: - return {"result": None, "error": str(e)} + node_type = "convert.toJson" + category = "convert" + description = "Convert value to JSON string" + + def execute(self, inputs, runtime=None): + value = inputs.get("value") + indent = inputs.get("indent") + + try: + result = json.dumps(value, indent=indent) + return {"result": result} + except (TypeError, ValueError) as e: + return {"result": None, "error": str(e)} diff --git a/workflow/plugins/python/convert/convert_to_json/factory.py b/workflow/plugins/python/convert/convert_to_json/factory.py new file mode 100644 index 000000000..c8189cfcc --- /dev/null +++ b/workflow/plugins/python/convert/convert_to_json/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConvertToJson plugin.""" + +from .convert_to_json import ConvertToJson + + +def create(): + return ConvertToJson() diff --git a/workflow/plugins/python/convert/convert_to_json/package.json b/workflow/plugins/python/convert/convert_to_json/package.json index 90ad24fab..1f81f9f05 100644 --- a/workflow/plugins/python/convert/convert_to_json/package.json +++ b/workflow/plugins/python/convert/convert_to_json/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/convert_to_json", "version": "1.0.0", - "description": "convert_to_json plugin", + "description": "Convert value to JSON string", "author": "MetaBuilder", "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_json.py", + "files": ["convert_to_json.py", "factory.py"], "metadata": { - "plugin_type": "convert.to_json", - "category": "convert" + "plugin_type": "convert.toJson", + "category": "convert", + "class": "ConvertToJson", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/convert/convert_to_list/convert_to_list.py b/workflow/plugins/python/convert/convert_to_list/convert_to_list.py index f68eafee9..055393ee6 100644 --- a/workflow/plugins/python/convert/convert_to_list/convert_to_list.py +++ b/workflow/plugins/python/convert/convert_to_list/convert_to_list.py @@ -1,17 +1,25 @@ """Workflow plugin: convert to list.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ConvertToList(NodeExecutor): """Convert value to list.""" - value = inputs.get("value") - if isinstance(value, list): - return {"result": value} - elif isinstance(value, (tuple, set)): - return {"result": list(value)} - elif isinstance(value, dict): - return {"result": list(value.items())} - elif value is None: - return {"result": []} - else: - return {"result": [value]} + node_type = "convert.toList" + category = "convert" + description = "Convert value to list" + + def execute(self, inputs, runtime=None): + value = inputs.get("value") + + if isinstance(value, list): + return {"result": value} + elif isinstance(value, (tuple, set)): + return {"result": list(value)} + elif isinstance(value, dict): + return {"result": list(value.items())} + elif value is None: + return {"result": []} + else: + return {"result": [value]} diff --git a/workflow/plugins/python/convert/convert_to_list/factory.py b/workflow/plugins/python/convert/convert_to_list/factory.py new file mode 100644 index 000000000..75c4fddc0 --- /dev/null +++ b/workflow/plugins/python/convert/convert_to_list/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConvertToList plugin.""" + +from .convert_to_list import ConvertToList + + +def create(): + return ConvertToList() diff --git a/workflow/plugins/python/convert/convert_to_list/package.json b/workflow/plugins/python/convert/convert_to_list/package.json index 944c1e1a6..1f7bfeb3c 100644 --- a/workflow/plugins/python/convert/convert_to_list/package.json +++ b/workflow/plugins/python/convert/convert_to_list/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/convert_to_list", "version": "1.0.0", - "description": "convert_to_list plugin", + "description": "Convert value to list", "author": "MetaBuilder", "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_list.py", + "files": ["convert_to_list.py", "factory.py"], "metadata": { - "plugin_type": "convert.to_list", - "category": "convert" + "plugin_type": "convert.toList", + "category": "convert", + "class": "ConvertToList", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/convert/convert_to_number/convert_to_number.py b/workflow/plugins/python/convert/convert_to_number/convert_to_number.py index 40ab59373..e2c04c0be 100644 --- a/workflow/plugins/python/convert/convert_to_number/convert_to_number.py +++ b/workflow/plugins/python/convert/convert_to_number/convert_to_number.py @@ -1,14 +1,21 @@ """Workflow plugin: convert to number.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ConvertToNumber(NodeExecutor): """Convert value to number.""" - value = inputs.get("value") - default = inputs.get("default", 0) - try: - if isinstance(value, str) and "." in value: - return {"result": float(value)} - return {"result": int(value)} - except (ValueError, TypeError): - return {"result": default, "error": "Cannot convert to number"} + node_type = "convert.toNumber" + category = "convert" + description = "Convert value to number" + + def execute(self, inputs, runtime=None): + value = inputs.get("value") + default = inputs.get("default", 0) + try: + if isinstance(value, str) and "." in value: + return {"result": float(value)} + return {"result": int(value)} + except (ValueError, TypeError): + return {"result": default, "error": "Cannot convert to number"} diff --git a/workflow/plugins/python/convert/convert_to_number/factory.py b/workflow/plugins/python/convert/convert_to_number/factory.py new file mode 100644 index 000000000..99dcb1a59 --- /dev/null +++ b/workflow/plugins/python/convert/convert_to_number/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConvertToNumber plugin.""" + +from .convert_to_number import ConvertToNumber + + +def create(): + return ConvertToNumber() diff --git a/workflow/plugins/python/convert/convert_to_number/package.json b/workflow/plugins/python/convert/convert_to_number/package.json index 553a5d8ec..cfe5da1f9 100644 --- a/workflow/plugins/python/convert/convert_to_number/package.json +++ b/workflow/plugins/python/convert/convert_to_number/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/convert_to_number", "version": "1.0.0", - "description": "convert_to_number plugin", + "description": "Convert value to number", "author": "MetaBuilder", "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_number.py", + "files": ["convert_to_number.py", "factory.py"], "metadata": { - "plugin_type": "convert.to_number", - "category": "convert" + "plugin_type": "convert.toNumber", + "category": "convert", + "class": "ConvertToNumber", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/convert/convert_to_string/convert_to_string.py b/workflow/plugins/python/convert/convert_to_string/convert_to_string.py index 4ce4123ae..6d23a5902 100644 --- a/workflow/plugins/python/convert/convert_to_string/convert_to_string.py +++ b/workflow/plugins/python/convert/convert_to_string/convert_to_string.py @@ -1,7 +1,15 @@ """Workflow plugin: convert to string.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ConvertToString(NodeExecutor): """Convert value to string.""" - value = inputs.get("value") - return {"result": str(value) if value is not None else ""} + + node_type = "convert.toString" + category = "convert" + description = "Convert value to string" + + def execute(self, inputs, runtime=None): + value = inputs.get("value") + return {"result": str(value) if value is not None else ""} diff --git a/workflow/plugins/python/convert/convert_to_string/factory.py b/workflow/plugins/python/convert/convert_to_string/factory.py new file mode 100644 index 000000000..40525cf2b --- /dev/null +++ b/workflow/plugins/python/convert/convert_to_string/factory.py @@ -0,0 +1,7 @@ +"""Factory for ConvertToString plugin.""" + +from .convert_to_string import ConvertToString + + +def create(): + return ConvertToString() diff --git a/workflow/plugins/python/convert/convert_to_string/package.json b/workflow/plugins/python/convert/convert_to_string/package.json index d5619e8e4..37052adb5 100644 --- a/workflow/plugins/python/convert/convert_to_string/package.json +++ b/workflow/plugins/python/convert/convert_to_string/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/convert_to_string", "version": "1.0.0", - "description": "convert_to_string plugin", + "description": "Convert value to string", "author": "MetaBuilder", "license": "MIT", "keywords": ["convert", "workflow", "plugin"], "main": "convert_to_string.py", + "files": ["convert_to_string.py", "factory.py"], "metadata": { - "plugin_type": "convert.to_string", - "category": "convert" + "plugin_type": "convert.toString", + "category": "convert", + "class": "ConvertToString", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/core/core_ai_request/core_ai_request.py b/workflow/plugins/python/core/core_ai_request/core_ai_request.py index face1e977..ef18450b9 100644 --- a/workflow/plugins/python/core/core_ai_request/core_ai_request.py +++ b/workflow/plugins/python/core/core_ai_request/core_ai_request.py @@ -1,6 +1,9 @@ """Workflow plugin: AI request.""" + from tenacity import retry, stop_after_attempt, wait_exponential +from ...base import NodeExecutor + @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)) def _get_completion(client, model, messages, tools): @@ -15,25 +18,32 @@ def _get_completion(client, model, messages, tools): ) -def run(runtime, inputs): - """Invoke the model with current messages.""" - messages = list(inputs.get("messages") or []) - response = _get_completion( - runtime.context["client"], - runtime.context["model_name"], - messages, - runtime.context["tools"] - ) - resp_msg = response.choices[0].message - runtime.logger.info( - resp_msg.content - if resp_msg.content - else runtime.context["msgs"]["info_tool_call_requested"] - ) - messages.append(resp_msg) - tool_calls = getattr(resp_msg, "tool_calls", None) or [] - return { - "response": resp_msg, - "has_tool_calls": bool(tool_calls), - "tool_calls_count": len(tool_calls) - } +class CoreAiRequest(NodeExecutor): + """Invoke the AI model with current messages.""" + + node_type = "core.ai_request" + category = "core" + description = "Invoke the AI model with current messages and return the response" + + def execute(self, inputs, runtime=None): + """Invoke the model with current messages.""" + messages = list(inputs.get("messages") or []) + response = _get_completion( + runtime.context["client"], + runtime.context["model_name"], + messages, + runtime.context["tools"] + ) + resp_msg = response.choices[0].message + runtime.logger.info( + resp_msg.content + if resp_msg.content + else runtime.context["msgs"]["info_tool_call_requested"] + ) + messages.append(resp_msg) + tool_calls = getattr(resp_msg, "tool_calls", None) or [] + return { + "response": resp_msg, + "has_tool_calls": bool(tool_calls), + "tool_calls_count": len(tool_calls) + } diff --git a/workflow/plugins/python/core/core_ai_request/factory.py b/workflow/plugins/python/core/core_ai_request/factory.py new file mode 100644 index 000000000..a4a81920b --- /dev/null +++ b/workflow/plugins/python/core/core_ai_request/factory.py @@ -0,0 +1,8 @@ +"""Factory for CoreAiRequest plugin.""" + +from .core_ai_request import CoreAiRequest + + +def create(): + """Create a new CoreAiRequest instance.""" + return CoreAiRequest() diff --git a/workflow/plugins/python/core/core_ai_request/package.json b/workflow/plugins/python/core/core_ai_request/package.json index 8d2d4b99c..618b293ee 100644 --- a/workflow/plugins/python/core/core_ai_request/package.json +++ b/workflow/plugins/python/core/core_ai_request/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/core_ai_request", "version": "1.0.0", - "description": "core_ai_request plugin", + "description": "Invoke the AI model with current messages and return the response", "author": "MetaBuilder", "license": "MIT", "keywords": ["core", "workflow", "plugin"], "main": "core_ai_request.py", + "files": ["core_ai_request.py", "factory.py"], "metadata": { "plugin_type": "core.ai_request", - "category": "core" + "category": "core", + "class": "CoreAiRequest", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/core/core_append_context_message/core_append_context_message.py b/workflow/plugins/python/core/core_append_context_message/core_append_context_message.py index cf5314d3b..cbd5d66b3 100644 --- a/workflow/plugins/python/core/core_append_context_message/core_append_context_message.py +++ b/workflow/plugins/python/core/core_append_context_message/core_append_context_message.py @@ -1,13 +1,22 @@ """Workflow plugin: append context message.""" +from ...base import NodeExecutor -def run(runtime, inputs): + +class CoreAppendContextMessage(NodeExecutor): """Append context to the message list.""" - messages = list(inputs.get("messages") or []) - context_val = inputs.get("context") - if context_val: - messages.append({ - "role": "system", - "content": f"{runtime.context['msgs']['sdlc_context_label']}{context_val}", - }) - return {"messages": messages} + + node_type = "core.append_context_message" + category = "core" + description = "Append context information to the message list as a system message" + + def execute(self, inputs, runtime=None): + """Append context to the message list.""" + messages = list(inputs.get("messages") or []) + context_val = inputs.get("context") + if context_val: + messages.append({ + "role": "system", + "content": f"{runtime.context['msgs']['sdlc_context_label']}{context_val}", + }) + return {"messages": messages} diff --git a/workflow/plugins/python/core/core_append_context_message/factory.py b/workflow/plugins/python/core/core_append_context_message/factory.py new file mode 100644 index 000000000..40b4ece68 --- /dev/null +++ b/workflow/plugins/python/core/core_append_context_message/factory.py @@ -0,0 +1,8 @@ +"""Factory for CoreAppendContextMessage plugin.""" + +from .core_append_context_message import CoreAppendContextMessage + + +def create(): + """Create a new CoreAppendContextMessage instance.""" + return CoreAppendContextMessage() diff --git a/workflow/plugins/python/core/core_append_context_message/package.json b/workflow/plugins/python/core/core_append_context_message/package.json index ea428098d..c9226f56b 100644 --- a/workflow/plugins/python/core/core_append_context_message/package.json +++ b/workflow/plugins/python/core/core_append_context_message/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/core_append_context_message", "version": "1.0.0", - "description": "core_append_context_message plugin", + "description": "Append context information to the message list as a system message", "author": "MetaBuilder", "license": "MIT", "keywords": ["core", "workflow", "plugin"], "main": "core_append_context_message.py", + "files": ["core_append_context_message.py", "factory.py"], "metadata": { "plugin_type": "core.append_context_message", - "category": "core" + "category": "core", + "class": "CoreAppendContextMessage", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/core/core_append_tool_results/core_append_tool_results.py b/workflow/plugins/python/core/core_append_tool_results/core_append_tool_results.py index f572aab27..cfc772f02 100644 --- a/workflow/plugins/python/core/core_append_tool_results/core_append_tool_results.py +++ b/workflow/plugins/python/core/core_append_tool_results/core_append_tool_results.py @@ -1,7 +1,10 @@ """Workflow plugin: append tool results.""" + import os import re +from ...base import NodeExecutor + def _is_mvp_reached() -> bool: """Check if the MVP section in ROADMAP.md is completed.""" @@ -31,14 +34,21 @@ def _is_mvp_reached() -> bool: return False -def run(runtime, inputs): +class CoreAppendToolResults(NodeExecutor): """Append tool results to the message list.""" - messages = list(inputs.get("messages") or []) - tool_results = inputs.get("tool_results") or [] - if tool_results: - messages.extend(tool_results) - if runtime.context.get("args", {}).get("yolo") and _is_mvp_reached(): - runtime.logger.info("MVP reached. Stopping YOLO loop.") + node_type = "core.append_tool_results" + category = "core" + description = "Append tool execution results to the message list" - return {"messages": messages} + def execute(self, inputs, runtime=None): + """Append tool results to the message list.""" + messages = list(inputs.get("messages") or []) + tool_results = inputs.get("tool_results") or [] + if tool_results: + messages.extend(tool_results) + + if runtime.context.get("args", {}).get("yolo") and _is_mvp_reached(): + runtime.logger.info("MVP reached. Stopping YOLO loop.") + + return {"messages": messages} diff --git a/workflow/plugins/python/core/core_append_tool_results/factory.py b/workflow/plugins/python/core/core_append_tool_results/factory.py new file mode 100644 index 000000000..4ce91b8a1 --- /dev/null +++ b/workflow/plugins/python/core/core_append_tool_results/factory.py @@ -0,0 +1,8 @@ +"""Factory for CoreAppendToolResults plugin.""" + +from .core_append_tool_results import CoreAppendToolResults + + +def create(): + """Create a new CoreAppendToolResults instance.""" + return CoreAppendToolResults() diff --git a/workflow/plugins/python/core/core_append_tool_results/package.json b/workflow/plugins/python/core/core_append_tool_results/package.json index 3f481668b..0cf2b8bda 100644 --- a/workflow/plugins/python/core/core_append_tool_results/package.json +++ b/workflow/plugins/python/core/core_append_tool_results/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/core_append_tool_results", "version": "1.0.0", - "description": "core_append_tool_results plugin", + "description": "Append tool execution results to the message list", "author": "MetaBuilder", "license": "MIT", "keywords": ["core", "workflow", "plugin"], "main": "core_append_tool_results.py", + "files": ["core_append_tool_results.py", "factory.py"], "metadata": { "plugin_type": "core.append_tool_results", - "category": "core" + "category": "core", + "class": "CoreAppendToolResults", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/core/core_append_user_instruction/core_append_user_instruction.py b/workflow/plugins/python/core/core_append_user_instruction/core_append_user_instruction.py index 556249c7b..5ab30588d 100644 --- a/workflow/plugins/python/core/core_append_user_instruction/core_append_user_instruction.py +++ b/workflow/plugins/python/core/core_append_user_instruction/core_append_user_instruction.py @@ -1,8 +1,17 @@ """Workflow plugin: append user instruction.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Append the next user instruction.""" - messages = list(inputs.get("messages") or []) - messages.append({"role": "user", "content": runtime.context["msgs"]["user_next_step"]}) - return {"messages": messages} + +class CoreAppendUserInstruction(NodeExecutor): + """Append the next user instruction to the message list.""" + + node_type = "core.append_user_instruction" + category = "core" + description = "Append the next user instruction to the message list" + + def execute(self, inputs, runtime=None): + """Append the next user instruction.""" + messages = list(inputs.get("messages") or []) + messages.append({"role": "user", "content": runtime.context["msgs"]["user_next_step"]}) + return {"messages": messages} diff --git a/workflow/plugins/python/core/core_append_user_instruction/factory.py b/workflow/plugins/python/core/core_append_user_instruction/factory.py new file mode 100644 index 000000000..eb5df9c8f --- /dev/null +++ b/workflow/plugins/python/core/core_append_user_instruction/factory.py @@ -0,0 +1,8 @@ +"""Factory for CoreAppendUserInstruction plugin.""" + +from .core_append_user_instruction import CoreAppendUserInstruction + + +def create(): + """Create a new CoreAppendUserInstruction instance.""" + return CoreAppendUserInstruction() diff --git a/workflow/plugins/python/core/core_append_user_instruction/package.json b/workflow/plugins/python/core/core_append_user_instruction/package.json index de9a8239a..0989100e7 100644 --- a/workflow/plugins/python/core/core_append_user_instruction/package.json +++ b/workflow/plugins/python/core/core_append_user_instruction/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/core_append_user_instruction", "version": "1.0.0", - "description": "core_append_user_instruction plugin", + "description": "Append the next user instruction to the message list", "author": "MetaBuilder", "license": "MIT", "keywords": ["core", "workflow", "plugin"], "main": "core_append_user_instruction.py", + "files": ["core_append_user_instruction.py", "factory.py"], "metadata": { "plugin_type": "core.append_user_instruction", - "category": "core" + "category": "core", + "class": "CoreAppendUserInstruction", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/core/core_load_context/core_load_context.py b/workflow/plugins/python/core/core_load_context/core_load_context.py index b76d37475..1e2a268f0 100644 --- a/workflow/plugins/python/core/core_load_context/core_load_context.py +++ b/workflow/plugins/python/core/core_load_context/core_load_context.py @@ -1,43 +1,53 @@ """Workflow plugin: load SDLC context.""" + import os import logging +from ...base import NodeExecutor + logger = logging.getLogger("metabuilder") -def run(runtime, _inputs): +class CoreLoadContext(NodeExecutor): """Load SDLC context into the workflow store.""" - gh = runtime.context.get("gh") - msgs = runtime.context.get("msgs", {}) - sdlc_context = "" + node_type = "core.load_context" + category = "core" + description = "Load SDLC context from ROADMAP.md and GitHub issues/PRs" - # Load ROADMAP.md if it exists - if os.path.exists("ROADMAP.md"): - with open("ROADMAP.md", "r", encoding="utf-8") as f: - roadmap_content = f.read() - label = msgs.get("roadmap_label", "ROADMAP.md Content:") - sdlc_context += f"\n{label}\n{roadmap_content}\n" - else: - msg = msgs.get( - "missing_roadmap_msg", - "ROADMAP.md is missing. Please analyze the repository and create it." - ) - sdlc_context += f"\n{msg}\n" + def execute(self, inputs, runtime=None): + """Load SDLC context into the workflow store.""" + gh = runtime.context.get("gh") + msgs = runtime.context.get("msgs", {}) - # Load GitHub issues and PRs if integration is available - if gh: - try: - issues = gh.get_open_issues() - issue_list = "\n".join([f"- #{i.number}: {i.title}" for i in issues[:5]]) - if issue_list: - sdlc_context += f"\n{msgs['open_issues_label']}\n{issue_list}" + sdlc_context = "" - prs = gh.get_pull_requests() - pr_list = "\n".join([f"- #{p.number}: {p.title}" for p in prs[:5]]) - if pr_list: - sdlc_context += f"\n{msgs['open_prs_label']}\n{pr_list}" - except Exception as error: - logger.error(msgs.get("error_sdlc_context", "Error: {error}").format(error=error)) + # Load ROADMAP.md if it exists + if os.path.exists("ROADMAP.md"): + with open("ROADMAP.md", "r", encoding="utf-8") as f: + roadmap_content = f.read() + label = msgs.get("roadmap_label", "ROADMAP.md Content:") + sdlc_context += f"\n{label}\n{roadmap_content}\n" + else: + msg = msgs.get( + "missing_roadmap_msg", + "ROADMAP.md is missing. Please analyze the repository and create it." + ) + sdlc_context += f"\n{msg}\n" - return {"context": sdlc_context} + # Load GitHub issues and PRs if integration is available + if gh: + try: + issues = gh.get_open_issues() + issue_list = "\n".join([f"- #{i.number}: {i.title}" for i in issues[:5]]) + if issue_list: + sdlc_context += f"\n{msgs['open_issues_label']}\n{issue_list}" + + prs = gh.get_pull_requests() + pr_list = "\n".join([f"- #{p.number}: {p.title}" for p in prs[:5]]) + if pr_list: + sdlc_context += f"\n{msgs['open_prs_label']}\n{pr_list}" + except Exception as error: + logger.error(msgs.get("error_sdlc_context", "Error: {error}").format(error=error)) + + return {"context": sdlc_context} diff --git a/workflow/plugins/python/core/core_load_context/factory.py b/workflow/plugins/python/core/core_load_context/factory.py new file mode 100644 index 000000000..509c59a09 --- /dev/null +++ b/workflow/plugins/python/core/core_load_context/factory.py @@ -0,0 +1,8 @@ +"""Factory for CoreLoadContext plugin.""" + +from .core_load_context import CoreLoadContext + + +def create(): + """Create a new CoreLoadContext instance.""" + return CoreLoadContext() diff --git a/workflow/plugins/python/core/core_load_context/package.json b/workflow/plugins/python/core/core_load_context/package.json index 63f76cfbf..ec5778296 100644 --- a/workflow/plugins/python/core/core_load_context/package.json +++ b/workflow/plugins/python/core/core_load_context/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/core_load_context", "version": "1.0.0", - "description": "core_load_context plugin", + "description": "Load SDLC context from ROADMAP.md and GitHub issues/PRs", "author": "MetaBuilder", "license": "MIT", "keywords": ["core", "workflow", "plugin"], "main": "core_load_context.py", + "files": ["core_load_context.py", "factory.py"], "metadata": { "plugin_type": "core.load_context", - "category": "core" + "category": "core", + "class": "CoreLoadContext", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/core/core_run_tool_calls/core_run_tool_calls.py b/workflow/plugins/python/core/core_run_tool_calls/core_run_tool_calls.py index 1e6e88a7b..d10a6c3d0 100644 --- a/workflow/plugins/python/core/core_run_tool_calls/core_run_tool_calls.py +++ b/workflow/plugins/python/core/core_run_tool_calls/core_run_tool_calls.py @@ -1,37 +1,47 @@ """Workflow plugin: run tool calls.""" +import json -def run(runtime, inputs): +from ...base import NodeExecutor + + +class CoreRunToolCalls(NodeExecutor): """Execute tool calls from an AI response.""" - resp_msg = inputs.get("response") - tool_calls = getattr(resp_msg, "tool_calls", None) or [] - if not resp_msg: - return {"tool_results": [], "no_tool_calls": True} - # Handle tool calls using tool map from context - tool_results = [] - tool_map = runtime.context.get("tool_map", {}) + node_type = "core.run_tool_calls" + category = "core" + description = "Execute tool calls from an AI response and return results" - for tool_call in tool_calls: - func_name = tool_call.function.name - if func_name in tool_map: - try: - import json - args = json.loads(tool_call.function.arguments) - result = tool_map[func_name](**args) - tool_results.append({ - "role": "tool", - "tool_call_id": tool_call.id, - "content": str(result) - }) - except Exception as e: - tool_results.append({ - "role": "tool", - "tool_call_id": tool_call.id, - "content": f"Error: {str(e)}" - }) + def execute(self, inputs, runtime=None): + """Execute tool calls from an AI response.""" + resp_msg = inputs.get("response") + tool_calls = getattr(resp_msg, "tool_calls", None) or [] + if not resp_msg: + return {"tool_results": [], "no_tool_calls": True} - return { - "tool_results": tool_results, - "no_tool_calls": not bool(tool_calls) - } + # Handle tool calls using tool map from context + tool_results = [] + tool_map = runtime.context.get("tool_map", {}) + + for tool_call in tool_calls: + func_name = tool_call.function.name + if func_name in tool_map: + try: + args = json.loads(tool_call.function.arguments) + result = tool_map[func_name](**args) + tool_results.append({ + "role": "tool", + "tool_call_id": tool_call.id, + "content": str(result) + }) + except Exception as e: + tool_results.append({ + "role": "tool", + "tool_call_id": tool_call.id, + "content": f"Error: {str(e)}" + }) + + return { + "tool_results": tool_results, + "no_tool_calls": not bool(tool_calls) + } diff --git a/workflow/plugins/python/core/core_run_tool_calls/factory.py b/workflow/plugins/python/core/core_run_tool_calls/factory.py new file mode 100644 index 000000000..e6fef5f08 --- /dev/null +++ b/workflow/plugins/python/core/core_run_tool_calls/factory.py @@ -0,0 +1,8 @@ +"""Factory for CoreRunToolCalls plugin.""" + +from .core_run_tool_calls import CoreRunToolCalls + + +def create(): + """Create a new CoreRunToolCalls instance.""" + return CoreRunToolCalls() diff --git a/workflow/plugins/python/core/core_run_tool_calls/package.json b/workflow/plugins/python/core/core_run_tool_calls/package.json index e51539f18..e8234c297 100644 --- a/workflow/plugins/python/core/core_run_tool_calls/package.json +++ b/workflow/plugins/python/core/core_run_tool_calls/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/core_run_tool_calls", "version": "1.0.0", - "description": "core_run_tool_calls plugin", + "description": "Execute tool calls from an AI response and return results", "author": "MetaBuilder", "license": "MIT", "keywords": ["core", "workflow", "plugin"], "main": "core_run_tool_calls.py", + "files": ["core_run_tool_calls.py", "factory.py"], "metadata": { "plugin_type": "core.run_tool_calls", - "category": "core" + "category": "core", + "class": "CoreRunToolCalls", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/core/core_seed_messages/core_seed_messages.py b/workflow/plugins/python/core/core_seed_messages/core_seed_messages.py index 405010e7f..48761d03d 100644 --- a/workflow/plugins/python/core/core_seed_messages/core_seed_messages.py +++ b/workflow/plugins/python/core/core_seed_messages/core_seed_messages.py @@ -1,7 +1,16 @@ """Workflow plugin: seed messages.""" +from ...base import NodeExecutor -def run(runtime, _inputs): + +class CoreSeedMessages(NodeExecutor): """Seed messages from the prompt.""" - prompt = runtime.context["prompt"] - return {"messages": list(prompt["messages"])} + + node_type = "core.seed_messages" + category = "core" + description = "Initialize the message list from the prompt configuration" + + def execute(self, inputs, runtime=None): + """Seed messages from the prompt.""" + prompt = runtime.context["prompt"] + return {"messages": list(prompt["messages"])} diff --git a/workflow/plugins/python/core/core_seed_messages/factory.py b/workflow/plugins/python/core/core_seed_messages/factory.py new file mode 100644 index 000000000..99b0c8bf8 --- /dev/null +++ b/workflow/plugins/python/core/core_seed_messages/factory.py @@ -0,0 +1,8 @@ +"""Factory for CoreSeedMessages plugin.""" + +from .core_seed_messages import CoreSeedMessages + + +def create(): + """Create a new CoreSeedMessages instance.""" + return CoreSeedMessages() diff --git a/workflow/plugins/python/core/core_seed_messages/package.json b/workflow/plugins/python/core/core_seed_messages/package.json index 253342e69..b6a270f63 100644 --- a/workflow/plugins/python/core/core_seed_messages/package.json +++ b/workflow/plugins/python/core/core_seed_messages/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/core_seed_messages", "version": "1.0.0", - "description": "core_seed_messages plugin", + "description": "Initialize the message list from the prompt configuration", "author": "MetaBuilder", "license": "MIT", "keywords": ["core", "workflow", "plugin"], "main": "core_seed_messages.py", + "files": ["core_seed_messages.py", "factory.py"], "metadata": { "plugin_type": "core.seed_messages", - "category": "core" + "category": "core", + "class": "CoreSeedMessages", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/dict/dict_get/dict_get.py b/workflow/plugins/python/dict/dict_get/dict_get.py index b23030145..7ceef604a 100644 --- a/workflow/plugins/python/dict/dict_get/dict_get.py +++ b/workflow/plugins/python/dict/dict_get/dict_get.py @@ -1,14 +1,33 @@ """Workflow plugin: get value from dictionary.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class DictGet(NodeExecutor): """Get value from dictionary by key.""" - obj = inputs.get("object", {}) - key = inputs.get("key") - default = inputs.get("default") - if not isinstance(obj, dict): - return {"result": default, "found": False} + node_type = "dict.get" + category = "dict" + description = "Get value from dictionary by key" - result = obj.get(key, default) - return {"result": result, "found": key in obj} + def execute(self, inputs, runtime=None): + obj = inputs.get("object", inputs.get("dict", {})) + key = inputs.get("key", inputs.get("path")) + default = inputs.get("default") + + if not isinstance(obj, dict): + return {"result": default, "found": False} + + # Support dot notation paths + if key and "." in key: + parts = key.split(".") + current = obj + for part in parts: + if isinstance(current, dict): + current = current.get(part) + else: + return {"result": default, "found": False} + return {"result": current if current is not None else default, "found": current is not None} + + result = obj.get(key, default) + return {"result": result, "found": key in obj} diff --git a/workflow/plugins/python/dict/dict_get/factory.py b/workflow/plugins/python/dict/dict_get/factory.py new file mode 100644 index 000000000..2bbcadaa0 --- /dev/null +++ b/workflow/plugins/python/dict/dict_get/factory.py @@ -0,0 +1,7 @@ +"""Factory for DictGet plugin.""" + +from .dict_get import DictGet + + +def create(): + return DictGet() diff --git a/workflow/plugins/python/dict/dict_get/package.json b/workflow/plugins/python/dict/dict_get/package.json index 678f2da99..3d7f76fdd 100644 --- a/workflow/plugins/python/dict/dict_get/package.json +++ b/workflow/plugins/python/dict/dict_get/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/dict_get", "version": "1.0.0", - "description": "dict_get plugin", + "description": "Get value from dictionary by key", "author": "MetaBuilder", "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_get.py", + "files": ["dict_get.py", "factory.py"], "metadata": { "plugin_type": "dict.get", - "category": "dict" + "category": "dict", + "class": "DictGet", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/dict/dict_items/dict_items.py b/workflow/plugins/python/dict/dict_items/dict_items.py index 69481092b..ef1f03f81 100644 --- a/workflow/plugins/python/dict/dict_items/dict_items.py +++ b/workflow/plugins/python/dict/dict_items/dict_items.py @@ -1,11 +1,21 @@ """Workflow plugin: get dictionary items as key-value pairs.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class DictItems(NodeExecutor): """Get dictionary items as list of [key, value] pairs.""" - obj = inputs.get("object", {}) - if not isinstance(obj, dict): - return {"result": []} + node_type = "dict.items" + category = "dict" + description = "Get dictionary items as list of [key, value] pairs" - return {"result": [[k, v] for k, v in obj.items()]} + def execute(self, inputs, runtime=None): + obj = inputs.get("object", inputs.get("dict", {})) + + if isinstance(obj, dict): + result = [[k, v] for k, v in obj.items()] + else: + result = [] + + return {"result": result} diff --git a/workflow/plugins/python/dict/dict_items/factory.py b/workflow/plugins/python/dict/dict_items/factory.py new file mode 100644 index 000000000..d2a99063c --- /dev/null +++ b/workflow/plugins/python/dict/dict_items/factory.py @@ -0,0 +1,7 @@ +"""Factory for DictItems plugin.""" + +from .dict_items import DictItems + + +def create(): + return DictItems() diff --git a/workflow/plugins/python/dict/dict_items/package.json b/workflow/plugins/python/dict/dict_items/package.json index 502e1927d..224c15c92 100644 --- a/workflow/plugins/python/dict/dict_items/package.json +++ b/workflow/plugins/python/dict/dict_items/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/dict_items", "version": "1.0.0", - "description": "dict_items plugin", + "description": "Get dictionary items as list of key-value pairs", "author": "MetaBuilder", "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_items.py", + "files": ["dict_items.py", "factory.py"], "metadata": { "plugin_type": "dict.items", - "category": "dict" + "category": "dict", + "class": "DictItems", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/dict/dict_keys/dict_keys.py b/workflow/plugins/python/dict/dict_keys/dict_keys.py index 74a7c00df..80cf78340 100644 --- a/workflow/plugins/python/dict/dict_keys/dict_keys.py +++ b/workflow/plugins/python/dict/dict_keys/dict_keys.py @@ -1,11 +1,21 @@ """Workflow plugin: get dictionary keys.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class DictKeys(NodeExecutor): """Get all keys from dictionary.""" - obj = inputs.get("object", {}) - if not isinstance(obj, dict): - return {"result": []} + node_type = "dict.keys" + category = "dict" + description = "Get all keys from dictionary" - return {"result": list(obj.keys())} + def execute(self, inputs, runtime=None): + obj = inputs.get("object", inputs.get("dict", {})) + + if isinstance(obj, dict): + result = list(obj.keys()) + else: + result = [] + + return {"result": result} diff --git a/workflow/plugins/python/dict/dict_keys/factory.py b/workflow/plugins/python/dict/dict_keys/factory.py new file mode 100644 index 000000000..4b269b06e --- /dev/null +++ b/workflow/plugins/python/dict/dict_keys/factory.py @@ -0,0 +1,7 @@ +"""Factory for DictKeys plugin.""" + +from .dict_keys import DictKeys + + +def create(): + return DictKeys() diff --git a/workflow/plugins/python/dict/dict_keys/package.json b/workflow/plugins/python/dict/dict_keys/package.json index 8f5306aea..b0c6e19a2 100644 --- a/workflow/plugins/python/dict/dict_keys/package.json +++ b/workflow/plugins/python/dict/dict_keys/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/dict_keys", "version": "1.0.0", - "description": "dict_keys plugin", + "description": "Get all keys from dictionary", "author": "MetaBuilder", "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_keys.py", + "files": ["dict_keys.py", "factory.py"], "metadata": { "plugin_type": "dict.keys", - "category": "dict" + "category": "dict", + "class": "DictKeys", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/dict/dict_merge/dict_merge.py b/workflow/plugins/python/dict/dict_merge/dict_merge.py index 55fb69765..591c338c8 100644 --- a/workflow/plugins/python/dict/dict_merge/dict_merge.py +++ b/workflow/plugins/python/dict/dict_merge/dict_merge.py @@ -1,13 +1,19 @@ """Workflow plugin: merge dictionaries.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class DictMerge(NodeExecutor): """Merge multiple dictionaries.""" - objects = inputs.get("objects", []) - result = {} - for obj in objects: - if isinstance(obj, dict): - result.update(obj) + node_type = "dict.merge" + category = "dict" + description = "Merge multiple dictionaries" - return {"result": result} + def execute(self, inputs, runtime=None): + objects = inputs.get("objects", []) + result = {} + for obj in objects: + if isinstance(obj, dict): + result.update(obj) + return {"result": result} diff --git a/workflow/plugins/python/dict/dict_merge/factory.py b/workflow/plugins/python/dict/dict_merge/factory.py new file mode 100644 index 000000000..0cb65d7d6 --- /dev/null +++ b/workflow/plugins/python/dict/dict_merge/factory.py @@ -0,0 +1,7 @@ +"""Factory for DictMerge plugin.""" + +from .dict_merge import DictMerge + + +def create(): + return DictMerge() diff --git a/workflow/plugins/python/dict/dict_merge/package.json b/workflow/plugins/python/dict/dict_merge/package.json index fdf8d8d5a..990a692c5 100644 --- a/workflow/plugins/python/dict/dict_merge/package.json +++ b/workflow/plugins/python/dict/dict_merge/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/dict_merge", "version": "1.0.0", - "description": "dict_merge plugin", + "description": "Merge multiple dictionaries", "author": "MetaBuilder", "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_merge.py", + "files": ["dict_merge.py", "factory.py"], "metadata": { "plugin_type": "dict.merge", - "category": "dict" + "category": "dict", + "class": "DictMerge", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/dict/dict_set/dict_set.py b/workflow/plugins/python/dict/dict_set/dict_set.py index a589c0189..4514b7cbd 100644 --- a/workflow/plugins/python/dict/dict_set/dict_set.py +++ b/workflow/plugins/python/dict/dict_set/dict_set.py @@ -1,15 +1,22 @@ """Workflow plugin: set value in dictionary.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class DictSet(NodeExecutor): """Set value in dictionary by key.""" - obj = inputs.get("object", {}) - key = inputs.get("key") - value = inputs.get("value") - if not isinstance(obj, dict): - obj = {} + node_type = "dict.set" + category = "dict" + description = "Set value in dictionary by key" - result = dict(obj) - result[key] = value - return {"result": result} + def execute(self, inputs, runtime=None): + obj = inputs.get("object", inputs.get("dict", {})) + key = inputs.get("key", inputs.get("path")) + value = inputs.get("value") + + if not isinstance(obj, dict): + obj = {} + + result = {**obj, key: value} + return {"result": result} diff --git a/workflow/plugins/python/dict/dict_set/factory.py b/workflow/plugins/python/dict/dict_set/factory.py new file mode 100644 index 000000000..9ba0975c8 --- /dev/null +++ b/workflow/plugins/python/dict/dict_set/factory.py @@ -0,0 +1,7 @@ +"""Factory for DictSet plugin.""" + +from .dict_set import DictSet + + +def create(): + return DictSet() diff --git a/workflow/plugins/python/dict/dict_set/package.json b/workflow/plugins/python/dict/dict_set/package.json index 3f1213937..43b6c82fb 100644 --- a/workflow/plugins/python/dict/dict_set/package.json +++ b/workflow/plugins/python/dict/dict_set/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/dict_set", "version": "1.0.0", - "description": "dict_set plugin", + "description": "Set value in dictionary by key", "author": "MetaBuilder", "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_set.py", + "files": ["dict_set.py", "factory.py"], "metadata": { "plugin_type": "dict.set", - "category": "dict" + "category": "dict", + "class": "DictSet", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/dict/dict_values/dict_values.py b/workflow/plugins/python/dict/dict_values/dict_values.py index fc37f5cfc..e357614c5 100644 --- a/workflow/plugins/python/dict/dict_values/dict_values.py +++ b/workflow/plugins/python/dict/dict_values/dict_values.py @@ -1,11 +1,21 @@ """Workflow plugin: get dictionary values.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class DictValues(NodeExecutor): """Get all values from dictionary.""" - obj = inputs.get("object", {}) - if not isinstance(obj, dict): - return {"result": []} + node_type = "dict.values" + category = "dict" + description = "Get all values from dictionary" - return {"result": list(obj.values())} + def execute(self, inputs, runtime=None): + obj = inputs.get("object", inputs.get("dict", {})) + + if isinstance(obj, dict): + result = list(obj.values()) + else: + result = [] + + return {"result": result} diff --git a/workflow/plugins/python/dict/dict_values/factory.py b/workflow/plugins/python/dict/dict_values/factory.py new file mode 100644 index 000000000..4945bf58b --- /dev/null +++ b/workflow/plugins/python/dict/dict_values/factory.py @@ -0,0 +1,7 @@ +"""Factory for DictValues plugin.""" + +from .dict_values import DictValues + + +def create(): + return DictValues() diff --git a/workflow/plugins/python/dict/dict_values/package.json b/workflow/plugins/python/dict/dict_values/package.json index e43d08e53..62d5066de 100644 --- a/workflow/plugins/python/dict/dict_values/package.json +++ b/workflow/plugins/python/dict/dict_values/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/dict_values", "version": "1.0.0", - "description": "dict_values plugin", + "description": "Get all values from dictionary", "author": "MetaBuilder", "license": "MIT", "keywords": ["dict", "workflow", "plugin"], "main": "dict_values.py", + "files": ["dict_values.py", "factory.py"], "metadata": { "plugin_type": "dict.values", - "category": "dict" + "category": "dict", + "class": "DictValues", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/factory.py b/workflow/plugins/python/factory.py new file mode 100644 index 000000000..ec0c61a08 --- /dev/null +++ b/workflow/plugins/python/factory.py @@ -0,0 +1,173 @@ +""" +Plugin factory for creating workflow plugins from simple functions. + +This module provides a factory pattern for generating plugins. Plugins should +NOT call these functions at module load time - instead they should just export +NODE_TYPE, CATEGORY, DESCRIPTION, and impl. The registry handles instantiation. +""" + +from typing import Any, Callable, Dict, List + +from .base import NodeExecutor + + +def create_plugin( + node_type: str, + category: str, + description: str, + execute_fn: Callable[[Dict[str, Any], Any], Dict[str, Any]], +) -> NodeExecutor: + """ + Create a plugin executor from a simple function. + + Args: + node_type: The node type identifier (e.g., "math.add") + category: Plugin category (e.g., "math") + description: Human-readable description + execute_fn: Function that takes (inputs, runtime) and returns result dict + + Returns: + NodeExecutor instance with run() method exposed + """ + + class DynamicExecutor(NodeExecutor): + pass + + DynamicExecutor.node_type = node_type + DynamicExecutor.category = category + DynamicExecutor.description = description + DynamicExecutor.execute = lambda self, inputs, runtime=None: execute_fn( + inputs, runtime + ) + + return DynamicExecutor() + + +def wrap_math_impl( + fn: Callable[..., float], + input_keys: List[str] = None, + array_key: str = None, +) -> Callable[[Dict[str, Any], Any], Dict[str, Any]]: + """ + Wrap a math function to handle common input patterns. + + Args: + fn: Math function to apply + input_keys: List of input parameter names (for binary ops like a, b) + array_key: Key for array input (for reduce ops like sum) + + Returns: + Wrapped impl function + """ + + def impl(inputs, runtime=None): + try: + if array_key: + values = inputs.get(array_key, inputs.get("values", [])) + result = fn([float(v) for v in values]) + elif input_keys: + args = [float(inputs.get(k, 0)) for k in input_keys] + result = fn(*args) + else: + value = float(inputs.get("value", 0)) + result = fn(value) + return {"result": result} + except Exception as e: + return {"error": str(e)} + + return impl + + +def wrap_string_impl( + fn: Callable[[str, Dict[str, Any]], str], +) -> Callable[[Dict[str, Any], Any], Dict[str, Any]]: + """ + Wrap a string function to handle common input patterns. + + Args: + fn: Function that takes (value, inputs) and returns transformed string + + Returns: + Wrapped impl function + """ + + def impl(inputs, runtime=None): + try: + value = str(inputs.get("value", inputs.get("text", ""))) + result = fn(value, inputs) + return {"result": result} + except Exception as e: + return {"error": str(e)} + + return impl + + +def wrap_logic_impl( + fn: Callable[[Dict[str, Any]], bool], +) -> Callable[[Dict[str, Any], Any], Dict[str, Any]]: + """ + Wrap a logic function to handle common input patterns. + + Args: + fn: Function that takes inputs and returns boolean + + Returns: + Wrapped impl function + """ + + def impl(inputs, runtime=None): + try: + result = fn(inputs) + return {"result": result} + except Exception as e: + return {"error": str(e)} + + return impl + + +def wrap_list_impl( + fn: Callable[[List[Any], Dict[str, Any]], Any], +) -> Callable[[Dict[str, Any], Any], Dict[str, Any]]: + """ + Wrap a list function to handle common input patterns. + + Args: + fn: Function that takes (array, inputs) and returns result + + Returns: + Wrapped impl function + """ + + def impl(inputs, runtime=None): + try: + array = inputs.get("array", inputs.get("list", [])) + result = fn(array, inputs) + return {"result": result} + except Exception as e: + return {"error": str(e)} + + return impl + + +def wrap_dict_impl( + fn: Callable[[Dict[str, Any], Dict[str, Any]], Any], +) -> Callable[[Dict[str, Any], Any], Dict[str, Any]]: + """ + Wrap a dict function to handle common input patterns. + + Args: + fn: Function that takes (obj, inputs) and returns result + + Returns: + Wrapped impl function + """ + + def impl(inputs, runtime=None): + try: + obj = inputs.get("object", inputs.get("dict", {})) + result = fn(obj, inputs) + return {"result": result} + except Exception as e: + return {"error": str(e)} + + return impl diff --git a/workflow/plugins/python/list/list_concat/factory.py b/workflow/plugins/python/list/list_concat/factory.py new file mode 100644 index 000000000..e47cadfa7 --- /dev/null +++ b/workflow/plugins/python/list/list_concat/factory.py @@ -0,0 +1,7 @@ +"""Factory for ListConcat plugin.""" + +from .list_concat import ListConcat + + +def create(): + return ListConcat() diff --git a/workflow/plugins/python/list/list_concat/list_concat.py b/workflow/plugins/python/list/list_concat/list_concat.py index b2d2792a3..16fdc89bc 100644 --- a/workflow/plugins/python/list/list_concat/list_concat.py +++ b/workflow/plugins/python/list/list_concat/list_concat.py @@ -1,11 +1,20 @@ """Workflow plugin: concatenate lists.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ListConcat(NodeExecutor): """Concatenate multiple lists.""" - lists = inputs.get("lists", []) - result = [] - for lst in lists: - if isinstance(lst, list): - result.extend(lst) - return {"result": result} + + node_type = "list.concat" + category = "list" + description = "Concatenate multiple lists" + + def execute(self, inputs, runtime=None): + array = inputs.get("array", inputs.get("list", [])) + lists = inputs.get("lists", inputs.get("arrays", [array])) + result = [] + for lst in lists: + if isinstance(lst, list): + result.extend(lst) + return {"result": result} diff --git a/workflow/plugins/python/list/list_concat/package.json b/workflow/plugins/python/list/list_concat/package.json index 5d11e3e7e..43654534d 100644 --- a/workflow/plugins/python/list/list_concat/package.json +++ b/workflow/plugins/python/list/list_concat/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/list_concat", "version": "1.0.0", - "description": "list_concat plugin", + "description": "Concatenate multiple lists", "author": "MetaBuilder", "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_concat.py", + "files": ["list_concat.py", "factory.py"], "metadata": { "plugin_type": "list.concat", - "category": "list" + "category": "list", + "class": "ListConcat", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/list/list_every/factory.py b/workflow/plugins/python/list/list_every/factory.py new file mode 100644 index 000000000..fcf8eb71d --- /dev/null +++ b/workflow/plugins/python/list/list_every/factory.py @@ -0,0 +1,7 @@ +"""Factory for ListEvery plugin.""" + +from .list_every import ListEvery + + +def create(): + return ListEvery() diff --git a/workflow/plugins/python/list/list_every/list_every.py b/workflow/plugins/python/list/list_every/list_every.py index 1c9d4acc4..bb5812ec4 100644 --- a/workflow/plugins/python/list/list_every/list_every.py +++ b/workflow/plugins/python/list/list_every/list_every.py @@ -1,18 +1,26 @@ """Workflow plugin: check if all items match condition.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ListEvery(NodeExecutor): """Check if all items match condition.""" - items = inputs.get("items", []) - key = inputs.get("key") - value = inputs.get("value") - if not items: - return {"result": True} + node_type = "list.every" + category = "list" + description = "Check if all items match condition" - if key is not None and value is not None: - result = all(isinstance(item, dict) and item.get(key) == value for item in items) - else: - result = all(items) + def execute(self, inputs, runtime=None): + items = inputs.get("items", inputs.get("array", [])) + key = inputs.get("key") + value = inputs.get("value") - return {"result": result} + if not items: + return {"result": True} + + if key is not None and value is not None: + result = all(isinstance(item, dict) and item.get(key) == value for item in items) + else: + result = all(items) + + return {"result": result} diff --git a/workflow/plugins/python/list/list_every/package.json b/workflow/plugins/python/list/list_every/package.json index c057e88c9..06abb701d 100644 --- a/workflow/plugins/python/list/list_every/package.json +++ b/workflow/plugins/python/list/list_every/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/list_every", "version": "1.0.0", - "description": "list_every plugin", + "description": "Check if all items match condition", "author": "MetaBuilder", "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_every.py", + "files": ["list_every.py", "factory.py"], "metadata": { "plugin_type": "list.every", - "category": "list" + "category": "list", + "class": "ListEvery", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/list/list_find/factory.py b/workflow/plugins/python/list/list_find/factory.py new file mode 100644 index 000000000..cc3c7ecba --- /dev/null +++ b/workflow/plugins/python/list/list_find/factory.py @@ -0,0 +1,7 @@ +"""Factory for ListFind plugin.""" + +from .list_find import ListFind + + +def create(): + return ListFind() diff --git a/workflow/plugins/python/list/list_find/list_find.py b/workflow/plugins/python/list/list_find/list_find.py index 759e601d1..e0691092e 100644 --- a/workflow/plugins/python/list/list_find/list_find.py +++ b/workflow/plugins/python/list/list_find/list_find.py @@ -1,14 +1,22 @@ """Workflow plugin: find item in list.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ListFind(NodeExecutor): """Find first item matching condition.""" - items = inputs.get("items", []) - key = inputs.get("key") - value = inputs.get("value") - for item in items: - if isinstance(item, dict) and item.get(key) == value: - return {"result": item, "found": True} + node_type = "list.find" + category = "list" + description = "Find first item matching condition" - return {"result": None, "found": False} + def execute(self, inputs, runtime=None): + items = inputs.get("items", inputs.get("array", [])) + key = inputs.get("key") + value = inputs.get("value") + + for item in items: + if isinstance(item, dict) and item.get(key) == value: + return {"result": item, "found": True} + + return {"result": None, "found": False} diff --git a/workflow/plugins/python/list/list_find/package.json b/workflow/plugins/python/list/list_find/package.json index a02889fc1..634d07917 100644 --- a/workflow/plugins/python/list/list_find/package.json +++ b/workflow/plugins/python/list/list_find/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/list_find", "version": "1.0.0", - "description": "list_find plugin", + "description": "Find first item matching condition", "author": "MetaBuilder", "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_find.py", + "files": ["list_find.py", "factory.py"], "metadata": { "plugin_type": "list.find", - "category": "list" + "category": "list", + "class": "ListFind", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/list/list_length/factory.py b/workflow/plugins/python/list/list_length/factory.py new file mode 100644 index 000000000..04f78259c --- /dev/null +++ b/workflow/plugins/python/list/list_length/factory.py @@ -0,0 +1,7 @@ +"""Factory for ListLength plugin.""" + +from .list_length import ListLength + + +def create(): + return ListLength() diff --git a/workflow/plugins/python/list/list_length/list_length.py b/workflow/plugins/python/list/list_length/list_length.py index fbe95c6e3..1430cfc94 100644 --- a/workflow/plugins/python/list/list_length/list_length.py +++ b/workflow/plugins/python/list/list_length/list_length.py @@ -1,7 +1,15 @@ """Workflow plugin: get list length.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Get length of a list or string.""" - items = inputs.get("items", []) - return {"result": len(items) if items is not None else 0} + +class ListLength(NodeExecutor): + """Get list length.""" + + node_type = "list.length" + category = "list" + description = "Get list length" + + def execute(self, inputs, runtime=None): + array = inputs.get("array", inputs.get("list", [])) + return {"result": len(array) if array is not None else 0} diff --git a/workflow/plugins/python/list/list_length/package.json b/workflow/plugins/python/list/list_length/package.json index 79970dc8c..9c79b0512 100644 --- a/workflow/plugins/python/list/list_length/package.json +++ b/workflow/plugins/python/list/list_length/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/list_length", "version": "1.0.0", - "description": "list_length plugin", + "description": "Get length of list", "author": "MetaBuilder", "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_length.py", + "files": ["list_length.py", "factory.py"], "metadata": { "plugin_type": "list.length", - "category": "list" + "category": "list", + "class": "ListLength", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/list/list_slice/factory.py b/workflow/plugins/python/list/list_slice/factory.py new file mode 100644 index 000000000..a20115daf --- /dev/null +++ b/workflow/plugins/python/list/list_slice/factory.py @@ -0,0 +1,7 @@ +"""Factory for ListSlice plugin.""" + +from .list_slice import ListSlice + + +def create(): + return ListSlice() diff --git a/workflow/plugins/python/list/list_slice/list_slice.py b/workflow/plugins/python/list/list_slice/list_slice.py index 6a34fdb5f..1c7c012b2 100644 --- a/workflow/plugins/python/list/list_slice/list_slice.py +++ b/workflow/plugins/python/list/list_slice/list_slice.py @@ -1,15 +1,19 @@ """Workflow plugin: slice a list.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ListSlice(NodeExecutor): """Extract slice from list.""" - items = inputs.get("items", []) - start = inputs.get("start", 0) - end = inputs.get("end") - if end is None: - result = items[start:] - else: - result = items[start:end] + node_type = "list.slice" + category = "list" + description = "Extract slice from list" - return {"result": result} + def execute(self, inputs, runtime=None): + array = inputs.get("array", inputs.get("items", inputs.get("list", []))) + start = inputs.get("start", 0) + end = inputs.get("end") + + result = array[start:end] if end is not None else array[start:] + return {"result": result} diff --git a/workflow/plugins/python/list/list_slice/package.json b/workflow/plugins/python/list/list_slice/package.json index df49ef881..3922f5162 100644 --- a/workflow/plugins/python/list/list_slice/package.json +++ b/workflow/plugins/python/list/list_slice/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/list_slice", "version": "1.0.0", - "description": "list_slice plugin", + "description": "Extract slice from list", "author": "MetaBuilder", "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_slice.py", + "files": ["list_slice.py", "factory.py"], "metadata": { "plugin_type": "list.slice", - "category": "list" + "category": "list", + "class": "ListSlice", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/list/list_some/factory.py b/workflow/plugins/python/list/list_some/factory.py new file mode 100644 index 000000000..7284cfd29 --- /dev/null +++ b/workflow/plugins/python/list/list_some/factory.py @@ -0,0 +1,7 @@ +"""Factory for ListSome plugin.""" + +from .list_some import ListSome + + +def create(): + return ListSome() diff --git a/workflow/plugins/python/list/list_some/list_some.py b/workflow/plugins/python/list/list_some/list_some.py index 0cda24005..7337386f7 100644 --- a/workflow/plugins/python/list/list_some/list_some.py +++ b/workflow/plugins/python/list/list_some/list_some.py @@ -1,15 +1,23 @@ """Workflow plugin: check if some items match condition.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Check if at least one item matches condition.""" - items = inputs.get("items", []) - key = inputs.get("key") - value = inputs.get("value") - if key is not None and value is not None: - result = any(isinstance(item, dict) and item.get(key) == value for item in items) - else: - result = any(items) +class ListSome(NodeExecutor): + """Check if some items match condition.""" - return {"result": result} + node_type = "list.some" + category = "list" + description = "Check if some items match condition" + + def execute(self, inputs, runtime=None): + items = inputs.get("items", inputs.get("array", [])) + key = inputs.get("key") + value = inputs.get("value") + + if key is not None and value is not None: + result = any(isinstance(item, dict) and item.get(key) == value for item in items) + else: + result = any(items) + + return {"result": result} diff --git a/workflow/plugins/python/list/list_some/package.json b/workflow/plugins/python/list/list_some/package.json index 921c26f75..c02efba37 100644 --- a/workflow/plugins/python/list/list_some/package.json +++ b/workflow/plugins/python/list/list_some/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/list_some", "version": "1.0.0", - "description": "list_some plugin", + "description": "Check if some items match condition", "author": "MetaBuilder", "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_some.py", + "files": ["list_some.py", "factory.py"], "metadata": { "plugin_type": "list.some", - "category": "list" + "category": "list", + "class": "ListSome", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/list/list_sort/factory.py b/workflow/plugins/python/list/list_sort/factory.py new file mode 100644 index 000000000..9401e60e3 --- /dev/null +++ b/workflow/plugins/python/list/list_sort/factory.py @@ -0,0 +1,7 @@ +"""Factory for ListSort plugin.""" + +from .list_sort import ListSort + + +def create(): + return ListSort() diff --git a/workflow/plugins/python/list/list_sort/list_sort.py b/workflow/plugins/python/list/list_sort/list_sort.py index fa921bad4..680449a57 100644 --- a/workflow/plugins/python/list/list_sort/list_sort.py +++ b/workflow/plugins/python/list/list_sort/list_sort.py @@ -1,17 +1,29 @@ """Workflow plugin: sort a list.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ListSort(NodeExecutor): """Sort list by key or naturally.""" - items = inputs.get("items", []) - key = inputs.get("key") - reverse = inputs.get("reverse", False) - try: - if key: - result = sorted(items, key=lambda x: x.get(key) if isinstance(x, dict) else x, reverse=reverse) - else: - result = sorted(items, reverse=reverse) - return {"result": result} - except (TypeError, AttributeError): - return {"result": items, "error": "Cannot sort items"} + node_type = "list.sort" + category = "list" + description = "Sort list by key or naturally" + + def execute(self, inputs, runtime=None): + items = inputs.get("items", inputs.get("array", [])) + key = inputs.get("key") + reverse = inputs.get("reverse", inputs.get("order") == "desc") + + try: + if key: + result = sorted( + items, + key=lambda x: x.get(key) if isinstance(x, dict) else x, + reverse=reverse, + ) + else: + result = sorted(items, reverse=reverse) + return {"result": result} + except (TypeError, AttributeError): + return {"result": items, "error": "Cannot sort items"} diff --git a/workflow/plugins/python/list/list_sort/package.json b/workflow/plugins/python/list/list_sort/package.json index 6c5d2edef..7179e8c88 100644 --- a/workflow/plugins/python/list/list_sort/package.json +++ b/workflow/plugins/python/list/list_sort/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/list_sort", "version": "1.0.0", - "description": "list_sort plugin", + "description": "Sort list by key or naturally", "author": "MetaBuilder", "license": "MIT", "keywords": ["list", "workflow", "plugin"], "main": "list_sort.py", + "files": ["list_sort.py", "factory.py"], "metadata": { "plugin_type": "list.sort", - "category": "list" + "category": "list", + "class": "ListSort", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_and/factory.py b/workflow/plugins/python/logic/logic_and/factory.py new file mode 100644 index 000000000..37540b06a --- /dev/null +++ b/workflow/plugins/python/logic/logic_and/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicAnd plugin.""" + +from .logic_and import LogicAnd + + +def create(): + return LogicAnd() diff --git a/workflow/plugins/python/logic/logic_and/logic_and.py b/workflow/plugins/python/logic/logic_and/logic_and.py index ee12fd301..554870a70 100644 --- a/workflow/plugins/python/logic/logic_and/logic_and.py +++ b/workflow/plugins/python/logic/logic_and/logic_and.py @@ -1,7 +1,15 @@ """Workflow plugin: logical AND.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicAnd(NodeExecutor): """Perform logical AND on values.""" - values = inputs.get("values", []) - return {"result": all(values)} + + node_type = "logic.and" + category = "logic" + description = "Perform logical AND on values" + + def execute(self, inputs, runtime=None): + values = inputs.get("values", []) + return {"result": all(values)} diff --git a/workflow/plugins/python/logic/logic_and/package.json b/workflow/plugins/python/logic/logic_and/package.json index 3acc9fa49..47dfac3c2 100644 --- a/workflow/plugins/python/logic/logic_and/package.json +++ b/workflow/plugins/python/logic/logic_and/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_and", "version": "1.0.0", - "description": "logic_and plugin", + "description": "Perform logical AND on values", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_and.py", + "files": ["logic_and.py", "factory.py"], "metadata": { "plugin_type": "logic.and", - "category": "logic" + "category": "logic", + "class": "LogicAnd", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_equals/factory.py b/workflow/plugins/python/logic/logic_equals/factory.py new file mode 100644 index 000000000..c75329cee --- /dev/null +++ b/workflow/plugins/python/logic/logic_equals/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicEquals plugin.""" + +from .logic_equals import LogicEquals + + +def create(): + return LogicEquals() diff --git a/workflow/plugins/python/logic/logic_equals/logic_equals.py b/workflow/plugins/python/logic/logic_equals/logic_equals.py index 9ccc4a421..c4e4bfe4c 100644 --- a/workflow/plugins/python/logic/logic_equals/logic_equals.py +++ b/workflow/plugins/python/logic/logic_equals/logic_equals.py @@ -1,8 +1,16 @@ """Workflow plugin: equality comparison.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicEquals(NodeExecutor): """Check if two values are equal.""" - a = inputs.get("a") - b = inputs.get("b") - return {"result": a == b} + + node_type = "logic.equals" + category = "logic" + description = "Check if two values are equal" + + def execute(self, inputs, runtime=None): + a = inputs.get("a") + b = inputs.get("b") + return {"result": a == b} diff --git a/workflow/plugins/python/logic/logic_equals/package.json b/workflow/plugins/python/logic/logic_equals/package.json index e9432912e..31169447d 100644 --- a/workflow/plugins/python/logic/logic_equals/package.json +++ b/workflow/plugins/python/logic/logic_equals/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_equals", "version": "1.0.0", - "description": "logic_equals plugin", + "description": "Check if two values are equal", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_equals.py", + "files": ["logic_equals.py", "factory.py"], "metadata": { "plugin_type": "logic.equals", - "category": "logic" + "category": "logic", + "class": "LogicEquals", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_gt/factory.py b/workflow/plugins/python/logic/logic_gt/factory.py new file mode 100644 index 000000000..f02e05834 --- /dev/null +++ b/workflow/plugins/python/logic/logic_gt/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicGt plugin.""" + +from .logic_gt import LogicGt + + +def create(): + return LogicGt() diff --git a/workflow/plugins/python/logic/logic_gt/logic_gt.py b/workflow/plugins/python/logic/logic_gt/logic_gt.py index 635dda370..8261613b6 100644 --- a/workflow/plugins/python/logic/logic_gt/logic_gt.py +++ b/workflow/plugins/python/logic/logic_gt/logic_gt.py @@ -1,8 +1,16 @@ """Workflow plugin: greater than comparison.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicGt(NodeExecutor): """Check if a > b.""" - a = inputs.get("a") - b = inputs.get("b") - return {"result": a > b} + + node_type = "logic.gt" + category = "logic" + description = "Check if a > b" + + def execute(self, inputs, runtime=None): + a = inputs.get("a") + b = inputs.get("b") + return {"result": a > b} diff --git a/workflow/plugins/python/logic/logic_gt/package.json b/workflow/plugins/python/logic/logic_gt/package.json index 3e46ca63e..e98241e74 100644 --- a/workflow/plugins/python/logic/logic_gt/package.json +++ b/workflow/plugins/python/logic/logic_gt/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_gt", "version": "1.0.0", - "description": "logic_gt plugin", + "description": "Check if a > b", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_gt.py", + "files": ["logic_gt.py", "factory.py"], "metadata": { "plugin_type": "logic.gt", - "category": "logic" + "category": "logic", + "class": "LogicGt", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_gte/factory.py b/workflow/plugins/python/logic/logic_gte/factory.py new file mode 100644 index 000000000..9c417227d --- /dev/null +++ b/workflow/plugins/python/logic/logic_gte/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicGte plugin.""" + +from .logic_gte import LogicGte + + +def create(): + return LogicGte() diff --git a/workflow/plugins/python/logic/logic_gte/logic_gte.py b/workflow/plugins/python/logic/logic_gte/logic_gte.py index 4f9f6e91f..0bda897aa 100644 --- a/workflow/plugins/python/logic/logic_gte/logic_gte.py +++ b/workflow/plugins/python/logic/logic_gte/logic_gte.py @@ -1,8 +1,16 @@ """Workflow plugin: greater than or equal comparison.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicGte(NodeExecutor): """Check if a >= b.""" - a = inputs.get("a") - b = inputs.get("b") - return {"result": a >= b} + + node_type = "logic.gte" + category = "logic" + description = "Check if a >= b" + + def execute(self, inputs, runtime=None): + a = inputs.get("a") + b = inputs.get("b") + return {"result": a >= b} diff --git a/workflow/plugins/python/logic/logic_gte/package.json b/workflow/plugins/python/logic/logic_gte/package.json index 4961c9f9a..d310c5da7 100644 --- a/workflow/plugins/python/logic/logic_gte/package.json +++ b/workflow/plugins/python/logic/logic_gte/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_gte", "version": "1.0.0", - "description": "logic_gte plugin", + "description": "Check if a >= b", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_gte.py", + "files": ["logic_gte.py", "factory.py"], "metadata": { "plugin_type": "logic.gte", - "category": "logic" + "category": "logic", + "class": "LogicGte", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_in/factory.py b/workflow/plugins/python/logic/logic_in/factory.py new file mode 100644 index 000000000..4d2f8de5f --- /dev/null +++ b/workflow/plugins/python/logic/logic_in/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicIn plugin.""" + +from .logic_in import LogicIn + + +def create(): + return LogicIn() diff --git a/workflow/plugins/python/logic/logic_in/logic_in.py b/workflow/plugins/python/logic/logic_in/logic_in.py index afb014155..eac8a8fcc 100644 --- a/workflow/plugins/python/logic/logic_in/logic_in.py +++ b/workflow/plugins/python/logic/logic_in/logic_in.py @@ -1,8 +1,16 @@ """Workflow plugin: membership test.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicIn(NodeExecutor): """Check if value is in collection.""" - value = inputs.get("value") - collection = inputs.get("collection", []) - return {"result": value in collection} + + node_type = "logic.in" + category = "logic" + description = "Check if value is in collection" + + def execute(self, inputs, runtime=None): + value = inputs.get("value") + collection = inputs.get("collection", inputs.get("array", [])) + return {"result": value in collection} diff --git a/workflow/plugins/python/logic/logic_in/package.json b/workflow/plugins/python/logic/logic_in/package.json index d181e238d..a5cb5aae5 100644 --- a/workflow/plugins/python/logic/logic_in/package.json +++ b/workflow/plugins/python/logic/logic_in/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_in", "version": "1.0.0", - "description": "logic_in plugin", + "description": "Check if value is in collection", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_in.py", + "files": ["logic_in.py", "factory.py"], "metadata": { "plugin_type": "logic.in", - "category": "logic" + "category": "logic", + "class": "LogicIn", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_lt/factory.py b/workflow/plugins/python/logic/logic_lt/factory.py new file mode 100644 index 000000000..89bfdd6bd --- /dev/null +++ b/workflow/plugins/python/logic/logic_lt/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicLt plugin.""" + +from .logic_lt import LogicLt + + +def create(): + return LogicLt() diff --git a/workflow/plugins/python/logic/logic_lt/logic_lt.py b/workflow/plugins/python/logic/logic_lt/logic_lt.py index ce17ffe65..5394efd06 100644 --- a/workflow/plugins/python/logic/logic_lt/logic_lt.py +++ b/workflow/plugins/python/logic/logic_lt/logic_lt.py @@ -1,8 +1,16 @@ """Workflow plugin: less than comparison.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicLt(NodeExecutor): """Check if a < b.""" - a = inputs.get("a") - b = inputs.get("b") - return {"result": a < b} + + node_type = "logic.lt" + category = "logic" + description = "Check if a < b" + + def execute(self, inputs, runtime=None): + a = inputs.get("a") + b = inputs.get("b") + return {"result": a < b} diff --git a/workflow/plugins/python/logic/logic_lt/package.json b/workflow/plugins/python/logic/logic_lt/package.json index b72e27043..a0784d301 100644 --- a/workflow/plugins/python/logic/logic_lt/package.json +++ b/workflow/plugins/python/logic/logic_lt/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_lt", "version": "1.0.0", - "description": "logic_lt plugin", + "description": "Check if a < b", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_lt.py", + "files": ["logic_lt.py", "factory.py"], "metadata": { "plugin_type": "logic.lt", - "category": "logic" + "category": "logic", + "class": "LogicLt", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_lte/factory.py b/workflow/plugins/python/logic/logic_lte/factory.py new file mode 100644 index 000000000..862d2ef64 --- /dev/null +++ b/workflow/plugins/python/logic/logic_lte/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicLte plugin.""" + +from .logic_lte import LogicLte + + +def create(): + return LogicLte() diff --git a/workflow/plugins/python/logic/logic_lte/logic_lte.py b/workflow/plugins/python/logic/logic_lte/logic_lte.py index 2fdebdef7..478a7150a 100644 --- a/workflow/plugins/python/logic/logic_lte/logic_lte.py +++ b/workflow/plugins/python/logic/logic_lte/logic_lte.py @@ -1,8 +1,16 @@ """Workflow plugin: less than or equal comparison.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicLte(NodeExecutor): """Check if a <= b.""" - a = inputs.get("a") - b = inputs.get("b") - return {"result": a <= b} + + node_type = "logic.lte" + category = "logic" + description = "Check if a <= b" + + def execute(self, inputs, runtime=None): + a = inputs.get("a") + b = inputs.get("b") + return {"result": a <= b} diff --git a/workflow/plugins/python/logic/logic_lte/package.json b/workflow/plugins/python/logic/logic_lte/package.json index 5bdb09d94..a0786d1ba 100644 --- a/workflow/plugins/python/logic/logic_lte/package.json +++ b/workflow/plugins/python/logic/logic_lte/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_lte", "version": "1.0.0", - "description": "logic_lte plugin", + "description": "Check if a <= b", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_lte.py", + "files": ["logic_lte.py", "factory.py"], "metadata": { "plugin_type": "logic.lte", - "category": "logic" + "category": "logic", + "class": "LogicLte", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_or/factory.py b/workflow/plugins/python/logic/logic_or/factory.py new file mode 100644 index 000000000..28e04ec44 --- /dev/null +++ b/workflow/plugins/python/logic/logic_or/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicOr plugin.""" + +from .logic_or import LogicOr + + +def create(): + return LogicOr() diff --git a/workflow/plugins/python/logic/logic_or/logic_or.py b/workflow/plugins/python/logic/logic_or/logic_or.py index 69a7c26f4..e4857ff08 100644 --- a/workflow/plugins/python/logic/logic_or/logic_or.py +++ b/workflow/plugins/python/logic/logic_or/logic_or.py @@ -1,7 +1,15 @@ """Workflow plugin: logical OR.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicOr(NodeExecutor): """Perform logical OR on values.""" - values = inputs.get("values", []) - return {"result": any(values)} + + node_type = "logic.or" + category = "logic" + description = "Perform logical OR on values" + + def execute(self, inputs, runtime=None): + values = inputs.get("values", []) + return {"result": any(values)} diff --git a/workflow/plugins/python/logic/logic_or/package.json b/workflow/plugins/python/logic/logic_or/package.json index fda22e8a4..7bef53064 100644 --- a/workflow/plugins/python/logic/logic_or/package.json +++ b/workflow/plugins/python/logic/logic_or/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_or", "version": "1.0.0", - "description": "logic_or plugin", + "description": "Perform logical OR on values", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_or.py", + "files": ["logic_or.py", "factory.py"], "metadata": { "plugin_type": "logic.or", - "category": "logic" + "category": "logic", + "class": "LogicOr", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/logic/logic_xor/factory.py b/workflow/plugins/python/logic/logic_xor/factory.py new file mode 100644 index 000000000..5ca9ac56d --- /dev/null +++ b/workflow/plugins/python/logic/logic_xor/factory.py @@ -0,0 +1,7 @@ +"""Factory for LogicXor plugin.""" + +from .logic_xor import LogicXor + + +def create(): + return LogicXor() diff --git a/workflow/plugins/python/logic/logic_xor/logic_xor.py b/workflow/plugins/python/logic/logic_xor/logic_xor.py index 17f81d900..2931677ff 100644 --- a/workflow/plugins/python/logic/logic_xor/logic_xor.py +++ b/workflow/plugins/python/logic/logic_xor/logic_xor.py @@ -1,8 +1,16 @@ """Workflow plugin: logical XOR.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class LogicXor(NodeExecutor): """Perform logical XOR on two values.""" - a = inputs.get("a", False) - b = inputs.get("b", False) - return {"result": bool(a) != bool(b)} + + node_type = "logic.xor" + category = "logic" + description = "Perform logical XOR on two values" + + def execute(self, inputs, runtime=None): + a = bool(inputs.get("a", False)) + b = bool(inputs.get("b", False)) + return {"result": a != b} diff --git a/workflow/plugins/python/logic/logic_xor/package.json b/workflow/plugins/python/logic/logic_xor/package.json index 34c4c84e2..39c14d5c2 100644 --- a/workflow/plugins/python/logic/logic_xor/package.json +++ b/workflow/plugins/python/logic/logic_xor/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/logic_xor", "version": "1.0.0", - "description": "logic_xor plugin", + "description": "Perform logical XOR on two values", "author": "MetaBuilder", "license": "MIT", "keywords": ["logic", "workflow", "plugin"], "main": "logic_xor.py", + "files": ["logic_xor.py", "factory.py"], "metadata": { "plugin_type": "logic.xor", - "category": "logic" + "category": "logic", + "class": "LogicXor", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_abs/factory.py b/workflow/plugins/python/math/math_abs/factory.py new file mode 100644 index 000000000..9bdccd5fb --- /dev/null +++ b/workflow/plugins/python/math/math_abs/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathAbs plugin.""" + +from .math_abs import MathAbs + + +def create(): + return MathAbs() diff --git a/workflow/plugins/python/math/math_abs/math_abs.py b/workflow/plugins/python/math/math_abs/math_abs.py index 344054ad8..077d4add1 100644 --- a/workflow/plugins/python/math/math_abs/math_abs.py +++ b/workflow/plugins/python/math/math_abs/math_abs.py @@ -1,7 +1,18 @@ """Workflow plugin: absolute value.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Calculate absolute value.""" - value = inputs.get("value", 0) - return {"result": abs(value)} + +class MathAbs(NodeExecutor): + """Get absolute value.""" + + node_type = "math.abs" + category = "math" + description = "Get absolute value" + + def execute(self, inputs, runtime=None): + try: + value = float(inputs.get("value", 0)) + return {"result": abs(value)} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_abs/package.json b/workflow/plugins/python/math/math_abs/package.json index e1be6fc5a..d2a287b3c 100644 --- a/workflow/plugins/python/math/math_abs/package.json +++ b/workflow/plugins/python/math/math_abs/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_abs", "version": "1.0.0", - "description": "math_abs plugin", + "description": "Get absolute value", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_abs.py", + "files": ["math_abs.py", "factory.py"], "metadata": { "plugin_type": "math.abs", - "category": "math" + "category": "math", + "class": "MathAbs", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_add/factory.py b/workflow/plugins/python/math/math_add/factory.py new file mode 100644 index 000000000..1d7de1f19 --- /dev/null +++ b/workflow/plugins/python/math/math_add/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathAdd plugin.""" + +from .math_add import MathAdd + + +def create(): + return MathAdd() diff --git a/workflow/plugins/python/math/math_add/math_add.py b/workflow/plugins/python/math/math_add/math_add.py index 726dea252..07c34218a 100644 --- a/workflow/plugins/python/math/math_add/math_add.py +++ b/workflow/plugins/python/math/math_add/math_add.py @@ -1,7 +1,19 @@ """Workflow plugin: add numbers.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Add two or more numbers.""" - numbers = inputs.get("numbers", []) - return {"result": sum(numbers)} + +class MathAdd(NodeExecutor): + """Add numbers together.""" + + node_type = "math.add" + category = "math" + description = "Add numbers together" + + def execute(self, inputs, runtime=None): + numbers = inputs.get("numbers", inputs.get("values", [])) + try: + result = sum(float(n) for n in numbers) + return {"result": result} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_add/package.json b/workflow/plugins/python/math/math_add/package.json index 04ece07fb..58f13b41b 100644 --- a/workflow/plugins/python/math/math_add/package.json +++ b/workflow/plugins/python/math/math_add/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_add", "version": "1.0.0", - "description": "math_add plugin", + "description": "Add numbers together", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_add.py", + "files": ["math_add.py", "factory.py"], "metadata": { "plugin_type": "math.add", - "category": "math" + "category": "math", + "class": "MathAdd", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_divide/factory.py b/workflow/plugins/python/math/math_divide/factory.py new file mode 100644 index 000000000..e14fc74aa --- /dev/null +++ b/workflow/plugins/python/math/math_divide/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathDivide plugin.""" + +from .math_divide import MathDivide + + +def create(): + return MathDivide() diff --git a/workflow/plugins/python/math/math_divide/math_divide.py b/workflow/plugins/python/math/math_divide/math_divide.py index aff10336b..935ba5809 100644 --- a/workflow/plugins/python/math/math_divide/math_divide.py +++ b/workflow/plugins/python/math/math_divide/math_divide.py @@ -1,12 +1,21 @@ """Workflow plugin: divide numbers.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class MathDivide(NodeExecutor): """Divide a by b.""" - a = inputs.get("a", 0) - b = inputs.get("b", 1) - if b == 0: - return {"result": None, "error": "Division by zero"} + node_type = "math.divide" + category = "math" + description = "Divide a by b" - return {"result": a / b} + def execute(self, inputs, runtime=None): + try: + a = float(inputs.get("a", 0)) + b = float(inputs.get("b", 0)) + if b == 0: + return {"error": "Division by zero"} + return {"result": a / b} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_divide/package.json b/workflow/plugins/python/math/math_divide/package.json index 68853ea4f..b1c9e928c 100644 --- a/workflow/plugins/python/math/math_divide/package.json +++ b/workflow/plugins/python/math/math_divide/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_divide", "version": "1.0.0", - "description": "math_divide plugin", + "description": "Divide a by b", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_divide.py", + "files": ["math_divide.py", "factory.py"], "metadata": { "plugin_type": "math.divide", - "category": "math" + "category": "math", + "class": "MathDivide", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_max/factory.py b/workflow/plugins/python/math/math_max/factory.py new file mode 100644 index 000000000..049539af1 --- /dev/null +++ b/workflow/plugins/python/math/math_max/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathMax plugin.""" + +from .math_max import MathMax + + +def create(): + return MathMax() diff --git a/workflow/plugins/python/math/math_max/math_max.py b/workflow/plugins/python/math/math_max/math_max.py index 42e2728aa..85c250c58 100644 --- a/workflow/plugins/python/math/math_max/math_max.py +++ b/workflow/plugins/python/math/math_max/math_max.py @@ -1,11 +1,19 @@ """Workflow plugin: maximum value.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Find maximum value in numbers.""" - numbers = inputs.get("numbers", []) - if not numbers: - return {"result": None} +class MathMax(NodeExecutor): + """Get maximum of values.""" - return {"result": max(numbers)} + node_type = "math.max" + category = "math" + description = "Get maximum of values" + + def execute(self, inputs, runtime=None): + numbers = inputs.get("numbers", inputs.get("values", [])) + try: + result = max(float(n) for n in numbers) + return {"result": result} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_max/package.json b/workflow/plugins/python/math/math_max/package.json index 989cd0700..d0473fe92 100644 --- a/workflow/plugins/python/math/math_max/package.json +++ b/workflow/plugins/python/math/math_max/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_max", "version": "1.0.0", - "description": "math_max plugin", + "description": "Get maximum of values", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_max.py", + "files": ["math_max.py", "factory.py"], "metadata": { "plugin_type": "math.max", - "category": "math" + "category": "math", + "class": "MathMax", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_min/factory.py b/workflow/plugins/python/math/math_min/factory.py new file mode 100644 index 000000000..68e3b5fd4 --- /dev/null +++ b/workflow/plugins/python/math/math_min/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathMin plugin.""" + +from .math_min import MathMin + + +def create(): + return MathMin() diff --git a/workflow/plugins/python/math/math_min/math_min.py b/workflow/plugins/python/math/math_min/math_min.py index 71b883109..77bc00395 100644 --- a/workflow/plugins/python/math/math_min/math_min.py +++ b/workflow/plugins/python/math/math_min/math_min.py @@ -1,11 +1,19 @@ """Workflow plugin: minimum value.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Find minimum value in numbers.""" - numbers = inputs.get("numbers", []) - if not numbers: - return {"result": None} +class MathMin(NodeExecutor): + """Get minimum of values.""" - return {"result": min(numbers)} + node_type = "math.min" + category = "math" + description = "Get minimum of values" + + def execute(self, inputs, runtime=None): + numbers = inputs.get("numbers", inputs.get("values", [])) + try: + result = min(float(n) for n in numbers) + return {"result": result} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_min/package.json b/workflow/plugins/python/math/math_min/package.json index 0fb9bd50e..0b58faa56 100644 --- a/workflow/plugins/python/math/math_min/package.json +++ b/workflow/plugins/python/math/math_min/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_min", "version": "1.0.0", - "description": "math_min plugin", + "description": "Get minimum of values", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_min.py", + "files": ["math_min.py", "factory.py"], "metadata": { "plugin_type": "math.min", - "category": "math" + "category": "math", + "class": "MathMin", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_modulo/factory.py b/workflow/plugins/python/math/math_modulo/factory.py new file mode 100644 index 000000000..89eafaa25 --- /dev/null +++ b/workflow/plugins/python/math/math_modulo/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathModulo plugin.""" + +from .math_modulo import MathModulo + + +def create(): + return MathModulo() diff --git a/workflow/plugins/python/math/math_modulo/math_modulo.py b/workflow/plugins/python/math/math_modulo/math_modulo.py index f64578890..949f66004 100644 --- a/workflow/plugins/python/math/math_modulo/math_modulo.py +++ b/workflow/plugins/python/math/math_modulo/math_modulo.py @@ -1,12 +1,21 @@ """Workflow plugin: modulo operation.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class MathModulo(NodeExecutor): """Calculate a modulo b.""" - a = inputs.get("a", 0) - b = inputs.get("b", 1) - if b == 0: - return {"result": None, "error": "Modulo by zero"} + node_type = "math.modulo" + category = "math" + description = "Calculate a modulo b" - return {"result": a % b} + def execute(self, inputs, runtime=None): + try: + a = float(inputs.get("a", 0)) + b = float(inputs.get("b", 0)) + if b == 0: + return {"error": "Modulo by zero"} + return {"result": a % b} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_modulo/package.json b/workflow/plugins/python/math/math_modulo/package.json index 82a7701f2..2043fe2d8 100644 --- a/workflow/plugins/python/math/math_modulo/package.json +++ b/workflow/plugins/python/math/math_modulo/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_modulo", "version": "1.0.0", - "description": "math_modulo plugin", + "description": "Calculate a modulo b", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_modulo.py", + "files": ["math_modulo.py", "factory.py"], "metadata": { "plugin_type": "math.modulo", - "category": "math" + "category": "math", + "class": "MathModulo", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_multiply/factory.py b/workflow/plugins/python/math/math_multiply/factory.py new file mode 100644 index 000000000..8b6c938c4 --- /dev/null +++ b/workflow/plugins/python/math/math_multiply/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathMultiply plugin.""" + +from .math_multiply import MathMultiply + + +def create(): + return MathMultiply() diff --git a/workflow/plugins/python/math/math_multiply/math_multiply.py b/workflow/plugins/python/math/math_multiply/math_multiply.py index ed4cfc529..5713f3c55 100644 --- a/workflow/plugins/python/math/math_multiply/math_multiply.py +++ b/workflow/plugins/python/math/math_multiply/math_multiply.py @@ -1,10 +1,22 @@ """Workflow plugin: multiply numbers.""" +from functools import reduce +from operator import mul -def run(_runtime, inputs): - """Multiply two or more numbers.""" - numbers = inputs.get("numbers", []) - result = 1 - for num in numbers: - result *= num - return {"result": result} +from ...base import NodeExecutor + + +class MathMultiply(NodeExecutor): + """Multiply numbers together.""" + + node_type = "math.multiply" + category = "math" + description = "Multiply numbers together" + + def execute(self, inputs, runtime=None): + numbers = inputs.get("numbers", inputs.get("values", [])) + try: + result = reduce(mul, (float(n) for n in numbers), 1) + return {"result": result} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_multiply/package.json b/workflow/plugins/python/math/math_multiply/package.json index 5133fbe89..410c5bb81 100644 --- a/workflow/plugins/python/math/math_multiply/package.json +++ b/workflow/plugins/python/math/math_multiply/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_multiply", "version": "1.0.0", - "description": "math_multiply plugin", + "description": "Multiply numbers together", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_multiply.py", + "files": ["math_multiply.py", "factory.py"], "metadata": { "plugin_type": "math.multiply", - "category": "math" + "category": "math", + "class": "MathMultiply", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_power/factory.py b/workflow/plugins/python/math/math_power/factory.py new file mode 100644 index 000000000..39d884bf2 --- /dev/null +++ b/workflow/plugins/python/math/math_power/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathPower plugin.""" + +from .math_power import MathPower + + +def create(): + return MathPower() diff --git a/workflow/plugins/python/math/math_power/math_power.py b/workflow/plugins/python/math/math_power/math_power.py index 931126898..09a5074ba 100644 --- a/workflow/plugins/python/math/math_power/math_power.py +++ b/workflow/plugins/python/math/math_power/math_power.py @@ -1,8 +1,19 @@ """Workflow plugin: power operation.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Calculate a to the power of b.""" - a = inputs.get("a", 0) - b = inputs.get("b", 1) - return {"result": a ** b} + +class MathPower(NodeExecutor): + """Raise base to exponent power.""" + + node_type = "math.power" + category = "math" + description = "Raise base to exponent power" + + def execute(self, inputs, runtime=None): + try: + a = float(inputs.get("a", inputs.get("base", 0))) + b = float(inputs.get("b", inputs.get("exponent", 0))) + return {"result": a**b} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_power/package.json b/workflow/plugins/python/math/math_power/package.json index adc23d23e..40d3d5501 100644 --- a/workflow/plugins/python/math/math_power/package.json +++ b/workflow/plugins/python/math/math_power/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_power", "version": "1.0.0", - "description": "math_power plugin", + "description": "Raise base to exponent power", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_power.py", + "files": ["math_power.py", "factory.py"], "metadata": { "plugin_type": "math.power", - "category": "math" + "category": "math", + "class": "MathPower", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_round/factory.py b/workflow/plugins/python/math/math_round/factory.py new file mode 100644 index 000000000..d17b4ecb4 --- /dev/null +++ b/workflow/plugins/python/math/math_round/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathRound plugin.""" + +from .math_round import MathRound + + +def create(): + return MathRound() diff --git a/workflow/plugins/python/math/math_round/math_round.py b/workflow/plugins/python/math/math_round/math_round.py index 4fd81f5e8..9bc324bed 100644 --- a/workflow/plugins/python/math/math_round/math_round.py +++ b/workflow/plugins/python/math/math_round/math_round.py @@ -1,8 +1,19 @@ """Workflow plugin: round number.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Round number to specified precision.""" - value = inputs.get("value", 0) - precision = inputs.get("precision", 0) - return {"result": round(value, precision)} + +class MathRound(NodeExecutor): + """Round to specified decimals.""" + + node_type = "math.round" + category = "math" + description = "Round to specified decimals" + + def execute(self, inputs, runtime=None): + try: + value = float(inputs.get("value", 0)) + decimals = int(inputs.get("decimals", inputs.get("precision", 0))) + return {"result": round(value, decimals)} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_round/package.json b/workflow/plugins/python/math/math_round/package.json index d2de9587f..ef634e509 100644 --- a/workflow/plugins/python/math/math_round/package.json +++ b/workflow/plugins/python/math/math_round/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_round", "version": "1.0.0", - "description": "math_round plugin", + "description": "Round to specified decimals", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_round.py", + "files": ["math_round.py", "factory.py"], "metadata": { "plugin_type": "math.round", - "category": "math" + "category": "math", + "class": "MathRound", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/math/math_subtract/factory.py b/workflow/plugins/python/math/math_subtract/factory.py new file mode 100644 index 000000000..ce5763b5d --- /dev/null +++ b/workflow/plugins/python/math/math_subtract/factory.py @@ -0,0 +1,7 @@ +"""Factory for MathSubtract plugin.""" + +from .math_subtract import MathSubtract + + +def create(): + return MathSubtract() diff --git a/workflow/plugins/python/math/math_subtract/math_subtract.py b/workflow/plugins/python/math/math_subtract/math_subtract.py index 1898efd7d..e8fe0b2e4 100644 --- a/workflow/plugins/python/math/math_subtract/math_subtract.py +++ b/workflow/plugins/python/math/math_subtract/math_subtract.py @@ -1,8 +1,19 @@ """Workflow plugin: subtract numbers.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class MathSubtract(NodeExecutor): """Subtract b from a.""" - a = inputs.get("a", 0) - b = inputs.get("b", 0) - return {"result": a - b} + + node_type = "math.subtract" + category = "math" + description = "Subtract b from a" + + def execute(self, inputs, runtime=None): + try: + a = float(inputs.get("a", 0)) + b = float(inputs.get("b", 0)) + return {"result": a - b} + except (ValueError, TypeError) as e: + return {"error": str(e)} diff --git a/workflow/plugins/python/math/math_subtract/package.json b/workflow/plugins/python/math/math_subtract/package.json index 855dc4f86..69fd8e826 100644 --- a/workflow/plugins/python/math/math_subtract/package.json +++ b/workflow/plugins/python/math/math_subtract/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/math_subtract", "version": "1.0.0", - "description": "math_subtract plugin", + "description": "Subtract b from a", "author": "MetaBuilder", "license": "MIT", "keywords": ["math", "workflow", "plugin"], "main": "math_subtract.py", + "files": ["math_subtract.py", "factory.py"], "metadata": { "plugin_type": "math.subtract", - "category": "math" + "category": "math", + "class": "MathSubtract", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/notifications/notifications_all/factory.py b/workflow/plugins/python/notifications/notifications_all/factory.py new file mode 100644 index 000000000..eb623523a --- /dev/null +++ b/workflow/plugins/python/notifications/notifications_all/factory.py @@ -0,0 +1,7 @@ +"""Factory for NotificationsAll plugin.""" + +from .notifications_all import NotificationsAll + + +def create(): + return NotificationsAll() diff --git a/workflow/plugins/python/notifications/notifications_all/notifications_all.py b/workflow/plugins/python/notifications/notifications_all/notifications_all.py index 345013dec..bc2de1f62 100644 --- a/workflow/plugins/python/notifications/notifications_all/notifications_all.py +++ b/workflow/plugins/python/notifications/notifications_all/notifications_all.py @@ -1,33 +1,45 @@ """Workflow plugin: send notification to all channels.""" -import os + import logging +from ...base import NodeExecutor + logger = logging.getLogger("metabuilder.notifications") -def run(runtime, inputs): - """Send a notification to all configured channels (Slack and Discord). +class NotificationsAll(NodeExecutor): + """Send a notification to all configured channels (Slack and Discord).""" - Inputs: - message: The message to send to all channels + node_type = "notifications.all" + category = "notifications" + description = "Send a notification to all configured channels (Slack and Discord)" - Returns: - dict: Contains success status for all channels - """ - message = inputs.get("message", "") + def execute(self, inputs, runtime=None): + """Send a notification to all configured channels (Slack and Discord). - # Import sibling plugins - from . import notifications_slack, notifications_discord + Inputs: + message: The message to send to all channels - # Send to Slack - slack_result = notifications_slack.run(runtime, {"message": message}) + Returns: + dict: Contains success status for all channels + """ + message = inputs.get("message", "") - # Send to Discord - discord_result = notifications_discord.run(runtime, {"message": message}) + # Import sibling plugins + from ..notifications_slack.factory import create as create_slack + from ..notifications_discord.factory import create as create_discord - return { - "success": True, - "message": "Notifications sent to all channels", - "slack": slack_result, - "discord": discord_result - } + # Send to Slack + slack_plugin = create_slack() + slack_result = slack_plugin.execute({"message": message}, runtime) + + # Send to Discord + discord_plugin = create_discord() + discord_result = discord_plugin.execute({"message": message}, runtime) + + return { + "success": True, + "message": "Notifications sent to all channels", + "slack": slack_result, + "discord": discord_result + } diff --git a/workflow/plugins/python/notifications/notifications_all/package.json b/workflow/plugins/python/notifications/notifications_all/package.json index eede62cf9..e593ead5c 100644 --- a/workflow/plugins/python/notifications/notifications_all/package.json +++ b/workflow/plugins/python/notifications/notifications_all/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/notifications_all", "version": "1.0.0", - "description": "notifications_all plugin", + "description": "Send notifications to all configured channels (Slack and Discord)", "author": "MetaBuilder", "license": "MIT", "keywords": ["notifications", "workflow", "plugin"], "main": "notifications_all.py", + "files": ["notifications_all.py", "factory.py"], "metadata": { "plugin_type": "notifications.all", - "category": "notifications" + "category": "notifications", + "class": "NotificationsAll", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/notifications/notifications_discord/factory.py b/workflow/plugins/python/notifications/notifications_discord/factory.py new file mode 100644 index 000000000..25ec6e7b9 --- /dev/null +++ b/workflow/plugins/python/notifications/notifications_discord/factory.py @@ -0,0 +1,7 @@ +"""Factory for NotificationsDiscord plugin.""" + +from .notifications_discord import NotificationsDiscord + + +def create(): + return NotificationsDiscord() diff --git a/workflow/plugins/python/notifications/notifications_discord/notifications_discord.py b/workflow/plugins/python/notifications/notifications_discord/notifications_discord.py index 412ff7fc9..007761f83 100644 --- a/workflow/plugins/python/notifications/notifications_discord/notifications_discord.py +++ b/workflow/plugins/python/notifications/notifications_discord/notifications_discord.py @@ -1,8 +1,11 @@ """Workflow plugin: send Discord notification.""" + import os import logging import asyncio +from ...base import NodeExecutor + logger = logging.getLogger("metabuilder.notifications") @@ -27,41 +30,48 @@ async def _send_discord_notification_async(message: str, token: str, intents, ch raise -def run(runtime, inputs): - """Send a notification to Discord. +class NotificationsDiscord(NodeExecutor): + """Send a notification to Discord.""" - Inputs: - message: The message to send - channel_id: Optional channel ID (defaults to DISCORD_CHANNEL_ID env var) + node_type = "notifications.discord" + category = "notifications" + description = "Send a notification to Discord" - Returns: - dict: Contains success status and any error message - """ - message = inputs.get("message", "") - channel_id = inputs.get("channel_id") or os.environ.get("DISCORD_CHANNEL_ID") + def execute(self, inputs, runtime=None): + """Send a notification to Discord. - token = runtime.context.get("discord_token") - intents = runtime.context.get("discord_intents") + Inputs: + message: The message to send + channel_id: Optional channel ID (defaults to DISCORD_CHANNEL_ID env var) - if not token: - logger.warning("Discord notification skipped: Discord client not initialized.") - return { - "success": False, - "skipped": True, - "error": "Discord client not initialized" - } + Returns: + dict: Contains success status and any error message + """ + message = inputs.get("message", "") + channel_id = inputs.get("channel_id") or os.environ.get("DISCORD_CHANNEL_ID") - if not channel_id: - logger.warning("Discord notification skipped: DISCORD_CHANNEL_ID missing.") - return { - "success": False, - "skipped": True, - "error": "DISCORD_CHANNEL_ID missing" - } + token = runtime.context.get("discord_token") if runtime else None + intents = runtime.context.get("discord_intents") if runtime else None - try: - asyncio.run(_send_discord_notification_async(message, token, intents, channel_id)) - return {"success": True, "message": "Discord notification sent"} - except Exception as e: - logger.error(f"Error running Discord notification: {e}") - return {"success": False, "error": str(e)} + if not token: + logger.warning("Discord notification skipped: Discord client not initialized.") + return { + "success": False, + "skipped": True, + "error": "Discord client not initialized" + } + + if not channel_id: + logger.warning("Discord notification skipped: DISCORD_CHANNEL_ID missing.") + return { + "success": False, + "skipped": True, + "error": "DISCORD_CHANNEL_ID missing" + } + + try: + asyncio.run(_send_discord_notification_async(message, token, intents, channel_id)) + return {"success": True, "message": "Discord notification sent"} + except Exception as e: + logger.error(f"Error running Discord notification: {e}") + return {"success": False, "error": str(e)} diff --git a/workflow/plugins/python/notifications/notifications_discord/package.json b/workflow/plugins/python/notifications/notifications_discord/package.json index fbf9fb005..7f9abe196 100644 --- a/workflow/plugins/python/notifications/notifications_discord/package.json +++ b/workflow/plugins/python/notifications/notifications_discord/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/notifications_discord", "version": "1.0.0", - "description": "notifications_discord plugin", + "description": "Send notifications to Discord channels", "author": "MetaBuilder", "license": "MIT", "keywords": ["notifications", "workflow", "plugin"], "main": "notifications_discord.py", + "files": ["notifications_discord.py", "factory.py"], "metadata": { "plugin_type": "notifications.discord", - "category": "notifications" + "category": "notifications", + "class": "NotificationsDiscord", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/notifications/notifications_slack/factory.py b/workflow/plugins/python/notifications/notifications_slack/factory.py new file mode 100644 index 000000000..37f2e9d3a --- /dev/null +++ b/workflow/plugins/python/notifications/notifications_slack/factory.py @@ -0,0 +1,7 @@ +"""Factory for NotificationsSlack plugin.""" + +from .notifications_slack import NotificationsSlack + + +def create(): + return NotificationsSlack() diff --git a/workflow/plugins/python/notifications/notifications_slack/notifications_slack.py b/workflow/plugins/python/notifications/notifications_slack/notifications_slack.py index 711406f43..f942b3233 100644 --- a/workflow/plugins/python/notifications/notifications_slack/notifications_slack.py +++ b/workflow/plugins/python/notifications/notifications_slack/notifications_slack.py @@ -1,46 +1,56 @@ """Workflow plugin: send Slack notification.""" + import os import logging +from ...base import NodeExecutor + logger = logging.getLogger("metabuilder.notifications") -def run(runtime, inputs): - """Send a notification to Slack. +class NotificationsSlack(NodeExecutor): + """Send a notification to Slack.""" - Inputs: - message: The message to send - channel: Optional channel (defaults to SLACK_CHANNEL env var) + node_type = "notifications.slack" + category = "notifications" + description = "Send a notification to Slack" - Returns: - dict: Contains success status and any error message - """ - message = inputs.get("message", "") - channel = inputs.get("channel") or os.environ.get("SLACK_CHANNEL") + def execute(self, inputs, runtime=None): + """Send a notification to Slack. - client = runtime.context.get("slack_client") + Inputs: + message: The message to send + channel: Optional channel (defaults to SLACK_CHANNEL env var) - if not client: - logger.warning("Slack notification skipped: Slack client not initialized.") - return { - "success": False, - "skipped": True, - "error": "Slack client not initialized" - } + Returns: + dict: Contains success status and any error message + """ + message = inputs.get("message", "") + channel = inputs.get("channel") or os.environ.get("SLACK_CHANNEL") - if not channel: - logger.warning("Slack notification skipped: SLACK_CHANNEL missing.") - return { - "success": False, - "skipped": True, - "error": "SLACK_CHANNEL missing" - } + client = runtime.context.get("slack_client") if runtime else None - try: - from slack_sdk.errors import SlackApiError - client.chat_postMessage(channel=channel, text=message) - logger.info("Slack notification sent successfully.") - return {"success": True, "message": "Slack notification sent"} - except SlackApiError as e: - logger.error(f"Error sending Slack notification: {e}") - return {"success": False, "error": str(e)} + if not client: + logger.warning("Slack notification skipped: Slack client not initialized.") + return { + "success": False, + "skipped": True, + "error": "Slack client not initialized" + } + + if not channel: + logger.warning("Slack notification skipped: SLACK_CHANNEL missing.") + return { + "success": False, + "skipped": True, + "error": "SLACK_CHANNEL missing" + } + + try: + from slack_sdk.errors import SlackApiError + client.chat_postMessage(channel=channel, text=message) + logger.info("Slack notification sent successfully.") + return {"success": True, "message": "Slack notification sent"} + except SlackApiError as e: + logger.error(f"Error sending Slack notification: {e}") + return {"success": False, "error": str(e)} diff --git a/workflow/plugins/python/notifications/notifications_slack/package.json b/workflow/plugins/python/notifications/notifications_slack/package.json index 64890b1df..93bc1d555 100644 --- a/workflow/plugins/python/notifications/notifications_slack/package.json +++ b/workflow/plugins/python/notifications/notifications_slack/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/notifications_slack", "version": "1.0.0", - "description": "notifications_slack plugin", + "description": "Send notifications to Slack channels", "author": "MetaBuilder", "license": "MIT", "keywords": ["notifications", "workflow", "plugin"], "main": "notifications_slack.py", + "files": ["notifications_slack.py", "factory.py"], "metadata": { "plugin_type": "notifications.slack", - "category": "notifications" + "category": "notifications", + "class": "NotificationsSlack", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/registry.py b/workflow/plugins/python/registry.py new file mode 100644 index 000000000..09eeea06f --- /dev/null +++ b/workflow/plugins/python/registry.py @@ -0,0 +1,136 @@ +""" +Plugin registry for discovering and instantiating workflow plugins. + +Plugins export NODE_TYPE, CATEGORY, DESCRIPTION, and impl - no side effects. +The registry handles lazy instantiation of executors when needed. +""" + +import importlib +import pkgutil +from pathlib import Path +from typing import Any, Callable, Dict, Optional + +from .base import NodeExecutor +from .factory import create_plugin + + +class PluginRegistry: + """Registry for workflow plugins with lazy instantiation.""" + + def __init__(self): + self._plugins: Dict[str, dict] = {} # node_type -> plugin metadata + self._executors: Dict[str, NodeExecutor] = {} # node_type -> executor (lazy) + + def register( + self, + node_type: str, + category: str, + description: str, + impl: Callable[[Dict[str, Any], Any], Dict[str, Any]], + ) -> None: + """Register a plugin without instantiating it.""" + self._plugins[node_type] = { + "node_type": node_type, + "category": category, + "description": description, + "impl": impl, + } + + def get_executor(self, node_type: str) -> Optional[NodeExecutor]: + """Get or create an executor for a plugin (lazy instantiation).""" + if node_type in self._executors: + return self._executors[node_type] + + if node_type not in self._plugins: + return None + + plugin = self._plugins[node_type] + executor = create_plugin( + plugin["node_type"], + plugin["category"], + plugin["description"], + plugin["impl"], + ) + self._executors[node_type] = executor + return executor + + def run( + self, node_type: str, runtime: Any, inputs: Dict[str, Any] + ) -> Dict[str, Any]: + """Run a plugin by node type.""" + executor = self.get_executor(node_type) + if executor is None: + return {"error": f"Unknown plugin: {node_type}"} + return executor.run(runtime, inputs) + + def list_plugins(self) -> Dict[str, dict]: + """List all registered plugins.""" + return { + node_type: { + "node_type": p["node_type"], + "category": p["category"], + "description": p["description"], + } + for node_type, p in self._plugins.items() + } + + def discover(self, package_path: str = None) -> None: + """ + Discover and register all plugins from the plugins package. + + Scans for modules with NODE_TYPE, CATEGORY, DESCRIPTION, and impl. + """ + if package_path is None: + package_path = str(Path(__file__).parent) + + # Walk through all subdirectories + for category_dir in Path(package_path).iterdir(): + if not category_dir.is_dir() or category_dir.name.startswith("_"): + continue + + # Skip non-plugin directories + if category_dir.name in ("__pycache__",): + continue + + # Each subdirectory in category is a plugin + for plugin_dir in category_dir.iterdir(): + if not plugin_dir.is_dir() or plugin_dir.name.startswith("_"): + continue + + # Look for the plugin module + plugin_file = plugin_dir / f"{plugin_dir.name}.py" + if not plugin_file.exists(): + continue + + try: + # Import the module + module_name = f"workflow.plugins.python.{category_dir.name}.{plugin_dir.name}.{plugin_dir.name}" + module = importlib.import_module(module_name) + + # Check for required exports + if hasattr(module, "NODE_TYPE") and hasattr(module, "impl"): + self.register( + node_type=module.NODE_TYPE, + category=getattr(module, "CATEGORY", category_dir.name), + description=getattr(module, "DESCRIPTION", ""), + impl=module.impl, + ) + except Exception: + # Skip plugins that fail to load + pass + + +# Global registry instance +registry = PluginRegistry() + + +def get_registry() -> PluginRegistry: + """Get the global plugin registry.""" + return registry + + +def run_plugin( + node_type: str, runtime: Any, inputs: Dict[str, Any] +) -> Dict[str, Any]: + """Convenience function to run a plugin.""" + return registry.run(node_type, runtime, inputs) diff --git a/workflow/plugins/python/string/string_concat/factory.py b/workflow/plugins/python/string/string_concat/factory.py new file mode 100644 index 000000000..ab321df7a --- /dev/null +++ b/workflow/plugins/python/string/string_concat/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringConcat plugin.""" + +from .string_concat import StringConcat + + +def create(): + return StringConcat() diff --git a/workflow/plugins/python/string/string_concat/package.json b/workflow/plugins/python/string/string_concat/package.json index 8cc7e98df..45a10284b 100644 --- a/workflow/plugins/python/string/string_concat/package.json +++ b/workflow/plugins/python/string/string_concat/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_concat", "version": "1.0.0", - "description": "string_concat plugin", + "description": "Concatenate multiple strings", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_concat.py", + "files": ["string_concat.py", "factory.py"], "metadata": { "plugin_type": "string.concat", - "category": "string" + "category": "string", + "class": "StringConcat", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_concat/string_concat.py b/workflow/plugins/python/string/string_concat/string_concat.py index 2935b2013..c28af5d59 100644 --- a/workflow/plugins/python/string/string_concat/string_concat.py +++ b/workflow/plugins/python/string/string_concat/string_concat.py @@ -1,10 +1,16 @@ """Workflow plugin: concatenate strings.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class StringConcat(NodeExecutor): """Concatenate multiple strings.""" - strings = inputs.get("strings", []) - separator = inputs.get("separator", "") - str_list = [str(s) for s in strings] - return {"result": separator.join(str_list)} + node_type = "string.concat" + category = "string" + description = "Concatenate multiple strings" + + def execute(self, inputs, runtime=None): + separator = inputs.get("separator", "") + strings = inputs.get("strings", inputs.get("values", [])) + return {"result": separator.join(str(s) for s in strings)} diff --git a/workflow/plugins/python/string/string_format/factory.py b/workflow/plugins/python/string/string_format/factory.py new file mode 100644 index 000000000..c82c92c33 --- /dev/null +++ b/workflow/plugins/python/string/string_format/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringFormat plugin.""" + +from .string_format import StringFormat + + +def create(): + return StringFormat() diff --git a/workflow/plugins/python/string/string_format/package.json b/workflow/plugins/python/string/string_format/package.json index bec06755b..84c20436a 100644 --- a/workflow/plugins/python/string/string_format/package.json +++ b/workflow/plugins/python/string/string_format/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_format", "version": "1.0.0", - "description": "string_format plugin", + "description": "Format string with variables", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_format.py", + "files": ["string_format.py", "factory.py"], "metadata": { "plugin_type": "string.format", - "category": "string" + "category": "string", + "class": "StringFormat", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_format/string_format.py b/workflow/plugins/python/string/string_format/string_format.py index e913d2f5c..c92772644 100644 --- a/workflow/plugins/python/string/string_format/string_format.py +++ b/workflow/plugins/python/string/string_format/string_format.py @@ -1,13 +1,19 @@ """Workflow plugin: format string with variables.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class StringFormat(NodeExecutor): """Format string with variables.""" - template = inputs.get("template", "") - variables = inputs.get("variables", {}) - try: - result = template.format(**variables) - return {"result": result} - except (KeyError, ValueError) as e: - return {"result": template, "error": str(e)} + node_type = "string.format" + category = "string" + description = "Format string with variables" + + def execute(self, inputs, runtime=None): + template = inputs.get("template", "") + variables = inputs.get("variables", {}) + try: + return {"result": template.format(**variables)} + except (KeyError, ValueError) as e: + return {"result": template, "error": str(e)} diff --git a/workflow/plugins/python/string/string_length/factory.py b/workflow/plugins/python/string/string_length/factory.py new file mode 100644 index 000000000..6b2ab424e --- /dev/null +++ b/workflow/plugins/python/string/string_length/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringLength plugin.""" + +from .string_length import StringLength + + +def create(): + return StringLength() diff --git a/workflow/plugins/python/string/string_length/package.json b/workflow/plugins/python/string/string_length/package.json index cf2903fe9..89b026503 100644 --- a/workflow/plugins/python/string/string_length/package.json +++ b/workflow/plugins/python/string/string_length/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_length", "version": "1.0.0", - "description": "string_length plugin", + "description": "Get string length", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_length.py", + "files": ["string_length.py", "factory.py"], "metadata": { "plugin_type": "string.length", - "category": "string" + "category": "string", + "class": "StringLength", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_length/string_length.py b/workflow/plugins/python/string/string_length/string_length.py index e459e5d18..8e921a984 100644 --- a/workflow/plugins/python/string/string_length/string_length.py +++ b/workflow/plugins/python/string/string_length/string_length.py @@ -1,7 +1,15 @@ """Workflow plugin: get string length.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Get length of a string.""" - text = inputs.get("text", "") - return {"result": len(text)} + +class StringLength(NodeExecutor): + """Get string length.""" + + node_type = "string.length" + category = "string" + description = "Get string length" + + def execute(self, inputs, runtime=None): + value = str(inputs.get("value", inputs.get("text", ""))) + return {"result": len(value)} diff --git a/workflow/plugins/python/string/string_lower/factory.py b/workflow/plugins/python/string/string_lower/factory.py new file mode 100644 index 000000000..915dedb73 --- /dev/null +++ b/workflow/plugins/python/string/string_lower/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringLower plugin.""" + +from .string_lower import StringLower + + +def create(): + return StringLower() diff --git a/workflow/plugins/python/string/string_lower/package.json b/workflow/plugins/python/string/string_lower/package.json index 8a605599d..dd7e7de07 100644 --- a/workflow/plugins/python/string/string_lower/package.json +++ b/workflow/plugins/python/string/string_lower/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_lower", "version": "1.0.0", - "description": "string_lower plugin", + "description": "Convert string to lowercase", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_lower.py", + "files": ["string_lower.py", "factory.py"], "metadata": { "plugin_type": "string.lower", - "category": "string" + "category": "string", + "class": "StringLower", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_lower/string_lower.py b/workflow/plugins/python/string/string_lower/string_lower.py index 73e8f7357..92d8637bb 100644 --- a/workflow/plugins/python/string/string_lower/string_lower.py +++ b/workflow/plugins/python/string/string_lower/string_lower.py @@ -1,7 +1,15 @@ """Workflow plugin: convert string to lowercase.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class StringLower(NodeExecutor): """Convert string to lowercase.""" - text = inputs.get("text", "") - return {"result": text.lower()} + + node_type = "string.lower" + category = "string" + description = "Convert string to lowercase" + + def execute(self, inputs, runtime=None): + value = str(inputs.get("value", inputs.get("text", ""))) + return {"result": value.lower()} diff --git a/workflow/plugins/python/string/string_replace/factory.py b/workflow/plugins/python/string/string_replace/factory.py new file mode 100644 index 000000000..bad6c819f --- /dev/null +++ b/workflow/plugins/python/string/string_replace/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringReplace plugin.""" + +from .string_replace import StringReplace + + +def create(): + return StringReplace() diff --git a/workflow/plugins/python/string/string_replace/package.json b/workflow/plugins/python/string/string_replace/package.json index fd69292eb..717feff59 100644 --- a/workflow/plugins/python/string/string_replace/package.json +++ b/workflow/plugins/python/string/string_replace/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_replace", "version": "1.0.0", - "description": "string_replace plugin", + "description": "Replace occurrences in string", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_replace.py", + "files": ["string_replace.py", "factory.py"], "metadata": { "plugin_type": "string.replace", - "category": "string" + "category": "string", + "class": "StringReplace", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_replace/string_replace.py b/workflow/plugins/python/string/string_replace/string_replace.py index 2d23b7e3e..f66f14bf6 100644 --- a/workflow/plugins/python/string/string_replace/string_replace.py +++ b/workflow/plugins/python/string/string_replace/string_replace.py @@ -1,12 +1,18 @@ """Workflow plugin: replace in string.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class StringReplace(NodeExecutor): """Replace occurrences in string.""" - text = inputs.get("text", "") - old = inputs.get("old", "") - new = inputs.get("new", "") - count = inputs.get("count", -1) - result = text.replace(old, new, count) - return {"result": result} + node_type = "string.replace" + category = "string" + description = "Replace occurrences in string" + + def execute(self, inputs, runtime=None): + value = str(inputs.get("value", inputs.get("text", ""))) + old = inputs.get("old", inputs.get("search", "")) + new = inputs.get("new", inputs.get("replacement", "")) + count = inputs.get("count", -1) + return {"result": value.replace(old, new, count)} diff --git a/workflow/plugins/python/string/string_split/factory.py b/workflow/plugins/python/string/string_split/factory.py new file mode 100644 index 000000000..4ace01a99 --- /dev/null +++ b/workflow/plugins/python/string/string_split/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringSplit plugin.""" + +from .string_split import StringSplit + + +def create(): + return StringSplit() diff --git a/workflow/plugins/python/string/string_split/package.json b/workflow/plugins/python/string/string_split/package.json index 127343584..7cb59c1b2 100644 --- a/workflow/plugins/python/string/string_split/package.json +++ b/workflow/plugins/python/string/string_split/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_split", "version": "1.0.0", - "description": "string_split plugin", + "description": "Split string by separator", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_split.py", + "files": ["string_split.py", "factory.py"], "metadata": { "plugin_type": "string.split", - "category": "string" + "category": "string", + "class": "StringSplit", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_split/string_split.py b/workflow/plugins/python/string/string_split/string_split.py index 6b38fae50..6d43781dc 100644 --- a/workflow/plugins/python/string/string_split/string_split.py +++ b/workflow/plugins/python/string/string_split/string_split.py @@ -1,15 +1,19 @@ """Workflow plugin: split string.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class StringSplit(NodeExecutor): """Split string by separator.""" - text = inputs.get("text", "") - separator = inputs.get("separator", " ") - max_splits = inputs.get("max_splits") - if max_splits is not None: - result = text.split(separator, max_splits) - else: - result = text.split(separator) + node_type = "string.split" + category = "string" + description = "Split string by separator" - return {"result": result} + def execute(self, inputs, runtime=None): + text = str(inputs.get("text", inputs.get("value", ""))) + separator = inputs.get("separator", " ") + max_splits = inputs.get("max_splits", inputs.get("limit")) + if max_splits is not None: + return {"result": text.split(separator, max_splits)} + return {"result": text.split(separator)} diff --git a/workflow/plugins/python/string/string_trim/factory.py b/workflow/plugins/python/string/string_trim/factory.py new file mode 100644 index 000000000..fd9d6b7a7 --- /dev/null +++ b/workflow/plugins/python/string/string_trim/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringTrim plugin.""" + +from .string_trim import StringTrim + + +def create(): + return StringTrim() diff --git a/workflow/plugins/python/string/string_trim/package.json b/workflow/plugins/python/string/string_trim/package.json index 07f01fe58..5f3cfa518 100644 --- a/workflow/plugins/python/string/string_trim/package.json +++ b/workflow/plugins/python/string/string_trim/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_trim", "version": "1.0.0", - "description": "string_trim plugin", + "description": "Trim whitespace from string", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_trim.py", + "files": ["string_trim.py", "factory.py"], "metadata": { "plugin_type": "string.trim", - "category": "string" + "category": "string", + "class": "StringTrim", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_trim/string_trim.py b/workflow/plugins/python/string/string_trim/string_trim.py index e1126d3b9..5db977708 100644 --- a/workflow/plugins/python/string/string_trim/string_trim.py +++ b/workflow/plugins/python/string/string_trim/string_trim.py @@ -1,16 +1,20 @@ """Workflow plugin: trim whitespace from string.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class StringTrim(NodeExecutor): """Trim whitespace from string.""" - text = inputs.get("text", "") - mode = inputs.get("mode", "both") - if mode == "start": - result = text.lstrip() - elif mode == "end": - result = text.rstrip() - else: - result = text.strip() + node_type = "string.trim" + category = "string" + description = "Trim whitespace from string" - return {"result": result} + def execute(self, inputs, runtime=None): + value = str(inputs.get("value", inputs.get("text", ""))) + mode = inputs.get("mode", "both") + if mode == "start": + return {"result": value.lstrip()} + elif mode == "end": + return {"result": value.rstrip()} + return {"result": value.strip()} diff --git a/workflow/plugins/python/string/string_upper/factory.py b/workflow/plugins/python/string/string_upper/factory.py new file mode 100644 index 000000000..3e12793d2 --- /dev/null +++ b/workflow/plugins/python/string/string_upper/factory.py @@ -0,0 +1,7 @@ +"""Factory for StringUpper plugin.""" + +from .string_upper import StringUpper + + +def create(): + return StringUpper() diff --git a/workflow/plugins/python/string/string_upper/package.json b/workflow/plugins/python/string/string_upper/package.json index 145320deb..14ca0786b 100644 --- a/workflow/plugins/python/string/string_upper/package.json +++ b/workflow/plugins/python/string/string_upper/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/string_upper", "version": "1.0.0", - "description": "string_upper plugin", + "description": "Convert string to uppercase", "author": "MetaBuilder", "license": "MIT", "keywords": ["string", "workflow", "plugin"], "main": "string_upper.py", + "files": ["string_upper.py", "factory.py"], "metadata": { "plugin_type": "string.upper", - "category": "string" + "category": "string", + "class": "StringUpper", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/string/string_upper/string_upper.py b/workflow/plugins/python/string/string_upper/string_upper.py index 2490a34f3..33a56f19a 100644 --- a/workflow/plugins/python/string/string_upper/string_upper.py +++ b/workflow/plugins/python/string/string_upper/string_upper.py @@ -1,7 +1,15 @@ """Workflow plugin: convert string to uppercase.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class StringUpper(NodeExecutor): """Convert string to uppercase.""" - text = inputs.get("text", "") - return {"result": text.upper()} + + node_type = "string.upper" + category = "string" + description = "Convert string to uppercase" + + def execute(self, inputs, runtime=None): + value = str(inputs.get("value", inputs.get("text", ""))) + return {"result": value.upper()} diff --git a/workflow/plugins/python/test/test_assert_equals/factory.py b/workflow/plugins/python/test/test_assert_equals/factory.py new file mode 100644 index 000000000..a0ffd96db --- /dev/null +++ b/workflow/plugins/python/test/test_assert_equals/factory.py @@ -0,0 +1,7 @@ +"""Factory for TestAssertEquals plugin.""" + +from .test_assert_equals import TestAssertEquals + + +def create(): + return TestAssertEquals() diff --git a/workflow/plugins/python/test/test_assert_equals/package.json b/workflow/plugins/python/test/test_assert_equals/package.json index 6a843e22b..1e2b6981e 100644 --- a/workflow/plugins/python/test/test_assert_equals/package.json +++ b/workflow/plugins/python/test/test_assert_equals/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/test_assert_equals", "version": "1.0.0", - "description": "test_assert_equals plugin", + "description": "Assert that two values are equal", "author": "MetaBuilder", "license": "MIT", "keywords": ["test", "workflow", "plugin"], "main": "test_assert_equals.py", + "files": ["test_assert_equals.py", "factory.py"], "metadata": { "plugin_type": "test.assert_equals", - "category": "test" + "category": "test", + "class": "TestAssertEquals", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/test/test_assert_equals/test_assert_equals.py b/workflow/plugins/python/test/test_assert_equals/test_assert_equals.py index ecea4e3ff..578c5694c 100644 --- a/workflow/plugins/python/test/test_assert_equals/test_assert_equals.py +++ b/workflow/plugins/python/test/test_assert_equals/test_assert_equals.py @@ -1,26 +1,35 @@ """Workflow plugin: assert two values are equal.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class TestAssertEquals(NodeExecutor): """Assert that two values are equal.""" - actual = inputs.get("actual") - expected = inputs.get("expected") - message = inputs.get("message", "") - passed = actual == expected + node_type = "test.assert_equals" + category = "test" + description = "Assert that two values are equal" + + def execute(self, inputs, runtime=None): + """Assert that two values are equal.""" + actual = inputs.get("actual") + expected = inputs.get("expected") + message = inputs.get("message", "") + + passed = actual == expected + + if not passed: + error_msg = f"Assertion failed: {message}" if message else "Assertion failed" + error_msg += f"\n Expected: {expected}\n Actual: {actual}" + return { + "passed": False, + "error": error_msg, + "expected": expected, + "actual": actual + } - if not passed: - error_msg = f"Assertion failed: {message}" if message else "Assertion failed" - error_msg += f"\n Expected: {expected}\n Actual: {actual}" return { - "passed": False, - "error": error_msg, + "passed": True, "expected": expected, "actual": actual } - - return { - "passed": True, - "expected": expected, - "actual": actual - } diff --git a/workflow/plugins/python/test/test_assert_exists/factory.py b/workflow/plugins/python/test/test_assert_exists/factory.py new file mode 100644 index 000000000..647d4c510 --- /dev/null +++ b/workflow/plugins/python/test/test_assert_exists/factory.py @@ -0,0 +1,7 @@ +"""Factory for TestAssertExists plugin.""" + +from .test_assert_exists import TestAssertExists + + +def create(): + return TestAssertExists() diff --git a/workflow/plugins/python/test/test_assert_exists/package.json b/workflow/plugins/python/test/test_assert_exists/package.json index 4c968f3f0..9e369a61f 100644 --- a/workflow/plugins/python/test/test_assert_exists/package.json +++ b/workflow/plugins/python/test/test_assert_exists/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/test_assert_exists", "version": "1.0.0", - "description": "test_assert_exists plugin", + "description": "Assert that a value exists (is not None/null)", "author": "MetaBuilder", "license": "MIT", "keywords": ["test", "workflow", "plugin"], "main": "test_assert_exists.py", + "files": ["test_assert_exists.py", "factory.py"], "metadata": { "plugin_type": "test.assert_exists", - "category": "test" + "category": "test", + "class": "TestAssertExists", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/test/test_assert_exists/test_assert_exists.py b/workflow/plugins/python/test/test_assert_exists/test_assert_exists.py index 13a139d37..b1a14cbbe 100644 --- a/workflow/plugins/python/test/test_assert_exists/test_assert_exists.py +++ b/workflow/plugins/python/test/test_assert_exists/test_assert_exists.py @@ -1,23 +1,32 @@ """Workflow plugin: assert value exists (is not None/null).""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class TestAssertExists(NodeExecutor): """Assert that a value exists (is not None).""" - value = inputs.get("value") - message = inputs.get("message", "") - passed = value is not None + node_type = "test.assert_exists" + category = "test" + description = "Assert that a value exists (is not None/null)" + + def execute(self, inputs, runtime=None): + """Assert that a value exists (is not None).""" + value = inputs.get("value") + message = inputs.get("message", "") + + passed = value is not None + + if not passed: + error_msg = f"Assertion failed: {message}" if message else "Assertion failed" + error_msg += f"\n Expected: non-null value\n Actual: None" + return { + "passed": False, + "error": error_msg, + "value": value + } - if not passed: - error_msg = f"Assertion failed: {message}" if message else "Assertion failed" - error_msg += f"\n Expected: non-null value\n Actual: None" return { - "passed": False, - "error": error_msg, + "passed": True, "value": value } - - return { - "passed": True, - "value": value - } diff --git a/workflow/plugins/python/test/test_assert_false/factory.py b/workflow/plugins/python/test/test_assert_false/factory.py new file mode 100644 index 000000000..ef184a1ec --- /dev/null +++ b/workflow/plugins/python/test/test_assert_false/factory.py @@ -0,0 +1,7 @@ +"""Factory for TestAssertFalse plugin.""" + +from .test_assert_false import TestAssertFalse + + +def create(): + return TestAssertFalse() diff --git a/workflow/plugins/python/test/test_assert_false/package.json b/workflow/plugins/python/test/test_assert_false/package.json index b5d878c14..3848455d5 100644 --- a/workflow/plugins/python/test/test_assert_false/package.json +++ b/workflow/plugins/python/test/test_assert_false/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/test_assert_false", "version": "1.0.0", - "description": "test_assert_false plugin", + "description": "Assert that a value is false", "author": "MetaBuilder", "license": "MIT", "keywords": ["test", "workflow", "plugin"], "main": "test_assert_false.py", + "files": ["test_assert_false.py", "factory.py"], "metadata": { "plugin_type": "test.assert_false", - "category": "test" + "category": "test", + "class": "TestAssertFalse", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/test/test_assert_false/test_assert_false.py b/workflow/plugins/python/test/test_assert_false/test_assert_false.py index 11073e539..b455c9f22 100644 --- a/workflow/plugins/python/test/test_assert_false/test_assert_false.py +++ b/workflow/plugins/python/test/test_assert_false/test_assert_false.py @@ -1,23 +1,32 @@ """Workflow plugin: assert value is false.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class TestAssertFalse(NodeExecutor): """Assert that a value is false.""" - value = inputs.get("value") - message = inputs.get("message", "") - passed = value is False + node_type = "test.assert_false" + category = "test" + description = "Assert that a value is false" + + def execute(self, inputs, runtime=None): + """Assert that a value is false.""" + value = inputs.get("value") + message = inputs.get("message", "") + + passed = value is False + + if not passed: + error_msg = f"Assertion failed: {message}" if message else "Assertion failed" + error_msg += f"\n Expected: False\n Actual: {value}" + return { + "passed": False, + "error": error_msg, + "value": value + } - if not passed: - error_msg = f"Assertion failed: {message}" if message else "Assertion failed" - error_msg += f"\n Expected: False\n Actual: {value}" return { - "passed": False, - "error": error_msg, + "passed": True, "value": value } - - return { - "passed": True, - "value": value - } diff --git a/workflow/plugins/python/test/test_assert_true/factory.py b/workflow/plugins/python/test/test_assert_true/factory.py new file mode 100644 index 000000000..bde7626ef --- /dev/null +++ b/workflow/plugins/python/test/test_assert_true/factory.py @@ -0,0 +1,7 @@ +"""Factory for TestAssertTrue plugin.""" + +from .test_assert_true import TestAssertTrue + + +def create(): + return TestAssertTrue() diff --git a/workflow/plugins/python/test/test_assert_true/package.json b/workflow/plugins/python/test/test_assert_true/package.json index 40148a19f..626728565 100644 --- a/workflow/plugins/python/test/test_assert_true/package.json +++ b/workflow/plugins/python/test/test_assert_true/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/test_assert_true", "version": "1.0.0", - "description": "test_assert_true plugin", + "description": "Assert that a value is true", "author": "MetaBuilder", "license": "MIT", "keywords": ["test", "workflow", "plugin"], "main": "test_assert_true.py", + "files": ["test_assert_true.py", "factory.py"], "metadata": { "plugin_type": "test.assert_true", - "category": "test" + "category": "test", + "class": "TestAssertTrue", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/test/test_assert_true/test_assert_true.py b/workflow/plugins/python/test/test_assert_true/test_assert_true.py index 8bc25d37c..f3ce423be 100644 --- a/workflow/plugins/python/test/test_assert_true/test_assert_true.py +++ b/workflow/plugins/python/test/test_assert_true/test_assert_true.py @@ -1,23 +1,32 @@ """Workflow plugin: assert value is true.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class TestAssertTrue(NodeExecutor): """Assert that a value is true.""" - value = inputs.get("value") - message = inputs.get("message", "") - passed = value is True + node_type = "test.assert_true" + category = "test" + description = "Assert that a value is true" + + def execute(self, inputs, runtime=None): + """Assert that a value is true.""" + value = inputs.get("value") + message = inputs.get("message", "") + + passed = value is True + + if not passed: + error_msg = f"Assertion failed: {message}" if message else "Assertion failed" + error_msg += f"\n Expected: True\n Actual: {value}" + return { + "passed": False, + "error": error_msg, + "value": value + } - if not passed: - error_msg = f"Assertion failed: {message}" if message else "Assertion failed" - error_msg += f"\n Expected: True\n Actual: {value}" return { - "passed": False, - "error": error_msg, + "passed": True, "value": value } - - return { - "passed": True, - "value": value - } diff --git a/workflow/plugins/python/test/test_run_suite/factory.py b/workflow/plugins/python/test/test_run_suite/factory.py new file mode 100644 index 000000000..faf33741d --- /dev/null +++ b/workflow/plugins/python/test/test_run_suite/factory.py @@ -0,0 +1,7 @@ +"""Factory for TestRunSuite plugin.""" + +from .test_run_suite import TestRunSuite + + +def create(): + return TestRunSuite() diff --git a/workflow/plugins/python/test/test_run_suite/package.json b/workflow/plugins/python/test/test_run_suite/package.json index cd58fedb4..6028be62a 100644 --- a/workflow/plugins/python/test/test_run_suite/package.json +++ b/workflow/plugins/python/test/test_run_suite/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/test_run_suite", "version": "1.0.0", - "description": "test_run_suite plugin", + "description": "Run a suite of test assertions and report results", "author": "MetaBuilder", "license": "MIT", "keywords": ["test", "workflow", "plugin"], "main": "test_run_suite.py", + "files": ["test_run_suite.py", "factory.py"], "metadata": { "plugin_type": "test.run_suite", - "category": "test" + "category": "test", + "class": "TestRunSuite", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/test/test_run_suite/test_run_suite.py b/workflow/plugins/python/test/test_run_suite/test_run_suite.py index 900f39ece..5570abbbf 100644 --- a/workflow/plugins/python/test/test_run_suite/test_run_suite.py +++ b/workflow/plugins/python/test/test_run_suite/test_run_suite.py @@ -1,63 +1,72 @@ """Workflow plugin: run a suite of test assertions and report results.""" +from ...base import NodeExecutor -def run(_runtime, inputs): - """Run a suite of test assertions and aggregate results. - Inputs: - - results: Array of test result objects (each with 'passed' field) - - suite_name: Optional name for the test suite +class TestRunSuite(NodeExecutor): + """Run a suite of test assertions and aggregate results.""" - Outputs: - - passed: Boolean indicating if all tests passed - - total: Total number of tests - - passed_count: Number of tests that passed - - failed_count: Number of tests that failed - - failures: Array of failed test details - """ - results = inputs.get("results", []) - suite_name = inputs.get("suite_name", "Test Suite") + node_type = "test.run_suite" + category = "test" + description = "Run a suite of test assertions and report results" - if not isinstance(results, list): - return { - "passed": False, - "error": "results must be an array", - "total": 0, - "passed_count": 0, - "failed_count": 0, - "failures": [] - } + def execute(self, inputs, runtime=None): + """Run a suite of test assertions and aggregate results. - total = len(results) - passed_count = 0 - failed_count = 0 - failures = [] + Inputs: + - results: Array of test result objects (each with 'passed' field) + - suite_name: Optional name for the test suite - for i, result in enumerate(results): - if isinstance(result, dict) and result.get("passed") is True: - passed_count += 1 - else: - failed_count += 1 - failure_info = { - "test_index": i, - "error": result.get("error", "Unknown error") if isinstance(result, dict) else str(result) + Outputs: + - passed: Boolean indicating if all tests passed + - total: Total number of tests + - passed_count: Number of tests that passed + - failed_count: Number of tests that failed + - failures: Array of failed test details + """ + results = inputs.get("results", []) + suite_name = inputs.get("suite_name", "Test Suite") + + if not isinstance(results, list): + return { + "passed": False, + "error": "results must be an array", + "total": 0, + "passed_count": 0, + "failed_count": 0, + "failures": [] } - if isinstance(result, dict): - failure_info.update({ - "expected": result.get("expected"), - "actual": result.get("actual") - }) - failures.append(failure_info) - all_passed = failed_count == 0 and total > 0 + total = len(results) + passed_count = 0 + failed_count = 0 + failures = [] - summary = f"{suite_name}: {passed_count}/{total} tests passed" + for i, result in enumerate(results): + if isinstance(result, dict) and result.get("passed") is True: + passed_count += 1 + else: + failed_count += 1 + failure_info = { + "test_index": i, + "error": result.get("error", "Unknown error") if isinstance(result, dict) else str(result) + } + if isinstance(result, dict): + failure_info.update({ + "expected": result.get("expected"), + "actual": result.get("actual") + }) + failures.append(failure_info) - return { - "passed": all_passed, - "total": total, - "passed_count": passed_count, - "failed_count": failed_count, - "failures": failures, - "summary": summary - } + all_passed = failed_count == 0 and total > 0 + + summary = f"{suite_name}: {passed_count}/{total} tests passed" + + return { + "passed": all_passed, + "total": total, + "passed_count": passed_count, + "failed_count": failed_count, + "failures": failures, + "summary": summary + } diff --git a/workflow/plugins/python/tools/tools_create_branch/factory.py b/workflow/plugins/python/tools/tools_create_branch/factory.py new file mode 100644 index 000000000..7d2224108 --- /dev/null +++ b/workflow/plugins/python/tools/tools_create_branch/factory.py @@ -0,0 +1,8 @@ +"""Factory for ToolsCreateBranch plugin.""" + +from .tools_create_branch import ToolsCreateBranch + + +def create(): + """Create and return a ToolsCreateBranch instance.""" + return ToolsCreateBranch() diff --git a/workflow/plugins/python/tools/tools_create_branch/package.json b/workflow/plugins/python/tools/tools_create_branch/package.json index 83f5c4cd1..e28cb7341 100644 --- a/workflow/plugins/python/tools/tools_create_branch/package.json +++ b/workflow/plugins/python/tools/tools_create_branch/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/tools_create_branch", "version": "1.0.0", - "description": "tools_create_branch plugin", + "description": "Create a Git branch from a base branch", "author": "MetaBuilder", "license": "MIT", "keywords": ["tools", "workflow", "plugin"], "main": "tools_create_branch.py", + "files": ["tools_create_branch.py", "factory.py"], "metadata": { "plugin_type": "tools.create_branch", - "category": "tools" + "category": "tools", + "class": "ToolsCreateBranch", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/tools/tools_create_branch/tools_create_branch.py b/workflow/plugins/python/tools/tools_create_branch/tools_create_branch.py index e11142d10..11c937eb1 100644 --- a/workflow/plugins/python/tools/tools_create_branch/tools_create_branch.py +++ b/workflow/plugins/python/tools/tools_create_branch/tools_create_branch.py @@ -1,11 +1,20 @@ """Workflow plugin: create branch.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Create a branch via tool runner.""" - result = runtime.tool_runner.call( - "create_branch", - branch_name=inputs.get("branch_name"), - base_branch=inputs.get("base_branch", "main") - ) - return {"result": result} + +class ToolsCreateBranch(NodeExecutor): + """Create a Git branch via tool runner.""" + + node_type = "tools.create_branch" + category = "tools" + description = "Create a Git branch from a base branch" + + def execute(self, inputs, runtime=None): + """Create a branch via tool runner.""" + result = runtime.tool_runner.call( + "create_branch", + branch_name=inputs.get("branch_name"), + base_branch=inputs.get("base_branch", "main") + ) + return {"result": result} diff --git a/workflow/plugins/python/tools/tools_create_pull_request/factory.py b/workflow/plugins/python/tools/tools_create_pull_request/factory.py new file mode 100644 index 000000000..a0073d094 --- /dev/null +++ b/workflow/plugins/python/tools/tools_create_pull_request/factory.py @@ -0,0 +1,8 @@ +"""Factory for ToolsCreatePullRequest plugin.""" + +from .tools_create_pull_request import ToolsCreatePullRequest + + +def create(): + """Create and return a ToolsCreatePullRequest instance.""" + return ToolsCreatePullRequest() diff --git a/workflow/plugins/python/tools/tools_create_pull_request/package.json b/workflow/plugins/python/tools/tools_create_pull_request/package.json index a022e89bf..bcdfdbb9e 100644 --- a/workflow/plugins/python/tools/tools_create_pull_request/package.json +++ b/workflow/plugins/python/tools/tools_create_pull_request/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/tools_create_pull_request", "version": "1.0.0", - "description": "tools_create_pull_request plugin", + "description": "Create a pull request with title, body, and branch information", "author": "MetaBuilder", "license": "MIT", "keywords": ["tools", "workflow", "plugin"], "main": "tools_create_pull_request.py", + "files": ["tools_create_pull_request.py", "factory.py"], "metadata": { "plugin_type": "tools.create_pull_request", - "category": "tools" + "category": "tools", + "class": "ToolsCreatePullRequest", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/tools/tools_create_pull_request/tools_create_pull_request.py b/workflow/plugins/python/tools/tools_create_pull_request/tools_create_pull_request.py index 1128f6f77..357b04cde 100644 --- a/workflow/plugins/python/tools/tools_create_pull_request/tools_create_pull_request.py +++ b/workflow/plugins/python/tools/tools_create_pull_request/tools_create_pull_request.py @@ -1,13 +1,22 @@ """Workflow plugin: create pull request.""" +from ...base import NodeExecutor -def run(runtime, inputs): + +class ToolsCreatePullRequest(NodeExecutor): """Create a pull request via tool runner.""" - result = runtime.tool_runner.call( - "create_pull_request", - title=inputs.get("title"), - body=inputs.get("body"), - head_branch=inputs.get("head_branch"), - base_branch=inputs.get("base_branch", "main") - ) - return {"result": result} + + node_type = "tools.create_pull_request" + category = "tools" + description = "Create a pull request with title, body, and branch information" + + def execute(self, inputs, runtime=None): + """Create a pull request via tool runner.""" + result = runtime.tool_runner.call( + "create_pull_request", + title=inputs.get("title"), + body=inputs.get("body"), + head_branch=inputs.get("head_branch"), + base_branch=inputs.get("base_branch", "main") + ) + return {"result": result} diff --git a/workflow/plugins/python/tools/tools_list_files/factory.py b/workflow/plugins/python/tools/tools_list_files/factory.py new file mode 100644 index 000000000..75c6333af --- /dev/null +++ b/workflow/plugins/python/tools/tools_list_files/factory.py @@ -0,0 +1,8 @@ +"""Factory for ToolsListFiles plugin.""" + +from .tools_list_files import ToolsListFiles + + +def create(): + """Create and return a ToolsListFiles instance.""" + return ToolsListFiles() diff --git a/workflow/plugins/python/tools/tools_list_files/package.json b/workflow/plugins/python/tools/tools_list_files/package.json index 40a07234d..6908aefc3 100644 --- a/workflow/plugins/python/tools/tools_list_files/package.json +++ b/workflow/plugins/python/tools/tools_list_files/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/tools_list_files", "version": "1.0.0", - "description": "tools_list_files plugin", + "description": "List files in a directory", "author": "MetaBuilder", "license": "MIT", "keywords": ["tools", "workflow", "plugin"], "main": "tools_list_files.py", + "files": ["tools_list_files.py", "factory.py"], "metadata": { "plugin_type": "tools.list_files", - "category": "tools" + "category": "tools", + "class": "ToolsListFiles", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/tools/tools_list_files/tools_list_files.py b/workflow/plugins/python/tools/tools_list_files/tools_list_files.py index 90a8facfd..b50abe921 100644 --- a/workflow/plugins/python/tools/tools_list_files/tools_list_files.py +++ b/workflow/plugins/python/tools/tools_list_files/tools_list_files.py @@ -1,7 +1,16 @@ """Workflow plugin: list files.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """List files via tool runner.""" - result = runtime.tool_runner.call("list_files", directory=inputs.get("path", ".")) - return {"files": result} + +class ToolsListFiles(NodeExecutor): + """List files in a directory via tool runner.""" + + node_type = "tools.list_files" + category = "tools" + description = "List files in a directory" + + def execute(self, inputs, runtime=None): + """List files via tool runner.""" + result = runtime.tool_runner.call("list_files", directory=inputs.get("path", ".")) + return {"files": result} diff --git a/workflow/plugins/python/tools/tools_read_file/factory.py b/workflow/plugins/python/tools/tools_read_file/factory.py new file mode 100644 index 000000000..40a0cfd6c --- /dev/null +++ b/workflow/plugins/python/tools/tools_read_file/factory.py @@ -0,0 +1,8 @@ +"""Factory for ToolsReadFile plugin.""" + +from .tools_read_file import ToolsReadFile + + +def create(): + """Create and return a ToolsReadFile instance.""" + return ToolsReadFile() diff --git a/workflow/plugins/python/tools/tools_read_file/package.json b/workflow/plugins/python/tools/tools_read_file/package.json index 73ec19bd7..354036cce 100644 --- a/workflow/plugins/python/tools/tools_read_file/package.json +++ b/workflow/plugins/python/tools/tools_read_file/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/tools_read_file", "version": "1.0.0", - "description": "tools_read_file plugin", + "description": "Read the contents of a file", "author": "MetaBuilder", "license": "MIT", "keywords": ["tools", "workflow", "plugin"], "main": "tools_read_file.py", + "files": ["tools_read_file.py", "factory.py"], "metadata": { "plugin_type": "tools.read_file", - "category": "tools" + "category": "tools", + "class": "ToolsReadFile", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/tools/tools_read_file/tools_read_file.py b/workflow/plugins/python/tools/tools_read_file/tools_read_file.py index b41962315..430e4b417 100644 --- a/workflow/plugins/python/tools/tools_read_file/tools_read_file.py +++ b/workflow/plugins/python/tools/tools_read_file/tools_read_file.py @@ -1,7 +1,16 @@ """Workflow plugin: read file.""" +from ...base import NodeExecutor -def run(runtime, inputs): + +class ToolsReadFile(NodeExecutor): """Read a file via tool runner.""" - result = runtime.tool_runner.call("read_file", path=inputs.get("path")) - return {"content": result} + + node_type = "tools.read_file" + category = "tools" + description = "Read the contents of a file" + + def execute(self, inputs, runtime=None): + """Read a file via tool runner.""" + result = runtime.tool_runner.call("read_file", path=inputs.get("path")) + return {"content": result} diff --git a/workflow/plugins/python/tools/tools_run_docker/factory.py b/workflow/plugins/python/tools/tools_run_docker/factory.py new file mode 100644 index 000000000..a6e67a406 --- /dev/null +++ b/workflow/plugins/python/tools/tools_run_docker/factory.py @@ -0,0 +1,8 @@ +"""Factory for ToolsRunDocker plugin.""" + +from .tools_run_docker import ToolsRunDocker + + +def create(): + """Create and return a ToolsRunDocker instance.""" + return ToolsRunDocker() diff --git a/workflow/plugins/python/tools/tools_run_docker/package.json b/workflow/plugins/python/tools/tools_run_docker/package.json index c2ebeb2e0..38233f311 100644 --- a/workflow/plugins/python/tools/tools_run_docker/package.json +++ b/workflow/plugins/python/tools/tools_run_docker/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/tools_run_docker", "version": "1.0.0", - "description": "tools_run_docker plugin", + "description": "Run a command inside a Docker container with optional volumes and working directory", "author": "MetaBuilder", "license": "MIT", - "keywords": ["tools", "workflow", "plugin"], + "keywords": ["tools", "workflow", "plugin", "docker"], "main": "tools_run_docker.py", + "files": ["tools_run_docker.py", "factory.py"], "metadata": { "plugin_type": "tools.run_docker", - "category": "tools" + "category": "tools", + "class": "ToolsRunDocker", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/tools/tools_run_docker/tools_run_docker.py b/workflow/plugins/python/tools/tools_run_docker/tools_run_docker.py index 513a9764f..aba01415b 100644 --- a/workflow/plugins/python/tools/tools_run_docker/tools_run_docker.py +++ b/workflow/plugins/python/tools/tools_run_docker/tools_run_docker.py @@ -1,59 +1,68 @@ """Workflow plugin: run command in Docker container.""" + import subprocess import os import logging +from ...base import NodeExecutor + logger = logging.getLogger("metabuilder.docker") -def _run_command_in_docker(image: str, command: str, volumes: dict = None, workdir: str = None): - """Run a command inside a Docker container. +class ToolsRunDocker(NodeExecutor): + """Run a command inside a Docker container.""" - :param image: Docker image to use. - :param command: Command to execute. - :param volumes: Dictionary of volume mappings {host_path: container_path}. - :param workdir: Working directory inside the container. - :return: Standard output of the command. - """ - docker_command = ["docker", "run", "--rm"] + node_type = "tools.run_docker" + category = "tools" + description = "Run a command inside a Docker container with optional volumes and working directory" - if volumes: - for host_path, container_path in volumes.items(): - docker_command.extend(["-v", f"{os.path.abspath(host_path)}:{container_path}"]) + def execute(self, inputs, runtime=None): + """Run a command inside a Docker container. - if workdir: - docker_command.extend(["-w", workdir]) + Inputs: + - image: Docker image to use + - command: Command to execute + - volumes: Optional dict of volume mappings {host_path: container_path} + - workdir: Optional working directory inside the container + """ + image = inputs.get("image") + command = inputs.get("command") + volumes = inputs.get("volumes") + workdir = inputs.get("workdir") - docker_command.append(image) - docker_command.extend(["sh", "-c", command]) + if not image or not command: + return {"error": "Both 'image' and 'command' are required"} - logger.info(f"Executing in Docker ({image}): {command}") - result = subprocess.run(docker_command, capture_output=True, text=True, check=False) + output = self._run_command_in_docker(image, command, volumes, workdir) + return {"output": output} - output = result.stdout - if result.stderr: - output += "\n" + result.stderr + def _run_command_in_docker(self, image: str, command: str, volumes: dict = None, workdir: str = None): + """Run a command inside a Docker container. - logger.info(output) - return output + :param image: Docker image to use. + :param command: Command to execute. + :param volumes: Dictionary of volume mappings {host_path: container_path}. + :param workdir: Working directory inside the container. + :return: Standard output of the command. + """ + docker_command = ["docker", "run", "--rm"] + if volumes: + for host_path, container_path in volumes.items(): + docker_command.extend(["-v", f"{os.path.abspath(host_path)}:{container_path}"]) -def run(_runtime, inputs): - """Run a command inside a Docker container. + if workdir: + docker_command.extend(["-w", workdir]) - Inputs: - - image: Docker image to use - - command: Command to execute - - volumes: Optional dict of volume mappings {host_path: container_path} - - workdir: Optional working directory inside the container - """ - image = inputs.get("image") - command = inputs.get("command") - volumes = inputs.get("volumes") - workdir = inputs.get("workdir") + docker_command.append(image) + docker_command.extend(["sh", "-c", command]) - if not image or not command: - return {"error": "Both 'image' and 'command' are required"} + logger.info(f"Executing in Docker ({image}): {command}") + result = subprocess.run(docker_command, capture_output=True, text=True, check=False) - output = _run_command_in_docker(image, command, volumes, workdir) - return {"output": output} + output = result.stdout + if result.stderr: + output += "\n" + result.stderr + + logger.info(output) + return output diff --git a/workflow/plugins/python/tools/tools_run_lint/factory.py b/workflow/plugins/python/tools/tools_run_lint/factory.py new file mode 100644 index 000000000..170e84c9e --- /dev/null +++ b/workflow/plugins/python/tools/tools_run_lint/factory.py @@ -0,0 +1,8 @@ +"""Factory for ToolsRunLint plugin.""" + +from .tools_run_lint import ToolsRunLint + + +def create(): + """Create and return a ToolsRunLint instance.""" + return ToolsRunLint() diff --git a/workflow/plugins/python/tools/tools_run_lint/package.json b/workflow/plugins/python/tools/tools_run_lint/package.json index 85599dc30..eee68c639 100644 --- a/workflow/plugins/python/tools/tools_run_lint/package.json +++ b/workflow/plugins/python/tools/tools_run_lint/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/tools_run_lint", "version": "1.0.0", - "description": "tools_run_lint plugin", + "description": "Run linting on source files", "author": "MetaBuilder", "license": "MIT", - "keywords": ["tools", "workflow", "plugin"], + "keywords": ["tools", "workflow", "plugin", "lint"], "main": "tools_run_lint.py", + "files": ["tools_run_lint.py", "factory.py"], "metadata": { "plugin_type": "tools.run_lint", - "category": "tools" + "category": "tools", + "class": "ToolsRunLint", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/tools/tools_run_lint/tools_run_lint.py b/workflow/plugins/python/tools/tools_run_lint/tools_run_lint.py index 085c363a1..0cd7766ce 100644 --- a/workflow/plugins/python/tools/tools_run_lint/tools_run_lint.py +++ b/workflow/plugins/python/tools/tools_run_lint/tools_run_lint.py @@ -1,7 +1,16 @@ """Workflow plugin: run lint.""" +from ...base import NodeExecutor -def run(runtime, inputs): + +class ToolsRunLint(NodeExecutor): """Run lint via tool runner.""" - result = runtime.tool_runner.call("run_lint", path=inputs.get("path", "src")) - return {"results": result} + + node_type = "tools.run_lint" + category = "tools" + description = "Run linting on source files" + + def execute(self, inputs, runtime=None): + """Run lint via tool runner.""" + result = runtime.tool_runner.call("run_lint", path=inputs.get("path", "src")) + return {"results": result} diff --git a/workflow/plugins/python/tools/tools_run_tests/factory.py b/workflow/plugins/python/tools/tools_run_tests/factory.py new file mode 100644 index 000000000..59a02761b --- /dev/null +++ b/workflow/plugins/python/tools/tools_run_tests/factory.py @@ -0,0 +1,8 @@ +"""Factory for ToolsRunTests plugin.""" + +from .tools_run_tests import ToolsRunTests + + +def create(): + """Create and return a ToolsRunTests instance.""" + return ToolsRunTests() diff --git a/workflow/plugins/python/tools/tools_run_tests/package.json b/workflow/plugins/python/tools/tools_run_tests/package.json index 43ab07fa8..a681d52ce 100644 --- a/workflow/plugins/python/tools/tools_run_tests/package.json +++ b/workflow/plugins/python/tools/tools_run_tests/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/tools_run_tests", "version": "1.0.0", - "description": "tools_run_tests plugin", + "description": "Run tests in a specified directory", "author": "MetaBuilder", "license": "MIT", - "keywords": ["tools", "workflow", "plugin"], + "keywords": ["tools", "workflow", "plugin", "tests"], "main": "tools_run_tests.py", + "files": ["tools_run_tests.py", "factory.py"], "metadata": { "plugin_type": "tools.run_tests", - "category": "tools" + "category": "tools", + "class": "ToolsRunTests", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/tools/tools_run_tests/tools_run_tests.py b/workflow/plugins/python/tools/tools_run_tests/tools_run_tests.py index 2061f3a1f..acddd9768 100644 --- a/workflow/plugins/python/tools/tools_run_tests/tools_run_tests.py +++ b/workflow/plugins/python/tools/tools_run_tests/tools_run_tests.py @@ -1,7 +1,16 @@ """Workflow plugin: run tests.""" +from ...base import NodeExecutor -def run(runtime, inputs): + +class ToolsRunTests(NodeExecutor): """Run tests via tool runner.""" - result = runtime.tool_runner.call("run_tests", path=inputs.get("path", "tests")) - return {"results": result} + + node_type = "tools.run_tests" + category = "tools" + description = "Run tests in a specified directory" + + def execute(self, inputs, runtime=None): + """Run tests via tool runner.""" + result = runtime.tool_runner.call("run_tests", path=inputs.get("path", "tests")) + return {"results": result} diff --git a/workflow/plugins/python/utils/utils_branch_condition/factory.py b/workflow/plugins/python/utils/utils_branch_condition/factory.py new file mode 100644 index 000000000..ff01d5d8d --- /dev/null +++ b/workflow/plugins/python/utils/utils_branch_condition/factory.py @@ -0,0 +1,7 @@ +"""Factory for BranchCondition plugin.""" + +from .utils_branch_condition import BranchCondition + + +def create(): + return BranchCondition() diff --git a/workflow/plugins/python/utils/utils_branch_condition/package.json b/workflow/plugins/python/utils/utils_branch_condition/package.json index 3463f2efe..fc0502ae9 100644 --- a/workflow/plugins/python/utils/utils_branch_condition/package.json +++ b/workflow/plugins/python/utils/utils_branch_condition/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/utils_branch_condition", "version": "1.0.0", - "description": "utils_branch_condition plugin", + "description": "Evaluate branch conditions using various comparison modes", "author": "MetaBuilder", "license": "MIT", "keywords": ["utils", "workflow", "plugin"], "main": "utils_branch_condition.py", + "files": ["utils_branch_condition.py", "factory.py"], "metadata": { "plugin_type": "utils.branch_condition", - "category": "utils" + "category": "utils", + "class": "BranchCondition", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/utils/utils_branch_condition/utils_branch_condition.py b/workflow/plugins/python/utils/utils_branch_condition/utils_branch_condition.py index 117b9f1bb..36f2660c3 100644 --- a/workflow/plugins/python/utils/utils_branch_condition/utils_branch_condition.py +++ b/workflow/plugins/python/utils/utils_branch_condition/utils_branch_condition.py @@ -1,25 +1,35 @@ """Workflow plugin: branch condition.""" + import re +from ...base import NodeExecutor -def run(_runtime, inputs): - """Evaluate a branch condition.""" - value = inputs.get("value") - mode = inputs.get("mode", "is_truthy") - compare = inputs.get("compare", "") - decision = False - if mode == "is_empty": - decision = not value if isinstance(value, (list, dict, str)) else not bool(value) - elif mode == "is_truthy": - decision = bool(value) - elif mode == "equals": - decision = str(value) == compare - elif mode == "not_equals": - decision = str(value) != compare - elif mode == "contains": - decision = compare in str(value) - elif mode == "regex": - decision = bool(re.search(compare, str(value))) +class BranchCondition(NodeExecutor): + """Evaluate a branch condition using various comparison modes.""" - return {"result": decision} + node_type = "utils.branch_condition" + category = "utils" + description = "Evaluate a branch condition using various comparison modes" + + def execute(self, inputs, runtime=None): + """Evaluate a branch condition.""" + value = inputs.get("value") + mode = inputs.get("mode", "is_truthy") + compare = inputs.get("compare", "") + decision = False + + if mode == "is_empty": + decision = not value if isinstance(value, (list, dict, str)) else not bool(value) + elif mode == "is_truthy": + decision = bool(value) + elif mode == "equals": + decision = str(value) == compare + elif mode == "not_equals": + decision = str(value) != compare + elif mode == "contains": + decision = compare in str(value) + elif mode == "regex": + decision = bool(re.search(compare, str(value))) + + return {"result": decision} diff --git a/workflow/plugins/python/utils/utils_check_mvp/factory.py b/workflow/plugins/python/utils/utils_check_mvp/factory.py new file mode 100644 index 000000000..a0b819164 --- /dev/null +++ b/workflow/plugins/python/utils/utils_check_mvp/factory.py @@ -0,0 +1,7 @@ +"""Factory for CheckMvp plugin.""" + +from .utils_check_mvp import CheckMvp + + +def create(): + return CheckMvp() diff --git a/workflow/plugins/python/utils/utils_check_mvp/package.json b/workflow/plugins/python/utils/utils_check_mvp/package.json index b009232d7..9b60652fb 100644 --- a/workflow/plugins/python/utils/utils_check_mvp/package.json +++ b/workflow/plugins/python/utils/utils_check_mvp/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/utils_check_mvp", "version": "1.0.0", - "description": "utils_check_mvp plugin", + "description": "Check if the MVP section in ROADMAP.md is completed", "author": "MetaBuilder", "license": "MIT", "keywords": ["utils", "workflow", "plugin"], "main": "utils_check_mvp.py", + "files": ["utils_check_mvp.py", "factory.py"], "metadata": { "plugin_type": "utils.check_mvp", - "category": "utils" + "category": "utils", + "class": "CheckMvp", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/utils/utils_check_mvp/utils_check_mvp.py b/workflow/plugins/python/utils/utils_check_mvp/utils_check_mvp.py index 215d29315..d2816449d 100644 --- a/workflow/plugins/python/utils/utils_check_mvp/utils_check_mvp.py +++ b/workflow/plugins/python/utils/utils_check_mvp/utils_check_mvp.py @@ -1,37 +1,46 @@ """Workflow plugin: check if MVP is reached.""" + import os import re +from ...base import NodeExecutor -def _is_mvp_reached() -> bool: + +class CheckMvp(NodeExecutor): """Check if the MVP section in ROADMAP.md is completed.""" - if not os.path.exists("ROADMAP.md"): + + node_type = "utils.check_mvp" + category = "utils" + description = "Check if the MVP section in ROADMAP.md is completed" + + def execute(self, inputs, runtime=None): + """Check if the MVP section in ROADMAP.md is completed.""" + mvp_reached = self._is_mvp_reached() + return {"mvp_reached": mvp_reached} + + def _is_mvp_reached(self) -> bool: + """Check if the MVP section in ROADMAP.md is completed.""" + if not os.path.exists("ROADMAP.md"): + return False + + with open("ROADMAP.md", "r", encoding="utf-8") as f: + content = f.read() + + # Find the header line containing (MVP) + header_match = re.search(r"^## .*?\(MVP\).*?$", content, re.MULTILINE | re.IGNORECASE) + if not header_match: + return False + + start_pos = header_match.end() + next_header_match = re.search(r"^## ", content[start_pos:], re.MULTILINE) + if next_header_match: + mvp_section = content[start_pos : start_pos + next_header_match.start()] + else: + mvp_section = content[start_pos:] + + if "[ ]" in mvp_section: + return False + if "[x]" in mvp_section: + return True + return False - - with open("ROADMAP.md", "r", encoding="utf-8") as f: - content = f.read() - - # Find the header line containing (MVP) - header_match = re.search(r"^## .*?\(MVP\).*?$", content, re.MULTILINE | re.IGNORECASE) - if not header_match: - return False - - start_pos = header_match.end() - next_header_match = re.search(r"^## ", content[start_pos:], re.MULTILINE) - if next_header_match: - mvp_section = content[start_pos : start_pos + next_header_match.start()] - else: - mvp_section = content[start_pos:] - - if "[ ]" in mvp_section: - return False - if "[x]" in mvp_section: - return True - - return False - - -def run(_runtime, _inputs): - """Check if the MVP section in ROADMAP.md is completed.""" - mvp_reached = _is_mvp_reached() - return {"mvp_reached": mvp_reached} diff --git a/workflow/plugins/python/utils/utils_filter_list/factory.py b/workflow/plugins/python/utils/utils_filter_list/factory.py new file mode 100644 index 000000000..2fc20a608 --- /dev/null +++ b/workflow/plugins/python/utils/utils_filter_list/factory.py @@ -0,0 +1,7 @@ +"""Factory for FilterList plugin.""" + +from .utils_filter_list import FilterList + + +def create(): + return FilterList() diff --git a/workflow/plugins/python/utils/utils_filter_list/package.json b/workflow/plugins/python/utils/utils_filter_list/package.json index f4a980b09..f3facdd78 100644 --- a/workflow/plugins/python/utils/utils_filter_list/package.json +++ b/workflow/plugins/python/utils/utils_filter_list/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/utils_filter_list", "version": "1.0.0", - "description": "utils_filter_list plugin", + "description": "Filter items using a match mode", "author": "MetaBuilder", "license": "MIT", "keywords": ["utils", "workflow", "plugin"], "main": "utils_filter_list.py", + "files": ["utils_filter_list.py", "factory.py"], "metadata": { "plugin_type": "utils.filter_list", - "category": "utils" + "category": "utils", + "class": "FilterList", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/utils/utils_filter_list/utils_filter_list.py b/workflow/plugins/python/utils/utils_filter_list/utils_filter_list.py index 9e62d4fe6..11da923dc 100644 --- a/workflow/plugins/python/utils/utils_filter_list/utils_filter_list.py +++ b/workflow/plugins/python/utils/utils_filter_list/utils_filter_list.py @@ -1,33 +1,43 @@ """Workflow plugin: filter list.""" + import re +from ...base import NodeExecutor -def run(_runtime, inputs): + +class FilterList(NodeExecutor): """Filter items using a match mode.""" - items = inputs.get("items", []) - if not isinstance(items, list): - items = [items] if items else [] - mode = inputs.get("mode", "contains") - pattern = inputs.get("pattern", "") - filtered = [] + node_type = "utils.filter_list" + category = "utils" + description = "Filter items using a match mode" - for item in items: - candidate = str(item) - matched = False - if mode == "contains": - matched = pattern in candidate - elif mode == "regex": - matched = bool(re.search(pattern, candidate)) - elif mode == "equals": - matched = candidate == pattern - elif mode == "not_equals": - matched = candidate != pattern - elif mode == "starts_with": - matched = candidate.startswith(pattern) - elif mode == "ends_with": - matched = candidate.endswith(pattern) - if matched: - filtered.append(item) + def execute(self, inputs, runtime=None): + """Filter items using a match mode.""" + items = inputs.get("items", []) + if not isinstance(items, list): + items = [items] if items else [] - return {"items": filtered} + mode = inputs.get("mode", "contains") + pattern = inputs.get("pattern", "") + filtered = [] + + for item in items: + candidate = str(item) + matched = False + if mode == "contains": + matched = pattern in candidate + elif mode == "regex": + matched = bool(re.search(pattern, candidate)) + elif mode == "equals": + matched = candidate == pattern + elif mode == "not_equals": + matched = candidate != pattern + elif mode == "starts_with": + matched = candidate.startswith(pattern) + elif mode == "ends_with": + matched = candidate.endswith(pattern) + if matched: + filtered.append(item) + + return {"items": filtered} diff --git a/workflow/plugins/python/utils/utils_map_list/factory.py b/workflow/plugins/python/utils/utils_map_list/factory.py new file mode 100644 index 000000000..4aeb4dc68 --- /dev/null +++ b/workflow/plugins/python/utils/utils_map_list/factory.py @@ -0,0 +1,7 @@ +"""Factory for MapList plugin.""" + +from .utils_map_list import MapList + + +def create(): + return MapList() diff --git a/workflow/plugins/python/utils/utils_map_list/package.json b/workflow/plugins/python/utils/utils_map_list/package.json index e89e6f618..d524a6b62 100644 --- a/workflow/plugins/python/utils/utils_map_list/package.json +++ b/workflow/plugins/python/utils/utils_map_list/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/utils_map_list", "version": "1.0.0", - "description": "utils_map_list plugin", + "description": "Map items to formatted strings", "author": "MetaBuilder", "license": "MIT", "keywords": ["utils", "workflow", "plugin"], "main": "utils_map_list.py", + "files": ["utils_map_list.py", "factory.py"], "metadata": { "plugin_type": "utils.map_list", - "category": "utils" + "category": "utils", + "class": "MapList", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/utils/utils_map_list/utils_map_list.py b/workflow/plugins/python/utils/utils_map_list/utils_map_list.py index bccc39b1e..e025fcd57 100644 --- a/workflow/plugins/python/utils/utils_map_list/utils_map_list.py +++ b/workflow/plugins/python/utils/utils_map_list/utils_map_list.py @@ -1,19 +1,28 @@ """Workflow plugin: map list.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class MapList(NodeExecutor): """Map items to formatted strings.""" - items = inputs.get("items", []) - if not isinstance(items, list): - items = [items] if items else [] - template = inputs.get("template", "{item}") - mapped = [] + node_type = "utils.map_list" + category = "utils" + description = "Map items to formatted strings" - for item in items: - try: - mapped.append(template.format(item=item)) - except Exception: - mapped.append(str(item)) + def execute(self, inputs, runtime=None): + """Map items to formatted strings.""" + items = inputs.get("items", []) + if not isinstance(items, list): + items = [items] if items else [] - return {"items": mapped} + template = inputs.get("template", "{item}") + mapped = [] + + for item in items: + try: + mapped.append(template.format(item=item)) + except Exception: + mapped.append(str(item)) + + return {"items": mapped} diff --git a/workflow/plugins/python/utils/utils_not/factory.py b/workflow/plugins/python/utils/utils_not/factory.py new file mode 100644 index 000000000..6990fb121 --- /dev/null +++ b/workflow/plugins/python/utils/utils_not/factory.py @@ -0,0 +1,7 @@ +"""Factory for Not plugin.""" + +from .utils_not import Not + + +def create(): + return Not() diff --git a/workflow/plugins/python/utils/utils_not/package.json b/workflow/plugins/python/utils/utils_not/package.json index cde7e2247..81ca701ce 100644 --- a/workflow/plugins/python/utils/utils_not/package.json +++ b/workflow/plugins/python/utils/utils_not/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/utils_not", "version": "1.0.0", - "description": "utils_not plugin", + "description": "Negate a boolean value", "author": "MetaBuilder", "license": "MIT", "keywords": ["utils", "workflow", "plugin"], "main": "utils_not.py", + "files": ["utils_not.py", "factory.py"], "metadata": { "plugin_type": "utils.not", - "category": "utils" + "category": "utils", + "class": "Not", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/utils/utils_not/utils_not.py b/workflow/plugins/python/utils/utils_not/utils_not.py index 880e00f25..5bd7b2f3f 100644 --- a/workflow/plugins/python/utils/utils_not/utils_not.py +++ b/workflow/plugins/python/utils/utils_not/utils_not.py @@ -1,7 +1,16 @@ """Workflow plugin: boolean not.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class Not(NodeExecutor): """Negate a boolean value.""" - value = inputs.get("value") - return {"result": not bool(value)} + + node_type = "utils.not" + category = "utils" + description = "Negate a boolean value" + + def execute(self, inputs, runtime=None): + """Negate a boolean value.""" + value = inputs.get("value") + return {"result": not bool(value)} diff --git a/workflow/plugins/python/utils/utils_reduce_list/factory.py b/workflow/plugins/python/utils/utils_reduce_list/factory.py new file mode 100644 index 000000000..d05f3653d --- /dev/null +++ b/workflow/plugins/python/utils/utils_reduce_list/factory.py @@ -0,0 +1,7 @@ +"""Factory for ReduceList plugin.""" + +from .utils_reduce_list import ReduceList + + +def create(): + return ReduceList() diff --git a/workflow/plugins/python/utils/utils_reduce_list/package.json b/workflow/plugins/python/utils/utils_reduce_list/package.json index e448331a2..3ce2d0748 100644 --- a/workflow/plugins/python/utils/utils_reduce_list/package.json +++ b/workflow/plugins/python/utils/utils_reduce_list/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/utils_reduce_list", "version": "1.0.0", - "description": "utils_reduce_list plugin", + "description": "Reduce a list into a string", "author": "MetaBuilder", "license": "MIT", "keywords": ["utils", "workflow", "plugin"], "main": "utils_reduce_list.py", + "files": ["utils_reduce_list.py", "factory.py"], "metadata": { "plugin_type": "utils.reduce_list", - "category": "utils" + "category": "utils", + "class": "ReduceList", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/utils/utils_reduce_list/utils_reduce_list.py b/workflow/plugins/python/utils/utils_reduce_list/utils_reduce_list.py index 69bb12820..c1520a4dd 100644 --- a/workflow/plugins/python/utils/utils_reduce_list/utils_reduce_list.py +++ b/workflow/plugins/python/utils/utils_reduce_list/utils_reduce_list.py @@ -1,18 +1,27 @@ """Workflow plugin: reduce list.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class ReduceList(NodeExecutor): """Reduce a list into a string.""" - items = inputs.get("items", []) - if not isinstance(items, list): - items = [items] if items else [] - separator = inputs.get("separator", "") - # Handle escape sequences - if separator == "\\n": - separator = "\n" - elif separator == "\\t": - separator = "\t" + node_type = "utils.reduce_list" + category = "utils" + description = "Reduce a list into a string" - reduced = separator.join([str(item) for item in items]) - return {"result": reduced} + def execute(self, inputs, runtime=None): + """Reduce a list into a string.""" + items = inputs.get("items", []) + if not isinstance(items, list): + items = [items] if items else [] + + separator = inputs.get("separator", "") + # Handle escape sequences + if separator == "\\n": + separator = "\n" + elif separator == "\\t": + separator = "\t" + + reduced = separator.join([str(item) for item in items]) + return {"result": reduced} diff --git a/workflow/plugins/python/utils/utils_update_roadmap/factory.py b/workflow/plugins/python/utils/utils_update_roadmap/factory.py new file mode 100644 index 000000000..b469b9924 --- /dev/null +++ b/workflow/plugins/python/utils/utils_update_roadmap/factory.py @@ -0,0 +1,7 @@ +"""Factory for UpdateRoadmap plugin.""" + +from .utils_update_roadmap import UpdateRoadmap + + +def create(): + return UpdateRoadmap() diff --git a/workflow/plugins/python/utils/utils_update_roadmap/package.json b/workflow/plugins/python/utils/utils_update_roadmap/package.json index 8166d9a50..a241c9819 100644 --- a/workflow/plugins/python/utils/utils_update_roadmap/package.json +++ b/workflow/plugins/python/utils/utils_update_roadmap/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/utils_update_roadmap", "version": "1.0.0", - "description": "utils_update_roadmap plugin", + "description": "Update ROADMAP.md with new content", "author": "MetaBuilder", "license": "MIT", "keywords": ["utils", "workflow", "plugin"], "main": "utils_update_roadmap.py", + "files": ["utils_update_roadmap.py", "factory.py"], "metadata": { "plugin_type": "utils.update_roadmap", - "category": "utils" + "category": "utils", + "class": "UpdateRoadmap", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/utils/utils_update_roadmap/utils_update_roadmap.py b/workflow/plugins/python/utils/utils_update_roadmap/utils_update_roadmap.py index 7750eb17a..b5cc4d4aa 100644 --- a/workflow/plugins/python/utils/utils_update_roadmap/utils_update_roadmap.py +++ b/workflow/plugins/python/utils/utils_update_roadmap/utils_update_roadmap.py @@ -1,21 +1,30 @@ """Workflow plugin: update roadmap file.""" + import logging +from ...base import NodeExecutor + logger = logging.getLogger("metabuilder") -def _update_roadmap(content: str): +class UpdateRoadmap(NodeExecutor): """Update ROADMAP.md with new content.""" - with open("ROADMAP.md", "w", encoding="utf-8") as f: - f.write(content) - logger.info("ROADMAP.md updated successfully.") + node_type = "utils.update_roadmap" + category = "utils" + description = "Update ROADMAP.md with new content" -def run(_runtime, inputs): - """Update ROADMAP.md with new content.""" - content = inputs.get("content") - if not content: - return {"error": "Content is required"} + def execute(self, inputs, runtime=None): + """Update ROADMAP.md with new content.""" + content = inputs.get("content") + if not content: + return {"error": "Content is required"} - _update_roadmap(content) - return {"result": "ROADMAP.md updated successfully"} + self._update_roadmap(content) + return {"result": "ROADMAP.md updated successfully"} + + def _update_roadmap(self, content: str): + """Update ROADMAP.md with new content.""" + with open("ROADMAP.md", "w", encoding="utf-8") as f: + f.write(content) + logger.info("ROADMAP.md updated successfully.") diff --git a/workflow/plugins/python/var/var_delete/factory.py b/workflow/plugins/python/var/var_delete/factory.py new file mode 100644 index 000000000..bbca1d536 --- /dev/null +++ b/workflow/plugins/python/var/var_delete/factory.py @@ -0,0 +1,7 @@ +"""Factory for VarDelete plugin.""" + +from .var_delete import VarDelete + + +def create(): + return VarDelete() diff --git a/workflow/plugins/python/var/var_delete/package.json b/workflow/plugins/python/var/var_delete/package.json index 0853804b1..3f4e1e876 100644 --- a/workflow/plugins/python/var/var_delete/package.json +++ b/workflow/plugins/python/var/var_delete/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/var_delete", "version": "1.0.0", - "description": "var_delete plugin", + "description": "Delete variable from workflow store", "author": "MetaBuilder", "license": "MIT", "keywords": ["var", "workflow", "plugin"], "main": "var_delete.py", + "files": ["var_delete.py", "factory.py"], "metadata": { "plugin_type": "var.delete", - "category": "var" + "category": "var", + "class": "VarDelete", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/var/var_delete/var_delete.py b/workflow/plugins/python/var/var_delete/var_delete.py index b78a7466a..3562e507b 100644 --- a/workflow/plugins/python/var/var_delete/var_delete.py +++ b/workflow/plugins/python/var/var_delete/var_delete.py @@ -1,15 +1,24 @@ """Workflow plugin: delete variable from workflow store.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Delete variable from workflow store.""" - key = inputs.get("key") - if key is None: - return {"result": False, "deleted": False, "error": "key is required"} +class VarDelete(NodeExecutor): + """Delete a variable from the workflow store.""" - if key in runtime.store: - del runtime.store[key] - return {"result": True, "deleted": True} + node_type = "var.delete" + category = "var" + description = "Delete variable from workflow store" - return {"result": False, "deleted": False} + def execute(self, inputs, runtime=None): + """Delete variable from workflow store.""" + key = inputs.get("key") + + if key is None: + return {"result": False, "deleted": False, "error": "key is required"} + + if key in runtime.store: + del runtime.store[key] + return {"result": True, "deleted": True} + + return {"result": False, "deleted": False} diff --git a/workflow/plugins/python/var/var_exists/factory.py b/workflow/plugins/python/var/var_exists/factory.py new file mode 100644 index 000000000..64c73cc5e --- /dev/null +++ b/workflow/plugins/python/var/var_exists/factory.py @@ -0,0 +1,7 @@ +"""Factory for VarExists plugin.""" + +from .var_exists import VarExists + + +def create(): + return VarExists() diff --git a/workflow/plugins/python/var/var_exists/package.json b/workflow/plugins/python/var/var_exists/package.json index 2d6f9a609..6877cebfc 100644 --- a/workflow/plugins/python/var/var_exists/package.json +++ b/workflow/plugins/python/var/var_exists/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/var_exists", "version": "1.0.0", - "description": "var_exists plugin", + "description": "Check if variable exists in workflow store", "author": "MetaBuilder", "license": "MIT", "keywords": ["var", "workflow", "plugin"], "main": "var_exists.py", + "files": ["var_exists.py", "factory.py"], "metadata": { "plugin_type": "var.exists", - "category": "var" + "category": "var", + "class": "VarExists", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/var/var_exists/var_exists.py b/workflow/plugins/python/var/var_exists/var_exists.py index 7e7ed44de..6588ce640 100644 --- a/workflow/plugins/python/var/var_exists/var_exists.py +++ b/workflow/plugins/python/var/var_exists/var_exists.py @@ -1,13 +1,22 @@ """Workflow plugin: check if variable exists in workflow store.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Check if variable exists in workflow store.""" - key = inputs.get("key") - if key is None: - return {"result": False, "error": "key is required"} +class VarExists(NodeExecutor): + """Check if a variable exists in the workflow store.""" - exists = key in runtime.store + node_type = "var.exists" + category = "var" + description = "Check if variable exists in workflow store" - return {"result": exists} + def execute(self, inputs, runtime=None): + """Check if variable exists in workflow store.""" + key = inputs.get("key") + + if key is None: + return {"result": False, "error": "key is required"} + + exists = key in runtime.store + + return {"result": exists} diff --git a/workflow/plugins/python/var/var_get/factory.py b/workflow/plugins/python/var/var_get/factory.py new file mode 100644 index 000000000..6f21d33d0 --- /dev/null +++ b/workflow/plugins/python/var/var_get/factory.py @@ -0,0 +1,7 @@ +"""Factory for VarGet plugin.""" + +from .var_get import VarGet + + +def create(): + return VarGet() diff --git a/workflow/plugins/python/var/var_get/package.json b/workflow/plugins/python/var/var_get/package.json index 4056b038b..7b97dfc9b 100644 --- a/workflow/plugins/python/var/var_get/package.json +++ b/workflow/plugins/python/var/var_get/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/var_get", "version": "1.0.0", - "description": "var_get plugin", + "description": "Get variable from workflow store", "author": "MetaBuilder", "license": "MIT", "keywords": ["var", "workflow", "plugin"], "main": "var_get.py", + "files": ["var_get.py", "factory.py"], "metadata": { "plugin_type": "var.get", - "category": "var" + "category": "var", + "class": "VarGet", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/var/var_get/var_get.py b/workflow/plugins/python/var/var_get/var_get.py index f34b7dac0..ab202b825 100644 --- a/workflow/plugins/python/var/var_get/var_get.py +++ b/workflow/plugins/python/var/var_get/var_get.py @@ -1,15 +1,24 @@ """Workflow plugin: get variable from workflow store.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Get variable value from workflow store.""" - key = inputs.get("key") - default = inputs.get("default") - if key is None: - return {"result": default, "exists": False, "error": "key is required"} +class VarGet(NodeExecutor): + """Get a variable value from the workflow store.""" - exists = key in runtime.store - value = runtime.store.get(key, default) + node_type = "var.get" + category = "var" + description = "Get variable from workflow store" - return {"result": value, "exists": exists} + def execute(self, inputs, runtime=None): + """Get variable value from workflow store.""" + key = inputs.get("key") + default = inputs.get("default") + + if key is None: + return {"result": default, "exists": False, "error": "key is required"} + + exists = key in runtime.store + value = runtime.store.get(key, default) + + return {"result": value, "exists": exists} diff --git a/workflow/plugins/python/var/var_set/factory.py b/workflow/plugins/python/var/var_set/factory.py new file mode 100644 index 000000000..7396b692f --- /dev/null +++ b/workflow/plugins/python/var/var_set/factory.py @@ -0,0 +1,7 @@ +"""Factory for VarSet plugin.""" + +from .var_set import VarSet + + +def create(): + return VarSet() diff --git a/workflow/plugins/python/var/var_set/package.json b/workflow/plugins/python/var/var_set/package.json index 8c866c582..6aa9085ac 100644 --- a/workflow/plugins/python/var/var_set/package.json +++ b/workflow/plugins/python/var/var_set/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/var_set", "version": "1.0.0", - "description": "var_set plugin", + "description": "Set variable in workflow store", "author": "MetaBuilder", "license": "MIT", "keywords": ["var", "workflow", "plugin"], "main": "var_set.py", + "files": ["var_set.py", "factory.py"], "metadata": { "plugin_type": "var.set", - "category": "var" + "category": "var", + "class": "VarSet", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/var/var_set/var_set.py b/workflow/plugins/python/var/var_set/var_set.py index baea19953..70f9ed2a4 100644 --- a/workflow/plugins/python/var/var_set/var_set.py +++ b/workflow/plugins/python/var/var_set/var_set.py @@ -1,14 +1,23 @@ """Workflow plugin: set variable in workflow store.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Set variable value in workflow store.""" - key = inputs.get("key") - value = inputs.get("value") - if key is None: - return {"result": None, "key": None, "error": "key is required"} +class VarSet(NodeExecutor): + """Set a variable value in the workflow store.""" - runtime.store[key] = value + node_type = "var.set" + category = "var" + description = "Set variable in workflow store" - return {"result": value, "key": key} + def execute(self, inputs, runtime=None): + """Set variable value in workflow store.""" + key = inputs.get("key") + value = inputs.get("value") + + if key is None: + return {"result": None, "key": None, "error": "key is required"} + + runtime.store[key] = value + + return {"result": value, "key": key} diff --git a/workflow/plugins/python/web/web_build_prompt_yaml/factory.py b/workflow/plugins/python/web/web_build_prompt_yaml/factory.py new file mode 100644 index 000000000..d3427d7a1 --- /dev/null +++ b/workflow/plugins/python/web/web_build_prompt_yaml/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebBuildPromptYaml plugin.""" + +from .web_build_prompt_yaml import WebBuildPromptYaml + + +def create(): + return WebBuildPromptYaml() diff --git a/workflow/plugins/python/web/web_build_prompt_yaml/package.json b/workflow/plugins/python/web/web_build_prompt_yaml/package.json index 079cf199b..84db44356 100644 --- a/workflow/plugins/python/web/web_build_prompt_yaml/package.json +++ b/workflow/plugins/python/web/web_build_prompt_yaml/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_build_prompt_yaml", "version": "1.0.0", - "description": "web_build_prompt_yaml plugin", + "description": "Build prompt YAML from system and user content", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_build_prompt_yaml.py", + "files": ["web_build_prompt_yaml.py", "factory.py"], "metadata": { "plugin_type": "web.build_prompt_yaml", - "category": "web" + "category": "web", + "class": "WebBuildPromptYaml", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_build_prompt_yaml/web_build_prompt_yaml.py b/workflow/plugins/python/web/web_build_prompt_yaml/web_build_prompt_yaml.py index 4c891935e..c3197b9c5 100644 --- a/workflow/plugins/python/web/web_build_prompt_yaml/web_build_prompt_yaml.py +++ b/workflow/plugins/python/web/web_build_prompt_yaml/web_build_prompt_yaml.py @@ -1,22 +1,31 @@ """Workflow plugin: build prompt YAML.""" +from ...base import NodeExecutor -def run(_runtime, inputs): + +class WebBuildPromptYaml(NodeExecutor): """Build prompt YAML from system and user content.""" - system_content = inputs.get("system_content") - user_content = inputs.get("user_content") - model = inputs.get("model") - def indent_block(text): - if not text: - return "" - return "\n ".join(line.rstrip() for line in text.splitlines()) + node_type = "web.build_prompt_yaml" + category = "web" + description = "Build prompt YAML from system and user content" - model_value = model or "openai/gpt-4o" - system_block = indent_block(system_content) - user_block = indent_block(user_content) + def execute(self, inputs, runtime=None): + """Build prompt YAML from system and user content.""" + system_content = inputs.get("system_content") + user_content = inputs.get("user_content") + model = inputs.get("model") - yaml_content = f"""messages: + def indent_block(text): + if not text: + return "" + return "\n ".join(line.rstrip() for line in text.splitlines()) + + model_value = model or "openai/gpt-4o" + system_block = indent_block(system_content) + user_block = indent_block(user_content) + + yaml_content = f"""messages: - role: system content: >- {system_block} @@ -26,4 +35,4 @@ def run(_runtime, inputs): model: {model_value} """ - return {"result": yaml_content} + return {"result": yaml_content} diff --git a/workflow/plugins/python/web/web_create_flask_app/factory.py b/workflow/plugins/python/web/web_create_flask_app/factory.py new file mode 100644 index 000000000..4aed6a6e5 --- /dev/null +++ b/workflow/plugins/python/web/web_create_flask_app/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebCreateFlaskApp plugin.""" + +from .web_create_flask_app import WebCreateFlaskApp + + +def create(): + return WebCreateFlaskApp() diff --git a/workflow/plugins/python/web/web_create_flask_app/package.json b/workflow/plugins/python/web/web_create_flask_app/package.json index 097da2043..b0268ab58 100644 --- a/workflow/plugins/python/web/web_create_flask_app/package.json +++ b/workflow/plugins/python/web/web_create_flask_app/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_create_flask_app", "version": "1.0.0", - "description": "web_create_flask_app plugin", + "description": "Create a Flask application instance", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_create_flask_app.py", + "files": ["web_create_flask_app.py", "factory.py"], "metadata": { "plugin_type": "web.create_flask_app", - "category": "web" + "category": "web", + "class": "WebCreateFlaskApp", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_create_flask_app/web_create_flask_app.py b/workflow/plugins/python/web/web_create_flask_app/web_create_flask_app.py index 889656413..f5f67fe65 100644 --- a/workflow/plugins/python/web/web_create_flask_app/web_create_flask_app.py +++ b/workflow/plugins/python/web/web_create_flask_app/web_create_flask_app.py @@ -1,27 +1,38 @@ """Workflow plugin: create Flask app.""" + from flask import Flask +from ...base import NodeExecutor -def run(runtime, inputs): - """Create a Flask application instance. - Inputs: - name: Application name (default: __name__) - config: Dictionary of Flask configuration options +class WebCreateFlaskApp(NodeExecutor): + """Create a Flask application instance.""" - Returns: - dict: Contains the Flask app in result - """ - name = inputs.get("name", "__main__") - config = inputs.get("config", {}) + node_type = "web.create_flask_app" + category = "web" + description = "Create a Flask application instance" - app = Flask(name) + def execute(self, inputs, runtime=None): + """Create a Flask application instance. - # Apply configuration - for key, value in config.items(): - app.config[key] = value + Inputs: + name: Application name (default: __name__) + config: Dictionary of Flask configuration options - # Store app in runtime context for other plugins to use - runtime.context["flask_app"] = app + Returns: + dict: Contains the Flask app in result + """ + name = inputs.get("name", "__main__") + config = inputs.get("config", {}) - return {"result": app, "message": "Flask app created"} + app = Flask(name) + + # Apply configuration + for key, value in config.items(): + app.config[key] = value + + # Store app in runtime context for other plugins to use + if runtime is not None: + runtime.context["flask_app"] = app + + return {"result": app, "message": "Flask app created"} diff --git a/workflow/plugins/python/web/web_get_env_vars/factory.py b/workflow/plugins/python/web/web_get_env_vars/factory.py new file mode 100644 index 000000000..1aaaf1c22 --- /dev/null +++ b/workflow/plugins/python/web/web_get_env_vars/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebGetEnvVars plugin.""" + +from .web_get_env_vars import WebGetEnvVars + + +def create(): + return WebGetEnvVars() diff --git a/workflow/plugins/python/web/web_get_env_vars/package.json b/workflow/plugins/python/web/web_get_env_vars/package.json index 5e9effbe9..b60a34433 100644 --- a/workflow/plugins/python/web/web_get_env_vars/package.json +++ b/workflow/plugins/python/web/web_get_env_vars/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_get_env_vars", "version": "1.0.0", - "description": "web_get_env_vars plugin", + "description": "Get environment variables from .env file", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_get_env_vars.py", + "files": ["web_get_env_vars.py", "factory.py"], "metadata": { "plugin_type": "web.get_env_vars", - "category": "web" + "category": "web", + "class": "WebGetEnvVars", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_get_env_vars/web_get_env_vars.py b/workflow/plugins/python/web/web_get_env_vars/web_get_env_vars.py index cbd491e0a..1710b6a71 100644 --- a/workflow/plugins/python/web/web_get_env_vars/web_get_env_vars.py +++ b/workflow/plugins/python/web/web_get_env_vars/web_get_env_vars.py @@ -1,22 +1,32 @@ """Workflow plugin: get environment variables.""" + from pathlib import Path +from ...base import NodeExecutor -def run(_runtime, _inputs): + +class WebGetEnvVars(NodeExecutor): """Get environment variables from .env file.""" - env_path = Path(".env") - if not env_path.exists(): - return {"result": {}} - result = {} - for raw in env_path.read_text(encoding="utf-8").splitlines(): - line = raw.strip() - if not line or line.startswith("#"): - continue - if "=" not in line: - continue - key, value = line.split("=", 1) - value = value.strip().strip("'\"") - result[key.strip()] = value + node_type = "web.get_env_vars" + category = "web" + description = "Get environment variables from .env file" - return {"result": result} + def execute(self, inputs, runtime=None): + """Get environment variables from .env file.""" + env_path = Path(".env") + if not env_path.exists(): + return {"result": {}} + + result = {} + for raw in env_path.read_text(encoding="utf-8").splitlines(): + line = raw.strip() + if not line or line.startswith("#"): + continue + if "=" not in line: + continue + key, value = line.split("=", 1) + value = value.strip().strip("'\"") + result[key.strip()] = value + + return {"result": result} diff --git a/workflow/plugins/python/web/web_get_prompt_content/factory.py b/workflow/plugins/python/web/web_get_prompt_content/factory.py new file mode 100644 index 000000000..1450c8c29 --- /dev/null +++ b/workflow/plugins/python/web/web_get_prompt_content/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebGetPromptContent plugin.""" + +from .web_get_prompt_content import WebGetPromptContent + + +def create(): + return WebGetPromptContent() diff --git a/workflow/plugins/python/web/web_get_prompt_content/package.json b/workflow/plugins/python/web/web_get_prompt_content/package.json index 188fc62cc..cf6f0457c 100644 --- a/workflow/plugins/python/web/web_get_prompt_content/package.json +++ b/workflow/plugins/python/web/web_get_prompt_content/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_get_prompt_content", "version": "1.0.0", - "description": "web_get_prompt_content plugin", + "description": "Get prompt content from prompt file", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_get_prompt_content.py", + "files": ["web_get_prompt_content.py", "factory.py"], "metadata": { "plugin_type": "web.get_prompt_content", - "category": "web" + "category": "web", + "class": "WebGetPromptContent", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_get_prompt_content/web_get_prompt_content.py b/workflow/plugins/python/web/web_get_prompt_content/web_get_prompt_content.py index fd8eb67df..4678b98b4 100644 --- a/workflow/plugins/python/web/web_get_prompt_content/web_get_prompt_content.py +++ b/workflow/plugins/python/web/web_get_prompt_content/web_get_prompt_content.py @@ -1,12 +1,22 @@ """Workflow plugin: get prompt content.""" + import os from pathlib import Path +from ...base import NodeExecutor -def run(_runtime, _inputs): + +class WebGetPromptContent(NodeExecutor): """Get prompt content from prompt file.""" - path = Path(os.environ.get("PROMPT_PATH", "prompt.yml")) - if path.is_file(): - content = path.read_text(encoding="utf-8") - return {"result": content} - return {"result": ""} + + node_type = "web.get_prompt_content" + category = "web" + description = "Get prompt content from prompt file" + + def execute(self, inputs, runtime=None): + """Get prompt content from prompt file.""" + path = Path(os.environ.get("PROMPT_PATH", "prompt.yml")) + if path.is_file(): + content = path.read_text(encoding="utf-8") + return {"result": content} + return {"result": ""} diff --git a/workflow/plugins/python/web/web_get_recent_logs/factory.py b/workflow/plugins/python/web/web_get_recent_logs/factory.py new file mode 100644 index 000000000..3f98e9ca1 --- /dev/null +++ b/workflow/plugins/python/web/web_get_recent_logs/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebGetRecentLogs plugin.""" + +from .web_get_recent_logs import WebGetRecentLogs + + +def create(): + return WebGetRecentLogs() diff --git a/workflow/plugins/python/web/web_get_recent_logs/package.json b/workflow/plugins/python/web/web_get_recent_logs/package.json index be5494346..9df888d15 100644 --- a/workflow/plugins/python/web/web_get_recent_logs/package.json +++ b/workflow/plugins/python/web/web_get_recent_logs/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_get_recent_logs", "version": "1.0.0", - "description": "web_get_recent_logs plugin", + "description": "Get recent log entries", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_get_recent_logs.py", + "files": ["web_get_recent_logs.py", "factory.py"], "metadata": { "plugin_type": "web.get_recent_logs", - "category": "web" + "category": "web", + "class": "WebGetRecentLogs", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_get_recent_logs/web_get_recent_logs.py b/workflow/plugins/python/web/web_get_recent_logs/web_get_recent_logs.py index 46dcce418..045f8d3fd 100644 --- a/workflow/plugins/python/web/web_get_recent_logs/web_get_recent_logs.py +++ b/workflow/plugins/python/web/web_get_recent_logs/web_get_recent_logs.py @@ -1,16 +1,26 @@ """Workflow plugin: get recent logs.""" + from pathlib import Path +from ...base import NodeExecutor -def run(_runtime, inputs): + +class WebGetRecentLogs(NodeExecutor): """Get recent log entries.""" - lines = inputs.get("lines", 50) - log_file = Path("metabuilder.log") - if not log_file.exists(): - return {"result": ""} + node_type = "web.get_recent_logs" + category = "web" + description = "Get recent log entries" - with log_file.open("r", encoding="utf-8") as handle: - content = handle.readlines() + def execute(self, inputs, runtime=None): + """Get recent log entries.""" + lines = inputs.get("lines", 50) + log_file = Path("metabuilder.log") - return {"result": "".join(content[-lines:])} + if not log_file.exists(): + return {"result": ""} + + with log_file.open("r", encoding="utf-8") as handle: + content = handle.readlines() + + return {"result": "".join(content[-lines:])} diff --git a/workflow/plugins/python/web/web_persist_env_vars/factory.py b/workflow/plugins/python/web/web_persist_env_vars/factory.py new file mode 100644 index 000000000..fa6aee07e --- /dev/null +++ b/workflow/plugins/python/web/web_persist_env_vars/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebPersistEnvVars plugin.""" + +from .web_persist_env_vars import WebPersistEnvVars + + +def create(): + return WebPersistEnvVars() diff --git a/workflow/plugins/python/web/web_persist_env_vars/package.json b/workflow/plugins/python/web/web_persist_env_vars/package.json index 85f8e9a91..1c4660227 100644 --- a/workflow/plugins/python/web/web_persist_env_vars/package.json +++ b/workflow/plugins/python/web/web_persist_env_vars/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_persist_env_vars", "version": "1.0.0", - "description": "web_persist_env_vars plugin", + "description": "Persist environment variables to .env file", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_persist_env_vars.py", + "files": ["web_persist_env_vars.py", "factory.py"], "metadata": { "plugin_type": "web.persist_env_vars", - "category": "web" + "category": "web", + "class": "WebPersistEnvVars", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_persist_env_vars/web_persist_env_vars.py b/workflow/plugins/python/web/web_persist_env_vars/web_persist_env_vars.py index 0cdcbbd32..81fc9f656 100644 --- a/workflow/plugins/python/web/web_persist_env_vars/web_persist_env_vars.py +++ b/workflow/plugins/python/web/web_persist_env_vars/web_persist_env_vars.py @@ -1,15 +1,25 @@ """Workflow plugin: persist environment variables.""" + from pathlib import Path +from ...base import NodeExecutor -def run(_runtime, inputs): + +class WebPersistEnvVars(NodeExecutor): """Persist environment variables to .env file.""" - from dotenv import set_key - updates = inputs.get("updates", {}) - env_path = Path(".env") - env_path.touch(exist_ok=True) - for key, value in updates.items(): - set_key(env_path, key, value) + node_type = "web.persist_env_vars" + category = "web" + description = "Persist environment variables to .env file" - return {"result": "Environment variables persisted"} + def execute(self, inputs, runtime=None): + """Persist environment variables to .env file.""" + from dotenv import set_key + + updates = inputs.get("updates", {}) + env_path = Path(".env") + env_path.touch(exist_ok=True) + for key, value in updates.items(): + set_key(env_path, key, value) + + return {"result": "Environment variables persisted"} diff --git a/workflow/plugins/python/web/web_read_json/factory.py b/workflow/plugins/python/web/web_read_json/factory.py new file mode 100644 index 000000000..d3578de2e --- /dev/null +++ b/workflow/plugins/python/web/web_read_json/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebReadJson plugin.""" + +from .web_read_json import WebReadJson + + +def create(): + return WebReadJson() diff --git a/workflow/plugins/python/web/web_read_json/package.json b/workflow/plugins/python/web/web_read_json/package.json index 116ab2507..fea31d586 100644 --- a/workflow/plugins/python/web/web_read_json/package.json +++ b/workflow/plugins/python/web/web_read_json/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_read_json", "version": "1.0.0", - "description": "web_read_json plugin", + "description": "Read JSON file", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_read_json.py", + "files": ["web_read_json.py", "factory.py"], "metadata": { "plugin_type": "web.read_json", - "category": "web" + "category": "web", + "class": "WebReadJson", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_read_json/web_read_json.py b/workflow/plugins/python/web/web_read_json/web_read_json.py index 201e30e7d..375518a2a 100644 --- a/workflow/plugins/python/web/web_read_json/web_read_json.py +++ b/workflow/plugins/python/web/web_read_json/web_read_json.py @@ -1,21 +1,31 @@ """Workflow plugin: read JSON file.""" + import json from pathlib import Path +from ...base import NodeExecutor -def run(_runtime, inputs): + +class WebReadJson(NodeExecutor): """Read JSON file.""" - path = inputs.get("path") - if not path: - return {"error": "path is required"} - path_obj = Path(path) - if not path_obj.exists(): - return {"result": {}} + node_type = "web.read_json" + category = "web" + description = "Read JSON file" - try: - json_data = json.loads(path_obj.read_text(encoding="utf-8")) - except json.JSONDecodeError: - return {"result": {}} + def execute(self, inputs, runtime=None): + """Read JSON file.""" + path = inputs.get("path") + if not path: + return {"error": "path is required"} - return {"result": json_data} + path_obj = Path(path) + if not path_obj.exists(): + return {"result": {}} + + try: + json_data = json.loads(path_obj.read_text(encoding="utf-8")) + except json.JSONDecodeError: + return {"result": {}} + + return {"result": json_data} diff --git a/workflow/plugins/python/web/web_start_server/factory.py b/workflow/plugins/python/web/web_start_server/factory.py new file mode 100644 index 000000000..429b3ace1 --- /dev/null +++ b/workflow/plugins/python/web/web_start_server/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebStartServer plugin.""" + +from .web_start_server import WebStartServer + + +def create(): + return WebStartServer() diff --git a/workflow/plugins/python/web/web_start_server/package.json b/workflow/plugins/python/web/web_start_server/package.json index a527de0f5..248a01961 100644 --- a/workflow/plugins/python/web/web_start_server/package.json +++ b/workflow/plugins/python/web/web_start_server/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_start_server", "version": "1.0.0", - "description": "web_start_server plugin", + "description": "Start the Flask web server", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_start_server.py", + "files": ["web_start_server.py", "factory.py"], "metadata": { "plugin_type": "web.start_server", - "category": "web" + "category": "web", + "class": "WebStartServer", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_start_server/web_start_server.py b/workflow/plugins/python/web/web_start_server/web_start_server.py index 2bfb9f618..5c1713708 100644 --- a/workflow/plugins/python/web/web_start_server/web_start_server.py +++ b/workflow/plugins/python/web/web_start_server/web_start_server.py @@ -1,26 +1,38 @@ """Workflow plugin: start Flask server.""" +from ...base import NodeExecutor -def run(runtime, inputs): - """Start the Flask web server. - Inputs: - host: Host address (default: 0.0.0.0) - port: Port number (default: 8000) - debug: Enable debug mode (default: False) +class WebStartServer(NodeExecutor): + """Start the Flask web server.""" - Returns: - dict: Success indicator (note: this blocks until server stops) - """ - app = runtime.context.get("flask_app") - if not app: - return {"error": "Flask app not found in context. Run web.create_flask_app first."} + node_type = "web.start_server" + category = "web" + description = "Start the Flask web server" - host = inputs.get("host", "0.0.0.0") - port = inputs.get("port", 8000) - debug = inputs.get("debug", False) + def execute(self, inputs, runtime=None): + """Start the Flask web server. - # This will block until the server is stopped - app.run(host=host, port=port, debug=debug) + Inputs: + host: Host address (default: 0.0.0.0) + port: Port number (default: 8000) + debug: Enable debug mode (default: False) - return {"result": "Server stopped"} + Returns: + dict: Success indicator (note: this blocks until server stops) + """ + if runtime is None: + return {"error": "Runtime context required"} + + app = runtime.context.get("flask_app") + if not app: + return {"error": "Flask app not found in context. Run web.create_flask_app first."} + + host = inputs.get("host", "0.0.0.0") + port = inputs.get("port", 8000) + debug = inputs.get("debug", False) + + # This will block until the server is stopped + app.run(host=host, port=port, debug=debug) + + return {"result": "Server stopped"} diff --git a/workflow/plugins/python/web/web_write_prompt/factory.py b/workflow/plugins/python/web/web_write_prompt/factory.py new file mode 100644 index 000000000..dd294020a --- /dev/null +++ b/workflow/plugins/python/web/web_write_prompt/factory.py @@ -0,0 +1,7 @@ +"""Factory for WebWritePrompt plugin.""" + +from .web_write_prompt import WebWritePrompt + + +def create(): + return WebWritePrompt() diff --git a/workflow/plugins/python/web/web_write_prompt/package.json b/workflow/plugins/python/web/web_write_prompt/package.json index 622f29413..d38243d86 100644 --- a/workflow/plugins/python/web/web_write_prompt/package.json +++ b/workflow/plugins/python/web/web_write_prompt/package.json @@ -1,13 +1,16 @@ { "name": "@metabuilder/web_write_prompt", "version": "1.0.0", - "description": "web_write_prompt plugin", + "description": "Write prompt content to file", "author": "MetaBuilder", "license": "MIT", "keywords": ["web", "workflow", "plugin"], "main": "web_write_prompt.py", + "files": ["web_write_prompt.py", "factory.py"], "metadata": { "plugin_type": "web.write_prompt", - "category": "web" + "category": "web", + "class": "WebWritePrompt", + "entrypoint": "execute" } } diff --git a/workflow/plugins/python/web/web_write_prompt/web_write_prompt.py b/workflow/plugins/python/web/web_write_prompt/web_write_prompt.py index 7a3ffc4b5..d5b17018f 100644 --- a/workflow/plugins/python/web/web_write_prompt/web_write_prompt.py +++ b/workflow/plugins/python/web/web_write_prompt/web_write_prompt.py @@ -1,11 +1,21 @@ """Workflow plugin: write prompt.""" + import os from pathlib import Path +from ...base import NodeExecutor -def run(_runtime, inputs): + +class WebWritePrompt(NodeExecutor): """Write prompt content to file.""" - content = inputs.get("content", "") - path = Path(os.environ.get("PROMPT_PATH", "prompt.yml")) - path.write_text(content or "", encoding="utf-8") - return {"result": "Prompt written successfully"} + + node_type = "web.write_prompt" + category = "web" + description = "Write prompt content to file" + + def execute(self, inputs, runtime=None): + """Write prompt content to file.""" + content = inputs.get("content", "") + path = Path(os.environ.get("PROMPT_PATH", "prompt.yml")) + path.write_text(content or "", encoding="utf-8") + return {"result": "Prompt written successfully"} diff --git a/workflow/plugins/rust/convert/convert_parse_json/package.json b/workflow/plugins/rust/convert/convert_parse_json/package.json index f6f2d2415..3b8b1c8f5 100644 --- a/workflow/plugins/rust/convert/convert_parse_json/package.json +++ b/workflow/plugins/rust/convert/convert_parse_json/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/convert_parse_json", - "version": "0.1.0", + "version": "1.0.0", "description": "Parse JSON string to value", "author": "MetaBuilder", "license": "MIT", - "keywords": ["convert", "workflow", "plugin", "rust"], + "keywords": ["convert", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "convert.parse_json", "category": "convert", - "runtime": "rust" + "struct": "ConvertParseJson", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/convert/convert_parse_json/src/factory.rs b/workflow/plugins/rust/convert/convert_parse_json/src/factory.rs new file mode 100644 index 000000000..d5bb3cde8 --- /dev/null +++ b/workflow/plugins/rust/convert/convert_parse_json/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ConvertParseJson plugin. + +use super::ConvertParseJson; + +/// Creates a new ConvertParseJson instance. +pub fn create() -> ConvertParseJson { + ConvertParseJson::new() +} diff --git a/workflow/plugins/rust/convert/convert_parse_json/src/lib.rs b/workflow/plugins/rust/convert/convert_parse_json/src/lib.rs index a5d2e281a..758696ab7 100644 --- a/workflow/plugins/rust/convert/convert_parse_json/src/lib.rs +++ b/workflow/plugins/rust/convert/convert_parse_json/src/lib.rs @@ -1,28 +1,65 @@ //! Workflow plugin: parse JSON string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Parse JSON string to value. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// ConvertParseJson implements the NodeExecutor trait for JSON parsing. +pub struct ConvertParseJson { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - match serde_json::from_str::(&string) { - Ok(value) => { - output.insert("result".to_string(), value); - } - Err(e) => { - output.insert("result".to_string(), Value::Null); - output.insert("error".to_string(), serde_json::json!(e.to_string())); +impl ConvertParseJson { + /// Creates a new ConvertParseJson instance. + pub fn new() -> Self { + Self { + node_type: "convert.parse_json", + category: "convert", + description: "Parse JSON string to value", } } +} - Ok(output) +impl Default for ConvertParseJson { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ConvertParseJson { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut output = HashMap::new(); + + match serde_json::from_str::(&string) { + Ok(value) => { + output.insert("result".to_string(), value); + } + Err(e) => { + output.insert("result".to_string(), Value::Null); + output.insert("error".to_string(), serde_json::json!(e.to_string())); + } + } + + output + } +} + +/// Creates a new ConvertParseJson instance. +pub fn create() -> ConvertParseJson { + ConvertParseJson::new() } #[cfg(test)] @@ -31,11 +68,29 @@ mod tests { #[test] fn test_parse_json() { - let mut runtime = HashMap::new(); + let executor = ConvertParseJson::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("{\"a\":1}")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!({"a": 1}))); } + + #[test] + fn test_parse_json_invalid() { + let executor = ConvertParseJson::new(); + let mut inputs = HashMap::new(); + inputs.insert("string".to_string(), serde_json::json!("{invalid}")); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&Value::Null)); + assert!(result.get("error").is_some()); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "convert.parse_json"); + assert_eq!(executor.category, "convert"); + } } diff --git a/workflow/plugins/rust/convert/convert_to_boolean/package.json b/workflow/plugins/rust/convert/convert_to_boolean/package.json index 3942fad7b..84999462b 100644 --- a/workflow/plugins/rust/convert/convert_to_boolean/package.json +++ b/workflow/plugins/rust/convert/convert_to_boolean/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/convert_to_boolean", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert value to boolean", "author": "MetaBuilder", "license": "MIT", - "keywords": ["convert", "workflow", "plugin", "rust"], + "keywords": ["convert", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "convert.to_boolean", "category": "convert", - "runtime": "rust" + "struct": "ConvertToBoolean", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/convert/convert_to_boolean/src/factory.rs b/workflow/plugins/rust/convert/convert_to_boolean/src/factory.rs new file mode 100644 index 000000000..c338a68bc --- /dev/null +++ b/workflow/plugins/rust/convert/convert_to_boolean/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ConvertToBoolean plugin. + +use super::ConvertToBoolean; + +/// Creates a new ConvertToBoolean instance. +pub fn create() -> ConvertToBoolean { + ConvertToBoolean::new() +} diff --git a/workflow/plugins/rust/convert/convert_to_boolean/src/lib.rs b/workflow/plugins/rust/convert/convert_to_boolean/src/lib.rs index ea80a9e35..1de4f8613 100644 --- a/workflow/plugins/rust/convert/convert_to_boolean/src/lib.rs +++ b/workflow/plugins/rust/convert/convert_to_boolean/src/lib.rs @@ -1,27 +1,64 @@ //! Workflow plugin: convert to boolean. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert value to boolean. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value = inputs.get("value").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = match value { - Value::Bool(b) => *b, - Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false), - Value::String(s) => { - let lower = s.to_lowercase(); - lower == "true" || lower == "1" || lower == "yes" +/// ConvertToBoolean implements the NodeExecutor trait for boolean conversion. +pub struct ConvertToBoolean { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl ConvertToBoolean { + /// Creates a new ConvertToBoolean instance. + pub fn new() -> Self { + Self { + node_type: "convert.to_boolean", + category: "convert", + description: "Convert value to boolean", } - Value::Null => false, - Value::Array(a) => !a.is_empty(), - Value::Object(o) => !o.is_empty(), - }; + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl Default for ConvertToBoolean { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ConvertToBoolean { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value = inputs.get("value").unwrap_or(&Value::Null); + + let result = match value { + Value::Bool(b) => *b, + Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false), + Value::String(s) => { + let lower = s.to_lowercase(); + lower == "true" || lower == "1" || lower == "yes" + } + Value::Null => false, + Value::Array(a) => !a.is_empty(), + Value::Object(o) => !o.is_empty(), + }; + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(result)); + output + } +} + +/// Creates a new ConvertToBoolean instance. +pub fn create() -> ConvertToBoolean { + ConvertToBoolean::new() } #[cfg(test)] @@ -29,12 +66,29 @@ mod tests { use super::*; #[test] - fn test_to_boolean() { - let mut runtime = HashMap::new(); + fn test_to_boolean_string() { + let executor = ConvertToBoolean::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!("true")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_to_boolean_number() { + let executor = ConvertToBoolean::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!(0)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "convert.to_boolean"); + assert_eq!(executor.category, "convert"); + } } diff --git a/workflow/plugins/rust/convert/convert_to_json/package.json b/workflow/plugins/rust/convert/convert_to_json/package.json index 5e197f867..e0c922b38 100644 --- a/workflow/plugins/rust/convert/convert_to_json/package.json +++ b/workflow/plugins/rust/convert/convert_to_json/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/convert_to_json", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert value to JSON string", "author": "MetaBuilder", "license": "MIT", - "keywords": ["convert", "workflow", "plugin", "rust"], + "keywords": ["convert", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "convert.to_json", "category": "convert", - "runtime": "rust" + "struct": "ConvertToJson", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/convert/convert_to_json/src/factory.rs b/workflow/plugins/rust/convert/convert_to_json/src/factory.rs new file mode 100644 index 000000000..695423ec0 --- /dev/null +++ b/workflow/plugins/rust/convert/convert_to_json/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ConvertToJson plugin. + +use super::ConvertToJson; + +/// Creates a new ConvertToJson instance. +pub fn create() -> ConvertToJson { + ConvertToJson::new() +} diff --git a/workflow/plugins/rust/convert/convert_to_json/src/lib.rs b/workflow/plugins/rust/convert/convert_to_json/src/lib.rs index 6292267d4..237237e8e 100644 --- a/workflow/plugins/rust/convert/convert_to_json/src/lib.rs +++ b/workflow/plugins/rust/convert/convert_to_json/src/lib.rs @@ -1,25 +1,62 @@ //! Workflow plugin: convert to JSON string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert value to JSON string. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value = inputs.get("value").unwrap_or(&Value::Null); - let pretty: bool = inputs - .get("pretty") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(false); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = if pretty { - serde_json::to_string_pretty(value).unwrap_or_default() - } else { - serde_json::to_string(value).unwrap_or_default() - }; +/// ConvertToJson implements the NodeExecutor trait for JSON string conversion. +pub struct ConvertToJson { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl ConvertToJson { + /// Creates a new ConvertToJson instance. + pub fn new() -> Self { + Self { + node_type: "convert.to_json", + category: "convert", + description: "Convert value to JSON string", + } + } +} + +impl Default for ConvertToJson { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ConvertToJson { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value = inputs.get("value").unwrap_or(&Value::Null); + let pretty: bool = inputs + .get("pretty") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(false); + + let result = if pretty { + serde_json::to_string_pretty(value).unwrap_or_default() + } else { + serde_json::to_string(value).unwrap_or_default() + }; + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(result)); + output + } +} + +/// Creates a new ConvertToJson instance. +pub fn create() -> ConvertToJson { + ConvertToJson::new() } #[cfg(test)] @@ -28,11 +65,30 @@ mod tests { #[test] fn test_to_json() { - let mut runtime = HashMap::new(); + let executor = ConvertToJson::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!({"a": 1})); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("{\"a\":1}"))); } + + #[test] + fn test_to_json_pretty() { + let executor = ConvertToJson::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!({"a": 1})); + inputs.insert("pretty".to_string(), serde_json::json!(true)); + + let result = executor.execute(inputs, None); + let json_str = result.get("result").unwrap().as_str().unwrap(); + assert!(json_str.contains('\n')); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "convert.to_json"); + assert_eq!(executor.category, "convert"); + } } diff --git a/workflow/plugins/rust/convert/convert_to_list/package.json b/workflow/plugins/rust/convert/convert_to_list/package.json index 5b5139539..6a06d6b35 100644 --- a/workflow/plugins/rust/convert/convert_to_list/package.json +++ b/workflow/plugins/rust/convert/convert_to_list/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/convert_to_list", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert value to list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["convert", "workflow", "plugin", "rust"], + "keywords": ["convert", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "convert.to_list", "category": "convert", - "runtime": "rust" + "struct": "ConvertToList", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/convert/convert_to_list/src/factory.rs b/workflow/plugins/rust/convert/convert_to_list/src/factory.rs new file mode 100644 index 000000000..5b9dbb0fb --- /dev/null +++ b/workflow/plugins/rust/convert/convert_to_list/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ConvertToList plugin. + +use super::ConvertToList; + +/// Creates a new ConvertToList instance. +pub fn create() -> ConvertToList { + ConvertToList::new() +} diff --git a/workflow/plugins/rust/convert/convert_to_list/src/lib.rs b/workflow/plugins/rust/convert/convert_to_list/src/lib.rs index 406548650..014d15689 100644 --- a/workflow/plugins/rust/convert/convert_to_list/src/lib.rs +++ b/workflow/plugins/rust/convert/convert_to_list/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: convert to list. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert value to list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value = inputs.get("value").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = match value { - Value::Array(a) => a.clone(), - Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(), - Value::Null => vec![], - _ => vec![value.clone()], - }; +/// ConvertToList implements the NodeExecutor trait for list conversion. +pub struct ConvertToList { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl ConvertToList { + /// Creates a new ConvertToList instance. + pub fn new() -> Self { + Self { + node_type: "convert.to_list", + category: "convert", + description: "Convert value to list", + } + } +} + +impl Default for ConvertToList { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ConvertToList { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value = inputs.get("value").unwrap_or(&Value::Null); + + let result = match value { + Value::Array(a) => a.clone(), + Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(), + Value::Null => vec![], + _ => vec![value.clone()], + }; + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(result)); + output + } +} + +/// Creates a new ConvertToList instance. +pub fn create() -> ConvertToList { + ConvertToList::new() } #[cfg(test)] @@ -24,12 +61,29 @@ mod tests { use super::*; #[test] - fn test_to_list() { - let mut runtime = HashMap::new(); + fn test_to_list_string() { + let executor = ConvertToList::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!("abc")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(["a", "b", "c"]))); } + + #[test] + fn test_to_list_single_value() { + let executor = ConvertToList::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!(42)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!([42]))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "convert.to_list"); + assert_eq!(executor.category, "convert"); + } } diff --git a/workflow/plugins/rust/convert/convert_to_number/package.json b/workflow/plugins/rust/convert/convert_to_number/package.json index 33eb1c0a5..60b5c04f7 100644 --- a/workflow/plugins/rust/convert/convert_to_number/package.json +++ b/workflow/plugins/rust/convert/convert_to_number/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/convert_to_number", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert value to number", "author": "MetaBuilder", "license": "MIT", - "keywords": ["convert", "workflow", "plugin", "rust"], + "keywords": ["convert", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "convert.to_number", "category": "convert", - "runtime": "rust" + "struct": "ConvertToNumber", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/convert/convert_to_number/src/factory.rs b/workflow/plugins/rust/convert/convert_to_number/src/factory.rs new file mode 100644 index 000000000..907365fa6 --- /dev/null +++ b/workflow/plugins/rust/convert/convert_to_number/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ConvertToNumber plugin. + +use super::ConvertToNumber; + +/// Creates a new ConvertToNumber instance. +pub fn create() -> ConvertToNumber { + ConvertToNumber::new() +} diff --git a/workflow/plugins/rust/convert/convert_to_number/src/lib.rs b/workflow/plugins/rust/convert/convert_to_number/src/lib.rs index cda9b17b5..2031e2d0f 100644 --- a/workflow/plugins/rust/convert/convert_to_number/src/lib.rs +++ b/workflow/plugins/rust/convert/convert_to_number/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: convert to number. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert value to number. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value = inputs.get("value").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = match value { - Value::Number(n) => n.as_f64().unwrap_or(0.0), - Value::String(s) => s.parse::().unwrap_or(0.0), - Value::Bool(b) => if *b { 1.0 } else { 0.0 }, - _ => 0.0, - }; +/// ConvertToNumber implements the NodeExecutor trait for number conversion. +pub struct ConvertToNumber { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl ConvertToNumber { + /// Creates a new ConvertToNumber instance. + pub fn new() -> Self { + Self { + node_type: "convert.to_number", + category: "convert", + description: "Convert value to number", + } + } +} + +impl Default for ConvertToNumber { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ConvertToNumber { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value = inputs.get("value").unwrap_or(&Value::Null); + + let result = match value { + Value::Number(n) => n.as_f64().unwrap_or(0.0), + Value::String(s) => s.parse::().unwrap_or(0.0), + Value::Bool(b) => if *b { 1.0 } else { 0.0 }, + _ => 0.0, + }; + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(result)); + output + } +} + +/// Creates a new ConvertToNumber instance. +pub fn create() -> ConvertToNumber { + ConvertToNumber::new() } #[cfg(test)] @@ -24,12 +61,29 @@ mod tests { use super::*; #[test] - fn test_to_number() { - let mut runtime = HashMap::new(); + fn test_to_number_string() { + let executor = ConvertToNumber::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!("42.5")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(42.5))); } + + #[test] + fn test_to_number_bool() { + let executor = ConvertToNumber::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!(true)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(1.0))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "convert.to_number"); + assert_eq!(executor.category, "convert"); + } } diff --git a/workflow/plugins/rust/convert/convert_to_object/package.json b/workflow/plugins/rust/convert/convert_to_object/package.json index 36e9bc1f6..8e3dfd0a8 100644 --- a/workflow/plugins/rust/convert/convert_to_object/package.json +++ b/workflow/plugins/rust/convert/convert_to_object/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/convert_to_object", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert value to object/dict", "author": "MetaBuilder", "license": "MIT", - "keywords": ["convert", "workflow", "plugin", "rust"], + "keywords": ["convert", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "convert.to_object", "category": "convert", - "runtime": "rust" + "struct": "ConvertToObject", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/convert/convert_to_object/src/factory.rs b/workflow/plugins/rust/convert/convert_to_object/src/factory.rs new file mode 100644 index 000000000..3db9e1b40 --- /dev/null +++ b/workflow/plugins/rust/convert/convert_to_object/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ConvertToObject plugin. + +use super::ConvertToObject; + +/// Creates a new ConvertToObject instance. +pub fn create() -> ConvertToObject { + ConvertToObject::new() +} diff --git a/workflow/plugins/rust/convert/convert_to_object/src/lib.rs b/workflow/plugins/rust/convert/convert_to_object/src/lib.rs index b25c35133..c14edf8bd 100644 --- a/workflow/plugins/rust/convert/convert_to_object/src/lib.rs +++ b/workflow/plugins/rust/convert/convert_to_object/src/lib.rs @@ -1,34 +1,71 @@ //! Workflow plugin: convert to object. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert value to object/dict. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value = inputs.get("value").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = match value { - Value::Object(o) => Value::Object(o.clone()), - Value::Array(a) => { - // Convert array of [key, value] pairs to object - let mut obj = serde_json::Map::new(); - for item in a { - if let Value::Array(pair) = item { - if pair.len() >= 2 { - if let Value::String(key) = &pair[0] { - obj.insert(key.clone(), pair[1].clone()); +/// ConvertToObject implements the NodeExecutor trait for object conversion. +pub struct ConvertToObject { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl ConvertToObject { + /// Creates a new ConvertToObject instance. + pub fn new() -> Self { + Self { + node_type: "convert.to_object", + category: "convert", + description: "Convert value to object/dict", + } + } +} + +impl Default for ConvertToObject { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ConvertToObject { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value = inputs.get("value").unwrap_or(&Value::Null); + + let result = match value { + Value::Object(o) => Value::Object(o.clone()), + Value::Array(a) => { + // Convert array of [key, value] pairs to object + let mut obj = serde_json::Map::new(); + for item in a { + if let Value::Array(pair) = item { + if pair.len() >= 2 { + if let Value::String(key) = &pair[0] { + obj.insert(key.clone(), pair[1].clone()); + } } } } + Value::Object(obj) } - Value::Object(obj) - } - _ => Value::Object(serde_json::Map::new()), - }; + _ => Value::Object(serde_json::Map::new()), + }; - let mut output = HashMap::new(); - output.insert("result".to_string(), result); - Ok(output) + let mut output = HashMap::new(); + output.insert("result".to_string(), result); + output + } +} + +/// Creates a new ConvertToObject instance. +pub fn create() -> ConvertToObject { + ConvertToObject::new() } #[cfg(test)] @@ -36,12 +73,29 @@ mod tests { use super::*; #[test] - fn test_to_object() { - let mut runtime = HashMap::new(); + fn test_to_object_array() { + let executor = ConvertToObject::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!([["a", 1], ["b", 2]])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!({"a": 1, "b": 2}))); } + + #[test] + fn test_to_object_already_object() { + let executor = ConvertToObject::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!({"x": 10})); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!({"x": 10}))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "convert.to_object"); + assert_eq!(executor.category, "convert"); + } } diff --git a/workflow/plugins/rust/convert/convert_to_string/package.json b/workflow/plugins/rust/convert/convert_to_string/package.json index ea5b29829..21cf3450b 100644 --- a/workflow/plugins/rust/convert/convert_to_string/package.json +++ b/workflow/plugins/rust/convert/convert_to_string/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/convert_to_string", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert value to string", "author": "MetaBuilder", "license": "MIT", - "keywords": ["convert", "workflow", "plugin", "rust"], + "keywords": ["convert", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "convert.to_string", "category": "convert", - "runtime": "rust" + "struct": "ConvertToString", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/convert/convert_to_string/src/factory.rs b/workflow/plugins/rust/convert/convert_to_string/src/factory.rs new file mode 100644 index 000000000..a978d0fbe --- /dev/null +++ b/workflow/plugins/rust/convert/convert_to_string/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ConvertToString plugin. + +use super::ConvertToString; + +/// Creates a new ConvertToString instance. +pub fn create() -> ConvertToString { + ConvertToString::new() +} diff --git a/workflow/plugins/rust/convert/convert_to_string/src/lib.rs b/workflow/plugins/rust/convert/convert_to_string/src/lib.rs index 2d2171e70..124675b26 100644 --- a/workflow/plugins/rust/convert/convert_to_string/src/lib.rs +++ b/workflow/plugins/rust/convert/convert_to_string/src/lib.rs @@ -1,21 +1,58 @@ //! Workflow plugin: convert to string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert value to string. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value = inputs.get("value").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = match value { - Value::String(s) => s.clone(), - Value::Null => String::new(), - _ => serde_json::to_string(value).unwrap_or_default(), - }; +/// ConvertToString implements the NodeExecutor trait for string conversion. +pub struct ConvertToString { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl ConvertToString { + /// Creates a new ConvertToString instance. + pub fn new() -> Self { + Self { + node_type: "convert.to_string", + category: "convert", + description: "Convert value to string", + } + } +} + +impl Default for ConvertToString { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ConvertToString { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value = inputs.get("value").unwrap_or(&Value::Null); + + let result = match value { + Value::String(s) => s.clone(), + Value::Null => String::new(), + _ => serde_json::to_string(value).unwrap_or_default(), + }; + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(result)); + output + } +} + +/// Creates a new ConvertToString instance. +pub fn create() -> ConvertToString { + ConvertToString::new() } #[cfg(test)] @@ -23,12 +60,29 @@ mod tests { use super::*; #[test] - fn test_to_string() { - let mut runtime = HashMap::new(); + fn test_to_string_number() { + let executor = ConvertToString::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!(42)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("42"))); } + + #[test] + fn test_to_string_already_string() { + let executor = ConvertToString::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!("hello")); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!("hello"))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "convert.to_string"); + assert_eq!(executor.category, "convert"); + } } diff --git a/workflow/plugins/rust/list/list_at/package.json b/workflow/plugins/rust/list/list_at/package.json index 93400d1e6..ef157a786 100644 --- a/workflow/plugins/rust/list/list_at/package.json +++ b/workflow/plugins/rust/list/list_at/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_at", - "version": "0.1.0", + "version": "1.0.0", "description": "Get element at index", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.at", "category": "list", - "runtime": "rust" + "struct": "ListAt", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_at/src/factory.rs b/workflow/plugins/rust/list/list_at/src/factory.rs new file mode 100644 index 000000000..65e0585e5 --- /dev/null +++ b/workflow/plugins/rust/list/list_at/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListAt plugin. + +use super::ListAt; + +/// Creates a new ListAt instance. +pub fn create() -> ListAt { + ListAt::new() +} diff --git a/workflow/plugins/rust/list/list_at/src/lib.rs b/workflow/plugins/rust/list/list_at/src/lib.rs index 9c5621974..8b2d1691e 100644 --- a/workflow/plugins/rust/list/list_at/src/lib.rs +++ b/workflow/plugins/rust/list/list_at/src/lib.rs @@ -1,31 +1,68 @@ //! Workflow plugin: get element at index. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Get element at index. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let index: i64 = inputs - .get("index") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let len = list.len() as i64; - let idx = if index < 0 { len + index } else { index }; +/// ListAt implements the NodeExecutor trait for getting element at index. +pub struct ListAt { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let result = if idx >= 0 && (idx as usize) < list.len() { - list[idx as usize].clone() - } else { - Value::Null - }; +impl ListAt { + /// Creates a new ListAt instance. + pub fn new() -> Self { + Self { + node_type: "list.at", + category: "list", + description: "Get element at index", + } + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), result); - Ok(output) +impl Default for ListAt { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListAt { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let index: i64 = inputs + .get("index") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0); + + let len = list.len() as i64; + let idx = if index < 0 { len + index } else { index }; + + let value = if idx >= 0 && (idx as usize) < list.len() { + list[idx as usize].clone() + } else { + Value::Null + }; + + let mut result = HashMap::new(); + result.insert("result".to_string(), value); + result + } +} + +/// Creates a new ListAt instance. +pub fn create() -> ListAt { + ListAt::new() } #[cfg(test)] @@ -34,12 +71,19 @@ mod tests { #[test] fn test_at() { - let mut runtime = HashMap::new(); + let executor = ListAt::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3])); inputs.insert("index".to_string(), serde_json::json!(1)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(2))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.at"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_concat/package.json b/workflow/plugins/rust/list/list_concat/package.json index 8fb4e0bbf..c3b02194c 100644 --- a/workflow/plugins/rust/list/list_concat/package.json +++ b/workflow/plugins/rust/list/list_concat/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_concat", - "version": "0.1.0", + "version": "1.0.0", "description": "Concatenate multiple lists", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.concat", "category": "list", - "runtime": "rust" + "struct": "ListConcat", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_concat/src/factory.rs b/workflow/plugins/rust/list/list_concat/src/factory.rs new file mode 100644 index 000000000..d1235c687 --- /dev/null +++ b/workflow/plugins/rust/list/list_concat/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListConcat plugin. + +use super::ListConcat; + +/// Creates a new ListConcat instance. +pub fn create() -> ListConcat { + ListConcat::new() +} diff --git a/workflow/plugins/rust/list/list_concat/src/lib.rs b/workflow/plugins/rust/list/list_concat/src/lib.rs index 6150ebb1b..ec0f4e0d7 100644 --- a/workflow/plugins/rust/list/list_concat/src/lib.rs +++ b/workflow/plugins/rust/list/list_concat/src/lib.rs @@ -1,20 +1,57 @@ //! Workflow plugin: concatenate lists. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Concatenate multiple lists. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let lists: Vec> = inputs - .get("lists") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result: Vec = lists.into_iter().flatten().collect(); +/// ListConcat implements the NodeExecutor trait for concatenating lists. +pub struct ListConcat { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl ListConcat { + /// Creates a new ListConcat instance. + pub fn new() -> Self { + Self { + node_type: "list.concat", + category: "list", + description: "Concatenate multiple lists", + } + } +} + +impl Default for ListConcat { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListConcat { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let lists: Vec> = inputs + .get("lists") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let concatenated: Vec = lists.into_iter().flatten().collect(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(concatenated)); + result + } +} + +/// Creates a new ListConcat instance. +pub fn create() -> ListConcat { + ListConcat::new() } #[cfg(test)] @@ -23,11 +60,18 @@ mod tests { #[test] fn test_concat() { - let mut runtime = HashMap::new(); + let executor = ListConcat::new(); let mut inputs = HashMap::new(); inputs.insert("lists".to_string(), serde_json::json!([[1, 2], [3, 4]])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!([1, 2, 3, 4]))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.concat"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_contains/package.json b/workflow/plugins/rust/list/list_contains/package.json index 77b765f76..64d1c31f9 100644 --- a/workflow/plugins/rust/list/list_contains/package.json +++ b/workflow/plugins/rust/list/list_contains/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_contains", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if list contains value", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.contains", "category": "list", - "runtime": "rust" + "struct": "ListContains", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_contains/src/factory.rs b/workflow/plugins/rust/list/list_contains/src/factory.rs new file mode 100644 index 000000000..24e0837e0 --- /dev/null +++ b/workflow/plugins/rust/list/list_contains/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListContains plugin. + +use super::ListContains; + +/// Creates a new ListContains instance. +pub fn create() -> ListContains { + ListContains::new() +} diff --git a/workflow/plugins/rust/list/list_contains/src/lib.rs b/workflow/plugins/rust/list/list_contains/src/lib.rs index 15002bee4..feab2cfcd 100644 --- a/workflow/plugins/rust/list/list_contains/src/lib.rs +++ b/workflow/plugins/rust/list/list_contains/src/lib.rs @@ -1,19 +1,56 @@ //! Workflow plugin: check if list contains value. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if list contains value. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let value = inputs.get("value").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(list.contains(value))); - Ok(output) +/// ListContains implements the NodeExecutor trait for checking if list contains value. +pub struct ListContains { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl ListContains { + /// Creates a new ListContains instance. + pub fn new() -> Self { + Self { + node_type: "list.contains", + category: "list", + description: "Check if list contains value", + } + } +} + +impl Default for ListContains { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListContains { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let value = inputs.get("value").unwrap_or(&Value::Null); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(list.contains(value))); + result + } +} + +/// Creates a new ListContains instance. +pub fn create() -> ListContains { + ListContains::new() } #[cfg(test)] @@ -22,12 +59,19 @@ mod tests { #[test] fn test_contains() { - let mut runtime = HashMap::new(); + let executor = ListContains::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3])); inputs.insert("value".to_string(), serde_json::json!(2)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.contains"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_first/package.json b/workflow/plugins/rust/list/list_first/package.json index 0db48a824..9558c79b5 100644 --- a/workflow/plugins/rust/list/list_first/package.json +++ b/workflow/plugins/rust/list/list_first/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_first", - "version": "0.1.0", + "version": "1.0.0", "description": "Get first element of list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.first", "category": "list", - "runtime": "rust" + "struct": "ListFirst", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_first/src/factory.rs b/workflow/plugins/rust/list/list_first/src/factory.rs new file mode 100644 index 000000000..624ea8a14 --- /dev/null +++ b/workflow/plugins/rust/list/list_first/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListFirst plugin. + +use super::ListFirst; + +/// Creates a new ListFirst instance. +pub fn create() -> ListFirst { + ListFirst::new() +} diff --git a/workflow/plugins/rust/list/list_first/src/lib.rs b/workflow/plugins/rust/list/list_first/src/lib.rs index cbe371fb5..32a5bccb0 100644 --- a/workflow/plugins/rust/list/list_first/src/lib.rs +++ b/workflow/plugins/rust/list/list_first/src/lib.rs @@ -1,20 +1,57 @@ //! Workflow plugin: get first element. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Get first element of list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = list.first().cloned().unwrap_or(Value::Null); +/// ListFirst implements the NodeExecutor trait for getting first element. +pub struct ListFirst { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), result); - Ok(output) +impl ListFirst { + /// Creates a new ListFirst instance. + pub fn new() -> Self { + Self { + node_type: "list.first", + category: "list", + description: "Get first element of list", + } + } +} + +impl Default for ListFirst { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListFirst { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let value = list.first().cloned().unwrap_or(Value::Null); + + let mut result = HashMap::new(); + result.insert("result".to_string(), value); + result + } +} + +/// Creates a new ListFirst instance. +pub fn create() -> ListFirst { + ListFirst::new() } #[cfg(test)] @@ -23,11 +60,18 @@ mod tests { #[test] fn test_first() { - let mut runtime = HashMap::new(); + let executor = ListFirst::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(1))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.first"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_index_of/package.json b/workflow/plugins/rust/list/list_index_of/package.json index eacf5c865..827f885cb 100644 --- a/workflow/plugins/rust/list/list_index_of/package.json +++ b/workflow/plugins/rust/list/list_index_of/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_index_of", - "version": "0.1.0", + "version": "1.0.0", "description": "Find index of value in list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.index_of", "category": "list", - "runtime": "rust" + "struct": "ListIndexOf", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_index_of/src/factory.rs b/workflow/plugins/rust/list/list_index_of/src/factory.rs new file mode 100644 index 000000000..fbde38663 --- /dev/null +++ b/workflow/plugins/rust/list/list_index_of/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListIndexOf plugin. + +use super::ListIndexOf; + +/// Creates a new ListIndexOf instance. +pub fn create() -> ListIndexOf { + ListIndexOf::new() +} diff --git a/workflow/plugins/rust/list/list_index_of/src/lib.rs b/workflow/plugins/rust/list/list_index_of/src/lib.rs index 3b4fec0f1..561f800f0 100644 --- a/workflow/plugins/rust/list/list_index_of/src/lib.rs +++ b/workflow/plugins/rust/list/list_index_of/src/lib.rs @@ -1,21 +1,58 @@ //! Workflow plugin: find index of value in list. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Find index of value in list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let value = inputs.get("value").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = list.iter().position(|v| v == value).map(|i| i as i64).unwrap_or(-1); +/// ListIndexOf implements the NodeExecutor trait for finding index of value. +pub struct ListIndexOf { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl ListIndexOf { + /// Creates a new ListIndexOf instance. + pub fn new() -> Self { + Self { + node_type: "list.index_of", + category: "list", + description: "Find index of value in list", + } + } +} + +impl Default for ListIndexOf { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListIndexOf { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let value = inputs.get("value").unwrap_or(&Value::Null); + + let index = list.iter().position(|v| v == value).map(|i| i as i64).unwrap_or(-1); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(index)); + result + } +} + +/// Creates a new ListIndexOf instance. +pub fn create() -> ListIndexOf { + ListIndexOf::new() } #[cfg(test)] @@ -24,12 +61,19 @@ mod tests { #[test] fn test_index_of() { - let mut runtime = HashMap::new(); + let executor = ListIndexOf::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3])); inputs.insert("value".to_string(), serde_json::json!(2)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(1))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.index_of"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_last/package.json b/workflow/plugins/rust/list/list_last/package.json index 27f1bc6c1..a7d37f858 100644 --- a/workflow/plugins/rust/list/list_last/package.json +++ b/workflow/plugins/rust/list/list_last/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_last", - "version": "0.1.0", + "version": "1.0.0", "description": "Get last element of list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.last", "category": "list", - "runtime": "rust" + "struct": "ListLast", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_last/src/factory.rs b/workflow/plugins/rust/list/list_last/src/factory.rs new file mode 100644 index 000000000..c678ab453 --- /dev/null +++ b/workflow/plugins/rust/list/list_last/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListLast plugin. + +use super::ListLast; + +/// Creates a new ListLast instance. +pub fn create() -> ListLast { + ListLast::new() +} diff --git a/workflow/plugins/rust/list/list_last/src/lib.rs b/workflow/plugins/rust/list/list_last/src/lib.rs index 283fc0878..4ef84465f 100644 --- a/workflow/plugins/rust/list/list_last/src/lib.rs +++ b/workflow/plugins/rust/list/list_last/src/lib.rs @@ -1,20 +1,57 @@ //! Workflow plugin: get last element. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Get last element of list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = list.last().cloned().unwrap_or(Value::Null); +/// ListLast implements the NodeExecutor trait for getting last element. +pub struct ListLast { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), result); - Ok(output) +impl ListLast { + /// Creates a new ListLast instance. + pub fn new() -> Self { + Self { + node_type: "list.last", + category: "list", + description: "Get last element of list", + } + } +} + +impl Default for ListLast { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListLast { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let value = list.last().cloned().unwrap_or(Value::Null); + + let mut result = HashMap::new(); + result.insert("result".to_string(), value); + result + } +} + +/// Creates a new ListLast instance. +pub fn create() -> ListLast { + ListLast::new() } #[cfg(test)] @@ -23,11 +60,18 @@ mod tests { #[test] fn test_last() { - let mut runtime = HashMap::new(); + let executor = ListLast::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(3))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.last"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_length/package.json b/workflow/plugins/rust/list/list_length/package.json index c6342fea7..6d944ff78 100644 --- a/workflow/plugins/rust/list/list_length/package.json +++ b/workflow/plugins/rust/list/list_length/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_length", - "version": "0.1.0", + "version": "1.0.0", "description": "Get list length", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.length", "category": "list", - "runtime": "rust" + "struct": "ListLength", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_length/src/factory.rs b/workflow/plugins/rust/list/list_length/src/factory.rs new file mode 100644 index 000000000..1139dd1eb --- /dev/null +++ b/workflow/plugins/rust/list/list_length/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListLength plugin. + +use super::ListLength; + +/// Creates a new ListLength instance. +pub fn create() -> ListLength { + ListLength::new() +} diff --git a/workflow/plugins/rust/list/list_length/src/lib.rs b/workflow/plugins/rust/list/list_length/src/lib.rs index e7d03cc38..890eca04c 100644 --- a/workflow/plugins/rust/list/list_length/src/lib.rs +++ b/workflow/plugins/rust/list/list_length/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: list length. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Get list length. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(list.len())); - Ok(output) +/// ListLength implements the NodeExecutor trait for getting list length. +pub struct ListLength { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl ListLength { + /// Creates a new ListLength instance. + pub fn new() -> Self { + Self { + node_type: "list.length", + category: "list", + description: "Get list length", + } + } +} + +impl Default for ListLength { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListLength { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(list.len())); + result + } +} + +/// Creates a new ListLength instance. +pub fn create() -> ListLength { + ListLength::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_length() { - let mut runtime = HashMap::new(); + let executor = ListLength::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3, 4, 5])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(5))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.length"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_reverse/package.json b/workflow/plugins/rust/list/list_reverse/package.json index 1d4de6e83..df1f534ed 100644 --- a/workflow/plugins/rust/list/list_reverse/package.json +++ b/workflow/plugins/rust/list/list_reverse/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_reverse", - "version": "0.1.0", + "version": "1.0.0", "description": "Reverse a list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.reverse", "category": "list", - "runtime": "rust" + "struct": "ListReverse", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_reverse/src/factory.rs b/workflow/plugins/rust/list/list_reverse/src/factory.rs new file mode 100644 index 000000000..2a891323d --- /dev/null +++ b/workflow/plugins/rust/list/list_reverse/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListReverse plugin. + +use super::ListReverse; + +/// Creates a new ListReverse instance. +pub fn create() -> ListReverse { + ListReverse::new() +} diff --git a/workflow/plugins/rust/list/list_reverse/src/lib.rs b/workflow/plugins/rust/list/list_reverse/src/lib.rs index 9b23f75ef..d12f4cd14 100644 --- a/workflow/plugins/rust/list/list_reverse/src/lib.rs +++ b/workflow/plugins/rust/list/list_reverse/src/lib.rs @@ -1,20 +1,57 @@ //! Workflow plugin: reverse a list. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Reverse a list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let mut list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - list.reverse(); +/// ListReverse implements the NodeExecutor trait for reversing lists. +pub struct ListReverse { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(list)); - Ok(output) +impl ListReverse { + /// Creates a new ListReverse instance. + pub fn new() -> Self { + Self { + node_type: "list.reverse", + category: "list", + description: "Reverse a list", + } + } +} + +impl Default for ListReverse { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListReverse { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let mut list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + list.reverse(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(list)); + result + } +} + +/// Creates a new ListReverse instance. +pub fn create() -> ListReverse { + ListReverse::new() } #[cfg(test)] @@ -23,11 +60,18 @@ mod tests { #[test] fn test_reverse() { - let mut runtime = HashMap::new(); + let executor = ListReverse::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!([3, 2, 1]))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.reverse"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_slice/package.json b/workflow/plugins/rust/list/list_slice/package.json index 08e6b870a..cd85c113e 100644 --- a/workflow/plugins/rust/list/list_slice/package.json +++ b/workflow/plugins/rust/list/list_slice/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_slice", - "version": "0.1.0", + "version": "1.0.0", "description": "Slice a list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.slice", "category": "list", - "runtime": "rust" + "struct": "ListSlice", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_slice/src/factory.rs b/workflow/plugins/rust/list/list_slice/src/factory.rs new file mode 100644 index 000000000..36cf8a9d7 --- /dev/null +++ b/workflow/plugins/rust/list/list_slice/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListSlice plugin. + +use super::ListSlice; + +/// Creates a new ListSlice instance. +pub fn create() -> ListSlice { + ListSlice::new() +} diff --git a/workflow/plugins/rust/list/list_slice/src/lib.rs b/workflow/plugins/rust/list/list_slice/src/lib.rs index b9820f1ce..e622dba0c 100644 --- a/workflow/plugins/rust/list/list_slice/src/lib.rs +++ b/workflow/plugins/rust/list/list_slice/src/lib.rs @@ -1,41 +1,78 @@ //! Workflow plugin: slice a list. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Slice a list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let start: i64 = inputs - .get("start") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0); - let end: Option = inputs - .get("end") - .and_then(|v| serde_json::from_value(v.clone()).ok()); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let len = list.len() as i64; +/// ListSlice implements the NodeExecutor trait for slicing lists. +pub struct ListSlice { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - // Handle negative indices - let start_idx = if start < 0 { (len + start).max(0) } else { start.min(len) } as usize; - let end_idx = match end { - Some(e) if e < 0 => (len + e).max(0) as usize, - Some(e) => e.min(len) as usize, - None => len as usize, - }; +impl ListSlice { + /// Creates a new ListSlice instance. + pub fn new() -> Self { + Self { + node_type: "list.slice", + category: "list", + description: "Slice a list", + } + } +} - let result: Vec = if start_idx < end_idx { - list[start_idx..end_idx].to_vec() - } else { - vec![] - }; +impl Default for ListSlice { + fn default() -> Self { + Self::new() + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl NodeExecutor for ListSlice { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let start: i64 = inputs + .get("start") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0); + let end: Option = inputs + .get("end") + .and_then(|v| serde_json::from_value(v.clone()).ok()); + + let len = list.len() as i64; + + // Handle negative indices + let start_idx = if start < 0 { (len + start).max(0) } else { start.min(len) } as usize; + let end_idx = match end { + Some(e) if e < 0 => (len + e).max(0) as usize, + Some(e) => e.min(len) as usize, + None => len as usize, + }; + + let sliced: Vec = if start_idx < end_idx { + list[start_idx..end_idx].to_vec() + } else { + vec![] + }; + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(sliced)); + result + } +} + +/// Creates a new ListSlice instance. +pub fn create() -> ListSlice { + ListSlice::new() } #[cfg(test)] @@ -44,13 +81,20 @@ mod tests { #[test] fn test_slice() { - let mut runtime = HashMap::new(); + let executor = ListSlice::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 3, 4, 5])); inputs.insert("start".to_string(), serde_json::json!(1)); inputs.insert("end".to_string(), serde_json::json!(4)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!([2, 3, 4]))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.slice"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_sort/package.json b/workflow/plugins/rust/list/list_sort/package.json index 14c508996..55c265cb4 100644 --- a/workflow/plugins/rust/list/list_sort/package.json +++ b/workflow/plugins/rust/list/list_sort/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_sort", - "version": "0.1.0", + "version": "1.0.0", "description": "Sort a list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.sort", "category": "list", - "runtime": "rust" + "struct": "ListSort", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_sort/src/factory.rs b/workflow/plugins/rust/list/list_sort/src/factory.rs new file mode 100644 index 000000000..bca37ec43 --- /dev/null +++ b/workflow/plugins/rust/list/list_sort/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListSort plugin. + +use super::ListSort; + +/// Creates a new ListSort instance. +pub fn create() -> ListSort { + ListSort::new() +} diff --git a/workflow/plugins/rust/list/list_sort/src/lib.rs b/workflow/plugins/rust/list/list_sort/src/lib.rs index ecbd94d40..ee064b6c8 100644 --- a/workflow/plugins/rust/list/list_sort/src/lib.rs +++ b/workflow/plugins/rust/list/list_sort/src/lib.rs @@ -1,39 +1,76 @@ //! Workflow plugin: sort a list. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Sort a list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let mut list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - list.sort_by(|a, b| { - match (a, b) { - // Numbers - (Value::Number(n1), Value::Number(n2)) => { - let f1 = n1.as_f64().unwrap_or(0.0); - let f2 = n2.as_f64().unwrap_or(0.0); - f1.partial_cmp(&f2).unwrap_or(std::cmp::Ordering::Equal) - } - // Strings - (Value::String(s1), Value::String(s2)) => s1.cmp(s2), - // Booleans (false < true) - (Value::Bool(b1), Value::Bool(b2)) => b1.cmp(b2), - // Null is smallest - (Value::Null, Value::Null) => std::cmp::Ordering::Equal, - (Value::Null, _) => std::cmp::Ordering::Less, - (_, Value::Null) => std::cmp::Ordering::Greater, - // Mixed types: compare by type name as fallback - _ => std::cmp::Ordering::Equal, +/// ListSort implements the NodeExecutor trait for sorting lists. +pub struct ListSort { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl ListSort { + /// Creates a new ListSort instance. + pub fn new() -> Self { + Self { + node_type: "list.sort", + category: "list", + description: "Sort a list", } - }); + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(list)); - Ok(output) +impl Default for ListSort { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListSort { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let mut list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + list.sort_by(|a, b| { + match (a, b) { + // Numbers + (Value::Number(n1), Value::Number(n2)) => { + let f1 = n1.as_f64().unwrap_or(0.0); + let f2 = n2.as_f64().unwrap_or(0.0); + f1.partial_cmp(&f2).unwrap_or(std::cmp::Ordering::Equal) + } + // Strings + (Value::String(s1), Value::String(s2)) => s1.cmp(s2), + // Booleans (false < true) + (Value::Bool(b1), Value::Bool(b2)) => b1.cmp(b2), + // Null is smallest + (Value::Null, Value::Null) => std::cmp::Ordering::Equal, + (Value::Null, _) => std::cmp::Ordering::Less, + (_, Value::Null) => std::cmp::Ordering::Greater, + // Mixed types: compare by type name as fallback + _ => std::cmp::Ordering::Equal, + } + }); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(list)); + result + } +} + +/// Creates a new ListSort instance. +pub fn create() -> ListSort { + ListSort::new() } #[cfg(test)] @@ -42,11 +79,18 @@ mod tests { #[test] fn test_sort() { - let mut runtime = HashMap::new(); + let executor = ListSort::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([3, 1, 2])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!([1, 2, 3]))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.sort"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/list/list_unique/package.json b/workflow/plugins/rust/list/list_unique/package.json index 1442d1775..05fc08950 100644 --- a/workflow/plugins/rust/list/list_unique/package.json +++ b/workflow/plugins/rust/list/list_unique/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/list_unique", - "version": "0.1.0", + "version": "1.0.0", "description": "Remove duplicates from list", "author": "MetaBuilder", "license": "MIT", - "keywords": ["list", "workflow", "plugin", "rust"], + "keywords": ["list", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "list.unique", "category": "list", - "runtime": "rust" + "struct": "ListUnique", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/list/list_unique/src/factory.rs b/workflow/plugins/rust/list/list_unique/src/factory.rs new file mode 100644 index 000000000..0561d2bf8 --- /dev/null +++ b/workflow/plugins/rust/list/list_unique/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for ListUnique plugin. + +use super::ListUnique; + +/// Creates a new ListUnique instance. +pub fn create() -> ListUnique { + ListUnique::new() +} diff --git a/workflow/plugins/rust/list/list_unique/src/lib.rs b/workflow/plugins/rust/list/list_unique/src/lib.rs index 1c5e86002..bcd209760 100644 --- a/workflow/plugins/rust/list/list_unique/src/lib.rs +++ b/workflow/plugins/rust/list/list_unique/src/lib.rs @@ -1,25 +1,62 @@ //! Workflow plugin: remove duplicates from list. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Remove duplicates from list. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let list: Vec = inputs - .get("list") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut seen = Vec::new(); - for item in list { - if !seen.contains(&item) { - seen.push(item); +/// ListUnique implements the NodeExecutor trait for removing duplicates. +pub struct ListUnique { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl ListUnique { + /// Creates a new ListUnique instance. + pub fn new() -> Self { + Self { + node_type: "list.unique", + category: "list", + description: "Remove duplicates from list", } } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(seen)); - Ok(output) +impl Default for ListUnique { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for ListUnique { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let list: Vec = inputs + .get("list") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut seen = Vec::new(); + for item in list { + if !seen.contains(&item) { + seen.push(item); + } + } + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(seen)); + result + } +} + +/// Creates a new ListUnique instance. +pub fn create() -> ListUnique { + ListUnique::new() } #[cfg(test)] @@ -28,11 +65,18 @@ mod tests { #[test] fn test_unique() { - let mut runtime = HashMap::new(); + let executor = ListUnique::new(); let mut inputs = HashMap::new(); inputs.insert("list".to_string(), serde_json::json!([1, 2, 2, 3, 3, 3])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!([1, 2, 3]))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "list.unique"); + assert_eq!(executor.category, "list"); + } } diff --git a/workflow/plugins/rust/logic/logic_and/package.json b/workflow/plugins/rust/logic/logic_and/package.json index be761f793..b39a4f144 100644 --- a/workflow/plugins/rust/logic/logic_and/package.json +++ b/workflow/plugins/rust/logic/logic_and/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_and", - "version": "0.1.0", + "version": "1.0.0", "description": "Logical AND on boolean values", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.and", "category": "logic", - "runtime": "rust" + "struct": "LogicAnd", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_and/src/factory.rs b/workflow/plugins/rust/logic/logic_and/src/factory.rs new file mode 100644 index 000000000..53ce07ec3 --- /dev/null +++ b/workflow/plugins/rust/logic/logic_and/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicAnd plugin. + +use super::LogicAnd; + +/// Creates a new LogicAnd instance. +pub fn create() -> LogicAnd { + LogicAnd::new() +} diff --git a/workflow/plugins/rust/logic/logic_and/src/lib.rs b/workflow/plugins/rust/logic/logic_and/src/lib.rs index 6a748186e..f59aa7d9a 100644 --- a/workflow/plugins/rust/logic/logic_and/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_and/src/lib.rs @@ -1,8 +1,15 @@ //! Workflow plugin: logical AND. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} + /// Helper to convert Value to bool. fn to_bool(v: &Value) -> bool { match v { @@ -15,18 +22,48 @@ fn to_bool(v: &Value) -> bool { } } -/// Logical AND on boolean values. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let values: Vec = inputs - .get("values") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// LogicAnd implements the NodeExecutor trait for logical AND operations. +pub struct LogicAnd { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let result = values.iter().all(to_bool); +impl LogicAnd { + /// Creates a new LogicAnd instance. + pub fn new() -> Self { + Self { + node_type: "logic.and", + category: "logic", + description: "Logical AND on boolean values", + } + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl Default for LogicAnd { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicAnd { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let values: Vec = inputs + .get("values") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let result = values.iter().all(to_bool); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(result)); + output + } +} + +/// Creates a new LogicAnd instance. +pub fn create() -> LogicAnd { + LogicAnd::new() } #[cfg(test)] @@ -35,11 +72,28 @@ mod tests { #[test] fn test_and() { - let mut runtime = HashMap::new(); + let executor = LogicAnd::new(); let mut inputs = HashMap::new(); inputs.insert("values".to_string(), serde_json::json!([true, true, true])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_and_false() { + let executor = LogicAnd::new(); + let mut inputs = HashMap::new(); + inputs.insert("values".to_string(), serde_json::json!([true, false, true])); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.and"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_equals/package.json b/workflow/plugins/rust/logic/logic_equals/package.json index dc283ce80..f4cc6ffe5 100644 --- a/workflow/plugins/rust/logic/logic_equals/package.json +++ b/workflow/plugins/rust/logic/logic_equals/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_equals", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if two values are equal", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.equals", "category": "logic", - "runtime": "rust" + "struct": "LogicEquals", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_equals/src/factory.rs b/workflow/plugins/rust/logic/logic_equals/src/factory.rs new file mode 100644 index 000000000..7bd335a03 --- /dev/null +++ b/workflow/plugins/rust/logic/logic_equals/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicEquals plugin. + +use super::LogicEquals; + +/// Creates a new LogicEquals instance. +pub fn create() -> LogicEquals { + LogicEquals::new() +} diff --git a/workflow/plugins/rust/logic/logic_equals/src/lib.rs b/workflow/plugins/rust/logic/logic_equals/src/lib.rs index f284c562f..4b289fbb6 100644 --- a/workflow/plugins/rust/logic/logic_equals/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_equals/src/lib.rs @@ -1,16 +1,53 @@ //! Workflow plugin: equals comparison. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if two values are equal. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let a = inputs.get("a").unwrap_or(&Value::Null); - let b = inputs.get("b").unwrap_or(&Value::Null); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(a == b)); - Ok(output) +/// LogicEquals implements the NodeExecutor trait for equality comparison. +pub struct LogicEquals { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl LogicEquals { + /// Creates a new LogicEquals instance. + pub fn new() -> Self { + Self { + node_type: "logic.equals", + category: "logic", + description: "Check if two values are equal", + } + } +} + +impl Default for LogicEquals { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicEquals { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let a = inputs.get("a").unwrap_or(&Value::Null); + let b = inputs.get("b").unwrap_or(&Value::Null); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(a == b)); + output + } +} + +/// Creates a new LogicEquals instance. +pub fn create() -> LogicEquals { + LogicEquals::new() } #[cfg(test)] @@ -18,13 +55,31 @@ mod tests { use super::*; #[test] - fn test_equals() { - let mut runtime = HashMap::new(); + fn test_equals_true() { + let executor = LogicEquals::new(); let mut inputs = HashMap::new(); inputs.insert("a".to_string(), serde_json::json!(5)); inputs.insert("b".to_string(), serde_json::json!(5)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_equals_false() { + let executor = LogicEquals::new(); + let mut inputs = HashMap::new(); + inputs.insert("a".to_string(), serde_json::json!(5)); + inputs.insert("b".to_string(), serde_json::json!(10)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.equals"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_gt/package.json b/workflow/plugins/rust/logic/logic_gt/package.json index 5aada1569..876159cbf 100644 --- a/workflow/plugins/rust/logic/logic_gt/package.json +++ b/workflow/plugins/rust/logic/logic_gt/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_gt", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if a > b", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.gt", "category": "logic", - "runtime": "rust" + "struct": "LogicGt", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_gt/src/factory.rs b/workflow/plugins/rust/logic/logic_gt/src/factory.rs new file mode 100644 index 000000000..151ca0673 --- /dev/null +++ b/workflow/plugins/rust/logic/logic_gt/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicGt plugin. + +use super::LogicGt; + +/// Creates a new LogicGt instance. +pub fn create() -> LogicGt { + LogicGt::new() +} diff --git a/workflow/plugins/rust/logic/logic_gt/src/lib.rs b/workflow/plugins/rust/logic/logic_gt/src/lib.rs index 854d5f39c..2203670da 100644 --- a/workflow/plugins/rust/logic/logic_gt/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_gt/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: greater than comparison. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if a > b. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let a: f64 = inputs - .get("a") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); - let b: f64 = inputs - .get("b") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(a > b)); - Ok(output) +/// LogicGt implements the NodeExecutor trait for greater than comparison. +pub struct LogicGt { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl LogicGt { + /// Creates a new LogicGt instance. + pub fn new() -> Self { + Self { + node_type: "logic.gt", + category: "logic", + description: "Check if a > b", + } + } +} + +impl Default for LogicGt { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicGt { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let a: f64 = inputs + .get("a") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + let b: f64 = inputs + .get("b") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(a > b)); + output + } +} + +/// Creates a new LogicGt instance. +pub fn create() -> LogicGt { + LogicGt::new() } #[cfg(test)] @@ -24,13 +61,31 @@ mod tests { use super::*; #[test] - fn test_gt() { - let mut runtime = HashMap::new(); + fn test_gt_true() { + let executor = LogicGt::new(); let mut inputs = HashMap::new(); inputs.insert("a".to_string(), serde_json::json!(10.0)); inputs.insert("b".to_string(), serde_json::json!(5.0)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_gt_false() { + let executor = LogicGt::new(); + let mut inputs = HashMap::new(); + inputs.insert("a".to_string(), serde_json::json!(5.0)); + inputs.insert("b".to_string(), serde_json::json!(10.0)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.gt"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_gte/package.json b/workflow/plugins/rust/logic/logic_gte/package.json index 2cbb1550e..4470c076b 100644 --- a/workflow/plugins/rust/logic/logic_gte/package.json +++ b/workflow/plugins/rust/logic/logic_gte/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_gte", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if a >= b", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.gte", "category": "logic", - "runtime": "rust" + "struct": "LogicGte", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_gte/src/factory.rs b/workflow/plugins/rust/logic/logic_gte/src/factory.rs new file mode 100644 index 000000000..ddc03e6e2 --- /dev/null +++ b/workflow/plugins/rust/logic/logic_gte/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicGte plugin. + +use super::LogicGte; + +/// Creates a new LogicGte instance. +pub fn create() -> LogicGte { + LogicGte::new() +} diff --git a/workflow/plugins/rust/logic/logic_gte/src/lib.rs b/workflow/plugins/rust/logic/logic_gte/src/lib.rs index 27be50f9b..ae2a71fbd 100644 --- a/workflow/plugins/rust/logic/logic_gte/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_gte/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: greater than or equal comparison. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if a >= b. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let a: f64 = inputs - .get("a") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); - let b: f64 = inputs - .get("b") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(a >= b)); - Ok(output) +/// LogicGte implements the NodeExecutor trait for greater than or equal comparison. +pub struct LogicGte { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl LogicGte { + /// Creates a new LogicGte instance. + pub fn new() -> Self { + Self { + node_type: "logic.gte", + category: "logic", + description: "Check if a >= b", + } + } +} + +impl Default for LogicGte { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicGte { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let a: f64 = inputs + .get("a") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + let b: f64 = inputs + .get("b") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(a >= b)); + output + } +} + +/// Creates a new LogicGte instance. +pub fn create() -> LogicGte { + LogicGte::new() } #[cfg(test)] @@ -24,13 +61,31 @@ mod tests { use super::*; #[test] - fn test_gte() { - let mut runtime = HashMap::new(); + fn test_gte_greater() { + let executor = LogicGte::new(); + let mut inputs = HashMap::new(); + inputs.insert("a".to_string(), serde_json::json!(10.0)); + inputs.insert("b".to_string(), serde_json::json!(5.0)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(true))); + } + + #[test] + fn test_gte_equal() { + let executor = LogicGte::new(); let mut inputs = HashMap::new(); inputs.insert("a".to_string(), serde_json::json!(10.0)); inputs.insert("b".to_string(), serde_json::json!(10.0)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.gte"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_lt/package.json b/workflow/plugins/rust/logic/logic_lt/package.json index 72905b4f5..c8de3af51 100644 --- a/workflow/plugins/rust/logic/logic_lt/package.json +++ b/workflow/plugins/rust/logic/logic_lt/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_lt", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if a < b", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.lt", "category": "logic", - "runtime": "rust" + "struct": "LogicLt", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_lt/src/factory.rs b/workflow/plugins/rust/logic/logic_lt/src/factory.rs new file mode 100644 index 000000000..c2884bc7a --- /dev/null +++ b/workflow/plugins/rust/logic/logic_lt/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicLt plugin. + +use super::LogicLt; + +/// Creates a new LogicLt instance. +pub fn create() -> LogicLt { + LogicLt::new() +} diff --git a/workflow/plugins/rust/logic/logic_lt/src/lib.rs b/workflow/plugins/rust/logic/logic_lt/src/lib.rs index dfc758cf4..65d4d75ee 100644 --- a/workflow/plugins/rust/logic/logic_lt/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_lt/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: less than comparison. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if a < b. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let a: f64 = inputs - .get("a") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); - let b: f64 = inputs - .get("b") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(a < b)); - Ok(output) +/// LogicLt implements the NodeExecutor trait for less than comparison. +pub struct LogicLt { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl LogicLt { + /// Creates a new LogicLt instance. + pub fn new() -> Self { + Self { + node_type: "logic.lt", + category: "logic", + description: "Check if a < b", + } + } +} + +impl Default for LogicLt { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicLt { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let a: f64 = inputs + .get("a") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + let b: f64 = inputs + .get("b") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(a < b)); + output + } +} + +/// Creates a new LogicLt instance. +pub fn create() -> LogicLt { + LogicLt::new() } #[cfg(test)] @@ -24,13 +61,31 @@ mod tests { use super::*; #[test] - fn test_lt() { - let mut runtime = HashMap::new(); + fn test_lt_true() { + let executor = LogicLt::new(); let mut inputs = HashMap::new(); inputs.insert("a".to_string(), serde_json::json!(5.0)); inputs.insert("b".to_string(), serde_json::json!(10.0)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_lt_false() { + let executor = LogicLt::new(); + let mut inputs = HashMap::new(); + inputs.insert("a".to_string(), serde_json::json!(10.0)); + inputs.insert("b".to_string(), serde_json::json!(5.0)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.lt"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_lte/package.json b/workflow/plugins/rust/logic/logic_lte/package.json index 55df203ec..abbb0f8c7 100644 --- a/workflow/plugins/rust/logic/logic_lte/package.json +++ b/workflow/plugins/rust/logic/logic_lte/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_lte", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if a <= b", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.lte", "category": "logic", - "runtime": "rust" + "struct": "LogicLte", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_lte/src/factory.rs b/workflow/plugins/rust/logic/logic_lte/src/factory.rs new file mode 100644 index 000000000..099e47ab8 --- /dev/null +++ b/workflow/plugins/rust/logic/logic_lte/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicLte plugin. + +use super::LogicLte; + +/// Creates a new LogicLte instance. +pub fn create() -> LogicLte { + LogicLte::new() +} diff --git a/workflow/plugins/rust/logic/logic_lte/src/lib.rs b/workflow/plugins/rust/logic/logic_lte/src/lib.rs index 6bdf084b0..f4018ae7f 100644 --- a/workflow/plugins/rust/logic/logic_lte/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_lte/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: less than or equal comparison. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if a <= b. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let a: f64 = inputs - .get("a") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); - let b: f64 = inputs - .get("b") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(a <= b)); - Ok(output) +/// LogicLte implements the NodeExecutor trait for less than or equal comparison. +pub struct LogicLte { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl LogicLte { + /// Creates a new LogicLte instance. + pub fn new() -> Self { + Self { + node_type: "logic.lte", + category: "logic", + description: "Check if a <= b", + } + } +} + +impl Default for LogicLte { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicLte { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let a: f64 = inputs + .get("a") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + let b: f64 = inputs + .get("b") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(a <= b)); + output + } +} + +/// Creates a new LogicLte instance. +pub fn create() -> LogicLte { + LogicLte::new() } #[cfg(test)] @@ -24,13 +61,31 @@ mod tests { use super::*; #[test] - fn test_lte() { - let mut runtime = HashMap::new(); + fn test_lte_less() { + let executor = LogicLte::new(); + let mut inputs = HashMap::new(); + inputs.insert("a".to_string(), serde_json::json!(5.0)); + inputs.insert("b".to_string(), serde_json::json!(10.0)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(true))); + } + + #[test] + fn test_lte_equal() { + let executor = LogicLte::new(); let mut inputs = HashMap::new(); inputs.insert("a".to_string(), serde_json::json!(10.0)); inputs.insert("b".to_string(), serde_json::json!(10.0)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.lte"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_not/package.json b/workflow/plugins/rust/logic/logic_not/package.json index 392f5a3b8..66f7e35f5 100644 --- a/workflow/plugins/rust/logic/logic_not/package.json +++ b/workflow/plugins/rust/logic/logic_not/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_not", - "version": "0.1.0", + "version": "1.0.0", "description": "Logical NOT on a boolean value", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.not", "category": "logic", - "runtime": "rust" + "struct": "LogicNot", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_not/src/factory.rs b/workflow/plugins/rust/logic/logic_not/src/factory.rs new file mode 100644 index 000000000..d898766ef --- /dev/null +++ b/workflow/plugins/rust/logic/logic_not/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicNot plugin. + +use super::LogicNot; + +/// Creates a new LogicNot instance. +pub fn create() -> LogicNot { + LogicNot::new() +} diff --git a/workflow/plugins/rust/logic/logic_not/src/lib.rs b/workflow/plugins/rust/logic/logic_not/src/lib.rs index d4a999b30..67d931ccb 100644 --- a/workflow/plugins/rust/logic/logic_not/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_not/src/lib.rs @@ -1,8 +1,15 @@ //! Workflow plugin: logical NOT. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} + /// Helper to convert Value to bool. fn to_bool(v: &Value) -> bool { match v { @@ -15,13 +22,43 @@ fn to_bool(v: &Value) -> bool { } } -/// Logical NOT on a boolean value. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value = inputs.get("value").unwrap_or(&Value::Null); +/// LogicNot implements the NodeExecutor trait for logical NOT operations. +pub struct LogicNot { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(!to_bool(value))); - Ok(output) +impl LogicNot { + /// Creates a new LogicNot instance. + pub fn new() -> Self { + Self { + node_type: "logic.not", + category: "logic", + description: "Logical NOT on a boolean value", + } + } +} + +impl Default for LogicNot { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicNot { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value = inputs.get("value").unwrap_or(&Value::Null); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(!to_bool(value))); + output + } +} + +/// Creates a new LogicNot instance. +pub fn create() -> LogicNot { + LogicNot::new() } #[cfg(test)] @@ -29,12 +66,29 @@ mod tests { use super::*; #[test] - fn test_not() { - let mut runtime = HashMap::new(); + fn test_not_true() { + let executor = LogicNot::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!(true)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(false))); } + + #[test] + fn test_not_false() { + let executor = LogicNot::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!(false)); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(true))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.not"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_or/package.json b/workflow/plugins/rust/logic/logic_or/package.json index f1e076350..90aa5cf51 100644 --- a/workflow/plugins/rust/logic/logic_or/package.json +++ b/workflow/plugins/rust/logic/logic_or/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_or", - "version": "0.1.0", + "version": "1.0.0", "description": "Logical OR on boolean values", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.or", "category": "logic", - "runtime": "rust" + "struct": "LogicOr", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_or/src/factory.rs b/workflow/plugins/rust/logic/logic_or/src/factory.rs new file mode 100644 index 000000000..dd5d9e344 --- /dev/null +++ b/workflow/plugins/rust/logic/logic_or/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicOr plugin. + +use super::LogicOr; + +/// Creates a new LogicOr instance. +pub fn create() -> LogicOr { + LogicOr::new() +} diff --git a/workflow/plugins/rust/logic/logic_or/src/lib.rs b/workflow/plugins/rust/logic/logic_or/src/lib.rs index 4a44de592..37aec57a1 100644 --- a/workflow/plugins/rust/logic/logic_or/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_or/src/lib.rs @@ -1,8 +1,15 @@ //! Workflow plugin: logical OR. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} + /// Helper to convert Value to bool. fn to_bool(v: &Value) -> bool { match v { @@ -15,18 +22,48 @@ fn to_bool(v: &Value) -> bool { } } -/// Logical OR on boolean values. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let values: Vec = inputs - .get("values") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// LogicOr implements the NodeExecutor trait for logical OR operations. +pub struct LogicOr { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let result = values.iter().any(to_bool); +impl LogicOr { + /// Creates a new LogicOr instance. + pub fn new() -> Self { + Self { + node_type: "logic.or", + category: "logic", + description: "Logical OR on boolean values", + } + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl Default for LogicOr { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicOr { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let values: Vec = inputs + .get("values") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let result = values.iter().any(to_bool); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(result)); + output + } +} + +/// Creates a new LogicOr instance. +pub fn create() -> LogicOr { + LogicOr::new() } #[cfg(test)] @@ -35,11 +72,28 @@ mod tests { #[test] fn test_or() { - let mut runtime = HashMap::new(); + let executor = LogicOr::new(); let mut inputs = HashMap::new(); inputs.insert("values".to_string(), serde_json::json!([false, true, false])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_or_all_false() { + let executor = LogicOr::new(); + let mut inputs = HashMap::new(); + inputs.insert("values".to_string(), serde_json::json!([false, false, false])); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.or"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/logic/logic_xor/package.json b/workflow/plugins/rust/logic/logic_xor/package.json index bb8256bcb..eeecb846c 100644 --- a/workflow/plugins/rust/logic/logic_xor/package.json +++ b/workflow/plugins/rust/logic/logic_xor/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/logic_xor", - "version": "0.1.0", - "description": "Logical XOR on boolean values", + "version": "1.0.0", + "description": "Logical XOR on boolean values (exactly one true)", "author": "MetaBuilder", "license": "MIT", - "keywords": ["logic", "workflow", "plugin", "rust"], + "keywords": ["logic", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "logic.xor", "category": "logic", - "runtime": "rust" + "struct": "LogicXor", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/logic/logic_xor/src/factory.rs b/workflow/plugins/rust/logic/logic_xor/src/factory.rs new file mode 100644 index 000000000..cf86932c9 --- /dev/null +++ b/workflow/plugins/rust/logic/logic_xor/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for LogicXor plugin. + +use super::LogicXor; + +/// Creates a new LogicXor instance. +pub fn create() -> LogicXor { + LogicXor::new() +} diff --git a/workflow/plugins/rust/logic/logic_xor/src/lib.rs b/workflow/plugins/rust/logic/logic_xor/src/lib.rs index 409598f74..7c16aa1a1 100644 --- a/workflow/plugins/rust/logic/logic_xor/src/lib.rs +++ b/workflow/plugins/rust/logic/logic_xor/src/lib.rs @@ -1,8 +1,15 @@ //! Workflow plugin: logical XOR. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} + /// Helper to convert Value to bool. fn to_bool(v: &Value) -> bool { match v { @@ -15,18 +22,48 @@ fn to_bool(v: &Value) -> bool { } } -/// Logical XOR on boolean values (exactly one true). -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let values: Vec = inputs - .get("values") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// LogicXor implements the NodeExecutor trait for logical XOR operations. +pub struct LogicXor { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let true_count = values.iter().filter(|v| to_bool(v)).count(); +impl LogicXor { + /// Creates a new LogicXor instance. + pub fn new() -> Self { + Self { + node_type: "logic.xor", + category: "logic", + description: "Logical XOR on boolean values (exactly one true)", + } + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(true_count == 1)); - Ok(output) +impl Default for LogicXor { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for LogicXor { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let values: Vec = inputs + .get("values") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let true_count = values.iter().filter(|v| to_bool(v)).count(); + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(true_count == 1)); + output + } +} + +/// Creates a new LogicXor instance. +pub fn create() -> LogicXor { + LogicXor::new() } #[cfg(test)] @@ -34,12 +71,29 @@ mod tests { use super::*; #[test] - fn test_xor() { - let mut runtime = HashMap::new(); + fn test_xor_one_true() { + let executor = LogicXor::new(); let mut inputs = HashMap::new(); inputs.insert("values".to_string(), serde_json::json!([false, true, false])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_xor_multiple_true() { + let executor = LogicXor::new(); + let mut inputs = HashMap::new(); + inputs.insert("values".to_string(), serde_json::json!([true, true, false])); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "logic.xor"); + assert_eq!(executor.category, "logic"); + } } diff --git a/workflow/plugins/rust/math/math_abs/package.json b/workflow/plugins/rust/math/math_abs/package.json index 371e76050..fef2ef539 100644 --- a/workflow/plugins/rust/math/math_abs/package.json +++ b/workflow/plugins/rust/math/math_abs/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_abs", - "version": "0.1.0", + "version": "1.0.0", "description": "Calculate absolute value", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.abs", "category": "math", - "runtime": "rust" + "struct": "MathAbs", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_abs/src/factory.rs b/workflow/plugins/rust/math/math_abs/src/factory.rs new file mode 100644 index 000000000..9ac0fde1b --- /dev/null +++ b/workflow/plugins/rust/math/math_abs/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathAbs plugin. + +use super::MathAbs; + +/// Creates a new MathAbs instance. +pub fn create() -> MathAbs { + MathAbs::new() +} diff --git a/workflow/plugins/rust/math/math_abs/src/lib.rs b/workflow/plugins/rust/math/math_abs/src/lib.rs index 820251b55..e3a07a07e 100644 --- a/workflow/plugins/rust/math/math_abs/src/lib.rs +++ b/workflow/plugins/rust/math/math_abs/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: absolute value. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Calculate absolute value of a number. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value: f64 = inputs - .get("value") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(value.abs())); - Ok(output) +/// MathAbs implements the NodeExecutor trait for absolute value operations. +pub struct MathAbs { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl MathAbs { + /// Creates a new MathAbs instance. + pub fn new() -> Self { + Self { + node_type: "math.abs", + category: "math", + description: "Calculate absolute value", + } + } +} + +impl Default for MathAbs { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathAbs { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value: f64 = inputs + .get("value") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(value.abs())); + result + } +} + +/// Creates a new MathAbs instance. +pub fn create() -> MathAbs { + MathAbs::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_abs() { - let mut runtime = HashMap::new(); + let executor = MathAbs::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!(-5.0)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(5.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.abs"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_add/package.json b/workflow/plugins/rust/math/math_add/package.json index a8c851787..82c333da3 100644 --- a/workflow/plugins/rust/math/math_add/package.json +++ b/workflow/plugins/rust/math/math_add/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_add", - "version": "0.1.0", + "version": "1.0.0", "description": "Add two or more numbers", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.add", "category": "math", - "runtime": "rust" + "struct": "MathAdd", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_add/src/factory.rs b/workflow/plugins/rust/math/math_add/src/factory.rs new file mode 100644 index 000000000..1c3139c1c --- /dev/null +++ b/workflow/plugins/rust/math/math_add/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathAdd plugin. + +use super::MathAdd; + +/// Creates a new MathAdd instance. +pub fn create() -> MathAdd { + MathAdd::new() +} diff --git a/workflow/plugins/rust/math/math_add/src/lib.rs b/workflow/plugins/rust/math/math_add/src/lib.rs index a1790ecc5..f5797f2d8 100644 --- a/workflow/plugins/rust/math/math_add/src/lib.rs +++ b/workflow/plugins/rust/math/math_add/src/lib.rs @@ -1,20 +1,57 @@ //! Workflow plugin: add numbers. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Add two or more numbers. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let numbers: Vec = inputs - .get("numbers") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let sum: f64 = numbers.iter().sum(); +/// MathAdd implements the NodeExecutor trait for adding numbers. +pub struct MathAdd { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(sum)); - Ok(output) +impl MathAdd { + /// Creates a new MathAdd instance. + pub fn new() -> Self { + Self { + node_type: "math.add", + category: "math", + description: "Add two or more numbers", + } + } +} + +impl Default for MathAdd { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathAdd { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let numbers: Vec = inputs + .get("numbers") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let sum: f64 = numbers.iter().sum(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(sum)); + result + } +} + +/// Creates a new MathAdd instance. +pub fn create() -> MathAdd { + MathAdd::new() } #[cfg(test)] @@ -23,11 +60,18 @@ mod tests { #[test] fn test_add() { - let mut runtime = HashMap::new(); + let executor = MathAdd::new(); let mut inputs = HashMap::new(); inputs.insert("numbers".to_string(), serde_json::json!([1.0, 2.0, 3.0])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(6.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.add"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_ceil/package.json b/workflow/plugins/rust/math/math_ceil/package.json index 93232f37f..c8580b6a0 100644 --- a/workflow/plugins/rust/math/math_ceil/package.json +++ b/workflow/plugins/rust/math/math_ceil/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_ceil", - "version": "0.1.0", + "version": "1.0.0", "description": "Ceil a number (round up)", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.ceil", "category": "math", - "runtime": "rust" + "struct": "MathCeil", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_ceil/src/factory.rs b/workflow/plugins/rust/math/math_ceil/src/factory.rs new file mode 100644 index 000000000..a8c236bee --- /dev/null +++ b/workflow/plugins/rust/math/math_ceil/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathCeil plugin. + +use super::MathCeil; + +/// Creates a new MathCeil instance. +pub fn create() -> MathCeil { + MathCeil::new() +} diff --git a/workflow/plugins/rust/math/math_ceil/src/lib.rs b/workflow/plugins/rust/math/math_ceil/src/lib.rs index 40cbe4595..269c9d0b3 100644 --- a/workflow/plugins/rust/math/math_ceil/src/lib.rs +++ b/workflow/plugins/rust/math/math_ceil/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: ceil a number. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Ceil a number (round up). -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value: f64 = inputs - .get("value") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(value.ceil())); - Ok(output) +/// MathCeil implements the NodeExecutor trait for ceiling operations. +pub struct MathCeil { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl MathCeil { + /// Creates a new MathCeil instance. + pub fn new() -> Self { + Self { + node_type: "math.ceil", + category: "math", + description: "Ceil a number (round up)", + } + } +} + +impl Default for MathCeil { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathCeil { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value: f64 = inputs + .get("value") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(value.ceil())); + result + } +} + +/// Creates a new MathCeil instance. +pub fn create() -> MathCeil { + MathCeil::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_ceil() { - let mut runtime = HashMap::new(); + let executor = MathCeil::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!(3.2)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(4.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.ceil"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_divide/package.json b/workflow/plugins/rust/math/math_divide/package.json index 91752a6ba..f5f94f63b 100644 --- a/workflow/plugins/rust/math/math_divide/package.json +++ b/workflow/plugins/rust/math/math_divide/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_divide", - "version": "0.1.0", + "version": "1.0.0", "description": "Divide the first number by subsequent numbers", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.divide", "category": "math", - "runtime": "rust" + "struct": "MathDivide", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_divide/src/factory.rs b/workflow/plugins/rust/math/math_divide/src/factory.rs new file mode 100644 index 000000000..d0357c6df --- /dev/null +++ b/workflow/plugins/rust/math/math_divide/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathDivide plugin. + +use super::MathDivide; + +/// Creates a new MathDivide instance. +pub fn create() -> MathDivide { + MathDivide::new() +} diff --git a/workflow/plugins/rust/math/math_divide/src/lib.rs b/workflow/plugins/rust/math/math_divide/src/lib.rs index bb2b90e0e..2dff57a4c 100644 --- a/workflow/plugins/rust/math/math_divide/src/lib.rs +++ b/workflow/plugins/rust/math/math_divide/src/lib.rs @@ -1,34 +1,71 @@ //! Workflow plugin: divide numbers. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Divide the first number by subsequent numbers. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let numbers: Vec = inputs - .get("numbers") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// MathDivide implements the NodeExecutor trait for dividing numbers. +pub struct MathDivide { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - if numbers.len() < 2 { - output.insert("result".to_string(), serde_json::json!(0)); - output.insert("error".to_string(), serde_json::json!("need at least 2 numbers")); - return Ok(output); - } - - for &n in &numbers[1..] { - if n == 0.0 { - output.insert("result".to_string(), serde_json::json!(0)); - output.insert("error".to_string(), serde_json::json!("division by zero")); - return Ok(output); +impl MathDivide { + /// Creates a new MathDivide instance. + pub fn new() -> Self { + Self { + node_type: "math.divide", + category: "math", + description: "Divide the first number by subsequent numbers", } } +} - let result = numbers.iter().skip(1).fold(numbers[0], |acc, x| acc / x); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl Default for MathDivide { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathDivide { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let numbers: Vec = inputs + .get("numbers") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + + if numbers.len() < 2 { + result.insert("result".to_string(), serde_json::json!(0)); + result.insert("error".to_string(), serde_json::json!("need at least 2 numbers")); + return result; + } + + for &n in &numbers[1..] { + if n == 0.0 { + result.insert("result".to_string(), serde_json::json!(0)); + result.insert("error".to_string(), serde_json::json!("division by zero")); + return result; + } + } + + let quotient = numbers.iter().skip(1).fold(numbers[0], |acc, x| acc / x); + result.insert("result".to_string(), serde_json::json!(quotient)); + result + } +} + +/// Creates a new MathDivide instance. +pub fn create() -> MathDivide { + MathDivide::new() } #[cfg(test)] @@ -37,11 +74,18 @@ mod tests { #[test] fn test_divide() { - let mut runtime = HashMap::new(); + let executor = MathDivide::new(); let mut inputs = HashMap::new(); inputs.insert("numbers".to_string(), serde_json::json!([24.0, 3.0, 2.0])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(4.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.divide"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_floor/package.json b/workflow/plugins/rust/math/math_floor/package.json index db8d05eaa..ea5cfc9ff 100644 --- a/workflow/plugins/rust/math/math_floor/package.json +++ b/workflow/plugins/rust/math/math_floor/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_floor", - "version": "0.1.0", + "version": "1.0.0", "description": "Floor a number (round down)", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.floor", "category": "math", - "runtime": "rust" + "struct": "MathFloor", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_floor/src/factory.rs b/workflow/plugins/rust/math/math_floor/src/factory.rs new file mode 100644 index 000000000..7dc21f449 --- /dev/null +++ b/workflow/plugins/rust/math/math_floor/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathFloor plugin. + +use super::MathFloor; + +/// Creates a new MathFloor instance. +pub fn create() -> MathFloor { + MathFloor::new() +} diff --git a/workflow/plugins/rust/math/math_floor/src/lib.rs b/workflow/plugins/rust/math/math_floor/src/lib.rs index bcdee6726..97e1456b7 100644 --- a/workflow/plugins/rust/math/math_floor/src/lib.rs +++ b/workflow/plugins/rust/math/math_floor/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: floor a number. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Floor a number (round down). -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value: f64 = inputs - .get("value") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(value.floor())); - Ok(output) +/// MathFloor implements the NodeExecutor trait for floor operations. +pub struct MathFloor { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl MathFloor { + /// Creates a new MathFloor instance. + pub fn new() -> Self { + Self { + node_type: "math.floor", + category: "math", + description: "Floor a number (round down)", + } + } +} + +impl Default for MathFloor { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathFloor { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value: f64 = inputs + .get("value") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(value.floor())); + result + } +} + +/// Creates a new MathFloor instance. +pub fn create() -> MathFloor { + MathFloor::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_floor() { - let mut runtime = HashMap::new(); + let executor = MathFloor::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!(3.7)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(3.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.floor"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_modulo/package.json b/workflow/plugins/rust/math/math_modulo/package.json index eb4170013..b0e818a0a 100644 --- a/workflow/plugins/rust/math/math_modulo/package.json +++ b/workflow/plugins/rust/math/math_modulo/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_modulo", - "version": "0.1.0", + "version": "1.0.0", "description": "Calculate modulo of two numbers", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.modulo", "category": "math", - "runtime": "rust" + "struct": "MathModulo", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_modulo/src/factory.rs b/workflow/plugins/rust/math/math_modulo/src/factory.rs new file mode 100644 index 000000000..3c1d821db --- /dev/null +++ b/workflow/plugins/rust/math/math_modulo/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathModulo plugin. + +use super::MathModulo; + +/// Creates a new MathModulo instance. +pub fn create() -> MathModulo { + MathModulo::new() +} diff --git a/workflow/plugins/rust/math/math_modulo/src/lib.rs b/workflow/plugins/rust/math/math_modulo/src/lib.rs index c19c6e61a..671bbb8b0 100644 --- a/workflow/plugins/rust/math/math_modulo/src/lib.rs +++ b/workflow/plugins/rust/math/math_modulo/src/lib.rs @@ -1,29 +1,66 @@ //! Workflow plugin: modulo operation. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Calculate modulo of two numbers. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let a: f64 = inputs - .get("a") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); - let b: f64 = inputs - .get("b") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(1.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// MathModulo implements the NodeExecutor trait for modulo operations. +pub struct MathModulo { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - if b == 0.0 { - output.insert("result".to_string(), serde_json::json!(0)); - output.insert("error".to_string(), serde_json::json!("division by zero")); - return Ok(output); +impl MathModulo { + /// Creates a new MathModulo instance. + pub fn new() -> Self { + Self { + node_type: "math.modulo", + category: "math", + description: "Calculate modulo of two numbers", + } } +} - output.insert("result".to_string(), serde_json::json!(a % b)); - Ok(output) +impl Default for MathModulo { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathModulo { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let a: f64 = inputs + .get("a") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + let b: f64 = inputs + .get("b") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(1.0); + + let mut result = HashMap::new(); + + if b == 0.0 { + result.insert("result".to_string(), serde_json::json!(0)); + result.insert("error".to_string(), serde_json::json!("division by zero")); + return result; + } + + result.insert("result".to_string(), serde_json::json!(a % b)); + result + } +} + +/// Creates a new MathModulo instance. +pub fn create() -> MathModulo { + MathModulo::new() } #[cfg(test)] @@ -32,12 +69,19 @@ mod tests { #[test] fn test_modulo() { - let mut runtime = HashMap::new(); + let executor = MathModulo::new(); let mut inputs = HashMap::new(); inputs.insert("a".to_string(), serde_json::json!(10.0)); inputs.insert("b".to_string(), serde_json::json!(3.0)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(1.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.modulo"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_multiply/package.json b/workflow/plugins/rust/math/math_multiply/package.json index 3e4fefa98..ba78d0777 100644 --- a/workflow/plugins/rust/math/math_multiply/package.json +++ b/workflow/plugins/rust/math/math_multiply/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_multiply", - "version": "0.1.0", + "version": "1.0.0", "description": "Multiply two or more numbers", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.multiply", "category": "math", - "runtime": "rust" + "struct": "MathMultiply", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_multiply/src/factory.rs b/workflow/plugins/rust/math/math_multiply/src/factory.rs new file mode 100644 index 000000000..ae8963cd4 --- /dev/null +++ b/workflow/plugins/rust/math/math_multiply/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathMultiply plugin. + +use super::MathMultiply; + +/// Creates a new MathMultiply instance. +pub fn create() -> MathMultiply { + MathMultiply::new() +} diff --git a/workflow/plugins/rust/math/math_multiply/src/lib.rs b/workflow/plugins/rust/math/math_multiply/src/lib.rs index fa42e150b..4b687689e 100644 --- a/workflow/plugins/rust/math/math_multiply/src/lib.rs +++ b/workflow/plugins/rust/math/math_multiply/src/lib.rs @@ -1,25 +1,62 @@ //! Workflow plugin: multiply numbers. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Multiply two or more numbers. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let numbers: Vec = inputs - .get("numbers") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// MathMultiply implements the NodeExecutor trait for multiplying numbers. +pub struct MathMultiply { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - if numbers.is_empty() { - output.insert("result".to_string(), serde_json::json!(0)); - return Ok(output); +impl MathMultiply { + /// Creates a new MathMultiply instance. + pub fn new() -> Self { + Self { + node_type: "math.multiply", + category: "math", + description: "Multiply two or more numbers", + } } +} - let result: f64 = numbers.iter().product(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl Default for MathMultiply { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathMultiply { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let numbers: Vec = inputs + .get("numbers") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + + if numbers.is_empty() { + result.insert("result".to_string(), serde_json::json!(0)); + return result; + } + + let product: f64 = numbers.iter().product(); + result.insert("result".to_string(), serde_json::json!(product)); + result + } +} + +/// Creates a new MathMultiply instance. +pub fn create() -> MathMultiply { + MathMultiply::new() } #[cfg(test)] @@ -28,11 +65,18 @@ mod tests { #[test] fn test_multiply() { - let mut runtime = HashMap::new(); + let executor = MathMultiply::new(); let mut inputs = HashMap::new(); inputs.insert("numbers".to_string(), serde_json::json!([2.0, 3.0, 4.0])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(24.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.multiply"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_power/package.json b/workflow/plugins/rust/math/math_power/package.json index f8ea878f6..3a627c223 100644 --- a/workflow/plugins/rust/math/math_power/package.json +++ b/workflow/plugins/rust/math/math_power/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_power", - "version": "0.1.0", + "version": "1.0.0", "description": "Calculate power of a number", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.power", "category": "math", - "runtime": "rust" + "struct": "MathPower", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_power/src/factory.rs b/workflow/plugins/rust/math/math_power/src/factory.rs new file mode 100644 index 000000000..9bfb4853a --- /dev/null +++ b/workflow/plugins/rust/math/math_power/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathPower plugin. + +use super::MathPower; + +/// Creates a new MathPower instance. +pub fn create() -> MathPower { + MathPower::new() +} diff --git a/workflow/plugins/rust/math/math_power/src/lib.rs b/workflow/plugins/rust/math/math_power/src/lib.rs index 6e5909c29..de97e112b 100644 --- a/workflow/plugins/rust/math/math_power/src/lib.rs +++ b/workflow/plugins/rust/math/math_power/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: power operation. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Calculate power of a number. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let base: f64 = inputs - .get("base") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); - let exp: f64 = inputs - .get("exponent") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(1.0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(base.powf(exp))); - Ok(output) +/// MathPower implements the NodeExecutor trait for power operations. +pub struct MathPower { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl MathPower { + /// Creates a new MathPower instance. + pub fn new() -> Self { + Self { + node_type: "math.power", + category: "math", + description: "Calculate power of a number", + } + } +} + +impl Default for MathPower { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathPower { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let base: f64 = inputs + .get("base") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + let exp: f64 = inputs + .get("exponent") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(1.0); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(base.powf(exp))); + result + } +} + +/// Creates a new MathPower instance. +pub fn create() -> MathPower { + MathPower::new() } #[cfg(test)] @@ -25,12 +62,19 @@ mod tests { #[test] fn test_power() { - let mut runtime = HashMap::new(); + let executor = MathPower::new(); let mut inputs = HashMap::new(); inputs.insert("base".to_string(), serde_json::json!(2.0)); inputs.insert("exponent".to_string(), serde_json::json!(3.0)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(8.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.power"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_round/package.json b/workflow/plugins/rust/math/math_round/package.json index ca4a3ea12..d4f241dfc 100644 --- a/workflow/plugins/rust/math/math_round/package.json +++ b/workflow/plugins/rust/math/math_round/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_round", - "version": "0.1.0", + "version": "1.0.0", "description": "Round a number to specified decimals", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.round", "category": "math", - "runtime": "rust" + "struct": "MathRound", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_round/src/factory.rs b/workflow/plugins/rust/math/math_round/src/factory.rs new file mode 100644 index 000000000..326457d8a --- /dev/null +++ b/workflow/plugins/rust/math/math_round/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathRound plugin. + +use super::MathRound; + +/// Creates a new MathRound instance. +pub fn create() -> MathRound { + MathRound::new() +} diff --git a/workflow/plugins/rust/math/math_round/src/lib.rs b/workflow/plugins/rust/math/math_round/src/lib.rs index 6300cfee0..a26ca9b03 100644 --- a/workflow/plugins/rust/math/math_round/src/lib.rs +++ b/workflow/plugins/rust/math/math_round/src/lib.rs @@ -1,25 +1,62 @@ //! Workflow plugin: round a number. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Round a number to specified decimal places. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let value: f64 = inputs - .get("value") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0.0); - let decimals: i32 = inputs - .get("decimals") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let factor = 10_f64.powi(decimals); - let result = (value * factor).round() / factor; +/// MathRound implements the NodeExecutor trait for rounding operations. +pub struct MathRound { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl MathRound { + /// Creates a new MathRound instance. + pub fn new() -> Self { + Self { + node_type: "math.round", + category: "math", + description: "Round a number to specified decimals", + } + } +} + +impl Default for MathRound { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathRound { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let value: f64 = inputs + .get("value") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0.0); + let decimals: i32 = inputs + .get("decimals") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0); + + let factor = 10_f64.powi(decimals); + let rounded = (value * factor).round() / factor; + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(rounded)); + result + } +} + +/// Creates a new MathRound instance. +pub fn create() -> MathRound { + MathRound::new() } #[cfg(test)] @@ -28,12 +65,19 @@ mod tests { #[test] fn test_round() { - let mut runtime = HashMap::new(); + let executor = MathRound::new(); let mut inputs = HashMap::new(); inputs.insert("value".to_string(), serde_json::json!(3.14159)); inputs.insert("decimals".to_string(), serde_json::json!(2)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(3.14))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.round"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/math/math_subtract/package.json b/workflow/plugins/rust/math/math_subtract/package.json index 9b3bff174..a57799b31 100644 --- a/workflow/plugins/rust/math/math_subtract/package.json +++ b/workflow/plugins/rust/math/math_subtract/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/math_subtract", - "version": "0.1.0", + "version": "1.0.0", "description": "Subtract numbers from the first number", "author": "MetaBuilder", "license": "MIT", - "keywords": ["math", "workflow", "plugin", "rust"], + "keywords": ["math", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "math.subtract", "category": "math", - "runtime": "rust" + "struct": "MathSubtract", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/math/math_subtract/src/factory.rs b/workflow/plugins/rust/math/math_subtract/src/factory.rs new file mode 100644 index 000000000..2f171fbe0 --- /dev/null +++ b/workflow/plugins/rust/math/math_subtract/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for MathSubtract plugin. + +use super::MathSubtract; + +/// Creates a new MathSubtract instance. +pub fn create() -> MathSubtract { + MathSubtract::new() +} diff --git a/workflow/plugins/rust/math/math_subtract/src/lib.rs b/workflow/plugins/rust/math/math_subtract/src/lib.rs index 98a7e9dd5..197657fad 100644 --- a/workflow/plugins/rust/math/math_subtract/src/lib.rs +++ b/workflow/plugins/rust/math/math_subtract/src/lib.rs @@ -1,26 +1,63 @@ //! Workflow plugin: subtract numbers. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Subtract numbers from the first number. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let numbers: Vec = inputs - .get("numbers") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// MathSubtract implements the NodeExecutor trait for subtracting numbers. +pub struct MathSubtract { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - if numbers.is_empty() { - output.insert("result".to_string(), serde_json::json!(0)); - output.insert("error".to_string(), serde_json::json!("numbers must be non-empty")); - return Ok(output); +impl MathSubtract { + /// Creates a new MathSubtract instance. + pub fn new() -> Self { + Self { + node_type: "math.subtract", + category: "math", + description: "Subtract numbers from the first number", + } } +} - let result = numbers.iter().skip(1).fold(numbers[0], |acc, x| acc - x); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl Default for MathSubtract { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for MathSubtract { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let numbers: Vec = inputs + .get("numbers") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + + if numbers.is_empty() { + result.insert("result".to_string(), serde_json::json!(0)); + result.insert("error".to_string(), serde_json::json!("numbers must be non-empty")); + return result; + } + + let difference = numbers.iter().skip(1).fold(numbers[0], |acc, x| acc - x); + result.insert("result".to_string(), serde_json::json!(difference)); + result + } +} + +/// Creates a new MathSubtract instance. +pub fn create() -> MathSubtract { + MathSubtract::new() } #[cfg(test)] @@ -29,11 +66,18 @@ mod tests { #[test] fn test_subtract() { - let mut runtime = HashMap::new(); + let executor = MathSubtract::new(); let mut inputs = HashMap::new(); inputs.insert("numbers".to_string(), serde_json::json!([10.0, 3.0, 2.0])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(5.0))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "math.subtract"); + assert_eq!(executor.category, "math"); + } } diff --git a/workflow/plugins/rust/plugin.rs b/workflow/plugins/rust/plugin.rs index 37245b1ea..2e5b5b574 100644 --- a/workflow/plugins/rust/plugin.rs +++ b/workflow/plugins/rust/plugin.rs @@ -3,6 +3,7 @@ //! Shared types for all Rust workflow plugins. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; /// Runtime context for plugin execution. @@ -51,11 +52,17 @@ impl std::fmt::Display for PluginError { impl std::error::Error for PluginError {} -/// Trait for workflow plugins +/// Trait for workflow plugins (legacy). pub trait Plugin { fn run(&self, runtime: &mut Runtime, inputs: &HashMap) -> PluginResult; } +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} + /// Helper to get a value from inputs with type conversion pub fn get_input( inputs: &HashMap, diff --git a/workflow/plugins/rust/string/string_concat/package.json b/workflow/plugins/rust/string/string_concat/package.json index 4b1dad744..29468e2aa 100644 --- a/workflow/plugins/rust/string/string_concat/package.json +++ b/workflow/plugins/rust/string/string_concat/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_concat", - "version": "0.1.0", + "version": "1.0.0", "description": "Concatenate multiple strings", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.concat", "category": "string", - "runtime": "rust" + "struct": "StringConcat", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_concat/src/factory.rs b/workflow/plugins/rust/string/string_concat/src/factory.rs new file mode 100644 index 000000000..a524c6cbe --- /dev/null +++ b/workflow/plugins/rust/string/string_concat/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringConcat plugin. + +use super::StringConcat; + +/// Creates a new StringConcat instance. +pub fn create() -> StringConcat { + StringConcat::new() +} diff --git a/workflow/plugins/rust/string/string_concat/src/lib.rs b/workflow/plugins/rust/string/string_concat/src/lib.rs index 2ebfe59b4..74d5e8038 100644 --- a/workflow/plugins/rust/string/string_concat/src/lib.rs +++ b/workflow/plugins/rust/string/string_concat/src/lib.rs @@ -1,24 +1,61 @@ //! Workflow plugin: concatenate strings. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Concatenate multiple strings with optional separator. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let strings: Vec = inputs - .get("strings") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let separator: String = inputs - .get("separator") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = strings.join(&separator); +/// StringConcat implements the NodeExecutor trait for concatenating strings. +pub struct StringConcat { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl StringConcat { + /// Creates a new StringConcat instance. + pub fn new() -> Self { + Self { + node_type: "string.concat", + category: "string", + description: "Concatenate multiple strings", + } + } +} + +impl Default for StringConcat { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringConcat { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let strings: Vec = inputs + .get("strings") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let separator: String = inputs + .get("separator") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let concatenated = strings.join(&separator); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(concatenated)); + result + } +} + +/// Creates a new StringConcat instance. +pub fn create() -> StringConcat { + StringConcat::new() } #[cfg(test)] @@ -27,11 +64,18 @@ mod tests { #[test] fn test_concat() { - let mut runtime = HashMap::new(); + let executor = StringConcat::new(); let mut inputs = HashMap::new(); inputs.insert("strings".to_string(), serde_json::json!(["hello", " ", "world"])); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("hello world"))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.concat"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_contains/package.json b/workflow/plugins/rust/string/string_contains/package.json index 9b2c8f3e7..2a15bee59 100644 --- a/workflow/plugins/rust/string/string_contains/package.json +++ b/workflow/plugins/rust/string/string_contains/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_contains", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if string contains substring", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.contains", "category": "string", - "runtime": "rust" + "struct": "StringContains", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_contains/src/factory.rs b/workflow/plugins/rust/string/string_contains/src/factory.rs new file mode 100644 index 000000000..76233c07f --- /dev/null +++ b/workflow/plugins/rust/string/string_contains/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringContains plugin. + +use super::StringContains; + +/// Creates a new StringContains instance. +pub fn create() -> StringContains { + StringContains::new() +} diff --git a/workflow/plugins/rust/string/string_contains/src/lib.rs b/workflow/plugins/rust/string/string_contains/src/lib.rs index c1269d7b3..82dc0e043 100644 --- a/workflow/plugins/rust/string/string_contains/src/lib.rs +++ b/workflow/plugins/rust/string/string_contains/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: string contains. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if string contains substring. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let substring: String = inputs - .get("substring") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(string.contains(&substring))); - Ok(output) +/// StringContains implements the NodeExecutor trait for checking if string contains substring. +pub struct StringContains { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl StringContains { + /// Creates a new StringContains instance. + pub fn new() -> Self { + Self { + node_type: "string.contains", + category: "string", + description: "Check if string contains substring", + } + } +} + +impl Default for StringContains { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringContains { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let substring: String = inputs + .get("substring") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(string.contains(&substring))); + result + } +} + +/// Creates a new StringContains instance. +pub fn create() -> StringContains { + StringContains::new() } #[cfg(test)] @@ -25,12 +62,19 @@ mod tests { #[test] fn test_contains() { - let mut runtime = HashMap::new(); + let executor = StringContains::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("hello world")); inputs.insert("substring".to_string(), serde_json::json!("world")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.contains"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_ends_with/package.json b/workflow/plugins/rust/string/string_ends_with/package.json index ec5c76204..ba4f7ad7d 100644 --- a/workflow/plugins/rust/string/string_ends_with/package.json +++ b/workflow/plugins/rust/string/string_ends_with/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_ends_with", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if string ends with suffix", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.ends_with", "category": "string", - "runtime": "rust" + "struct": "StringEndsWith", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_ends_with/src/factory.rs b/workflow/plugins/rust/string/string_ends_with/src/factory.rs new file mode 100644 index 000000000..6e6ed46ed --- /dev/null +++ b/workflow/plugins/rust/string/string_ends_with/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringEndsWith plugin. + +use super::StringEndsWith; + +/// Creates a new StringEndsWith instance. +pub fn create() -> StringEndsWith { + StringEndsWith::new() +} diff --git a/workflow/plugins/rust/string/string_ends_with/src/lib.rs b/workflow/plugins/rust/string/string_ends_with/src/lib.rs index d30bc7d39..919f001af 100644 --- a/workflow/plugins/rust/string/string_ends_with/src/lib.rs +++ b/workflow/plugins/rust/string/string_ends_with/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: string ends with. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if string ends with suffix. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let suffix: String = inputs - .get("suffix") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(string.ends_with(&suffix))); - Ok(output) +/// StringEndsWith implements the NodeExecutor trait for checking if string ends with suffix. +pub struct StringEndsWith { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl StringEndsWith { + /// Creates a new StringEndsWith instance. + pub fn new() -> Self { + Self { + node_type: "string.ends_with", + category: "string", + description: "Check if string ends with suffix", + } + } +} + +impl Default for StringEndsWith { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringEndsWith { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let suffix: String = inputs + .get("suffix") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(string.ends_with(&suffix))); + result + } +} + +/// Creates a new StringEndsWith instance. +pub fn create() -> StringEndsWith { + StringEndsWith::new() } #[cfg(test)] @@ -25,12 +62,19 @@ mod tests { #[test] fn test_ends_with() { - let mut runtime = HashMap::new(); + let executor = StringEndsWith::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("hello world")); inputs.insert("suffix".to_string(), serde_json::json!("world")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.ends_with"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_length/package.json b/workflow/plugins/rust/string/string_length/package.json index 75b6bb607..72db0dd56 100644 --- a/workflow/plugins/rust/string/string_length/package.json +++ b/workflow/plugins/rust/string/string_length/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_length", - "version": "0.1.0", + "version": "1.0.0", "description": "Get string length", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.length", "category": "string", - "runtime": "rust" + "struct": "StringLength", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_length/src/factory.rs b/workflow/plugins/rust/string/string_length/src/factory.rs new file mode 100644 index 000000000..0de7a320c --- /dev/null +++ b/workflow/plugins/rust/string/string_length/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringLength plugin. + +use super::StringLength; + +/// Creates a new StringLength instance. +pub fn create() -> StringLength { + StringLength::new() +} diff --git a/workflow/plugins/rust/string/string_length/src/lib.rs b/workflow/plugins/rust/string/string_length/src/lib.rs index 2c2f6e90f..a7d7beb27 100644 --- a/workflow/plugins/rust/string/string_length/src/lib.rs +++ b/workflow/plugins/rust/string/string_length/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: string length. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Get string length. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(string.len())); - Ok(output) +/// StringLength implements the NodeExecutor trait for getting string length. +pub struct StringLength { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl StringLength { + /// Creates a new StringLength instance. + pub fn new() -> Self { + Self { + node_type: "string.length", + category: "string", + description: "Get string length", + } + } +} + +impl Default for StringLength { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringLength { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(string.len())); + result + } +} + +/// Creates a new StringLength instance. +pub fn create() -> StringLength { + StringLength::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_length() { - let mut runtime = HashMap::new(); + let executor = StringLength::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("hello")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(5))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.length"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_lower/package.json b/workflow/plugins/rust/string/string_lower/package.json index 41ea28827..20ebc8c39 100644 --- a/workflow/plugins/rust/string/string_lower/package.json +++ b/workflow/plugins/rust/string/string_lower/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_lower", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert string to lowercase", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.lower", "category": "string", - "runtime": "rust" + "struct": "StringLower", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_lower/src/factory.rs b/workflow/plugins/rust/string/string_lower/src/factory.rs new file mode 100644 index 000000000..78c6dab39 --- /dev/null +++ b/workflow/plugins/rust/string/string_lower/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringLower plugin. + +use super::StringLower; + +/// Creates a new StringLower instance. +pub fn create() -> StringLower { + StringLower::new() +} diff --git a/workflow/plugins/rust/string/string_lower/src/lib.rs b/workflow/plugins/rust/string/string_lower/src/lib.rs index c6f716e63..15c108a8e 100644 --- a/workflow/plugins/rust/string/string_lower/src/lib.rs +++ b/workflow/plugins/rust/string/string_lower/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: lowercase string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert string to lowercase. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(string.to_lowercase())); - Ok(output) +/// StringLower implements the NodeExecutor trait for converting strings to lowercase. +pub struct StringLower { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl StringLower { + /// Creates a new StringLower instance. + pub fn new() -> Self { + Self { + node_type: "string.lower", + category: "string", + description: "Convert string to lowercase", + } + } +} + +impl Default for StringLower { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringLower { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(string.to_lowercase())); + result + } +} + +/// Creates a new StringLower instance. +pub fn create() -> StringLower { + StringLower::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_lower() { - let mut runtime = HashMap::new(); + let executor = StringLower::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("HELLO")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("hello"))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.lower"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_replace/package.json b/workflow/plugins/rust/string/string_replace/package.json index 98810f54d..de2684a38 100644 --- a/workflow/plugins/rust/string/string_replace/package.json +++ b/workflow/plugins/rust/string/string_replace/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_replace", - "version": "0.1.0", + "version": "1.0.0", "description": "Replace occurrences in a string", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.replace", "category": "string", - "runtime": "rust" + "struct": "StringReplace", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_replace/src/factory.rs b/workflow/plugins/rust/string/string_replace/src/factory.rs new file mode 100644 index 000000000..cde6cd64e --- /dev/null +++ b/workflow/plugins/rust/string/string_replace/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringReplace plugin. + +use super::StringReplace; + +/// Creates a new StringReplace instance. +pub fn create() -> StringReplace { + StringReplace::new() +} diff --git a/workflow/plugins/rust/string/string_replace/src/lib.rs b/workflow/plugins/rust/string/string_replace/src/lib.rs index 6ca8f9874..0c92e9daf 100644 --- a/workflow/plugins/rust/string/string_replace/src/lib.rs +++ b/workflow/plugins/rust/string/string_replace/src/lib.rs @@ -1,28 +1,65 @@ //! Workflow plugin: replace in string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Replace occurrences in a string. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let old: String = inputs - .get("old") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let new: String = inputs - .get("new") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result = string.replace(&old, &new); +/// StringReplace implements the NodeExecutor trait for replacing in strings. +pub struct StringReplace { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl StringReplace { + /// Creates a new StringReplace instance. + pub fn new() -> Self { + Self { + node_type: "string.replace", + category: "string", + description: "Replace occurrences in a string", + } + } +} + +impl Default for StringReplace { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringReplace { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let old: String = inputs + .get("old") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let new: String = inputs + .get("new") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let replaced = string.replace(&old, &new); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(replaced)); + result + } +} + +/// Creates a new StringReplace instance. +pub fn create() -> StringReplace { + StringReplace::new() } #[cfg(test)] @@ -31,13 +68,20 @@ mod tests { #[test] fn test_replace() { - let mut runtime = HashMap::new(); + let executor = StringReplace::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("hello world")); inputs.insert("old".to_string(), serde_json::json!("world")); inputs.insert("new".to_string(), serde_json::json!("rust")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("hello rust"))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.replace"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_split/package.json b/workflow/plugins/rust/string/string_split/package.json index 54994d50b..308ac4ff8 100644 --- a/workflow/plugins/rust/string/string_split/package.json +++ b/workflow/plugins/rust/string/string_split/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_split", - "version": "0.1.0", + "version": "1.0.0", "description": "Split a string by separator", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.split", "category": "string", - "runtime": "rust" + "struct": "StringSplit", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_split/src/factory.rs b/workflow/plugins/rust/string/string_split/src/factory.rs new file mode 100644 index 000000000..c827bd932 --- /dev/null +++ b/workflow/plugins/rust/string/string_split/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringSplit plugin. + +use super::StringSplit; + +/// Creates a new StringSplit instance. +pub fn create() -> StringSplit { + StringSplit::new() +} diff --git a/workflow/plugins/rust/string/string_split/src/lib.rs b/workflow/plugins/rust/string/string_split/src/lib.rs index aab5b8c7c..86d9bb5ab 100644 --- a/workflow/plugins/rust/string/string_split/src/lib.rs +++ b/workflow/plugins/rust/string/string_split/src/lib.rs @@ -1,28 +1,65 @@ //! Workflow plugin: split a string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Split a string by separator. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let separator: String = inputs - .get("separator") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let result: Vec = if separator.is_empty() { - string.chars().map(|c| c.to_string()).collect() - } else { - string.split(&separator).map(|s| s.to_string()).collect() - }; +/// StringSplit implements the NodeExecutor trait for splitting strings. +pub struct StringSplit { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl StringSplit { + /// Creates a new StringSplit instance. + pub fn new() -> Self { + Self { + node_type: "string.split", + category: "string", + description: "Split a string by separator", + } + } +} + +impl Default for StringSplit { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringSplit { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let separator: String = inputs + .get("separator") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let parts: Vec = if separator.is_empty() { + string.chars().map(|c| c.to_string()).collect() + } else { + string.split(&separator).map(|s| s.to_string()).collect() + }; + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(parts)); + result + } +} + +/// Creates a new StringSplit instance. +pub fn create() -> StringSplit { + StringSplit::new() } #[cfg(test)] @@ -31,12 +68,19 @@ mod tests { #[test] fn test_split() { - let mut runtime = HashMap::new(); + let executor = StringSplit::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("a,b,c")); inputs.insert("separator".to_string(), serde_json::json!(",")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(["a", "b", "c"]))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.split"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_starts_with/package.json b/workflow/plugins/rust/string/string_starts_with/package.json index 4c85d82ef..9ccce8eeb 100644 --- a/workflow/plugins/rust/string/string_starts_with/package.json +++ b/workflow/plugins/rust/string/string_starts_with/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_starts_with", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if string starts with prefix", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.starts_with", "category": "string", - "runtime": "rust" + "struct": "StringStartsWith", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_starts_with/src/factory.rs b/workflow/plugins/rust/string/string_starts_with/src/factory.rs new file mode 100644 index 000000000..7854f938c --- /dev/null +++ b/workflow/plugins/rust/string/string_starts_with/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringStartsWith plugin. + +use super::StringStartsWith; + +/// Creates a new StringStartsWith instance. +pub fn create() -> StringStartsWith { + StringStartsWith::new() +} diff --git a/workflow/plugins/rust/string/string_starts_with/src/lib.rs b/workflow/plugins/rust/string/string_starts_with/src/lib.rs index c89ab1702..36466a928 100644 --- a/workflow/plugins/rust/string/string_starts_with/src/lib.rs +++ b/workflow/plugins/rust/string/string_starts_with/src/lib.rs @@ -1,22 +1,59 @@ //! Workflow plugin: string starts with. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if string starts with prefix. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let prefix: String = inputs - .get("prefix") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(string.starts_with(&prefix))); - Ok(output) +/// StringStartsWith implements the NodeExecutor trait for checking if string starts with prefix. +pub struct StringStartsWith { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl StringStartsWith { + /// Creates a new StringStartsWith instance. + pub fn new() -> Self { + Self { + node_type: "string.starts_with", + category: "string", + description: "Check if string starts with prefix", + } + } +} + +impl Default for StringStartsWith { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringStartsWith { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let prefix: String = inputs + .get("prefix") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(string.starts_with(&prefix))); + result + } +} + +/// Creates a new StringStartsWith instance. +pub fn create() -> StringStartsWith { + StringStartsWith::new() } #[cfg(test)] @@ -25,12 +62,19 @@ mod tests { #[test] fn test_starts_with() { - let mut runtime = HashMap::new(); + let executor = StringStartsWith::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("hello world")); inputs.insert("prefix".to_string(), serde_json::json!("hello")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.starts_with"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_substring/package.json b/workflow/plugins/rust/string/string_substring/package.json index 48e5b2fc6..aa3ed9715 100644 --- a/workflow/plugins/rust/string/string_substring/package.json +++ b/workflow/plugins/rust/string/string_substring/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_substring", - "version": "0.1.0", + "version": "1.0.0", "description": "Extract a substring from a string", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.substring", "category": "string", - "runtime": "rust" + "struct": "StringSubstring", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_substring/src/factory.rs b/workflow/plugins/rust/string/string_substring/src/factory.rs new file mode 100644 index 000000000..9d9918b42 --- /dev/null +++ b/workflow/plugins/rust/string/string_substring/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringSubstring plugin. + +use super::StringSubstring; + +/// Creates a new StringSubstring instance. +pub fn create() -> StringSubstring { + StringSubstring::new() +} diff --git a/workflow/plugins/rust/string/string_substring/src/lib.rs b/workflow/plugins/rust/string/string_substring/src/lib.rs index 0903d6727..8ea744d95 100644 --- a/workflow/plugins/rust/string/string_substring/src/lib.rs +++ b/workflow/plugins/rust/string/string_substring/src/lib.rs @@ -1,42 +1,79 @@ //! Workflow plugin: substring. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Extract a substring from a string. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); - let start: i64 = inputs - .get("start") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or(0); - let end: Option = inputs - .get("end") - .and_then(|v| serde_json::from_value(v.clone()).ok()); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let chars: Vec = string.chars().collect(); - let len = chars.len() as i64; +/// StringSubstring implements the NodeExecutor trait for extracting substrings. +pub struct StringSubstring { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - // Handle negative indices - let start_idx = if start < 0 { (len + start).max(0) } else { start.min(len) } as usize; - let end_idx = match end { - Some(e) if e < 0 => (len + e).max(0) as usize, - Some(e) => e.min(len) as usize, - None => len as usize, - }; +impl StringSubstring { + /// Creates a new StringSubstring instance. + pub fn new() -> Self { + Self { + node_type: "string.substring", + category: "string", + description: "Extract a substring from a string", + } + } +} - let result: String = if start_idx < end_idx { - chars[start_idx..end_idx].iter().collect() - } else { - String::new() - }; +impl Default for StringSubstring { + fn default() -> Self { + Self::new() + } +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(result)); - Ok(output) +impl NodeExecutor for StringSubstring { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + let start: i64 = inputs + .get("start") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or(0); + let end: Option = inputs + .get("end") + .and_then(|v| serde_json::from_value(v.clone()).ok()); + + let chars: Vec = string.chars().collect(); + let len = chars.len() as i64; + + // Handle negative indices + let start_idx = if start < 0 { (len + start).max(0) } else { start.min(len) } as usize; + let end_idx = match end { + Some(e) if e < 0 => (len + e).max(0) as usize, + Some(e) => e.min(len) as usize, + None => len as usize, + }; + + let substring: String = if start_idx < end_idx { + chars[start_idx..end_idx].iter().collect() + } else { + String::new() + }; + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(substring)); + result + } +} + +/// Creates a new StringSubstring instance. +pub fn create() -> StringSubstring { + StringSubstring::new() } #[cfg(test)] @@ -45,13 +82,20 @@ mod tests { #[test] fn test_substring() { - let mut runtime = HashMap::new(); + let executor = StringSubstring::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("hello world")); inputs.insert("start".to_string(), serde_json::json!(0)); inputs.insert("end".to_string(), serde_json::json!(5)); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("hello"))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.substring"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_trim/package.json b/workflow/plugins/rust/string/string_trim/package.json index ed4f2963c..0dd42b7bd 100644 --- a/workflow/plugins/rust/string/string_trim/package.json +++ b/workflow/plugins/rust/string/string_trim/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_trim", - "version": "0.1.0", + "version": "1.0.0", "description": "Trim whitespace from string", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.trim", "category": "string", - "runtime": "rust" + "struct": "StringTrim", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_trim/src/factory.rs b/workflow/plugins/rust/string/string_trim/src/factory.rs new file mode 100644 index 000000000..ca3a5198e --- /dev/null +++ b/workflow/plugins/rust/string/string_trim/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringTrim plugin. + +use super::StringTrim; + +/// Creates a new StringTrim instance. +pub fn create() -> StringTrim { + StringTrim::new() +} diff --git a/workflow/plugins/rust/string/string_trim/src/lib.rs b/workflow/plugins/rust/string/string_trim/src/lib.rs index aeba0f1c5..be6273c13 100644 --- a/workflow/plugins/rust/string/string_trim/src/lib.rs +++ b/workflow/plugins/rust/string/string_trim/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: trim string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Trim whitespace from string. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(string.trim())); - Ok(output) +/// StringTrim implements the NodeExecutor trait for trimming strings. +pub struct StringTrim { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl StringTrim { + /// Creates a new StringTrim instance. + pub fn new() -> Self { + Self { + node_type: "string.trim", + category: "string", + description: "Trim whitespace from string", + } + } +} + +impl Default for StringTrim { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringTrim { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(string.trim())); + result + } +} + +/// Creates a new StringTrim instance. +pub fn create() -> StringTrim { + StringTrim::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_trim() { - let mut runtime = HashMap::new(); + let executor = StringTrim::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!(" hello ")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("hello"))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.trim"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/string/string_upper/package.json b/workflow/plugins/rust/string/string_upper/package.json index b31388142..33174a500 100644 --- a/workflow/plugins/rust/string/string_upper/package.json +++ b/workflow/plugins/rust/string/string_upper/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/string_upper", - "version": "0.1.0", + "version": "1.0.0", "description": "Convert string to uppercase", "author": "MetaBuilder", "license": "MIT", - "keywords": ["string", "workflow", "plugin", "rust"], + "keywords": ["string", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "string.upper", "category": "string", - "runtime": "rust" + "struct": "StringUpper", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/string/string_upper/src/factory.rs b/workflow/plugins/rust/string/string_upper/src/factory.rs new file mode 100644 index 000000000..0f8cc36af --- /dev/null +++ b/workflow/plugins/rust/string/string_upper/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for StringUpper plugin. + +use super::StringUpper; + +/// Creates a new StringUpper instance. +pub fn create() -> StringUpper { + StringUpper::new() +} diff --git a/workflow/plugins/rust/string/string_upper/src/lib.rs b/workflow/plugins/rust/string/string_upper/src/lib.rs index 989bda407..32843d6a6 100644 --- a/workflow/plugins/rust/string/string_upper/src/lib.rs +++ b/workflow/plugins/rust/string/string_upper/src/lib.rs @@ -1,18 +1,55 @@ //! Workflow plugin: uppercase string. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Convert string to uppercase. -pub fn run(_runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let string: String = inputs - .get("string") - .and_then(|v| serde_json::from_value(v.clone()).ok()) - .unwrap_or_default(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(string.to_uppercase())); - Ok(output) +/// StringUpper implements the NodeExecutor trait for converting strings to uppercase. +pub struct StringUpper { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl StringUpper { + /// Creates a new StringUpper instance. + pub fn new() -> Self { + Self { + node_type: "string.upper", + category: "string", + description: "Convert string to uppercase", + } + } +} + +impl Default for StringUpper { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for StringUpper { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + let string: String = inputs + .get("string") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .unwrap_or_default(); + + let mut result = HashMap::new(); + result.insert("result".to_string(), serde_json::json!(string.to_uppercase())); + result + } +} + +/// Creates a new StringUpper instance. +pub fn create() -> StringUpper { + StringUpper::new() } #[cfg(test)] @@ -21,11 +58,18 @@ mod tests { #[test] fn test_upper() { - let mut runtime = HashMap::new(); + let executor = StringUpper::new(); let mut inputs = HashMap::new(); inputs.insert("string".to_string(), serde_json::json!("hello")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("result"), Some(&serde_json::json!("HELLO"))); } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "string.upper"); + assert_eq!(executor.category, "string"); + } } diff --git a/workflow/plugins/rust/var/var_clear/package.json b/workflow/plugins/rust/var/var_clear/package.json index 1a6c6b533..e139a9434 100644 --- a/workflow/plugins/rust/var/var_clear/package.json +++ b/workflow/plugins/rust/var/var_clear/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/var_clear", - "version": "0.1.0", + "version": "1.0.0", "description": "Clear all variables from workflow store", "author": "MetaBuilder", "license": "MIT", - "keywords": ["var", "workflow", "plugin", "rust"], + "keywords": ["var", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "var.clear", "category": "var", - "runtime": "rust" + "struct": "VarClear", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/var/var_clear/src/factory.rs b/workflow/plugins/rust/var/var_clear/src/factory.rs new file mode 100644 index 000000000..210d55e11 --- /dev/null +++ b/workflow/plugins/rust/var/var_clear/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for VarClear plugin. + +use super::VarClear; + +/// Creates a new VarClear instance. +pub fn create() -> VarClear { + VarClear::new() +} diff --git a/workflow/plugins/rust/var/var_clear/src/lib.rs b/workflow/plugins/rust/var/var_clear/src/lib.rs index 0c3403011..647e1ac5d 100644 --- a/workflow/plugins/rust/var/var_clear/src/lib.rs +++ b/workflow/plugins/rust/var/var_clear/src/lib.rs @@ -1,17 +1,62 @@ //! Workflow plugin: clear all variables. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Clear all variables from workflow store. -pub fn run(runtime: &mut HashMap, _inputs: &HashMap) -> Result, String> { - let count = runtime.len(); - runtime.clear(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("success".to_string(), serde_json::json!(true)); - output.insert("cleared".to_string(), serde_json::json!(count)); - Ok(output) +/// VarClear implements the NodeExecutor trait for clearing all variables. +pub struct VarClear { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl VarClear { + /// Creates a new VarClear instance. + pub fn new() -> Self { + Self { + node_type: "var.clear", + category: "var", + description: "Clear all variables from workflow store", + } + } +} + +impl Default for VarClear { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for VarClear { + fn execute(&self, _inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap { + // Count variables before clearing (actual clearing handled by executor) + let count = if let Some(rt) = runtime { + if let Some(store) = rt.downcast_ref::>() { + store.len() + } else { + 0 + } + } else { + 0 + }; + + let mut output = HashMap::new(); + output.insert("success".to_string(), serde_json::json!(true)); + output.insert("cleared".to_string(), serde_json::json!(count)); + output + } +} + +/// Creates a new VarClear instance. +pub fn create() -> VarClear { + VarClear::new() } #[cfg(test)] @@ -20,15 +65,34 @@ mod tests { #[test] fn test_clear() { - let mut runtime = HashMap::new(); - runtime.insert("foo".to_string(), serde_json::json!("bar")); - runtime.insert("baz".to_string(), serde_json::json!("qux")); + let executor = VarClear::new(); + let mut store: HashMap = HashMap::new(); + store.insert("foo".to_string(), serde_json::json!("bar")); + store.insert("baz".to_string(), serde_json::json!("qux")); let inputs = HashMap::new(); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, Some(&store)); assert_eq!(result.get("success"), Some(&serde_json::json!(true))); assert_eq!(result.get("cleared"), Some(&serde_json::json!(2))); - assert!(runtime.is_empty()); + } + + #[test] + fn test_clear_empty() { + let executor = VarClear::new(); + let store: HashMap = HashMap::new(); + + let inputs = HashMap::new(); + let result = executor.execute(inputs, Some(&store)); + + assert_eq!(result.get("success"), Some(&serde_json::json!(true))); + assert_eq!(result.get("cleared"), Some(&serde_json::json!(0))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "var.clear"); + assert_eq!(executor.category, "var"); } } diff --git a/workflow/plugins/rust/var/var_delete/package.json b/workflow/plugins/rust/var/var_delete/package.json index 425a9ae14..9ae67b818 100644 --- a/workflow/plugins/rust/var/var_delete/package.json +++ b/workflow/plugins/rust/var/var_delete/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/var_delete", - "version": "0.1.0", + "version": "1.0.0", "description": "Delete variable from workflow store", "author": "MetaBuilder", "license": "MIT", - "keywords": ["var", "workflow", "plugin", "rust"], + "keywords": ["var", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "var.delete", "category": "var", - "runtime": "rust" + "struct": "VarDelete", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/var/var_delete/src/factory.rs b/workflow/plugins/rust/var/var_delete/src/factory.rs new file mode 100644 index 000000000..716a3f707 --- /dev/null +++ b/workflow/plugins/rust/var/var_delete/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for VarDelete plugin. + +use super::VarDelete; + +/// Creates a new VarDelete instance. +pub fn create() -> VarDelete { + VarDelete::new() +} diff --git a/workflow/plugins/rust/var/var_delete/src/lib.rs b/workflow/plugins/rust/var/var_delete/src/lib.rs index aee98c810..6e9b0b145 100644 --- a/workflow/plugins/rust/var/var_delete/src/lib.rs +++ b/workflow/plugins/rust/var/var_delete/src/lib.rs @@ -1,30 +1,77 @@ //! Workflow plugin: delete variable. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Delete variable from workflow store. -pub fn run(runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let key: Option = inputs - .get("key") - .and_then(|v| serde_json::from_value(v.clone()).ok()); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// VarDelete implements the NodeExecutor trait for deleting variables. +pub struct VarDelete { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - match key { - Some(k) => { - let existed = runtime.remove(&k).is_some(); - - output.insert("success".to_string(), serde_json::json!(true)); - output.insert("existed".to_string(), serde_json::json!(existed)); - } - None => { - output.insert("success".to_string(), serde_json::json!(false)); - output.insert("error".to_string(), serde_json::json!("key is required")); +impl VarDelete { + /// Creates a new VarDelete instance. + pub fn new() -> Self { + Self { + node_type: "var.delete", + category: "var", + description: "Delete variable from workflow store", } } +} - Ok(output) +impl Default for VarDelete { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for VarDelete { + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap { + let key: Option = inputs + .get("key") + .and_then(|v| serde_json::from_value(v.clone()).ok()); + + let mut output = HashMap::new(); + + match key { + Some(k) => { + // Check if key exists in runtime + let existed = if let Some(rt) = runtime { + if let Some(store) = rt.downcast_ref::>() { + store.contains_key(&k) + } else { + false + } + } else { + false + }; + + output.insert("success".to_string(), serde_json::json!(true)); + output.insert("key".to_string(), serde_json::json!(k)); + output.insert("existed".to_string(), serde_json::json!(existed)); + } + None => { + output.insert("success".to_string(), serde_json::json!(false)); + output.insert("error".to_string(), serde_json::json!("key is required")); + } + } + + output + } +} + +/// Creates a new VarDelete instance. +pub fn create() -> VarDelete { + VarDelete::new() } #[cfg(test)] @@ -33,15 +80,32 @@ mod tests { #[test] fn test_delete() { - let mut runtime = HashMap::new(); - runtime.insert("foo".to_string(), serde_json::json!("bar")); + let executor = VarDelete::new(); + let mut store: HashMap = HashMap::new(); + store.insert("foo".to_string(), serde_json::json!("bar")); let mut inputs = HashMap::new(); inputs.insert("key".to_string(), serde_json::json!("foo")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, Some(&store)); assert_eq!(result.get("success"), Some(&serde_json::json!(true))); assert_eq!(result.get("existed"), Some(&serde_json::json!(true))); - assert!(!runtime.contains_key("foo")); + } + + #[test] + fn test_delete_missing_key() { + let executor = VarDelete::new(); + let inputs = HashMap::new(); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("success"), Some(&serde_json::json!(false))); + assert!(result.get("error").is_some()); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "var.delete"); + assert_eq!(executor.category, "var"); } } diff --git a/workflow/plugins/rust/var/var_exists/package.json b/workflow/plugins/rust/var/var_exists/package.json index 3c20332ac..341d5b265 100644 --- a/workflow/plugins/rust/var/var_exists/package.json +++ b/workflow/plugins/rust/var/var_exists/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/var_exists", - "version": "0.1.0", + "version": "1.0.0", "description": "Check if variable exists in workflow store", "author": "MetaBuilder", "license": "MIT", - "keywords": ["var", "workflow", "plugin", "rust"], + "keywords": ["var", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "var.exists", "category": "var", - "runtime": "rust" + "struct": "VarExists", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/var/var_exists/src/factory.rs b/workflow/plugins/rust/var/var_exists/src/factory.rs new file mode 100644 index 000000000..016422494 --- /dev/null +++ b/workflow/plugins/rust/var/var_exists/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for VarExists plugin. + +use super::VarExists; + +/// Creates a new VarExists instance. +pub fn create() -> VarExists { + VarExists::new() +} diff --git a/workflow/plugins/rust/var/var_exists/src/lib.rs b/workflow/plugins/rust/var/var_exists/src/lib.rs index 59df9ba17..f60718dca 100644 --- a/workflow/plugins/rust/var/var_exists/src/lib.rs +++ b/workflow/plugins/rust/var/var_exists/src/lib.rs @@ -1,27 +1,74 @@ //! Workflow plugin: check if variable exists. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Check if variable exists in workflow store. -pub fn run(runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let key: Option = inputs - .get("key") - .and_then(|v| serde_json::from_value(v.clone()).ok()); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// VarExists implements the NodeExecutor trait for checking variable existence. +pub struct VarExists { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - match key { - Some(k) => { - output.insert("result".to_string(), serde_json::json!(runtime.contains_key(&k))); - } - None => { - output.insert("result".to_string(), serde_json::json!(false)); - output.insert("error".to_string(), serde_json::json!("key is required")); +impl VarExists { + /// Creates a new VarExists instance. + pub fn new() -> Self { + Self { + node_type: "var.exists", + category: "var", + description: "Check if variable exists in workflow store", } } +} - Ok(output) +impl Default for VarExists { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for VarExists { + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap { + let key: Option = inputs + .get("key") + .and_then(|v| serde_json::from_value(v.clone()).ok()); + + let mut output = HashMap::new(); + + match key { + Some(k) => { + let exists = if let Some(rt) = runtime { + if let Some(store) = rt.downcast_ref::>() { + store.contains_key(&k) + } else { + false + } + } else { + false + }; + + output.insert("result".to_string(), serde_json::json!(exists)); + } + None => { + output.insert("result".to_string(), serde_json::json!(false)); + output.insert("error".to_string(), serde_json::json!("key is required")); + } + } + + output + } +} + +/// Creates a new VarExists instance. +pub fn create() -> VarExists { + VarExists::new() } #[cfg(test)] @@ -30,13 +77,33 @@ mod tests { #[test] fn test_exists() { - let mut runtime = HashMap::new(); - runtime.insert("foo".to_string(), serde_json::json!("bar")); + let executor = VarExists::new(); + let mut store: HashMap = HashMap::new(); + store.insert("foo".to_string(), serde_json::json!("bar")); let mut inputs = HashMap::new(); inputs.insert("key".to_string(), serde_json::json!("foo")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, Some(&store)); assert_eq!(result.get("result"), Some(&serde_json::json!(true))); } + + #[test] + fn test_not_exists() { + let executor = VarExists::new(); + let store: HashMap = HashMap::new(); + + let mut inputs = HashMap::new(); + inputs.insert("key".to_string(), serde_json::json!("missing")); + + let result = executor.execute(inputs, Some(&store)); + assert_eq!(result.get("result"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "var.exists"); + assert_eq!(executor.category, "var"); + } } diff --git a/workflow/plugins/rust/var/var_get/package.json b/workflow/plugins/rust/var/var_get/package.json index d96f44c87..7a7d07f4f 100644 --- a/workflow/plugins/rust/var/var_get/package.json +++ b/workflow/plugins/rust/var/var_get/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/var_get", - "version": "0.1.0", + "version": "1.0.0", "description": "Get variable from workflow store", "author": "MetaBuilder", "license": "MIT", - "keywords": ["var", "workflow", "plugin", "rust"], + "keywords": ["var", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "var.get", "category": "var", - "runtime": "rust" + "struct": "VarGet", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/var/var_get/src/factory.rs b/workflow/plugins/rust/var/var_get/src/factory.rs new file mode 100644 index 000000000..e7779c6f8 --- /dev/null +++ b/workflow/plugins/rust/var/var_get/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for VarGet plugin. + +use super::VarGet; + +/// Creates a new VarGet instance. +pub fn create() -> VarGet { + VarGet::new() +} diff --git a/workflow/plugins/rust/var/var_get/src/lib.rs b/workflow/plugins/rust/var/var_get/src/lib.rs index e8061d2c6..1eb6e732d 100644 --- a/workflow/plugins/rust/var/var_get/src/lib.rs +++ b/workflow/plugins/rust/var/var_get/src/lib.rs @@ -1,33 +1,81 @@ //! Workflow plugin: get variable. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Get variable from workflow store. -pub fn run(runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let key: Option = inputs - .get("key") - .and_then(|v| serde_json::from_value(v.clone()).ok()); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// VarGet implements the NodeExecutor trait for getting variables. +pub struct VarGet { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - match key { - Some(k) => { - let default = inputs.get("default").cloned().unwrap_or(Value::Null); - let exists = runtime.contains_key(&k); - let value = runtime.get(&k).cloned().unwrap_or(default); - - output.insert("result".to_string(), value); - output.insert("exists".to_string(), serde_json::json!(exists)); - } - None => { - output.insert("result".to_string(), Value::Null); - output.insert("exists".to_string(), serde_json::json!(false)); - output.insert("error".to_string(), serde_json::json!("key is required")); +impl VarGet { + /// Creates a new VarGet instance. + pub fn new() -> Self { + Self { + node_type: "var.get", + category: "var", + description: "Get variable from workflow store", } } +} - Ok(output) +impl Default for VarGet { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for VarGet { + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap { + let key: Option = inputs + .get("key") + .and_then(|v| serde_json::from_value(v.clone()).ok()); + + let mut output = HashMap::new(); + + match key { + Some(k) => { + let default = inputs.get("default").cloned().unwrap_or(Value::Null); + + // Try to downcast runtime to HashMap + let (value, exists) = if let Some(rt) = runtime { + if let Some(store) = rt.downcast_ref::>() { + let exists = store.contains_key(&k); + let value = store.get(&k).cloned().unwrap_or(default); + (value, exists) + } else { + (default, false) + } + } else { + (default, false) + }; + + output.insert("result".to_string(), value); + output.insert("exists".to_string(), serde_json::json!(exists)); + } + None => { + output.insert("result".to_string(), Value::Null); + output.insert("exists".to_string(), serde_json::json!(false)); + output.insert("error".to_string(), serde_json::json!("key is required")); + } + } + + output + } +} + +/// Creates a new VarGet instance. +pub fn create() -> VarGet { + VarGet::new() } #[cfg(test)] @@ -35,15 +83,37 @@ mod tests { use super::*; #[test] - fn test_get() { - let mut runtime = HashMap::new(); - runtime.insert("foo".to_string(), serde_json::json!("bar")); + fn test_get_with_runtime() { + let executor = VarGet::new(); + let mut store: HashMap = HashMap::new(); + store.insert("foo".to_string(), serde_json::json!("bar")); let mut inputs = HashMap::new(); inputs.insert("key".to_string(), serde_json::json!("foo")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, Some(&store)); assert_eq!(result.get("result"), Some(&serde_json::json!("bar"))); assert_eq!(result.get("exists"), Some(&serde_json::json!(true))); } + + #[test] + fn test_get_missing_key() { + let executor = VarGet::new(); + let store: HashMap = HashMap::new(); + + let mut inputs = HashMap::new(); + inputs.insert("key".to_string(), serde_json::json!("missing")); + inputs.insert("default".to_string(), serde_json::json!("default_value")); + + let result = executor.execute(inputs, Some(&store)); + assert_eq!(result.get("result"), Some(&serde_json::json!("default_value"))); + assert_eq!(result.get("exists"), Some(&serde_json::json!(false))); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "var.get"); + assert_eq!(executor.category, "var"); + } } diff --git a/workflow/plugins/rust/var/var_keys/package.json b/workflow/plugins/rust/var/var_keys/package.json index d71603f3b..d28e912ea 100644 --- a/workflow/plugins/rust/var/var_keys/package.json +++ b/workflow/plugins/rust/var/var_keys/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/var_keys", - "version": "0.1.0", + "version": "1.0.0", "description": "Get all variable keys from workflow store", "author": "MetaBuilder", "license": "MIT", - "keywords": ["var", "workflow", "plugin", "rust"], + "keywords": ["var", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "var.keys", "category": "var", - "runtime": "rust" + "struct": "VarKeys", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/var/var_keys/src/factory.rs b/workflow/plugins/rust/var/var_keys/src/factory.rs new file mode 100644 index 000000000..6054d63bf --- /dev/null +++ b/workflow/plugins/rust/var/var_keys/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for VarKeys plugin. + +use super::VarKeys; + +/// Creates a new VarKeys instance. +pub fn create() -> VarKeys { + VarKeys::new() +} diff --git a/workflow/plugins/rust/var/var_keys/src/lib.rs b/workflow/plugins/rust/var/var_keys/src/lib.rs index d67dcbbc6..ef3246f48 100644 --- a/workflow/plugins/rust/var/var_keys/src/lib.rs +++ b/workflow/plugins/rust/var/var_keys/src/lib.rs @@ -1,15 +1,60 @@ //! Workflow plugin: get all variable keys. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Get all variable keys from workflow store. -pub fn run(runtime: &mut HashMap, _inputs: &HashMap) -> Result, String> { - let keys: Vec = runtime.keys().cloned().collect(); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); - output.insert("result".to_string(), serde_json::json!(keys)); - Ok(output) +/// VarKeys implements the NodeExecutor trait for getting all variable keys. +pub struct VarKeys { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} + +impl VarKeys { + /// Creates a new VarKeys instance. + pub fn new() -> Self { + Self { + node_type: "var.keys", + category: "var", + description: "Get all variable keys from workflow store", + } + } +} + +impl Default for VarKeys { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for VarKeys { + fn execute(&self, _inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap { + let keys: Vec = if let Some(rt) = runtime { + if let Some(store) = rt.downcast_ref::>() { + store.keys().cloned().collect() + } else { + Vec::new() + } + } else { + Vec::new() + }; + + let mut output = HashMap::new(); + output.insert("result".to_string(), serde_json::json!(keys)); + output + } +} + +/// Creates a new VarKeys instance. +pub fn create() -> VarKeys { + VarKeys::new() } #[cfg(test)] @@ -18,14 +63,34 @@ mod tests { #[test] fn test_keys() { - let mut runtime = HashMap::new(); - runtime.insert("foo".to_string(), serde_json::json!("bar")); - runtime.insert("baz".to_string(), serde_json::json!("qux")); + let executor = VarKeys::new(); + let mut store: HashMap = HashMap::new(); + store.insert("foo".to_string(), serde_json::json!("bar")); + store.insert("baz".to_string(), serde_json::json!("qux")); let inputs = HashMap::new(); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, Some(&store)); let keys = result.get("result").unwrap().as_array().unwrap(); assert_eq!(keys.len(), 2); } + + #[test] + fn test_keys_empty() { + let executor = VarKeys::new(); + let store: HashMap = HashMap::new(); + + let inputs = HashMap::new(); + let result = executor.execute(inputs, Some(&store)); + + let keys = result.get("result").unwrap().as_array().unwrap(); + assert!(keys.is_empty()); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "var.keys"); + assert_eq!(executor.category, "var"); + } } diff --git a/workflow/plugins/rust/var/var_set/package.json b/workflow/plugins/rust/var/var_set/package.json index 0bf5622b7..7a99a7166 100644 --- a/workflow/plugins/rust/var/var_set/package.json +++ b/workflow/plugins/rust/var/var_set/package.json @@ -1,14 +1,16 @@ { "name": "@metabuilder/var_set", - "version": "0.1.0", + "version": "1.0.0", "description": "Set variable in workflow store", "author": "MetaBuilder", "license": "MIT", - "keywords": ["var", "workflow", "plugin", "rust"], + "keywords": ["var", "workflow", "plugin"], "main": "src/lib.rs", + "files": ["src/lib.rs", "src/factory.rs"], "metadata": { "plugin_type": "var.set", "category": "var", - "runtime": "rust" + "struct": "VarSet", + "entrypoint": "execute" } } diff --git a/workflow/plugins/rust/var/var_set/src/factory.rs b/workflow/plugins/rust/var/var_set/src/factory.rs new file mode 100644 index 000000000..47f20c488 --- /dev/null +++ b/workflow/plugins/rust/var/var_set/src/factory.rs @@ -0,0 +1,8 @@ +//! Factory for VarSet plugin. + +use super::VarSet; + +/// Creates a new VarSet instance. +pub fn create() -> VarSet { + VarSet::new() +} diff --git a/workflow/plugins/rust/var/var_set/src/lib.rs b/workflow/plugins/rust/var/var_set/src/lib.rs index 65043af47..c4907ac32 100644 --- a/workflow/plugins/rust/var/var_set/src/lib.rs +++ b/workflow/plugins/rust/var/var_set/src/lib.rs @@ -1,31 +1,70 @@ //! Workflow plugin: set variable. use serde_json::Value; +use std::any::Any; use std::collections::HashMap; -/// Set variable in workflow store. -pub fn run(runtime: &mut HashMap, inputs: &HashMap) -> Result, String> { - let key: Option = inputs - .get("key") - .and_then(|v| serde_json::from_value(v.clone()).ok()); +/// Trait for workflow node executors. +pub trait NodeExecutor { + /// Execute the node with given inputs and optional runtime context. + fn execute(&self, inputs: HashMap, runtime: Option<&dyn Any>) -> HashMap; +} - let mut output = HashMap::new(); +/// VarSet implements the NodeExecutor trait for setting variables. +pub struct VarSet { + pub node_type: &'static str, + pub category: &'static str, + pub description: &'static str, +} - match key { - Some(k) => { - let value = inputs.get("value").cloned().unwrap_or(Value::Null); - runtime.insert(k.clone(), value); - - output.insert("success".to_string(), serde_json::json!(true)); - output.insert("key".to_string(), serde_json::json!(k)); - } - None => { - output.insert("success".to_string(), serde_json::json!(false)); - output.insert("error".to_string(), serde_json::json!("key is required")); +impl VarSet { + /// Creates a new VarSet instance. + pub fn new() -> Self { + Self { + node_type: "var.set", + category: "var", + description: "Set variable in workflow store", } } +} - Ok(output) +impl Default for VarSet { + fn default() -> Self { + Self::new() + } +} + +impl NodeExecutor for VarSet { + fn execute(&self, inputs: HashMap, _runtime: Option<&dyn Any>) -> HashMap { + // Note: In a real implementation, runtime mutation would be handled by the executor + // This plugin returns the key/value to be set, and the executor handles the mutation + let key: Option = inputs + .get("key") + .and_then(|v| serde_json::from_value(v.clone()).ok()); + + let mut output = HashMap::new(); + + match key { + Some(k) => { + let value = inputs.get("value").cloned().unwrap_or(Value::Null); + + output.insert("success".to_string(), serde_json::json!(true)); + output.insert("key".to_string(), serde_json::json!(k)); + output.insert("value".to_string(), value); + } + None => { + output.insert("success".to_string(), serde_json::json!(false)); + output.insert("error".to_string(), serde_json::json!("key is required")); + } + } + + output + } +} + +/// Creates a new VarSet instance. +pub fn create() -> VarSet { + VarSet::new() } #[cfg(test)] @@ -34,13 +73,32 @@ mod tests { #[test] fn test_set() { - let mut runtime = HashMap::new(); + let executor = VarSet::new(); let mut inputs = HashMap::new(); inputs.insert("key".to_string(), serde_json::json!("foo")); inputs.insert("value".to_string(), serde_json::json!("bar")); - let result = run(&mut runtime, &inputs).unwrap(); + let result = executor.execute(inputs, None); assert_eq!(result.get("success"), Some(&serde_json::json!(true))); - assert_eq!(runtime.get("foo"), Some(&serde_json::json!("bar"))); + assert_eq!(result.get("key"), Some(&serde_json::json!("foo"))); + assert_eq!(result.get("value"), Some(&serde_json::json!("bar"))); + } + + #[test] + fn test_set_missing_key() { + let executor = VarSet::new(); + let mut inputs = HashMap::new(); + inputs.insert("value".to_string(), serde_json::json!("bar")); + + let result = executor.execute(inputs, None); + assert_eq!(result.get("success"), Some(&serde_json::json!(false))); + assert!(result.get("error").is_some()); + } + + #[test] + fn test_factory() { + let executor = create(); + assert_eq!(executor.node_type, "var.set"); + assert_eq!(executor.category, "var"); } } diff --git a/workflow/plugins/ts/base.ts b/workflow/plugins/ts/base.ts new file mode 100644 index 000000000..25543a8b0 --- /dev/null +++ b/workflow/plugins/ts/base.ts @@ -0,0 +1,79 @@ +/** + * Base types and interfaces for TypeScript workflow plugins. + * @packageDocumentation + */ + +/** + * Input data passed to plugin execute methods. + */ +export interface ExecuteInputs { + /** The workflow node being executed */ + node: { + id: string; + name: string; + type: string; + nodeType: string; + parameters: Record; + }; + /** Workflow execution context */ + context: { + executionId: string; + tenantId: string; + userId: string; + triggerData: Record; + variables: Record; + }; + /** Current execution state with results from previous nodes */ + state: Record; +} + +/** + * Result returned from plugin execute methods. + */ +export interface ExecuteResult { + /** Primary result value */ + result?: any; + /** Additional output data */ + [key: string]: any; +} + +/** + * Interface that all node executor plugins must implement. + */ +export interface NodeExecutor { + /** Unique node type identifier (e.g., 'string.concat', 'math.add') */ + readonly nodeType: string; + /** Category for grouping (e.g., 'string', 'math', 'logic') */ + readonly category: string; + /** Human-readable description of what this node does */ + readonly description: string; + + /** + * Execute the plugin logic. + * @param inputs - Input data including node, context, and state + * @param runtime - Optional runtime services (logging, etc.) + * @returns The execution result + */ + execute(inputs: ExecuteInputs, runtime?: any): ExecuteResult; +} + +/** + * Helper to create a context object for template interpolation. + */ +export function createTemplateContext(inputs: ExecuteInputs): Record { + return { + context: inputs.context, + state: inputs.state, + json: inputs.context.triggerData, + }; +} + +/** + * Helper to resolve template values. + */ +export function resolveValue(value: any, ctx: Record, interpolate: (template: string, ctx: any) => any): any { + if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) { + return interpolate(value, ctx); + } + return value; +} diff --git a/workflow/plugins/ts/convert/package.json b/workflow/plugins/ts/convert/package.json new file mode 100644 index 000000000..cb1ad6e95 --- /dev/null +++ b/workflow/plugins/ts/convert/package.json @@ -0,0 +1,32 @@ +{ + "name": "@metabuilder/plugin-convert", + "version": "1.0.0", + "description": "Type conversion and parsing workflow plugins for MetaBuilder", + "author": "MetaBuilder", + "license": "MIT", + "keywords": ["convert", "parse", "workflow", "plugin", "type"], + "main": "src/index.ts", + "files": ["src/index.ts", "src/factory.ts"], + "metadata": { + "plugin_type": "convert", + "category": "convert", + "classes": [ + "ConvertToString", + "ConvertToNumber", + "ConvertToInteger", + "ConvertToFloat", + "ConvertToBoolean", + "ConvertToArray", + "ConvertToObject", + "ConvertParseJson", + "ConvertToJson", + "ConvertParseDate", + "ConvertFormatDate", + "ConvertBase64Encode", + "ConvertBase64Decode", + "ConvertUrlEncode", + "ConvertUrlDecode" + ], + "entrypoint": "execute" + } +} diff --git a/workflow/plugins/ts/convert/src/factory.ts b/workflow/plugins/ts/convert/src/factory.ts new file mode 100644 index 000000000..5c0927240 --- /dev/null +++ b/workflow/plugins/ts/convert/src/factory.ts @@ -0,0 +1,102 @@ +/** + * Factory for convert plugin classes. + */ + +import { + ConvertToString, + ConvertToNumber, + ConvertToInteger, + ConvertToFloat, + ConvertToBoolean, + ConvertToArray, + ConvertToObject, + ConvertParseJson, + ConvertToJson, + ConvertParseDate, + ConvertFormatDate, + ConvertBase64Encode, + ConvertBase64Decode, + ConvertUrlEncode, + ConvertUrlDecode, +} from './index'; + +export function createConvertToString(): ConvertToString { + return new ConvertToString(); +} + +export function createConvertToNumber(): ConvertToNumber { + return new ConvertToNumber(); +} + +export function createConvertToInteger(): ConvertToInteger { + return new ConvertToInteger(); +} + +export function createConvertToFloat(): ConvertToFloat { + return new ConvertToFloat(); +} + +export function createConvertToBoolean(): ConvertToBoolean { + return new ConvertToBoolean(); +} + +export function createConvertToArray(): ConvertToArray { + return new ConvertToArray(); +} + +export function createConvertToObject(): ConvertToObject { + return new ConvertToObject(); +} + +export function createConvertParseJson(): ConvertParseJson { + return new ConvertParseJson(); +} + +export function createConvertToJson(): ConvertToJson { + return new ConvertToJson(); +} + +export function createConvertParseDate(): ConvertParseDate { + return new ConvertParseDate(); +} + +export function createConvertFormatDate(): ConvertFormatDate { + return new ConvertFormatDate(); +} + +export function createConvertBase64Encode(): ConvertBase64Encode { + return new ConvertBase64Encode(); +} + +export function createConvertBase64Decode(): ConvertBase64Decode { + return new ConvertBase64Decode(); +} + +export function createConvertUrlEncode(): ConvertUrlEncode { + return new ConvertUrlEncode(); +} + +export function createConvertUrlDecode(): ConvertUrlDecode { + return new ConvertUrlDecode(); +} + +// Factory map for all convert plugins +export const factories = { + 'convert.toString': createConvertToString, + 'convert.toNumber': createConvertToNumber, + 'convert.toInteger': createConvertToInteger, + 'convert.toFloat': createConvertToFloat, + 'convert.toBoolean': createConvertToBoolean, + 'convert.toArray': createConvertToArray, + 'convert.toObject': createConvertToObject, + 'convert.parseJson': createConvertParseJson, + 'convert.toJson': createConvertToJson, + 'convert.parseDate': createConvertParseDate, + 'convert.formatDate': createConvertFormatDate, + 'convert.base64Encode': createConvertBase64Encode, + 'convert.base64Decode': createConvertBase64Decode, + 'convert.urlEncode': createConvertUrlEncode, + 'convert.urlDecode': createConvertUrlDecode, +}; + +export default factories; diff --git a/workflow/plugins/ts/convert/src/index.ts b/workflow/plugins/ts/convert/src/index.ts new file mode 100644 index 000000000..677a3ad30 --- /dev/null +++ b/workflow/plugins/ts/convert/src/index.ts @@ -0,0 +1,314 @@ +/** + * Workflow plugin: Type conversion and parsing operations. + */ + +import { NodeExecutor, ExecuteInputs, ExecuteResult, createTemplateContext } from '../../base'; +import { interpolateTemplate } from '../../../executor/ts/utils/template-engine'; + +const resolve = (value: any, ctx: any): any => { + if (typeof value === 'string' && value.startsWith('{{')) { + return interpolateTemplate(value, ctx); + } + return value; +}; + +export class ConvertToString implements NodeExecutor { + readonly nodeType = 'convert.toString'; + readonly category = 'convert'; + readonly description = 'Convert value to string'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + let result: string; + if (value === null || value === undefined) { + result = ''; + } else if (typeof value === 'object') { + result = JSON.stringify(value); + } else { + result = String(value); + } + return { result }; + } +} + +export class ConvertToNumber implements NodeExecutor { + readonly nodeType = 'convert.toNumber'; + readonly category = 'convert'; + readonly description = 'Convert value to number'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const defaultValue = inputs.node.parameters.default ?? 0; + const result = Number(value); + return { result: isNaN(result) ? defaultValue : result }; + } +} + +export class ConvertToInteger implements NodeExecutor { + readonly nodeType = 'convert.toInteger'; + readonly category = 'convert'; + readonly description = 'Convert value to integer (with optional radix)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const defaultValue = inputs.node.parameters.default ?? 0; + const radix = inputs.node.parameters.radix ?? 10; + const result = parseInt(String(value), radix); + return { result: isNaN(result) ? defaultValue : result }; + } +} + +export class ConvertToFloat implements NodeExecutor { + readonly nodeType = 'convert.toFloat'; + readonly category = 'convert'; + readonly description = 'Convert value to floating point number'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const defaultValue = inputs.node.parameters.default ?? 0.0; + const result = parseFloat(String(value)); + return { result: isNaN(result) ? defaultValue : result }; + } +} + +export class ConvertToBoolean implements NodeExecutor { + readonly nodeType = 'convert.toBoolean'; + readonly category = 'convert'; + readonly description = 'Convert value to boolean (handles "true", "1", "yes", "on")'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + let result: boolean; + if (typeof value === 'string') { + const lower = value.toLowerCase().trim(); + result = lower === 'true' || lower === '1' || lower === 'yes' || lower === 'on'; + } else { + result = Boolean(value); + } + return { result }; + } +} + +export class ConvertToArray implements NodeExecutor { + readonly nodeType = 'convert.toArray'; + readonly category = 'convert'; + readonly description = 'Convert value to array (parses JSON or splits by separator)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + let result: any[]; + if (Array.isArray(value)) { + result = value; + } else if (typeof value === 'string') { + try { + const parsed = JSON.parse(value); + result = Array.isArray(parsed) ? parsed : [parsed]; + } catch { + const separator = inputs.node.parameters.separator ?? ','; + result = value.split(separator).map(s => s.trim()); + } + } else if (value === null || value === undefined) { + result = []; + } else if (typeof value === 'object') { + result = Object.values(value); + } else { + result = [value]; + } + return { result }; + } +} + +export class ConvertToObject implements NodeExecutor { + readonly nodeType = 'convert.toObject'; + readonly category = 'convert'; + readonly description = 'Convert value to object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + let result: Record; + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + result = value; + } else if (typeof value === 'string') { + try { + const parsed = JSON.parse(value); + result = typeof parsed === 'object' && parsed !== null ? parsed : { value: parsed }; + } catch { + result = { value }; + } + } else if (Array.isArray(value)) { + result = Object.fromEntries(value.map((v, i) => [String(i), v])); + } else { + result = { value }; + } + return { result }; + } +} + +export class ConvertParseJson implements NodeExecutor { + readonly nodeType = 'convert.parseJson'; + readonly category = 'convert'; + readonly description = 'Parse JSON string to value'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const defaultValue = inputs.node.parameters.default ?? null; + if (typeof value !== 'string') return { result: value }; + try { + return { result: JSON.parse(value) }; + } catch { + return { result: defaultValue }; + } + } +} + +export class ConvertToJson implements NodeExecutor { + readonly nodeType = 'convert.toJson'; + readonly category = 'convert'; + readonly description = 'Stringify value to JSON (optionally pretty-printed)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const pretty = inputs.node.parameters.pretty ?? false; + const indent = pretty ? (inputs.node.parameters.indent ?? 2) : undefined; + const result = JSON.stringify(value, null, indent); + return { result }; + } +} + +export class ConvertParseDate implements NodeExecutor { + readonly nodeType = 'convert.parseDate'; + readonly category = 'convert'; + readonly description = 'Parse date string to ISO format'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const date = new Date(value); + if (isNaN(date.getTime())) { + return { result: null, valid: false }; + } + return { + result: date.toISOString(), + timestamp: date.getTime(), + valid: true, + }; + } +} + +export class ConvertFormatDate implements NodeExecutor { + readonly nodeType = 'convert.formatDate'; + readonly category = 'convert'; + readonly description = 'Format date to various string formats'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const format = inputs.node.parameters.format ?? 'iso'; + const date = value instanceof Date ? value : new Date(value); + if (isNaN(date.getTime())) { + return { result: null }; + } + let result: string; + switch (format) { + case 'iso': result = date.toISOString(); break; + case 'date': result = date.toDateString(); break; + case 'time': result = date.toTimeString(); break; + case 'locale': result = date.toLocaleString(); break; + case 'localeDate': result = date.toLocaleDateString(); break; + case 'localeTime': result = date.toLocaleTimeString(); break; + case 'timestamp': result = String(date.getTime()); break; + case 'unix': result = String(Math.floor(date.getTime() / 1000)); break; + default: result = date.toISOString(); + } + return { result }; + } +} + +export class ConvertBase64Encode implements NodeExecutor { + readonly nodeType = 'convert.base64Encode'; + readonly category = 'convert'; + readonly description = 'Encode value to Base64 string'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const str = typeof value === 'string' ? value : JSON.stringify(value); + const result = typeof btoa !== 'undefined' + ? btoa(unescape(encodeURIComponent(str))) + : Buffer.from(str).toString('base64'); + return { result }; + } +} + +export class ConvertBase64Decode implements NodeExecutor { + readonly nodeType = 'convert.base64Decode'; + readonly category = 'convert'; + readonly description = 'Decode Base64 string to value'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const result = typeof atob !== 'undefined' + ? decodeURIComponent(escape(atob(value))) + : Buffer.from(value, 'base64').toString('utf-8'); + return { result }; + } +} + +export class ConvertUrlEncode implements NodeExecutor { + readonly nodeType = 'convert.urlEncode'; + readonly category = 'convert'; + readonly description = 'URL encode string'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value, ctx)); + const component = inputs.node.parameters.component ?? true; + const result = component ? encodeURIComponent(value) : encodeURI(value); + return { result }; + } +} + +export class ConvertUrlDecode implements NodeExecutor { + readonly nodeType = 'convert.urlDecode'; + readonly category = 'convert'; + readonly description = 'URL decode string'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value, ctx)); + const component = inputs.node.parameters.component ?? true; + const result = component ? decodeURIComponent(value) : decodeURI(value); + return { result }; + } +} + +// Export all convert plugin classes +export const convertPluginClasses = { + 'convert.toString': ConvertToString, + 'convert.toNumber': ConvertToNumber, + 'convert.toInteger': ConvertToInteger, + 'convert.toFloat': ConvertToFloat, + 'convert.toBoolean': ConvertToBoolean, + 'convert.toArray': ConvertToArray, + 'convert.toObject': ConvertToObject, + 'convert.parseJson': ConvertParseJson, + 'convert.toJson': ConvertToJson, + 'convert.parseDate': ConvertParseDate, + 'convert.formatDate': ConvertFormatDate, + 'convert.base64Encode': ConvertBase64Encode, + 'convert.base64Decode': ConvertBase64Decode, + 'convert.urlEncode': ConvertUrlEncode, + 'convert.urlDecode': ConvertUrlDecode, +}; + +export default convertPluginClasses; diff --git a/workflow/plugins/ts/dict/package.json b/workflow/plugins/ts/dict/package.json new file mode 100644 index 000000000..a7d427b49 --- /dev/null +++ b/workflow/plugins/ts/dict/package.json @@ -0,0 +1,33 @@ +{ + "name": "@metabuilder/plugin-dict", + "version": "1.0.0", + "description": "Object/dictionary operations workflow plugins for MetaBuilder", + "author": "MetaBuilder", + "license": "MIT", + "keywords": ["dict", "object", "workflow", "plugin", "dictionary"], + "main": "src/index.ts", + "files": ["src/index.ts", "src/factory.ts"], + "metadata": { + "plugin_type": "dict", + "category": "dict", + "classes": [ + "DictGet", + "DictSet", + "DictDelete", + "DictKeys", + "DictValues", + "DictEntries", + "DictFromEntries", + "DictMerge", + "DictDeepMerge", + "DictPick", + "DictOmit", + "DictHas", + "DictSize", + "DictInvert", + "DictMapValues", + "DictFilterEntries" + ], + "entrypoint": "execute" + } +} diff --git a/workflow/plugins/ts/dict/src/factory.ts b/workflow/plugins/ts/dict/src/factory.ts new file mode 100644 index 000000000..78fae7421 --- /dev/null +++ b/workflow/plugins/ts/dict/src/factory.ts @@ -0,0 +1,108 @@ +/** + * Factory for dict plugin classes. + */ + +import { + DictGet, + DictSet, + DictDelete, + DictKeys, + DictValues, + DictEntries, + DictFromEntries, + DictMerge, + DictDeepMerge, + DictPick, + DictOmit, + DictHas, + DictSize, + DictInvert, + DictMapValues, + DictFilterEntries, +} from './index'; + +export function createDictGet(): DictGet { + return new DictGet(); +} + +export function createDictSet(): DictSet { + return new DictSet(); +} + +export function createDictDelete(): DictDelete { + return new DictDelete(); +} + +export function createDictKeys(): DictKeys { + return new DictKeys(); +} + +export function createDictValues(): DictValues { + return new DictValues(); +} + +export function createDictEntries(): DictEntries { + return new DictEntries(); +} + +export function createDictFromEntries(): DictFromEntries { + return new DictFromEntries(); +} + +export function createDictMerge(): DictMerge { + return new DictMerge(); +} + +export function createDictDeepMerge(): DictDeepMerge { + return new DictDeepMerge(); +} + +export function createDictPick(): DictPick { + return new DictPick(); +} + +export function createDictOmit(): DictOmit { + return new DictOmit(); +} + +export function createDictHas(): DictHas { + return new DictHas(); +} + +export function createDictSize(): DictSize { + return new DictSize(); +} + +export function createDictInvert(): DictInvert { + return new DictInvert(); +} + +export function createDictMapValues(): DictMapValues { + return new DictMapValues(); +} + +export function createDictFilterEntries(): DictFilterEntries { + return new DictFilterEntries(); +} + +// Factory map for all dict plugins +export const factories = { + 'dict.get': createDictGet, + 'dict.set': createDictSet, + 'dict.delete': createDictDelete, + 'dict.keys': createDictKeys, + 'dict.values': createDictValues, + 'dict.entries': createDictEntries, + 'dict.fromEntries': createDictFromEntries, + 'dict.merge': createDictMerge, + 'dict.deepMerge': createDictDeepMerge, + 'dict.pick': createDictPick, + 'dict.omit': createDictOmit, + 'dict.has': createDictHas, + 'dict.size': createDictSize, + 'dict.invert': createDictInvert, + 'dict.mapValues': createDictMapValues, + 'dict.filterEntries': createDictFilterEntries, +}; + +export default factories; diff --git a/workflow/plugins/ts/dict/src/index.ts b/workflow/plugins/ts/dict/src/index.ts new file mode 100644 index 000000000..fb60740aa --- /dev/null +++ b/workflow/plugins/ts/dict/src/index.ts @@ -0,0 +1,308 @@ +/** + * Workflow plugin: Object/dictionary operations. + */ + +import { NodeExecutor, ExecuteInputs, ExecuteResult, createTemplateContext } from '../../base'; +import { interpolateTemplate, evaluateTemplate } from '../../../executor/ts/utils/template-engine'; + +const resolve = (value: any, ctx: any): any => { + if (typeof value === 'string' && value.startsWith('{{')) { + return interpolateTemplate(value, ctx); + } + return value; +}; + +export class DictGet implements NodeExecutor { + readonly nodeType = 'dict.get'; + readonly category = 'dict'; + readonly description = 'Get value by key (supports nested dot notation paths)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const path = resolve(inputs.node.parameters.path, ctx); + const defaultValue = resolve(inputs.node.parameters.default, ctx); + if (typeof obj !== 'object' || obj === null) return { result: defaultValue }; + const keys = String(path).split('.'); + let result: any = obj; + for (const key of keys) { + if (result === null || result === undefined) break; + result = result[key]; + } + return { result: result ?? defaultValue }; + } +} + +export class DictSet implements NodeExecutor { + readonly nodeType = 'dict.set'; + readonly category = 'dict'; + readonly description = 'Set value by key (supports nested paths, returns new object)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const path = resolve(inputs.node.parameters.path, ctx); + const value = resolve(inputs.node.parameters.value, ctx); + if (typeof obj !== 'object' || obj === null) throw new Error('Object is required'); + const result = JSON.parse(JSON.stringify(obj)); + const keys = String(path).split('.'); + let current = result; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!(key in current) || typeof current[key] !== 'object') { + current[key] = {}; + } + current = current[key]; + } + current[keys[keys.length - 1]] = value; + return { result }; + } +} + +export class DictDelete implements NodeExecutor { + readonly nodeType = 'dict.delete'; + readonly category = 'dict'; + readonly description = 'Delete key from object (returns new object)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const key = resolve(inputs.node.parameters.key, ctx); + if (typeof obj !== 'object' || obj === null) return { result: {} }; + const result = { ...obj }; + delete result[key]; + return { result }; + } +} + +export class DictKeys implements NodeExecutor { + readonly nodeType = 'dict.keys'; + readonly category = 'dict'; + readonly description = 'Get all keys from object as array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + if (typeof obj !== 'object' || obj === null) return { result: [] }; + return { result: Object.keys(obj) }; + } +} + +export class DictValues implements NodeExecutor { + readonly nodeType = 'dict.values'; + readonly category = 'dict'; + readonly description = 'Get all values from object as array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + if (typeof obj !== 'object' || obj === null) return { result: [] }; + return { result: Object.values(obj) }; + } +} + +export class DictEntries implements NodeExecutor { + readonly nodeType = 'dict.entries'; + readonly category = 'dict'; + readonly description = 'Get all entries as [key, value] pairs array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + if (typeof obj !== 'object' || obj === null) return { result: [] }; + return { result: Object.entries(obj) }; + } +} + +export class DictFromEntries implements NodeExecutor { + readonly nodeType = 'dict.fromEntries'; + readonly category = 'dict'; + readonly description = 'Create object from [key, value] entries array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const entries = resolve(inputs.node.parameters.entries, ctx) || []; + if (!Array.isArray(entries)) return { result: {} }; + return { result: Object.fromEntries(entries) }; + } +} + +export class DictMerge implements NodeExecutor { + readonly nodeType = 'dict.merge'; + readonly category = 'dict'; + readonly description = 'Shallow merge multiple objects'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const objects = (inputs.node.parameters.objects || []).map((o: any) => resolve(o, ctx) || {}); + const result = Object.assign({}, ...objects); + return { result }; + } +} + +export class DictDeepMerge implements NodeExecutor { + readonly nodeType = 'dict.deepMerge'; + readonly category = 'dict'; + readonly description = 'Deep merge multiple objects (recursive)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const objects = (inputs.node.parameters.objects || []).map((o: any) => resolve(o, ctx) || {}); + const deepMerge = (target: any, source: any): any => { + const result = { ...target }; + for (const key of Object.keys(source)) { + if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { + result[key] = deepMerge(result[key] || {}, source[key]); + } else { + result[key] = source[key]; + } + } + return result; + }; + const result = objects.reduce((acc, obj) => deepMerge(acc, obj), {}); + return { result }; + } +} + +export class DictPick implements NodeExecutor { + readonly nodeType = 'dict.pick'; + readonly category = 'dict'; + readonly description = 'Pick specific keys from object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const keys = resolve(inputs.node.parameters.keys, ctx) || []; + if (typeof obj !== 'object' || obj === null) return { result: {} }; + const result: Record = {}; + for (const key of keys) { + if (key in obj) result[key] = obj[key]; + } + return { result }; + } +} + +export class DictOmit implements NodeExecutor { + readonly nodeType = 'dict.omit'; + readonly category = 'dict'; + readonly description = 'Omit specific keys from object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const keys = resolve(inputs.node.parameters.keys, ctx) || []; + if (typeof obj !== 'object' || obj === null) return { result: {} }; + const omitSet = new Set(keys); + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + if (!omitSet.has(key)) result[key] = value; + } + return { result }; + } +} + +export class DictHas implements NodeExecutor { + readonly nodeType = 'dict.has'; + readonly category = 'dict'; + readonly description = 'Check if key exists in object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const key = resolve(inputs.node.parameters.key, ctx); + if (typeof obj !== 'object' || obj === null) return { result: false }; + return { result: key in obj }; + } +} + +export class DictSize implements NodeExecutor { + readonly nodeType = 'dict.size'; + readonly category = 'dict'; + readonly description = 'Get number of keys in object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + if (typeof obj !== 'object' || obj === null) return { result: 0 }; + return { result: Object.keys(obj).length }; + } +} + +export class DictInvert implements NodeExecutor { + readonly nodeType = 'dict.invert'; + readonly category = 'dict'; + readonly description = 'Swap keys and values in object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + if (typeof obj !== 'object' || obj === null) return { result: {} }; + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + result[String(value)] = key; + } + return { result }; + } +} + +export class DictMapValues implements NodeExecutor { + readonly nodeType = 'dict.mapValues'; + readonly category = 'dict'; + readonly description = 'Transform all values using a template'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const transform = inputs.node.parameters.transform; + if (typeof obj !== 'object' || obj === null) return { result: {} }; + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + const evalCtx = { ...ctx, key, value, $key: key, $value: value }; + result[key] = interpolateTemplate(transform, evalCtx); + } + return { result }; + } +} + +export class DictFilterEntries implements NodeExecutor { + readonly nodeType = 'dict.filterEntries'; + readonly category = 'dict'; + readonly description = 'Filter object entries by a condition'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const obj = resolve(inputs.node.parameters.object, ctx) || {}; + const condition = inputs.node.parameters.condition; + if (typeof obj !== 'object' || obj === null) return { result: {} }; + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + const evalCtx = { ...ctx, key, value, $key: key, $value: value }; + if (evaluateTemplate(condition, evalCtx)) { + result[key] = value; + } + } + return { result }; + } +} + +// Export all dict plugin classes +export const dictPluginClasses = { + 'dict.get': DictGet, + 'dict.set': DictSet, + 'dict.delete': DictDelete, + 'dict.keys': DictKeys, + 'dict.values': DictValues, + 'dict.entries': DictEntries, + 'dict.fromEntries': DictFromEntries, + 'dict.merge': DictMerge, + 'dict.deepMerge': DictDeepMerge, + 'dict.pick': DictPick, + 'dict.omit': DictOmit, + 'dict.has': DictHas, + 'dict.size': DictSize, + 'dict.invert': DictInvert, + 'dict.mapValues': DictMapValues, + 'dict.filterEntries': DictFilterEntries, +}; + +export default dictPluginClasses; diff --git a/workflow/plugins/ts/list/package.json b/workflow/plugins/ts/list/package.json new file mode 100644 index 000000000..0e3eb62bb --- /dev/null +++ b/workflow/plugins/ts/list/package.json @@ -0,0 +1,39 @@ +{ + "name": "@metabuilder/plugin-list", + "version": "1.0.0", + "description": "Array/list operations workflow plugins for MetaBuilder", + "author": "MetaBuilder", + "license": "MIT", + "keywords": ["list", "array", "workflow", "plugin", "collection"], + "main": "src/index.ts", + "files": ["src/index.ts", "src/factory.ts"], + "metadata": { + "plugin_type": "list", + "category": "list", + "classes": [ + "ListConcat", + "ListLength", + "ListSlice", + "ListFind", + "ListFindIndex", + "ListFilter", + "ListMap", + "ListReduce", + "ListEvery", + "ListSome", + "ListSort", + "ListReverse", + "ListUnique", + "ListFlatten", + "ListPush", + "ListPop", + "ListShift", + "ListUnshift", + "ListIncludes", + "ListIndexOf", + "ListAt", + "ListGroupBy" + ], + "entrypoint": "execute" + } +} diff --git a/workflow/plugins/ts/list/src/factory.ts b/workflow/plugins/ts/list/src/factory.ts new file mode 100644 index 000000000..1bef31ffe --- /dev/null +++ b/workflow/plugins/ts/list/src/factory.ts @@ -0,0 +1,144 @@ +/** + * Factory for list plugin classes. + */ + +import { + ListConcat, + ListLength, + ListSlice, + ListFind, + ListFindIndex, + ListFilter, + ListMap, + ListReduce, + ListEvery, + ListSome, + ListSort, + ListReverse, + ListUnique, + ListFlatten, + ListPush, + ListPop, + ListShift, + ListUnshift, + ListIncludes, + ListIndexOf, + ListAt, + ListGroupBy, +} from './index'; + +export function createListConcat(): ListConcat { + return new ListConcat(); +} + +export function createListLength(): ListLength { + return new ListLength(); +} + +export function createListSlice(): ListSlice { + return new ListSlice(); +} + +export function createListFind(): ListFind { + return new ListFind(); +} + +export function createListFindIndex(): ListFindIndex { + return new ListFindIndex(); +} + +export function createListFilter(): ListFilter { + return new ListFilter(); +} + +export function createListMap(): ListMap { + return new ListMap(); +} + +export function createListReduce(): ListReduce { + return new ListReduce(); +} + +export function createListEvery(): ListEvery { + return new ListEvery(); +} + +export function createListSome(): ListSome { + return new ListSome(); +} + +export function createListSort(): ListSort { + return new ListSort(); +} + +export function createListReverse(): ListReverse { + return new ListReverse(); +} + +export function createListUnique(): ListUnique { + return new ListUnique(); +} + +export function createListFlatten(): ListFlatten { + return new ListFlatten(); +} + +export function createListPush(): ListPush { + return new ListPush(); +} + +export function createListPop(): ListPop { + return new ListPop(); +} + +export function createListShift(): ListShift { + return new ListShift(); +} + +export function createListUnshift(): ListUnshift { + return new ListUnshift(); +} + +export function createListIncludes(): ListIncludes { + return new ListIncludes(); +} + +export function createListIndexOf(): ListIndexOf { + return new ListIndexOf(); +} + +export function createListAt(): ListAt { + return new ListAt(); +} + +export function createListGroupBy(): ListGroupBy { + return new ListGroupBy(); +} + +// Factory map for all list plugins +export const factories = { + 'list.concat': createListConcat, + 'list.length': createListLength, + 'list.slice': createListSlice, + 'list.find': createListFind, + 'list.findIndex': createListFindIndex, + 'list.filter': createListFilter, + 'list.map': createListMap, + 'list.reduce': createListReduce, + 'list.every': createListEvery, + 'list.some': createListSome, + 'list.sort': createListSort, + 'list.reverse': createListReverse, + 'list.unique': createListUnique, + 'list.flatten': createListFlatten, + 'list.push': createListPush, + 'list.pop': createListPop, + 'list.shift': createListShift, + 'list.unshift': createListUnshift, + 'list.includes': createListIncludes, + 'list.indexOf': createListIndexOf, + 'list.at': createListAt, + 'list.groupBy': createListGroupBy, +}; + +export default factories; diff --git a/workflow/plugins/ts/list/src/index.ts b/workflow/plugins/ts/list/src/index.ts new file mode 100644 index 000000000..ffd574f7f --- /dev/null +++ b/workflow/plugins/ts/list/src/index.ts @@ -0,0 +1,394 @@ +/** + * Workflow plugin: Array/list operations. + */ + +import { NodeExecutor, ExecuteInputs, ExecuteResult, createTemplateContext } from '../../base'; +import { interpolateTemplate, evaluateTemplate } from '../../../executor/ts/utils/template-engine'; + +const resolve = (value: any, ctx: any): any => { + if (typeof value === 'string' && value.startsWith('{{')) { + return interpolateTemplate(value, ctx); + } + return value; +}; + +export class ListConcat implements NodeExecutor { + readonly nodeType = 'list.concat'; + readonly category = 'list'; + readonly description = 'Concatenate multiple arrays into one'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const arrays = (inputs.node.parameters.arrays || []).map((a: any) => resolve(a, ctx) || []); + return { result: arrays.flat() }; + } +} + +export class ListLength implements NodeExecutor { + readonly nodeType = 'list.length'; + readonly category = 'list'; + readonly description = 'Get the length of an array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + return { result: Array.isArray(array) ? array.length : 0 }; + } +} + +export class ListSlice implements NodeExecutor { + readonly nodeType = 'list.slice'; + readonly category = 'list'; + readonly description = 'Extract a portion of an array by start and end indices'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const startIdx = inputs.node.parameters.start ?? 0; + const endIdx = inputs.node.parameters.end; + return { result: Array.isArray(array) ? array.slice(startIdx, endIdx) : [] }; + } +} + +export class ListFind implements NodeExecutor { + readonly nodeType = 'list.find'; + readonly category = 'list'; + readonly description = 'Find first element matching a condition'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const condition = inputs.node.parameters.condition; + if (!Array.isArray(array)) return { result: null }; + const result = array.find((item, index) => { + const evalCtx = { ...ctx, item, index, $item: item, $index: index }; + return evaluateTemplate(condition, evalCtx); + }); + return { result: result ?? null }; + } +} + +export class ListFindIndex implements NodeExecutor { + readonly nodeType = 'list.findIndex'; + readonly category = 'list'; + readonly description = 'Find index of first element matching a condition'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const condition = inputs.node.parameters.condition; + if (!Array.isArray(array)) return { result: -1 }; + const result = array.findIndex((item, index) => { + const evalCtx = { ...ctx, item, index, $item: item, $index: index }; + return evaluateTemplate(condition, evalCtx); + }); + return { result }; + } +} + +export class ListFilter implements NodeExecutor { + readonly nodeType = 'list.filter'; + readonly category = 'list'; + readonly description = 'Filter array elements by a condition'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const condition = inputs.node.parameters.condition; + if (!Array.isArray(array)) return { result: [] }; + const result = array.filter((item, index) => { + const evalCtx = { ...ctx, item, index, $item: item, $index: index }; + return evaluateTemplate(condition, evalCtx); + }); + return { result }; + } +} + +export class ListMap implements NodeExecutor { + readonly nodeType = 'list.map'; + readonly category = 'list'; + readonly description = 'Transform each element using a template'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const transform = inputs.node.parameters.transform; + if (!Array.isArray(array)) return { result: [] }; + const result = array.map((item, index) => { + const evalCtx = { ...ctx, item, index, $item: item, $index: index }; + return interpolateTemplate(transform, evalCtx); + }); + return { result }; + } +} + +export class ListReduce implements NodeExecutor { + readonly nodeType = 'list.reduce'; + readonly category = 'list'; + readonly description = 'Reduce array to a single value using a reducer expression'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const reducer = inputs.node.parameters.reducer; + const initial = resolve(inputs.node.parameters.initial, ctx); + if (!Array.isArray(array)) return { result: initial }; + const result = array.reduce((acc, item, index) => { + const evalCtx = { ...ctx, acc, item, index, $acc: acc, $item: item, $index: index }; + return evaluateTemplate(reducer, evalCtx); + }, initial); + return { result }; + } +} + +export class ListEvery implements NodeExecutor { + readonly nodeType = 'list.every'; + readonly category = 'list'; + readonly description = 'Check if all elements match a condition'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const condition = inputs.node.parameters.condition; + if (!Array.isArray(array)) return { result: true }; + const result = array.every((item, index) => { + const evalCtx = { ...ctx, item, index, $item: item, $index: index }; + return evaluateTemplate(condition, evalCtx); + }); + return { result }; + } +} + +export class ListSome implements NodeExecutor { + readonly nodeType = 'list.some'; + readonly category = 'list'; + readonly description = 'Check if any element matches a condition'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const condition = inputs.node.parameters.condition; + if (!Array.isArray(array)) return { result: false }; + const result = array.some((item, index) => { + const evalCtx = { ...ctx, item, index, $item: item, $index: index }; + return evaluateTemplate(condition, evalCtx); + }); + return { result }; + } +} + +export class ListSort implements NodeExecutor { + readonly nodeType = 'list.sort'; + readonly category = 'list'; + readonly description = 'Sort array by key in ascending or descending order'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const key = inputs.node.parameters.key; + const order = inputs.node.parameters.order ?? 'asc'; + if (!Array.isArray(array)) return { result: [] }; + const sorted = [...array].sort((a, b) => { + const valA = key ? a[key] : a; + const valB = key ? b[key] : b; + if (valA < valB) return order === 'asc' ? -1 : 1; + if (valA > valB) return order === 'asc' ? 1 : -1; + return 0; + }); + return { result: sorted }; + } +} + +export class ListReverse implements NodeExecutor { + readonly nodeType = 'list.reverse'; + readonly category = 'list'; + readonly description = 'Reverse the order of array elements'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + return { result: Array.isArray(array) ? [...array].reverse() : [] }; + } +} + +export class ListUnique implements NodeExecutor { + readonly nodeType = 'list.unique'; + readonly category = 'list'; + readonly description = 'Remove duplicate elements from array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const key = inputs.node.parameters.key; + if (!Array.isArray(array)) return { result: [] }; + let result: any[]; + if (key) { + const seen = new Set(); + result = array.filter(item => { + const val = item[key]; + if (seen.has(val)) return false; + seen.add(val); + return true; + }); + } else { + result = [...new Set(array)]; + } + return { result }; + } +} + +export class ListFlatten implements NodeExecutor { + readonly nodeType = 'list.flatten'; + readonly category = 'list'; + readonly description = 'Flatten nested arrays to specified depth'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const depth = inputs.node.parameters.depth ?? 1; + return { result: Array.isArray(array) ? array.flat(depth) : [] }; + } +} + +export class ListPush implements NodeExecutor { + readonly nodeType = 'list.push'; + readonly category = 'list'; + readonly description = 'Add element to end of array (immutable)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const value = resolve(inputs.node.parameters.value, ctx); + return { result: Array.isArray(array) ? [...array, value] : [value] }; + } +} + +export class ListPop implements NodeExecutor { + readonly nodeType = 'list.pop'; + readonly category = 'list'; + readonly description = 'Remove and return last element from array (immutable)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + if (!Array.isArray(array) || array.length === 0) return { result: [], removed: null }; + const removed = array[array.length - 1]; + return { result: array.slice(0, -1), removed }; + } +} + +export class ListShift implements NodeExecutor { + readonly nodeType = 'list.shift'; + readonly category = 'list'; + readonly description = 'Remove and return first element from array (immutable)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + if (!Array.isArray(array) || array.length === 0) return { result: [], removed: null }; + const removed = array[0]; + return { result: array.slice(1), removed }; + } +} + +export class ListUnshift implements NodeExecutor { + readonly nodeType = 'list.unshift'; + readonly category = 'list'; + readonly description = 'Add element to beginning of array (immutable)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const value = resolve(inputs.node.parameters.value, ctx); + return { result: Array.isArray(array) ? [value, ...array] : [value] }; + } +} + +export class ListIncludes implements NodeExecutor { + readonly nodeType = 'list.includes'; + readonly category = 'list'; + readonly description = 'Check if array includes a specific value'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const value = resolve(inputs.node.parameters.value, ctx); + return { result: Array.isArray(array) && array.includes(value) }; + } +} + +export class ListIndexOf implements NodeExecutor { + readonly nodeType = 'list.indexOf'; + readonly category = 'list'; + readonly description = 'Get the index of a value in array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const value = resolve(inputs.node.parameters.value, ctx); + return { result: Array.isArray(array) ? array.indexOf(value) : -1 }; + } +} + +export class ListAt implements NodeExecutor { + readonly nodeType = 'list.at'; + readonly category = 'list'; + readonly description = 'Get element at index (supports negative indices)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const index = inputs.node.parameters.index ?? 0; + if (!Array.isArray(array)) return { result: null }; + const result = array.at(index) ?? null; + return { result }; + } +} + +export class ListGroupBy implements NodeExecutor { + readonly nodeType = 'list.groupBy'; + readonly category = 'list'; + readonly description = 'Group array elements by a key into an object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const array = resolve(inputs.node.parameters.array, ctx) || []; + const key = inputs.node.parameters.key; + if (!Array.isArray(array)) return { result: {} }; + const groups: Record = {}; + for (const item of array) { + const groupKey = String(item[key] ?? 'undefined'); + if (!groups[groupKey]) groups[groupKey] = []; + groups[groupKey].push(item); + } + return { result: groups }; + } +} + +// Export all list plugin classes +export const listPluginClasses = { + 'list.concat': ListConcat, + 'list.length': ListLength, + 'list.slice': ListSlice, + 'list.find': ListFind, + 'list.findIndex': ListFindIndex, + 'list.filter': ListFilter, + 'list.map': ListMap, + 'list.reduce': ListReduce, + 'list.every': ListEvery, + 'list.some': ListSome, + 'list.sort': ListSort, + 'list.reverse': ListReverse, + 'list.unique': ListUnique, + 'list.flatten': ListFlatten, + 'list.push': ListPush, + 'list.pop': ListPop, + 'list.shift': ListShift, + 'list.unshift': ListUnshift, + 'list.includes': ListIncludes, + 'list.indexOf': ListIndexOf, + 'list.at': ListAt, + 'list.groupBy': ListGroupBy, +}; + +export default listPluginClasses; diff --git a/workflow/plugins/ts/logic/package.json b/workflow/plugins/ts/logic/package.json new file mode 100644 index 000000000..3cb0dfadc --- /dev/null +++ b/workflow/plugins/ts/logic/package.json @@ -0,0 +1,34 @@ +{ + "name": "@metabuilder/plugin-logic", + "version": "1.0.0", + "description": "Logical and comparison operations workflow plugins for MetaBuilder", + "author": "MetaBuilder", + "license": "MIT", + "keywords": ["logic", "workflow", "plugin", "comparison", "boolean"], + "main": "src/index.ts", + "files": ["src/index.ts", "src/factory.ts"], + "metadata": { + "plugin_type": "logic", + "category": "logic", + "classes": [ + "LogicAnd", + "LogicOr", + "LogicNot", + "LogicXor", + "LogicEquals", + "LogicNotEquals", + "LogicGt", + "LogicGte", + "LogicLt", + "LogicLte", + "LogicIn", + "LogicBetween", + "LogicIsNull", + "LogicIsEmpty", + "LogicTypeOf", + "LogicTernary", + "LogicCoalesce" + ], + "entrypoint": "execute" + } +} diff --git a/workflow/plugins/ts/logic/src/factory.ts b/workflow/plugins/ts/logic/src/factory.ts new file mode 100644 index 000000000..57ac22758 --- /dev/null +++ b/workflow/plugins/ts/logic/src/factory.ts @@ -0,0 +1,114 @@ +/** + * Factory for logic plugin classes. + */ + +import { + LogicAnd, + LogicOr, + LogicNot, + LogicXor, + LogicEquals, + LogicNotEquals, + LogicGt, + LogicGte, + LogicLt, + LogicLte, + LogicIn, + LogicBetween, + LogicIsNull, + LogicIsEmpty, + LogicTypeOf, + LogicTernary, + LogicCoalesce, +} from './index'; + +export function createLogicAnd(): LogicAnd { + return new LogicAnd(); +} + +export function createLogicOr(): LogicOr { + return new LogicOr(); +} + +export function createLogicNot(): LogicNot { + return new LogicNot(); +} + +export function createLogicXor(): LogicXor { + return new LogicXor(); +} + +export function createLogicEquals(): LogicEquals { + return new LogicEquals(); +} + +export function createLogicNotEquals(): LogicNotEquals { + return new LogicNotEquals(); +} + +export function createLogicGt(): LogicGt { + return new LogicGt(); +} + +export function createLogicGte(): LogicGte { + return new LogicGte(); +} + +export function createLogicLt(): LogicLt { + return new LogicLt(); +} + +export function createLogicLte(): LogicLte { + return new LogicLte(); +} + +export function createLogicIn(): LogicIn { + return new LogicIn(); +} + +export function createLogicBetween(): LogicBetween { + return new LogicBetween(); +} + +export function createLogicIsNull(): LogicIsNull { + return new LogicIsNull(); +} + +export function createLogicIsEmpty(): LogicIsEmpty { + return new LogicIsEmpty(); +} + +export function createLogicTypeOf(): LogicTypeOf { + return new LogicTypeOf(); +} + +export function createLogicTernary(): LogicTernary { + return new LogicTernary(); +} + +export function createLogicCoalesce(): LogicCoalesce { + return new LogicCoalesce(); +} + +// Factory map for all logic plugins +export const factories = { + 'logic.and': createLogicAnd, + 'logic.or': createLogicOr, + 'logic.not': createLogicNot, + 'logic.xor': createLogicXor, + 'logic.equals': createLogicEquals, + 'logic.notEquals': createLogicNotEquals, + 'logic.gt': createLogicGt, + 'logic.gte': createLogicGte, + 'logic.lt': createLogicLt, + 'logic.lte': createLogicLte, + 'logic.in': createLogicIn, + 'logic.between': createLogicBetween, + 'logic.isNull': createLogicIsNull, + 'logic.isEmpty': createLogicIsEmpty, + 'logic.typeOf': createLogicTypeOf, + 'logic.ternary': createLogicTernary, + 'logic.coalesce': createLogicCoalesce, +}; + +export default factories; diff --git a/workflow/plugins/ts/logic/src/index.ts b/workflow/plugins/ts/logic/src/index.ts new file mode 100644 index 000000000..037443df9 --- /dev/null +++ b/workflow/plugins/ts/logic/src/index.ts @@ -0,0 +1,268 @@ +/** + * Workflow plugin: Logical and comparison operations. + */ + +import { NodeExecutor, ExecuteInputs, ExecuteResult, createTemplateContext } from '../../base'; +import { interpolateTemplate } from '../../../executor/ts/utils/template-engine'; + +const resolve = (value: any, ctx: any): any => { + if (typeof value === 'string' && value.startsWith('{{')) { + return interpolateTemplate(value, ctx); + } + return value; +}; + +export class LogicAnd implements NodeExecutor { + readonly nodeType = 'logic.and'; + readonly category = 'logic'; + readonly description = 'Logical AND - returns true if all values are truthy'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => Boolean(resolve(v, ctx))); + return { result: values.every(v => v) }; + } +} + +export class LogicOr implements NodeExecutor { + readonly nodeType = 'logic.or'; + readonly category = 'logic'; + readonly description = 'Logical OR - returns true if any value is truthy'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => Boolean(resolve(v, ctx))); + return { result: values.some(v => v) }; + } +} + +export class LogicNot implements NodeExecutor { + readonly nodeType = 'logic.not'; + readonly category = 'logic'; + readonly description = 'Logical NOT - inverts boolean value'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = Boolean(resolve(inputs.node.parameters.value, ctx)); + return { result: !value }; + } +} + +export class LogicXor implements NodeExecutor { + readonly nodeType = 'logic.xor'; + readonly category = 'logic'; + readonly description = 'Logical XOR - returns true if values are different'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = Boolean(resolve(inputs.node.parameters.a, ctx)); + const b = Boolean(resolve(inputs.node.parameters.b, ctx)); + return { result: a !== b }; + } +} + +export class LogicEquals implements NodeExecutor { + readonly nodeType = 'logic.equals'; + readonly category = 'logic'; + readonly description = 'Equality check (strict or loose comparison)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + const strict = inputs.node.parameters.strict ?? true; + const result = strict ? a === b : a == b; + return { result }; + } +} + +export class LogicNotEquals implements NodeExecutor { + readonly nodeType = 'logic.notEquals'; + readonly category = 'logic'; + readonly description = 'Inequality check (strict or loose comparison)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + const strict = inputs.node.parameters.strict ?? true; + const result = strict ? a !== b : a != b; + return { result }; + } +} + +export class LogicGt implements NodeExecutor { + readonly nodeType = 'logic.gt'; + readonly category = 'logic'; + readonly description = 'Greater than comparison'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + return { result: a > b }; + } +} + +export class LogicGte implements NodeExecutor { + readonly nodeType = 'logic.gte'; + readonly category = 'logic'; + readonly description = 'Greater than or equal comparison'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + return { result: a >= b }; + } +} + +export class LogicLt implements NodeExecutor { + readonly nodeType = 'logic.lt'; + readonly category = 'logic'; + readonly description = 'Less than comparison'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + return { result: a < b }; + } +} + +export class LogicLte implements NodeExecutor { + readonly nodeType = 'logic.lte'; + readonly category = 'logic'; + readonly description = 'Less than or equal comparison'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + return { result: a <= b }; + } +} + +export class LogicIn implements NodeExecutor { + readonly nodeType = 'logic.in'; + readonly category = 'logic'; + readonly description = 'Check if value is in array'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const array = resolve(inputs.node.parameters.array, ctx) || []; + return { result: Array.isArray(array) && array.includes(value) }; + } +} + +export class LogicBetween implements NodeExecutor { + readonly nodeType = 'logic.between'; + readonly category = 'logic'; + readonly description = 'Check if value is between min and max (inclusive or exclusive)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const min = resolve(inputs.node.parameters.min, ctx); + const max = resolve(inputs.node.parameters.max, ctx); + const inclusive = inputs.node.parameters.inclusive ?? true; + const result = inclusive ? (value >= min && value <= max) : (value > min && value < max); + return { result }; + } +} + +export class LogicIsNull implements NodeExecutor { + readonly nodeType = 'logic.isNull'; + readonly category = 'logic'; + readonly description = 'Check if value is null or undefined'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + return { result: value === null || value === undefined }; + } +} + +export class LogicIsEmpty implements NodeExecutor { + readonly nodeType = 'logic.isEmpty'; + readonly category = 'logic'; + readonly description = 'Check if value is empty (null, undefined, empty string, empty array, empty object)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + let isEmpty = false; + if (value === null || value === undefined) isEmpty = true; + else if (typeof value === 'string') isEmpty = value.length === 0; + else if (Array.isArray(value)) isEmpty = value.length === 0; + else if (typeof value === 'object') isEmpty = Object.keys(value).length === 0; + return { result: isEmpty }; + } +} + +export class LogicTypeOf implements NodeExecutor { + readonly nodeType = 'logic.typeOf'; + readonly category = 'logic'; + readonly description = 'Get type of value (string, number, boolean, array, object, null, undefined)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + let type: string; + if (value === null) type = 'null'; + else if (Array.isArray(value)) type = 'array'; + else type = typeof value; + return { result: type }; + } +} + +export class LogicTernary implements NodeExecutor { + readonly nodeType = 'logic.ternary'; + readonly category = 'logic'; + readonly description = 'Ternary conditional - returns then value if condition is truthy, else value otherwise'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const condition = Boolean(resolve(inputs.node.parameters.condition, ctx)); + const thenValue = resolve(inputs.node.parameters.then, ctx); + const elseValue = resolve(inputs.node.parameters.else, ctx); + return { result: condition ? thenValue : elseValue }; + } +} + +export class LogicCoalesce implements NodeExecutor { + readonly nodeType = 'logic.coalesce'; + readonly category = 'logic'; + readonly description = 'Return first non-null/undefined value from list'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + const result = values.find(v => v !== null && v !== undefined) ?? null; + return { result }; + } +} + +// Export all logic plugin classes +export const logicPluginClasses = { + 'logic.and': LogicAnd, + 'logic.or': LogicOr, + 'logic.not': LogicNot, + 'logic.xor': LogicXor, + 'logic.equals': LogicEquals, + 'logic.notEquals': LogicNotEquals, + 'logic.gt': LogicGt, + 'logic.gte': LogicGte, + 'logic.lt': LogicLt, + 'logic.lte': LogicLte, + 'logic.in': LogicIn, + 'logic.between': LogicBetween, + 'logic.isNull': LogicIsNull, + 'logic.isEmpty': LogicIsEmpty, + 'logic.typeOf': LogicTypeOf, + 'logic.ternary': LogicTernary, + 'logic.coalesce': LogicCoalesce, +}; + +export default logicPluginClasses; diff --git a/workflow/plugins/ts/math/package.json b/workflow/plugins/ts/math/package.json new file mode 100644 index 000000000..269660592 --- /dev/null +++ b/workflow/plugins/ts/math/package.json @@ -0,0 +1,34 @@ +{ + "name": "@metabuilder/plugin-math", + "version": "1.0.0", + "description": "Mathematical operations workflow plugins for MetaBuilder", + "author": "MetaBuilder", + "license": "MIT", + "keywords": ["math", "workflow", "plugin", "arithmetic", "calculation"], + "main": "src/index.ts", + "files": ["src/index.ts", "src/factory.ts"], + "metadata": { + "plugin_type": "math", + "category": "math", + "classes": [ + "MathAdd", + "MathSubtract", + "MathMultiply", + "MathDivide", + "MathModulo", + "MathPower", + "MathSqrt", + "MathAbs", + "MathRound", + "MathFloor", + "MathCeil", + "MathMin", + "MathMax", + "MathSum", + "MathAverage", + "MathRandom", + "MathClamp" + ], + "entrypoint": "execute" + } +} diff --git a/workflow/plugins/ts/math/src/factory.ts b/workflow/plugins/ts/math/src/factory.ts new file mode 100644 index 000000000..191802423 --- /dev/null +++ b/workflow/plugins/ts/math/src/factory.ts @@ -0,0 +1,114 @@ +/** + * Factory for math plugin classes. + */ + +import { + MathAdd, + MathSubtract, + MathMultiply, + MathDivide, + MathModulo, + MathPower, + MathSqrt, + MathAbs, + MathRound, + MathFloor, + MathCeil, + MathMin, + MathMax, + MathSum, + MathAverage, + MathRandom, + MathClamp, +} from './index'; + +export function createMathAdd(): MathAdd { + return new MathAdd(); +} + +export function createMathSubtract(): MathSubtract { + return new MathSubtract(); +} + +export function createMathMultiply(): MathMultiply { + return new MathMultiply(); +} + +export function createMathDivide(): MathDivide { + return new MathDivide(); +} + +export function createMathModulo(): MathModulo { + return new MathModulo(); +} + +export function createMathPower(): MathPower { + return new MathPower(); +} + +export function createMathSqrt(): MathSqrt { + return new MathSqrt(); +} + +export function createMathAbs(): MathAbs { + return new MathAbs(); +} + +export function createMathRound(): MathRound { + return new MathRound(); +} + +export function createMathFloor(): MathFloor { + return new MathFloor(); +} + +export function createMathCeil(): MathCeil { + return new MathCeil(); +} + +export function createMathMin(): MathMin { + return new MathMin(); +} + +export function createMathMax(): MathMax { + return new MathMax(); +} + +export function createMathSum(): MathSum { + return new MathSum(); +} + +export function createMathAverage(): MathAverage { + return new MathAverage(); +} + +export function createMathRandom(): MathRandom { + return new MathRandom(); +} + +export function createMathClamp(): MathClamp { + return new MathClamp(); +} + +// Factory map for all math plugins +export const factories = { + 'math.add': createMathAdd, + 'math.subtract': createMathSubtract, + 'math.multiply': createMathMultiply, + 'math.divide': createMathDivide, + 'math.modulo': createMathModulo, + 'math.power': createMathPower, + 'math.sqrt': createMathSqrt, + 'math.abs': createMathAbs, + 'math.round': createMathRound, + 'math.floor': createMathFloor, + 'math.ceil': createMathCeil, + 'math.min': createMathMin, + 'math.max': createMathMax, + 'math.sum': createMathSum, + 'math.average': createMathAverage, + 'math.random': createMathRandom, + 'math.clamp': createMathClamp, +}; + +export default factories; diff --git a/workflow/plugins/ts/math/src/index.ts b/workflow/plugins/ts/math/src/index.ts new file mode 100644 index 000000000..3b99afacf --- /dev/null +++ b/workflow/plugins/ts/math/src/index.ts @@ -0,0 +1,259 @@ +/** + * Workflow plugin: Mathematical operations. + */ + +import { NodeExecutor, ExecuteInputs, ExecuteResult, createTemplateContext } from '../../base'; +import { interpolateTemplate } from '../../../executor/ts/utils/template-engine'; + +const resolve = (value: any, ctx: any): number => { + if (typeof value === 'string' && value.startsWith('{{')) { + value = interpolateTemplate(value, ctx); + } + return Number(value); +}; + +export class MathAdd implements NodeExecutor { + readonly nodeType = 'math.add'; + readonly category = 'math'; + readonly description = 'Add multiple numbers together'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + const result = values.reduce((sum: number, val: number) => sum + val, 0); + return { result }; + } +} + +export class MathSubtract implements NodeExecutor { + readonly nodeType = 'math.subtract'; + readonly category = 'math'; + readonly description = 'Subtract one number from another'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + return { result: a - b }; + } +} + +export class MathMultiply implements NodeExecutor { + readonly nodeType = 'math.multiply'; + readonly category = 'math'; + readonly description = 'Multiply multiple numbers together'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + const result = values.reduce((prod: number, val: number) => prod * val, 1); + return { result }; + } +} + +export class MathDivide implements NodeExecutor { + readonly nodeType = 'math.divide'; + readonly category = 'math'; + readonly description = 'Divide one number by another'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + if (b === 0) throw new Error('Division by zero'); + return { result: a / b }; + } +} + +export class MathModulo implements NodeExecutor { + readonly nodeType = 'math.modulo'; + readonly category = 'math'; + readonly description = 'Get the remainder of division (modulo operation)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const a = resolve(inputs.node.parameters.a, ctx); + const b = resolve(inputs.node.parameters.b, ctx); + if (b === 0) throw new Error('Modulo by zero'); + return { result: a % b }; + } +} + +export class MathPower implements NodeExecutor { + readonly nodeType = 'math.power'; + readonly category = 'math'; + readonly description = 'Raise a number to a power (exponentiation)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const base = resolve(inputs.node.parameters.base, ctx); + const exponent = resolve(inputs.node.parameters.exponent, ctx); + return { result: Math.pow(base, exponent) }; + } +} + +export class MathSqrt implements NodeExecutor { + readonly nodeType = 'math.sqrt'; + readonly category = 'math'; + readonly description = 'Calculate the square root of a number'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + if (value < 0) throw new Error('Cannot calculate square root of negative number'); + return { result: Math.sqrt(value) }; + } +} + +export class MathAbs implements NodeExecutor { + readonly nodeType = 'math.abs'; + readonly category = 'math'; + readonly description = 'Get the absolute value of a number'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + return { result: Math.abs(value) }; + } +} + +export class MathRound implements NodeExecutor { + readonly nodeType = 'math.round'; + readonly category = 'math'; + readonly description = 'Round a number to specified decimal places'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const decimals = inputs.node.parameters.decimals ?? 0; + const factor = Math.pow(10, decimals); + return { result: Math.round(value * factor) / factor }; + } +} + +export class MathFloor implements NodeExecutor { + readonly nodeType = 'math.floor'; + readonly category = 'math'; + readonly description = 'Round a number down to the nearest integer'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + return { result: Math.floor(value) }; + } +} + +export class MathCeil implements NodeExecutor { + readonly nodeType = 'math.ceil'; + readonly category = 'math'; + readonly description = 'Round a number up to the nearest integer'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + return { result: Math.ceil(value) }; + } +} + +export class MathMin implements NodeExecutor { + readonly nodeType = 'math.min'; + readonly category = 'math'; + readonly description = 'Get the minimum value from a list of numbers'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + return { result: Math.min(...values) }; + } +} + +export class MathMax implements NodeExecutor { + readonly nodeType = 'math.max'; + readonly category = 'math'; + readonly description = 'Get the maximum value from a list of numbers'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + return { result: Math.max(...values) }; + } +} + +export class MathSum implements NodeExecutor { + readonly nodeType = 'math.sum'; + readonly category = 'math'; + readonly description = 'Calculate the sum of a list of numbers'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + return { result: values.reduce((a: number, b: number) => a + b, 0) }; + } +} + +export class MathAverage implements NodeExecutor { + readonly nodeType = 'math.average'; + readonly category = 'math'; + readonly description = 'Calculate the average of a list of numbers'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + if (values.length === 0) return { result: 0 }; + const sum = values.reduce((a: number, b: number) => a + b, 0); + return { result: sum / values.length }; + } +} + +export class MathRandom implements NodeExecutor { + readonly nodeType = 'math.random'; + readonly category = 'math'; + readonly description = 'Generate a random number within a range'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const min = inputs.node.parameters.min !== undefined ? resolve(inputs.node.parameters.min, ctx) : 0; + const max = inputs.node.parameters.max !== undefined ? resolve(inputs.node.parameters.max, ctx) : 1; + const integer = inputs.node.parameters.integer ?? false; + let result = Math.random() * (max - min) + min; + if (integer) result = Math.floor(result); + return { result }; + } +} + +export class MathClamp implements NodeExecutor { + readonly nodeType = 'math.clamp'; + readonly category = 'math'; + readonly description = 'Clamp a value between a minimum and maximum'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = resolve(inputs.node.parameters.value, ctx); + const min = resolve(inputs.node.parameters.min, ctx); + const max = resolve(inputs.node.parameters.max, ctx); + return { result: Math.min(Math.max(value, min), max) }; + } +} + +// Export all math plugin classes +export const mathPluginClasses = { + 'math.add': MathAdd, + 'math.subtract': MathSubtract, + 'math.multiply': MathMultiply, + 'math.divide': MathDivide, + 'math.modulo': MathModulo, + 'math.power': MathPower, + 'math.sqrt': MathSqrt, + 'math.abs': MathAbs, + 'math.round': MathRound, + 'math.floor': MathFloor, + 'math.ceil': MathCeil, + 'math.min': MathMin, + 'math.max': MathMax, + 'math.sum': MathSum, + 'math.average': MathAverage, + 'math.random': MathRandom, + 'math.clamp': MathClamp, +}; + +export default mathPluginClasses; diff --git a/workflow/plugins/ts/string/package.json b/workflow/plugins/ts/string/package.json new file mode 100644 index 000000000..6e2ed8278 --- /dev/null +++ b/workflow/plugins/ts/string/package.json @@ -0,0 +1,32 @@ +{ + "name": "@metabuilder/plugin-string", + "version": "1.0.0", + "description": "String manipulation workflow plugins for MetaBuilder", + "author": "MetaBuilder", + "license": "MIT", + "keywords": ["string", "workflow", "plugin", "text", "manipulation"], + "main": "src/index.ts", + "files": ["src/index.ts", "src/factory.ts"], + "metadata": { + "plugin_type": "string", + "category": "string", + "classes": [ + "StringConcat", + "StringFormat", + "StringLength", + "StringLower", + "StringUpper", + "StringTrim", + "StringReplace", + "StringSplit", + "StringJoin", + "StringSubstring", + "StringIncludes", + "StringStartsWith", + "StringEndsWith", + "StringPadStart", + "StringPadEnd" + ], + "entrypoint": "execute" + } +} diff --git a/workflow/plugins/ts/string/src/factory.ts b/workflow/plugins/ts/string/src/factory.ts new file mode 100644 index 000000000..1f70fdfe8 --- /dev/null +++ b/workflow/plugins/ts/string/src/factory.ts @@ -0,0 +1,102 @@ +/** + * Factory for string plugin classes. + */ + +import { + StringConcat, + StringFormat, + StringLength, + StringLower, + StringUpper, + StringTrim, + StringReplace, + StringSplit, + StringJoin, + StringSubstring, + StringIncludes, + StringStartsWith, + StringEndsWith, + StringPadStart, + StringPadEnd, +} from './index'; + +export function createStringConcat(): StringConcat { + return new StringConcat(); +} + +export function createStringFormat(): StringFormat { + return new StringFormat(); +} + +export function createStringLength(): StringLength { + return new StringLength(); +} + +export function createStringLower(): StringLower { + return new StringLower(); +} + +export function createStringUpper(): StringUpper { + return new StringUpper(); +} + +export function createStringTrim(): StringTrim { + return new StringTrim(); +} + +export function createStringReplace(): StringReplace { + return new StringReplace(); +} + +export function createStringSplit(): StringSplit { + return new StringSplit(); +} + +export function createStringJoin(): StringJoin { + return new StringJoin(); +} + +export function createStringSubstring(): StringSubstring { + return new StringSubstring(); +} + +export function createStringIncludes(): StringIncludes { + return new StringIncludes(); +} + +export function createStringStartsWith(): StringStartsWith { + return new StringStartsWith(); +} + +export function createStringEndsWith(): StringEndsWith { + return new StringEndsWith(); +} + +export function createStringPadStart(): StringPadStart { + return new StringPadStart(); +} + +export function createStringPadEnd(): StringPadEnd { + return new StringPadEnd(); +} + +// Factory map for all string plugins +export const factories = { + 'string.concat': createStringConcat, + 'string.format': createStringFormat, + 'string.length': createStringLength, + 'string.lower': createStringLower, + 'string.upper': createStringUpper, + 'string.trim': createStringTrim, + 'string.replace': createStringReplace, + 'string.split': createStringSplit, + 'string.join': createStringJoin, + 'string.substring': createStringSubstring, + 'string.includes': createStringIncludes, + 'string.startsWith': createStringStartsWith, + 'string.endsWith': createStringEndsWith, + 'string.padStart': createStringPadStart, + 'string.padEnd': createStringPadEnd, +}; + +export default factories; diff --git a/workflow/plugins/ts/string/src/index.ts b/workflow/plugins/ts/string/src/index.ts new file mode 100644 index 000000000..1a0b1ad62 --- /dev/null +++ b/workflow/plugins/ts/string/src/index.ts @@ -0,0 +1,247 @@ +/** + * Workflow plugin: String manipulation operations. + */ + +import { NodeExecutor, ExecuteInputs, ExecuteResult, createTemplateContext } from '../../base'; +import { interpolateTemplate } from '../../../executor/ts/utils/template-engine'; + +const resolve = (value: any, ctx: any): any => { + if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) { + return interpolateTemplate(value, ctx); + } + return value; +}; + +export class StringConcat implements NodeExecutor { + readonly nodeType = 'string.concat'; + readonly category = 'string'; + readonly description = 'Concatenate multiple strings with an optional separator'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = (inputs.node.parameters.values || []).map((v: any) => resolve(v, ctx)); + const separator = resolve(inputs.node.parameters.separator ?? '', ctx); + return { result: values.join(separator) }; + } +} + +export class StringFormat implements NodeExecutor { + readonly nodeType = 'string.format'; + readonly category = 'string'; + readonly description = 'Format string with variables using {key} placeholders'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const template = resolve(inputs.node.parameters.template || '', ctx); + const variables = inputs.node.parameters.variables || {}; + const result = template.replace(/\{(\w+)\}/g, (_: string, key: string) => + variables[key] !== undefined ? String(variables[key]) : `{${key}}` + ); + return { result }; + } +} + +export class StringLength implements NodeExecutor { + readonly nodeType = 'string.length'; + readonly category = 'string'; + readonly description = 'Get the length of a string'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + return { result: value.length }; + } +} + +export class StringLower implements NodeExecutor { + readonly nodeType = 'string.lower'; + readonly category = 'string'; + readonly description = 'Convert string to lowercase'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + return { result: value.toLowerCase() }; + } +} + +export class StringUpper implements NodeExecutor { + readonly nodeType = 'string.upper'; + readonly category = 'string'; + readonly description = 'Convert string to uppercase'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + return { result: value.toUpperCase() }; + } +} + +export class StringTrim implements NodeExecutor { + readonly nodeType = 'string.trim'; + readonly category = 'string'; + readonly description = 'Trim whitespace from string (both ends, start only, or end only)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const mode = inputs.node.parameters.mode || 'both'; + let result: string; + switch (mode) { + case 'start': result = value.trimStart(); break; + case 'end': result = value.trimEnd(); break; + default: result = value.trim(); + } + return { result }; + } +} + +export class StringReplace implements NodeExecutor { + readonly nodeType = 'string.replace'; + readonly category = 'string'; + readonly description = 'Replace substring in string (single or all occurrences)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const search = resolve(inputs.node.parameters.search || '', ctx); + const replacement = resolve(inputs.node.parameters.replacement ?? '', ctx); + const all = inputs.node.parameters.all ?? false; + const result = all + ? value.replaceAll(search, replacement) + : value.replace(search, replacement); + return { result }; + } +} + +export class StringSplit implements NodeExecutor { + readonly nodeType = 'string.split'; + readonly category = 'string'; + readonly description = 'Split string into array by separator'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const separator = resolve(inputs.node.parameters.separator ?? ',', ctx); + const limit = inputs.node.parameters.limit; + const result = limit ? value.split(separator, limit) : value.split(separator); + return { result }; + } +} + +export class StringJoin implements NodeExecutor { + readonly nodeType = 'string.join'; + readonly category = 'string'; + readonly description = 'Join array elements into string with separator'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const values = resolve(inputs.node.parameters.values || [], ctx); + const separator = resolve(inputs.node.parameters.separator ?? ',', ctx); + const result = Array.isArray(values) ? values.join(separator) : String(values); + return { result }; + } +} + +export class StringSubstring implements NodeExecutor { + readonly nodeType = 'string.substring'; + readonly category = 'string'; + readonly description = 'Extract substring from string by start and end index'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const startIdx = inputs.node.parameters.start ?? 0; + const endIdx = inputs.node.parameters.end; + const result = endIdx !== undefined ? value.substring(startIdx, endIdx) : value.substring(startIdx); + return { result }; + } +} + +export class StringIncludes implements NodeExecutor { + readonly nodeType = 'string.includes'; + readonly category = 'string'; + readonly description = 'Check if string contains a substring'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const search = String(resolve(inputs.node.parameters.search || '', ctx)); + return { result: value.includes(search) }; + } +} + +export class StringStartsWith implements NodeExecutor { + readonly nodeType = 'string.startsWith'; + readonly category = 'string'; + readonly description = 'Check if string starts with a prefix'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const prefix = String(resolve(inputs.node.parameters.prefix || '', ctx)); + return { result: value.startsWith(prefix) }; + } +} + +export class StringEndsWith implements NodeExecutor { + readonly nodeType = 'string.endsWith'; + readonly category = 'string'; + readonly description = 'Check if string ends with a suffix'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const suffix = String(resolve(inputs.node.parameters.suffix || '', ctx)); + return { result: value.endsWith(suffix) }; + } +} + +export class StringPadStart implements NodeExecutor { + readonly nodeType = 'string.padStart'; + readonly category = 'string'; + readonly description = 'Pad string at the start to reach target length'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const length = inputs.node.parameters.length || value.length; + const fillString = resolve(inputs.node.parameters.fillString ?? ' ', ctx); + return { result: value.padStart(length, fillString) }; + } +} + +export class StringPadEnd implements NodeExecutor { + readonly nodeType = 'string.padEnd'; + readonly category = 'string'; + readonly description = 'Pad string at the end to reach target length'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const value = String(resolve(inputs.node.parameters.value || '', ctx)); + const length = inputs.node.parameters.length || value.length; + const fillString = resolve(inputs.node.parameters.fillString ?? ' ', ctx); + return { result: value.padEnd(length, fillString) }; + } +} + +// Export all string plugin classes +export const stringPluginClasses = { + 'string.concat': StringConcat, + 'string.format': StringFormat, + 'string.length': StringLength, + 'string.lower': StringLower, + 'string.upper': StringUpper, + 'string.trim': StringTrim, + 'string.replace': StringReplace, + 'string.split': StringSplit, + 'string.join': StringJoin, + 'string.substring': StringSubstring, + 'string.includes': StringIncludes, + 'string.startsWith': StringStartsWith, + 'string.endsWith': StringEndsWith, + 'string.padStart': StringPadStart, + 'string.padEnd': StringPadEnd, +}; + +export default stringPluginClasses; diff --git a/workflow/plugins/ts/var/package.json b/workflow/plugins/ts/var/package.json new file mode 100644 index 000000000..1e2047dfb --- /dev/null +++ b/workflow/plugins/ts/var/package.json @@ -0,0 +1,31 @@ +{ + "name": "@metabuilder/plugin-var", + "version": "1.0.0", + "description": "Variable management workflow plugins for MetaBuilder", + "author": "MetaBuilder", + "license": "MIT", + "keywords": ["var", "variable", "workflow", "plugin", "state"], + "main": "src/index.ts", + "files": ["src/index.ts", "src/factory.ts"], + "metadata": { + "plugin_type": "var", + "category": "var", + "classes": [ + "VarGet", + "VarSet", + "VarSetMultiple", + "VarDelete", + "VarExists", + "VarIncrement", + "VarDecrement", + "VarToggle", + "VarAppend", + "VarConcat", + "VarList", + "VarGetAll", + "VarClear", + "VarMerge" + ], + "entrypoint": "execute" + } +} diff --git a/workflow/plugins/ts/var/src/factory.ts b/workflow/plugins/ts/var/src/factory.ts new file mode 100644 index 000000000..82e1ff7f1 --- /dev/null +++ b/workflow/plugins/ts/var/src/factory.ts @@ -0,0 +1,96 @@ +/** + * Factory for var plugin classes. + */ + +import { + VarGet, + VarSet, + VarSetMultiple, + VarDelete, + VarExists, + VarIncrement, + VarDecrement, + VarToggle, + VarAppend, + VarConcat, + VarList, + VarGetAll, + VarClear, + VarMerge, +} from './index'; + +export function createVarGet(): VarGet { + return new VarGet(); +} + +export function createVarSet(): VarSet { + return new VarSet(); +} + +export function createVarSetMultiple(): VarSetMultiple { + return new VarSetMultiple(); +} + +export function createVarDelete(): VarDelete { + return new VarDelete(); +} + +export function createVarExists(): VarExists { + return new VarExists(); +} + +export function createVarIncrement(): VarIncrement { + return new VarIncrement(); +} + +export function createVarDecrement(): VarDecrement { + return new VarDecrement(); +} + +export function createVarToggle(): VarToggle { + return new VarToggle(); +} + +export function createVarAppend(): VarAppend { + return new VarAppend(); +} + +export function createVarConcat(): VarConcat { + return new VarConcat(); +} + +export function createVarList(): VarList { + return new VarList(); +} + +export function createVarGetAll(): VarGetAll { + return new VarGetAll(); +} + +export function createVarClear(): VarClear { + return new VarClear(); +} + +export function createVarMerge(): VarMerge { + return new VarMerge(); +} + +// Factory map for all var plugins +export const factories = { + 'var.get': createVarGet, + 'var.set': createVarSet, + 'var.setMultiple': createVarSetMultiple, + 'var.delete': createVarDelete, + 'var.exists': createVarExists, + 'var.increment': createVarIncrement, + 'var.decrement': createVarDecrement, + 'var.toggle': createVarToggle, + 'var.append': createVarAppend, + 'var.concat': createVarConcat, + 'var.list': createVarList, + 'var.getAll': createVarGetAll, + 'var.clear': createVarClear, + 'var.merge': createVarMerge, +}; + +export default factories; diff --git a/workflow/plugins/ts/var/src/index.ts b/workflow/plugins/ts/var/src/index.ts new file mode 100644 index 000000000..34eef8a46 --- /dev/null +++ b/workflow/plugins/ts/var/src/index.ts @@ -0,0 +1,262 @@ +/** + * Workflow plugin: Variable management operations. + */ + +import { NodeExecutor, ExecuteInputs, ExecuteResult, createTemplateContext } from '../../base'; +import { interpolateTemplate } from '../../../executor/ts/utils/template-engine'; + +const resolve = (value: any, ctx: any): any => { + if (typeof value === 'string' && value.startsWith('{{')) { + return interpolateTemplate(value, ctx); + } + return value; +}; + +// Ensure state.variables exists +const ensureVariables = (state: Record): Record => { + if (!state.variables) state.variables = {}; + return state.variables; +}; + +export class VarGet implements NodeExecutor { + readonly nodeType = 'var.get'; + readonly category = 'var'; + readonly description = 'Get variable value by name'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const defaultValue = resolve(inputs.node.parameters.default, ctx); + const vars = ensureVariables(inputs.state); + const result = name in vars ? vars[name] : defaultValue; + return { result }; + } +} + +export class VarSet implements NodeExecutor { + readonly nodeType = 'var.set'; + readonly category = 'var'; + readonly description = 'Set variable value by name'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const value = resolve(inputs.node.parameters.value, ctx); + const vars = ensureVariables(inputs.state); + vars[name] = value; + return { result: value, name }; + } +} + +export class VarSetMultiple implements NodeExecutor { + readonly nodeType = 'var.setMultiple'; + readonly category = 'var'; + readonly description = 'Set multiple variables at once from an object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const variables = inputs.node.parameters.variables || {}; + const vars = ensureVariables(inputs.state); + const set: Record = {}; + for (const [name, value] of Object.entries(variables)) { + const resolvedValue = resolve(value, ctx); + vars[name] = resolvedValue; + set[name] = resolvedValue; + } + return { result: set, count: Object.keys(set).length }; + } +} + +export class VarDelete implements NodeExecutor { + readonly nodeType = 'var.delete'; + readonly category = 'var'; + readonly description = 'Delete variable by name'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const vars = ensureVariables(inputs.state); + const existed = name in vars; + const previousValue = vars[name]; + delete vars[name]; + return { result: existed, previousValue }; + } +} + +export class VarExists implements NodeExecutor { + readonly nodeType = 'var.exists'; + readonly category = 'var'; + readonly description = 'Check if variable exists'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const vars = ensureVariables(inputs.state); + return { result: name in vars }; + } +} + +export class VarIncrement implements NodeExecutor { + readonly nodeType = 'var.increment'; + readonly category = 'var'; + readonly description = 'Increment numeric variable by amount (default 1)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const amount = inputs.node.parameters.amount ?? 1; + const vars = ensureVariables(inputs.state); + const current = Number(vars[name] ?? 0); + const result = current + amount; + vars[name] = result; + return { result, previous: current }; + } +} + +export class VarDecrement implements NodeExecutor { + readonly nodeType = 'var.decrement'; + readonly category = 'var'; + readonly description = 'Decrement numeric variable by amount (default 1)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const amount = inputs.node.parameters.amount ?? 1; + const vars = ensureVariables(inputs.state); + const current = Number(vars[name] ?? 0); + const result = current - amount; + vars[name] = result; + return { result, previous: current }; + } +} + +export class VarToggle implements NodeExecutor { + readonly nodeType = 'var.toggle'; + readonly category = 'var'; + readonly description = 'Toggle boolean variable'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const vars = ensureVariables(inputs.state); + const current = Boolean(vars[name]); + const result = !current; + vars[name] = result; + return { result, previous: current }; + } +} + +export class VarAppend implements NodeExecutor { + readonly nodeType = 'var.append'; + readonly category = 'var'; + readonly description = 'Append value to array variable'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const value = resolve(inputs.node.parameters.value, ctx); + const vars = ensureVariables(inputs.state); + if (!Array.isArray(vars[name])) vars[name] = []; + vars[name].push(value); + return { result: vars[name], length: vars[name].length }; + } +} + +export class VarConcat implements NodeExecutor { + readonly nodeType = 'var.concat'; + readonly category = 'var'; + readonly description = 'Concatenate value to string variable with optional separator'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const value = resolve(inputs.node.parameters.value, ctx); + const separator = resolve(inputs.node.parameters.separator ?? '', ctx); + const vars = ensureVariables(inputs.state); + const current = String(vars[name] ?? ''); + const result = current ? `${current}${separator}${value}` : String(value); + vars[name] = result; + return { result }; + } +} + +export class VarList implements NodeExecutor { + readonly nodeType = 'var.list'; + readonly category = 'var'; + readonly description = 'List all variable names'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const vars = ensureVariables(inputs.state); + return { + result: Object.keys(vars), + count: Object.keys(vars).length, + }; + } +} + +export class VarGetAll implements NodeExecutor { + readonly nodeType = 'var.getAll'; + readonly category = 'var'; + readonly description = 'Get all variables as object'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const vars = ensureVariables(inputs.state); + return { result: { ...vars } }; + } +} + +export class VarClear implements NodeExecutor { + readonly nodeType = 'var.clear'; + readonly category = 'var'; + readonly description = 'Clear all variables'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const vars = ensureVariables(inputs.state); + const count = Object.keys(vars).length; + const previous = { ...vars }; + for (const key of Object.keys(vars)) { + delete vars[key]; + } + return { result: count, previous }; + } +} + +export class VarMerge implements NodeExecutor { + readonly nodeType = 'var.merge'; + readonly category = 'var'; + readonly description = 'Merge object into variable (shallow merge for objects)'; + + execute(inputs: ExecuteInputs): ExecuteResult { + const ctx = createTemplateContext(inputs); + const name = resolve(inputs.node.parameters.name, ctx); + const value = resolve(inputs.node.parameters.value, ctx); + const vars = ensureVariables(inputs.state); + const current = vars[name]; + if (typeof current === 'object' && typeof value === 'object' && !Array.isArray(current) && !Array.isArray(value)) { + vars[name] = { ...current, ...value }; + } else { + vars[name] = value; + } + return { result: vars[name] }; + } +} + +// Export all var plugin classes +export const varPluginClasses = { + 'var.get': VarGet, + 'var.set': VarSet, + 'var.setMultiple': VarSetMultiple, + 'var.delete': VarDelete, + 'var.exists': VarExists, + 'var.increment': VarIncrement, + 'var.decrement': VarDecrement, + 'var.toggle': VarToggle, + 'var.append': VarAppend, + 'var.concat': VarConcat, + 'var.list': VarList, + 'var.getAll': VarGetAll, + 'var.clear': VarClear, + 'var.merge': VarMerge, +}; + +export default varPluginClasses;