openclaw-safeclaw-plugin 0.2.0 → 0.4.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/README.md +2 -2
- package/cli.tsx +19 -5
- package/dist/cli.js +22 -4
- package/dist/tui/Status.js +45 -5
- package/package.json +1 -1
- package/tui/Status.tsx +63 -6
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Neurosymbolic governance plugin for OpenClaw AI agents. Validates every tool cal
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
openclaw plugins install openclaw-safeclaw-plugin
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
@@ -13,7 +13,7 @@ npm install openclaw-safeclaw-plugin
|
|
|
13
13
|
Install and go — the plugin connects to SafeClaw's hosted service by default:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
16
|
+
openclaw plugins install openclaw-safeclaw-plugin
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
No configuration needed. The default service URL is `https://api.safeclaw.eu/api/v1`.
|
package/cli.tsx
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render } from 'ink';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
4
5
|
import App from './tui/App.js';
|
|
5
6
|
|
|
6
7
|
const args = process.argv.slice(2);
|
|
8
|
+
const command = args[0];
|
|
7
9
|
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
+
if (command === 'tui') {
|
|
11
|
+
render(React.createElement(App));
|
|
12
|
+
} else if (command === 'restart-openclaw') {
|
|
13
|
+
try {
|
|
14
|
+
const output = execSync('openclaw daemon restart', { encoding: 'utf-8', timeout: 15000 });
|
|
15
|
+
console.log(output.trim());
|
|
16
|
+
console.log('OpenClaw daemon restarted successfully.');
|
|
17
|
+
} catch (err: unknown) {
|
|
18
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
19
|
+
console.error('Failed to restart OpenClaw daemon:', message);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
console.log('Usage: safeclaw <command>');
|
|
10
24
|
console.log('');
|
|
11
|
-
console.log('
|
|
25
|
+
console.log('Commands:');
|
|
26
|
+
console.log(' tui Open the interactive SafeClaw settings TUI');
|
|
27
|
+
console.log(' restart-openclaw Restart the OpenClaw daemon');
|
|
12
28
|
process.exit(0);
|
|
13
29
|
}
|
|
14
|
-
|
|
15
|
-
render(React.createElement(App));
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render } from 'ink';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
4
5
|
import App from './tui/App.js';
|
|
5
6
|
const args = process.argv.slice(2);
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const command = args[0];
|
|
8
|
+
if (command === 'tui') {
|
|
9
|
+
render(React.createElement(App));
|
|
10
|
+
}
|
|
11
|
+
else if (command === 'restart-openclaw') {
|
|
12
|
+
try {
|
|
13
|
+
const output = execSync('openclaw daemon restart', { encoding: 'utf-8', timeout: 15000 });
|
|
14
|
+
console.log(output.trim());
|
|
15
|
+
console.log('OpenClaw daemon restarted successfully.');
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
19
|
+
console.error('Failed to restart OpenClaw daemon:', message);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log('Usage: safeclaw <command>');
|
|
8
25
|
console.log('');
|
|
9
|
-
console.log('
|
|
26
|
+
console.log('Commands:');
|
|
27
|
+
console.log(' tui Open the interactive SafeClaw settings TUI');
|
|
28
|
+
console.log(' restart-openclaw Restart the OpenClaw daemon');
|
|
10
29
|
process.exit(0);
|
|
11
30
|
}
|
|
12
|
-
render(React.createElement(App));
|
package/dist/tui/Status.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Text, Box } from 'ink';
|
|
3
|
+
import { Text, Box, useInput } from 'ink';
|
|
4
|
+
import { exec } from 'child_process';
|
|
4
5
|
export default function Status({ config }) {
|
|
5
6
|
const [health, setHealth] = useState(null);
|
|
6
7
|
const [error, setError] = useState(null);
|
|
7
8
|
const [lastCheck, setLastCheck] = useState(null);
|
|
9
|
+
const [openclawStatus, setOpenclawStatus] = useState('checking');
|
|
10
|
+
const [restartMsg, setRestartMsg] = useState(null);
|
|
8
11
|
const checkHealth = async () => {
|
|
9
12
|
try {
|
|
10
13
|
const res = await fetch(`${config.serviceUrl}/health`, {
|
|
@@ -26,16 +29,53 @@ export default function Status({ config }) {
|
|
|
26
29
|
}
|
|
27
30
|
setLastCheck(new Date());
|
|
28
31
|
};
|
|
32
|
+
const checkOpenClaw = () => {
|
|
33
|
+
exec('openclaw daemon status', { timeout: 10000 }, (err, stdout) => {
|
|
34
|
+
if (err) {
|
|
35
|
+
setOpenclawStatus('not running');
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const output = stdout.toLowerCase();
|
|
39
|
+
setOpenclawStatus(output.includes('running') ? 'running' : 'not running');
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const restartOpenClaw = () => {
|
|
44
|
+
setRestartMsg('Restarting...');
|
|
45
|
+
exec('openclaw daemon restart', { timeout: 15000 }, (err) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
setRestartMsg('Restart failed');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
setRestartMsg('Restarted');
|
|
51
|
+
checkOpenClaw();
|
|
52
|
+
}
|
|
53
|
+
setTimeout(() => setRestartMsg(null), 3000);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
29
56
|
useEffect(() => {
|
|
30
57
|
checkHealth();
|
|
31
|
-
|
|
58
|
+
checkOpenClaw();
|
|
59
|
+
const interval = setInterval(() => {
|
|
60
|
+
checkHealth();
|
|
61
|
+
checkOpenClaw();
|
|
62
|
+
}, 10000);
|
|
32
63
|
return () => clearInterval(interval);
|
|
33
64
|
}, []);
|
|
65
|
+
useInput((input) => {
|
|
66
|
+
if (input === 'r') {
|
|
67
|
+
restartOpenClaw();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
34
70
|
const connected = health !== null;
|
|
35
71
|
const dot = '●';
|
|
36
|
-
const
|
|
37
|
-
const
|
|
72
|
+
const serviceDotColor = connected ? 'green' : 'red';
|
|
73
|
+
const serviceText = connected
|
|
38
74
|
? `Connected (${config.serviceUrl.replace(/^https?:\/\//, '').replace(/\/api\/v1$/, '')})`
|
|
39
75
|
: error ?? 'Disconnected';
|
|
40
|
-
|
|
76
|
+
const openclawDotColor = openclawStatus === 'running' ? 'green' : openclawStatus === 'checking' ? 'yellow' : 'red';
|
|
77
|
+
const openclawText = openclawStatus === 'checking' ? 'Checking...'
|
|
78
|
+
: openclawStatus === 'running' ? 'Running'
|
|
79
|
+
: 'Not running';
|
|
80
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Status" }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: ' Service ' }), _jsxs(Text, { color: serviceDotColor, children: [dot, " "] }), _jsx(Text, { children: serviceText })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: ' OpenClaw ' }), _jsxs(Text, { color: openclawDotColor, children: [dot, " "] }), _jsx(Text, { children: openclawText }), restartMsg && _jsx(Text, { dimColor: true, children: ` (${restartMsg})` })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: ' Enforcement ' }), _jsx(Text, { children: config.enforcement })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: ' Fail Mode ' }), _jsx(Text, { children: config.failMode })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: ' Enabled ' }), _jsx(Text, { color: config.enabled ? 'green' : 'red', children: config.enabled ? 'ON' : 'OFF' })] }), health?.version && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: ' Service v' }), _jsx(Text, { children: health.version })] })), lastCheck && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [' Last check: ', lastCheck.toLocaleTimeString()] }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: ' Press ' }), _jsx(Text, { bold: true, children: "r" }), _jsx(Text, { dimColor: true, children: ' to restart OpenClaw daemon' })] })] }));
|
|
41
81
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-safeclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
package/tui/Status.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Text, Box } from 'ink';
|
|
2
|
+
import { Text, Box, useInput } from 'ink';
|
|
3
|
+
import { exec } from 'child_process';
|
|
3
4
|
import { type SafeClawConfig } from './config.js';
|
|
4
5
|
|
|
5
6
|
interface StatusProps {
|
|
@@ -12,10 +13,14 @@ interface HealthData {
|
|
|
12
13
|
engine_ready?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
type OpenClawStatus = 'checking' | 'running' | 'not running' | 'error';
|
|
17
|
+
|
|
15
18
|
export default function Status({ config }: StatusProps) {
|
|
16
19
|
const [health, setHealth] = useState<HealthData | null>(null);
|
|
17
20
|
const [error, setError] = useState<string | null>(null);
|
|
18
21
|
const [lastCheck, setLastCheck] = useState<Date | null>(null);
|
|
22
|
+
const [openclawStatus, setOpenclawStatus] = useState<OpenClawStatus>('checking');
|
|
23
|
+
const [restartMsg, setRestartMsg] = useState<string | null>(null);
|
|
19
24
|
|
|
20
25
|
const checkHealth = async () => {
|
|
21
26
|
try {
|
|
@@ -37,19 +42,58 @@ export default function Status({ config }: StatusProps) {
|
|
|
37
42
|
setLastCheck(new Date());
|
|
38
43
|
};
|
|
39
44
|
|
|
45
|
+
const checkOpenClaw = () => {
|
|
46
|
+
exec('openclaw daemon status', { timeout: 10000 }, (err, stdout) => {
|
|
47
|
+
if (err) {
|
|
48
|
+
setOpenclawStatus('not running');
|
|
49
|
+
} else {
|
|
50
|
+
const output = stdout.toLowerCase();
|
|
51
|
+
setOpenclawStatus(output.includes('running') ? 'running' : 'not running');
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const restartOpenClaw = () => {
|
|
57
|
+
setRestartMsg('Restarting...');
|
|
58
|
+
exec('openclaw daemon restart', { timeout: 15000 }, (err) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
setRestartMsg('Restart failed');
|
|
61
|
+
} else {
|
|
62
|
+
setRestartMsg('Restarted');
|
|
63
|
+
checkOpenClaw();
|
|
64
|
+
}
|
|
65
|
+
setTimeout(() => setRestartMsg(null), 3000);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
40
69
|
useEffect(() => {
|
|
41
70
|
checkHealth();
|
|
42
|
-
|
|
71
|
+
checkOpenClaw();
|
|
72
|
+
const interval = setInterval(() => {
|
|
73
|
+
checkHealth();
|
|
74
|
+
checkOpenClaw();
|
|
75
|
+
}, 10000);
|
|
43
76
|
return () => clearInterval(interval);
|
|
44
77
|
}, []);
|
|
45
78
|
|
|
79
|
+
useInput((input) => {
|
|
80
|
+
if (input === 'r') {
|
|
81
|
+
restartOpenClaw();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
46
85
|
const connected = health !== null;
|
|
47
86
|
const dot = '●';
|
|
48
|
-
const
|
|
49
|
-
const
|
|
87
|
+
const serviceDotColor = connected ? 'green' : 'red';
|
|
88
|
+
const serviceText = connected
|
|
50
89
|
? `Connected (${config.serviceUrl.replace(/^https?:\/\//, '').replace(/\/api\/v1$/, '')})`
|
|
51
90
|
: error ?? 'Disconnected';
|
|
52
91
|
|
|
92
|
+
const openclawDotColor = openclawStatus === 'running' ? 'green' : openclawStatus === 'checking' ? 'yellow' : 'red';
|
|
93
|
+
const openclawText = openclawStatus === 'checking' ? 'Checking...'
|
|
94
|
+
: openclawStatus === 'running' ? 'Running'
|
|
95
|
+
: 'Not running';
|
|
96
|
+
|
|
53
97
|
return (
|
|
54
98
|
<Box flexDirection="column" paddingX={1}>
|
|
55
99
|
<Box marginBottom={1}>
|
|
@@ -58,8 +102,15 @@ export default function Status({ config }: StatusProps) {
|
|
|
58
102
|
|
|
59
103
|
<Box>
|
|
60
104
|
<Text dimColor>{' Service '}</Text>
|
|
61
|
-
<Text color={
|
|
62
|
-
<Text>{
|
|
105
|
+
<Text color={serviceDotColor}>{dot} </Text>
|
|
106
|
+
<Text>{serviceText}</Text>
|
|
107
|
+
</Box>
|
|
108
|
+
|
|
109
|
+
<Box>
|
|
110
|
+
<Text dimColor>{' OpenClaw '}</Text>
|
|
111
|
+
<Text color={openclawDotColor}>{dot} </Text>
|
|
112
|
+
<Text>{openclawText}</Text>
|
|
113
|
+
{restartMsg && <Text dimColor>{` (${restartMsg})`}</Text>}
|
|
63
114
|
</Box>
|
|
64
115
|
|
|
65
116
|
<Box>
|
|
@@ -94,6 +145,12 @@ export default function Status({ config }: StatusProps) {
|
|
|
94
145
|
</Text>
|
|
95
146
|
</Box>
|
|
96
147
|
)}
|
|
148
|
+
|
|
149
|
+
<Box marginTop={1}>
|
|
150
|
+
<Text dimColor>{' Press '}</Text>
|
|
151
|
+
<Text bold>r</Text>
|
|
152
|
+
<Text dimColor>{' to restart OpenClaw daemon'}</Text>
|
|
153
|
+
</Box>
|
|
97
154
|
</Box>
|
|
98
155
|
);
|
|
99
156
|
}
|