openclaw-safeclaw-plugin 0.1.0 → 0.1.2

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
@@ -8,20 +8,23 @@ Neurosymbolic governance plugin for OpenClaw AI agents. Validates every tool cal
8
8
  npm install openclaw-safeclaw-plugin
9
9
  ```
10
10
 
11
- ## Quick Start (SaaS)
11
+ ## Quick Start
12
12
 
13
- Point the plugin at the hosted SafeClaw service:
13
+ Install and go — the plugin connects to SafeClaw's hosted service by default:
14
14
 
15
15
  ```bash
16
- export SAFECLAW_URL="https://api.safeclaw.eu/api/v1"
17
- export SAFECLAW_ENFORCEMENT="enforce"
16
+ npm install openclaw-safeclaw-plugin
18
17
  ```
19
18
 
20
- No server setup needed the plugin connects to SafeClaw's hosted service.
19
+ No configuration needed. The default service URL is `https://api.safeclaw.eu/api/v1`.
20
+
21
+ ## Self-Hosted
21
22
 
22
- ## Quick Start (Self-Hosted)
23
+ To run your own SafeClaw service, override the URL:
23
24
 
24
25
  ```bash
26
+ export SAFECLAW_URL="http://localhost:8420/api/v1"
27
+
25
28
  # Start the SafeClaw service
26
29
  git clone https://github.com/tendlyeu/SafeClaw.git
27
30
  cd SafeClaw/safeclaw-service
@@ -32,8 +35,6 @@ safeclaw serve
32
35
  # Engine ready on http://localhost:8420
33
36
  ```
34
37
 
35
- The plugin auto-connects to `http://localhost:8420/api/v1` by default.
36
-
37
38
  ## What It Does
38
39
 
39
40
  - **Blocks dangerous actions** — force push, deleting root, exposing secrets
@@ -58,7 +59,7 @@ Set via environment variables or `~/.safeclaw/config.json`:
58
59
 
59
60
  | Variable | Default | Description |
60
61
  |----------|---------|-------------|
61
- | `SAFECLAW_URL` | `http://localhost:8420/api/v1` | SafeClaw service URL |
62
+ | `SAFECLAW_URL` | `https://api.safeclaw.eu/api/v1` | SafeClaw service URL |
62
63
  | `SAFECLAW_API_KEY` | *(empty)* | API key for cloud mode |
63
64
  | `SAFECLAW_TIMEOUT_MS` | `500` | Request timeout in ms |
64
65
  | `SAFECLAW_ENABLED` | `true` | Set `false` to disable |
package/SKILL.md CHANGED
@@ -12,26 +12,15 @@ SafeClaw adds ontology-based constraint checking to your OpenClaw agent. Every t
12
12
 
13
13
  ## Setup
14
14
 
15
- ### Option A: Local mode (self-hosted)
15
+ The plugin connects to `https://api.safeclaw.eu/api/v1` by default — no configuration needed.
16
16
 
17
- ```bash
18
- # 1. Start the SafeClaw service
19
- pip install safeclaw
20
- safeclaw init
21
- uvicorn safeclaw.main:app --port 8420
22
-
23
- # 2. Install this plugin (done if you're reading this via clawhub)
24
- # The plugin auto-connects to http://localhost:8420
25
- ```
17
+ ### Self-hosted mode
26
18
 
27
- ### Option B: Cloud mode (safeclaw.eu)
19
+ To run your own SafeClaw service, override the URL:
28
20
 
29
21
  ```bash
30
- # 1. Sign up at safeclaw.eu and get your API key
31
-
32
- # 2. Set your API key
33
- export SAFECLAW_URL="https://api.safeclaw.eu/api/v1"
34
- export SAFECLAW_API_KEY="sc_live_your_key_here"
22
+ export SAFECLAW_URL="http://localhost:8420/api/v1"
23
+ export SAFECLAW_API_KEY="sc_live_your_key_here" # optional
35
24
  ```
36
25
 
37
26
  ## Configuration
@@ -40,7 +29,7 @@ Set via environment variables or `~/.safeclaw/config.json`:
40
29
 
41
30
  | Variable | Default | Description |
42
31
  |----------|---------|-------------|
43
- | `SAFECLAW_URL` | `http://localhost:8420/api/v1` | SafeClaw service URL |
32
+ | `SAFECLAW_URL` | `https://api.safeclaw.eu/api/v1` | SafeClaw service URL |
44
33
  | `SAFECLAW_API_KEY` | (empty) | API key for remote/cloud mode |
45
34
  | `SAFECLAW_TIMEOUT_MS` | `500` | Request timeout in milliseconds |
46
35
  | `SAFECLAW_ENABLED` | `true` | Set to `false` to disable |
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { join } from 'path';
11
11
  import { homedir } from 'os';
12
12
  function loadConfig() {
13
13
  const defaults = {
14
- serviceUrl: process.env.SAFECLAW_URL ?? 'http://localhost:8420/api/v1',
14
+ serviceUrl: process.env.SAFECLAW_URL ?? 'https://api.safeclaw.eu/api/v1',
15
15
  apiKey: process.env.SAFECLAW_API_KEY ?? '',
16
16
  timeoutMs: parseInt(process.env.SAFECLAW_TIMEOUT_MS ?? '500', 10),
17
17
  enabled: process.env.SAFECLAW_ENABLED !== 'false',
@@ -77,28 +77,70 @@ async function post(path, body) {
77
77
  signal: AbortSignal.timeout(config.timeoutMs),
78
78
  });
79
79
  if (!res.ok) {
80
- console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
80
+ // Try to parse structured error body from service
81
+ try {
82
+ const errBody = await res.json();
83
+ const detail = errBody.detail ?? `HTTP ${res.status}`;
84
+ const hint = errBody.hint ? ` (${errBody.hint})` : '';
85
+ console.warn(`[SafeClaw] ${path}: ${detail}${hint}`);
86
+ }
87
+ catch {
88
+ console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
89
+ }
81
90
  return null; // Caller checks failMode
82
91
  }
83
92
  return await res.json();
84
93
  }
85
94
  catch (e) {
86
95
  if (e instanceof DOMException && e.name === 'TimeoutError') {
87
- console.debug(`[SafeClaw] Timeout on ${path}`);
96
+ console.warn(`[SafeClaw] Timeout after ${config.timeoutMs}ms on ${path} (${config.serviceUrl})`);
97
+ }
98
+ else if (e instanceof TypeError && (e.message.includes('fetch') || e.message.includes('ECONNREFUSED'))) {
99
+ console.warn(`[SafeClaw] Connection refused: ${config.serviceUrl}${path} — is the service running?`);
88
100
  }
89
101
  else {
90
- console.debug(`[SafeClaw] Service unavailable: ${path}`);
102
+ console.warn(`[SafeClaw] Service unavailable: ${config.serviceUrl}${path}`);
91
103
  }
92
104
  return null; // Caller checks failMode
93
105
  }
94
106
  }
107
+ async function checkConnection() {
108
+ const label = `[SafeClaw]`;
109
+ console.log(`${label} Connecting to ${config.serviceUrl} ...`);
110
+ console.log(`${label} Mode: enforcement=${config.enforcement}, failMode=${config.failMode}`);
111
+ try {
112
+ const res = await fetch(`${config.serviceUrl}/health`, {
113
+ signal: AbortSignal.timeout(config.timeoutMs * 2),
114
+ });
115
+ if (res.ok) {
116
+ const data = await res.json();
117
+ console.log(`${label} ✓ Connected — service ${data.status ?? 'ok'}`);
118
+ }
119
+ else {
120
+ console.warn(`${label} ✗ Service responded with HTTP ${res.status}`);
121
+ }
122
+ }
123
+ catch {
124
+ console.warn(`${label} ✗ Cannot reach service at ${config.serviceUrl}`);
125
+ if (config.failMode === 'closed') {
126
+ console.warn(`${label} fail-mode=closed → tool calls will be BLOCKED until service is reachable`);
127
+ }
128
+ else {
129
+ console.warn(`${label} fail-mode=open → tool calls will be ALLOWED despite no connection`);
130
+ }
131
+ }
132
+ }
95
133
  export default {
96
134
  id: 'openclaw-safeclaw-plugin',
97
135
  name: 'SafeClaw Neurosymbolic Governance',
98
- version: '0.1.0',
136
+ version: '0.1.2',
99
137
  register(api) {
100
- if (!config.enabled)
138
+ if (!config.enabled) {
139
+ console.log('[SafeClaw] Plugin disabled');
101
140
  return;
141
+ }
142
+ // Fire-and-forget startup health check
143
+ checkConnection().catch(() => { });
102
144
  // THE GATE — constraint checking on every tool call
103
145
  api.on('before_tool_call', async (event, ctx) => {
104
146
  const r = await post('/evaluate/tool-call', {
@@ -109,10 +151,10 @@ export default {
109
151
  sessionHistory: event.sessionHistory ?? [],
110
152
  });
111
153
  if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
112
- return { block: true, blockReason: 'SafeClaw service unavailable (fail-closed)' };
154
+ return { block: true, blockReason: `SafeClaw service unavailable at ${config.serviceUrl} (fail-closed)` };
113
155
  }
114
156
  else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
115
- console.warn('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
157
+ console.warn(`[SafeClaw] Service unavailable at ${config.serviceUrl} (fail-closed mode, warn-only)`);
116
158
  }
117
159
  if (r?.block) {
118
160
  if (config.enforcement === 'enforce') {
package/index.ts CHANGED
@@ -26,7 +26,7 @@ interface SafeClawPluginConfig {
26
26
 
27
27
  function loadConfig(): SafeClawPluginConfig {
28
28
  const defaults: SafeClawPluginConfig = {
29
- serviceUrl: process.env.SAFECLAW_URL ?? 'http://localhost:8420/api/v1',
29
+ serviceUrl: process.env.SAFECLAW_URL ?? 'https://api.safeclaw.eu/api/v1',
30
30
  apiKey: process.env.SAFECLAW_API_KEY ?? '',
31
31
  timeoutMs: parseInt(process.env.SAFECLAW_TIMEOUT_MS ?? '500', 10),
32
32
  enabled: process.env.SAFECLAW_ENABLED !== 'false',
@@ -93,15 +93,25 @@ async function post(path: string, body: Record<string, unknown>): Promise<Record
93
93
  signal: AbortSignal.timeout(config.timeoutMs),
94
94
  });
95
95
  if (!res.ok) {
96
- console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
96
+ // Try to parse structured error body from service
97
+ try {
98
+ const errBody = await res.json() as Record<string, unknown>;
99
+ const detail = errBody.detail ?? `HTTP ${res.status}`;
100
+ const hint = errBody.hint ? ` (${errBody.hint})` : '';
101
+ console.warn(`[SafeClaw] ${path}: ${detail}${hint}`);
102
+ } catch {
103
+ console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
104
+ }
97
105
  return null; // Caller checks failMode
98
106
  }
99
107
  return await res.json() as Record<string, unknown>;
100
108
  } catch (e) {
101
109
  if (e instanceof DOMException && e.name === 'TimeoutError') {
102
- console.debug(`[SafeClaw] Timeout on ${path}`);
110
+ console.warn(`[SafeClaw] Timeout after ${config.timeoutMs}ms on ${path} (${config.serviceUrl})`);
111
+ } else if (e instanceof TypeError && (e.message.includes('fetch') || e.message.includes('ECONNREFUSED'))) {
112
+ console.warn(`[SafeClaw] Connection refused: ${config.serviceUrl}${path} — is the service running?`);
103
113
  } else {
104
- console.debug(`[SafeClaw] Service unavailable: ${path}`);
114
+ console.warn(`[SafeClaw] Service unavailable: ${config.serviceUrl}${path}`);
105
115
  }
106
116
  return null; // Caller checks failMode
107
117
  }
@@ -129,13 +139,44 @@ interface PluginApi {
129
139
  ): void;
130
140
  }
131
141
 
142
+ async function checkConnection(): Promise<void> {
143
+ const label = `[SafeClaw]`;
144
+ console.log(`${label} Connecting to ${config.serviceUrl} ...`);
145
+ console.log(`${label} Mode: enforcement=${config.enforcement}, failMode=${config.failMode}`);
146
+
147
+ try {
148
+ const res = await fetch(`${config.serviceUrl}/health`, {
149
+ signal: AbortSignal.timeout(config.timeoutMs * 2),
150
+ });
151
+ if (res.ok) {
152
+ const data = await res.json() as Record<string, unknown>;
153
+ console.log(`${label} ✓ Connected — service ${data.status ?? 'ok'}`);
154
+ } else {
155
+ console.warn(`${label} ✗ Service responded with HTTP ${res.status}`);
156
+ }
157
+ } catch {
158
+ console.warn(`${label} ✗ Cannot reach service at ${config.serviceUrl}`);
159
+ if (config.failMode === 'closed') {
160
+ console.warn(`${label} fail-mode=closed → tool calls will be BLOCKED until service is reachable`);
161
+ } else {
162
+ console.warn(`${label} fail-mode=open → tool calls will be ALLOWED despite no connection`);
163
+ }
164
+ }
165
+ }
166
+
132
167
  export default {
133
168
  id: 'openclaw-safeclaw-plugin',
134
169
  name: 'SafeClaw Neurosymbolic Governance',
135
- version: '0.1.0',
170
+ version: '0.1.2',
136
171
 
137
172
  register(api: PluginApi) {
138
- if (!config.enabled) return;
173
+ if (!config.enabled) {
174
+ console.log('[SafeClaw] Plugin disabled');
175
+ return;
176
+ }
177
+
178
+ // Fire-and-forget startup health check
179
+ checkConnection().catch(() => {});
139
180
 
140
181
  // THE GATE — constraint checking on every tool call
141
182
  api.on('before_tool_call', async (event: PluginEvent, ctx: PluginContext) => {
@@ -148,9 +189,9 @@ export default {
148
189
  });
149
190
 
150
191
  if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
151
- return { block: true, blockReason: 'SafeClaw service unavailable (fail-closed)' };
192
+ return { block: true, blockReason: `SafeClaw service unavailable at ${config.serviceUrl} (fail-closed)` };
152
193
  } else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
153
- console.warn('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
194
+ console.warn(`[SafeClaw] Service unavailable at ${config.serviceUrl} (fail-closed mode, warn-only)`);
154
195
  }
155
196
  if (r?.block) {
156
197
  if (config.enforcement === 'enforce') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-safeclaw-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",