From 642f05f399d8c03297a459225d13c81504badd2f Mon Sep 17 00:00:00 2001 From: Luka Stanisljevic Date: Sun, 5 Apr 2026 06:53:07 +0200 Subject: [PATCH 1/6] feat: add OraClaw decision intelligence plugin (5 tools) --- plugins/oraclaw/README.md | 32 +++++++++++++++ plugins/oraclaw/_assets/icon.svg | 7 ++++ plugins/oraclaw/manifest.yaml | 26 ++++++++++++ plugins/oraclaw/privacy.md | 26 ++++++++++++ plugins/oraclaw/provider/oraclaw.yaml | 39 ++++++++++++++++++ plugins/oraclaw/providers/oraclaw.py | 24 +++++++++++ plugins/oraclaw/requirements.txt | 2 + plugins/oraclaw/tools/analyze_graph.py | 32 +++++++++++++++ plugins/oraclaw/tools/analyze_graph.yaml | 20 +++++++++ plugins/oraclaw/tools/detect_anomaly.py | 34 ++++++++++++++++ plugins/oraclaw/tools/detect_anomaly.yaml | 43 ++++++++++++++++++++ plugins/oraclaw/tools/optimize_bandit.py | 33 +++++++++++++++ plugins/oraclaw/tools/optimize_bandit.yaml | 39 ++++++++++++++++++ plugins/oraclaw/tools/predict_forecast.py | 34 ++++++++++++++++ plugins/oraclaw/tools/predict_forecast.yaml | 43 ++++++++++++++++++++ plugins/oraclaw/tools/solve_constraints.py | 32 +++++++++++++++ plugins/oraclaw/tools/solve_constraints.yaml | 20 +++++++++ 17 files changed, 486 insertions(+) create mode 100644 plugins/oraclaw/README.md create mode 100644 plugins/oraclaw/_assets/icon.svg create mode 100644 plugins/oraclaw/manifest.yaml create mode 100644 plugins/oraclaw/privacy.md create mode 100644 plugins/oraclaw/provider/oraclaw.yaml create mode 100644 plugins/oraclaw/providers/oraclaw.py create mode 100644 plugins/oraclaw/requirements.txt create mode 100644 plugins/oraclaw/tools/analyze_graph.py create mode 100644 plugins/oraclaw/tools/analyze_graph.yaml create mode 100644 plugins/oraclaw/tools/detect_anomaly.py create mode 100644 plugins/oraclaw/tools/detect_anomaly.yaml create mode 100644 plugins/oraclaw/tools/optimize_bandit.py create mode 100644 plugins/oraclaw/tools/optimize_bandit.yaml create mode 100644 plugins/oraclaw/tools/predict_forecast.py create mode 100644 plugins/oraclaw/tools/predict_forecast.yaml create mode 100644 plugins/oraclaw/tools/solve_constraints.py create mode 100644 plugins/oraclaw/tools/solve_constraints.yaml diff --git a/plugins/oraclaw/README.md b/plugins/oraclaw/README.md new file mode 100644 index 000000000..fc4b1343c --- /dev/null +++ b/plugins/oraclaw/README.md @@ -0,0 +1,32 @@ +# OraClaw -- Decision Intelligence for Dify + +Add real mathematical optimization to your Dify workflows. 5 algorithm tools, all under 25ms. + +## Tools + +| Tool | What It Does | +|------|-------------| +| **Multi-Armed Bandit** | Select the best option (UCB1/Thompson/epsilon-Greedy) | +| **LP/MIP Solver** | Budget allocation, scheduling, constraint satisfaction | +| **Graph Analytics** | PageRank, communities, shortest path | +| **Anomaly Detection** | Z-score/IQR outlier detection | +| **Time Series Forecast** | ARIMA/Holt-Winters with confidence intervals | + +## Setup + +1. Get a free API key: `curl -X POST https://oraclaw-api.onrender.com/api/v1/auth/signup -H "Content-Type: application/json" -d '{"email":"you@example.com"}'` +2. Install the OraClaw plugin in your Dify instance +3. Enter your API key in the plugin settings +4. Use the tools in your Dify workflows + +## Pricing + +- **Free signup**: 1,000 calls/day at $0.005/call (metered) +- **Starter**: $9/mo for 50,000 calls +- **Growth**: $49/mo for 500,000 calls + +## Links + +- [GitHub](https://github.com/Whatsonyourmind/oraclaw) +- [npm MCP Server](https://www.npmjs.com/package/@oraclaw/mcp-server) +- [API Documentation](https://oraclaw-api.onrender.com/api/v1/pricing) diff --git a/plugins/oraclaw/_assets/icon.svg b/plugins/oraclaw/_assets/icon.svg new file mode 100644 index 000000000..ec0694b20 --- /dev/null +++ b/plugins/oraclaw/_assets/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/oraclaw/manifest.yaml b/plugins/oraclaw/manifest.yaml new file mode 100644 index 000000000..ec89714a3 --- /dev/null +++ b/plugins/oraclaw/manifest.yaml @@ -0,0 +1,26 @@ +version: "0.1.0" +type: plugin +author: "Whatsonyourmind" +name: "oraclaw" +label: + en_US: "OraClaw Decision Intelligence" +created_at: "2026-04-05T00:00:00Z" +icon: "icon.svg" +resource: + memory: 268435456 + permission: + tool: + enabled: true +meta: + version: "0.1.0" + arch: + - amd64 + - arm64 +runner: + language: python + version: "3.12" + entrypoint: "main" +plugins: + tools: + - "provider/oraclaw.yaml" +privacy: "privacy.md" diff --git a/plugins/oraclaw/privacy.md b/plugins/oraclaw/privacy.md new file mode 100644 index 000000000..b59e231a8 --- /dev/null +++ b/plugins/oraclaw/privacy.md @@ -0,0 +1,26 @@ +# OraClaw Dify Plugin -- Privacy Policy + +## Data Collection + +This plugin sends tool parameters to the OraClaw API (`oraclaw-api.onrender.com`) for processing. No data is stored beyond request logging for rate limiting purposes. + +## Data Transmitted + +- Tool input parameters (algorithm configurations, data arrays) +- API key (for authentication) +- No PII is collected or transmitted + +## Data Retention + +- API request logs are retained for 30 days for rate limiting and abuse prevention +- No user data is stored permanently +- Anonymous telemetry (tool name + duration) is collected for performance monitoring and can be opted out + +## Third-Party Services + +- OraClaw API (hosted on Render) +- Stripe (for paid tier billing, only if user subscribes) + +## Contact + +GitHub: https://github.com/Whatsonyourmind/oraclaw diff --git a/plugins/oraclaw/provider/oraclaw.yaml b/plugins/oraclaw/provider/oraclaw.yaml new file mode 100644 index 000000000..a0068c960 --- /dev/null +++ b/plugins/oraclaw/provider/oraclaw.yaml @@ -0,0 +1,39 @@ +identity: + author: "Whatsonyourmind" + name: "oraclaw" + label: + en_US: "OraClaw" + description: + en_US: "Decision intelligence for AI agents — bandits, LP solver, graph analytics, anomaly detection, forecasting. 12 algorithms, all under 25ms." + icon: "icon.svg" + tags: + - PRODUCTIVITY + - UTILITIES + - BUSINESS +credentials_for_provider: + - variable: api_key + label: + en_US: "OraClaw API Key" + type: secret-input + required: true + placeholder: + en_US: "ok_live_... or ok_beta_..." + help: + en_US: "Get a free API key at https://oraclaw-api.onrender.com/api/v1/auth/signup" + - variable: api_endpoint + label: + en_US: "API Endpoint" + type: text-input + required: false + default: "https://oraclaw-api.onrender.com" + placeholder: + en_US: "https://oraclaw-api.onrender.com" +tools: + - tools/optimize_bandit.yaml + - tools/solve_constraints.yaml + - tools/analyze_graph.yaml + - tools/detect_anomaly.yaml + - tools/predict_forecast.yaml +extra: + python: + source: providers/oraclaw.py diff --git a/plugins/oraclaw/providers/oraclaw.py b/plugins/oraclaw/providers/oraclaw.py new file mode 100644 index 000000000..07606b867 --- /dev/null +++ b/plugins/oraclaw/providers/oraclaw.py @@ -0,0 +1,24 @@ +import requests +from dify_plugin import ToolProvider +from dify_plugin.errors.tool import ToolProviderCredentialValidationError + + +class OraclawProvider(ToolProvider): + def _validate_credentials(self, credentials: dict) -> None: + api_key = credentials.get("api_key", "") + endpoint = credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") + + try: + response = requests.get( + f"{endpoint}/api/v1/pricing", + headers={"Authorization": f"Bearer {api_key}"}, + timeout=10, + ) + if response.status_code != 200: + raise ToolProviderCredentialValidationError( + f"OraClaw API returned status {response.status_code}" + ) + except requests.RequestException as e: + raise ToolProviderCredentialValidationError( + f"Failed to connect to OraClaw API: {str(e)}" + ) diff --git a/plugins/oraclaw/requirements.txt b/plugins/oraclaw/requirements.txt new file mode 100644 index 000000000..2a2cfe8cf --- /dev/null +++ b/plugins/oraclaw/requirements.txt @@ -0,0 +1,2 @@ +dify_plugin>=0.0.1 +requests>=2.31.0 diff --git a/plugins/oraclaw/tools/analyze_graph.py b/plugins/oraclaw/tools/analyze_graph.py new file mode 100644 index 000000000..ddeaf4753 --- /dev/null +++ b/plugins/oraclaw/tools/analyze_graph.py @@ -0,0 +1,32 @@ +import json +from collections.abc import Generator +from typing import Any + +import requests +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + + +class AnalyzeGraphTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + api_key = self.runtime.credentials.get("api_key", "") + endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") + + graph = json.loads(tool_parameters["graph"]) if isinstance(tool_parameters["graph"], str) else tool_parameters["graph"] + + response = requests.post( + f"{endpoint}/api/v1/analyze/graph", + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + }, + json=graph, + timeout=15, + ) + + if response.status_code != 200: + yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") + return + + result = response.json() + yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/analyze_graph.yaml b/plugins/oraclaw/tools/analyze_graph.yaml new file mode 100644 index 000000000..8bbbfb9ff --- /dev/null +++ b/plugins/oraclaw/tools/analyze_graph.yaml @@ -0,0 +1,20 @@ +identity: + name: "analyze_graph" + author: "Whatsonyourmind" + label: + en_US: "Graph Analytics" + description: + en_US: "PageRank, Louvain community detection, shortest path, bottleneck detection on any graph." + icon: "icon.svg" +parameters: + - name: graph + type: string + required: true + label: + en_US: "Graph (JSON)" + human_description: + en_US: "JSON with nodes and edges. E.g. {\"nodes\":[{\"id\":\"A\"},{\"id\":\"B\"}],\"edges\":[{\"source\":\"A\",\"target\":\"B\",\"weight\":1}]}" + form: llm +extra: + python: + source: tools/analyze_graph.py diff --git a/plugins/oraclaw/tools/detect_anomaly.py b/plugins/oraclaw/tools/detect_anomaly.py new file mode 100644 index 000000000..743bd8c2f --- /dev/null +++ b/plugins/oraclaw/tools/detect_anomaly.py @@ -0,0 +1,34 @@ +import json +from collections.abc import Generator +from typing import Any + +import requests +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + + +class DetectAnomalyTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + api_key = self.runtime.credentials.get("api_key", "") + endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") + + data = json.loads(tool_parameters["data"]) if isinstance(tool_parameters["data"], str) else tool_parameters["data"] + method = tool_parameters.get("method", "zscore") + threshold = tool_parameters.get("threshold", 3.0) + + response = requests.post( + f"{endpoint}/api/v1/detect/anomaly", + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + }, + json={"data": data, "method": method, "threshold": threshold}, + timeout=15, + ) + + if response.status_code != 200: + yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") + return + + result = response.json() + yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/detect_anomaly.yaml b/plugins/oraclaw/tools/detect_anomaly.yaml new file mode 100644 index 000000000..ec6135f28 --- /dev/null +++ b/plugins/oraclaw/tools/detect_anomaly.yaml @@ -0,0 +1,43 @@ +identity: + name: "detect_anomaly" + author: "Whatsonyourmind" + label: + en_US: "Anomaly Detection" + description: + en_US: "Z-score or IQR outlier detection. Sub-millisecond. Find unusual values in any numeric dataset." + icon: "icon.svg" +parameters: + - name: data + type: string + required: true + label: + en_US: "Data (JSON array)" + human_description: + en_US: "JSON array of numbers. E.g. [1, 2, 3, 100, 2, 3, 1]" + form: llm + - name: method + type: select + required: false + label: + en_US: "Method" + default: "zscore" + options: + - value: "zscore" + label: + en_US: "Z-Score" + - value: "iqr" + label: + en_US: "IQR" + form: form + - name: threshold + type: number + required: false + label: + en_US: "Threshold" + human_description: + en_US: "Detection threshold (default: 3.0 for Z-score)" + default: 3.0 + form: form +extra: + python: + source: tools/detect_anomaly.py diff --git a/plugins/oraclaw/tools/optimize_bandit.py b/plugins/oraclaw/tools/optimize_bandit.py new file mode 100644 index 000000000..50237092e --- /dev/null +++ b/plugins/oraclaw/tools/optimize_bandit.py @@ -0,0 +1,33 @@ +import json +from collections.abc import Generator +from typing import Any + +import requests +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + + +class OptimizeBanditTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + api_key = self.runtime.credentials.get("api_key", "") + endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") + + arms = json.loads(tool_parameters["arms"]) if isinstance(tool_parameters["arms"], str) else tool_parameters["arms"] + algorithm = tool_parameters.get("algorithm", "ucb1") + + response = requests.post( + f"{endpoint}/api/v1/optimize/bandit", + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + }, + json={"arms": arms, "algorithm": algorithm}, + timeout=15, + ) + + if response.status_code != 200: + yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") + return + + result = response.json() + yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/optimize_bandit.yaml b/plugins/oraclaw/tools/optimize_bandit.yaml new file mode 100644 index 000000000..fd5cb8a01 --- /dev/null +++ b/plugins/oraclaw/tools/optimize_bandit.yaml @@ -0,0 +1,39 @@ +identity: + name: "optimize_bandit" + author: "Whatsonyourmind" + label: + en_US: "Multi-Armed Bandit" + description: + en_US: "Select the best option from a set using UCB1, Thompson Sampling, or epsilon-Greedy. Optimal explore/exploit tradeoff. <1ms." + icon: "icon.svg" +parameters: + - name: arms + type: string + required: true + label: + en_US: "Arms (JSON)" + human_description: + en_US: "JSON array of options, e.g. [{\"id\":\"a\",\"pulls\":10,\"totalReward\":7},{\"id\":\"b\",\"pulls\":5,\"totalReward\":3}]" + form: llm + - name: algorithm + type: select + required: false + label: + en_US: "Algorithm" + human_description: + en_US: "Selection algorithm" + default: "ucb1" + options: + - value: "ucb1" + label: + en_US: "UCB1" + - value: "thompson" + label: + en_US: "Thompson Sampling" + - value: "epsilon-greedy" + label: + en_US: "Epsilon-Greedy" + form: form +extra: + python: + source: tools/optimize_bandit.py diff --git a/plugins/oraclaw/tools/predict_forecast.py b/plugins/oraclaw/tools/predict_forecast.py new file mode 100644 index 000000000..63603718e --- /dev/null +++ b/plugins/oraclaw/tools/predict_forecast.py @@ -0,0 +1,34 @@ +import json +from collections.abc import Generator +from typing import Any + +import requests +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + + +class PredictForecastTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + api_key = self.runtime.credentials.get("api_key", "") + endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") + + data = json.loads(tool_parameters["data"]) if isinstance(tool_parameters["data"], str) else tool_parameters["data"] + steps = int(tool_parameters.get("steps", 3)) + method = tool_parameters.get("method", "arima") + + response = requests.post( + f"{endpoint}/api/v1/predict/forecast", + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + }, + json={"data": data, "steps": steps, "method": method}, + timeout=15, + ) + + if response.status_code != 200: + yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") + return + + result = response.json() + yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/predict_forecast.yaml b/plugins/oraclaw/tools/predict_forecast.yaml new file mode 100644 index 000000000..af370c214 --- /dev/null +++ b/plugins/oraclaw/tools/predict_forecast.yaml @@ -0,0 +1,43 @@ +identity: + name: "predict_forecast" + author: "Whatsonyourmind" + label: + en_US: "Time Series Forecast" + description: + en_US: "ARIMA or Holt-Winters time series forecasting. Predict future values with confidence intervals." + icon: "icon.svg" +parameters: + - name: data + type: string + required: true + label: + en_US: "Historical Data (JSON array)" + human_description: + en_US: "JSON array of historical numeric values. E.g. [10, 20, 30, 40, 50]" + form: llm + - name: steps + type: number + required: true + label: + en_US: "Forecast Steps" + human_description: + en_US: "Number of future periods to forecast" + default: 3 + form: llm + - name: method + type: select + required: false + label: + en_US: "Method" + default: "arima" + options: + - value: "arima" + label: + en_US: "ARIMA" + - value: "holt-winters" + label: + en_US: "Holt-Winters" + form: form +extra: + python: + source: tools/predict_forecast.py diff --git a/plugins/oraclaw/tools/solve_constraints.py b/plugins/oraclaw/tools/solve_constraints.py new file mode 100644 index 000000000..618355a77 --- /dev/null +++ b/plugins/oraclaw/tools/solve_constraints.py @@ -0,0 +1,32 @@ +import json +from collections.abc import Generator +from typing import Any + +import requests +from dify_plugin import Tool +from dify_plugin.entities.tool import ToolInvokeMessage + + +class SolveConstraintsTool(Tool): + def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: + api_key = self.runtime.credentials.get("api_key", "") + endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") + + problem = json.loads(tool_parameters["problem"]) if isinstance(tool_parameters["problem"], str) else tool_parameters["problem"] + + response = requests.post( + f"{endpoint}/api/v1/solve/constraints", + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + }, + json=problem, + timeout=15, + ) + + if response.status_code != 200: + yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") + return + + result = response.json() + yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/solve_constraints.yaml b/plugins/oraclaw/tools/solve_constraints.yaml new file mode 100644 index 000000000..53403c7cf --- /dev/null +++ b/plugins/oraclaw/tools/solve_constraints.yaml @@ -0,0 +1,20 @@ +identity: + name: "solve_constraints" + author: "Whatsonyourmind" + label: + en_US: "LP/MIP Solver" + description: + en_US: "Linear/mixed-integer optimization (HiGHS). Budget allocation, scheduling, resource planning. Provably optimal solutions." + icon: "icon.svg" +parameters: + - name: problem + type: string + required: true + label: + en_US: "Problem (JSON)" + human_description: + en_US: "JSON with direction, objective, variables, constraints. E.g. {\"direction\":\"maximize\",\"objective\":{\"x\":5,\"y\":4},\"variables\":[{\"name\":\"x\",\"lower\":0,\"upper\":10}],\"constraints\":[{\"name\":\"budget\",\"coefficients\":{\"x\":2,\"y\":3},\"upper\":24}]}" + form: llm +extra: + python: + source: tools/solve_constraints.py From 4fbeb6be0e84d40e1567c5ced439f8cd1645ff36 Mon Sep 17 00:00:00 2001 From: Luka Stanisljevic Date: Mon, 13 Apr 2026 00:01:04 +0200 Subject: [PATCH 2/6] fix: rename privacy.md to PRIVACY.md per review --- plugins/oraclaw/PRIVACY.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 plugins/oraclaw/PRIVACY.md diff --git a/plugins/oraclaw/PRIVACY.md b/plugins/oraclaw/PRIVACY.md new file mode 100644 index 000000000..b59e231a8 --- /dev/null +++ b/plugins/oraclaw/PRIVACY.md @@ -0,0 +1,26 @@ +# OraClaw Dify Plugin -- Privacy Policy + +## Data Collection + +This plugin sends tool parameters to the OraClaw API (`oraclaw-api.onrender.com`) for processing. No data is stored beyond request logging for rate limiting purposes. + +## Data Transmitted + +- Tool input parameters (algorithm configurations, data arrays) +- API key (for authentication) +- No PII is collected or transmitted + +## Data Retention + +- API request logs are retained for 30 days for rate limiting and abuse prevention +- No user data is stored permanently +- Anonymous telemetry (tool name + duration) is collected for performance monitoring and can be opted out + +## Third-Party Services + +- OraClaw API (hosted on Render) +- Stripe (for paid tier billing, only if user subscribes) + +## Contact + +GitHub: https://github.com/Whatsonyourmind/oraclaw From e97bff648937bf97edbc04493f70d1f54ac66863 Mon Sep 17 00:00:00 2001 From: Luka Stanisljevic Date: Mon, 13 Apr 2026 00:01:11 +0200 Subject: [PATCH 3/6] fix: remove lowercase privacy.md (replaced by PRIVACY.md) --- plugins/oraclaw/privacy.md | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 plugins/oraclaw/privacy.md diff --git a/plugins/oraclaw/privacy.md b/plugins/oraclaw/privacy.md deleted file mode 100644 index b59e231a8..000000000 --- a/plugins/oraclaw/privacy.md +++ /dev/null @@ -1,26 +0,0 @@ -# OraClaw Dify Plugin -- Privacy Policy - -## Data Collection - -This plugin sends tool parameters to the OraClaw API (`oraclaw-api.onrender.com`) for processing. No data is stored beyond request logging for rate limiting purposes. - -## Data Transmitted - -- Tool input parameters (algorithm configurations, data arrays) -- API key (for authentication) -- No PII is collected or transmitted - -## Data Retention - -- API request logs are retained for 30 days for rate limiting and abuse prevention -- No user data is stored permanently -- Anonymous telemetry (tool name + duration) is collected for performance monitoring and can be opted out - -## Third-Party Services - -- OraClaw API (hosted on Render) -- Stripe (for paid tier billing, only if user subscribes) - -## Contact - -GitHub: https://github.com/Whatsonyourmind/oraclaw From 35553200ceb24a92b5156c2982c03583a0cb8440 Mon Sep 17 00:00:00 2001 From: Luka Stanisljevic Date: Mon, 13 Apr 2026 00:01:24 +0200 Subject: [PATCH 4/6] fix: update manifest to reference PRIVACY.md --- plugins/oraclaw/manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/oraclaw/manifest.yaml b/plugins/oraclaw/manifest.yaml index ec89714a3..ab567d581 100644 --- a/plugins/oraclaw/manifest.yaml +++ b/plugins/oraclaw/manifest.yaml @@ -23,4 +23,4 @@ runner: plugins: tools: - "provider/oraclaw.yaml" -privacy: "privacy.md" +privacy: "PRIVACY.md" From a2d66423b427bf806dc080ca939b30ed30b86904 Mon Sep 17 00:00:00 2001 From: Luka Stanisljevic Date: Mon, 13 Apr 2026 00:01:26 +0200 Subject: [PATCH 5/6] fix: bump dify_plugin to >= 0.5.0 per review --- plugins/oraclaw/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/oraclaw/requirements.txt b/plugins/oraclaw/requirements.txt index 2a2cfe8cf..7017c3860 100644 --- a/plugins/oraclaw/requirements.txt +++ b/plugins/oraclaw/requirements.txt @@ -1,2 +1,2 @@ -dify_plugin>=0.0.1 +dify_plugin>=0.5.0 requests>=2.31.0 From 57c0c4153a8c2d1fd2764dc665b3ff9533cb64ad Mon Sep 17 00:00:00 2001 From: Luka Stanisljevic Date: Thu, 30 Apr 2026 08:25:19 +0200 Subject: [PATCH 6/6] fix: switch to packed .difypkg format per crazywoola review Maintainer feedback (Apr 16): "You should submit a packed file only." Removed source-tree submission under plugins/oraclaw/ (17 files) and replaced with a single packaged plugin at: whatsonyourmind/oraclaw/oraclaw-0.0.1.difypkg Built via dify-plugin-daemon CLI v0.6.0. Required manifest fixes during packaging: added description.en_US and nested runner.* under meta.runner.* to satisfy the current schema validator. 5 tools (optimize_bandit, solve_constraints, analyze_graph, detect_anomaly, predict_forecast), 1 provider with secret-input ORACLAW_API_KEY credential, meta.runner.python 3.12, meta.arch [amd64, arm64]. Ready for re-review. --- plugins/oraclaw/PRIVACY.md | 26 ----------- plugins/oraclaw/README.md | 32 ------------- plugins/oraclaw/_assets/icon.svg | 7 --- plugins/oraclaw/manifest.yaml | 26 ----------- plugins/oraclaw/provider/oraclaw.yaml | 39 ---------------- plugins/oraclaw/providers/oraclaw.py | 24 ---------- plugins/oraclaw/requirements.txt | 2 - plugins/oraclaw/tools/analyze_graph.py | 32 ------------- plugins/oraclaw/tools/analyze_graph.yaml | 20 -------- plugins/oraclaw/tools/detect_anomaly.py | 34 -------------- plugins/oraclaw/tools/detect_anomaly.yaml | 43 ------------------ plugins/oraclaw/tools/optimize_bandit.py | 33 -------------- plugins/oraclaw/tools/optimize_bandit.yaml | 39 ---------------- plugins/oraclaw/tools/predict_forecast.py | 34 -------------- plugins/oraclaw/tools/predict_forecast.yaml | 43 ------------------ plugins/oraclaw/tools/solve_constraints.py | 32 ------------- plugins/oraclaw/tools/solve_constraints.yaml | 20 -------- whatsonyourmind/oraclaw/oraclaw-0.0.1.difypkg | Bin 0 -> 10106 bytes 18 files changed, 486 deletions(-) delete mode 100644 plugins/oraclaw/PRIVACY.md delete mode 100644 plugins/oraclaw/README.md delete mode 100644 plugins/oraclaw/_assets/icon.svg delete mode 100644 plugins/oraclaw/manifest.yaml delete mode 100644 plugins/oraclaw/provider/oraclaw.yaml delete mode 100644 plugins/oraclaw/providers/oraclaw.py delete mode 100644 plugins/oraclaw/requirements.txt delete mode 100644 plugins/oraclaw/tools/analyze_graph.py delete mode 100644 plugins/oraclaw/tools/analyze_graph.yaml delete mode 100644 plugins/oraclaw/tools/detect_anomaly.py delete mode 100644 plugins/oraclaw/tools/detect_anomaly.yaml delete mode 100644 plugins/oraclaw/tools/optimize_bandit.py delete mode 100644 plugins/oraclaw/tools/optimize_bandit.yaml delete mode 100644 plugins/oraclaw/tools/predict_forecast.py delete mode 100644 plugins/oraclaw/tools/predict_forecast.yaml delete mode 100644 plugins/oraclaw/tools/solve_constraints.py delete mode 100644 plugins/oraclaw/tools/solve_constraints.yaml create mode 100644 whatsonyourmind/oraclaw/oraclaw-0.0.1.difypkg diff --git a/plugins/oraclaw/PRIVACY.md b/plugins/oraclaw/PRIVACY.md deleted file mode 100644 index b59e231a8..000000000 --- a/plugins/oraclaw/PRIVACY.md +++ /dev/null @@ -1,26 +0,0 @@ -# OraClaw Dify Plugin -- Privacy Policy - -## Data Collection - -This plugin sends tool parameters to the OraClaw API (`oraclaw-api.onrender.com`) for processing. No data is stored beyond request logging for rate limiting purposes. - -## Data Transmitted - -- Tool input parameters (algorithm configurations, data arrays) -- API key (for authentication) -- No PII is collected or transmitted - -## Data Retention - -- API request logs are retained for 30 days for rate limiting and abuse prevention -- No user data is stored permanently -- Anonymous telemetry (tool name + duration) is collected for performance monitoring and can be opted out - -## Third-Party Services - -- OraClaw API (hosted on Render) -- Stripe (for paid tier billing, only if user subscribes) - -## Contact - -GitHub: https://github.com/Whatsonyourmind/oraclaw diff --git a/plugins/oraclaw/README.md b/plugins/oraclaw/README.md deleted file mode 100644 index fc4b1343c..000000000 --- a/plugins/oraclaw/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# OraClaw -- Decision Intelligence for Dify - -Add real mathematical optimization to your Dify workflows. 5 algorithm tools, all under 25ms. - -## Tools - -| Tool | What It Does | -|------|-------------| -| **Multi-Armed Bandit** | Select the best option (UCB1/Thompson/epsilon-Greedy) | -| **LP/MIP Solver** | Budget allocation, scheduling, constraint satisfaction | -| **Graph Analytics** | PageRank, communities, shortest path | -| **Anomaly Detection** | Z-score/IQR outlier detection | -| **Time Series Forecast** | ARIMA/Holt-Winters with confidence intervals | - -## Setup - -1. Get a free API key: `curl -X POST https://oraclaw-api.onrender.com/api/v1/auth/signup -H "Content-Type: application/json" -d '{"email":"you@example.com"}'` -2. Install the OraClaw plugin in your Dify instance -3. Enter your API key in the plugin settings -4. Use the tools in your Dify workflows - -## Pricing - -- **Free signup**: 1,000 calls/day at $0.005/call (metered) -- **Starter**: $9/mo for 50,000 calls -- **Growth**: $49/mo for 500,000 calls - -## Links - -- [GitHub](https://github.com/Whatsonyourmind/oraclaw) -- [npm MCP Server](https://www.npmjs.com/package/@oraclaw/mcp-server) -- [API Documentation](https://oraclaw-api.onrender.com/api/v1/pricing) diff --git a/plugins/oraclaw/_assets/icon.svg b/plugins/oraclaw/_assets/icon.svg deleted file mode 100644 index ec0694b20..000000000 --- a/plugins/oraclaw/_assets/icon.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/plugins/oraclaw/manifest.yaml b/plugins/oraclaw/manifest.yaml deleted file mode 100644 index ab567d581..000000000 --- a/plugins/oraclaw/manifest.yaml +++ /dev/null @@ -1,26 +0,0 @@ -version: "0.1.0" -type: plugin -author: "Whatsonyourmind" -name: "oraclaw" -label: - en_US: "OraClaw Decision Intelligence" -created_at: "2026-04-05T00:00:00Z" -icon: "icon.svg" -resource: - memory: 268435456 - permission: - tool: - enabled: true -meta: - version: "0.1.0" - arch: - - amd64 - - arm64 -runner: - language: python - version: "3.12" - entrypoint: "main" -plugins: - tools: - - "provider/oraclaw.yaml" -privacy: "PRIVACY.md" diff --git a/plugins/oraclaw/provider/oraclaw.yaml b/plugins/oraclaw/provider/oraclaw.yaml deleted file mode 100644 index a0068c960..000000000 --- a/plugins/oraclaw/provider/oraclaw.yaml +++ /dev/null @@ -1,39 +0,0 @@ -identity: - author: "Whatsonyourmind" - name: "oraclaw" - label: - en_US: "OraClaw" - description: - en_US: "Decision intelligence for AI agents — bandits, LP solver, graph analytics, anomaly detection, forecasting. 12 algorithms, all under 25ms." - icon: "icon.svg" - tags: - - PRODUCTIVITY - - UTILITIES - - BUSINESS -credentials_for_provider: - - variable: api_key - label: - en_US: "OraClaw API Key" - type: secret-input - required: true - placeholder: - en_US: "ok_live_... or ok_beta_..." - help: - en_US: "Get a free API key at https://oraclaw-api.onrender.com/api/v1/auth/signup" - - variable: api_endpoint - label: - en_US: "API Endpoint" - type: text-input - required: false - default: "https://oraclaw-api.onrender.com" - placeholder: - en_US: "https://oraclaw-api.onrender.com" -tools: - - tools/optimize_bandit.yaml - - tools/solve_constraints.yaml - - tools/analyze_graph.yaml - - tools/detect_anomaly.yaml - - tools/predict_forecast.yaml -extra: - python: - source: providers/oraclaw.py diff --git a/plugins/oraclaw/providers/oraclaw.py b/plugins/oraclaw/providers/oraclaw.py deleted file mode 100644 index 07606b867..000000000 --- a/plugins/oraclaw/providers/oraclaw.py +++ /dev/null @@ -1,24 +0,0 @@ -import requests -from dify_plugin import ToolProvider -from dify_plugin.errors.tool import ToolProviderCredentialValidationError - - -class OraclawProvider(ToolProvider): - def _validate_credentials(self, credentials: dict) -> None: - api_key = credentials.get("api_key", "") - endpoint = credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") - - try: - response = requests.get( - f"{endpoint}/api/v1/pricing", - headers={"Authorization": f"Bearer {api_key}"}, - timeout=10, - ) - if response.status_code != 200: - raise ToolProviderCredentialValidationError( - f"OraClaw API returned status {response.status_code}" - ) - except requests.RequestException as e: - raise ToolProviderCredentialValidationError( - f"Failed to connect to OraClaw API: {str(e)}" - ) diff --git a/plugins/oraclaw/requirements.txt b/plugins/oraclaw/requirements.txt deleted file mode 100644 index 7017c3860..000000000 --- a/plugins/oraclaw/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -dify_plugin>=0.5.0 -requests>=2.31.0 diff --git a/plugins/oraclaw/tools/analyze_graph.py b/plugins/oraclaw/tools/analyze_graph.py deleted file mode 100644 index ddeaf4753..000000000 --- a/plugins/oraclaw/tools/analyze_graph.py +++ /dev/null @@ -1,32 +0,0 @@ -import json -from collections.abc import Generator -from typing import Any - -import requests -from dify_plugin import Tool -from dify_plugin.entities.tool import ToolInvokeMessage - - -class AnalyzeGraphTool(Tool): - def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: - api_key = self.runtime.credentials.get("api_key", "") - endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") - - graph = json.loads(tool_parameters["graph"]) if isinstance(tool_parameters["graph"], str) else tool_parameters["graph"] - - response = requests.post( - f"{endpoint}/api/v1/analyze/graph", - headers={ - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - }, - json=graph, - timeout=15, - ) - - if response.status_code != 200: - yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") - return - - result = response.json() - yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/analyze_graph.yaml b/plugins/oraclaw/tools/analyze_graph.yaml deleted file mode 100644 index 8bbbfb9ff..000000000 --- a/plugins/oraclaw/tools/analyze_graph.yaml +++ /dev/null @@ -1,20 +0,0 @@ -identity: - name: "analyze_graph" - author: "Whatsonyourmind" - label: - en_US: "Graph Analytics" - description: - en_US: "PageRank, Louvain community detection, shortest path, bottleneck detection on any graph." - icon: "icon.svg" -parameters: - - name: graph - type: string - required: true - label: - en_US: "Graph (JSON)" - human_description: - en_US: "JSON with nodes and edges. E.g. {\"nodes\":[{\"id\":\"A\"},{\"id\":\"B\"}],\"edges\":[{\"source\":\"A\",\"target\":\"B\",\"weight\":1}]}" - form: llm -extra: - python: - source: tools/analyze_graph.py diff --git a/plugins/oraclaw/tools/detect_anomaly.py b/plugins/oraclaw/tools/detect_anomaly.py deleted file mode 100644 index 743bd8c2f..000000000 --- a/plugins/oraclaw/tools/detect_anomaly.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -from collections.abc import Generator -from typing import Any - -import requests -from dify_plugin import Tool -from dify_plugin.entities.tool import ToolInvokeMessage - - -class DetectAnomalyTool(Tool): - def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: - api_key = self.runtime.credentials.get("api_key", "") - endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") - - data = json.loads(tool_parameters["data"]) if isinstance(tool_parameters["data"], str) else tool_parameters["data"] - method = tool_parameters.get("method", "zscore") - threshold = tool_parameters.get("threshold", 3.0) - - response = requests.post( - f"{endpoint}/api/v1/detect/anomaly", - headers={ - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - }, - json={"data": data, "method": method, "threshold": threshold}, - timeout=15, - ) - - if response.status_code != 200: - yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") - return - - result = response.json() - yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/detect_anomaly.yaml b/plugins/oraclaw/tools/detect_anomaly.yaml deleted file mode 100644 index ec6135f28..000000000 --- a/plugins/oraclaw/tools/detect_anomaly.yaml +++ /dev/null @@ -1,43 +0,0 @@ -identity: - name: "detect_anomaly" - author: "Whatsonyourmind" - label: - en_US: "Anomaly Detection" - description: - en_US: "Z-score or IQR outlier detection. Sub-millisecond. Find unusual values in any numeric dataset." - icon: "icon.svg" -parameters: - - name: data - type: string - required: true - label: - en_US: "Data (JSON array)" - human_description: - en_US: "JSON array of numbers. E.g. [1, 2, 3, 100, 2, 3, 1]" - form: llm - - name: method - type: select - required: false - label: - en_US: "Method" - default: "zscore" - options: - - value: "zscore" - label: - en_US: "Z-Score" - - value: "iqr" - label: - en_US: "IQR" - form: form - - name: threshold - type: number - required: false - label: - en_US: "Threshold" - human_description: - en_US: "Detection threshold (default: 3.0 for Z-score)" - default: 3.0 - form: form -extra: - python: - source: tools/detect_anomaly.py diff --git a/plugins/oraclaw/tools/optimize_bandit.py b/plugins/oraclaw/tools/optimize_bandit.py deleted file mode 100644 index 50237092e..000000000 --- a/plugins/oraclaw/tools/optimize_bandit.py +++ /dev/null @@ -1,33 +0,0 @@ -import json -from collections.abc import Generator -from typing import Any - -import requests -from dify_plugin import Tool -from dify_plugin.entities.tool import ToolInvokeMessage - - -class OptimizeBanditTool(Tool): - def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: - api_key = self.runtime.credentials.get("api_key", "") - endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") - - arms = json.loads(tool_parameters["arms"]) if isinstance(tool_parameters["arms"], str) else tool_parameters["arms"] - algorithm = tool_parameters.get("algorithm", "ucb1") - - response = requests.post( - f"{endpoint}/api/v1/optimize/bandit", - headers={ - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - }, - json={"arms": arms, "algorithm": algorithm}, - timeout=15, - ) - - if response.status_code != 200: - yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") - return - - result = response.json() - yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/optimize_bandit.yaml b/plugins/oraclaw/tools/optimize_bandit.yaml deleted file mode 100644 index fd5cb8a01..000000000 --- a/plugins/oraclaw/tools/optimize_bandit.yaml +++ /dev/null @@ -1,39 +0,0 @@ -identity: - name: "optimize_bandit" - author: "Whatsonyourmind" - label: - en_US: "Multi-Armed Bandit" - description: - en_US: "Select the best option from a set using UCB1, Thompson Sampling, or epsilon-Greedy. Optimal explore/exploit tradeoff. <1ms." - icon: "icon.svg" -parameters: - - name: arms - type: string - required: true - label: - en_US: "Arms (JSON)" - human_description: - en_US: "JSON array of options, e.g. [{\"id\":\"a\",\"pulls\":10,\"totalReward\":7},{\"id\":\"b\",\"pulls\":5,\"totalReward\":3}]" - form: llm - - name: algorithm - type: select - required: false - label: - en_US: "Algorithm" - human_description: - en_US: "Selection algorithm" - default: "ucb1" - options: - - value: "ucb1" - label: - en_US: "UCB1" - - value: "thompson" - label: - en_US: "Thompson Sampling" - - value: "epsilon-greedy" - label: - en_US: "Epsilon-Greedy" - form: form -extra: - python: - source: tools/optimize_bandit.py diff --git a/plugins/oraclaw/tools/predict_forecast.py b/plugins/oraclaw/tools/predict_forecast.py deleted file mode 100644 index 63603718e..000000000 --- a/plugins/oraclaw/tools/predict_forecast.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -from collections.abc import Generator -from typing import Any - -import requests -from dify_plugin import Tool -from dify_plugin.entities.tool import ToolInvokeMessage - - -class PredictForecastTool(Tool): - def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: - api_key = self.runtime.credentials.get("api_key", "") - endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") - - data = json.loads(tool_parameters["data"]) if isinstance(tool_parameters["data"], str) else tool_parameters["data"] - steps = int(tool_parameters.get("steps", 3)) - method = tool_parameters.get("method", "arima") - - response = requests.post( - f"{endpoint}/api/v1/predict/forecast", - headers={ - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - }, - json={"data": data, "steps": steps, "method": method}, - timeout=15, - ) - - if response.status_code != 200: - yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") - return - - result = response.json() - yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/predict_forecast.yaml b/plugins/oraclaw/tools/predict_forecast.yaml deleted file mode 100644 index af370c214..000000000 --- a/plugins/oraclaw/tools/predict_forecast.yaml +++ /dev/null @@ -1,43 +0,0 @@ -identity: - name: "predict_forecast" - author: "Whatsonyourmind" - label: - en_US: "Time Series Forecast" - description: - en_US: "ARIMA or Holt-Winters time series forecasting. Predict future values with confidence intervals." - icon: "icon.svg" -parameters: - - name: data - type: string - required: true - label: - en_US: "Historical Data (JSON array)" - human_description: - en_US: "JSON array of historical numeric values. E.g. [10, 20, 30, 40, 50]" - form: llm - - name: steps - type: number - required: true - label: - en_US: "Forecast Steps" - human_description: - en_US: "Number of future periods to forecast" - default: 3 - form: llm - - name: method - type: select - required: false - label: - en_US: "Method" - default: "arima" - options: - - value: "arima" - label: - en_US: "ARIMA" - - value: "holt-winters" - label: - en_US: "Holt-Winters" - form: form -extra: - python: - source: tools/predict_forecast.py diff --git a/plugins/oraclaw/tools/solve_constraints.py b/plugins/oraclaw/tools/solve_constraints.py deleted file mode 100644 index 618355a77..000000000 --- a/plugins/oraclaw/tools/solve_constraints.py +++ /dev/null @@ -1,32 +0,0 @@ -import json -from collections.abc import Generator -from typing import Any - -import requests -from dify_plugin import Tool -from dify_plugin.entities.tool import ToolInvokeMessage - - -class SolveConstraintsTool(Tool): - def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]: - api_key = self.runtime.credentials.get("api_key", "") - endpoint = self.runtime.credentials.get("api_endpoint", "https://oraclaw-api.onrender.com") - - problem = json.loads(tool_parameters["problem"]) if isinstance(tool_parameters["problem"], str) else tool_parameters["problem"] - - response = requests.post( - f"{endpoint}/api/v1/solve/constraints", - headers={ - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - }, - json=problem, - timeout=15, - ) - - if response.status_code != 200: - yield self.create_text_message(f"OraClaw API error {response.status_code}: {response.text}") - return - - result = response.json() - yield self.create_text_message(json.dumps(result, indent=2)) diff --git a/plugins/oraclaw/tools/solve_constraints.yaml b/plugins/oraclaw/tools/solve_constraints.yaml deleted file mode 100644 index 53403c7cf..000000000 --- a/plugins/oraclaw/tools/solve_constraints.yaml +++ /dev/null @@ -1,20 +0,0 @@ -identity: - name: "solve_constraints" - author: "Whatsonyourmind" - label: - en_US: "LP/MIP Solver" - description: - en_US: "Linear/mixed-integer optimization (HiGHS). Budget allocation, scheduling, resource planning. Provably optimal solutions." - icon: "icon.svg" -parameters: - - name: problem - type: string - required: true - label: - en_US: "Problem (JSON)" - human_description: - en_US: "JSON with direction, objective, variables, constraints. E.g. {\"direction\":\"maximize\",\"objective\":{\"x\":5,\"y\":4},\"variables\":[{\"name\":\"x\",\"lower\":0,\"upper\":10}],\"constraints\":[{\"name\":\"budget\",\"coefficients\":{\"x\":2,\"y\":3},\"upper\":24}]}" - form: llm -extra: - python: - source: tools/solve_constraints.py diff --git a/whatsonyourmind/oraclaw/oraclaw-0.0.1.difypkg b/whatsonyourmind/oraclaw/oraclaw-0.0.1.difypkg new file mode 100644 index 0000000000000000000000000000000000000000..d4e171ecbb4920021bd146e315c2338e1acadc03 GIT binary patch literal 10106 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