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 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
- npm install openclaw-safeclaw-plugin
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
- npm install openclaw-safeclaw-plugin
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 (args[0] !== 'tui') {
9
- console.log('Usage: safeclaw tui');
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('Opens the interactive SafeClaw settings TUI.');
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
- if (args[0] !== 'tui') {
7
- console.log('Usage: safeclaw tui');
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('Opens the interactive SafeClaw settings TUI.');
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));
@@ -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
- const interval = setInterval(checkHealth, 10000);
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 dotColor = connected ? 'green' : 'red';
37
- const statusText = connected
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
- 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: dotColor, children: [dot, " "] }), _jsx(Text, { children: statusText })] }), _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()] }) }))] }));
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.2.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
- const interval = setInterval(checkHealth, 10000);
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 dotColor = connected ? 'green' : 'red';
49
- const statusText = connected
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={dotColor}>{dot} </Text>
62
- <Text>{statusText}</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
  }