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 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! Your API key has been saved to ${configPath}`);
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> Save your API key to ~/.safeclaw/config.json');
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! Your API key has been saved to ${configPath}`);
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> Save your API key to ~/.safeclaw/config.json');
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
@@ -80,7 +80,7 @@ async function checkConnection() {
80
80
  }
81
81
  }
82
82
  export default {
83
- id: 'openclaw-safeclaw-plugin',
83
+ id: 'safeclaw',
84
84
  name: 'SafeClaw Neurosymbolic Governance',
85
85
  version: '0.1.2',
86
86
  register(api) {
package/index.ts CHANGED
@@ -105,7 +105,7 @@ async function checkConnection(): Promise<void> {
105
105
  }
106
106
 
107
107
  export default {
108
- id: 'openclaw-safeclaw-plugin',
108
+ id: 'safeclaw',
109
109
  name: 'SafeClaw Neurosymbolic Governance',
110
110
  version: '0.1.2',
111
111
 
@@ -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.5.1",
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/",