@ckcloudai.com/clawrouter 0.0.1

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.
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "ckcloud",
3
+ "name": "ckcloud",
4
+ "description": "Smart LLM router — 41+ models, x402 micropayments, 92% cost savings",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "walletKey": {
9
+ "type": "string",
10
+ "description": "EVM wallet private key (0x...). Optional — auto-generated if not set."
11
+ },
12
+ "routing": {
13
+ "type": "object",
14
+ "description": "Override default routing configuration"
15
+ }
16
+ }
17
+ },
18
+ "uiHints": {
19
+ "walletKey": {
20
+ "label": "Wallet Private Key",
21
+ "sensitive": true,
22
+ "placeholder": "0x... (optional — auto-generated)"
23
+ }
24
+ }
25
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@ckcloudai.com/clawrouter",
3
+ "version": "0.0.1",
4
+ "description": "openclaw plugin for ckcloud - save your llm cost",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "ckcloud": "./dist/cli.js"
10
+ },
11
+ "openclaw": {
12
+ "extensions": [
13
+ "./dist/index.js"
14
+ ]
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "types": "./dist/index.d.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "scripts",
25
+ "openclaw.plugin.json"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "test": "vitest run",
31
+ "lint": "eslint src/"
32
+ },
33
+ "keywords": [
34
+ "llm",
35
+ "router",
36
+ "openrouter",
37
+ "ai",
38
+ "openclaw",
39
+ "openai",
40
+ "anthropic",
41
+ "gemini",
42
+ "deepseek"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://git.ucloudadmin.com/yai/ckcloud/clawrouter"
47
+ },
48
+ "author": "",
49
+ "license": "ISC",
50
+ "engines": {
51
+ "node": ">=20"
52
+ },
53
+ "dependencies": {
54
+ "@scure/bip32": "^1.6.0",
55
+ "@scure/bip39": "^1.5.0",
56
+ "@solana/kit": "^5.0.0",
57
+ "@x402/evm": "^2.4.0",
58
+ "@x402/fetch": "^2.4.0",
59
+ "@x402/svm": "^2.4.0",
60
+ "ethers": "^6.16.0",
61
+ "viem": "^2.39.3"
62
+ },
63
+ "devDependencies": {
64
+ "@types/node": "^25.4.0",
65
+ "tsup": "^8.5.1",
66
+ "typescript": "^5.9.3"
67
+ }
68
+ }
@@ -0,0 +1,343 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ kill_port_processes() {
7
+ local port="$1"
8
+ local pids=""
9
+
10
+ if command -v lsof >/dev/null 2>&1; then
11
+ pids="$(lsof -ti :"$port" 2>/dev/null || true)"
12
+ elif command -v fuser >/dev/null 2>&1; then
13
+ pids="$(fuser "$port"/tcp 2>/dev/null || true)"
14
+ elif command -v ss >/dev/null 2>&1; then
15
+ pids="$(ss -lptn "sport = :$port" 2>/dev/null | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)"
16
+ elif command -v netstat >/dev/null 2>&1; then
17
+ pids="$(netstat -nlpt 2>/dev/null | awk -v p=":$port" '$4 ~ p"$" {split($7,a,"/"); if (a[1] ~ /^[0-9]+$/) print a[1]}' | sort -u)"
18
+ else
19
+ echo " Warning: could not find lsof/fuser/ss/netstat; skipping proxy stop"
20
+ return 0
21
+ fi
22
+
23
+ if [ -n "$pids" ]; then
24
+ echo "$pids" | xargs kill -9 2>/dev/null || true
25
+ fi
26
+ }
27
+
28
+ echo "🦞 ckcloud Reinstall"
29
+ echo ""
30
+
31
+ # 0. Back up wallet key BEFORE removing anything
32
+ WALLET_FILE="$HOME/.openclaw/ckcloud/wallet.key"
33
+ WALLET_BACKUP=""
34
+
35
+ echo "→ Backing up wallet..."
36
+ if [ -f "$WALLET_FILE" ]; then
37
+ WALLET_KEY=$(cat "$WALLET_FILE" | tr -d '[:space:]')
38
+ KEY_LEN=${#WALLET_KEY}
39
+ if [[ "$WALLET_KEY" == 0x* ]] && [ "$KEY_LEN" -eq 66 ]; then
40
+ WALLET_BACKUP="$HOME/.openclaw/ckcloud/wallet.key.bak.$(date +%s)"
41
+ cp "$WALLET_FILE" "$WALLET_BACKUP"
42
+ chmod 600 "$WALLET_BACKUP"
43
+ echo " ✓ Wallet backed up to: $WALLET_BACKUP"
44
+ else
45
+ echo " ⚠ Wallet file exists but has invalid format — skipping backup"
46
+ fi
47
+ else
48
+ echo " ℹ No existing wallet found"
49
+ fi
50
+ echo ""
51
+
52
+ # 1. Remove plugin files
53
+ echo "→ Removing plugin files..."
54
+ rm -rf ~/.openclaw/extensions/ckcloud
55
+
56
+ # 2. Clean config entries
57
+ echo "→ Cleaning config entries..."
58
+ node -e "
59
+ const f = require('os').homedir() + '/.openclaw/openclaw.json';
60
+ const fs = require('fs');
61
+ if (!fs.existsSync(f)) {
62
+ console.log(' No openclaw.json found, skipping');
63
+ process.exit(0);
64
+ }
65
+
66
+ let c;
67
+ try {
68
+ c = JSON.parse(fs.readFileSync(f, 'utf8'));
69
+ } catch (err) {
70
+ const backupPath = f + '.corrupt.' + Date.now();
71
+ console.error(' ERROR: Invalid JSON in openclaw.json');
72
+ console.error(' ' + err.message);
73
+ try {
74
+ fs.copyFileSync(f, backupPath);
75
+ console.log(' Backed up to: ' + backupPath);
76
+ } catch {}
77
+ console.log(' Skipping config cleanup...');
78
+ process.exit(0);
79
+ }
80
+
81
+ // Clean plugin entries
82
+ if (c.plugins?.entries?.ckcloud) delete c.plugins.entries.ckcloud;
83
+ if (c.plugins?.installs?.ckcloud) delete c.plugins.installs.ckcloud;
84
+ // Clean plugins.allow (removes stale ckcloud reference)
85
+ if (Array.isArray(c.plugins?.allow)) {
86
+ c.plugins.allow = c.plugins.allow.filter(p => p !== 'ckcloud' && p !== '@ckcloudai.com/clawrouter');
87
+ }
88
+ // Remove deprecated model aliases from picker
89
+ const deprecated = ['ckcloud/mini', 'ckcloud/nvidia', 'ckcloud/gpt', 'ckcloud/o3', 'ckcloud/grok'];
90
+ if (c.agents?.defaults?.models) {
91
+ for (const key of deprecated) {
92
+ if (c.agents.defaults.models[key]) {
93
+ delete c.agents.defaults.models[key];
94
+ console.log(' Removed deprecated alias: ' + key);
95
+ }
96
+ }
97
+ }
98
+ fs.writeFileSync(f, JSON.stringify(c, null, 2));
99
+ console.log(' Config cleaned');
100
+ "
101
+
102
+ # 3. Kill old proxy
103
+ echo "→ Stopping old proxy..."
104
+ kill_port_processes 8402
105
+
106
+ # 3.1. Remove stale models.json so it gets regenerated with apiKey
107
+ echo "→ Cleaning models cache..."
108
+ rm -f ~/.openclaw/agents/main/agent/models.json 2>/dev/null || true
109
+
110
+ # 4. Inject auth profile (ensures ckcloud provider is recognized)
111
+ echo "→ Injecting auth profile..."
112
+ node -e "
113
+ const os = require('os');
114
+ const fs = require('fs');
115
+ const path = require('path');
116
+ const authDir = path.join(os.homedir(), '.openclaw', 'agents', 'main', 'agent');
117
+ const authPath = path.join(authDir, 'auth-profiles.json');
118
+
119
+ // Create directory if needed
120
+ fs.mkdirSync(authDir, { recursive: true });
121
+
122
+ // Load or create auth-profiles.json with correct OpenClaw format
123
+ let store = { version: 1, profiles: {} };
124
+ if (fs.existsSync(authPath)) {
125
+ try {
126
+ const existing = JSON.parse(fs.readFileSync(authPath, 'utf8'));
127
+ // Migrate if old format (no version field)
128
+ if (existing.version && existing.profiles) {
129
+ store = existing;
130
+ } else {
131
+ // Old format - keep version/profiles structure, old data is discarded
132
+ store = { version: 1, profiles: {} };
133
+ }
134
+ } catch (err) {
135
+ console.log(' Warning: Could not parse auth-profiles.json, creating fresh');
136
+ }
137
+ }
138
+
139
+ // Inject ckcloud auth if missing (OpenClaw format: profiles['provider:profileId'])
140
+ const profileKey = 'ckcloud:default';
141
+ if (!store.profiles[profileKey]) {
142
+ store.profiles[profileKey] = {
143
+ type: 'api_key',
144
+ provider: 'ckcloud',
145
+ key: 'x402-proxy-handles-auth'
146
+ };
147
+ fs.writeFileSync(authPath, JSON.stringify(store, null, 2));
148
+ console.log(' Auth profile created');
149
+ } else {
150
+ console.log(' Auth profile already exists');
151
+ }
152
+ "
153
+
154
+ # 5. Ensure apiKey is present for /model picker (but DON'T override default model)
155
+ echo "→ Finalizing setup..."
156
+ node -e "
157
+ const os = require('os');
158
+ const fs = require('fs');
159
+ const path = require('path');
160
+ const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
161
+
162
+ if (fs.existsSync(configPath)) {
163
+ try {
164
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
165
+ let changed = false;
166
+
167
+ // Ensure ckcloud provider has apiKey (required by ModelRegistry for /model picker)
168
+ if (config.models?.providers?.ckcloud && !config.models.providers.ckcloud.apiKey) {
169
+ config.models.providers.ckcloud.apiKey = 'x402-proxy-handles-auth';
170
+ console.log(' Added apiKey to ckcloud provider config');
171
+ changed = true;
172
+ }
173
+
174
+ if (changed) {
175
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
176
+ }
177
+ } catch (e) {
178
+ console.log(' Could not update config:', e.message);
179
+ }
180
+ } else {
181
+ console.log(' No openclaw.json found, skipping');
182
+ }
183
+ "
184
+
185
+ # 6. Install plugin (config is ready, but no allow list yet to avoid validation error)
186
+ echo "→ Installing ckcloud..."
187
+
188
+ openclaw plugins install @ckcloudai.com/clawrouter
189
+
190
+ # 6.1. Verify installation (check dist/ files exist)
191
+ echo "→ Verifying installation..."
192
+ DIST_PATH="$HOME/.openclaw/extensions/ckcloud/dist/index.js"
193
+ if [ ! -f "$DIST_PATH" ]; then
194
+ echo " ⚠️ dist/ files missing, clearing npm cache and retrying..."
195
+ npm cache clean --force 2>/dev/null || true
196
+ rm -rf ~/.openclaw/extensions/ckcloud
197
+
198
+ openclaw plugins install @ckcloudai.com/clawrouter
199
+
200
+ if [ ! -f "$DIST_PATH" ]; then
201
+ echo " ❌ Installation failed - dist/index.js still missing"
202
+ echo " Please report this issue"
203
+ exit 1
204
+ fi
205
+ fi
206
+ echo " ✓ dist/index.js verified"
207
+
208
+ # 6.2. Populate model allowlist so top ckcloud models appear in /model picker
209
+ echo "→ Populating model allowlist..."
210
+ node -e "
211
+ const os = require('os');
212
+ const fs = require('fs');
213
+ const path = require('path');
214
+
215
+ const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
216
+ if (!fs.existsSync(configPath)) {
217
+ console.log(' No openclaw.json found, skipping');
218
+ process.exit(0);
219
+ }
220
+
221
+ try {
222
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
223
+ let changed = false;
224
+
225
+ // Ensure provider exists with apiKey
226
+ if (!config.models) config.models = {};
227
+ if (!config.models.providers) config.models.providers = {};
228
+ if (!config.models.providers.ckcloud) {
229
+ config.models.providers.ckcloud = { api: 'openai-completions', models: [] };
230
+ changed = true;
231
+ }
232
+ if (!config.models.providers.ckcloud.apiKey) {
233
+ config.models.providers.ckcloud.apiKey = 'x402-proxy-handles-auth';
234
+ changed = true;
235
+ }
236
+
237
+ // models for the /model picker
238
+ const TOP_MODELS = [
239
+ 'auto', 'free', 'eco', 'premium',
240
+ 'moonshot/kimi-k2.5'
241
+ ];
242
+
243
+ if (!config.agents) config.agents = {};
244
+ if (!config.agents.defaults) config.agents.defaults = {};
245
+ if (!config.agents.defaults.models || typeof config.agents.defaults.models !== 'object') {
246
+ config.agents.defaults.models = {};
247
+ changed = true;
248
+ }
249
+
250
+ const allowlist = config.agents.defaults.models;
251
+ // Clean out old ckcloud entries not in TOP_MODELS
252
+ const topSet = new Set(TOP_MODELS.map(id => 'ckcloud/' + id));
253
+ for (const key of Object.keys(allowlist)) {
254
+ if (key.startsWith('ckcloud/') && !topSet.has(key)) {
255
+ delete allowlist[key];
256
+ changed = true;
257
+ }
258
+ }
259
+ let added = 0;
260
+ for (const id of TOP_MODELS) {
261
+ const key = 'ckcloud/' + id;
262
+ if (!allowlist[key]) {
263
+ allowlist[key] = {};
264
+ added++;
265
+ }
266
+ }
267
+ if (added > 0) {
268
+ changed = true;
269
+ console.log(' Added ' + added + ' models to allowlist (' + TOP_MODELS.length + ' total)');
270
+ } else {
271
+ console.log(' Allowlist already up to date');
272
+ }
273
+ if (changed) {
274
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
275
+ }
276
+ } catch (err) {
277
+ console.log(' Could not update config:', err.message);
278
+ }
279
+ "
280
+
281
+ # 7. Add plugin to allow list (done AFTER install so plugin files exist for validation)
282
+ echo "→ Adding to plugins allow list..."
283
+ node -e "
284
+ const os = require('os');
285
+ const fs = require('fs');
286
+ const path = require('path');
287
+ const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
288
+
289
+ if (fs.existsSync(configPath)) {
290
+ try {
291
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
292
+
293
+ // Ensure plugins.allow exists and includes ckcloud
294
+ if (!config.plugins) config.plugins = {};
295
+ if (!Array.isArray(config.plugins.allow)) {
296
+ config.plugins.allow = [];
297
+ }
298
+ if (!config.plugins.allow.includes('ckcloud') && !config.plugins.allow.includes('@ckcloudai.com/clawrouter')) {
299
+ config.plugins.allow.push('ckcloud');
300
+ console.log(' Added ckcloud to plugins.allow');
301
+ } else {
302
+ console.log(' Plugin already in allow list');
303
+ }
304
+
305
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
306
+ } catch (e) {
307
+ console.log(' Could not update config:', e.message);
308
+ }
309
+ } else {
310
+ console.log(' No openclaw.json found, skipping');
311
+ }
312
+ "
313
+
314
+ # Final: verify wallet survived reinstall
315
+ echo "→ Verifying wallet integrity..."
316
+ if [ -f "$WALLET_FILE" ]; then
317
+ CURRENT_KEY=$(cat "$WALLET_FILE" | tr -d '[:space:]')
318
+ CURRENT_LEN=${#CURRENT_KEY}
319
+ if [[ "$CURRENT_KEY" == 0x* ]] && [ "$CURRENT_LEN" -eq 66 ]; then
320
+ echo " ✓ Wallet key intact"
321
+ else
322
+ if [ -n "$WALLET_BACKUP" ] && [ -f "$WALLET_BACKUP" ]; then
323
+ cp "$WALLET_BACKUP" "$WALLET_FILE"
324
+ chmod 600 "$WALLET_FILE"
325
+ echo " ✓ Wallet restored from backup"
326
+ fi
327
+ fi
328
+ else
329
+ if [ -n "$WALLET_BACKUP" ] && [ -f "$WALLET_BACKUP" ]; then
330
+ mkdir -p "$(dirname "$WALLET_FILE")"
331
+ cp "$WALLET_BACKUP" "$WALLET_FILE"
332
+ chmod 600 "$WALLET_FILE"
333
+ echo " ✓ Wallet restored from backup: $WALLET_BACKUP"
334
+ fi
335
+ fi
336
+
337
+ echo ""
338
+ echo "✓ Done! Smart routing enabled by default."
339
+ echo ""
340
+ echo "Run: openclaw gateway restart"
341
+ echo ""
342
+
343
+ echo "To uninstall: bash ~/.openclaw/extensions/ckcloud/scripts/uninstall.sh"
@@ -0,0 +1,157 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ kill_port_processes() {
5
+ local port="$1"
6
+ local pids=""
7
+
8
+ if command -v lsof >/dev/null 2>&1; then
9
+ pids="$(lsof -ti :"$port" 2>/dev/null || true)"
10
+ elif command -v fuser >/dev/null 2>&1; then
11
+ pids="$(fuser "$port"/tcp 2>/dev/null || true)"
12
+ elif command -v ss >/dev/null 2>&1; then
13
+ pids="$(ss -lptn "sport = :$port" 2>/dev/null | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)"
14
+ elif command -v netstat >/dev/null 2>&1; then
15
+ pids="$(netstat -nlpt 2>/dev/null | awk -v p=":$port" '$4 ~ p"$" {split($7,a,"/"); if (a[1] ~ /^[0-9]+$/) print a[1]}' | sort -u)"
16
+ else
17
+ echo " Warning: could not find lsof/fuser/ss/netstat; skipping proxy stop"
18
+ return 0
19
+ fi
20
+
21
+ if [ -n "$pids" ]; then
22
+ echo "$pids" | xargs kill -9 2>/dev/null || true
23
+ fi
24
+ }
25
+
26
+ echo "🦞 ckcloud plugin uninstall"
27
+ echo ""
28
+
29
+ # 1. Stop proxy
30
+ echo "→ Stopping proxy..."
31
+ kill_port_processes 8402
32
+
33
+ # 2. Remove plugin files
34
+ echo "→ Removing plugin files..."
35
+ rm -rf ~/.openclaw/extensions/ckcloud
36
+
37
+ # 3. Clean openclaw.json
38
+ echo "→ Cleaning openclaw.json..."
39
+ node -e "
40
+ const os = require('os');
41
+ const fs = require('fs');
42
+ const path = require('path');
43
+ const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
44
+
45
+ if (!fs.existsSync(configPath)) {
46
+ console.log(' No openclaw.json found, skipping');
47
+ process.exit(0);
48
+ }
49
+
50
+ try {
51
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
52
+ let changed = false;
53
+
54
+ // Remove ckcloud provider
55
+ if (config.models?.providers?.ckcloud) {
56
+ delete config.models.providers.ckcloud;
57
+ console.log(' Removed ckcloud provider');
58
+ changed = true;
59
+ }
60
+
61
+ // Remove plugin entries
62
+ if (config.plugins?.entries?.ckcloud) {
63
+ delete config.plugins.entries.ckcloud;
64
+ changed = true;
65
+ }
66
+ if (config.plugins?.installs?.ckcloud) {
67
+ delete config.plugins.installs.ckcloud;
68
+ changed = true;
69
+ }
70
+
71
+ // Remove from plugins.allow
72
+ if (Array.isArray(config.plugins?.allow)) {
73
+ const before = config.plugins.allow.length;
74
+ config.plugins.allow = config.plugins.allow.filter(
75
+ p => p !== 'ckcloud' && p !== '@ckcloudai.com/clawrouter'
76
+ );
77
+ if (config.plugins.allow.length !== before) {
78
+ console.log(' Removed from plugins.allow');
79
+ changed = true;
80
+ }
81
+ }
82
+
83
+ // Reset default model if it's ckcloud/auto
84
+ if (config.agents?.defaults?.model?.primary === 'ckcloud/auto') {
85
+ delete config.agents.defaults.model.primary;
86
+ console.log(' Reset default model (was ckcloud/auto)');
87
+ changed = true;
88
+ }
89
+
90
+ // Remove ckcloud models from allowlist
91
+ if (config.agents?.defaults?.models) {
92
+ const models = config.agents.defaults.models;
93
+ let removedCount = 0;
94
+ for (const key of Object.keys(models)) {
95
+ if (key.startsWith('ckcloud/')) {
96
+ delete models[key];
97
+ removedCount++;
98
+ }
99
+ }
100
+ if (removedCount > 0) {
101
+ console.log(' Removed ' + removedCount + ' ckcloud models from allowlist');
102
+ changed = true;
103
+ }
104
+ }
105
+
106
+ if (changed) {
107
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
108
+ console.log(' Config cleaned');
109
+ } else {
110
+ console.log(' No changes needed');
111
+ }
112
+ } catch (err) {
113
+ console.error(' Error:', err.message);
114
+ }
115
+ "
116
+
117
+ # 4. Clean auth-profiles.json
118
+ echo "→ Cleaning auth profiles..."
119
+ node -e "
120
+ const os = require('os');
121
+ const fs = require('fs');
122
+ const path = require('path');
123
+ const agentsDir = path.join(os.homedir(), '.openclaw', 'agents');
124
+
125
+ if (!fs.existsSync(agentsDir)) {
126
+ console.log(' No agents directory found');
127
+ process.exit(0);
128
+ }
129
+
130
+ const agents = fs.readdirSync(agentsDir, { withFileTypes: true })
131
+ .filter(d => d.isDirectory())
132
+ .map(d => d.name);
133
+
134
+ for (const agentId of agents) {
135
+ const authPath = path.join(agentsDir, agentId, 'agent', 'auth-profiles.json');
136
+ if (!fs.existsSync(authPath)) continue;
137
+
138
+ try {
139
+ const store = JSON.parse(fs.readFileSync(authPath, 'utf8'));
140
+ if (store.profiles?.['ckcloud:default']) {
141
+ delete store.profiles['ckcloud:default'];
142
+ fs.writeFileSync(authPath, JSON.stringify(store, null, 2));
143
+ console.log(' Removed ckcloud auth from ' + agentId);
144
+ }
145
+ } catch {}
146
+ }
147
+ "
148
+
149
+ # 5. Clean models cache
150
+ echo "→ Cleaning models cache..."
151
+ rm -f ~/.openclaw/agents/*/agent/models.json 2>/dev/null || true
152
+
153
+ echo ""
154
+ echo "✓ ckcloud uninstalled"
155
+ echo ""
156
+ echo "Restart OpenClaw to apply changes:"
157
+ echo " openclaw gateway restart"