openclaw-safeclaw-plugin 0.7.2 → 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.
Files changed (3) hide show
  1. package/cli.tsx +107 -36
  2. package/dist/cli.js +101 -36
  3. 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
- function registerWithOpenClaw(): boolean {
11
- // Use OpenClaw's native plugin install mechanism
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+
13
+ function readJson(path: string): Record<string, unknown> {
12
14
  try {
13
- execSync('openclaw plugins install openclaw-safeclaw-plugin', {
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
- // Fallback: try linking from the global npm install location
21
- try {
22
- const globalRoot = execSync('npm root -g', { encoding: 'utf-8', timeout: 5000 }).trim();
23
- const pluginPath = join(globalRoot, 'openclaw-safeclaw-plugin');
24
- if (existsSync(pluginPath)) {
25
- execSync(`openclaw plugins install --link "${pluginPath}"`, {
26
- encoding: 'utf-8',
27
- timeout: 15000,
28
- stdio: 'pipe',
29
- });
30
- return true;
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
- return false;
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 registered with OpenClaw
189
- try {
190
- const pluginList = execSync('openclaw plugins list', {
191
- encoding: 'utf-8',
192
- timeout: 10000,
193
- stdio: 'pipe',
194
- });
195
- if (pluginList.includes('safeclaw')) {
196
- console.log('[ok] Plugin: registered with OpenClaw');
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: not registered with OpenClaw. Run: safeclaw setup');
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
- } catch {
202
- console.log('[??] Plugin: could not check OpenClaw plugin list');
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
- function registerWithOpenClaw() {
10
- // Use OpenClaw's native plugin install mechanism
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ function readJson(path) {
11
12
  try {
12
- execSync('openclaw plugins install openclaw-safeclaw-plugin', {
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
- // Fallback: try linking from the global npm install location
21
- try {
22
- const globalRoot = execSync('npm root -g', { encoding: 'utf-8', timeout: 5000 }).trim();
23
- const pluginPath = join(globalRoot, 'openclaw-safeclaw-plugin');
24
- if (existsSync(pluginPath)) {
25
- execSync(`openclaw plugins install --link "${pluginPath}"`, {
26
- encoding: 'utf-8',
27
- timeout: 15000,
28
- stdio: 'pipe',
29
- });
30
- return true;
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
- catch {
34
- // Both methods failed
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
- return false;
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 registered with OpenClaw
189
- try {
190
- const pluginList = execSync('openclaw plugins list', {
191
- encoding: 'utf-8',
192
- timeout: 10000,
193
- stdio: 'pipe',
194
- });
195
- if (pluginList.includes('safeclaw')) {
196
- console.log('[ok] Plugin: registered with OpenClaw');
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: not registered with OpenClaw. Run: safeclaw setup');
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
- catch {
204
- console.log('[??] Plugin: could not check OpenClaw plugin list');
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.7.2",
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",