unraidclaw 0.1.12 → 0.1.14

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
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/unraidclaw)](https://www.npmjs.com/package/unraidclaw)
6
6
 
7
- This is the [OpenClaw](https://github.com/openclaw/openclaw) plugin for **[UnraidClaw](https://github.com/emaspa/unraidclaw)**. It exposes **44 tools** to any AI agent running on OpenClaw, letting it monitor and manage your Unraid server. The plugin talks to the UnraidClaw gateway (a permission-enforcing REST API) running on your Unraid box.
7
+ This is the [OpenClaw](https://github.com/openclaw/openclaw) plugin for **[UnraidClaw](https://github.com/emaspa/unraidclaw)**. It exposes **55 tools** to any AI agent running on OpenClaw, letting it monitor and manage your Unraid server. The plugin talks to the UnraidClaw gateway (a permission-enforcing REST API) running on your Unraid box.
8
8
 
9
9
  ## Prerequisites
10
10
 
@@ -71,6 +71,24 @@ With multi-server config, every tool accepts an optional `server` parameter (e.g
71
71
 
72
72
  Set `tlsSkipVerify: true` when using UnraidClaw's auto-generated self-signed certificate.
73
73
 
74
+ ### Keeping the API key out of the config file
75
+
76
+ You don't have to hard-code the key in `openclaw.json`. Two options:
77
+
78
+ **Environment variable** — OpenClaw expands `${VAR}` references at config-load time:
79
+
80
+ ```json
81
+ "apiKey": "${UNRAID_API_KEY}"
82
+ ```
83
+
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:
85
+
86
+ ```json
87
+ "apiKey": { "source": "file", "provider": "default", "id": "/unraidclaw_key" }
88
+ ```
89
+
90
+ `source` is one of `file`, `env`, or `exec`; `provider` names a provider from your `secrets.providers` config; `id` is the lookup key. Both forms also work per-server on `servers[].apiKey`. (Requires unraidclaw 0.1.12+.)
91
+
74
92
  ## Usage
75
93
 
76
94
  Once installed and configured, just ask your agent:
@@ -80,17 +98,23 @@ Once installed and configured, just ask your agent:
80
98
  - "What's the array status?"
81
99
  - "Show me disk temperatures"
82
100
  - "Create a new nginx container with port 8080"
101
+ - "Find me a Community Applications backup tool"
102
+ - "Install Jellyfin from Community Applications, media on /mnt/user/media"
103
+ - "Update the jellyfin container to the latest image"
104
+ - "Which of my Unraid plugins have updates?"
83
105
  - "Check parity status"
84
106
  - "Reboot the server"
85
107
 
86
108
  ## Tools
87
109
 
88
- 44 tools across 11 categories:
110
+ 55 tools across 13 categories:
89
111
 
90
112
  | Category | Tools |
91
113
  |----------|-------|
92
114
  | Health | `unraid_health_check` |
93
115
  | Docker | `unraid_docker_list`, `unraid_docker_inspect`, `unraid_docker_logs`, `unraid_docker_create`, `unraid_docker_start`, `unraid_docker_stop`, `unraid_docker_restart`, `unraid_docker_pause`, `unraid_docker_unpause`, `unraid_docker_remove` |
116
+ | Community Apps | `unraid_ca_search`, `unraid_ca_app`, `unraid_ca_install`, `unraid_ca_update`, `unraid_ca_remove` |
117
+ | Plugins | `unraid_plugins_list`, `unraid_plugin_info`, `unraid_plugin_install`, `unraid_plugin_check_updates`, `unraid_plugin_update`, `unraid_plugin_remove` |
94
118
  | VMs | `unraid_vm_list`, `unraid_vm_inspect`, `unraid_vm_start`, `unraid_vm_stop`, `unraid_vm_pause`, `unraid_vm_resume`, `unraid_vm_force_stop`, `unraid_vm_reboot` |
95
119
  | Array | `unraid_array_status`, `unraid_array_start`, `unraid_array_stop`, `unraid_parity_status`, `unraid_parity_start`, `unraid_parity_pause`, `unraid_parity_resume`, `unraid_parity_cancel` |
96
120
  | Disks | `unraid_disk_list`, `unraid_disk_details` |
@@ -101,7 +125,11 @@ Once installed and configured, just ask your agent:
101
125
  | Users | `unraid_user_me` |
102
126
  | Logs | `unraid_syslog` |
103
127
 
104
- Every tool is gated by a 22-key `resource:action` permission matrix configured from the Unraid WebGUI, so you control exactly what agents can do.
128
+ `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
+
130
+ 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
+
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.
105
133
 
106
134
  ## Links
107
135
 
package/dist/index.js CHANGED
@@ -113,6 +113,20 @@ function errorResult(err) {
113
113
  const message = err instanceof Error ? err.message : String(err);
114
114
  return { content: [{ type: "text", text: `Error: ${message}` }] };
115
115
  }
116
+ function checkParams(params, allowed) {
117
+ for (const key of Object.keys(params)) {
118
+ if (allowed.includes(key)) continue;
119
+ const near = allowed.find((a) => a.toLowerCase() === key.toLowerCase());
120
+ throw new Error(
121
+ near ? `Unknown parameter "${key}". Did you mean "${near}"? Nothing was sent to the server.` : `Unknown parameter "${key}". Allowed parameters are: ${allowed.join(", ")}. Nothing was sent to the server.`
122
+ );
123
+ }
124
+ if (params.dryRun !== void 0 && typeof params.dryRun !== "boolean") {
125
+ throw new Error(
126
+ `"dryRun" must be true or false, not ${JSON.stringify(params.dryRun)}. Nothing was sent to the server.`
127
+ );
128
+ }
129
+ }
116
130
 
117
131
  // src/tools/health.ts
118
132
  function registerHealthTools(api, getClient) {
@@ -289,6 +303,359 @@ function registerDockerTools(api, getClient) {
289
303
  });
290
304
  }
291
305
 
306
+ // src/tools/ca.ts
307
+ function registerCaTools(api, getClient) {
308
+ api.registerTool({
309
+ name: "unraid_ca_search",
310
+ description: "Search the Unraid Community Applications catalog by name, description, Docker image or maintainer. Returns matching apps with their icon, categories and whether UnraidClaw can install them. Use this to find an app before calling unraid_ca_app or unraid_ca_install.",
311
+ parameters: {
312
+ type: "object",
313
+ properties: {
314
+ q: { type: "string", description: "Search text, e.g. 'plex' or 'backup tool'. All words must match." },
315
+ limit: { type: "number", description: "Maximum results to return (1-100, default: 25)" },
316
+ includeDeprecated: { type: "boolean", description: "Include templates the maintainer deprecated (default: false)" },
317
+ includePlugins: { type: "boolean", description: "Include Unraid plugin (.plg) entries, which are not containers (default: false)" },
318
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
319
+ },
320
+ required: ["q"]
321
+ },
322
+ execute: async (_id, params) => {
323
+ try {
324
+ const query = { q: String(params.q ?? "") };
325
+ if (params.limit) query.limit = String(params.limit);
326
+ if (params.includeDeprecated) query.includeDeprecated = "true";
327
+ if (params.includePlugins) query.includePlugins = "true";
328
+ return textResult(await getClient(params.server).get("/api/ca/search", query));
329
+ } catch (err) {
330
+ return errorResult(err);
331
+ }
332
+ }
333
+ });
334
+ api.registerTool({
335
+ name: "unraid_ca_app",
336
+ description: "Get the full Community Applications template for one app: its Docker image, icon, WebUI, network mode, and every configurable port, volume and environment variable with its default. Also reports which required fields have no default and any reason the app cannot be installed. Several apps share a name; pass repo to disambiguate when the call reports an ambiguity.",
337
+ parameters: {
338
+ type: "object",
339
+ properties: {
340
+ name: { type: "string", description: "App name exactly as it appears in the catalog, e.g. 'Jellyfin'" },
341
+ repo: { type: "string", description: `Owning repository, e.g. "linuxserver's Repository". Required when several templates share the name.` },
342
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
343
+ },
344
+ required: ["name"]
345
+ },
346
+ execute: async (_id, params) => {
347
+ try {
348
+ const query = {};
349
+ if (params.repo) query.repo = String(params.repo);
350
+ return textResult(
351
+ await getClient(params.server).get(
352
+ `/api/ca/app/${encodeURIComponent(String(params.name))}`,
353
+ query
354
+ )
355
+ );
356
+ } catch (err) {
357
+ return errorResult(err);
358
+ }
359
+ }
360
+ });
361
+ api.registerTool({
362
+ name: "unraid_ca_install",
363
+ description: "Install a Community Applications app on the Unraid server using its template defaults. Writes an Unraid docker-manager template and has Unraid create and start the container, so it appears on the Docker tab like any other app. Required fields with no default must be supplied in overrides; call unraid_ca_app first to see them. Pass dryRun=true to get the resolved template and a preview of the docker command without changing anything. Refuses apps whose templates need unsupported or unsafe options rather than installing something different from the template.",
364
+ parameters: {
365
+ type: "object",
366
+ properties: {
367
+ name: { type: "string", description: "App name exactly as it appears in the catalog, e.g. 'Jellyfin'" },
368
+ repo: { type: "string", description: "Owning repository. Required when several templates share the name." },
369
+ containerName: {
370
+ type: "string",
371
+ description: "Name for the new container. Defaults to the app name. Letters, digits, dot, dash and underscore only."
372
+ },
373
+ overrides: {
374
+ type: "object",
375
+ additionalProperties: { type: "string" },
376
+ description: "Values for template fields, keyed by the field's name or its container-side target, e.g. {'/data/tvshows': '/mnt/user/media/tv', 'PUID': '99'}. An unknown key is an error."
377
+ },
378
+ dryRun: {
379
+ type: "boolean",
380
+ description: "Resolve and validate everything and return the plan without installing (default: false)"
381
+ },
382
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
383
+ },
384
+ required: ["name"],
385
+ additionalProperties: false
386
+ },
387
+ execute: async (_id, params) => {
388
+ try {
389
+ checkParams(params, ["name", "repo", "containerName", "overrides", "dryRun", "server"]);
390
+ const body = {};
391
+ if (params.repo) body.repo = params.repo;
392
+ if (params.containerName) body.name = params.containerName;
393
+ if (params.overrides) body.overrides = params.overrides;
394
+ if (params.dryRun !== void 0) body.dryRun = params.dryRun;
395
+ return textResult(
396
+ await getClient(params.server).post(
397
+ `/api/ca/app/${encodeURIComponent(String(params.name))}/install`,
398
+ body
399
+ )
400
+ );
401
+ } catch (err) {
402
+ return errorResult(err);
403
+ }
404
+ }
405
+ }, { optional: true });
406
+ api.registerTool({
407
+ name: "unraid_ca_update",
408
+ description: "Update an app that is already installed on the Unraid server: pulls the newest image for the tag it runs and recreates the container from the template saved on the server, keeping its ports, paths, variables and its running or stopped state. The name is the INSTALLED CONTAINER NAME as shown on the Docker tab, not the app's name in the Community Applications catalog; call unraid_docker_list to find it and never guess it from a catalog name. Nothing is refreshed from the catalog, so values the user changed are kept. A failed pull leaves the running app untouched, the previous image is never deleted, and no appdata is removed. Pass dryRun=true to see the resolved configuration and the exact docker command without changing anything.",
409
+ parameters: {
410
+ type: "object",
411
+ properties: {
412
+ name: { type: "string", description: "Installed container name, e.g. 'jellyfin'. Not the catalog app name." },
413
+ dryRun: {
414
+ type: "boolean",
415
+ description: "Check everything and report what would happen without pulling or recreating anything (default: false)"
416
+ },
417
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
418
+ },
419
+ required: ["name"],
420
+ additionalProperties: false
421
+ },
422
+ execute: async (_id, params) => {
423
+ try {
424
+ checkParams(params, ["name", "dryRun", "server"]);
425
+ const body = {};
426
+ if (params.dryRun !== void 0) body.dryRun = params.dryRun;
427
+ return textResult(
428
+ await getClient(params.server).post(
429
+ `/api/ca/app/${encodeURIComponent(String(params.name))}/update`,
430
+ body
431
+ )
432
+ );
433
+ } catch (err) {
434
+ return errorResult(err);
435
+ }
436
+ }
437
+ }, { optional: true });
438
+ api.registerTool({
439
+ name: "unraid_ca_remove",
440
+ description: "Remove an installed app's Docker container from the Unraid server. The name is the INSTALLED CONTAINER NAME as shown on the Docker tab, not the app's name in the Community Applications catalog; call unraid_docker_list to find it and never guess it from a catalog name. Only the container is removed: its appdata, its Docker volumes, its image and the saved template all stay, so the app can be recreated with the same configuration. Pass dryRun=true to see exactly what would be removed and what would be kept.",
441
+ parameters: {
442
+ type: "object",
443
+ properties: {
444
+ name: { type: "string", description: "Installed container name, e.g. 'jellyfin'. Not the catalog app name." },
445
+ dryRun: {
446
+ type: "boolean",
447
+ description: "Report what would be removed and what would be kept, without removing anything (default: false)"
448
+ },
449
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
450
+ },
451
+ required: ["name"],
452
+ additionalProperties: false
453
+ },
454
+ execute: async (_id, params) => {
455
+ try {
456
+ checkParams(params, ["name", "dryRun", "server"]);
457
+ const body = {};
458
+ if (params.dryRun !== void 0) body.dryRun = params.dryRun;
459
+ return textResult(
460
+ await getClient(params.server).post(
461
+ `/api/ca/app/${encodeURIComponent(String(params.name))}/remove`,
462
+ body
463
+ )
464
+ );
465
+ } catch (err) {
466
+ return errorResult(err);
467
+ }
468
+ }
469
+ }, { optional: true });
470
+ }
471
+
472
+ // src/tools/plugins.ts
473
+ function registerPluginTools(api, getClient) {
474
+ api.registerTool({
475
+ name: "unraid_plugins_list",
476
+ description: "List the Unraid plugins (.plg) installed on the server, with author, version, update URL and whether an update is already staged. Unraid plugins are not Docker containers and not Community Applications: they install files and run scripts on the server itself. Plugins marked builtin belong to Unraid OS and cannot be changed through UnraidClaw.",
477
+ parameters: {
478
+ type: "object",
479
+ properties: {
480
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
481
+ }
482
+ },
483
+ execute: async (_id, params) => {
484
+ try {
485
+ return textResult(await getClient(params.server).get("/api/plugins"));
486
+ } catch (err) {
487
+ return errorResult(err);
488
+ }
489
+ }
490
+ });
491
+ api.registerTool({
492
+ name: "unraid_plugin_info",
493
+ description: "Show one installed Unraid plugin in full: its metadata, support link, Unraid version requirements, changelog text and the structure of the files it installs. Inline scripts inside the plugin are described but never returned or executed.",
494
+ parameters: {
495
+ type: "object",
496
+ properties: {
497
+ plugin: {
498
+ type: "string",
499
+ description: "Plugin file name, e.g. 'unassigned.devices.plg'. The .plg suffix is optional."
500
+ },
501
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
502
+ },
503
+ required: ["plugin"]
504
+ },
505
+ execute: async (_id, params) => {
506
+ try {
507
+ return textResult(
508
+ await getClient(params.server).get(
509
+ `/api/plugins/${encodeURIComponent(String(params.plugin))}`
510
+ )
511
+ );
512
+ } catch (err) {
513
+ return errorResult(err);
514
+ }
515
+ }
516
+ });
517
+ api.registerTool(
518
+ {
519
+ name: "unraid_plugin_install",
520
+ description: "Install an Unraid plugin from an explicit https URL to a .plg file. The plugin file is an installer that Unraid runs as root, so this executes code from whoever controls that URL: only use a URL the user gave you or one from a source they trust. The URL must be https, public, credential-free and end in .plg. Use dryRun first to see exactly what would happen. This is not the way to install Community Applications apps; those are Docker containers (unraid_ca_install).",
521
+ parameters: {
522
+ type: "object",
523
+ properties: {
524
+ url: {
525
+ type: "string",
526
+ description: "Direct https URL of the .plg file, e.g. 'https://example.com/myplugin.plg'"
527
+ },
528
+ dryRun: {
529
+ type: "boolean",
530
+ description: "Validate the URL and return the plan without downloading, writing or installing anything (default: false)"
531
+ },
532
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
533
+ },
534
+ required: ["url"],
535
+ additionalProperties: false
536
+ },
537
+ execute: async (_id, params) => {
538
+ try {
539
+ checkParams(params, ["url", "dryRun", "server"]);
540
+ const body = { url: String(params.url ?? "") };
541
+ if (params.dryRun !== void 0) body.dryRun = params.dryRun;
542
+ return textResult(
543
+ await getClient(params.server).post("/api/plugins/install", body)
544
+ );
545
+ } catch (err) {
546
+ return errorResult(err);
547
+ }
548
+ }
549
+ },
550
+ { optional: true }
551
+ );
552
+ api.registerTool(
553
+ {
554
+ name: "unraid_plugin_check_updates",
555
+ description: "Check whether a newer version of an installed Unraid plugin has been published, and stage it for installation. This is not a read-only lookup: it downloads the plugin file from the plugin's own update URL and writes it to the plugin manager's staging directory, where this tool's update call and the Unraid web UI will then offer to install it. Nothing is installed or executed by the check. Run this before unraid_plugin_update.",
556
+ parameters: {
557
+ type: "object",
558
+ properties: {
559
+ plugin: { type: "string", description: "Plugin file name, e.g. 'unassigned.devices.plg'" },
560
+ dryRun: {
561
+ type: "boolean",
562
+ description: "Return the plan without downloading or staging anything (default: false)"
563
+ },
564
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
565
+ },
566
+ required: ["plugin"],
567
+ additionalProperties: false
568
+ },
569
+ execute: async (_id, params) => {
570
+ try {
571
+ checkParams(params, ["plugin", "dryRun", "server"]);
572
+ const body = {};
573
+ if (params.dryRun !== void 0) body.dryRun = params.dryRun;
574
+ return textResult(
575
+ await getClient(params.server).post(
576
+ `/api/plugins/${encodeURIComponent(String(params.plugin))}/check`,
577
+ body
578
+ )
579
+ );
580
+ } catch (err) {
581
+ return errorResult(err);
582
+ }
583
+ }
584
+ },
585
+ { optional: true }
586
+ );
587
+ api.registerTool(
588
+ {
589
+ name: "unraid_plugin_update",
590
+ description: "Install the plugin version staged by unraid_plugin_check_updates. Unraid's plugin manager runs the new plugin's install scripts as root and there is no rollback. The result says whether the installed version actually changed; a plugin manager exit code alone is not treated as success.",
591
+ parameters: {
592
+ type: "object",
593
+ properties: {
594
+ plugin: { type: "string", description: "Plugin file name, e.g. 'unassigned.devices.plg'" },
595
+ dryRun: {
596
+ type: "boolean",
597
+ description: "Return the plan, including which version would replace which, without installing (default: false)"
598
+ },
599
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
600
+ },
601
+ required: ["plugin"],
602
+ additionalProperties: false
603
+ },
604
+ execute: async (_id, params) => {
605
+ try {
606
+ checkParams(params, ["plugin", "dryRun", "server"]);
607
+ const body = {};
608
+ if (params.dryRun !== void 0) body.dryRun = params.dryRun;
609
+ return textResult(
610
+ await getClient(params.server).post(
611
+ `/api/plugins/${encodeURIComponent(String(params.plugin))}/update`,
612
+ body
613
+ )
614
+ );
615
+ } catch (err) {
616
+ return errorResult(err);
617
+ }
618
+ }
619
+ },
620
+ { optional: true }
621
+ );
622
+ api.registerTool(
623
+ {
624
+ name: "unraid_plugin_remove",
625
+ description: "Remove an installed Unraid plugin through the plugin manager. The plugin's own removal scripts run as root and decide what they delete: some keep their configuration and data, others delete it, so removal cannot be promised to be data-preserving. Anything depending on the plugin stops working. Use dryRun first and confirm with the user before removing.",
626
+ parameters: {
627
+ type: "object",
628
+ properties: {
629
+ plugin: { type: "string", description: "Plugin file name, e.g. 'unassigned.devices.plg'" },
630
+ dryRun: {
631
+ type: "boolean",
632
+ description: "Return the plan and its warnings without removing anything (default: false)"
633
+ },
634
+ server: { type: "string", description: "Target server name (optional, uses default server)" }
635
+ },
636
+ required: ["plugin"],
637
+ additionalProperties: false
638
+ },
639
+ execute: async (_id, params) => {
640
+ try {
641
+ checkParams(params, ["plugin", "dryRun", "server"]);
642
+ const body = {};
643
+ if (params.dryRun !== void 0) body.dryRun = params.dryRun;
644
+ return textResult(
645
+ await getClient(params.server).post(
646
+ `/api/plugins/${encodeURIComponent(String(params.plugin))}/remove`,
647
+ body
648
+ )
649
+ );
650
+ } catch (err) {
651
+ return errorResult(err);
652
+ }
653
+ }
654
+ },
655
+ { optional: true }
656
+ );
657
+ }
658
+
292
659
  // src/tools/vms.ts
293
660
  function registerVMTools(api, getClient) {
294
661
  api.registerTool({
@@ -870,6 +1237,8 @@ function register(api) {
870
1237
  }
871
1238
  registerHealthTools(api, getClient);
872
1239
  registerDockerTools(api, getClient);
1240
+ registerCaTools(api, getClient);
1241
+ registerPluginTools(api, getClient);
873
1242
  registerVMTools(api, getClient);
874
1243
  registerArrayTools(api, getClient);
875
1244
  registerDiskTools(api, getClient);
@@ -6,6 +6,11 @@
6
6
  "unraid_array_start",
7
7
  "unraid_array_status",
8
8
  "unraid_array_stop",
9
+ "unraid_ca_app",
10
+ "unraid_ca_install",
11
+ "unraid_ca_remove",
12
+ "unraid_ca_search",
13
+ "unraid_ca_update",
9
14
  "unraid_disk_details",
10
15
  "unraid_disk_list",
11
16
  "unraid_docker_create",
@@ -29,6 +34,12 @@
29
34
  "unraid_parity_resume",
30
35
  "unraid_parity_start",
31
36
  "unraid_parity_status",
37
+ "unraid_plugin_check_updates",
38
+ "unraid_plugin_info",
39
+ "unraid_plugin_install",
40
+ "unraid_plugin_remove",
41
+ "unraid_plugin_update",
42
+ "unraid_plugins_list",
32
43
  "unraid_service_list",
33
44
  "unraid_share_details",
34
45
  "unraid_share_list",
@@ -52,8 +63,14 @@
52
63
  "configContracts": {
53
64
  "secretInputs": {
54
65
  "paths": [
55
- { "path": "apiKey", "expected": "string" },
56
- { "path": "servers.*.apiKey", "expected": "string" }
66
+ {
67
+ "path": "apiKey",
68
+ "expected": "string"
69
+ },
70
+ {
71
+ "path": "servers.*.apiKey",
72
+ "expected": "string"
73
+ }
57
74
  ]
58
75
  }
59
76
  },
@@ -68,14 +85,31 @@
68
85
  "apiKey": {
69
86
  "description": "API key for authenticating with UnraidClaw (single-server mode). A plaintext string, or an OpenClaw SecretRef object resolved before the plugin loads.",
70
87
  "anyOf": [
71
- { "type": "string" },
88
+ {
89
+ "type": "string"
90
+ },
72
91
  {
73
92
  "type": "object",
74
- "required": ["source", "provider", "id"],
93
+ "required": [
94
+ "source",
95
+ "provider",
96
+ "id"
97
+ ],
75
98
  "properties": {
76
- "source": { "type": "string", "enum": ["env", "file", "exec"] },
77
- "provider": { "type": "string" },
78
- "id": { "type": "string" }
99
+ "source": {
100
+ "type": "string",
101
+ "enum": [
102
+ "env",
103
+ "file",
104
+ "exec"
105
+ ]
106
+ },
107
+ "provider": {
108
+ "type": "string"
109
+ },
110
+ "id": {
111
+ "type": "string"
112
+ }
79
113
  },
80
114
  "additionalProperties": false
81
115
  }
@@ -103,14 +137,31 @@
103
137
  "apiKey": {
104
138
  "description": "API key for authenticating with this server. A plaintext string, or an OpenClaw SecretRef object resolved before the plugin loads.",
105
139
  "anyOf": [
106
- { "type": "string" },
140
+ {
141
+ "type": "string"
142
+ },
107
143
  {
108
144
  "type": "object",
109
- "required": ["source", "provider", "id"],
145
+ "required": [
146
+ "source",
147
+ "provider",
148
+ "id"
149
+ ],
110
150
  "properties": {
111
- "source": { "type": "string", "enum": ["env", "file", "exec"] },
112
- "provider": { "type": "string" },
113
- "id": { "type": "string" }
151
+ "source": {
152
+ "type": "string",
153
+ "enum": [
154
+ "env",
155
+ "file",
156
+ "exec"
157
+ ]
158
+ },
159
+ "provider": {
160
+ "type": "string"
161
+ },
162
+ "id": {
163
+ "type": "string"
164
+ }
114
165
  },
115
166
  "additionalProperties": false
116
167
  }
@@ -126,7 +177,11 @@
126
177
  "description": "Use this server when no server name is specified"
127
178
  }
128
179
  },
129
- "required": ["name", "serverUrl", "apiKey"]
180
+ "required": [
181
+ "name",
182
+ "serverUrl",
183
+ "apiKey"
184
+ ]
130
185
  }
131
186
  }
132
187
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "unraidclaw",
3
- "version": "0.1.12",
4
- "description": "OpenClaw plugin to manage your Unraid server — Docker, VMs, array, shares, system, and more.",
3
+ "version": "0.1.14",
4
+ "description": "OpenClaw plugin to manage your Unraid server: Docker, Community Applications, plugins, VMs, array, shares, system, and more.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",