unraidclaw 0.1.2 → 0.1.3

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.
Files changed (2) hide show
  1. package/dist/index.js +124 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // src/client.ts
2
+ import { request as httpsRequest, Agent as HttpsAgent } from "https";
3
+ import { request as httpRequest } from "http";
2
4
  var UnraidApiError = class extends Error {
3
5
  constructor(message, statusCode, errorCode) {
4
6
  super(message);
@@ -9,7 +11,7 @@ var UnraidApiError = class extends Error {
9
11
  };
10
12
  var UnraidClient = class {
11
13
  configResolver;
12
- tlsConfigured = false;
14
+ insecureAgent = null;
13
15
  constructor(configResolver) {
14
16
  this.configResolver = configResolver;
15
17
  }
@@ -18,13 +20,13 @@ var UnraidClient = class {
18
20
  if (!cfg.serverUrl) {
19
21
  throw new UnraidApiError("UnraidClaw serverUrl not configured", 0, "CONFIG_ERROR");
20
22
  }
21
- if (!this.tlsConfigured && cfg.tlsSkipVerify && cfg.serverUrl.startsWith("https")) {
22
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
23
- this.tlsConfigured = true;
23
+ if (cfg.tlsSkipVerify && cfg.serverUrl.startsWith("https") && !this.insecureAgent) {
24
+ this.insecureAgent = new HttpsAgent({ rejectUnauthorized: false });
24
25
  }
25
26
  return {
26
27
  baseUrl: cfg.serverUrl.replace(/\/+$/, ""),
27
- apiKey: cfg.apiKey || ""
28
+ apiKey: cfg.apiKey || "",
29
+ isHttps: cfg.serverUrl.startsWith("https")
28
30
  };
29
31
  }
30
32
  async get(path, query) {
@@ -34,49 +36,72 @@ var UnraidClient = class {
34
36
  const params = new URLSearchParams(query);
35
37
  url += `?${params.toString()}`;
36
38
  }
37
- return this.request("GET", url);
39
+ return this.doRequest("GET", url);
38
40
  }
39
41
  async post(path, body) {
40
42
  const { baseUrl } = this.getConfig();
41
- return this.request("POST", `${baseUrl}${path}`, body);
43
+ return this.doRequest("POST", `${baseUrl}${path}`, body);
42
44
  }
43
45
  async patch(path, body) {
44
46
  const { baseUrl } = this.getConfig();
45
- return this.request("PATCH", `${baseUrl}${path}`, body);
47
+ return this.doRequest("PATCH", `${baseUrl}${path}`, body);
46
48
  }
47
49
  async delete(path) {
48
50
  const { baseUrl } = this.getConfig();
49
- return this.request("DELETE", `${baseUrl}${path}`);
51
+ return this.doRequest("DELETE", `${baseUrl}${path}`);
50
52
  }
51
- async request(method, url, body) {
52
- const { apiKey } = this.getConfig();
53
+ doRequest(method, url, body) {
54
+ const { apiKey, isHttps } = this.getConfig();
55
+ const parsed = new URL(url);
56
+ const payload = body !== void 0 ? JSON.stringify(body) : void 0;
53
57
  const headers = {
54
58
  "x-api-key": apiKey
55
59
  };
56
- const init = { method, headers };
57
- if (body !== void 0) {
60
+ if (payload) {
58
61
  headers["Content-Type"] = "application/json";
59
- init.body = JSON.stringify(body);
60
- }
61
- let response;
62
- try {
63
- response = await fetch(url, init);
64
- } catch (err) {
65
- throw new UnraidApiError(
66
- `Connection failed: ${err instanceof Error ? err.message : "Unknown error"}`,
67
- 0,
68
- "CONNECTION_ERROR"
69
- );
70
- }
71
- const json = await response.json();
72
- if (!json.ok) {
73
- throw new UnraidApiError(
74
- json.error.message,
75
- response.status,
76
- json.error.code
62
+ headers["Content-Length"] = Buffer.byteLength(payload).toString();
63
+ }
64
+ const requestFn = isHttps ? httpsRequest : httpRequest;
65
+ return new Promise((resolve, reject) => {
66
+ const req = requestFn(
67
+ {
68
+ hostname: parsed.hostname,
69
+ port: parsed.port || (isHttps ? 443 : 80),
70
+ path: parsed.pathname + parsed.search,
71
+ method,
72
+ headers,
73
+ ...this.insecureAgent ? { agent: this.insecureAgent } : {}
74
+ },
75
+ (res) => {
76
+ const chunks = [];
77
+ res.on("data", (chunk) => chunks.push(chunk));
78
+ res.on("end", () => {
79
+ const text = Buffer.concat(chunks).toString();
80
+ let json;
81
+ try {
82
+ json = JSON.parse(text);
83
+ } catch {
84
+ reject(new UnraidApiError(`Invalid JSON response: ${text.slice(0, 200)}`, res.statusCode ?? 0, "PARSE_ERROR"));
85
+ return;
86
+ }
87
+ if (!json.ok) {
88
+ reject(new UnraidApiError(json.error.message, res.statusCode ?? 0, json.error.code));
89
+ return;
90
+ }
91
+ resolve(json.data);
92
+ });
93
+ }
77
94
  );
78
- }
79
- return json.data;
95
+ req.on("error", (err) => {
96
+ reject(new UnraidApiError(
97
+ `Connection failed: ${err.message}`,
98
+ 0,
99
+ "CONNECTION_ERROR"
100
+ ));
101
+ });
102
+ if (payload) req.write(payload);
103
+ req.end();
104
+ });
80
105
  }
81
106
  };
82
107
 
@@ -94,7 +119,7 @@ function registerHealthTools(api, client) {
94
119
  api.registerTool({
95
120
  name: "unraid_health_check",
96
121
  description: "Check the health status of the Unraid server connection, including API and GraphQL reachability.",
97
- parameters: { type: "object" },
122
+ parameters: { type: "object", properties: {} },
98
123
  execute: async () => {
99
124
  try {
100
125
  return textResult(await client.get("/api/health"));
@@ -110,7 +135,7 @@ function registerDockerTools(api, client) {
110
135
  api.registerTool({
111
136
  name: "unraid_docker_list",
112
137
  description: "List all Docker containers on the Unraid server with their current state, image, and status.",
113
- parameters: { type: "object" },
138
+ parameters: { type: "object", properties: {} },
114
139
  execute: async () => {
115
140
  try {
116
141
  return textResult(await client.get("/api/docker/containers"));
@@ -252,17 +277,59 @@ function registerDockerTools(api, client) {
252
277
  });
253
278
  api.registerTool({
254
279
  name: "unraid_docker_remove",
255
- description: "Remove a Docker container. This is a destructive operation that cannot be undone.",
280
+ description: "Remove a Docker container. Pass force=true to stop and remove in one step. This is a destructive operation that cannot be undone.",
256
281
  parameters: {
257
282
  type: "object",
258
283
  properties: {
259
- id: { type: "string", description: "Container ID or name" }
284
+ id: { type: "string", description: "Container ID or name" },
285
+ force: { type: "boolean", description: "Stop the container before removing (default: false)" }
260
286
  },
261
287
  required: ["id"]
262
288
  },
263
289
  execute: async (_id, params) => {
264
290
  try {
265
- return textResult(await client.delete(`/api/docker/containers/${params.id}`));
291
+ const query = params.force ? "?force=true" : "";
292
+ return textResult(await client.delete(`/api/docker/containers/${params.id}${query}`));
293
+ } catch (err) {
294
+ return errorResult(err);
295
+ }
296
+ }
297
+ });
298
+ api.registerTool({
299
+ name: "unraid_docker_create",
300
+ 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.",
301
+ parameters: {
302
+ type: "object",
303
+ properties: {
304
+ image: { type: "string", description: "Docker image to use (e.g. vikunja/vikunja:latest)" },
305
+ name: { type: "string", description: "Optional container name" },
306
+ ports: {
307
+ type: "array",
308
+ items: { type: "string" },
309
+ description: "Port mappings in host:container format (e.g. ['3456:3456'])"
310
+ },
311
+ volumes: {
312
+ type: "array",
313
+ items: { type: "string" },
314
+ description: "Volume mounts in host:container format (e.g. ['/mnt/cache/appdata/vikunja:/app/vikunja'])"
315
+ },
316
+ env: {
317
+ type: "array",
318
+ items: { type: "string" },
319
+ description: "Environment variables in KEY=VALUE format"
320
+ },
321
+ restart: {
322
+ type: "string",
323
+ enum: ["no", "always", "unless-stopped", "on-failure"],
324
+ description: "Restart policy (default: unless-stopped)"
325
+ },
326
+ network: { type: "string", description: "Network to attach the container to" }
327
+ },
328
+ required: ["image"]
329
+ },
330
+ execute: async (_id, params) => {
331
+ try {
332
+ return textResult(await client.post("/api/docker/containers", params));
266
333
  } catch (err) {
267
334
  return errorResult(err);
268
335
  }
@@ -275,7 +342,7 @@ function registerVMTools(api, client) {
275
342
  api.registerTool({
276
343
  name: "unraid_vm_list",
277
344
  description: "List all virtual machines on the Unraid server with their current state.",
278
- parameters: { type: "object" },
345
+ parameters: { type: "object", properties: {} },
279
346
  execute: async () => {
280
347
  try {
281
348
  return textResult(await client.get("/api/vms"));
@@ -419,7 +486,7 @@ function registerArrayTools(api, client) {
419
486
  api.registerTool({
420
487
  name: "unraid_array_status",
421
488
  description: "Get the current status of the Unraid array including state, capacity, disks, and parities. Capacity is in kilobytes (KiB). Disk 'size' fields are in kilobytes (KiB).",
422
- parameters: { type: "object" },
489
+ parameters: { type: "object", properties: {} },
423
490
  execute: async () => {
424
491
  try {
425
492
  return textResult(await client.get("/api/array/status"));
@@ -431,7 +498,7 @@ function registerArrayTools(api, client) {
431
498
  api.registerTool({
432
499
  name: "unraid_array_start",
433
500
  description: "Start the Unraid array. This will mount all disks and start Docker/VMs if configured.",
434
- parameters: { type: "object" },
501
+ parameters: { type: "object", properties: {} },
435
502
  execute: async () => {
436
503
  try {
437
504
  return textResult(await client.post("/api/array/start"));
@@ -443,7 +510,7 @@ function registerArrayTools(api, client) {
443
510
  api.registerTool({
444
511
  name: "unraid_array_stop",
445
512
  description: "Stop the Unraid array. This will stop all Docker containers and VMs, then unmount all disks.",
446
- parameters: { type: "object" },
513
+ parameters: { type: "object", properties: {} },
447
514
  execute: async () => {
448
515
  try {
449
516
  return textResult(await client.post("/api/array/stop"));
@@ -455,7 +522,7 @@ function registerArrayTools(api, client) {
455
522
  api.registerTool({
456
523
  name: "unraid_parity_status",
457
524
  description: "Get the current parity check status (running, progress, speed, errors).",
458
- parameters: { type: "object" },
525
+ parameters: { type: "object", properties: {} },
459
526
  execute: async () => {
460
527
  try {
461
528
  return textResult(await client.get("/api/array/parity/status"));
@@ -487,7 +554,7 @@ function registerArrayTools(api, client) {
487
554
  api.registerTool({
488
555
  name: "unraid_parity_pause",
489
556
  description: "Pause a running parity check.",
490
- parameters: { type: "object" },
557
+ parameters: { type: "object", properties: {} },
491
558
  execute: async () => {
492
559
  try {
493
560
  return textResult(await client.post("/api/array/parity/pause"));
@@ -499,7 +566,7 @@ function registerArrayTools(api, client) {
499
566
  api.registerTool({
500
567
  name: "unraid_parity_resume",
501
568
  description: "Resume a paused parity check.",
502
- parameters: { type: "object" },
569
+ parameters: { type: "object", properties: {} },
503
570
  execute: async () => {
504
571
  try {
505
572
  return textResult(await client.post("/api/array/parity/resume"));
@@ -511,7 +578,7 @@ function registerArrayTools(api, client) {
511
578
  api.registerTool({
512
579
  name: "unraid_parity_cancel",
513
580
  description: "Cancel a running or paused parity check.",
514
- parameters: { type: "object" },
581
+ parameters: { type: "object", properties: {} },
515
582
  execute: async () => {
516
583
  try {
517
584
  return textResult(await client.post("/api/array/parity/cancel"));
@@ -527,7 +594,7 @@ function registerDiskTools(api, client) {
527
594
  api.registerTool({
528
595
  name: "unraid_disk_list",
529
596
  description: "List all disks in the Unraid server with basic info (name, size, temp, status). The 'size' field is in kilobytes (KiB).",
530
- parameters: { type: "object" },
597
+ parameters: { type: "object", properties: {} },
531
598
  execute: async () => {
532
599
  try {
533
600
  return textResult(await client.get("/api/disks"));
@@ -561,7 +628,7 @@ function registerShareTools(api, client) {
561
628
  api.registerTool({
562
629
  name: "unraid_share_list",
563
630
  description: "List all user shares on the Unraid server with their settings and usage. The 'free' and 'size' fields are in kilobytes (KiB).",
564
- parameters: { type: "object" },
631
+ parameters: { type: "object", properties: {} },
565
632
  execute: async () => {
566
633
  try {
567
634
  return textResult(await client.get("/api/shares"));
@@ -618,7 +685,7 @@ function registerSystemTools(api, client) {
618
685
  api.registerTool({
619
686
  name: "unraid_system_info",
620
687
  description: "Get system information including OS, CPU, memory, and Unraid/kernel versions.",
621
- parameters: { type: "object" },
688
+ parameters: { type: "object", properties: {} },
622
689
  execute: async () => {
623
690
  try {
624
691
  return textResult(await client.get("/api/system/info"));
@@ -630,7 +697,7 @@ function registerSystemTools(api, client) {
630
697
  api.registerTool({
631
698
  name: "unraid_system_metrics",
632
699
  description: "Get live system metrics: CPU usage, memory usage, load average, and uptime.",
633
- parameters: { type: "object" },
700
+ parameters: { type: "object", properties: {} },
634
701
  execute: async () => {
635
702
  try {
636
703
  return textResult(await client.get("/api/system/metrics"));
@@ -642,7 +709,7 @@ function registerSystemTools(api, client) {
642
709
  api.registerTool({
643
710
  name: "unraid_service_list",
644
711
  description: "List system services and their current state.",
645
- parameters: { type: "object" },
712
+ parameters: { type: "object", properties: {} },
646
713
  execute: async () => {
647
714
  try {
648
715
  return textResult(await client.get("/api/system/services"));
@@ -654,7 +721,7 @@ function registerSystemTools(api, client) {
654
721
  api.registerTool({
655
722
  name: "unraid_system_reboot",
656
723
  description: "Reboot the Unraid server. This is a destructive operation that will interrupt all running services, VMs, and containers.",
657
- parameters: { type: "object" },
724
+ parameters: { type: "object", properties: {} },
658
725
  execute: async () => {
659
726
  try {
660
727
  return textResult(await client.post("/api/system/reboot"));
@@ -666,7 +733,7 @@ function registerSystemTools(api, client) {
666
733
  api.registerTool({
667
734
  name: "unraid_system_shutdown",
668
735
  description: "Shut down the Unraid server. This is a destructive operation that will power off the server.",
669
- parameters: { type: "object" },
736
+ parameters: { type: "object", properties: {} },
670
737
  execute: async () => {
671
738
  try {
672
739
  return textResult(await client.post("/api/system/shutdown"));
@@ -682,7 +749,7 @@ function registerNotificationTools(api, client) {
682
749
  api.registerTool({
683
750
  name: "unraid_notification_list",
684
751
  description: "List all system notifications with their importance level and archive status.",
685
- parameters: { type: "object" },
752
+ parameters: { type: "object", properties: {} },
686
753
  execute: async () => {
687
754
  try {
688
755
  return textResult(await client.get("/api/notifications"));
@@ -761,7 +828,7 @@ function registerNetworkTools(api, client) {
761
828
  api.registerTool({
762
829
  name: "unraid_network_info",
763
830
  description: "Get network information including hostname, gateway, DNS servers, and all network interfaces.",
764
- parameters: { type: "object" },
831
+ parameters: { type: "object", properties: {} },
765
832
  execute: async () => {
766
833
  try {
767
834
  return textResult(await client.get("/api/network"));
@@ -777,7 +844,7 @@ function registerUserTools(api, client) {
777
844
  api.registerTool({
778
845
  name: "unraid_user_me",
779
846
  description: "Get information about the current authenticated user.",
780
- parameters: { type: "object" },
847
+ parameters: { type: "object", properties: {} },
781
848
  execute: async () => {
782
849
  try {
783
850
  return textResult(await client.get("/api/users/me"));
@@ -837,7 +904,7 @@ function register(api) {
837
904
  registerUserTools(api, client);
838
905
  registerLogTools(api, client);
839
906
  const cfg = resolveConfig(api);
840
- log.info(`UnraidClaw: registered 39 tools${cfg.serverUrl ? ", server: " + cfg.serverUrl : " (config will resolve at runtime)"}`);
907
+ log.info(`UnraidClaw: registered 40 tools${cfg.serverUrl ? ", server: " + cfg.serverUrl : " (config will resolve at runtime)"}`);
841
908
  }
842
909
  export {
843
910
  register as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unraidclaw",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "OpenClaw plugin to manage your Unraid server — Docker, VMs, array, shares, system, and more.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",