openclaw-safeclaw-plugin 1.0.0 → 1.1.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 +9 -7
- package/cli.tsx +116 -26
- package/dist/cli.js +120 -26
- package/dist/index.js +88 -55
- 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 +94 -2
- package/dist/tui/config.d.ts +4 -0
- package/dist/tui/config.js +42 -3
- package/index.ts +89 -55
- package/package.json +2 -2
- package/tui/About.tsx +21 -4
- package/tui/App.tsx +1 -1
- package/tui/Settings.tsx +31 -7
- package/tui/Status.tsx +128 -2
- package/tui/config.ts +42 -2
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ npm install -g openclaw-safeclaw-plugin
|
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
npm install -g openclaw-safeclaw-plugin
|
|
18
|
-
safeclaw connect <your-api-key>
|
|
19
|
-
safeclaw restart-openclaw
|
|
18
|
+
safeclaw-plugin connect <your-api-key>
|
|
19
|
+
safeclaw-plugin restart-openclaw
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
That's it. Every tool call your AI agent makes is now governed by SafeClaw.
|
|
@@ -24,10 +24,12 @@ That's it. Every tool call your AI agent makes is now governed by SafeClaw.
|
|
|
24
24
|
## Commands
|
|
25
25
|
|
|
26
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
|
|
30
|
-
safeclaw
|
|
27
|
+
safeclaw-plugin connect <api-key> Connect to SafeClaw and register with OpenClaw
|
|
28
|
+
safeclaw-plugin setup Register plugin with OpenClaw (no key needed)
|
|
29
|
+
safeclaw-plugin config show Show current plugin configuration
|
|
30
|
+
safeclaw-plugin config set <k> <v> Set a plugin configuration value
|
|
31
|
+
safeclaw-plugin tui Open the interactive settings TUI
|
|
32
|
+
safeclaw-plugin restart-openclaw Restart the OpenClaw daemon
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
## What It Does
|
|
@@ -55,7 +57,7 @@ Set via environment variables or `~/.safeclaw/config.json`:
|
|
|
55
57
|
| Variable | Default | Description |
|
|
56
58
|
|----------|---------|-------------|
|
|
57
59
|
| `SAFECLAW_URL` | `https://api.safeclaw.eu/api/v1` | SafeClaw service URL |
|
|
58
|
-
| `SAFECLAW_API_KEY` | *(empty)* | API key (set automatically by `safeclaw connect`) |
|
|
60
|
+
| `SAFECLAW_API_KEY` | *(empty)* | API key (set automatically by `safeclaw-plugin connect`) |
|
|
59
61
|
| `SAFECLAW_TIMEOUT_MS` | `5000` | Request timeout in ms |
|
|
60
62
|
| `SAFECLAW_ENABLED` | `true` | Set `false` to disable |
|
|
61
63
|
| `SAFECLAW_ENFORCEMENT` | `enforce` | `enforce`, `warn-only`, `audit-only`, or `disabled` |
|
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, 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
|
|
|
@@ -399,13 +472,30 @@ if (command === 'connect') {
|
|
|
399
472
|
console.log('Some checks failed. Fix the issues above.');
|
|
400
473
|
}
|
|
401
474
|
} else {
|
|
402
|
-
console.log('
|
|
475
|
+
console.log('safeclaw-plugin — OpenClaw plugin CLI for SafeClaw governance');
|
|
476
|
+
console.log('');
|
|
477
|
+
console.log('Usage: safeclaw-plugin <command> [options]');
|
|
478
|
+
console.log('');
|
|
479
|
+
console.log('Setup:');
|
|
480
|
+
console.log(' connect <api-key> Save API key, validate via handshake, register with OpenClaw');
|
|
481
|
+
console.log(' Keys start with "sc_". Get yours at https://safeclaw.eu/dashboard');
|
|
482
|
+
console.log(' setup Register plugin with OpenClaw without an API key (manual setup)');
|
|
483
|
+
console.log(' restart-openclaw Restart the OpenClaw daemon to pick up plugin changes');
|
|
484
|
+
console.log('');
|
|
485
|
+
console.log('Diagnostics:');
|
|
486
|
+
console.log(' status Run 8 checks: config, API key, service health, evaluate endpoint,');
|
|
487
|
+
console.log(' handshake, OpenClaw binary, plugin files, OpenClaw config');
|
|
488
|
+
console.log('');
|
|
489
|
+
console.log('Configuration:');
|
|
490
|
+
console.log(' config show Show current enforcement, failMode, enabled, serviceUrl, apiKey');
|
|
491
|
+
console.log(' config set <k> <v> Set a config value. Keys: enforcement, failMode, enabled, serviceUrl');
|
|
492
|
+
console.log(' enforcement: enforce | warn-only | audit-only | disabled');
|
|
493
|
+
console.log(' failMode: open (allow on error) | closed (block on error)');
|
|
494
|
+
console.log(' enabled: true | false');
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log('Interactive:');
|
|
497
|
+
console.log(' tui Open the interactive settings TUI (Status, Settings, About tabs)');
|
|
403
498
|
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');
|
|
499
|
+
console.log('For the service CLI (serve, audit, policy, pref), use the "safeclaw" command.');
|
|
410
500
|
process.exit(0);
|
|
411
501
|
}
|
package/dist/cli.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
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 } 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 } from './tui/config.js';
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const PKG_VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')).version;
|
|
12
13
|
function readJson(path) {
|
|
13
14
|
try {
|
|
14
15
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
@@ -82,14 +83,18 @@ function registerWithOpenClaw() {
|
|
|
82
83
|
}
|
|
83
84
|
const args = process.argv.slice(2);
|
|
84
85
|
const command = args[0];
|
|
85
|
-
|
|
86
|
+
// Handle --help / -h for any command position
|
|
87
|
+
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
|
88
|
+
// Fall through to the else block at the bottom which prints full help
|
|
89
|
+
}
|
|
90
|
+
else if (command === 'connect') {
|
|
86
91
|
const apiKey = args[1];
|
|
87
92
|
const serviceUrlIdx = args.indexOf('--service-url');
|
|
88
93
|
const serviceUrl = serviceUrlIdx !== -1 && args[serviceUrlIdx + 1]
|
|
89
94
|
? args[serviceUrlIdx + 1]
|
|
90
95
|
: 'https://api.safeclaw.eu/api/v1';
|
|
91
96
|
if (!apiKey || apiKey.startsWith('--')) {
|
|
92
|
-
console.error('Usage: safeclaw connect <api-key> [--service-url <url>]');
|
|
97
|
+
console.error('Usage: safeclaw-plugin connect <api-key> [--service-url <url>]');
|
|
93
98
|
process.exit(1);
|
|
94
99
|
}
|
|
95
100
|
if (!apiKey.startsWith('sc_')) {
|
|
@@ -115,10 +120,9 @@ if (command === 'connect') {
|
|
|
115
120
|
}
|
|
116
121
|
config.remote.apiKey = apiKey;
|
|
117
122
|
config.remote.serviceUrl = serviceUrl;
|
|
118
|
-
// Write config with owner-only permissions
|
|
119
|
-
mkdirSync(configDir, { recursive: true });
|
|
120
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
121
|
-
chmodSync(configPath, 0o600);
|
|
123
|
+
// Write config with owner-only permissions (atomic mode via writeFileSync option)
|
|
124
|
+
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
125
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
122
126
|
console.log(`API key saved to ${configPath}`);
|
|
123
127
|
// Validate key via handshake
|
|
124
128
|
try {
|
|
@@ -128,7 +132,7 @@ if (command === 'connect') {
|
|
|
128
132
|
'Content-Type': 'application/json',
|
|
129
133
|
'Authorization': `Bearer ${apiKey}`,
|
|
130
134
|
},
|
|
131
|
-
body: JSON.stringify({ pluginVersion:
|
|
135
|
+
body: JSON.stringify({ pluginVersion: PKG_VERSION, configHash: '' }),
|
|
132
136
|
signal: AbortSignal.timeout(5000),
|
|
133
137
|
});
|
|
134
138
|
if (res.ok) {
|
|
@@ -150,7 +154,7 @@ if (command === 'connect') {
|
|
|
150
154
|
}
|
|
151
155
|
catch {
|
|
152
156
|
console.warn(`Warning: API key saved but could not reach ${serviceUrl}`);
|
|
153
|
-
console.warn('Run "safeclaw status" later to verify the connection.');
|
|
157
|
+
console.warn('Run "safeclaw-plugin status" later to verify the connection.');
|
|
154
158
|
}
|
|
155
159
|
// Register with OpenClaw
|
|
156
160
|
console.log('Registering SafeClaw plugin with OpenClaw...');
|
|
@@ -159,7 +163,7 @@ if (command === 'connect') {
|
|
|
159
163
|
console.log('SafeClaw plugin registered with OpenClaw.');
|
|
160
164
|
console.log('');
|
|
161
165
|
console.log('Restart OpenClaw to activate:');
|
|
162
|
-
console.log(' safeclaw restart-openclaw');
|
|
166
|
+
console.log(' safeclaw-plugin restart-openclaw');
|
|
163
167
|
}
|
|
164
168
|
else {
|
|
165
169
|
console.log('');
|
|
@@ -168,6 +172,79 @@ if (command === 'connect') {
|
|
|
168
172
|
console.log(' openclaw plugins install openclaw-safeclaw-plugin');
|
|
169
173
|
}
|
|
170
174
|
}
|
|
175
|
+
else if (command === 'config') {
|
|
176
|
+
const subcommand = args[1];
|
|
177
|
+
if (subcommand === 'show') {
|
|
178
|
+
const cfg = loadConfig();
|
|
179
|
+
console.log(`enabled: ${cfg.enabled}`);
|
|
180
|
+
console.log(`enforcement: ${cfg.enforcement}`);
|
|
181
|
+
console.log(`failMode: ${cfg.failMode}`);
|
|
182
|
+
console.log(`serviceUrl: ${cfg.serviceUrl}`);
|
|
183
|
+
console.log(`apiKey: ${cfg.apiKey ? `${cfg.apiKey.slice(0, 6)}...` : '(not set)'}`);
|
|
184
|
+
console.log(`timeoutMs: ${cfg.timeoutMs}`);
|
|
185
|
+
}
|
|
186
|
+
else if (subcommand === 'set') {
|
|
187
|
+
const key = args[2];
|
|
188
|
+
const value = args[3];
|
|
189
|
+
if (!key || !value) {
|
|
190
|
+
console.error('Usage: safeclaw-plugin config set <key> <value>');
|
|
191
|
+
console.error('');
|
|
192
|
+
console.error('Keys:');
|
|
193
|
+
console.error(' enforcement enforce | warn-only | audit-only | disabled');
|
|
194
|
+
console.error(' failMode open | closed');
|
|
195
|
+
console.error(' enabled true | false');
|
|
196
|
+
console.error(' serviceUrl https://...');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
const cfg = loadConfig();
|
|
200
|
+
const validEnforcement = ['enforce', 'warn-only', 'audit-only', 'disabled'];
|
|
201
|
+
const validFailModes = ['open', 'closed'];
|
|
202
|
+
if (key === 'enforcement') {
|
|
203
|
+
if (!validEnforcement.includes(value)) {
|
|
204
|
+
console.error(`Invalid enforcement mode: "${value}". Valid: ${validEnforcement.join(', ')}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
cfg.enforcement = value;
|
|
208
|
+
}
|
|
209
|
+
else if (key === 'failMode') {
|
|
210
|
+
if (!validFailModes.includes(value)) {
|
|
211
|
+
console.error(`Invalid fail mode: "${value}". Valid: ${validFailModes.join(', ')}`);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
cfg.failMode = value;
|
|
215
|
+
}
|
|
216
|
+
else if (key === 'enabled') {
|
|
217
|
+
if (value !== 'true' && value !== 'false') {
|
|
218
|
+
console.error('Invalid value for enabled: must be "true" or "false"');
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
cfg.enabled = value === 'true';
|
|
222
|
+
}
|
|
223
|
+
else if (key === 'serviceUrl') {
|
|
224
|
+
cfg.serviceUrl = value;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.error(`Unknown config key: "${key}"`);
|
|
228
|
+
console.error('Valid keys: enforcement, failMode, enabled, serviceUrl');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
saveConfig(cfg);
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
console.error(`Error: ${e instanceof Error ? e.message : e}`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
console.log(`Set ${key} = ${value}`);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
console.error('Usage: safeclaw-plugin config <show|set>');
|
|
242
|
+
console.error('');
|
|
243
|
+
console.error(' show Display all current config values');
|
|
244
|
+
console.error(' set <k> <v> Set a config value (enforcement, failMode, enabled, serviceUrl)');
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
171
248
|
else if (command === 'tui') {
|
|
172
249
|
render(React.createElement(App));
|
|
173
250
|
}
|
|
@@ -191,8 +268,8 @@ else if (command === 'setup') {
|
|
|
191
268
|
console.log('');
|
|
192
269
|
console.log('Next steps:');
|
|
193
270
|
console.log(' 1. Get an API key at https://safeclaw.eu/dashboard');
|
|
194
|
-
console.log(' 2. Run: safeclaw connect <your-api-key>');
|
|
195
|
-
console.log(' 3. Run: safeclaw restart-openclaw');
|
|
271
|
+
console.log(' 2. Run: safeclaw-plugin connect <your-api-key>');
|
|
272
|
+
console.log(' 3. Run: safeclaw-plugin restart-openclaw');
|
|
196
273
|
}
|
|
197
274
|
else {
|
|
198
275
|
console.log('Could not auto-register.');
|
|
@@ -212,7 +289,7 @@ else if (command === 'status') {
|
|
|
212
289
|
console.log('[ok] Config file: ' + configPath);
|
|
213
290
|
}
|
|
214
291
|
else {
|
|
215
|
-
console.log('[!!] Config file not found. Run: safeclaw connect <api-key>');
|
|
292
|
+
console.log('[!!] Config file not found. Run: safeclaw-plugin connect <api-key>');
|
|
216
293
|
allOk = false;
|
|
217
294
|
}
|
|
218
295
|
// 2. API key
|
|
@@ -224,7 +301,7 @@ else if (command === 'status') {
|
|
|
224
301
|
allOk = false;
|
|
225
302
|
}
|
|
226
303
|
else {
|
|
227
|
-
console.log('[!!] API key: not set. Run: safeclaw connect <api-key>');
|
|
304
|
+
console.log('[!!] API key: not set. Run: safeclaw-plugin connect <api-key>');
|
|
228
305
|
allOk = false;
|
|
229
306
|
}
|
|
230
307
|
// 3. SafeClaw service — health check (uses same timeout as plugin)
|
|
@@ -302,7 +379,7 @@ else if (command === 'status') {
|
|
|
302
379
|
'Content-Type': 'application/json',
|
|
303
380
|
'Authorization': `Bearer ${cfg.apiKey}`,
|
|
304
381
|
},
|
|
305
|
-
body: JSON.stringify({ pluginVersion:
|
|
382
|
+
body: JSON.stringify({ pluginVersion: PKG_VERSION, configHash: '' }),
|
|
306
383
|
signal: AbortSignal.timeout(cfg.timeoutMs),
|
|
307
384
|
});
|
|
308
385
|
if (res.ok) {
|
|
@@ -344,7 +421,7 @@ else if (command === 'status') {
|
|
|
344
421
|
}
|
|
345
422
|
else if (serviceHealthy && !cfg.apiKey) {
|
|
346
423
|
console.log('[!!] Handshake: skipped — no API key configured');
|
|
347
|
-
console.log(' ↳ Run: safeclaw connect <your-api-key>');
|
|
424
|
+
console.log(' ↳ Run: safeclaw-plugin connect <your-api-key>');
|
|
348
425
|
allOk = false;
|
|
349
426
|
}
|
|
350
427
|
// 6. OpenClaw installed
|
|
@@ -366,7 +443,7 @@ else if (command === 'status') {
|
|
|
366
443
|
else if (existsSync(extensionDir)) {
|
|
367
444
|
const stat = lstatSync(extensionDir);
|
|
368
445
|
if (stat.isSymbolicLink()) {
|
|
369
|
-
console.log('[!!] Plugin: stale symlink (run safeclaw setup to fix)');
|
|
446
|
+
console.log('[!!] Plugin: stale symlink (run safeclaw-plugin setup to fix)');
|
|
370
447
|
}
|
|
371
448
|
else {
|
|
372
449
|
console.log('[!!] Plugin: missing files in ' + extensionDir);
|
|
@@ -374,7 +451,7 @@ else if (command === 'status') {
|
|
|
374
451
|
allOk = false;
|
|
375
452
|
}
|
|
376
453
|
else {
|
|
377
|
-
console.log('[!!] Plugin: not installed. Run: safeclaw setup');
|
|
454
|
+
console.log('[!!] Plugin: not installed. Run: safeclaw-plugin setup');
|
|
378
455
|
allOk = false;
|
|
379
456
|
}
|
|
380
457
|
// 8. Plugin enabled in OpenClaw config
|
|
@@ -406,13 +483,30 @@ else if (command === 'status') {
|
|
|
406
483
|
}
|
|
407
484
|
}
|
|
408
485
|
else {
|
|
409
|
-
console.log('
|
|
486
|
+
console.log('safeclaw-plugin — OpenClaw plugin CLI for SafeClaw governance');
|
|
487
|
+
console.log('');
|
|
488
|
+
console.log('Usage: safeclaw-plugin <command> [options]');
|
|
489
|
+
console.log('');
|
|
490
|
+
console.log('Setup:');
|
|
491
|
+
console.log(' connect <api-key> Save API key, validate via handshake, register with OpenClaw');
|
|
492
|
+
console.log(' Keys start with "sc_". Get yours at https://safeclaw.eu/dashboard');
|
|
493
|
+
console.log(' setup Register plugin with OpenClaw without an API key (manual setup)');
|
|
494
|
+
console.log(' restart-openclaw Restart the OpenClaw daemon to pick up plugin changes');
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log('Diagnostics:');
|
|
497
|
+
console.log(' status Run 8 checks: config, API key, service health, evaluate endpoint,');
|
|
498
|
+
console.log(' handshake, OpenClaw binary, plugin files, OpenClaw config');
|
|
499
|
+
console.log('');
|
|
500
|
+
console.log('Configuration:');
|
|
501
|
+
console.log(' config show Show current enforcement, failMode, enabled, serviceUrl, apiKey');
|
|
502
|
+
console.log(' config set <k> <v> Set a config value. Keys: enforcement, failMode, enabled, serviceUrl');
|
|
503
|
+
console.log(' enforcement: enforce | warn-only | audit-only | disabled');
|
|
504
|
+
console.log(' failMode: open (allow on error) | closed (block on error)');
|
|
505
|
+
console.log(' enabled: true | false');
|
|
506
|
+
console.log('');
|
|
507
|
+
console.log('Interactive:');
|
|
508
|
+
console.log(' tui Open the interactive settings TUI (Status, Settings, About tabs)');
|
|
410
509
|
console.log('');
|
|
411
|
-
console.log('
|
|
412
|
-
console.log(' connect <api-key> Connect to SafeClaw and register with OpenClaw');
|
|
413
|
-
console.log(' setup Register SafeClaw plugin with OpenClaw (no key needed)');
|
|
414
|
-
console.log(' status Check SafeClaw + OpenClaw connection status');
|
|
415
|
-
console.log(' tui Open the interactive SafeClaw settings TUI');
|
|
416
|
-
console.log(' restart-openclaw Restart the OpenClaw daemon');
|
|
510
|
+
console.log('For the service CLI (serve, audit, policy, pref), use the "safeclaw" command.');
|
|
417
511
|
process.exit(0);
|
|
418
512
|
}
|