openclaw-syncralis 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -2
- package/README.md +27 -4
- package/openclaw.plugin.json +21 -5
- package/package.json +29 -5
- package/server.js +49 -6
package/.env.example
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
WORKSPACE_DIR=""
|
|
2
|
+
PUBLIC_TUNNEL_URL="https://your-domain.ngrok-free.app"
|
|
3
|
+
NGROK_AUTHTOKEN=your_ngrok_authtoken_here
|
|
4
|
+
NGROK_API_PORT=4040
|
|
5
|
+
URL_SIGNING_SECRET="your_custom_32_character_secret_here"
|
|
3
6
|
TAVILY_API_KEY=your_tavily_key_here
|
|
4
7
|
BRAVE_API_KEY=your_brave_key_here
|
|
5
8
|
FILE_SERVER_HOST=127.0.0.1
|
package/README.md
CHANGED
|
@@ -59,8 +59,17 @@ clawhub login pslkk/openclaw-syncralis
|
|
|
59
59
|
Syncralis is designed as a hybrid tool. It works perfectly on your native operating system (Windows/Mac/Linux) or securely inside a Dockerized environment.
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
#### The Workspace Directory (`WORKSPACE_DIR`):
|
|
63
63
|
|
|
64
|
+
The gateway needs a secure folder to store and manage files. We have designed this to be fully automated, but flexible for power users:
|
|
65
|
+
|
|
66
|
+
***Native / Default Install (Recommended):** Leave `WORKSPACE_DIR=` completely empty (or omit it). The gateway will automatically detect your OS and securely store files in your native home directory: `~/.openclaw/workspace`.
|
|
67
|
+
|
|
68
|
+
***Docker / Custom Environments:** If you are running OpenClaw inside a custom Docker container or want to force the gateway to use a specific volume mount, define the absolute path here:
|
|
69
|
+
`WORKSPACE_DIR=/custom/path/to/workspace`
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Choose the deployment method that matches your OpenClaw setup below:
|
|
64
73
|
|
|
65
74
|
### Option 1: Native NPM Setup (Without Docker)
|
|
66
75
|
|
|
@@ -69,8 +78,17 @@ When running OpenClaw natively on your host machine, Syncralis spins up a secure
|
|
|
69
78
|
|
|
70
79
|
1. Open a new terminal window and run Ngrok to expose the default port:
|
|
71
80
|
|
|
81
|
+
*Note: For a production setup, we highly recommend using your static URL from the Ngrok dashboard so your tunnel never changes.*
|
|
82
|
+
|
|
72
83
|
```bash
|
|
73
84
|
|
|
85
|
+
# 1. Authenticate your terminal (Run this once)
|
|
86
|
+
ngrok config add-authtoken your_ngrok_token_here
|
|
87
|
+
|
|
88
|
+
# 2. Start the tunnel using your static URL (Recommended)
|
|
89
|
+
ngrok http --url your-custom-url.ngrok-free.app 8080
|
|
90
|
+
|
|
91
|
+
# Or, using a dynamic URL (Testing only)
|
|
74
92
|
ngrok http 8080
|
|
75
93
|
|
|
76
94
|
```
|
|
@@ -85,8 +103,10 @@ When running OpenClaw natively on your host machine, Syncralis spins up a secure
|
|
|
85
103
|
"command": "openclaw-syncralis",
|
|
86
104
|
"env": {
|
|
87
105
|
"NODE_ENV": "production",
|
|
88
|
-
"FILE_SERVER_HOST": "127.0.0.1",
|
|
106
|
+
"FILE_SERVER_HOST": "127.0.0.1",
|
|
107
|
+
"WORKSPACE_DIR": "",
|
|
89
108
|
"PUBLIC_TUNNEL_URL": "https://your-ngrok-url.ngrok-free.app",
|
|
109
|
+
"NGROK_API_PORT": 4040,
|
|
90
110
|
"URL_SIGNING_SECRET": "your_custom_32_character_secret_here",
|
|
91
111
|
"TAVILY_API_KEY": "your_tavily_key",
|
|
92
112
|
"BRAVE_API_KEY": "your_brave_key"
|
|
@@ -114,7 +134,9 @@ OpenClaw often executes tools as ephemeral child processes. In a containerized s
|
|
|
114
134
|
"env": {
|
|
115
135
|
"NODE_ENV": "production",
|
|
116
136
|
"FILE_SERVER_HOST": "0.0.0.0",
|
|
137
|
+
"WORKSPACE_DIR": "",
|
|
117
138
|
"PUBLIC_TUNNEL_URL": "https://your-static-domain.ngrok-free.app",
|
|
139
|
+
"NGROK_API_PORT": 4040,
|
|
118
140
|
"URL_SIGNING_SECRET": "your_custom_32_character_secret_here",
|
|
119
141
|
"TAVILY_API_KEY": "your_tavily_key",
|
|
120
142
|
"BRAVE_API_KEY": "your_brave_key"
|
|
@@ -153,8 +175,9 @@ services:
|
|
|
153
175
|
extra_hosts:
|
|
154
176
|
- "host.docker.internal:host-gateway"
|
|
155
177
|
volumes:
|
|
156
|
-
-
|
|
157
|
-
-
|
|
178
|
+
- ./claw_data:/home/node/.openclaw:rw
|
|
179
|
+
- # Your config file
|
|
180
|
+
- ./workspace:/home/node/.openclaw/workspace:rw
|
|
158
181
|
environment:
|
|
159
182
|
- FILE_SERVER_HOST=0.0.0.0
|
|
160
183
|
- FILE_SERVER_PORT=8080
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-syncralis",
|
|
3
3
|
"name": "openclaw-syncralis",
|
|
4
4
|
"displayName": "Syncralis Gateway",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.2.0",
|
|
6
6
|
"description": "An industry-grade file sharing, secure download, and load-balanced gateway designed for high-availability OpenClaw environments.",
|
|
7
7
|
"type": "gateway",
|
|
8
8
|
"main": "./server.js",
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
},
|
|
23
23
|
"configSchema": {
|
|
24
24
|
"type": "object",
|
|
25
|
-
"required": [
|
|
25
|
+
"required": [
|
|
26
|
+
"TAVILY_API_KEY",
|
|
27
|
+
"BRAVE_API_KEY"
|
|
28
|
+
],
|
|
26
29
|
"properties": {
|
|
27
30
|
"FILE_SERVER_PORT": {
|
|
28
31
|
"type": "number",
|
|
@@ -58,6 +61,16 @@
|
|
|
58
61
|
"type": "string",
|
|
59
62
|
"description": "Authentication token for the Ngrok service."
|
|
60
63
|
},
|
|
64
|
+
"WORKSPACE_DIR": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "Optional: Custom absolute path for the workspace volume. Leave blank to default to ~/.openclaw/workspace",
|
|
67
|
+
"default": ""
|
|
68
|
+
},
|
|
69
|
+
"NGROK_API_PORT": {
|
|
70
|
+
"type": "number",
|
|
71
|
+
"default": 4040,
|
|
72
|
+
"description": "Optional: The local port Ngrok uses for its API (used for auto-discovery). Default is 4040."
|
|
73
|
+
},
|
|
61
74
|
"URL_SIGNING_SECRET": {
|
|
62
75
|
"type": "string",
|
|
63
76
|
"description": "Optional: 32-byte string for signing URLs."
|
|
@@ -66,14 +79,17 @@
|
|
|
66
79
|
"additionalProperties": true
|
|
67
80
|
},
|
|
68
81
|
"env": {
|
|
69
|
-
"required": [
|
|
70
|
-
"optional": [
|
|
82
|
+
"required": [
|
|
71
83
|
"TAVILY_API_KEY",
|
|
72
|
-
"BRAVE_API_KEY"
|
|
84
|
+
"BRAVE_API_KEY"
|
|
85
|
+
],
|
|
86
|
+
"optional": [
|
|
73
87
|
"PUBLIC_TUNNEL_URL",
|
|
74
88
|
"NGROK_AUTHTOKEN",
|
|
75
89
|
"FILE_SERVER_HOST",
|
|
76
90
|
"FILE_SERVER_PORT",
|
|
91
|
+
"WORKSPACE_DIR",
|
|
92
|
+
"NGROK_API_PORT",
|
|
77
93
|
"URL_SIGNING_SECRET"
|
|
78
94
|
]
|
|
79
95
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-syncralis",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "An industry-grade file sharing, secure download, and load-balanced gateway designed for high-availability OpenClaw environments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./server.js",
|
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
],
|
|
25
25
|
"author": "PSLKK <coregravity0.0.0@protonmail.com> (https://pslkk.space)",
|
|
26
26
|
"license": "ISC",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/pslkk/openclaw-syncralis.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/pslkk/openclaw-syncralis/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/pslkk/openclaw-syncralis#readme",
|
|
27
35
|
"dependencies": {
|
|
28
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
37
|
"dotenv": "^17.4.2",
|
|
@@ -56,7 +64,10 @@
|
|
|
56
64
|
},
|
|
57
65
|
"configSchema": {
|
|
58
66
|
"type": "object",
|
|
59
|
-
"required": [
|
|
67
|
+
"required": [
|
|
68
|
+
"TAVILY_API_KEY",
|
|
69
|
+
"BRAVE_API_KEY"
|
|
70
|
+
],
|
|
60
71
|
"properties": {
|
|
61
72
|
"FILE_SERVER_PORT": {
|
|
62
73
|
"type": "number",
|
|
@@ -92,6 +103,16 @@
|
|
|
92
103
|
"type": "string",
|
|
93
104
|
"description": "Authentication token for the Ngrok service."
|
|
94
105
|
},
|
|
106
|
+
"WORKSPACE_DIR": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "Optional: Custom absolute path for the workspace volume. Leave blank to default to ~/.openclaw/workspace",
|
|
109
|
+
"default": ""
|
|
110
|
+
},
|
|
111
|
+
"NGROK_API_PORT": {
|
|
112
|
+
"type": "number",
|
|
113
|
+
"default": 4040,
|
|
114
|
+
"description": "Optional: The local port Ngrok uses for its API (used for auto-discovery). Default is 4040."
|
|
115
|
+
},
|
|
95
116
|
"URL_SIGNING_SECRET": {
|
|
96
117
|
"type": "string",
|
|
97
118
|
"description": "Optional: 32-byte string for signing URLs."
|
|
@@ -100,14 +121,17 @@
|
|
|
100
121
|
"additionalProperties": true
|
|
101
122
|
},
|
|
102
123
|
"env": {
|
|
103
|
-
"required": [
|
|
104
|
-
"optional": [
|
|
124
|
+
"required": [
|
|
105
125
|
"TAVILY_API_KEY",
|
|
106
|
-
"BRAVE_API_KEY"
|
|
126
|
+
"BRAVE_API_KEY"
|
|
127
|
+
],
|
|
128
|
+
"optional": [
|
|
107
129
|
"PUBLIC_TUNNEL_URL",
|
|
108
130
|
"NGROK_AUTHTOKEN",
|
|
109
131
|
"FILE_SERVER_HOST",
|
|
110
132
|
"FILE_SERVER_PORT",
|
|
133
|
+
"WORKSPACE_DIR",
|
|
134
|
+
"NGROK_API_PORT",
|
|
111
135
|
"URL_SIGNING_SECRET"
|
|
112
136
|
]
|
|
113
137
|
}
|
package/server.js
CHANGED
|
@@ -19,14 +19,50 @@ import crypto from 'crypto';
|
|
|
19
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
20
|
dotenv.config({ path: path.resolve(__dirname, '.env') });
|
|
21
21
|
|
|
22
|
+
let activeTunnelUrl = process.env.PUBLIC_TUNNEL_URL;
|
|
23
|
+
if (!activeTunnelUrl) {
|
|
24
|
+
const ngrokApiPort = parseInt(process.env.NGROK_API_PORT, 10) || 4040;
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timeoutId = setTimeout(() => controller.abort(), 2000);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`http://127.0.0.1:${ngrokApiPort}/api/tunnels`, {
|
|
30
|
+
signal: controller.signal
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
clearTimeout(timeoutId);
|
|
34
|
+
|
|
35
|
+
if (response.ok) {
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
if (Array.isArray(data?.tunnels)) {
|
|
38
|
+
const httpsTunnel = data.tunnels.find(t =>
|
|
39
|
+
typeof t.public_url === 'string' && t.public_url.startsWith('https://')
|
|
40
|
+
);
|
|
41
|
+
if (httpsTunnel) {
|
|
42
|
+
activeTunnelUrl = httpsTunnel.public_url;
|
|
43
|
+
console.log(`\n\x1b[32m[System]\x1b[0m Auto-discovered active Ngrok tunnel: ${activeTunnelUrl}\n`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
clearTimeout(timeoutId);
|
|
49
|
+
if (error.name === 'AbortError') {
|
|
50
|
+
console.error(`\n\x1b[33m[Warning]\x1b[0m Ngrok auto-discovery timed out on port ${ngrokApiPort}.`);
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`\n\x1b[33m[Warning]\x1b[0m PUBLIC_TUNNEL_URL is empty and local Ngrok was not detected.`);
|
|
53
|
+
}
|
|
54
|
+
console.error(`External download links will fail. Operating in Local-Only Mode.\n`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
22
58
|
const GATEWAY_CONFIG = {
|
|
23
59
|
host: process.env.FILE_SERVER_HOST || '127.0.0.1',
|
|
24
60
|
port: parseInt(process.env.FILE_SERVER_PORT, 10) || 8080,
|
|
25
61
|
//workspace: path.join(os.homedir(), '.openclaw', 'workspace'),
|
|
26
62
|
tavilyKey: process.env.TAVILY_API_KEY,
|
|
27
63
|
braveKey: process.env.BRAVE_API_KEY,
|
|
28
|
-
tunnelUrl:
|
|
29
|
-
ngrokToken: process.env.NGROK_AUTHTOKEN,
|
|
64
|
+
tunnelUrl: activeTunnelUrl,
|
|
65
|
+
//ngrokToken: process.env.NGROK_AUTHTOKEN,
|
|
30
66
|
signingSecret: process.env.URL_SIGNING_SECRET || crypto.randomBytes(32).toString('hex')
|
|
31
67
|
};
|
|
32
68
|
|
|
@@ -36,13 +72,20 @@ let requestCount = 0;
|
|
|
36
72
|
|
|
37
73
|
const require = createRequire(import.meta.url);
|
|
38
74
|
const pdf = require("pdf-parse");
|
|
75
|
+
const pkg = require("./package.json");
|
|
76
|
+
|
|
77
|
+
// Check for version flags before starting the server
|
|
78
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
79
|
+
console.log(`openclaw-syncralis v${pkg.version}`);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
39
82
|
|
|
40
|
-
const WORKSPACE_DIR = path.join(os.homedir(), '.openclaw', 'workspace');
|
|
83
|
+
const WORKSPACE_DIR = process.env.WORKSPACE_DIR || path.join(os.homedir(), '.openclaw', 'workspace');
|
|
41
84
|
const MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
42
85
|
|
|
43
86
|
function generateSignedUrl(filename, expirationMinutes = 60) {
|
|
44
87
|
if (!GATEWAY_CONFIG.tunnelUrl) {
|
|
45
|
-
|
|
88
|
+
throw new Error("PUBLIC_TUNNEL_URL is not configured.");
|
|
46
89
|
}
|
|
47
90
|
|
|
48
91
|
const safeFilename = path.basename(filename);
|
|
@@ -72,7 +115,7 @@ async function getSecurePath(requestedPath) {
|
|
|
72
115
|
}
|
|
73
116
|
|
|
74
117
|
const server = new Server(
|
|
75
|
-
{ name: "openclaw-syncralis", version: "2.
|
|
118
|
+
{ name: "openclaw-syncralis", version: "2.2.0" },
|
|
76
119
|
{ capabilities: { tools: {} } }
|
|
77
120
|
);
|
|
78
121
|
|
|
@@ -424,7 +467,7 @@ async function main() {
|
|
|
424
467
|
startSecureFileServer();
|
|
425
468
|
const transport = new StdioServerTransport();
|
|
426
469
|
await server.connect(transport);
|
|
427
|
-
console.error("[System]
|
|
470
|
+
console.error("[System] openclaw-syncralis MCP running securely");
|
|
428
471
|
} catch (error) {
|
|
429
472
|
console.error("[Fatal] Server connection failed:", error);
|
|
430
473
|
process.exit(1);
|