openclaw-safeclaw-plugin 1.0.0 → 1.2.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/README.md CHANGED
@@ -1,72 +1,247 @@
1
1
  # openclaw-safeclaw-plugin
2
2
 
3
- Neurosymbolic governance plugin for OpenClaw AI agents. Validates every tool call, message, and action against safety constraints before execution.
3
+ Neurosymbolic governance plugin for OpenClaw AI agents. Validates every tool call, message, and action against OWL ontologies and SHACL constraints before execution.
4
4
 
5
- ## Install
5
+ ## Installation
6
+
7
+ ### Via ClawHub (recommended)
6
8
 
7
9
  ```bash
8
- npm install -g openclaw-safeclaw-plugin
10
+ openclaw plugins install safeclaw
9
11
  ```
10
12
 
11
- ## Quick Start
12
-
13
- 1. Sign up at [safeclaw.eu](https://safeclaw.eu) and create an API key
14
- 2. Install and connect:
13
+ ### Manual install
15
14
 
16
15
  ```bash
17
16
  npm install -g openclaw-safeclaw-plugin
18
- safeclaw connect <your-api-key>
19
- safeclaw restart-openclaw
17
+ safeclaw-plugin setup
18
+ safeclaw-plugin restart-openclaw
20
19
  ```
21
20
 
22
- That's it. Every tool call your AI agent makes is now governed by SafeClaw.
21
+ The `setup` command copies the plugin manifest to `~/.openclaw/extensions/safeclaw/` and enables it in `~/.openclaw/openclaw.json`. After install, restart OpenClaw to activate the plugin.
23
22
 
24
- ## Commands
23
+ ## Quick Start
25
24
 
26
- ```
27
- safeclaw connect <api-key> Connect to SafeClaw and register with OpenClaw
28
- safeclaw setup Register plugin with OpenClaw (no key needed)
29
- safeclaw tui Open the interactive settings TUI
30
- safeclaw restart-openclaw Restart the OpenClaw daemon
31
- ```
25
+ 1. Install the plugin (see above)
26
+ 2. Connect to SafeClaw (cloud or self-hosted):
32
27
 
33
- ## What It Does
28
+ ```bash
29
+ # Cloud
30
+ safeclaw-plugin connect <your-api-key>
34
31
 
35
- - **Blocks dangerous actions** — force push, deleting root, exposing secrets
36
- - **Enforces dependencies** tests must pass before git push
37
- - **Checks user preferences** — confirmation for irreversible actions
38
- - **Governs messages** — blocks sensitive data leaks
39
- - **Full audit trail** — every decision logged with ontological justification
32
+ # Self-hosted
33
+ safeclaw-plugin config set serviceUrl http://localhost:8420/api/v1
34
+ ```
40
35
 
41
- ## How It Works
36
+ 3. Restart OpenClaw:
42
37
 
43
- The plugin registers hooks on OpenClaw events:
38
+ ```bash
39
+ safeclaw-plugin restart-openclaw
40
+ ```
44
41
 
45
- 1. **before_tool_call** validates against SHACL shapes, policies, preferences, dependencies
46
- 2. **before_agent_start** — injects governance context into the agent's system prompt
47
- 3. **message_sending** — checks outbound messages for sensitive data
48
- 4. **after_tool_call** — records action outcomes for dependency tracking
49
- 5. **llm_input/output** — logs LLM interactions for audit
42
+ Every tool call your AI agent makes is now governed by SafeClaw.
50
43
 
51
44
  ## Configuration
52
45
 
53
- Set via environment variables or `~/.safeclaw/config.json`:
46
+ Configuration is resolved in this order (later sources override earlier ones):
47
+
48
+ 1. **Defaults** -- hardcoded in the plugin
49
+ 2. **Config file** -- `~/.safeclaw/config.json`
50
+ 3. **Environment variables** -- `SAFECLAW_*` prefixed vars
51
+ 4. **OpenClaw plugin config** -- values from `api.pluginConfig` (set via OpenClaw settings UI or `openclaw.json`)
52
+
53
+ ### Config file
54
+
55
+ Created automatically by `safeclaw-plugin connect`. Structure:
56
+
57
+ ```json
58
+ {
59
+ "enabled": true,
60
+ "remote": {
61
+ "serviceUrl": "http://localhost:8420/api/v1",
62
+ "apiKey": "sc_live_..."
63
+ },
64
+ "enforcement": {
65
+ "mode": "enforce",
66
+ "failMode": "open"
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### Environment variables
54
72
 
55
73
  | Variable | Default | Description |
56
74
  |----------|---------|-------------|
57
- | `SAFECLAW_URL` | `https://api.safeclaw.eu/api/v1` | SafeClaw service URL |
58
- | `SAFECLAW_API_KEY` | *(empty)* | API key (set automatically by `safeclaw connect`) |
59
- | `SAFECLAW_TIMEOUT_MS` | `5000` | Request timeout in ms |
60
- | `SAFECLAW_ENABLED` | `true` | Set `false` to disable |
61
- | `SAFECLAW_ENFORCEMENT` | `enforce` | `enforce`, `warn-only`, `audit-only`, or `disabled` |
62
- | `SAFECLAW_FAIL_MODE` | `open` | `open` (allow on failure) or `closed` (block on failure) |
75
+ | `SAFECLAW_URL` | `http://localhost:8420/api/v1` | SafeClaw service URL |
76
+ | `SAFECLAW_API_KEY` | *(empty)* | API key for authentication |
77
+ | `SAFECLAW_TIMEOUT_MS` | `5000` | HTTP request timeout in milliseconds |
78
+ | `SAFECLAW_ENABLED` | `true` | Set `false` to disable the plugin entirely |
79
+ | `SAFECLAW_ENFORCEMENT` | `enforce` | Enforcement mode (see below) |
80
+ | `SAFECLAW_FAIL_MODE` | `open` | Fail mode (see below) |
81
+ | `SAFECLAW_AGENT_ID` | *(empty)* | Agent identifier for multi-agent governance |
82
+ | `SAFECLAW_AGENT_TOKEN` | *(empty)* | Agent authentication token |
83
+
84
+ ### OpenClaw plugin config
85
+
86
+ When running inside OpenClaw, the plugin reads `api.pluginConfig` which maps to the `configSchema` in `openclaw.plugin.json`. These values take priority over the config file. Set them via the OpenClaw settings UI or directly in `~/.openclaw/openclaw.json`:
87
+
88
+ ```json
89
+ {
90
+ "plugins": {
91
+ "entries": {
92
+ "safeclaw": {
93
+ "enabled": true,
94
+ "config": {
95
+ "enforcement": "enforce",
96
+ "failMode": "open",
97
+ "serviceUrl": "http://localhost:8420/api/v1"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ ```
63
104
 
64
105
  ## Enforcement Modes
65
106
 
66
- - **`enforce`** block actions that violate constraints (recommended)
67
- - **`warn-only`** — log warnings but allow all actions
68
- - **`audit-only`** server-side logging only, no client-side action
69
- - **`disabled`** plugin is completely inactive
107
+ | Mode | Behavior |
108
+ |------|----------|
109
+ | `enforce` | Block tool calls and messages that violate constraints. Recommended for production. |
110
+ | `warn-only` | Log warnings but allow all actions through. Useful during initial rollout. |
111
+ | `audit-only` | Server-side logging only, no client-side warnings or blocks. |
112
+ | `disabled` | Plugin is completely inactive. No HTTP calls to the service. |
113
+
114
+ ## Fail Modes
115
+
116
+ Controls what happens when the SafeClaw service is unreachable:
117
+
118
+ | Mode | Behavior |
119
+ |------|----------|
120
+ | `open` | Allow all actions when the service is unavailable. Default. |
121
+ | `closed` | Block all actions when the service is unavailable. Use when safety is critical. |
122
+
123
+ ## Hooks
124
+
125
+ The plugin registers 11 hooks on OpenClaw events. Each hook communicates with the SafeClaw service via HTTP.
126
+
127
+ ### Blocking hooks (can prevent actions)
128
+
129
+ | Hook | Priority | Description |
130
+ |------|----------|-------------|
131
+ | `before_tool_call` | 100 | The main gate. Evaluates every tool call against SHACL shapes, policies, preferences, and dependencies. Returns `{ block: true }` if the action violates constraints. |
132
+ | `message_sending` | 100 | Checks outbound messages for sensitive data leaks, contact rule violations, and content policy. Returns `{ cancel: true }` to block. |
133
+ | `subagent_spawning` | 100 | Evaluates child agent spawn requests. Detects delegation bypass attempts where a blocked parent tries to spawn an unrestricted child. |
134
+
135
+ ### Context hooks (modify agent behavior)
136
+
137
+ | Hook | Priority | Description |
138
+ |------|----------|-------------|
139
+ | `before_prompt_build` | 100 | Injects governance context into the agent system prompt via `prependSystemContext`. Tells the agent what constraints are active. |
140
+
141
+ ### Recording hooks (fire-and-forget)
142
+
143
+ | Hook | Description |
144
+ |------|-------------|
145
+ | `after_tool_call` | Records tool execution results (success/failure, duration, errors) for dependency tracking and audit. |
146
+ | `llm_input` | Logs the prompt sent to the LLM, including provider and model name. |
147
+ | `llm_output` | Logs the LLM response, including token usage. |
148
+ | `subagent_ended` | Records child agent lifecycle completion. |
149
+ | `session_start` | Notifies the service when a new session begins. |
150
+ | `session_end` | Notifies the service when a session ends. |
151
+ | `message_received` | Evaluates inbound messages for governance (sender, channel, content). |
152
+
153
+ ## Agent Tools
154
+
155
+ The plugin registers two tools that agents can call to introspect governance state.
156
+
157
+ ### `safeclaw_status`
158
+
159
+ Returns the current governance status. No parameters.
160
+
161
+ ```json
162
+ {
163
+ "status": "ok",
164
+ "enforcement": "enforce",
165
+ "failMode": "open",
166
+ "serviceUrl": "http://localhost:8420/api/v1",
167
+ "handshakeCompleted": true
168
+ }
169
+ ```
170
+
171
+ ### `safeclaw_check_action`
172
+
173
+ Dry-run check of whether a tool call would be allowed. No side effects.
174
+
175
+ **Parameters:**
176
+ - `toolName` (string, required) -- tool name to check
177
+ - `params` (object, optional) -- tool parameters to validate
178
+
179
+ ```json
180
+ {
181
+ "block": false,
182
+ "reason": null,
183
+ "constraints": ["shacl:ActionShape", "policy:NoForceOnMain"]
184
+ }
185
+ ```
186
+
187
+ ## CLI Commands
188
+
189
+ The plugin ships a standalone CLI (`safeclaw-plugin`) and registers a `safeclaw` subcommand in the OpenClaw CLI via `api.registerCli`.
190
+
191
+ ### Standalone CLI
192
+
193
+ ```
194
+ safeclaw-plugin connect <api-key> Save API key, validate via handshake, register with OpenClaw
195
+ safeclaw-plugin setup Register plugin with OpenClaw (no key needed)
196
+ safeclaw-plugin restart-openclaw Restart the OpenClaw daemon
197
+ safeclaw-plugin status Run diagnostics (config, service, handshake, OpenClaw, NemoClaw)
198
+ safeclaw-plugin config show Show current configuration
199
+ safeclaw-plugin config set <k> <v> Set a config value (enforcement, failMode, enabled, serviceUrl)
200
+ safeclaw-plugin tui Open interactive settings TUI
201
+ ```
202
+
203
+ ### OpenClaw CLI extension
204
+
205
+ When loaded by OpenClaw, the plugin adds:
206
+
207
+ ```
208
+ openclaw safeclaw status Show SafeClaw service status and enforcement mode
209
+ ```
210
+
211
+ ## NemoClaw Sandbox
212
+
213
+ When running inside a NemoClaw sandbox (detected via the `OPENSHELL_SANDBOX` environment variable), the plugin automatically adjusts:
214
+
215
+ - **Service URL**: `localhost` is rewritten to `host.containers.internal` since the sandbox runs in a container and cannot reach the host's loopback interface directly.
216
+ - **Egress policy**: The bundled `policies/safeclaw.yaml` defines the network rules NemoClaw needs to allow SafeClaw traffic.
217
+
218
+ ### Setup
219
+
220
+ 1. Copy the egress policy into your NemoClaw configuration:
221
+
222
+ ```bash
223
+ nemoclaw policy-add safeclaw
224
+ ```
225
+
226
+ Or manually copy `policies/safeclaw.yaml` to your NemoClaw policy directory.
227
+
228
+ 2. The policy allows two destinations:
229
+ - `api.safeclaw.eu:443` (HTTPS) -- cloud service
230
+ - `host.containers.internal:8420` (HTTP) -- self-hosted service on the host machine
231
+
232
+ 3. No additional configuration is needed. The plugin detects the sandbox automatically and adjusts the service URL.
233
+
234
+ ## Architecture
235
+
236
+ This plugin is a thin HTTP bridge (~450 lines). All governance logic lives in the SafeClaw Python service. The plugin:
237
+
238
+ 1. Registers hooks on OpenClaw events
239
+ 2. Forwards event data to the SafeClaw service via HTTP POST
240
+ 3. Acts on the service response (block, warn, or allow)
241
+ 4. Sends a heartbeat every 30 seconds with config hash
242
+ 5. Registers as an OpenClaw service for clean lifecycle management (no `process.exit()`)
243
+
244
+ The plugin performs a handshake with the service on startup to validate the API key and confirm the engine is ready. If the handshake fails and `failMode` is `closed`, all tool calls are blocked until the service becomes reachable.
70
245
 
71
246
  ## License
72
247
 
package/SKILL.md CHANGED
@@ -1,48 +1,49 @@
1
- # SafeClaw Neurosymbolic Governance for OpenClaw
1
+ # SafeClaw -- Neurosymbolic Governance for OpenClaw
2
2
 
3
- SafeClaw adds ontology-based constraint checking to your OpenClaw agent. Every tool call, message, and action is validated against OWL ontologies and SHACL shapes before execution.
3
+ SafeClaw validates every tool call, message, and agent action against OWL ontologies and SHACL constraints before execution. It acts as a governance gate between your AI agent and the tools it uses.
4
4
 
5
5
  ## What it does
6
6
 
7
- - **Blocks dangerous actions** force push, deleting root, exposing secrets
8
- - **Enforces dependencies** tests must pass before git push
9
- - **Checks user preferences** confirmation for irreversible actions based on autonomy level
10
- - **Governs messages** blocks sensitive data leaks, enforces never-contact lists
11
- - **Full audit trail** every decision logged with ontological justification
7
+ - **Blocks dangerous actions** -- force push, deleting root, exposing secrets
8
+ - **Enforces dependencies** -- tests must pass before git push
9
+ - **Checks user preferences** -- confirmation for irreversible actions based on autonomy level
10
+ - **Governs messages** -- blocks sensitive data leaks, enforces contact rules
11
+ - **Controls subagent delegation** -- prevents blocked parents from spawning unrestricted children
12
+ - **Full audit trail** -- every decision logged with ontological justification
12
13
 
13
- ## Setup
14
+ ## Hooks
14
15
 
15
- The plugin connects to `https://api.safeclaw.eu/api/v1` by default — no configuration needed.
16
+ 11 hooks covering the full agent lifecycle:
16
17
 
17
- ### Self-hosted mode
18
+ - `before_tool_call` -- constraint gate for every tool invocation
19
+ - `before_prompt_build` -- injects governance context into system prompt
20
+ - `message_sending` -- outbound message governance
21
+ - `message_received` -- inbound message evaluation
22
+ - `llm_input` / `llm_output` -- LLM interaction audit logging
23
+ - `after_tool_call` -- records outcomes for dependency tracking
24
+ - `subagent_spawning` / `subagent_ended` -- multi-agent governance
25
+ - `session_start` / `session_end` -- session lifecycle tracking
18
26
 
19
- To run your own SafeClaw service, override the URL:
27
+ ## Agent tools
20
28
 
21
- ```bash
22
- export SAFECLAW_URL="http://localhost:8420/api/v1"
23
- export SAFECLAW_API_KEY="sc_live_your_key_here" # optional
24
- ```
29
+ - `safeclaw_status` -- check governance service status and active enforcement mode
30
+ - `safeclaw_check_action` -- dry-run check if a specific tool call would be allowed
25
31
 
26
32
  ## Configuration
27
33
 
28
- Set via environment variables or `~/.safeclaw/config.json`:
34
+ Set via OpenClaw plugin settings, `~/.safeclaw/config.json`, or `SAFECLAW_*` environment variables. Supports four enforcement modes (`enforce`, `warn-only`, `audit-only`, `disabled`) and two fail modes (`open`, `closed`).
35
+
36
+ ### NemoClaw sandbox
29
37
 
30
- | Variable | Default | Description |
31
- |----------|---------|-------------|
32
- | `SAFECLAW_URL` | `https://api.safeclaw.eu/api/v1` | SafeClaw service URL |
33
- | `SAFECLAW_API_KEY` | (empty) | API key for remote/cloud mode |
34
- | `SAFECLAW_TIMEOUT_MS` | `500` | Request timeout in milliseconds |
35
- | `SAFECLAW_ENABLED` | `true` | Set to `false` to disable |
36
- | `SAFECLAW_ENFORCEMENT` | `enforce` | `enforce`, `warn-only`, `audit-only`, or `disabled` |
38
+ Automatically detects NemoClaw sandboxes and rewrites `localhost` to `host.containers.internal`. Includes a bundled egress policy at `policies/safeclaw.yaml`.
37
39
 
38
- ## How it works
40
+ ### Self-hosted
39
41
 
40
- This plugin registers hooks on every OpenClaw event:
42
+ Run the SafeClaw service locally:
41
43
 
42
- 1. **before_tool_call** — validates against SHACL shapes, policies, preferences, dependencies
43
- 2. **before_agent_start** — injects governance context into the agent's system prompt
44
- 3. **message_sending** — checks outbound messages for sensitive data and contact rules
45
- 4. **after_tool_call** — records action outcomes for dependency tracking
46
- 5. **llm_input/output** — logs LLM interactions for audit
44
+ ```bash
45
+ pip install safeclaw
46
+ safeclaw serve
47
+ ```
47
48
 
48
- If the SafeClaw service is unavailable, the plugin degrades gracefully no blocks, no crashes.
49
+ The plugin connects to `http://localhost:8420/api/v1` by default.
package/cli.tsx CHANGED
@@ -2,14 +2,15 @@
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, copyFileSync, lstatSync, unlinkSync, rmSync } from 'fs';
5
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync, lstatSync, unlinkSync, rmSync } from 'fs';
6
6
  import { join, dirname } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { fileURLToPath } from 'url';
9
9
  import App from './tui/App.js';
10
- import { loadConfig } from './tui/config.js';
10
+ import { loadConfig, saveConfig, isNemoClawSandbox, getSandboxName, type SafeClawConfig } from './tui/config.js';
11
11
 
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const PKG_VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')).version as string;
13
14
 
14
15
  function readJson(path: string): Record<string, unknown> {
15
16
  try {
@@ -91,7 +92,10 @@ function registerWithOpenClaw(): boolean {
91
92
  const args = process.argv.slice(2);
92
93
  const command = args[0];
93
94
 
94
- if (command === 'connect') {
95
+ // Handle --help / -h for any command position
96
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
97
+ // Fall through to the else block at the bottom which prints full help
98
+ } else if (command === 'connect') {
95
99
  const apiKey = args[1];
96
100
  const serviceUrlIdx = args.indexOf('--service-url');
97
101
  const serviceUrl = serviceUrlIdx !== -1 && args[serviceUrlIdx + 1]
@@ -99,7 +103,7 @@ if (command === 'connect') {
99
103
  : 'https://api.safeclaw.eu/api/v1';
100
104
 
101
105
  if (!apiKey || apiKey.startsWith('--')) {
102
- console.error('Usage: safeclaw connect <api-key> [--service-url <url>]');
106
+ console.error('Usage: safeclaw-plugin connect <api-key> [--service-url <url>]');
103
107
  process.exit(1);
104
108
  }
105
109
 
@@ -129,10 +133,9 @@ if (command === 'connect') {
129
133
  (config.remote as Record<string, string>).apiKey = apiKey;
130
134
  (config.remote as Record<string, string>).serviceUrl = serviceUrl;
131
135
 
132
- // Write config with owner-only permissions
133
- mkdirSync(configDir, { recursive: true });
134
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
135
- chmodSync(configPath, 0o600);
136
+ // Write config with owner-only permissions (atomic mode via writeFileSync option)
137
+ mkdirSync(configDir, { recursive: true, mode: 0o700 });
138
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
136
139
 
137
140
  console.log(`API key saved to ${configPath}`);
138
141
 
@@ -144,7 +147,7 @@ if (command === 'connect') {
144
147
  'Content-Type': 'application/json',
145
148
  'Authorization': `Bearer ${apiKey}`,
146
149
  },
147
- body: JSON.stringify({ pluginVersion: '0.1.3', configHash: '' }),
150
+ body: JSON.stringify({ pluginVersion: PKG_VERSION, configHash: '' }),
148
151
  signal: AbortSignal.timeout(5000),
149
152
  });
150
153
  if (res.ok) {
@@ -163,7 +166,7 @@ if (command === 'connect') {
163
166
  }
164
167
  } catch {
165
168
  console.warn(`Warning: API key saved but could not reach ${serviceUrl}`);
166
- console.warn('Run "safeclaw status" later to verify the connection.');
169
+ console.warn('Run "safeclaw-plugin status" later to verify the connection.');
167
170
  }
168
171
 
169
172
  // Register with OpenClaw
@@ -173,13 +176,83 @@ if (command === 'connect') {
173
176
  console.log('SafeClaw plugin registered with OpenClaw.');
174
177
  console.log('');
175
178
  console.log('Restart OpenClaw to activate:');
176
- console.log(' safeclaw restart-openclaw');
179
+ console.log(' safeclaw-plugin restart-openclaw');
177
180
  } else {
178
181
  console.log('');
179
182
  console.log('Could not auto-register with OpenClaw.');
180
183
  console.log('Register manually:');
181
184
  console.log(' openclaw plugins install openclaw-safeclaw-plugin');
182
185
  }
186
+ } else if (command === 'config') {
187
+ const subcommand = args[1];
188
+
189
+ if (subcommand === 'show') {
190
+ const cfg = loadConfig();
191
+ console.log(`enabled: ${cfg.enabled}`);
192
+ console.log(`enforcement: ${cfg.enforcement}`);
193
+ console.log(`failMode: ${cfg.failMode}`);
194
+ console.log(`serviceUrl: ${cfg.serviceUrl}`);
195
+ console.log(`apiKey: ${cfg.apiKey ? `${cfg.apiKey.slice(0, 6)}...` : '(not set)'}`);
196
+ console.log(`timeoutMs: ${cfg.timeoutMs}`);
197
+ } else if (subcommand === 'set') {
198
+ const key = args[2];
199
+ const value = args[3];
200
+
201
+ if (!key || !value) {
202
+ console.error('Usage: safeclaw-plugin config set <key> <value>');
203
+ console.error('');
204
+ console.error('Keys:');
205
+ console.error(' enforcement enforce | warn-only | audit-only | disabled');
206
+ console.error(' failMode open | closed');
207
+ console.error(' enabled true | false');
208
+ console.error(' serviceUrl https://...');
209
+ process.exit(1);
210
+ }
211
+
212
+ const cfg = loadConfig();
213
+ const validEnforcement = ['enforce', 'warn-only', 'audit-only', 'disabled'] as const;
214
+ const validFailModes = ['open', 'closed'] as const;
215
+
216
+ if (key === 'enforcement') {
217
+ if (!validEnforcement.includes(value as any)) {
218
+ console.error(`Invalid enforcement mode: "${value}". Valid: ${validEnforcement.join(', ')}`);
219
+ process.exit(1);
220
+ }
221
+ cfg.enforcement = value as SafeClawConfig['enforcement'];
222
+ } else if (key === 'failMode') {
223
+ if (!validFailModes.includes(value as any)) {
224
+ console.error(`Invalid fail mode: "${value}". Valid: ${validFailModes.join(', ')}`);
225
+ process.exit(1);
226
+ }
227
+ cfg.failMode = value as SafeClawConfig['failMode'];
228
+ } else if (key === 'enabled') {
229
+ if (value !== 'true' && value !== 'false') {
230
+ console.error('Invalid value for enabled: must be "true" or "false"');
231
+ process.exit(1);
232
+ }
233
+ cfg.enabled = value === 'true';
234
+ } else if (key === 'serviceUrl') {
235
+ cfg.serviceUrl = value;
236
+ } else {
237
+ console.error(`Unknown config key: "${key}"`);
238
+ console.error('Valid keys: enforcement, failMode, enabled, serviceUrl');
239
+ process.exit(1);
240
+ }
241
+
242
+ try {
243
+ saveConfig(cfg);
244
+ } catch (e) {
245
+ console.error(`Error: ${e instanceof Error ? e.message : e}`);
246
+ process.exit(1);
247
+ }
248
+ console.log(`Set ${key} = ${value}`);
249
+ } else {
250
+ console.error('Usage: safeclaw-plugin config <show|set>');
251
+ console.error('');
252
+ console.error(' show Display all current config values');
253
+ console.error(' set <k> <v> Set a config value (enforcement, failMode, enabled, serviceUrl)');
254
+ process.exit(1);
255
+ }
183
256
  } else if (command === 'tui') {
184
257
  render(React.createElement(App));
185
258
  } else if (command === 'restart-openclaw') {
@@ -200,8 +273,8 @@ if (command === 'connect') {
200
273
  console.log('');
201
274
  console.log('Next steps:');
202
275
  console.log(' 1. Get an API key at https://safeclaw.eu/dashboard');
203
- console.log(' 2. Run: safeclaw connect <your-api-key>');
204
- console.log(' 3. Run: safeclaw restart-openclaw');
276
+ console.log(' 2. Run: safeclaw-plugin connect <your-api-key>');
277
+ console.log(' 3. Run: safeclaw-plugin restart-openclaw');
205
278
  } else {
206
279
  console.log('Could not auto-register.');
207
280
  console.log('Try: openclaw plugins install openclaw-safeclaw-plugin');
@@ -220,7 +293,7 @@ if (command === 'connect') {
220
293
  if (existsSync(configPath)) {
221
294
  console.log('[ok] Config file: ' + configPath);
222
295
  } else {
223
- console.log('[!!] Config file not found. Run: safeclaw connect <api-key>');
296
+ console.log('[!!] Config file not found. Run: safeclaw-plugin connect <api-key>');
224
297
  allOk = false;
225
298
  }
226
299
 
@@ -231,7 +304,7 @@ if (command === 'connect') {
231
304
  console.log('[!!] API key: invalid (must start with sc_)');
232
305
  allOk = false;
233
306
  } else {
234
- console.log('[!!] API key: not set. Run: safeclaw connect <api-key>');
307
+ console.log('[!!] API key: not set. Run: safeclaw-plugin connect <api-key>');
235
308
  allOk = false;
236
309
  }
237
310
 
@@ -306,7 +379,7 @@ if (command === 'connect') {
306
379
  'Content-Type': 'application/json',
307
380
  'Authorization': `Bearer ${cfg.apiKey}`,
308
381
  },
309
- body: JSON.stringify({ pluginVersion: '0.1.3', configHash: '' }),
382
+ body: JSON.stringify({ pluginVersion: PKG_VERSION, configHash: '' }),
310
383
  signal: AbortSignal.timeout(cfg.timeoutMs),
311
384
  });
312
385
  if (res.ok) {
@@ -341,7 +414,7 @@ if (command === 'connect') {
341
414
  }
342
415
  } else if (serviceHealthy && !cfg.apiKey) {
343
416
  console.log('[!!] Handshake: skipped — no API key configured');
344
- console.log(' ↳ Run: safeclaw connect <your-api-key>');
417
+ console.log(' ↳ Run: safeclaw-plugin connect <your-api-key>');
345
418
  allOk = false;
346
419
  }
347
420
 
@@ -363,13 +436,13 @@ if (command === 'connect') {
363
436
  } else if (existsSync(extensionDir)) {
364
437
  const stat = lstatSync(extensionDir);
365
438
  if (stat.isSymbolicLink()) {
366
- console.log('[!!] Plugin: stale symlink (run safeclaw setup to fix)');
439
+ console.log('[!!] Plugin: stale symlink (run safeclaw-plugin setup to fix)');
367
440
  } else {
368
441
  console.log('[!!] Plugin: missing files in ' + extensionDir);
369
442
  }
370
443
  allOk = false;
371
444
  } else {
372
- console.log('[!!] Plugin: not installed. Run: safeclaw setup');
445
+ console.log('[!!] Plugin: not installed. Run: safeclaw-plugin setup');
373
446
  allOk = false;
374
447
  }
375
448
 
@@ -391,6 +464,13 @@ if (command === 'connect') {
391
464
  allOk = false;
392
465
  }
393
466
 
467
+ // 9. NemoClaw sandbox
468
+ if (isNemoClawSandbox()) {
469
+ console.log(`[ok] NemoClaw sandbox: ${getSandboxName()}`);
470
+ } else {
471
+ console.log('[--] NemoClaw: not in sandbox (standalone mode)');
472
+ }
473
+
394
474
  // Summary
395
475
  console.log('');
396
476
  if (allOk) {
@@ -399,13 +479,30 @@ if (command === 'connect') {
399
479
  console.log('Some checks failed. Fix the issues above.');
400
480
  }
401
481
  } else {
402
- console.log('Usage: safeclaw <command>');
482
+ console.log('safeclaw-plugin — OpenClaw plugin CLI for SafeClaw governance');
483
+ console.log('');
484
+ console.log('Usage: safeclaw-plugin <command> [options]');
485
+ console.log('');
486
+ console.log('Setup:');
487
+ console.log(' connect <api-key> Save API key, validate via handshake, register with OpenClaw');
488
+ console.log(' Keys start with "sc_". Get yours at https://safeclaw.eu/dashboard');
489
+ console.log(' setup Register plugin with OpenClaw without an API key (manual setup)');
490
+ console.log(' restart-openclaw Restart the OpenClaw daemon to pick up plugin changes');
491
+ console.log('');
492
+ console.log('Diagnostics:');
493
+ console.log(' status Run 8 checks: config, API key, service health, evaluate endpoint,');
494
+ console.log(' handshake, OpenClaw binary, plugin files, OpenClaw config');
495
+ console.log('');
496
+ console.log('Configuration:');
497
+ console.log(' config show Show current enforcement, failMode, enabled, serviceUrl, apiKey');
498
+ console.log(' config set <k> <v> Set a config value. Keys: enforcement, failMode, enabled, serviceUrl');
499
+ console.log(' enforcement: enforce | warn-only | audit-only | disabled');
500
+ console.log(' failMode: open (allow on error) | closed (block on error)');
501
+ console.log(' enabled: true | false');
502
+ console.log('');
503
+ console.log('Interactive:');
504
+ console.log(' tui Open the interactive settings TUI (Status, Settings, About tabs)');
403
505
  console.log('');
404
- console.log('Commands:');
405
- console.log(' connect <api-key> Connect to SafeClaw and register with OpenClaw');
406
- console.log(' setup Register SafeClaw plugin with OpenClaw (no key needed)');
407
- console.log(' status Check SafeClaw + OpenClaw connection status');
408
- console.log(' tui Open the interactive SafeClaw settings TUI');
409
- console.log(' restart-openclaw Restart the OpenClaw daemon');
506
+ console.log('For the service CLI (serve, audit, policy, pref), use the "safeclaw" command.');
410
507
  process.exit(0);
411
508
  }