openclaw-safeclaw-plugin 0.7.0 → 0.7.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.
- package/README.md +16 -21
- package/cli.tsx +122 -72
- package/dist/cli.js +125 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
# openclaw-safeclaw-plugin
|
|
2
2
|
|
|
3
|
-
Neurosymbolic governance plugin for OpenClaw AI agents. Validates every tool call, message, and action against
|
|
3
|
+
Neurosymbolic governance plugin for OpenClaw AI agents. Validates every tool call, message, and action against safety constraints before execution.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
npm install -g openclaw-safeclaw-plugin
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
1. Sign up at [safeclaw.eu](https://safeclaw.eu) and create an API key
|
|
14
|
+
2. Install and connect:
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
|
-
|
|
17
|
+
npm install -g openclaw-safeclaw-plugin
|
|
18
|
+
safeclaw connect <your-api-key>
|
|
19
|
+
safeclaw restart-openclaw
|
|
17
20
|
```
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
That's it. Every tool call your AI agent makes is now governed by SafeClaw.
|
|
20
23
|
|
|
21
|
-
##
|
|
24
|
+
## Commands
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# Start the SafeClaw service
|
|
29
|
-
git clone https://github.com/tendlyeu/SafeClaw.git
|
|
30
|
-
cd SafeClaw/safeclaw-service
|
|
31
|
-
python -m venv .venv && source .venv/bin/activate
|
|
32
|
-
pip install -e ".[dev]"
|
|
33
|
-
safeclaw init --user-id yourname
|
|
34
|
-
safeclaw serve
|
|
35
|
-
# Engine ready on http://localhost:8420
|
|
26
|
+
```
|
|
27
|
+
safeclaw connect <api-key> Connect to SafeClaw and register with OpenClaw
|
|
28
|
+
safeclaw setup Register plugin with OpenClaw (no key needed)
|
|
29
|
+
safeclaw tui Open the interactive settings TUI
|
|
30
|
+
safeclaw restart-openclaw Restart the OpenClaw daemon
|
|
36
31
|
```
|
|
37
32
|
|
|
38
33
|
## What It Does
|
|
@@ -60,11 +55,11 @@ Set via environment variables or `~/.safeclaw/config.json`:
|
|
|
60
55
|
| Variable | Default | Description |
|
|
61
56
|
|----------|---------|-------------|
|
|
62
57
|
| `SAFECLAW_URL` | `https://api.safeclaw.eu/api/v1` | SafeClaw service URL |
|
|
63
|
-
| `SAFECLAW_API_KEY` | *(empty)* | API key
|
|
58
|
+
| `SAFECLAW_API_KEY` | *(empty)* | API key (set automatically by `safeclaw connect`) |
|
|
64
59
|
| `SAFECLAW_TIMEOUT_MS` | `500` | Request timeout in ms |
|
|
65
60
|
| `SAFECLAW_ENABLED` | `true` | Set `false` to disable |
|
|
66
61
|
| `SAFECLAW_ENFORCEMENT` | `enforce` | `enforce`, `warn-only`, `audit-only`, or `disabled` |
|
|
67
|
-
| `SAFECLAW_FAIL_MODE` | `
|
|
62
|
+
| `SAFECLAW_FAIL_MODE` | `open` | `open` (allow on failure) or `closed` (block on failure) |
|
|
68
63
|
|
|
69
64
|
## Enforcement Modes
|
|
70
65
|
|
package/cli.tsx
CHANGED
|
@@ -2,81 +2,38 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render } from 'ink';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
|
-
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync
|
|
6
|
-
import { join
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
8
|
import App from './tui/App.js';
|
|
10
9
|
|
|
11
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
|
|
13
|
-
function readJson(path: string): Record<string, unknown> {
|
|
14
|
-
try {
|
|
15
|
-
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
16
|
-
} catch {
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
10
|
function registerWithOpenClaw(): boolean {
|
|
22
|
-
//
|
|
23
|
-
const pluginRoot = join(__dirname, '..');
|
|
24
|
-
const extensionsDir = join(homedir(), '.openclaw', 'extensions');
|
|
25
|
-
const linkPath = join(extensionsDir, 'safeclaw');
|
|
26
|
-
|
|
27
|
-
// 1. Create symlink in ~/.openclaw/extensions/safeclaw -> plugin root
|
|
11
|
+
// Use OpenClaw's native plugin install mechanism
|
|
28
12
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
13
|
+
execSync('openclaw plugins install openclaw-safeclaw-plugin', {
|
|
14
|
+
encoding: 'utf-8',
|
|
15
|
+
timeout: 30000,
|
|
16
|
+
stdio: 'pipe',
|
|
17
|
+
});
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
// Fallback: try linking from the global npm install location
|
|
21
|
+
try {
|
|
22
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
23
|
+
const pluginPath = join(globalRoot, 'openclaw-safeclaw-plugin');
|
|
24
|
+
if (existsSync(pluginPath)) {
|
|
25
|
+
execSync(`openclaw plugins install --link "${pluginPath}"`, {
|
|
26
|
+
encoding: 'utf-8',
|
|
27
|
+
timeout: 15000,
|
|
28
|
+
stdio: 'pipe',
|
|
29
|
+
});
|
|
30
|
+
return true;
|
|
42
31
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
symlinkSync(pluginRoot, linkPath);
|
|
32
|
+
} catch {
|
|
33
|
+
// Both methods failed
|
|
46
34
|
}
|
|
47
|
-
} catch (e) {
|
|
48
|
-
console.warn(`Warning: Could not create extension symlink: ${e instanceof Error ? e.message : e}`);
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 2. Enable plugin in ~/.openclaw/openclaw.json
|
|
53
|
-
const openclawConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
54
|
-
const ocConfig = readJson(openclawConfigPath);
|
|
55
|
-
|
|
56
|
-
if (!ocConfig.plugins || typeof ocConfig.plugins !== 'object') {
|
|
57
|
-
ocConfig.plugins = {};
|
|
58
35
|
}
|
|
59
|
-
|
|
60
|
-
if (!plugins.entries || typeof plugins.entries !== 'object') {
|
|
61
|
-
plugins.entries = {};
|
|
62
|
-
}
|
|
63
|
-
const entries = plugins.entries as Record<string, unknown>;
|
|
64
|
-
|
|
65
|
-
// Only add/update if not already configured
|
|
66
|
-
if (!entries.safeclaw || typeof entries.safeclaw !== 'object') {
|
|
67
|
-
entries.safeclaw = { enabled: true };
|
|
68
|
-
} else {
|
|
69
|
-
(entries.safeclaw as Record<string, unknown>).enabled = true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
writeFileSync(openclawConfigPath, JSON.stringify(ocConfig, null, 2) + '\n');
|
|
74
|
-
} catch (e) {
|
|
75
|
-
console.warn(`Warning: Could not update OpenClaw config: ${e instanceof Error ? e.message : e}`);
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return true;
|
|
36
|
+
return false;
|
|
80
37
|
}
|
|
81
38
|
|
|
82
39
|
const args = process.argv.slice(2);
|
|
@@ -94,6 +51,12 @@ if (command === 'connect') {
|
|
|
94
51
|
process.exit(1);
|
|
95
52
|
}
|
|
96
53
|
|
|
54
|
+
if (!apiKey.startsWith('sc_')) {
|
|
55
|
+
console.error('Error: Invalid API key. Keys start with "sc_".');
|
|
56
|
+
console.error('Get your key at https://safeclaw.eu/dashboard');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
97
60
|
const configDir = join(homedir(), '.safeclaw');
|
|
98
61
|
const configPath = join(configDir, 'config.json');
|
|
99
62
|
|
|
@@ -122,18 +85,18 @@ if (command === 'connect') {
|
|
|
122
85
|
console.log(`Connected! API key saved to ${configPath}`);
|
|
123
86
|
|
|
124
87
|
// Register with OpenClaw
|
|
88
|
+
console.log('Registering SafeClaw plugin with OpenClaw...');
|
|
125
89
|
const registered = registerWithOpenClaw();
|
|
126
90
|
if (registered) {
|
|
127
91
|
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
128
92
|
console.log('');
|
|
129
93
|
console.log('Restart OpenClaw to activate:');
|
|
130
94
|
console.log(' safeclaw restart-openclaw');
|
|
131
|
-
console.log(' — or —');
|
|
132
|
-
console.log(' openclaw daemon restart');
|
|
133
95
|
} else {
|
|
134
96
|
console.log('');
|
|
135
97
|
console.log('Could not auto-register with OpenClaw.');
|
|
136
|
-
console.log('Register manually:
|
|
98
|
+
console.log('Register manually:');
|
|
99
|
+
console.log(' openclaw plugins install openclaw-safeclaw-plugin');
|
|
137
100
|
}
|
|
138
101
|
} else if (command === 'tui') {
|
|
139
102
|
render(React.createElement(App));
|
|
@@ -148,7 +111,7 @@ if (command === 'connect') {
|
|
|
148
111
|
process.exit(1);
|
|
149
112
|
}
|
|
150
113
|
} else if (command === 'setup') {
|
|
151
|
-
|
|
114
|
+
console.log('Registering SafeClaw plugin with OpenClaw...');
|
|
152
115
|
const registered = registerWithOpenClaw();
|
|
153
116
|
if (registered) {
|
|
154
117
|
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
@@ -158,7 +121,93 @@ if (command === 'connect') {
|
|
|
158
121
|
console.log(' 2. Run: safeclaw connect <your-api-key>');
|
|
159
122
|
console.log(' 3. Run: safeclaw restart-openclaw');
|
|
160
123
|
} else {
|
|
161
|
-
console.log('Could not auto-register.
|
|
124
|
+
console.log('Could not auto-register.');
|
|
125
|
+
console.log('Try: openclaw plugins install openclaw-safeclaw-plugin');
|
|
126
|
+
}
|
|
127
|
+
} else if (command === 'status') {
|
|
128
|
+
const configPath = join(homedir(), '.safeclaw', 'config.json');
|
|
129
|
+
let allOk = true;
|
|
130
|
+
|
|
131
|
+
// 1. Config file
|
|
132
|
+
if (existsSync(configPath)) {
|
|
133
|
+
console.log('[ok] Config file: ' + configPath);
|
|
134
|
+
} else {
|
|
135
|
+
console.log('[!!] Config file not found. Run: safeclaw connect <api-key>');
|
|
136
|
+
allOk = false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 2. API key
|
|
140
|
+
let apiKey = '';
|
|
141
|
+
let serviceUrl = 'https://api.safeclaw.eu/api/v1';
|
|
142
|
+
if (existsSync(configPath)) {
|
|
143
|
+
try {
|
|
144
|
+
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
145
|
+
const remote = cfg.remote as Record<string, string> | undefined;
|
|
146
|
+
apiKey = remote?.apiKey ?? '';
|
|
147
|
+
serviceUrl = remote?.serviceUrl ?? serviceUrl;
|
|
148
|
+
} catch { /* ignore */ }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (apiKey && apiKey.startsWith('sc_')) {
|
|
152
|
+
console.log('[ok] API key: configured (sc_...)');
|
|
153
|
+
} else if (apiKey) {
|
|
154
|
+
console.log('[!!] API key: invalid (must start with sc_)');
|
|
155
|
+
allOk = false;
|
|
156
|
+
} else {
|
|
157
|
+
console.log('[!!] API key: not set. Run: safeclaw connect <api-key>');
|
|
158
|
+
allOk = false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 3. SafeClaw service reachable
|
|
162
|
+
try {
|
|
163
|
+
const res = await fetch(`${serviceUrl}/health`, {
|
|
164
|
+
signal: AbortSignal.timeout(5000),
|
|
165
|
+
headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {},
|
|
166
|
+
});
|
|
167
|
+
if (res.ok) {
|
|
168
|
+
const data = await res.json() as Record<string, unknown>;
|
|
169
|
+
console.log(`[ok] SafeClaw service: ${data.status ?? 'ok'} (${serviceUrl})`);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(`[!!] SafeClaw service: HTTP ${res.status} (${serviceUrl})`);
|
|
172
|
+
allOk = false;
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
console.log(`[!!] SafeClaw service: unreachable (${serviceUrl})`);
|
|
176
|
+
allOk = false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 4. OpenClaw installed
|
|
180
|
+
try {
|
|
181
|
+
execSync('which openclaw', { encoding: 'utf-8', stdio: 'pipe' });
|
|
182
|
+
console.log('[ok] OpenClaw: installed');
|
|
183
|
+
} catch {
|
|
184
|
+
console.log('[!!] OpenClaw: not found in PATH');
|
|
185
|
+
allOk = false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 5. Plugin registered with OpenClaw
|
|
189
|
+
try {
|
|
190
|
+
const pluginList = execSync('openclaw plugins list', {
|
|
191
|
+
encoding: 'utf-8',
|
|
192
|
+
timeout: 10000,
|
|
193
|
+
stdio: 'pipe',
|
|
194
|
+
});
|
|
195
|
+
if (pluginList.includes('safeclaw')) {
|
|
196
|
+
console.log('[ok] Plugin: registered with OpenClaw');
|
|
197
|
+
} else {
|
|
198
|
+
console.log('[!!] Plugin: not registered with OpenClaw. Run: safeclaw setup');
|
|
199
|
+
allOk = false;
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
console.log('[??] Plugin: could not check OpenClaw plugin list');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Summary
|
|
206
|
+
console.log('');
|
|
207
|
+
if (allOk) {
|
|
208
|
+
console.log('All checks passed. SafeClaw is ready.');
|
|
209
|
+
} else {
|
|
210
|
+
console.log('Some checks failed. Fix the issues above.');
|
|
162
211
|
}
|
|
163
212
|
} else {
|
|
164
213
|
console.log('Usage: safeclaw <command>');
|
|
@@ -166,6 +215,7 @@ if (command === 'connect') {
|
|
|
166
215
|
console.log('Commands:');
|
|
167
216
|
console.log(' connect <api-key> Connect to SafeClaw and register with OpenClaw');
|
|
168
217
|
console.log(' setup Register SafeClaw plugin with OpenClaw (no key needed)');
|
|
218
|
+
console.log(' status Check SafeClaw + OpenClaw connection status');
|
|
169
219
|
console.log(' tui Open the interactive SafeClaw settings TUI');
|
|
170
220
|
console.log(' restart-openclaw Restart the OpenClaw daemon');
|
|
171
221
|
process.exit(0);
|
package/dist/cli.js
CHANGED
|
@@ -2,77 +2,39 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render } from 'ink';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
|
-
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync
|
|
6
|
-
import { join
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
8
|
import App from './tui/App.js';
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
function registerWithOpenClaw() {
|
|
10
|
+
// Use OpenClaw's native plugin install mechanism
|
|
12
11
|
try {
|
|
13
|
-
|
|
12
|
+
execSync('openclaw plugins install openclaw-safeclaw-plugin', {
|
|
13
|
+
encoding: 'utf-8',
|
|
14
|
+
timeout: 30000,
|
|
15
|
+
stdio: 'pipe',
|
|
16
|
+
});
|
|
17
|
+
return true;
|
|
14
18
|
}
|
|
15
19
|
catch {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Remove existing symlink/dir if it points elsewhere
|
|
28
|
-
if (existsSync(linkPath)) {
|
|
29
|
-
const stat = lstatSync(linkPath);
|
|
30
|
-
if (stat.isSymbolicLink()) {
|
|
31
|
-
const target = realpathSync(linkPath);
|
|
32
|
-
if (target === realpathSync(pluginRoot)) {
|
|
33
|
-
// Already linked correctly
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
unlinkSync(linkPath);
|
|
37
|
-
symlinkSync(pluginRoot, linkPath);
|
|
38
|
-
}
|
|
20
|
+
// Fallback: try linking from the global npm install location
|
|
21
|
+
try {
|
|
22
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
23
|
+
const pluginPath = join(globalRoot, 'openclaw-safeclaw-plugin');
|
|
24
|
+
if (existsSync(pluginPath)) {
|
|
25
|
+
execSync(`openclaw plugins install --link "${pluginPath}"`, {
|
|
26
|
+
encoding: 'utf-8',
|
|
27
|
+
timeout: 15000,
|
|
28
|
+
stdio: 'pipe',
|
|
29
|
+
});
|
|
30
|
+
return true;
|
|
39
31
|
}
|
|
40
|
-
// If it's a real directory (from openclaw plugins install), leave it
|
|
41
32
|
}
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
catch {
|
|
34
|
+
// Both methods failed
|
|
44
35
|
}
|
|
45
36
|
}
|
|
46
|
-
|
|
47
|
-
console.warn(`Warning: Could not create extension symlink: ${e instanceof Error ? e.message : e}`);
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
// 2. Enable plugin in ~/.openclaw/openclaw.json
|
|
51
|
-
const openclawConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
52
|
-
const ocConfig = readJson(openclawConfigPath);
|
|
53
|
-
if (!ocConfig.plugins || typeof ocConfig.plugins !== 'object') {
|
|
54
|
-
ocConfig.plugins = {};
|
|
55
|
-
}
|
|
56
|
-
const plugins = ocConfig.plugins;
|
|
57
|
-
if (!plugins.entries || typeof plugins.entries !== 'object') {
|
|
58
|
-
plugins.entries = {};
|
|
59
|
-
}
|
|
60
|
-
const entries = plugins.entries;
|
|
61
|
-
// Only add/update if not already configured
|
|
62
|
-
if (!entries.safeclaw || typeof entries.safeclaw !== 'object') {
|
|
63
|
-
entries.safeclaw = { enabled: true };
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
entries.safeclaw.enabled = true;
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
writeFileSync(openclawConfigPath, JSON.stringify(ocConfig, null, 2) + '\n');
|
|
70
|
-
}
|
|
71
|
-
catch (e) {
|
|
72
|
-
console.warn(`Warning: Could not update OpenClaw config: ${e instanceof Error ? e.message : e}`);
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
return true;
|
|
37
|
+
return false;
|
|
76
38
|
}
|
|
77
39
|
const args = process.argv.slice(2);
|
|
78
40
|
const command = args[0];
|
|
@@ -86,6 +48,11 @@ if (command === 'connect') {
|
|
|
86
48
|
console.error('Usage: safeclaw connect <api-key> [--service-url <url>]');
|
|
87
49
|
process.exit(1);
|
|
88
50
|
}
|
|
51
|
+
if (!apiKey.startsWith('sc_')) {
|
|
52
|
+
console.error('Error: Invalid API key. Keys start with "sc_".');
|
|
53
|
+
console.error('Get your key at https://safeclaw.eu/dashboard');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
89
56
|
const configDir = join(homedir(), '.safeclaw');
|
|
90
57
|
const configPath = join(configDir, 'config.json');
|
|
91
58
|
// Load existing config or start fresh
|
|
@@ -110,19 +77,19 @@ if (command === 'connect') {
|
|
|
110
77
|
chmodSync(configPath, 0o600);
|
|
111
78
|
console.log(`Connected! API key saved to ${configPath}`);
|
|
112
79
|
// Register with OpenClaw
|
|
80
|
+
console.log('Registering SafeClaw plugin with OpenClaw...');
|
|
113
81
|
const registered = registerWithOpenClaw();
|
|
114
82
|
if (registered) {
|
|
115
83
|
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
116
84
|
console.log('');
|
|
117
85
|
console.log('Restart OpenClaw to activate:');
|
|
118
86
|
console.log(' safeclaw restart-openclaw');
|
|
119
|
-
console.log(' — or —');
|
|
120
|
-
console.log(' openclaw daemon restart');
|
|
121
87
|
}
|
|
122
88
|
else {
|
|
123
89
|
console.log('');
|
|
124
90
|
console.log('Could not auto-register with OpenClaw.');
|
|
125
|
-
console.log('Register manually:
|
|
91
|
+
console.log('Register manually:');
|
|
92
|
+
console.log(' openclaw plugins install openclaw-safeclaw-plugin');
|
|
126
93
|
}
|
|
127
94
|
}
|
|
128
95
|
else if (command === 'tui') {
|
|
@@ -141,7 +108,7 @@ else if (command === 'restart-openclaw') {
|
|
|
141
108
|
}
|
|
142
109
|
}
|
|
143
110
|
else if (command === 'setup') {
|
|
144
|
-
|
|
111
|
+
console.log('Registering SafeClaw plugin with OpenClaw...');
|
|
145
112
|
const registered = registerWithOpenClaw();
|
|
146
113
|
if (registered) {
|
|
147
114
|
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
@@ -152,7 +119,97 @@ else if (command === 'setup') {
|
|
|
152
119
|
console.log(' 3. Run: safeclaw restart-openclaw');
|
|
153
120
|
}
|
|
154
121
|
else {
|
|
155
|
-
console.log('Could not auto-register.
|
|
122
|
+
console.log('Could not auto-register.');
|
|
123
|
+
console.log('Try: openclaw plugins install openclaw-safeclaw-plugin');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (command === 'status') {
|
|
127
|
+
const configPath = join(homedir(), '.safeclaw', 'config.json');
|
|
128
|
+
let allOk = true;
|
|
129
|
+
// 1. Config file
|
|
130
|
+
if (existsSync(configPath)) {
|
|
131
|
+
console.log('[ok] Config file: ' + configPath);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log('[!!] Config file not found. Run: safeclaw connect <api-key>');
|
|
135
|
+
allOk = false;
|
|
136
|
+
}
|
|
137
|
+
// 2. API key
|
|
138
|
+
let apiKey = '';
|
|
139
|
+
let serviceUrl = 'https://api.safeclaw.eu/api/v1';
|
|
140
|
+
if (existsSync(configPath)) {
|
|
141
|
+
try {
|
|
142
|
+
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
143
|
+
const remote = cfg.remote;
|
|
144
|
+
apiKey = remote?.apiKey ?? '';
|
|
145
|
+
serviceUrl = remote?.serviceUrl ?? serviceUrl;
|
|
146
|
+
}
|
|
147
|
+
catch { /* ignore */ }
|
|
148
|
+
}
|
|
149
|
+
if (apiKey && apiKey.startsWith('sc_')) {
|
|
150
|
+
console.log('[ok] API key: configured (sc_...)');
|
|
151
|
+
}
|
|
152
|
+
else if (apiKey) {
|
|
153
|
+
console.log('[!!] API key: invalid (must start with sc_)');
|
|
154
|
+
allOk = false;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log('[!!] API key: not set. Run: safeclaw connect <api-key>');
|
|
158
|
+
allOk = false;
|
|
159
|
+
}
|
|
160
|
+
// 3. SafeClaw service reachable
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch(`${serviceUrl}/health`, {
|
|
163
|
+
signal: AbortSignal.timeout(5000),
|
|
164
|
+
headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {},
|
|
165
|
+
});
|
|
166
|
+
if (res.ok) {
|
|
167
|
+
const data = await res.json();
|
|
168
|
+
console.log(`[ok] SafeClaw service: ${data.status ?? 'ok'} (${serviceUrl})`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.log(`[!!] SafeClaw service: HTTP ${res.status} (${serviceUrl})`);
|
|
172
|
+
allOk = false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
console.log(`[!!] SafeClaw service: unreachable (${serviceUrl})`);
|
|
177
|
+
allOk = false;
|
|
178
|
+
}
|
|
179
|
+
// 4. OpenClaw installed
|
|
180
|
+
try {
|
|
181
|
+
execSync('which openclaw', { encoding: 'utf-8', stdio: 'pipe' });
|
|
182
|
+
console.log('[ok] OpenClaw: installed');
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
console.log('[!!] OpenClaw: not found in PATH');
|
|
186
|
+
allOk = false;
|
|
187
|
+
}
|
|
188
|
+
// 5. Plugin registered with OpenClaw
|
|
189
|
+
try {
|
|
190
|
+
const pluginList = execSync('openclaw plugins list', {
|
|
191
|
+
encoding: 'utf-8',
|
|
192
|
+
timeout: 10000,
|
|
193
|
+
stdio: 'pipe',
|
|
194
|
+
});
|
|
195
|
+
if (pluginList.includes('safeclaw')) {
|
|
196
|
+
console.log('[ok] Plugin: registered with OpenClaw');
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log('[!!] Plugin: not registered with OpenClaw. Run: safeclaw setup');
|
|
200
|
+
allOk = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
console.log('[??] Plugin: could not check OpenClaw plugin list');
|
|
205
|
+
}
|
|
206
|
+
// Summary
|
|
207
|
+
console.log('');
|
|
208
|
+
if (allOk) {
|
|
209
|
+
console.log('All checks passed. SafeClaw is ready.');
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
console.log('Some checks failed. Fix the issues above.');
|
|
156
213
|
}
|
|
157
214
|
}
|
|
158
215
|
else {
|
|
@@ -161,6 +218,7 @@ else {
|
|
|
161
218
|
console.log('Commands:');
|
|
162
219
|
console.log(' connect <api-key> Connect to SafeClaw and register with OpenClaw');
|
|
163
220
|
console.log(' setup Register SafeClaw plugin with OpenClaw (no key needed)');
|
|
221
|
+
console.log(' status Check SafeClaw + OpenClaw connection status');
|
|
164
222
|
console.log(' tui Open the interactive SafeClaw settings TUI');
|
|
165
223
|
console.log(' restart-openclaw Restart the OpenClaw daemon');
|
|
166
224
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-safeclaw-plugin",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "SafeClaw Neurosymbolic Governance plugin for OpenClaw — validates AI agent actions against OWL ontologies and SHACL constraints",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|