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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # unraidclaw
2
2
 
3
- > OpenClaw plugin to manage your Unraid server through AI agents — Docker, VMs, array, shares, system, notifications, and more, with permission control.
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
  [![npm](https://img.shields.io/npm/v/unraidclaw)](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** — 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).
12
- 2. **An UnraidClaw API key** — generate one on the **Settings → UnraidClaw** page in the Unraid WebGUI.
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
- npm pack unraidclaw && openclaw plugins install unraidclaw-*.tgz && rm unraidclaw-*.tgz
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
- rm -rf ~/.openclaw/extensions/unraidclaw && npm pack unraidclaw && openclaw plugins install unraidclaw-*.tgz && rm unraidclaw-*.tgz
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://192.168.1.100:9876", "apiKey": "...", "tlsSkipVerify": true, "default": true },
61
- { "name": "work", "serverUrl": "https://10.0.0.50:9876", "apiKey": "..." }
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 default server is used when it's omitted.
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` when using UnraidClaw's auto-generated self-signed certificate.
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** — OpenClaw expands `${VAR}` references at config-load time:
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`)** — 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:
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, just ask your agent:
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
- Every tool is gated by a 30-key `resource:action` permission matrix configured from the Unraid WebGUI, so you control exactly what agents can do.
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
- return { content: [{ type: "text", text: `Error: ${message}` }] };
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: "number", description: "Number of lines from the end (default: 100)" },
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
- registerHealthTools(api, getClient);
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 };