openclaw-safeclaw-plugin 0.5.1 → 0.7.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 +105 -4
- package/dist/cli.js +103 -4
- package/dist/index.js +1 -1
- package/index.ts +1 -1
- package/openclaw.plugin.json +31 -0
- package/package.json +7 -1
package/cli.tsx
CHANGED
|
@@ -2,11 +2,83 @@
|
|
|
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, symlinkSync, lstatSync, unlinkSync, realpathSync } 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
|
|
|
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
|
+
function registerWithOpenClaw(): boolean {
|
|
22
|
+
// Plugin root is one level up from dist/
|
|
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
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
// Remove existing symlink/dir if it points elsewhere
|
|
32
|
+
if (existsSync(linkPath)) {
|
|
33
|
+
const stat = lstatSync(linkPath);
|
|
34
|
+
if (stat.isSymbolicLink()) {
|
|
35
|
+
const target = realpathSync(linkPath);
|
|
36
|
+
if (target === realpathSync(pluginRoot)) {
|
|
37
|
+
// Already linked correctly
|
|
38
|
+
} else {
|
|
39
|
+
unlinkSync(linkPath);
|
|
40
|
+
symlinkSync(pluginRoot, linkPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// If it's a real directory (from openclaw plugins install), leave it
|
|
44
|
+
} else {
|
|
45
|
+
symlinkSync(pluginRoot, linkPath);
|
|
46
|
+
}
|
|
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
|
+
}
|
|
59
|
+
const plugins = ocConfig.plugins as Record<string, unknown>;
|
|
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;
|
|
80
|
+
}
|
|
81
|
+
|
|
10
82
|
const args = process.argv.slice(2);
|
|
11
83
|
const command = args[0];
|
|
12
84
|
|
|
@@ -47,7 +119,22 @@ if (command === 'connect') {
|
|
|
47
119
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
48
120
|
chmodSync(configPath, 0o600);
|
|
49
121
|
|
|
50
|
-
console.log(`Connected!
|
|
122
|
+
console.log(`Connected! API key saved to ${configPath}`);
|
|
123
|
+
|
|
124
|
+
// Register with OpenClaw
|
|
125
|
+
const registered = registerWithOpenClaw();
|
|
126
|
+
if (registered) {
|
|
127
|
+
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log('Restart OpenClaw to activate:');
|
|
130
|
+
console.log(' safeclaw restart-openclaw');
|
|
131
|
+
console.log(' — or —');
|
|
132
|
+
console.log(' openclaw daemon restart');
|
|
133
|
+
} else {
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log('Could not auto-register with OpenClaw.');
|
|
136
|
+
console.log('Register manually: openclaw plugins install openclaw-safeclaw-plugin');
|
|
137
|
+
}
|
|
51
138
|
} else if (command === 'tui') {
|
|
52
139
|
render(React.createElement(App));
|
|
53
140
|
} else if (command === 'restart-openclaw') {
|
|
@@ -60,11 +147,25 @@ if (command === 'connect') {
|
|
|
60
147
|
console.error('Failed to restart OpenClaw daemon:', message);
|
|
61
148
|
process.exit(1);
|
|
62
149
|
}
|
|
150
|
+
} else if (command === 'setup') {
|
|
151
|
+
// Just register with OpenClaw (no API key needed)
|
|
152
|
+
const registered = registerWithOpenClaw();
|
|
153
|
+
if (registered) {
|
|
154
|
+
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log('Next steps:');
|
|
157
|
+
console.log(' 1. Get an API key at https://safeclaw.eu/dashboard');
|
|
158
|
+
console.log(' 2. Run: safeclaw connect <your-api-key>');
|
|
159
|
+
console.log(' 3. Run: safeclaw restart-openclaw');
|
|
160
|
+
} else {
|
|
161
|
+
console.log('Could not auto-register. Try: openclaw plugins install openclaw-safeclaw-plugin');
|
|
162
|
+
}
|
|
63
163
|
} else {
|
|
64
164
|
console.log('Usage: safeclaw <command>');
|
|
65
165
|
console.log('');
|
|
66
166
|
console.log('Commands:');
|
|
67
|
-
console.log(' connect <api-key>
|
|
167
|
+
console.log(' connect <api-key> Connect to SafeClaw and register with OpenClaw');
|
|
168
|
+
console.log(' setup Register SafeClaw plugin with OpenClaw (no key needed)');
|
|
68
169
|
console.log(' tui Open the interactive SafeClaw settings TUI');
|
|
69
170
|
console.log(' restart-openclaw Restart the OpenClaw daemon');
|
|
70
171
|
process.exit(0);
|
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,78 @@
|
|
|
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, symlinkSync, lstatSync, unlinkSync, realpathSync } 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';
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
function readJson(path) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function registerWithOpenClaw() {
|
|
20
|
+
// Plugin root is one level up from dist/
|
|
21
|
+
const pluginRoot = join(__dirname, '..');
|
|
22
|
+
const extensionsDir = join(homedir(), '.openclaw', 'extensions');
|
|
23
|
+
const linkPath = join(extensionsDir, 'safeclaw');
|
|
24
|
+
// 1. Create symlink in ~/.openclaw/extensions/safeclaw -> plugin root
|
|
25
|
+
try {
|
|
26
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
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
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// If it's a real directory (from openclaw plugins install), leave it
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
symlinkSync(pluginRoot, linkPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
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;
|
|
76
|
+
}
|
|
9
77
|
const args = process.argv.slice(2);
|
|
10
78
|
const command = args[0];
|
|
11
79
|
if (command === 'connect') {
|
|
@@ -40,7 +108,22 @@ if (command === 'connect') {
|
|
|
40
108
|
mkdirSync(configDir, { recursive: true });
|
|
41
109
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
42
110
|
chmodSync(configPath, 0o600);
|
|
43
|
-
console.log(`Connected!
|
|
111
|
+
console.log(`Connected! API key saved to ${configPath}`);
|
|
112
|
+
// Register with OpenClaw
|
|
113
|
+
const registered = registerWithOpenClaw();
|
|
114
|
+
if (registered) {
|
|
115
|
+
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('Restart OpenClaw to activate:');
|
|
118
|
+
console.log(' safeclaw restart-openclaw');
|
|
119
|
+
console.log(' — or —');
|
|
120
|
+
console.log(' openclaw daemon restart');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log('Could not auto-register with OpenClaw.');
|
|
125
|
+
console.log('Register manually: openclaw plugins install openclaw-safeclaw-plugin');
|
|
126
|
+
}
|
|
44
127
|
}
|
|
45
128
|
else if (command === 'tui') {
|
|
46
129
|
render(React.createElement(App));
|
|
@@ -57,11 +140,27 @@ else if (command === 'restart-openclaw') {
|
|
|
57
140
|
process.exit(1);
|
|
58
141
|
}
|
|
59
142
|
}
|
|
143
|
+
else if (command === 'setup') {
|
|
144
|
+
// Just register with OpenClaw (no API key needed)
|
|
145
|
+
const registered = registerWithOpenClaw();
|
|
146
|
+
if (registered) {
|
|
147
|
+
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
148
|
+
console.log('');
|
|
149
|
+
console.log('Next steps:');
|
|
150
|
+
console.log(' 1. Get an API key at https://safeclaw.eu/dashboard');
|
|
151
|
+
console.log(' 2. Run: safeclaw connect <your-api-key>');
|
|
152
|
+
console.log(' 3. Run: safeclaw restart-openclaw');
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log('Could not auto-register. Try: openclaw plugins install openclaw-safeclaw-plugin');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
60
158
|
else {
|
|
61
159
|
console.log('Usage: safeclaw <command>');
|
|
62
160
|
console.log('');
|
|
63
161
|
console.log('Commands:');
|
|
64
|
-
console.log(' connect <api-key>
|
|
162
|
+
console.log(' connect <api-key> Connect to SafeClaw and register with OpenClaw');
|
|
163
|
+
console.log(' setup Register SafeClaw plugin with OpenClaw (no key needed)');
|
|
65
164
|
console.log(' tui Open the interactive SafeClaw settings TUI');
|
|
66
165
|
console.log(' restart-openclaw Restart the OpenClaw daemon');
|
|
67
166
|
process.exit(0);
|
package/dist/index.js
CHANGED
package/index.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "safeclaw",
|
|
3
|
+
"name": "SafeClaw Neurosymbolic Governance",
|
|
4
|
+
"description": "Validates AI agent actions against OWL ontologies and SHACL constraints before execution",
|
|
5
|
+
"version": "0.5.1",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"serviceUrl": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "SafeClaw service URL",
|
|
13
|
+
"default": "https://api.safeclaw.eu/api/v1"
|
|
14
|
+
},
|
|
15
|
+
"apiKey": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "SafeClaw API key"
|
|
18
|
+
},
|
|
19
|
+
"enforcement": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": ["enforce", "warn-only", "audit-only", "disabled"],
|
|
22
|
+
"default": "enforce"
|
|
23
|
+
},
|
|
24
|
+
"failMode": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"enum": ["open", "closed"],
|
|
27
|
+
"default": "open"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-safeclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -13,8 +13,14 @@
|
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
14
|
"prepublishOnly": "npm run build"
|
|
15
15
|
},
|
|
16
|
+
"openclaw": {
|
|
17
|
+
"extensions": [
|
|
18
|
+
"dist/index.js"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
16
21
|
"files": [
|
|
17
22
|
"dist/",
|
|
23
|
+
"openclaw.plugin.json",
|
|
18
24
|
"index.ts",
|
|
19
25
|
"cli.tsx",
|
|
20
26
|
"tui/",
|