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 +216 -41
- package/SKILL.md +32 -31
- package/cli.tsx +123 -26
- package/dist/cli.js +127 -26
- package/dist/index.d.ts +2 -16
- package/dist/index.js +288 -85
- package/dist/tui/About.js +1 -1
- package/dist/tui/App.js +1 -1
- package/dist/tui/Settings.js +25 -6
- package/dist/tui/Status.js +95 -2
- package/dist/tui/config.d.ts +6 -0
- package/dist/tui/config.js +66 -4
- package/index.ts +305 -111
- package/openclaw.plugin.json +29 -4
- package/package.json +12 -3
- package/policies/safeclaw.yaml +14 -0
- package/tui/About.tsx +21 -4
- package/tui/App.tsx +1 -1
- package/tui/Settings.tsx +31 -7
- package/tui/Status.tsx +137 -3
- package/tui/config.ts +69 -3
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
|
|
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
|
-
##
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Via ClawHub (recommended)
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
10
|
+
openclaw plugins install safeclaw
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
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
|
|
19
|
-
safeclaw restart-openclaw
|
|
17
|
+
safeclaw-plugin setup
|
|
18
|
+
safeclaw-plugin restart-openclaw
|
|
20
19
|
```
|
|
21
20
|
|
|
22
|
-
|
|
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
|
-
##
|
|
23
|
+
## Quick Start
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
28
|
+
```bash
|
|
29
|
+
# Cloud
|
|
30
|
+
safeclaw-plugin connect <your-api-key>
|
|
34
31
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
|
|
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
|
-
|
|
36
|
+
3. Restart OpenClaw:
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
```bash
|
|
39
|
+
safeclaw-plugin restart-openclaw
|
|
40
|
+
```
|
|
44
41
|
|
|
45
|
-
|
|
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
|
-
|
|
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` | `
|
|
58
|
-
| `SAFECLAW_API_KEY` | *(empty)* | API key
|
|
59
|
-
| `SAFECLAW_TIMEOUT_MS` | `5000` |
|
|
60
|
-
| `SAFECLAW_ENABLED` | `true` | Set `false` to disable |
|
|
61
|
-
| `SAFECLAW_ENFORCEMENT` | `enforce` |
|
|
62
|
-
| `SAFECLAW_FAIL_MODE` | `open` |
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
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
|
|
1
|
+
# SafeClaw -- Neurosymbolic Governance for OpenClaw
|
|
2
2
|
|
|
3
|
-
SafeClaw
|
|
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**
|
|
8
|
-
- **Enforces dependencies**
|
|
9
|
-
- **Checks user preferences**
|
|
10
|
-
- **Governs messages**
|
|
11
|
-
- **
|
|
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
|
-
##
|
|
14
|
+
## Hooks
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
11 hooks covering the full agent lifecycle:
|
|
16
17
|
|
|
17
|
-
|
|
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
|
-
|
|
27
|
+
## Agent tools
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
40
|
+
### Self-hosted
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
Run the SafeClaw service locally:
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
5. **llm_input/output** — logs LLM interactions for audit
|
|
44
|
+
```bash
|
|
45
|
+
pip install safeclaw
|
|
46
|
+
safeclaw serve
|
|
47
|
+
```
|
|
47
48
|
|
|
48
|
-
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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('
|
|
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('
|
|
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
|
}
|