mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
feat: Add Python plugins from AutoMetabuilder + restructure workflow folder
Restructure workflow/ for multi-language plugin support:
- Rename src/ to core/ (engine code: DAG executor, registry, types)
- Create executor/{cpp,python,ts}/ for language-specific runtimes
- Consolidate plugins to plugins/{ts,python}/ by language then category
Add 80+ Python plugins from AutoMetabuilder in 14 categories:
- control: bot control, switch logic, state management
- convert: type conversions (json, boolean, dict, list, number, string)
- core: AI requests, context management, tool calls
- dict: dictionary operations (get, set, keys, values, merge)
- list: list operations (concat, find, sort, slice, filter)
- logic: boolean logic (and, or, xor, equals, comparisons)
- math: arithmetic operations (add, subtract, multiply, power, etc.)
- string: string manipulation (concat, split, replace, format)
- notifications: Slack, Discord integrations
- test: assertion helpers and test suite runner
- tools: file operations, git, docker, testing utilities
- utils: filtering, mapping, reducing, condition branching
- var: variable store operations (get, set, delete, exists)
- web: Flask server, environment variables, JSON handling
Add language executor runtimes:
- TypeScript: direct import execution (default, fast startup)
- Python: child process with JSON stdin/stdout communication
- C++: placeholder for native FFI bindings (Phase 3)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
21
workflow/plugins/python/__init__.py
Normal file
21
workflow/plugins/python/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""MetaBuilder Workflow Python Plugins.
|
||||
|
||||
This package contains Python workflow plugins organized by category.
|
||||
Each plugin follows the standard interface: run(runtime, inputs) -> dict
|
||||
|
||||
Categories:
|
||||
- control: Bot control and switch logic
|
||||
- convert: Type conversion utilities
|
||||
- core: Core AI/workflow operations
|
||||
- dict: Dictionary manipulation
|
||||
- list: List operations
|
||||
- logic: Boolean logic operations
|
||||
- math: Mathematical operations
|
||||
- notifications: Slack/Discord notifications
|
||||
- string: String manipulation
|
||||
- test: Unit testing assertions
|
||||
- tools: External tool integration
|
||||
- utils: Utility functions
|
||||
- var: Variable management
|
||||
- web: Web/Flask operations
|
||||
"""
|
||||
1
workflow/plugins/python/control/__init__.py
Normal file
1
workflow/plugins/python/control/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Control flow plugins: bot control and switch logic."""
|
||||
39
workflow/plugins/python/control/control_get_bot_status.py
Normal file
39
workflow/plugins/python/control/control_get_bot_status.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""Workflow plugin: get current bot execution status."""
|
||||
|
||||
# Global state for bot process
|
||||
_bot_process = None
|
||||
_mock_running = False
|
||||
_current_run_config = {}
|
||||
|
||||
|
||||
def get_bot_state():
|
||||
"""Get the current bot state (public interface).
|
||||
|
||||
Returns:
|
||||
dict: Bot state with keys: is_running, config, process
|
||||
"""
|
||||
return {
|
||||
"is_running": _bot_process is not None or _mock_running,
|
||||
"config": _current_run_config,
|
||||
"process": _bot_process,
|
||||
}
|
||||
|
||||
|
||||
def reset_bot_state():
|
||||
"""Reset the bot state (public interface)."""
|
||||
global _bot_process, _current_run_config, _mock_running
|
||||
_bot_process = None
|
||||
_current_run_config = {}
|
||||
_mock_running = False
|
||||
|
||||
|
||||
def run(_runtime, _inputs):
|
||||
"""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()
|
||||
13
workflow/plugins/python/control/control_reset_bot_state.py
Normal file
13
workflow/plugins/python/control/control_reset_bot_state.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: reset bot execution state."""
|
||||
from .control_get_bot_status import reset_bot_state
|
||||
|
||||
|
||||
def run(_runtime, _inputs):
|
||||
"""Reset bot execution state.
|
||||
|
||||
Returns:
|
||||
Dictionary with:
|
||||
- reset: bool - Always True to indicate state was reset
|
||||
"""
|
||||
reset_bot_state()
|
||||
return {"reset": True}
|
||||
97
workflow/plugins/python/control/control_start_bot.py
Normal file
97
workflow/plugins/python/control/control_start_bot.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Workflow plugin: start bot execution in background thread."""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from .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
|
||||
|
||||
|
||||
def _run_bot_task(mode: str, iterations: int, yolo: bool, stop_at_mvp: bool) -> None:
|
||||
"""Execute bot task in background thread."""
|
||||
bot_status._current_run_config = {
|
||||
"mode": mode,
|
||||
"iterations": iterations,
|
||||
"yolo": yolo,
|
||||
"stop_at_mvp": stop_at_mvp,
|
||||
}
|
||||
|
||||
if os.environ.get("MOCK_WEB_UI") == "true":
|
||||
bot_status._mock_running = True
|
||||
time.sleep(5)
|
||||
bot_status._mock_running = False
|
||||
reset_bot_state()
|
||||
return
|
||||
|
||||
try:
|
||||
cmd = [sys.executable, "-m", "autometabuilder.main"]
|
||||
if yolo:
|
||||
cmd.append("--yolo")
|
||||
if mode == "once":
|
||||
cmd.append("--once")
|
||||
if mode == "iterations" and iterations > 1:
|
||||
for _ in range(iterations):
|
||||
if stop_at_mvp:
|
||||
# Check MVP status
|
||||
pass
|
||||
bot_status._bot_process = subprocess.Popen(
|
||||
cmd + ["--once"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT
|
||||
)
|
||||
bot_status._bot_process.wait()
|
||||
else:
|
||||
bot_status._bot_process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT
|
||||
)
|
||||
bot_status._bot_process.wait()
|
||||
finally:
|
||||
reset_bot_state()
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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
|
||||
|
||||
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)
|
||||
|
||||
# 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}
|
||||
11
workflow/plugins/python/control/control_switch.py
Normal file
11
workflow/plugins/python/control/control_switch.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: switch/case control flow."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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}
|
||||
1
workflow/plugins/python/convert/__init__.py
Normal file
1
workflow/plugins/python/convert/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Type conversion plugins."""
|
||||
13
workflow/plugins/python/convert/convert_parse_json.py
Normal file
13
workflow/plugins/python/convert/convert_parse_json.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: parse JSON string."""
|
||||
import json
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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)}
|
||||
11
workflow/plugins/python/convert/convert_to_boolean.py
Normal file
11
workflow/plugins/python/convert/convert_to_boolean.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: convert to boolean."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Convert value to boolean."""
|
||||
value = inputs.get("value")
|
||||
|
||||
if isinstance(value, str):
|
||||
return {"result": value.lower() not in ("false", "0", "", "none", "null")}
|
||||
|
||||
return {"result": bool(value)}
|
||||
17
workflow/plugins/python/convert/convert_to_dict.py
Normal file
17
workflow/plugins/python/convert/convert_to_dict.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Workflow plugin: convert to dictionary."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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": {}}
|
||||
14
workflow/plugins/python/convert/convert_to_json.py
Normal file
14
workflow/plugins/python/convert/convert_to_json.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Workflow plugin: convert to JSON string."""
|
||||
import json
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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)}
|
||||
17
workflow/plugins/python/convert/convert_to_list.py
Normal file
17
workflow/plugins/python/convert/convert_to_list.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Workflow plugin: convert to list."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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]}
|
||||
14
workflow/plugins/python/convert/convert_to_number.py
Normal file
14
workflow/plugins/python/convert/convert_to_number.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Workflow plugin: convert to number."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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"}
|
||||
7
workflow/plugins/python/convert/convert_to_string.py
Normal file
7
workflow/plugins/python/convert/convert_to_string.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: convert to string."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Convert value to string."""
|
||||
value = inputs.get("value")
|
||||
return {"result": str(value) if value is not None else ""}
|
||||
1
workflow/plugins/python/core/__init__.py
Normal file
1
workflow/plugins/python/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Core AI/workflow plugins: AI requests, message handling, context loading."""
|
||||
39
workflow/plugins/python/core/core_ai_request.py
Normal file
39
workflow/plugins/python/core/core_ai_request.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""Workflow plugin: AI request."""
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
|
||||
def _get_completion(client, model, messages, tools):
|
||||
"""Request a chat completion with retries."""
|
||||
return client.chat.completions.create(
|
||||
model=model,
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
temperature=1.0,
|
||||
top_p=1.0,
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
13
workflow/plugins/python/core/core_append_context_message.py
Normal file
13
workflow/plugins/python/core/core_append_context_message.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: append context message."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""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}
|
||||
44
workflow/plugins/python/core/core_append_tool_results.py
Normal file
44
workflow/plugins/python/core/core_append_tool_results.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Workflow plugin: append tool results."""
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def _is_mvp_reached() -> 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
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""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}
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: append user instruction."""
|
||||
|
||||
|
||||
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}
|
||||
43
workflow/plugins/python/core/core_load_context.py
Normal file
43
workflow/plugins/python/core/core_load_context.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Workflow plugin: load SDLC context."""
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("metabuilder")
|
||||
|
||||
|
||||
def run(runtime, _inputs):
|
||||
"""Load SDLC context into the workflow store."""
|
||||
gh = runtime.context.get("gh")
|
||||
msgs = runtime.context.get("msgs", {})
|
||||
|
||||
sdlc_context = ""
|
||||
|
||||
# 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"
|
||||
|
||||
# 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}
|
||||
37
workflow/plugins/python/core/core_run_tool_calls.py
Normal file
37
workflow/plugins/python/core/core_run_tool_calls.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Workflow plugin: run tool calls."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""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", {})
|
||||
|
||||
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)}"
|
||||
})
|
||||
|
||||
return {
|
||||
"tool_results": tool_results,
|
||||
"no_tool_calls": not bool(tool_calls)
|
||||
}
|
||||
7
workflow/plugins/python/core/core_seed_messages.py
Normal file
7
workflow/plugins/python/core/core_seed_messages.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: seed messages."""
|
||||
|
||||
|
||||
def run(runtime, _inputs):
|
||||
"""Seed messages from the prompt."""
|
||||
prompt = runtime.context["prompt"]
|
||||
return {"messages": list(prompt["messages"])}
|
||||
1
workflow/plugins/python/dict/__init__.py
Normal file
1
workflow/plugins/python/dict/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Dictionary manipulation plugins."""
|
||||
14
workflow/plugins/python/dict/dict_get.py
Normal file
14
workflow/plugins/python/dict/dict_get.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Workflow plugin: get value from dictionary."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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}
|
||||
|
||||
result = obj.get(key, default)
|
||||
return {"result": result, "found": key in obj}
|
||||
11
workflow/plugins/python/dict/dict_items.py
Normal file
11
workflow/plugins/python/dict/dict_items.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: get dictionary items as key-value pairs."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Get dictionary items as list of [key, value] pairs."""
|
||||
obj = inputs.get("object", {})
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return {"result": []}
|
||||
|
||||
return {"result": [[k, v] for k, v in obj.items()]}
|
||||
11
workflow/plugins/python/dict/dict_keys.py
Normal file
11
workflow/plugins/python/dict/dict_keys.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: get dictionary keys."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Get all keys from dictionary."""
|
||||
obj = inputs.get("object", {})
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return {"result": []}
|
||||
|
||||
return {"result": list(obj.keys())}
|
||||
13
workflow/plugins/python/dict/dict_merge.py
Normal file
13
workflow/plugins/python/dict/dict_merge.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: merge dictionaries."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Merge multiple dictionaries."""
|
||||
objects = inputs.get("objects", [])
|
||||
result = {}
|
||||
|
||||
for obj in objects:
|
||||
if isinstance(obj, dict):
|
||||
result.update(obj)
|
||||
|
||||
return {"result": result}
|
||||
15
workflow/plugins/python/dict/dict_set.py
Normal file
15
workflow/plugins/python/dict/dict_set.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Workflow plugin: set value in dictionary."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Set value in dictionary by key."""
|
||||
obj = inputs.get("object", {})
|
||||
key = inputs.get("key")
|
||||
value = inputs.get("value")
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
obj = {}
|
||||
|
||||
result = dict(obj)
|
||||
result[key] = value
|
||||
return {"result": result}
|
||||
11
workflow/plugins/python/dict/dict_values.py
Normal file
11
workflow/plugins/python/dict/dict_values.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: get dictionary values."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Get all values from dictionary."""
|
||||
obj = inputs.get("object", {})
|
||||
|
||||
if not isinstance(obj, dict):
|
||||
return {"result": []}
|
||||
|
||||
return {"result": list(obj.values())}
|
||||
1
workflow/plugins/python/list/__init__.py
Normal file
1
workflow/plugins/python/list/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""List manipulation plugins."""
|
||||
11
workflow/plugins/python/list/list_concat.py
Normal file
11
workflow/plugins/python/list/list_concat.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: concatenate lists."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Concatenate multiple lists."""
|
||||
lists = inputs.get("lists", [])
|
||||
result = []
|
||||
for lst in lists:
|
||||
if isinstance(lst, list):
|
||||
result.extend(lst)
|
||||
return {"result": result}
|
||||
18
workflow/plugins/python/list/list_every.py
Normal file
18
workflow/plugins/python/list/list_every.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Workflow plugin: check if all items match condition."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Check if all items match condition."""
|
||||
items = inputs.get("items", [])
|
||||
key = inputs.get("key")
|
||||
value = inputs.get("value")
|
||||
|
||||
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}
|
||||
14
workflow/plugins/python/list/list_find.py
Normal file
14
workflow/plugins/python/list/list_find.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Workflow plugin: find item in list."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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}
|
||||
|
||||
return {"result": None, "found": False}
|
||||
7
workflow/plugins/python/list/list_length.py
Normal file
7
workflow/plugins/python/list/list_length.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: get list length."""
|
||||
|
||||
|
||||
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}
|
||||
15
workflow/plugins/python/list/list_slice.py
Normal file
15
workflow/plugins/python/list/list_slice.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Workflow plugin: slice a list."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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]
|
||||
|
||||
return {"result": result}
|
||||
15
workflow/plugins/python/list/list_some.py
Normal file
15
workflow/plugins/python/list/list_some.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Workflow plugin: check if some items match condition."""
|
||||
|
||||
|
||||
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)
|
||||
|
||||
return {"result": result}
|
||||
17
workflow/plugins/python/list/list_sort.py
Normal file
17
workflow/plugins/python/list/list_sort.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Workflow plugin: sort a list."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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"}
|
||||
1
workflow/plugins/python/logic/__init__.py
Normal file
1
workflow/plugins/python/logic/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Boolean logic plugins."""
|
||||
7
workflow/plugins/python/logic/logic_and.py
Normal file
7
workflow/plugins/python/logic/logic_and.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: logical AND."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Perform logical AND on values."""
|
||||
values = inputs.get("values", [])
|
||||
return {"result": all(values)}
|
||||
8
workflow/plugins/python/logic/logic_equals.py
Normal file
8
workflow/plugins/python/logic/logic_equals.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: equality comparison."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Check if two values are equal."""
|
||||
a = inputs.get("a")
|
||||
b = inputs.get("b")
|
||||
return {"result": a == b}
|
||||
8
workflow/plugins/python/logic/logic_gt.py
Normal file
8
workflow/plugins/python/logic/logic_gt.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: greater than comparison."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Check if a > b."""
|
||||
a = inputs.get("a")
|
||||
b = inputs.get("b")
|
||||
return {"result": a > b}
|
||||
8
workflow/plugins/python/logic/logic_gte.py
Normal file
8
workflow/plugins/python/logic/logic_gte.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: greater than or equal comparison."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Check if a >= b."""
|
||||
a = inputs.get("a")
|
||||
b = inputs.get("b")
|
||||
return {"result": a >= b}
|
||||
8
workflow/plugins/python/logic/logic_in.py
Normal file
8
workflow/plugins/python/logic/logic_in.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: membership test."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Check if value is in collection."""
|
||||
value = inputs.get("value")
|
||||
collection = inputs.get("collection", [])
|
||||
return {"result": value in collection}
|
||||
8
workflow/plugins/python/logic/logic_lt.py
Normal file
8
workflow/plugins/python/logic/logic_lt.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: less than comparison."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Check if a < b."""
|
||||
a = inputs.get("a")
|
||||
b = inputs.get("b")
|
||||
return {"result": a < b}
|
||||
8
workflow/plugins/python/logic/logic_lte.py
Normal file
8
workflow/plugins/python/logic/logic_lte.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: less than or equal comparison."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Check if a <= b."""
|
||||
a = inputs.get("a")
|
||||
b = inputs.get("b")
|
||||
return {"result": a <= b}
|
||||
7
workflow/plugins/python/logic/logic_or.py
Normal file
7
workflow/plugins/python/logic/logic_or.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: logical OR."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Perform logical OR on values."""
|
||||
values = inputs.get("values", [])
|
||||
return {"result": any(values)}
|
||||
8
workflow/plugins/python/logic/logic_xor.py
Normal file
8
workflow/plugins/python/logic/logic_xor.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: logical XOR."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Perform logical XOR on two values."""
|
||||
a = inputs.get("a", False)
|
||||
b = inputs.get("b", False)
|
||||
return {"result": bool(a) != bool(b)}
|
||||
1
workflow/plugins/python/math/__init__.py
Normal file
1
workflow/plugins/python/math/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Mathematical operations plugins."""
|
||||
7
workflow/plugins/python/math/math_abs.py
Normal file
7
workflow/plugins/python/math/math_abs.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: absolute value."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Calculate absolute value."""
|
||||
value = inputs.get("value", 0)
|
||||
return {"result": abs(value)}
|
||||
7
workflow/plugins/python/math/math_add.py
Normal file
7
workflow/plugins/python/math/math_add.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: add numbers."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Add two or more numbers."""
|
||||
numbers = inputs.get("numbers", [])
|
||||
return {"result": sum(numbers)}
|
||||
12
workflow/plugins/python/math/math_divide.py
Normal file
12
workflow/plugins/python/math/math_divide.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Workflow plugin: divide numbers."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Divide a by b."""
|
||||
a = inputs.get("a", 0)
|
||||
b = inputs.get("b", 1)
|
||||
|
||||
if b == 0:
|
||||
return {"result": None, "error": "Division by zero"}
|
||||
|
||||
return {"result": a / b}
|
||||
11
workflow/plugins/python/math/math_max.py
Normal file
11
workflow/plugins/python/math/math_max.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: maximum value."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Find maximum value in numbers."""
|
||||
numbers = inputs.get("numbers", [])
|
||||
|
||||
if not numbers:
|
||||
return {"result": None}
|
||||
|
||||
return {"result": max(numbers)}
|
||||
11
workflow/plugins/python/math/math_min.py
Normal file
11
workflow/plugins/python/math/math_min.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: minimum value."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Find minimum value in numbers."""
|
||||
numbers = inputs.get("numbers", [])
|
||||
|
||||
if not numbers:
|
||||
return {"result": None}
|
||||
|
||||
return {"result": min(numbers)}
|
||||
12
workflow/plugins/python/math/math_modulo.py
Normal file
12
workflow/plugins/python/math/math_modulo.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Workflow plugin: modulo operation."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Calculate a modulo b."""
|
||||
a = inputs.get("a", 0)
|
||||
b = inputs.get("b", 1)
|
||||
|
||||
if b == 0:
|
||||
return {"result": None, "error": "Modulo by zero"}
|
||||
|
||||
return {"result": a % b}
|
||||
10
workflow/plugins/python/math/math_multiply.py
Normal file
10
workflow/plugins/python/math/math_multiply.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Workflow plugin: multiply numbers."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Multiply two or more numbers."""
|
||||
numbers = inputs.get("numbers", [])
|
||||
result = 1
|
||||
for num in numbers:
|
||||
result *= num
|
||||
return {"result": result}
|
||||
8
workflow/plugins/python/math/math_power.py
Normal file
8
workflow/plugins/python/math/math_power.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: power operation."""
|
||||
|
||||
|
||||
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}
|
||||
8
workflow/plugins/python/math/math_round.py
Normal file
8
workflow/plugins/python/math/math_round.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: round number."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Round number to specified precision."""
|
||||
value = inputs.get("value", 0)
|
||||
precision = inputs.get("precision", 0)
|
||||
return {"result": round(value, precision)}
|
||||
8
workflow/plugins/python/math/math_subtract.py
Normal file
8
workflow/plugins/python/math/math_subtract.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Workflow plugin: subtract numbers."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Subtract b from a."""
|
||||
a = inputs.get("a", 0)
|
||||
b = inputs.get("b", 0)
|
||||
return {"result": a - b}
|
||||
1
workflow/plugins/python/notifications/__init__.py
Normal file
1
workflow/plugins/python/notifications/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Notification plugins: Slack, Discord, and multi-channel notifications."""
|
||||
33
workflow/plugins/python/notifications/notifications_all.py
Normal file
33
workflow/plugins/python/notifications/notifications_all.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Workflow plugin: send notification to all channels."""
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("metabuilder.notifications")
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Send a notification to all configured channels (Slack and Discord).
|
||||
|
||||
Inputs:
|
||||
message: The message to send to all channels
|
||||
|
||||
Returns:
|
||||
dict: Contains success status for all channels
|
||||
"""
|
||||
message = inputs.get("message", "")
|
||||
|
||||
# Import sibling plugins
|
||||
from . import notifications_slack, notifications_discord
|
||||
|
||||
# Send to Slack
|
||||
slack_result = notifications_slack.run(runtime, {"message": message})
|
||||
|
||||
# Send to Discord
|
||||
discord_result = notifications_discord.run(runtime, {"message": message})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Notifications sent to all channels",
|
||||
"slack": slack_result,
|
||||
"discord": discord_result
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Workflow plugin: send Discord notification."""
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger("metabuilder.notifications")
|
||||
|
||||
|
||||
async def _send_discord_notification_async(message: str, token: str, intents, channel_id: str):
|
||||
"""Send Discord notification asynchronously."""
|
||||
import discord
|
||||
|
||||
client = discord.Client(intents=intents)
|
||||
|
||||
@client.event
|
||||
async def on_ready():
|
||||
channel = client.get_channel(int(channel_id))
|
||||
if channel:
|
||||
await channel.send(message)
|
||||
logger.info("Discord notification sent successfully.")
|
||||
await client.close()
|
||||
|
||||
try:
|
||||
await client.start(token)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending Discord notification: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Send a notification to Discord.
|
||||
|
||||
Inputs:
|
||||
message: The message to send
|
||||
channel_id: Optional channel ID (defaults to DISCORD_CHANNEL_ID env var)
|
||||
|
||||
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")
|
||||
|
||||
token = runtime.context.get("discord_token")
|
||||
intents = runtime.context.get("discord_intents")
|
||||
|
||||
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)}
|
||||
46
workflow/plugins/python/notifications/notifications_slack.py
Normal file
46
workflow/plugins/python/notifications/notifications_slack.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Workflow plugin: send Slack notification."""
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("metabuilder.notifications")
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Send a notification to Slack.
|
||||
|
||||
Inputs:
|
||||
message: The message to send
|
||||
channel: Optional channel (defaults to SLACK_CHANNEL env var)
|
||||
|
||||
Returns:
|
||||
dict: Contains success status and any error message
|
||||
"""
|
||||
message = inputs.get("message", "")
|
||||
channel = inputs.get("channel") or os.environ.get("SLACK_CHANNEL")
|
||||
|
||||
client = runtime.context.get("slack_client")
|
||||
|
||||
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)}
|
||||
1
workflow/plugins/python/string/__init__.py
Normal file
1
workflow/plugins/python/string/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""String manipulation plugins."""
|
||||
10
workflow/plugins/python/string/string_concat.py
Normal file
10
workflow/plugins/python/string/string_concat.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Workflow plugin: concatenate strings."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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)}
|
||||
13
workflow/plugins/python/string/string_format.py
Normal file
13
workflow/plugins/python/string/string_format.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: format string with variables."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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)}
|
||||
7
workflow/plugins/python/string/string_length.py
Normal file
7
workflow/plugins/python/string/string_length.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: get string length."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Get length of a string."""
|
||||
text = inputs.get("text", "")
|
||||
return {"result": len(text)}
|
||||
7
workflow/plugins/python/string/string_lower.py
Normal file
7
workflow/plugins/python/string/string_lower.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: convert string to lowercase."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Convert string to lowercase."""
|
||||
text = inputs.get("text", "")
|
||||
return {"result": text.lower()}
|
||||
12
workflow/plugins/python/string/string_replace.py
Normal file
12
workflow/plugins/python/string/string_replace.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Workflow plugin: replace in string."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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}
|
||||
15
workflow/plugins/python/string/string_split.py
Normal file
15
workflow/plugins/python/string/string_split.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Workflow plugin: split string."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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)
|
||||
|
||||
return {"result": result}
|
||||
16
workflow/plugins/python/string/string_trim.py
Normal file
16
workflow/plugins/python/string/string_trim.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Workflow plugin: trim whitespace from string."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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()
|
||||
|
||||
return {"result": result}
|
||||
7
workflow/plugins/python/string/string_upper.py
Normal file
7
workflow/plugins/python/string/string_upper.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: convert string to uppercase."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Convert string to uppercase."""
|
||||
text = inputs.get("text", "")
|
||||
return {"result": text.upper()}
|
||||
1
workflow/plugins/python/test/__init__.py
Normal file
1
workflow/plugins/python/test/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Unit testing assertion plugins."""
|
||||
26
workflow/plugins/python/test/test_assert_equals.py
Normal file
26
workflow/plugins/python/test/test_assert_equals.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""Workflow plugin: assert two values are equal."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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
|
||||
}
|
||||
|
||||
return {
|
||||
"passed": True,
|
||||
"expected": expected,
|
||||
"actual": actual
|
||||
}
|
||||
23
workflow/plugins/python/test/test_assert_exists.py
Normal file
23
workflow/plugins/python/test/test_assert_exists.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Workflow plugin: assert value exists (is not None/null)."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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
|
||||
}
|
||||
|
||||
return {
|
||||
"passed": True,
|
||||
"value": value
|
||||
}
|
||||
23
workflow/plugins/python/test/test_assert_false.py
Normal file
23
workflow/plugins/python/test/test_assert_false.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Workflow plugin: assert value is false."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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
|
||||
}
|
||||
|
||||
return {
|
||||
"passed": True,
|
||||
"value": value
|
||||
}
|
||||
23
workflow/plugins/python/test/test_assert_true.py
Normal file
23
workflow/plugins/python/test/test_assert_true.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Workflow plugin: assert value is true."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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
|
||||
}
|
||||
|
||||
return {
|
||||
"passed": True,
|
||||
"value": value
|
||||
}
|
||||
63
workflow/plugins/python/test/test_run_suite.py
Normal file
63
workflow/plugins/python/test/test_run_suite.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Workflow plugin: run a suite of test assertions and report results."""
|
||||
|
||||
|
||||
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
|
||||
|
||||
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": []
|
||||
}
|
||||
|
||||
total = len(results)
|
||||
passed_count = 0
|
||||
failed_count = 0
|
||||
failures = []
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
1
workflow/plugins/python/tools/__init__.py
Normal file
1
workflow/plugins/python/tools/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""External tool integration plugins."""
|
||||
11
workflow/plugins/python/tools/tools_create_branch.py
Normal file
11
workflow/plugins/python/tools/tools_create_branch.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Workflow plugin: create branch."""
|
||||
|
||||
|
||||
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}
|
||||
13
workflow/plugins/python/tools/tools_create_pull_request.py
Normal file
13
workflow/plugins/python/tools/tools_create_pull_request.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: create pull request."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""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}
|
||||
7
workflow/plugins/python/tools/tools_list_files.py
Normal file
7
workflow/plugins/python/tools/tools_list_files.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: list files."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""List files via tool runner."""
|
||||
result = runtime.tool_runner.call("list_files", directory=inputs.get("path", "."))
|
||||
return {"files": result}
|
||||
7
workflow/plugins/python/tools/tools_read_file.py
Normal file
7
workflow/plugins/python/tools/tools_read_file.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: read file."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Read a file via tool runner."""
|
||||
result = runtime.tool_runner.call("read_file", path=inputs.get("path"))
|
||||
return {"content": result}
|
||||
59
workflow/plugins/python/tools/tools_run_docker.py
Normal file
59
workflow/plugins/python/tools/tools_run_docker.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Workflow plugin: run command in Docker container."""
|
||||
import subprocess
|
||||
import os
|
||||
import logging
|
||||
|
||||
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.
|
||||
|
||||
: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}"])
|
||||
|
||||
if workdir:
|
||||
docker_command.extend(["-w", workdir])
|
||||
|
||||
docker_command.append(image)
|
||||
docker_command.extend(["sh", "-c", command])
|
||||
|
||||
logger.info(f"Executing in Docker ({image}): {command}")
|
||||
result = subprocess.run(docker_command, capture_output=True, text=True, check=False)
|
||||
|
||||
output = result.stdout
|
||||
if result.stderr:
|
||||
output += "\n" + result.stderr
|
||||
|
||||
logger.info(output)
|
||||
return output
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Run a command inside a Docker container.
|
||||
|
||||
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")
|
||||
|
||||
if not image or not command:
|
||||
return {"error": "Both 'image' and 'command' are required"}
|
||||
|
||||
output = _run_command_in_docker(image, command, volumes, workdir)
|
||||
return {"output": output}
|
||||
7
workflow/plugins/python/tools/tools_run_lint.py
Normal file
7
workflow/plugins/python/tools/tools_run_lint.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: run lint."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Run lint via tool runner."""
|
||||
result = runtime.tool_runner.call("run_lint", path=inputs.get("path", "src"))
|
||||
return {"results": result}
|
||||
7
workflow/plugins/python/tools/tools_run_tests.py
Normal file
7
workflow/plugins/python/tools/tools_run_tests.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: run tests."""
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Run tests via tool runner."""
|
||||
result = runtime.tool_runner.call("run_tests", path=inputs.get("path", "tests"))
|
||||
return {"results": result}
|
||||
1
workflow/plugins/python/utils/__init__.py
Normal file
1
workflow/plugins/python/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Utility plugins: filtering, mapping, branching, MVP checking."""
|
||||
25
workflow/plugins/python/utils/utils_branch_condition.py
Normal file
25
workflow/plugins/python/utils/utils_branch_condition.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Workflow plugin: branch condition."""
|
||||
import re
|
||||
|
||||
|
||||
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)))
|
||||
|
||||
return {"result": decision}
|
||||
37
workflow/plugins/python/utils/utils_check_mvp.py
Normal file
37
workflow/plugins/python/utils/utils_check_mvp.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Workflow plugin: check if MVP is reached."""
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def _is_mvp_reached() -> 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
|
||||
|
||||
|
||||
def run(_runtime, _inputs):
|
||||
"""Check if the MVP section in ROADMAP.md is completed."""
|
||||
mvp_reached = _is_mvp_reached()
|
||||
return {"mvp_reached": mvp_reached}
|
||||
33
workflow/plugins/python/utils/utils_filter_list.py
Normal file
33
workflow/plugins/python/utils/utils_filter_list.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Workflow plugin: filter list."""
|
||||
import re
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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 = []
|
||||
|
||||
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}
|
||||
19
workflow/plugins/python/utils/utils_map_list.py
Normal file
19
workflow/plugins/python/utils/utils_map_list.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Workflow plugin: map list."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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 = []
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
mapped.append(template.format(item=item))
|
||||
except Exception:
|
||||
mapped.append(str(item))
|
||||
|
||||
return {"items": mapped}
|
||||
7
workflow/plugins/python/utils/utils_not.py
Normal file
7
workflow/plugins/python/utils/utils_not.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Workflow plugin: boolean not."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""Negate a boolean value."""
|
||||
value = inputs.get("value")
|
||||
return {"result": not bool(value)}
|
||||
18
workflow/plugins/python/utils/utils_reduce_list.py
Normal file
18
workflow/plugins/python/utils/utils_reduce_list.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Workflow plugin: reduce list."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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}
|
||||
21
workflow/plugins/python/utils/utils_update_roadmap.py
Normal file
21
workflow/plugins/python/utils/utils_update_roadmap.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Workflow plugin: update roadmap file."""
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("metabuilder")
|
||||
|
||||
|
||||
def _update_roadmap(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.")
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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"}
|
||||
1
workflow/plugins/python/var/__init__.py
Normal file
1
workflow/plugins/python/var/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Variable management plugins for workflow store."""
|
||||
15
workflow/plugins/python/var/var_delete.py
Normal file
15
workflow/plugins/python/var/var_delete.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Workflow plugin: delete variable from workflow store."""
|
||||
|
||||
|
||||
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"}
|
||||
|
||||
if key in runtime.store:
|
||||
del runtime.store[key]
|
||||
return {"result": True, "deleted": True}
|
||||
|
||||
return {"result": False, "deleted": False}
|
||||
13
workflow/plugins/python/var/var_exists.py
Normal file
13
workflow/plugins/python/var/var_exists.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Workflow plugin: check if variable exists in workflow store."""
|
||||
|
||||
|
||||
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"}
|
||||
|
||||
exists = key in runtime.store
|
||||
|
||||
return {"result": exists}
|
||||
15
workflow/plugins/python/var/var_get.py
Normal file
15
workflow/plugins/python/var/var_get.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Workflow plugin: get variable from workflow store."""
|
||||
|
||||
|
||||
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"}
|
||||
|
||||
exists = key in runtime.store
|
||||
value = runtime.store.get(key, default)
|
||||
|
||||
return {"result": value, "exists": exists}
|
||||
14
workflow/plugins/python/var/var_set.py
Normal file
14
workflow/plugins/python/var/var_set.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Workflow plugin: set variable in workflow store."""
|
||||
|
||||
|
||||
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"}
|
||||
|
||||
runtime.store[key] = value
|
||||
|
||||
return {"result": value, "key": key}
|
||||
44
workflow/plugins/python/web/__init__.py
Normal file
44
workflow/plugins/python/web/__init__.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Web workflow plugins: Flask server, API endpoints, file I/O, translations.
|
||||
|
||||
These plugins provide workflow-based access to web data operations, enabling
|
||||
declarative workflows to interact with web-related functionality.
|
||||
|
||||
Available Plugins:
|
||||
|
||||
Environment Management:
|
||||
- web_get_env_vars - Load environment variables
|
||||
- web_persist_env_vars - Save environment variables
|
||||
|
||||
File I/O:
|
||||
- web_read_json - Read JSON files
|
||||
- web_get_recent_logs - Get recent log entries
|
||||
- web_load_messages - Load translation messages
|
||||
|
||||
Translation Management:
|
||||
- web_list_translations - List available translations
|
||||
- web_load_translation - Load a translation
|
||||
- web_create_translation - Create new translation
|
||||
- web_update_translation - Update translation
|
||||
- web_delete_translation - Delete translation
|
||||
- web_get_ui_messages - Get UI messages with fallback
|
||||
|
||||
Navigation & Metadata:
|
||||
- web_get_navigation_items - Get navigation menu items
|
||||
|
||||
Prompt Management:
|
||||
- web_get_prompt_content - Read prompt content
|
||||
- web_write_prompt - Write prompt content
|
||||
- web_build_prompt_yaml - Build YAML prompt
|
||||
|
||||
Workflow Operations:
|
||||
- web_get_workflow_content - Read workflow JSON
|
||||
- web_write_workflow - Write workflow JSON
|
||||
- web_load_workflow_packages - Load workflow packages
|
||||
- web_summarize_workflow_packages - Summarize packages
|
||||
|
||||
Flask Server Setup:
|
||||
- web_create_flask_app - Create Flask application
|
||||
- web_register_blueprint - Register Flask blueprints
|
||||
- web_start_server - Start Flask server
|
||||
- web_build_context - Build API context
|
||||
"""
|
||||
29
workflow/plugins/python/web/web_build_prompt_yaml.py
Normal file
29
workflow/plugins/python/web/web_build_prompt_yaml.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Workflow plugin: build prompt YAML."""
|
||||
|
||||
|
||||
def run(_runtime, inputs):
|
||||
"""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())
|
||||
|
||||
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}
|
||||
- role: user
|
||||
content: >-
|
||||
{user_block}
|
||||
model: {model_value}
|
||||
"""
|
||||
|
||||
return {"result": yaml_content}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user