openclaw-safeclaw-plugin 0.7.1 → 0.8.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/cli.tsx +107 -36
- package/dist/cli.js +101 -36
- package/package.json +1 -1
package/cli.tsx
CHANGED
|
@@ -2,38 +2,89 @@
|
|
|
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 } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, copyFileSync, lstatSync, unlinkSync, rmSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
8
9
|
import App from './tui/App.js';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
function readJson(path: string): Record<string, unknown> {
|
|
12
14
|
try {
|
|
13
|
-
|
|
14
|
-
encoding: 'utf-8',
|
|
15
|
-
timeout: 30000,
|
|
16
|
-
stdio: 'pipe',
|
|
17
|
-
});
|
|
18
|
-
return true;
|
|
15
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
19
16
|
} catch {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function registerWithOpenClaw(): boolean {
|
|
22
|
+
const pluginRoot = join(__dirname, '..'); // one level up from dist/
|
|
23
|
+
const extensionDir = join(homedir(), '.openclaw', 'extensions', 'safeclaw');
|
|
24
|
+
const entryPoint = join(pluginRoot, 'dist', 'index.js');
|
|
25
|
+
const manifestSrc = join(pluginRoot, 'openclaw.plugin.json');
|
|
26
|
+
|
|
27
|
+
// Clean up stale symlink if it exists
|
|
28
|
+
try {
|
|
29
|
+
if (existsSync(extensionDir)) {
|
|
30
|
+
const stat = lstatSync(extensionDir);
|
|
31
|
+
if (stat.isSymbolicLink()) {
|
|
32
|
+
unlinkSync(extensionDir);
|
|
31
33
|
}
|
|
32
|
-
} catch {
|
|
33
|
-
// Both methods failed
|
|
34
34
|
}
|
|
35
|
+
} catch { /* ignore */ }
|
|
36
|
+
|
|
37
|
+
// Create extension directory with manifest + loader that imports from npm install
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
40
|
+
|
|
41
|
+
// Copy the manifest
|
|
42
|
+
if (existsSync(manifestSrc)) {
|
|
43
|
+
copyFileSync(manifestSrc, join(extensionDir, 'openclaw.plugin.json'));
|
|
44
|
+
} else {
|
|
45
|
+
// Write manifest inline if source file missing
|
|
46
|
+
writeFileSync(join(extensionDir, 'openclaw.plugin.json'), JSON.stringify({
|
|
47
|
+
id: 'safeclaw',
|
|
48
|
+
name: 'SafeClaw Neurosymbolic Governance',
|
|
49
|
+
configSchema: { type: 'object', additionalProperties: false, properties: {} },
|
|
50
|
+
}, null, 2) + '\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create index.js that loads from the actual npm install location
|
|
54
|
+
writeFileSync(join(extensionDir, 'index.js'),
|
|
55
|
+
`export { default } from '${entryPoint}';\n`);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.warn(`Warning: Could not create extension: ${e instanceof Error ? e.message : e}`);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Enable plugin in ~/.openclaw/openclaw.json
|
|
62
|
+
const openclawConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
63
|
+
const ocConfig = readJson(openclawConfigPath);
|
|
64
|
+
|
|
65
|
+
if (!ocConfig.plugins || typeof ocConfig.plugins !== 'object') {
|
|
66
|
+
ocConfig.plugins = {};
|
|
35
67
|
}
|
|
36
|
-
|
|
68
|
+
const plugins = ocConfig.plugins as Record<string, unknown>;
|
|
69
|
+
if (!plugins.entries || typeof plugins.entries !== 'object') {
|
|
70
|
+
plugins.entries = {};
|
|
71
|
+
}
|
|
72
|
+
const entries = plugins.entries as Record<string, unknown>;
|
|
73
|
+
|
|
74
|
+
if (!entries.safeclaw || typeof entries.safeclaw !== 'object') {
|
|
75
|
+
entries.safeclaw = { enabled: true };
|
|
76
|
+
} else {
|
|
77
|
+
(entries.safeclaw as Record<string, unknown>).enabled = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
writeFileSync(openclawConfigPath, JSON.stringify(ocConfig, null, 2) + '\n');
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.warn(`Warning: Could not update OpenClaw config: ${e instanceof Error ? e.message : e}`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true;
|
|
37
88
|
}
|
|
38
89
|
|
|
39
90
|
const args = process.argv.slice(2);
|
|
@@ -185,21 +236,41 @@ if (command === 'connect') {
|
|
|
185
236
|
allOk = false;
|
|
186
237
|
}
|
|
187
238
|
|
|
188
|
-
// 5. Plugin
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
239
|
+
// 5. Plugin extension files exist
|
|
240
|
+
const extensionDir = join(homedir(), '.openclaw', 'extensions', 'safeclaw');
|
|
241
|
+
const hasManifest = existsSync(join(extensionDir, 'openclaw.plugin.json'));
|
|
242
|
+
const hasEntry = existsSync(join(extensionDir, 'index.js'));
|
|
243
|
+
if (hasManifest && hasEntry) {
|
|
244
|
+
console.log('[ok] Plugin files: ' + extensionDir);
|
|
245
|
+
} else if (existsSync(extensionDir)) {
|
|
246
|
+
const stat = lstatSync(extensionDir);
|
|
247
|
+
if (stat.isSymbolicLink()) {
|
|
248
|
+
console.log('[!!] Plugin: stale symlink (run safeclaw setup to fix)');
|
|
197
249
|
} else {
|
|
198
|
-
console.log('[!!] Plugin:
|
|
250
|
+
console.log('[!!] Plugin: missing files in ' + extensionDir);
|
|
251
|
+
}
|
|
252
|
+
allOk = false;
|
|
253
|
+
} else {
|
|
254
|
+
console.log('[!!] Plugin: not installed. Run: safeclaw setup');
|
|
255
|
+
allOk = false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 6. Plugin enabled in OpenClaw config
|
|
259
|
+
const ocConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
260
|
+
if (existsSync(ocConfigPath)) {
|
|
261
|
+
const ocConfig = readJson(ocConfigPath);
|
|
262
|
+
const plugins = ocConfig.plugins as Record<string, unknown> | undefined;
|
|
263
|
+
const entries = plugins?.entries as Record<string, unknown> | undefined;
|
|
264
|
+
const safeclaw = entries?.safeclaw as Record<string, unknown> | undefined;
|
|
265
|
+
if (safeclaw?.enabled) {
|
|
266
|
+
console.log('[ok] OpenClaw config: safeclaw enabled');
|
|
267
|
+
} else {
|
|
268
|
+
console.log('[!!] OpenClaw config: safeclaw not enabled');
|
|
199
269
|
allOk = false;
|
|
200
270
|
}
|
|
201
|
-
}
|
|
202
|
-
console.log('[
|
|
271
|
+
} else {
|
|
272
|
+
console.log('[!!] OpenClaw config: not found');
|
|
273
|
+
allOk = false;
|
|
203
274
|
}
|
|
204
275
|
|
|
205
276
|
// Summary
|
package/dist/cli.js
CHANGED
|
@@ -2,39 +2,82 @@
|
|
|
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 } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, copyFileSync, lstatSync, unlinkSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
8
9
|
import App from './tui/App.js';
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
function readJson(path) {
|
|
11
12
|
try {
|
|
12
|
-
|
|
13
|
-
encoding: 'utf-8',
|
|
14
|
-
timeout: 30000,
|
|
15
|
-
stdio: 'pipe',
|
|
16
|
-
});
|
|
17
|
-
return true;
|
|
13
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
18
14
|
}
|
|
19
15
|
catch {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function registerWithOpenClaw() {
|
|
20
|
+
const pluginRoot = join(__dirname, '..'); // one level up from dist/
|
|
21
|
+
const extensionDir = join(homedir(), '.openclaw', 'extensions', 'safeclaw');
|
|
22
|
+
const entryPoint = join(pluginRoot, 'dist', 'index.js');
|
|
23
|
+
const manifestSrc = join(pluginRoot, 'openclaw.plugin.json');
|
|
24
|
+
// Clean up stale symlink if it exists
|
|
25
|
+
try {
|
|
26
|
+
if (existsSync(extensionDir)) {
|
|
27
|
+
const stat = lstatSync(extensionDir);
|
|
28
|
+
if (stat.isSymbolicLink()) {
|
|
29
|
+
unlinkSync(extensionDir);
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
}
|
|
33
|
+
catch { /* ignore */ }
|
|
34
|
+
// Create extension directory with manifest + loader that imports from npm install
|
|
35
|
+
try {
|
|
36
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
37
|
+
// Copy the manifest
|
|
38
|
+
if (existsSync(manifestSrc)) {
|
|
39
|
+
copyFileSync(manifestSrc, join(extensionDir, 'openclaw.plugin.json'));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Write manifest inline if source file missing
|
|
43
|
+
writeFileSync(join(extensionDir, 'openclaw.plugin.json'), JSON.stringify({
|
|
44
|
+
id: 'safeclaw',
|
|
45
|
+
name: 'SafeClaw Neurosymbolic Governance',
|
|
46
|
+
configSchema: { type: 'object', additionalProperties: false, properties: {} },
|
|
47
|
+
}, null, 2) + '\n');
|
|
35
48
|
}
|
|
49
|
+
// Create index.js that loads from the actual npm install location
|
|
50
|
+
writeFileSync(join(extensionDir, 'index.js'), `export { default } from '${entryPoint}';\n`);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.warn(`Warning: Could not create extension: ${e instanceof Error ? e.message : e}`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Enable plugin in ~/.openclaw/openclaw.json
|
|
57
|
+
const openclawConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
58
|
+
const ocConfig = readJson(openclawConfigPath);
|
|
59
|
+
if (!ocConfig.plugins || typeof ocConfig.plugins !== 'object') {
|
|
60
|
+
ocConfig.plugins = {};
|
|
61
|
+
}
|
|
62
|
+
const plugins = ocConfig.plugins;
|
|
63
|
+
if (!plugins.entries || typeof plugins.entries !== 'object') {
|
|
64
|
+
plugins.entries = {};
|
|
65
|
+
}
|
|
66
|
+
const entries = plugins.entries;
|
|
67
|
+
if (!entries.safeclaw || typeof entries.safeclaw !== 'object') {
|
|
68
|
+
entries.safeclaw = { enabled: true };
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
entries.safeclaw.enabled = true;
|
|
36
72
|
}
|
|
37
|
-
|
|
73
|
+
try {
|
|
74
|
+
writeFileSync(openclawConfigPath, JSON.stringify(ocConfig, null, 2) + '\n');
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
console.warn(`Warning: Could not update OpenClaw config: ${e instanceof Error ? e.message : e}`);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
38
81
|
}
|
|
39
82
|
const args = process.argv.slice(2);
|
|
40
83
|
const command = args[0];
|
|
@@ -185,23 +228,45 @@ else if (command === 'status') {
|
|
|
185
228
|
console.log('[!!] OpenClaw: not found in PATH');
|
|
186
229
|
allOk = false;
|
|
187
230
|
}
|
|
188
|
-
// 5. Plugin
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
231
|
+
// 5. Plugin extension files exist
|
|
232
|
+
const extensionDir = join(homedir(), '.openclaw', 'extensions', 'safeclaw');
|
|
233
|
+
const hasManifest = existsSync(join(extensionDir, 'openclaw.plugin.json'));
|
|
234
|
+
const hasEntry = existsSync(join(extensionDir, 'index.js'));
|
|
235
|
+
if (hasManifest && hasEntry) {
|
|
236
|
+
console.log('[ok] Plugin files: ' + extensionDir);
|
|
237
|
+
}
|
|
238
|
+
else if (existsSync(extensionDir)) {
|
|
239
|
+
const stat = lstatSync(extensionDir);
|
|
240
|
+
if (stat.isSymbolicLink()) {
|
|
241
|
+
console.log('[!!] Plugin: stale symlink (run safeclaw setup to fix)');
|
|
197
242
|
}
|
|
198
243
|
else {
|
|
199
|
-
console.log('[!!] Plugin:
|
|
244
|
+
console.log('[!!] Plugin: missing files in ' + extensionDir);
|
|
245
|
+
}
|
|
246
|
+
allOk = false;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.log('[!!] Plugin: not installed. Run: safeclaw setup');
|
|
250
|
+
allOk = false;
|
|
251
|
+
}
|
|
252
|
+
// 6. Plugin enabled in OpenClaw config
|
|
253
|
+
const ocConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
254
|
+
if (existsSync(ocConfigPath)) {
|
|
255
|
+
const ocConfig = readJson(ocConfigPath);
|
|
256
|
+
const plugins = ocConfig.plugins;
|
|
257
|
+
const entries = plugins?.entries;
|
|
258
|
+
const safeclaw = entries?.safeclaw;
|
|
259
|
+
if (safeclaw?.enabled) {
|
|
260
|
+
console.log('[ok] OpenClaw config: safeclaw enabled');
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.log('[!!] OpenClaw config: safeclaw not enabled');
|
|
200
264
|
allOk = false;
|
|
201
265
|
}
|
|
202
266
|
}
|
|
203
|
-
|
|
204
|
-
console.log('[
|
|
267
|
+
else {
|
|
268
|
+
console.log('[!!] OpenClaw config: not found');
|
|
269
|
+
allOk = false;
|
|
205
270
|
}
|
|
206
271
|
// Summary
|
|
207
272
|
console.log('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-safeclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|