unraidclaw 0.1.14 → 0.1.16
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 +27 -13
- package/dist/index.js +44 -17
- package/dist/registry.d.ts +38 -0
- package/dist/registry.js +1172 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# unraidclaw
|
|
2
2
|
|
|
3
|
-
> OpenClaw plugin to manage your Unraid server through AI agents
|
|
3
|
+
> OpenClaw plugin to manage your Unraid server through AI agents: Docker, VMs, array, shares, system, notifications, and more, with permission control.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/unraidclaw)
|
|
6
6
|
|
|
@@ -8,22 +8,30 @@ This is the [OpenClaw](https://github.com/openclaw/openclaw) plugin for **[Unrai
|
|
|
8
8
|
|
|
9
9
|
## Prerequisites
|
|
10
10
|
|
|
11
|
-
1. **The UnraidClaw plugin installed on your Unraid server
|
|
12
|
-
2. **An UnraidClaw API key
|
|
11
|
+
1. **The UnraidClaw plugin installed on your Unraid server.** Install it from the Unraid Community Apps store, or see the [main repo](https://github.com/emaspa/unraidclaw). It runs the gateway on port `9876` (HTTPS) by default.
|
|
12
|
+
2. **An UnraidClaw API key.** Generate one on the **Settings > UnraidClaw** page in the Unraid WebGUI.
|
|
13
13
|
3. **OpenClaw** installed (`openclaw --version`).
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
18
|
+
openclaw plugins install clawhub:unraidclaw --accept-capabilities
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The same package is on npm. OpenClaw asks you to confirm installs from outside ClawHub, so installing from npm needs `--force`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
openclaw plugins install unraidclaw --force --accept-capabilities
|
|
19
25
|
```
|
|
20
26
|
|
|
21
27
|
To update to the latest version:
|
|
22
28
|
|
|
23
29
|
```bash
|
|
24
|
-
|
|
30
|
+
openclaw plugins update unraidclaw --accept-capabilities
|
|
25
31
|
```
|
|
26
32
|
|
|
33
|
+
Then restart the gateway with `openclaw gateway restart` so it loads the new version.
|
|
34
|
+
|
|
27
35
|
## Configure
|
|
28
36
|
|
|
29
37
|
Edit `~/.openclaw/openclaw.json`.
|
|
@@ -57,8 +65,8 @@ Edit `~/.openclaw/openclaw.json`.
|
|
|
57
65
|
"unraidclaw": {
|
|
58
66
|
"config": {
|
|
59
67
|
"servers": [
|
|
60
|
-
{ "name": "home", "serverUrl": "https
|
|
61
|
-
{ "name": "work", "serverUrl": "https
|
|
68
|
+
{ "name": "home", "serverUrl": "https://<home-server>:9876", "apiKey": "<api-key>", "tlsSkipVerify": true, "default": true },
|
|
69
|
+
{ "name": "work", "serverUrl": "https://<work-server>:9876", "apiKey": "<api-key>" }
|
|
62
70
|
]
|
|
63
71
|
}
|
|
64
72
|
}
|
|
@@ -67,21 +75,21 @@ Edit `~/.openclaw/openclaw.json`.
|
|
|
67
75
|
}
|
|
68
76
|
```
|
|
69
77
|
|
|
70
|
-
With multi-server config, every tool accepts an optional `server` parameter (e.g. `unraid_docker_list(server: "work")`); the
|
|
78
|
+
With multi-server config, every tool accepts an optional `server` parameter (e.g. `unraid_docker_list(server: "work")`); the first server marked `default` is used when it's omitted, or the first configured server if none is marked.
|
|
71
79
|
|
|
72
|
-
Set `tlsSkipVerify: true`
|
|
80
|
+
Set `tlsSkipVerify: true` to accept the gateway's self-signed certificate. The plugin does not verify the certificate in that mode, so regenerating the certificate on the server does not affect it. The repository README's [TLS certificate](https://github.com/emaspa/unraidclaw#tls-certificate) section explains what the certificate contains and how strict clients can trust it.
|
|
73
81
|
|
|
74
82
|
### Keeping the API key out of the config file
|
|
75
83
|
|
|
76
84
|
You don't have to hard-code the key in `openclaw.json`. Two options:
|
|
77
85
|
|
|
78
|
-
**Environment variable
|
|
86
|
+
**Environment variable.** OpenClaw expands `${VAR}` references at config-load time:
|
|
79
87
|
|
|
80
88
|
```json
|
|
81
89
|
"apiKey": "${UNRAID_API_KEY}"
|
|
82
90
|
```
|
|
83
91
|
|
|
84
|
-
**Provider-backed secret (`SecretRef`)
|
|
92
|
+
**Provider-backed secret (`SecretRef`).** Point `apiKey` at one of your configured secret providers; OpenClaw resolves it before the plugin loads, so the plugin only ever sees the resolved string:
|
|
85
93
|
|
|
86
94
|
```json
|
|
87
95
|
"apiKey": { "source": "file", "provider": "default", "id": "/unraidclaw_key" }
|
|
@@ -91,7 +99,7 @@ You don't have to hard-code the key in `openclaw.json`. Two options:
|
|
|
91
99
|
|
|
92
100
|
## Usage
|
|
93
101
|
|
|
94
|
-
Once installed and configured,
|
|
102
|
+
Once installed and configured, ask your agent:
|
|
95
103
|
|
|
96
104
|
- "List all running Docker containers"
|
|
97
105
|
- "Stop the plex container"
|
|
@@ -127,9 +135,11 @@ Once installed and configured, just ask your agent:
|
|
|
127
135
|
|
|
128
136
|
`unraid_ca_update` and `unraid_ca_remove` act on an installed app, so their `name` is the container's name from the Docker tab, not the app's name in the catalog. Update keeps the configuration saved on the server and restores the running or stopped state; remove deletes the container and leaves appdata, volumes, the image and the template alone. Both take `dryRun`.
|
|
129
137
|
|
|
138
|
+
`unraid_docker_create` takes `image` plus optional `name`, `ports`, `volumes`, `env`, `restart` and `network`, and the advanced settings of Unraid's container form: `extraArgs` (Extra Parameters, docker run options such as `--gpus all` or `--memory=8g`), `postArgs` (Post Arguments, the container command), `staticIp` (a fixed IPv4 or IPv6 address, which needs a macvlan, ipvlan or custom bridge network), `privileged`, `cpuset` and `devices`. Each is saved to the matching field of the container's template. The two free-form fields accept only letters, digits, `: . , / + = _ -` and single spaces, because Unraid passes them to the shell unescaped when it rebuilds a container. A container created with any of these settings in effect cannot be rebuilt by `unraid_ca_update`, which refuses settings it does not reproduce exactly; use the Docker tab for it.
|
|
139
|
+
|
|
130
140
|
The six plugin tools manage Unraid `.plg` plugins through Unraid's own plugin manager. Installing one runs vendor code as root, checking for an update downloads a plugin file and stages it, and removing one runs the plugin's removal script, which may take its data with it. All four mutating tools take `dryRun`.
|
|
131
141
|
|
|
132
|
-
|
|
142
|
+
Tools use the gateway's 30-key `resource:action` permission matrix configured from the Unraid WebGUI. Health requires no permission.
|
|
133
143
|
|
|
134
144
|
## Links
|
|
135
145
|
|
|
@@ -137,6 +147,10 @@ Every tool is gated by a 30-key `resource:action` permission matrix configured f
|
|
|
137
147
|
- [Issues](https://github.com/emaspa/unraidclaw/issues)
|
|
138
148
|
- [Unraid Community Apps](https://unraid.net/community/apps)
|
|
139
149
|
|
|
150
|
+
## Gateway MCP mode
|
|
151
|
+
|
|
152
|
+
The gateway has an optional MCP endpoint at `/mcp` that serves the same 55 tools to MCP clients. It is off by default and is switched on with **Enable MCP** in the gateway's Settings tab. This plugin does not use it: OpenClaw keeps calling `/api/*` whether MCP is on or off. The tool definitions in this package are shared with the gateway through the `unraidclaw/tools` export, so OpenClaw, MCP and the standalone CLI use the same tools and the `READ_ONLY` set exported by `src/registry.ts`. The CLI, command `unraidclaw`, is published to npm as `unraidclaw-cli` and attached to each [GitHub release](https://github.com/emaspa/unraidclaw/releases/latest) as `unraidclaw-cli-<version>.tar.gz`; see the [CLI guide](https://github.com/emaspa/unraidclaw/blob/main/packages/cli/README.md). See the [repository README](https://github.com/emaspa/unraidclaw#mcp) for MCP client setup.
|
|
153
|
+
|
|
140
154
|
## License
|
|
141
155
|
|
|
142
156
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -109,9 +109,12 @@ var UnraidClient = class {
|
|
|
109
109
|
function textResult(data) {
|
|
110
110
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
111
111
|
}
|
|
112
|
+
var failures = /* @__PURE__ */ new WeakSet();
|
|
112
113
|
function errorResult(err) {
|
|
113
114
|
const message = err instanceof Error ? err.message : String(err);
|
|
114
|
-
|
|
115
|
+
const result = { content: [{ type: "text", text: `Error: ${message}` }] };
|
|
116
|
+
failures.add(result);
|
|
117
|
+
return result;
|
|
115
118
|
}
|
|
116
119
|
function checkParams(params, allowed) {
|
|
117
120
|
for (const key of Object.keys(params)) {
|
|
@@ -194,7 +197,7 @@ function registerDockerTools(api, getClient) {
|
|
|
194
197
|
type: "object",
|
|
195
198
|
properties: {
|
|
196
199
|
id: { type: "string", description: "Container ID or name" },
|
|
197
|
-
tail: { type: "
|
|
200
|
+
tail: { type: "integer", minimum: 1, maximum: 1e4, description: "Number of lines from the end (default: 100)" },
|
|
198
201
|
since: { type: "string", description: "Show logs since timestamp (e.g., 2024-01-01T00:00:00Z)" },
|
|
199
202
|
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
200
203
|
},
|
|
@@ -261,7 +264,7 @@ function registerDockerTools(api, getClient) {
|
|
|
261
264
|
});
|
|
262
265
|
api.registerTool({
|
|
263
266
|
name: "unraid_docker_create",
|
|
264
|
-
description: "Create and start a new Docker container on the Unraid server. Specify image, optional name, port mappings, volume mounts, environment variables, restart policy, and network.",
|
|
267
|
+
description: "Create and start a new Docker container on the Unraid server. Specify image, optional name, port mappings, volume mounts, environment variables, restart policy, network, and Unraid's advanced settings: extra docker run options, post arguments, a fixed IP on a custom network, privileged mode, CPU pinning and host devices.",
|
|
265
268
|
parameters: {
|
|
266
269
|
type: "object",
|
|
267
270
|
properties: {
|
|
@@ -288,6 +291,25 @@ function registerDockerTools(api, getClient) {
|
|
|
288
291
|
description: "Restart policy (default: unless-stopped)"
|
|
289
292
|
},
|
|
290
293
|
network: { type: "string", description: "Network to attach the container to" },
|
|
294
|
+
extraArgs: {
|
|
295
|
+
type: "string",
|
|
296
|
+
description: "Extra docker run options, space separated, as Unraid's Extra Parameters field (e.g. '--gpus all', '--cap-add=SYS_ADMIN --memory=8g', '--hostname=media'). Must start with an option. Only letters, digits, : . , / + = _ - and single spaces; no quotes or shell characters."
|
|
297
|
+
},
|
|
298
|
+
postArgs: {
|
|
299
|
+
type: "string",
|
|
300
|
+
description: "Arguments appended after the image as the container command, space separated, as Unraid's Post Arguments field (e.g. '--config /config/app.yml'). Same character rules as extraArgs."
|
|
301
|
+
},
|
|
302
|
+
staticIp: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description: "Fixed IPv4 or IPv6 address for the container (Unraid's Fixed IP field). Needs a user-defined network such as a macvlan, ipvlan or custom bridge; docker refuses it on bridge, host and none."
|
|
305
|
+
},
|
|
306
|
+
privileged: { type: "boolean", description: "Run the container privileged, with full host access (default: false)" },
|
|
307
|
+
cpuset: { type: "string", description: "CPUs the container may run on, as docker --cpuset-cpus reads it (e.g. '0-3,8')" },
|
|
308
|
+
devices: {
|
|
309
|
+
type: "array",
|
|
310
|
+
items: { type: "string" },
|
|
311
|
+
description: "Host devices to pass through, as docker --device reads them (e.g. ['/dev/dri', '/dev/ttyUSB0:/dev/ttyUSB0:rwm'])"
|
|
312
|
+
},
|
|
291
313
|
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
292
314
|
},
|
|
293
315
|
required: ["image"]
|
|
@@ -1078,7 +1100,7 @@ function registerNotificationTools(api, getClient) {
|
|
|
1078
1100
|
title: { type: "string", description: "Notification title" },
|
|
1079
1101
|
subject: { type: "string", description: "Notification subject" },
|
|
1080
1102
|
description: { type: "string", description: "Notification body text" },
|
|
1081
|
-
importance: { type: "string", description: "Importance level: alert, warning, or normal" },
|
|
1103
|
+
importance: { type: "string", enum: ["normal", "warning", "alert"], description: "Importance level: alert, warning, or normal" },
|
|
1082
1104
|
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
1083
1105
|
},
|
|
1084
1106
|
required: ["title", "subject", "description"]
|
|
@@ -1206,6 +1228,23 @@ function registerLogTools(api, getClient) {
|
|
|
1206
1228
|
});
|
|
1207
1229
|
}
|
|
1208
1230
|
|
|
1231
|
+
// src/registry.ts
|
|
1232
|
+
function registerTools(api, getClient) {
|
|
1233
|
+
registerHealthTools(api, getClient);
|
|
1234
|
+
registerDockerTools(api, getClient);
|
|
1235
|
+
registerCaTools(api, getClient);
|
|
1236
|
+
registerPluginTools(api, getClient);
|
|
1237
|
+
registerVMTools(api, getClient);
|
|
1238
|
+
registerArrayTools(api, getClient);
|
|
1239
|
+
registerDiskTools(api, getClient);
|
|
1240
|
+
registerShareTools(api, getClient);
|
|
1241
|
+
registerSystemTools(api, getClient);
|
|
1242
|
+
registerNotificationTools(api, getClient);
|
|
1243
|
+
registerNetworkTools(api, getClient);
|
|
1244
|
+
registerUserTools(api, getClient);
|
|
1245
|
+
registerLogTools(api, getClient);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1209
1248
|
// src/index.ts
|
|
1210
1249
|
function resolveServers(api) {
|
|
1211
1250
|
const raw = api.config?.servers ?? api.pluginConfig?.servers ?? api.config?.plugins?.entries?.unraidclaw?.config?.servers;
|
|
@@ -1235,19 +1274,7 @@ function register(api) {
|
|
|
1235
1274
|
}
|
|
1236
1275
|
return client;
|
|
1237
1276
|
}
|
|
1238
|
-
|
|
1239
|
-
registerDockerTools(api, getClient);
|
|
1240
|
-
registerCaTools(api, getClient);
|
|
1241
|
-
registerPluginTools(api, getClient);
|
|
1242
|
-
registerVMTools(api, getClient);
|
|
1243
|
-
registerArrayTools(api, getClient);
|
|
1244
|
-
registerDiskTools(api, getClient);
|
|
1245
|
-
registerShareTools(api, getClient);
|
|
1246
|
-
registerSystemTools(api, getClient);
|
|
1247
|
-
registerNotificationTools(api, getClient);
|
|
1248
|
-
registerNetworkTools(api, getClient);
|
|
1249
|
-
registerUserTools(api, getClient);
|
|
1250
|
-
registerLogTools(api, getClient);
|
|
1277
|
+
registerTools(api, getClient);
|
|
1251
1278
|
const servers = resolveServers(api);
|
|
1252
1279
|
if (servers.length > 1) {
|
|
1253
1280
|
log.info(`UnraidClaw: registered tools for ${servers.length} servers: ${servers.map((s) => s.name).join(", ")}`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface ToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
parameters: JsonSchema;
|
|
5
|
+
execute: (id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
6
|
+
}
|
|
7
|
+
interface ToolOptions {
|
|
8
|
+
optional?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface JsonSchema {
|
|
11
|
+
type: string;
|
|
12
|
+
properties?: Record<string, unknown>;
|
|
13
|
+
required?: string[];
|
|
14
|
+
additionalProperties?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface ToolResult {
|
|
17
|
+
content: Array<{
|
|
18
|
+
type: "text";
|
|
19
|
+
text: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare function isErrorResult(result: ToolResult): boolean;
|
|
24
|
+
|
|
25
|
+
/** The tools depend only on this transport, not on an HTTP client or host. */
|
|
26
|
+
interface ToolClient {
|
|
27
|
+
get<T>(path: string, query?: Record<string, string>): Promise<T>;
|
|
28
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
29
|
+
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
30
|
+
delete<T>(path: string): Promise<T>;
|
|
31
|
+
}
|
|
32
|
+
type ClientResolver = (serverName?: string) => ToolClient;
|
|
33
|
+
declare function registerTools(api: {
|
|
34
|
+
registerTool(tool: ToolDefinition, options?: ToolOptions): void;
|
|
35
|
+
}, getClient: ClientResolver): void;
|
|
36
|
+
declare const READ_ONLY: Set<string>;
|
|
37
|
+
|
|
38
|
+
export { type ClientResolver, READ_ONLY, type ToolClient, type ToolDefinition, type ToolOptions, type ToolResult, isErrorResult, registerTools };
|