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/dist/registry.js
ADDED
|
@@ -0,0 +1,1172 @@
|
|
|
1
|
+
// src/tools/util.ts
|
|
2
|
+
function textResult(data) {
|
|
3
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
4
|
+
}
|
|
5
|
+
var failures = /* @__PURE__ */ new WeakSet();
|
|
6
|
+
function isErrorResult(result) {
|
|
7
|
+
return failures.has(result);
|
|
8
|
+
}
|
|
9
|
+
function errorResult(err) {
|
|
10
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11
|
+
const result = { content: [{ type: "text", text: `Error: ${message}` }] };
|
|
12
|
+
failures.add(result);
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
function checkParams(params, allowed) {
|
|
16
|
+
for (const key of Object.keys(params)) {
|
|
17
|
+
if (allowed.includes(key)) continue;
|
|
18
|
+
const near = allowed.find((a) => a.toLowerCase() === key.toLowerCase());
|
|
19
|
+
throw new Error(
|
|
20
|
+
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.`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
if (params.dryRun !== void 0 && typeof params.dryRun !== "boolean") {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`"dryRun" must be true or false, not ${JSON.stringify(params.dryRun)}. Nothing was sent to the server.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/tools/health.ts
|
|
31
|
+
function registerHealthTools(api, getClient) {
|
|
32
|
+
api.registerTool({
|
|
33
|
+
name: "unraid_health_check",
|
|
34
|
+
description: "Check the health status of the Unraid server connection, including API and GraphQL reachability.",
|
|
35
|
+
parameters: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
execute: async (_id, params) => {
|
|
42
|
+
try {
|
|
43
|
+
return textResult(await getClient(params.server).get("/api/health"));
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return errorResult(err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/tools/docker.ts
|
|
52
|
+
function registerDockerTools(api, getClient) {
|
|
53
|
+
api.registerTool({
|
|
54
|
+
name: "unraid_docker_list",
|
|
55
|
+
description: "List all Docker containers on the Unraid server with their current state, image, and status.",
|
|
56
|
+
parameters: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
execute: async (_id, params) => {
|
|
63
|
+
try {
|
|
64
|
+
return textResult(await getClient(params.server).get("/api/docker/containers"));
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return errorResult(err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
api.registerTool({
|
|
71
|
+
name: "unraid_docker_inspect",
|
|
72
|
+
description: "Get detailed information about a specific Docker container including ports, mounts, and network mode.",
|
|
73
|
+
parameters: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
id: { type: "string", description: "Container ID or name" },
|
|
77
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
78
|
+
},
|
|
79
|
+
required: ["id"]
|
|
80
|
+
},
|
|
81
|
+
execute: async (_id, params) => {
|
|
82
|
+
try {
|
|
83
|
+
return textResult(await getClient(params.server).get(`/api/docker/containers/${params.id}`));
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return errorResult(err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
api.registerTool({
|
|
90
|
+
name: "unraid_docker_logs",
|
|
91
|
+
description: "Get logs from a specific Docker container.",
|
|
92
|
+
parameters: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
id: { type: "string", description: "Container ID or name" },
|
|
96
|
+
tail: { type: "integer", minimum: 1, maximum: 1e4, description: "Number of lines from the end (default: 100)" },
|
|
97
|
+
since: { type: "string", description: "Show logs since timestamp (e.g., 2024-01-01T00:00:00Z)" },
|
|
98
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
99
|
+
},
|
|
100
|
+
required: ["id"]
|
|
101
|
+
},
|
|
102
|
+
execute: async (_id, params) => {
|
|
103
|
+
try {
|
|
104
|
+
const query = {};
|
|
105
|
+
if (params.tail) query.tail = String(params.tail);
|
|
106
|
+
if (params.since) query.since = String(params.since);
|
|
107
|
+
return textResult(await getClient(params.server).get(`/api/docker/containers/${params.id}/logs`, query));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return errorResult(err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
for (const [toolName, desc, action] of [
|
|
114
|
+
["unraid_docker_start", "Start a stopped Docker container.", "start"],
|
|
115
|
+
["unraid_docker_stop", "Stop a running Docker container.", "stop"],
|
|
116
|
+
["unraid_docker_restart", "Restart a Docker container.", "restart"],
|
|
117
|
+
["unraid_docker_pause", "Pause a running Docker container (freeze all processes).", "pause"],
|
|
118
|
+
["unraid_docker_unpause", "Unpause a paused Docker container.", "unpause"]
|
|
119
|
+
]) {
|
|
120
|
+
api.registerTool({
|
|
121
|
+
name: toolName,
|
|
122
|
+
description: desc,
|
|
123
|
+
parameters: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
id: { type: "string", description: "Container ID or name" },
|
|
127
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
128
|
+
},
|
|
129
|
+
required: ["id"]
|
|
130
|
+
},
|
|
131
|
+
execute: async (_id, params) => {
|
|
132
|
+
try {
|
|
133
|
+
return textResult(await getClient(params.server).post(`/api/docker/containers/${params.id}/${action}`));
|
|
134
|
+
} catch (err) {
|
|
135
|
+
return errorResult(err);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
api.registerTool({
|
|
141
|
+
name: "unraid_docker_remove",
|
|
142
|
+
description: "Remove a Docker container. Pass force=true to stop and remove in one step. This is a destructive operation that cannot be undone.",
|
|
143
|
+
parameters: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
id: { type: "string", description: "Container ID or name" },
|
|
147
|
+
force: { type: "boolean", description: "Stop the container before removing (default: false)" },
|
|
148
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
149
|
+
},
|
|
150
|
+
required: ["id"]
|
|
151
|
+
},
|
|
152
|
+
execute: async (_id, params) => {
|
|
153
|
+
try {
|
|
154
|
+
const query = params.force ? "?force=true" : "";
|
|
155
|
+
return textResult(await getClient(params.server).delete(`/api/docker/containers/${params.id}${query}`));
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return errorResult(err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
api.registerTool({
|
|
162
|
+
name: "unraid_docker_create",
|
|
163
|
+
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.",
|
|
164
|
+
parameters: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {
|
|
167
|
+
image: { type: "string", description: "Docker image to use (e.g. vikunja/vikunja:latest)" },
|
|
168
|
+
name: { type: "string", description: "Optional container name" },
|
|
169
|
+
ports: {
|
|
170
|
+
type: "array",
|
|
171
|
+
items: { type: "string" },
|
|
172
|
+
description: "Port mappings in host:container format (e.g. ['3456:3456'])"
|
|
173
|
+
},
|
|
174
|
+
volumes: {
|
|
175
|
+
type: "array",
|
|
176
|
+
items: { type: "string" },
|
|
177
|
+
description: "Volume mounts in host:container format (e.g. ['/mnt/cache/appdata/vikunja:/app/vikunja'])"
|
|
178
|
+
},
|
|
179
|
+
env: {
|
|
180
|
+
type: "array",
|
|
181
|
+
items: { type: "string" },
|
|
182
|
+
description: "Environment variables in KEY=VALUE format"
|
|
183
|
+
},
|
|
184
|
+
restart: {
|
|
185
|
+
type: "string",
|
|
186
|
+
enum: ["no", "always", "unless-stopped", "on-failure"],
|
|
187
|
+
description: "Restart policy (default: unless-stopped)"
|
|
188
|
+
},
|
|
189
|
+
network: { type: "string", description: "Network to attach the container to" },
|
|
190
|
+
extraArgs: {
|
|
191
|
+
type: "string",
|
|
192
|
+
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."
|
|
193
|
+
},
|
|
194
|
+
postArgs: {
|
|
195
|
+
type: "string",
|
|
196
|
+
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."
|
|
197
|
+
},
|
|
198
|
+
staticIp: {
|
|
199
|
+
type: "string",
|
|
200
|
+
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."
|
|
201
|
+
},
|
|
202
|
+
privileged: { type: "boolean", description: "Run the container privileged, with full host access (default: false)" },
|
|
203
|
+
cpuset: { type: "string", description: "CPUs the container may run on, as docker --cpuset-cpus reads it (e.g. '0-3,8')" },
|
|
204
|
+
devices: {
|
|
205
|
+
type: "array",
|
|
206
|
+
items: { type: "string" },
|
|
207
|
+
description: "Host devices to pass through, as docker --device reads them (e.g. ['/dev/dri', '/dev/ttyUSB0:/dev/ttyUSB0:rwm'])"
|
|
208
|
+
},
|
|
209
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
210
|
+
},
|
|
211
|
+
required: ["image"]
|
|
212
|
+
},
|
|
213
|
+
execute: async (_id, params) => {
|
|
214
|
+
try {
|
|
215
|
+
const { server, ...body } = params;
|
|
216
|
+
return textResult(await getClient(server).post("/api/docker/containers", body));
|
|
217
|
+
} catch (err) {
|
|
218
|
+
return errorResult(err);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/tools/ca.ts
|
|
225
|
+
function registerCaTools(api, getClient) {
|
|
226
|
+
api.registerTool({
|
|
227
|
+
name: "unraid_ca_search",
|
|
228
|
+
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.",
|
|
229
|
+
parameters: {
|
|
230
|
+
type: "object",
|
|
231
|
+
properties: {
|
|
232
|
+
q: { type: "string", description: "Search text, e.g. 'plex' or 'backup tool'. All words must match." },
|
|
233
|
+
limit: { type: "number", description: "Maximum results to return (1-100, default: 25)" },
|
|
234
|
+
includeDeprecated: { type: "boolean", description: "Include templates the maintainer deprecated (default: false)" },
|
|
235
|
+
includePlugins: { type: "boolean", description: "Include Unraid plugin (.plg) entries, which are not containers (default: false)" },
|
|
236
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
237
|
+
},
|
|
238
|
+
required: ["q"]
|
|
239
|
+
},
|
|
240
|
+
execute: async (_id, params) => {
|
|
241
|
+
try {
|
|
242
|
+
const query = { q: String(params.q ?? "") };
|
|
243
|
+
if (params.limit) query.limit = String(params.limit);
|
|
244
|
+
if (params.includeDeprecated) query.includeDeprecated = "true";
|
|
245
|
+
if (params.includePlugins) query.includePlugins = "true";
|
|
246
|
+
return textResult(await getClient(params.server).get("/api/ca/search", query));
|
|
247
|
+
} catch (err) {
|
|
248
|
+
return errorResult(err);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
api.registerTool({
|
|
253
|
+
name: "unraid_ca_app",
|
|
254
|
+
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.",
|
|
255
|
+
parameters: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
name: { type: "string", description: "App name exactly as it appears in the catalog, e.g. 'Jellyfin'" },
|
|
259
|
+
repo: { type: "string", description: `Owning repository, e.g. "linuxserver's Repository". Required when several templates share the name.` },
|
|
260
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
261
|
+
},
|
|
262
|
+
required: ["name"]
|
|
263
|
+
},
|
|
264
|
+
execute: async (_id, params) => {
|
|
265
|
+
try {
|
|
266
|
+
const query = {};
|
|
267
|
+
if (params.repo) query.repo = String(params.repo);
|
|
268
|
+
return textResult(
|
|
269
|
+
await getClient(params.server).get(
|
|
270
|
+
`/api/ca/app/${encodeURIComponent(String(params.name))}`,
|
|
271
|
+
query
|
|
272
|
+
)
|
|
273
|
+
);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
return errorResult(err);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
api.registerTool({
|
|
280
|
+
name: "unraid_ca_install",
|
|
281
|
+
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.",
|
|
282
|
+
parameters: {
|
|
283
|
+
type: "object",
|
|
284
|
+
properties: {
|
|
285
|
+
name: { type: "string", description: "App name exactly as it appears in the catalog, e.g. 'Jellyfin'" },
|
|
286
|
+
repo: { type: "string", description: "Owning repository. Required when several templates share the name." },
|
|
287
|
+
containerName: {
|
|
288
|
+
type: "string",
|
|
289
|
+
description: "Name for the new container. Defaults to the app name. Letters, digits, dot, dash and underscore only."
|
|
290
|
+
},
|
|
291
|
+
overrides: {
|
|
292
|
+
type: "object",
|
|
293
|
+
additionalProperties: { type: "string" },
|
|
294
|
+
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."
|
|
295
|
+
},
|
|
296
|
+
dryRun: {
|
|
297
|
+
type: "boolean",
|
|
298
|
+
description: "Resolve and validate everything and return the plan without installing (default: false)"
|
|
299
|
+
},
|
|
300
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
301
|
+
},
|
|
302
|
+
required: ["name"],
|
|
303
|
+
additionalProperties: false
|
|
304
|
+
},
|
|
305
|
+
execute: async (_id, params) => {
|
|
306
|
+
try {
|
|
307
|
+
checkParams(params, ["name", "repo", "containerName", "overrides", "dryRun", "server"]);
|
|
308
|
+
const body = {};
|
|
309
|
+
if (params.repo) body.repo = params.repo;
|
|
310
|
+
if (params.containerName) body.name = params.containerName;
|
|
311
|
+
if (params.overrides) body.overrides = params.overrides;
|
|
312
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
313
|
+
return textResult(
|
|
314
|
+
await getClient(params.server).post(
|
|
315
|
+
`/api/ca/app/${encodeURIComponent(String(params.name))}/install`,
|
|
316
|
+
body
|
|
317
|
+
)
|
|
318
|
+
);
|
|
319
|
+
} catch (err) {
|
|
320
|
+
return errorResult(err);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}, { optional: true });
|
|
324
|
+
api.registerTool({
|
|
325
|
+
name: "unraid_ca_update",
|
|
326
|
+
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.",
|
|
327
|
+
parameters: {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: {
|
|
330
|
+
name: { type: "string", description: "Installed container name, e.g. 'jellyfin'. Not the catalog app name." },
|
|
331
|
+
dryRun: {
|
|
332
|
+
type: "boolean",
|
|
333
|
+
description: "Check everything and report what would happen without pulling or recreating anything (default: false)"
|
|
334
|
+
},
|
|
335
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
336
|
+
},
|
|
337
|
+
required: ["name"],
|
|
338
|
+
additionalProperties: false
|
|
339
|
+
},
|
|
340
|
+
execute: async (_id, params) => {
|
|
341
|
+
try {
|
|
342
|
+
checkParams(params, ["name", "dryRun", "server"]);
|
|
343
|
+
const body = {};
|
|
344
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
345
|
+
return textResult(
|
|
346
|
+
await getClient(params.server).post(
|
|
347
|
+
`/api/ca/app/${encodeURIComponent(String(params.name))}/update`,
|
|
348
|
+
body
|
|
349
|
+
)
|
|
350
|
+
);
|
|
351
|
+
} catch (err) {
|
|
352
|
+
return errorResult(err);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}, { optional: true });
|
|
356
|
+
api.registerTool({
|
|
357
|
+
name: "unraid_ca_remove",
|
|
358
|
+
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.",
|
|
359
|
+
parameters: {
|
|
360
|
+
type: "object",
|
|
361
|
+
properties: {
|
|
362
|
+
name: { type: "string", description: "Installed container name, e.g. 'jellyfin'. Not the catalog app name." },
|
|
363
|
+
dryRun: {
|
|
364
|
+
type: "boolean",
|
|
365
|
+
description: "Report what would be removed and what would be kept, without removing anything (default: false)"
|
|
366
|
+
},
|
|
367
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
368
|
+
},
|
|
369
|
+
required: ["name"],
|
|
370
|
+
additionalProperties: false
|
|
371
|
+
},
|
|
372
|
+
execute: async (_id, params) => {
|
|
373
|
+
try {
|
|
374
|
+
checkParams(params, ["name", "dryRun", "server"]);
|
|
375
|
+
const body = {};
|
|
376
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
377
|
+
return textResult(
|
|
378
|
+
await getClient(params.server).post(
|
|
379
|
+
`/api/ca/app/${encodeURIComponent(String(params.name))}/remove`,
|
|
380
|
+
body
|
|
381
|
+
)
|
|
382
|
+
);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
return errorResult(err);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}, { optional: true });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/tools/plugins.ts
|
|
391
|
+
function registerPluginTools(api, getClient) {
|
|
392
|
+
api.registerTool({
|
|
393
|
+
name: "unraid_plugins_list",
|
|
394
|
+
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.",
|
|
395
|
+
parameters: {
|
|
396
|
+
type: "object",
|
|
397
|
+
properties: {
|
|
398
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
execute: async (_id, params) => {
|
|
402
|
+
try {
|
|
403
|
+
return textResult(await getClient(params.server).get("/api/plugins"));
|
|
404
|
+
} catch (err) {
|
|
405
|
+
return errorResult(err);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
api.registerTool({
|
|
410
|
+
name: "unraid_plugin_info",
|
|
411
|
+
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.",
|
|
412
|
+
parameters: {
|
|
413
|
+
type: "object",
|
|
414
|
+
properties: {
|
|
415
|
+
plugin: {
|
|
416
|
+
type: "string",
|
|
417
|
+
description: "Plugin file name, e.g. 'unassigned.devices.plg'. The .plg suffix is optional."
|
|
418
|
+
},
|
|
419
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
420
|
+
},
|
|
421
|
+
required: ["plugin"]
|
|
422
|
+
},
|
|
423
|
+
execute: async (_id, params) => {
|
|
424
|
+
try {
|
|
425
|
+
return textResult(
|
|
426
|
+
await getClient(params.server).get(
|
|
427
|
+
`/api/plugins/${encodeURIComponent(String(params.plugin))}`
|
|
428
|
+
)
|
|
429
|
+
);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
return errorResult(err);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
api.registerTool(
|
|
436
|
+
{
|
|
437
|
+
name: "unraid_plugin_install",
|
|
438
|
+
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).",
|
|
439
|
+
parameters: {
|
|
440
|
+
type: "object",
|
|
441
|
+
properties: {
|
|
442
|
+
url: {
|
|
443
|
+
type: "string",
|
|
444
|
+
description: "Direct https URL of the .plg file, e.g. 'https://example.com/myplugin.plg'"
|
|
445
|
+
},
|
|
446
|
+
dryRun: {
|
|
447
|
+
type: "boolean",
|
|
448
|
+
description: "Validate the URL and return the plan without downloading, writing or installing anything (default: false)"
|
|
449
|
+
},
|
|
450
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
451
|
+
},
|
|
452
|
+
required: ["url"],
|
|
453
|
+
additionalProperties: false
|
|
454
|
+
},
|
|
455
|
+
execute: async (_id, params) => {
|
|
456
|
+
try {
|
|
457
|
+
checkParams(params, ["url", "dryRun", "server"]);
|
|
458
|
+
const body = { url: String(params.url ?? "") };
|
|
459
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
460
|
+
return textResult(
|
|
461
|
+
await getClient(params.server).post("/api/plugins/install", body)
|
|
462
|
+
);
|
|
463
|
+
} catch (err) {
|
|
464
|
+
return errorResult(err);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
{ optional: true }
|
|
469
|
+
);
|
|
470
|
+
api.registerTool(
|
|
471
|
+
{
|
|
472
|
+
name: "unraid_plugin_check_updates",
|
|
473
|
+
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.",
|
|
474
|
+
parameters: {
|
|
475
|
+
type: "object",
|
|
476
|
+
properties: {
|
|
477
|
+
plugin: { type: "string", description: "Plugin file name, e.g. 'unassigned.devices.plg'" },
|
|
478
|
+
dryRun: {
|
|
479
|
+
type: "boolean",
|
|
480
|
+
description: "Return the plan without downloading or staging anything (default: false)"
|
|
481
|
+
},
|
|
482
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
483
|
+
},
|
|
484
|
+
required: ["plugin"],
|
|
485
|
+
additionalProperties: false
|
|
486
|
+
},
|
|
487
|
+
execute: async (_id, params) => {
|
|
488
|
+
try {
|
|
489
|
+
checkParams(params, ["plugin", "dryRun", "server"]);
|
|
490
|
+
const body = {};
|
|
491
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
492
|
+
return textResult(
|
|
493
|
+
await getClient(params.server).post(
|
|
494
|
+
`/api/plugins/${encodeURIComponent(String(params.plugin))}/check`,
|
|
495
|
+
body
|
|
496
|
+
)
|
|
497
|
+
);
|
|
498
|
+
} catch (err) {
|
|
499
|
+
return errorResult(err);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
{ optional: true }
|
|
504
|
+
);
|
|
505
|
+
api.registerTool(
|
|
506
|
+
{
|
|
507
|
+
name: "unraid_plugin_update",
|
|
508
|
+
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.",
|
|
509
|
+
parameters: {
|
|
510
|
+
type: "object",
|
|
511
|
+
properties: {
|
|
512
|
+
plugin: { type: "string", description: "Plugin file name, e.g. 'unassigned.devices.plg'" },
|
|
513
|
+
dryRun: {
|
|
514
|
+
type: "boolean",
|
|
515
|
+
description: "Return the plan, including which version would replace which, without installing (default: false)"
|
|
516
|
+
},
|
|
517
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
518
|
+
},
|
|
519
|
+
required: ["plugin"],
|
|
520
|
+
additionalProperties: false
|
|
521
|
+
},
|
|
522
|
+
execute: async (_id, params) => {
|
|
523
|
+
try {
|
|
524
|
+
checkParams(params, ["plugin", "dryRun", "server"]);
|
|
525
|
+
const body = {};
|
|
526
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
527
|
+
return textResult(
|
|
528
|
+
await getClient(params.server).post(
|
|
529
|
+
`/api/plugins/${encodeURIComponent(String(params.plugin))}/update`,
|
|
530
|
+
body
|
|
531
|
+
)
|
|
532
|
+
);
|
|
533
|
+
} catch (err) {
|
|
534
|
+
return errorResult(err);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
{ optional: true }
|
|
539
|
+
);
|
|
540
|
+
api.registerTool(
|
|
541
|
+
{
|
|
542
|
+
name: "unraid_plugin_remove",
|
|
543
|
+
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.",
|
|
544
|
+
parameters: {
|
|
545
|
+
type: "object",
|
|
546
|
+
properties: {
|
|
547
|
+
plugin: { type: "string", description: "Plugin file name, e.g. 'unassigned.devices.plg'" },
|
|
548
|
+
dryRun: {
|
|
549
|
+
type: "boolean",
|
|
550
|
+
description: "Return the plan and its warnings without removing anything (default: false)"
|
|
551
|
+
},
|
|
552
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
553
|
+
},
|
|
554
|
+
required: ["plugin"],
|
|
555
|
+
additionalProperties: false
|
|
556
|
+
},
|
|
557
|
+
execute: async (_id, params) => {
|
|
558
|
+
try {
|
|
559
|
+
checkParams(params, ["plugin", "dryRun", "server"]);
|
|
560
|
+
const body = {};
|
|
561
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
562
|
+
return textResult(
|
|
563
|
+
await getClient(params.server).post(
|
|
564
|
+
`/api/plugins/${encodeURIComponent(String(params.plugin))}/remove`,
|
|
565
|
+
body
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
} catch (err) {
|
|
569
|
+
return errorResult(err);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
{ optional: true }
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/tools/vms.ts
|
|
578
|
+
function registerVMTools(api, getClient) {
|
|
579
|
+
api.registerTool({
|
|
580
|
+
name: "unraid_vm_list",
|
|
581
|
+
description: "List all virtual machines on the Unraid server with their current state.",
|
|
582
|
+
parameters: {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: {
|
|
585
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
execute: async (_id, params) => {
|
|
589
|
+
try {
|
|
590
|
+
return textResult(await getClient(params.server).get("/api/vms"));
|
|
591
|
+
} catch (err) {
|
|
592
|
+
return errorResult(err);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
for (const [name, desc, action] of [
|
|
597
|
+
["unraid_vm_inspect", "Get detailed information about a specific virtual machine.", null],
|
|
598
|
+
["unraid_vm_start", "Start a stopped virtual machine.", "start"],
|
|
599
|
+
["unraid_vm_stop", "Gracefully stop a running virtual machine (ACPI shutdown).", "stop"],
|
|
600
|
+
["unraid_vm_pause", "Pause a running virtual machine (suspend to RAM).", "pause"],
|
|
601
|
+
["unraid_vm_resume", "Resume a paused virtual machine.", "resume"],
|
|
602
|
+
["unraid_vm_force_stop", "Force stop a virtual machine (equivalent to pulling the power plug). This is destructive and may cause data loss.", "force-stop"],
|
|
603
|
+
["unraid_vm_reboot", "Reboot a running virtual machine (ACPI reboot).", "reboot"]
|
|
604
|
+
]) {
|
|
605
|
+
api.registerTool(
|
|
606
|
+
{
|
|
607
|
+
name,
|
|
608
|
+
description: desc,
|
|
609
|
+
parameters: {
|
|
610
|
+
type: "object",
|
|
611
|
+
properties: {
|
|
612
|
+
id: { type: "string", description: "VM ID or name" },
|
|
613
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
614
|
+
},
|
|
615
|
+
required: ["id"]
|
|
616
|
+
},
|
|
617
|
+
execute: async (_id, params) => {
|
|
618
|
+
try {
|
|
619
|
+
const client = getClient(params.server);
|
|
620
|
+
if (action) {
|
|
621
|
+
return textResult(await client.post(`/api/vms/${params.id}/${action}`));
|
|
622
|
+
}
|
|
623
|
+
return textResult(await client.get(`/api/vms/${params.id}`));
|
|
624
|
+
} catch (err) {
|
|
625
|
+
return errorResult(err);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
...name === "unraid_vm_force_stop" ? [{ optional: true }] : []
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/tools/array.ts
|
|
635
|
+
function registerArrayTools(api, getClient) {
|
|
636
|
+
api.registerTool({
|
|
637
|
+
name: "unraid_array_status",
|
|
638
|
+
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).",
|
|
639
|
+
parameters: {
|
|
640
|
+
type: "object",
|
|
641
|
+
properties: {
|
|
642
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
execute: async (_id, params) => {
|
|
646
|
+
try {
|
|
647
|
+
return textResult(await getClient(params.server).get("/api/array/status"));
|
|
648
|
+
} catch (err) {
|
|
649
|
+
return errorResult(err);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
api.registerTool({
|
|
654
|
+
name: "unraid_array_start",
|
|
655
|
+
description: "Start the Unraid array. This will mount all disks and start Docker/VMs if configured.",
|
|
656
|
+
parameters: {
|
|
657
|
+
type: "object",
|
|
658
|
+
properties: {
|
|
659
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
execute: async (_id, params) => {
|
|
663
|
+
try {
|
|
664
|
+
return textResult(await getClient(params.server).post("/api/array/start"));
|
|
665
|
+
} catch (err) {
|
|
666
|
+
return errorResult(err);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
api.registerTool({
|
|
671
|
+
name: "unraid_array_stop",
|
|
672
|
+
description: "Stop the Unraid array. This will stop all Docker containers and VMs, then unmount all disks.",
|
|
673
|
+
parameters: {
|
|
674
|
+
type: "object",
|
|
675
|
+
properties: {
|
|
676
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
execute: async (_id, params) => {
|
|
680
|
+
try {
|
|
681
|
+
return textResult(await getClient(params.server).post("/api/array/stop"));
|
|
682
|
+
} catch (err) {
|
|
683
|
+
return errorResult(err);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
api.registerTool({
|
|
688
|
+
name: "unraid_parity_status",
|
|
689
|
+
description: "Get the current parity check status (running, progress, speed, errors).",
|
|
690
|
+
parameters: {
|
|
691
|
+
type: "object",
|
|
692
|
+
properties: {
|
|
693
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
execute: async (_id, params) => {
|
|
697
|
+
try {
|
|
698
|
+
return textResult(await getClient(params.server).get("/api/array/parity/status"));
|
|
699
|
+
} catch (err) {
|
|
700
|
+
return errorResult(err);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
api.registerTool({
|
|
705
|
+
name: "unraid_parity_start",
|
|
706
|
+
description: "Start a parity check. Defaults to non-correcting (read-only) for safety. Set correct=true for a correcting check.",
|
|
707
|
+
parameters: {
|
|
708
|
+
type: "object",
|
|
709
|
+
properties: {
|
|
710
|
+
correct: {
|
|
711
|
+
type: "boolean",
|
|
712
|
+
description: "If true, run a correcting parity check. Defaults to false (non-correcting)."
|
|
713
|
+
},
|
|
714
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
execute: async (_id, params) => {
|
|
718
|
+
try {
|
|
719
|
+
return textResult(await getClient(params.server).post("/api/array/parity/start", { correct: params.correct ?? false }));
|
|
720
|
+
} catch (err) {
|
|
721
|
+
return errorResult(err);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
api.registerTool({
|
|
726
|
+
name: "unraid_parity_pause",
|
|
727
|
+
description: "Pause a running parity check.",
|
|
728
|
+
parameters: {
|
|
729
|
+
type: "object",
|
|
730
|
+
properties: {
|
|
731
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
execute: async (_id, params) => {
|
|
735
|
+
try {
|
|
736
|
+
return textResult(await getClient(params.server).post("/api/array/parity/pause"));
|
|
737
|
+
} catch (err) {
|
|
738
|
+
return errorResult(err);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
api.registerTool({
|
|
743
|
+
name: "unraid_parity_resume",
|
|
744
|
+
description: "Resume a paused parity check.",
|
|
745
|
+
parameters: {
|
|
746
|
+
type: "object",
|
|
747
|
+
properties: {
|
|
748
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
execute: async (_id, params) => {
|
|
752
|
+
try {
|
|
753
|
+
return textResult(await getClient(params.server).post("/api/array/parity/resume"));
|
|
754
|
+
} catch (err) {
|
|
755
|
+
return errorResult(err);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
api.registerTool({
|
|
760
|
+
name: "unraid_parity_cancel",
|
|
761
|
+
description: "Cancel a running or paused parity check.",
|
|
762
|
+
parameters: {
|
|
763
|
+
type: "object",
|
|
764
|
+
properties: {
|
|
765
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
execute: async (_id, params) => {
|
|
769
|
+
try {
|
|
770
|
+
return textResult(await getClient(params.server).post("/api/array/parity/cancel"));
|
|
771
|
+
} catch (err) {
|
|
772
|
+
return errorResult(err);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/tools/disks.ts
|
|
779
|
+
function registerDiskTools(api, getClient) {
|
|
780
|
+
api.registerTool({
|
|
781
|
+
name: "unraid_disk_list",
|
|
782
|
+
description: "List all disks (data + parity) with name, size, used, free, usedPercent, temp, status, and fsType.",
|
|
783
|
+
parameters: {
|
|
784
|
+
type: "object",
|
|
785
|
+
properties: {
|
|
786
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
execute: async (_id, params) => {
|
|
790
|
+
try {
|
|
791
|
+
return textResult(await getClient(params.server).get("/api/disks"));
|
|
792
|
+
} catch (err) {
|
|
793
|
+
return errorResult(err);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
api.registerTool({
|
|
798
|
+
name: "unraid_disk_details",
|
|
799
|
+
description: "Get details for a specific disk: size, used, free, usedPercent, temp, status, and fsType.",
|
|
800
|
+
parameters: {
|
|
801
|
+
type: "object",
|
|
802
|
+
properties: {
|
|
803
|
+
id: { type: "string", description: "Disk ID (e.g., 'disk1', 'parity')" },
|
|
804
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
805
|
+
},
|
|
806
|
+
required: ["id"]
|
|
807
|
+
},
|
|
808
|
+
execute: async (_id, params) => {
|
|
809
|
+
try {
|
|
810
|
+
return textResult(await getClient(params.server).get(`/api/disks/${params.id}`));
|
|
811
|
+
} catch (err) {
|
|
812
|
+
return errorResult(err);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/tools/shares.ts
|
|
819
|
+
function registerShareTools(api, getClient) {
|
|
820
|
+
api.registerTool({
|
|
821
|
+
name: "unraid_share_list",
|
|
822
|
+
description: "List all user shares on the Unraid server with their settings and usage. The 'free' and 'size' fields are in kilobytes (KiB).",
|
|
823
|
+
parameters: {
|
|
824
|
+
type: "object",
|
|
825
|
+
properties: {
|
|
826
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
execute: async (_id, params) => {
|
|
830
|
+
try {
|
|
831
|
+
return textResult(await getClient(params.server).get("/api/shares"));
|
|
832
|
+
} catch (err) {
|
|
833
|
+
return errorResult(err);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
api.registerTool({
|
|
838
|
+
name: "unraid_share_details",
|
|
839
|
+
description: "Get details for a specific user share by name. The 'free' and 'size' fields are in kilobytes (KiB).",
|
|
840
|
+
parameters: {
|
|
841
|
+
type: "object",
|
|
842
|
+
properties: {
|
|
843
|
+
name: { type: "string", description: "Share name" },
|
|
844
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
845
|
+
},
|
|
846
|
+
required: ["name"]
|
|
847
|
+
},
|
|
848
|
+
execute: async (_id, params) => {
|
|
849
|
+
try {
|
|
850
|
+
return textResult(await getClient(params.server).get(`/api/shares/${params.name}`));
|
|
851
|
+
} catch (err) {
|
|
852
|
+
return errorResult(err);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
api.registerTool({
|
|
857
|
+
name: "unraid_share_update",
|
|
858
|
+
description: "Update safe settings for a user share. Only affects metadata and future write behavior -- does not move existing data.",
|
|
859
|
+
parameters: {
|
|
860
|
+
type: "object",
|
|
861
|
+
properties: {
|
|
862
|
+
name: { type: "string", description: "Share name to update" },
|
|
863
|
+
comment: { type: "string", description: "Share description/comment" },
|
|
864
|
+
allocator: { type: "string", description: "Disk allocation method: highwater, fill, or most-free" },
|
|
865
|
+
floor: { type: "string", description: "Minimum free space per disk (e.g. '0' or '50000')" },
|
|
866
|
+
splitLevel: { type: "string", description: "Split level for distributing files across disks" },
|
|
867
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
868
|
+
},
|
|
869
|
+
required: ["name"]
|
|
870
|
+
},
|
|
871
|
+
execute: async (_id, params) => {
|
|
872
|
+
try {
|
|
873
|
+
const { name, server, ...updates } = params;
|
|
874
|
+
return textResult(await getClient(server).patch(`/api/shares/${name}`, updates));
|
|
875
|
+
} catch (err) {
|
|
876
|
+
return errorResult(err);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/tools/system.ts
|
|
883
|
+
function registerSystemTools(api, getClient) {
|
|
884
|
+
api.registerTool({
|
|
885
|
+
name: "unraid_system_info",
|
|
886
|
+
description: "Get system information: OS (platform, hostname, uptime), CPU (model, cores, threads), memory (total, used, free, percent), and CPU load averages (1m, 5m, 15m).",
|
|
887
|
+
parameters: {
|
|
888
|
+
type: "object",
|
|
889
|
+
properties: {
|
|
890
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
891
|
+
}
|
|
892
|
+
},
|
|
893
|
+
execute: async (_id, params) => {
|
|
894
|
+
try {
|
|
895
|
+
return textResult(await getClient(params.server).get("/api/system/info"));
|
|
896
|
+
} catch (err) {
|
|
897
|
+
return errorResult(err);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
api.registerTool({
|
|
902
|
+
name: "unraid_system_metrics",
|
|
903
|
+
description: "Get live system metrics: memory (total, used, free bytes, percent) and CPU load averages (1m, 5m, 15m). Lightweight endpoint for polling.",
|
|
904
|
+
parameters: {
|
|
905
|
+
type: "object",
|
|
906
|
+
properties: {
|
|
907
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
execute: async (_id, params) => {
|
|
911
|
+
try {
|
|
912
|
+
return textResult(await getClient(params.server).get("/api/system/metrics"));
|
|
913
|
+
} catch (err) {
|
|
914
|
+
return errorResult(err);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
api.registerTool({
|
|
919
|
+
name: "unraid_service_list",
|
|
920
|
+
description: "List system services and their current state.",
|
|
921
|
+
parameters: {
|
|
922
|
+
type: "object",
|
|
923
|
+
properties: {
|
|
924
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
execute: async (_id, params) => {
|
|
928
|
+
try {
|
|
929
|
+
return textResult(await getClient(params.server).get("/api/system/services"));
|
|
930
|
+
} catch (err) {
|
|
931
|
+
return errorResult(err);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
api.registerTool({
|
|
936
|
+
name: "unraid_system_reboot",
|
|
937
|
+
description: "Reboot the Unraid server. This is a destructive operation that will interrupt all running services, VMs, and containers.",
|
|
938
|
+
parameters: {
|
|
939
|
+
type: "object",
|
|
940
|
+
properties: {
|
|
941
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
942
|
+
}
|
|
943
|
+
},
|
|
944
|
+
execute: async (_id, params) => {
|
|
945
|
+
try {
|
|
946
|
+
return textResult(await getClient(params.server).post("/api/system/reboot"));
|
|
947
|
+
} catch (err) {
|
|
948
|
+
return errorResult(err);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
api.registerTool({
|
|
953
|
+
name: "unraid_system_shutdown",
|
|
954
|
+
description: "Shut down the Unraid server. This is a destructive operation that will power off the server.",
|
|
955
|
+
parameters: {
|
|
956
|
+
type: "object",
|
|
957
|
+
properties: {
|
|
958
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
execute: async (_id, params) => {
|
|
962
|
+
try {
|
|
963
|
+
return textResult(await getClient(params.server).post("/api/system/shutdown"));
|
|
964
|
+
} catch (err) {
|
|
965
|
+
return errorResult(err);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/tools/notifications.ts
|
|
972
|
+
function registerNotificationTools(api, getClient) {
|
|
973
|
+
api.registerTool({
|
|
974
|
+
name: "unraid_notification_list",
|
|
975
|
+
description: "List all system notifications with their importance level and archive status.",
|
|
976
|
+
parameters: {
|
|
977
|
+
type: "object",
|
|
978
|
+
properties: {
|
|
979
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
execute: async (_id, params) => {
|
|
983
|
+
try {
|
|
984
|
+
return textResult(await getClient(params.server).get("/api/notifications"));
|
|
985
|
+
} catch (err) {
|
|
986
|
+
return errorResult(err);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
api.registerTool({
|
|
991
|
+
name: "unraid_notification_create",
|
|
992
|
+
description: "Create a new system notification.",
|
|
993
|
+
parameters: {
|
|
994
|
+
type: "object",
|
|
995
|
+
properties: {
|
|
996
|
+
title: { type: "string", description: "Notification title" },
|
|
997
|
+
subject: { type: "string", description: "Notification subject" },
|
|
998
|
+
description: { type: "string", description: "Notification body text" },
|
|
999
|
+
importance: { type: "string", enum: ["normal", "warning", "alert"], description: "Importance level: alert, warning, or normal" },
|
|
1000
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
1001
|
+
},
|
|
1002
|
+
required: ["title", "subject", "description"]
|
|
1003
|
+
},
|
|
1004
|
+
execute: async (_id, params) => {
|
|
1005
|
+
try {
|
|
1006
|
+
const body = {
|
|
1007
|
+
title: params.title,
|
|
1008
|
+
subject: params.subject,
|
|
1009
|
+
description: params.description
|
|
1010
|
+
};
|
|
1011
|
+
if (params.importance) body.importance = params.importance;
|
|
1012
|
+
return textResult(await getClient(params.server).post("/api/notifications", body));
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
return errorResult(err);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
api.registerTool({
|
|
1019
|
+
name: "unraid_notification_archive",
|
|
1020
|
+
description: "Archive a notification (mark as read/handled).",
|
|
1021
|
+
parameters: {
|
|
1022
|
+
type: "object",
|
|
1023
|
+
properties: {
|
|
1024
|
+
id: { type: "string", description: "Notification ID" },
|
|
1025
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
1026
|
+
},
|
|
1027
|
+
required: ["id"]
|
|
1028
|
+
},
|
|
1029
|
+
execute: async (_id, params) => {
|
|
1030
|
+
try {
|
|
1031
|
+
return textResult(await getClient(params.server).post(`/api/notifications/${params.id}/archive`));
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
return errorResult(err);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
api.registerTool({
|
|
1038
|
+
name: "unraid_notification_delete",
|
|
1039
|
+
description: "Delete a notification permanently.",
|
|
1040
|
+
parameters: {
|
|
1041
|
+
type: "object",
|
|
1042
|
+
properties: {
|
|
1043
|
+
id: { type: "string", description: "Notification ID" },
|
|
1044
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
1045
|
+
},
|
|
1046
|
+
required: ["id"]
|
|
1047
|
+
},
|
|
1048
|
+
execute: async (_id, params) => {
|
|
1049
|
+
try {
|
|
1050
|
+
return textResult(await getClient(params.server).delete(`/api/notifications/${params.id}`));
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
return errorResult(err);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// src/tools/network.ts
|
|
1059
|
+
function registerNetworkTools(api, getClient) {
|
|
1060
|
+
api.registerTool({
|
|
1061
|
+
name: "unraid_network_info",
|
|
1062
|
+
description: "Get network information including hostname, gateway, DNS servers, and all network interfaces.",
|
|
1063
|
+
parameters: {
|
|
1064
|
+
type: "object",
|
|
1065
|
+
properties: {
|
|
1066
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
execute: async (_id, params) => {
|
|
1070
|
+
try {
|
|
1071
|
+
return textResult(await getClient(params.server).get("/api/network"));
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
return errorResult(err);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/tools/users.ts
|
|
1080
|
+
function registerUserTools(api, getClient) {
|
|
1081
|
+
api.registerTool({
|
|
1082
|
+
name: "unraid_user_me",
|
|
1083
|
+
description: "Get information about the current authenticated user.",
|
|
1084
|
+
parameters: {
|
|
1085
|
+
type: "object",
|
|
1086
|
+
properties: {
|
|
1087
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
execute: async (_id, params) => {
|
|
1091
|
+
try {
|
|
1092
|
+
return textResult(await getClient(params.server).get("/api/users/me"));
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
return errorResult(err);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// src/tools/logs.ts
|
|
1101
|
+
function registerLogTools(api, getClient) {
|
|
1102
|
+
api.registerTool({
|
|
1103
|
+
name: "unraid_syslog",
|
|
1104
|
+
description: "Get recent syslog entries from the Unraid server. Returns the most recent log lines.",
|
|
1105
|
+
parameters: {
|
|
1106
|
+
type: "object",
|
|
1107
|
+
properties: {
|
|
1108
|
+
lines: {
|
|
1109
|
+
type: "number",
|
|
1110
|
+
description: "Number of log lines to retrieve (1-1000, default 50)."
|
|
1111
|
+
},
|
|
1112
|
+
server: { type: "string", description: "Target server name (optional, uses default server)" }
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
execute: async (_id, params) => {
|
|
1116
|
+
try {
|
|
1117
|
+
const query = {};
|
|
1118
|
+
if (params.lines) query.lines = String(params.lines);
|
|
1119
|
+
return textResult(await getClient(params.server).get("/api/logs/syslog", query));
|
|
1120
|
+
} catch (err) {
|
|
1121
|
+
return errorResult(err);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// src/registry.ts
|
|
1128
|
+
function registerTools(api, getClient) {
|
|
1129
|
+
registerHealthTools(api, getClient);
|
|
1130
|
+
registerDockerTools(api, getClient);
|
|
1131
|
+
registerCaTools(api, getClient);
|
|
1132
|
+
registerPluginTools(api, getClient);
|
|
1133
|
+
registerVMTools(api, getClient);
|
|
1134
|
+
registerArrayTools(api, getClient);
|
|
1135
|
+
registerDiskTools(api, getClient);
|
|
1136
|
+
registerShareTools(api, getClient);
|
|
1137
|
+
registerSystemTools(api, getClient);
|
|
1138
|
+
registerNotificationTools(api, getClient);
|
|
1139
|
+
registerNetworkTools(api, getClient);
|
|
1140
|
+
registerUserTools(api, getClient);
|
|
1141
|
+
registerLogTools(api, getClient);
|
|
1142
|
+
}
|
|
1143
|
+
var READ_ONLY = /* @__PURE__ */ new Set([
|
|
1144
|
+
"unraid_health_check",
|
|
1145
|
+
"unraid_docker_list",
|
|
1146
|
+
"unraid_docker_inspect",
|
|
1147
|
+
"unraid_docker_logs",
|
|
1148
|
+
"unraid_ca_search",
|
|
1149
|
+
"unraid_ca_app",
|
|
1150
|
+
"unraid_plugins_list",
|
|
1151
|
+
"unraid_plugin_info",
|
|
1152
|
+
"unraid_vm_list",
|
|
1153
|
+
"unraid_vm_inspect",
|
|
1154
|
+
"unraid_array_status",
|
|
1155
|
+
"unraid_parity_status",
|
|
1156
|
+
"unraid_disk_list",
|
|
1157
|
+
"unraid_disk_details",
|
|
1158
|
+
"unraid_share_list",
|
|
1159
|
+
"unraid_share_details",
|
|
1160
|
+
"unraid_system_info",
|
|
1161
|
+
"unraid_system_metrics",
|
|
1162
|
+
"unraid_service_list",
|
|
1163
|
+
"unraid_notification_list",
|
|
1164
|
+
"unraid_network_info",
|
|
1165
|
+
"unraid_user_me",
|
|
1166
|
+
"unraid_syslog"
|
|
1167
|
+
]);
|
|
1168
|
+
export {
|
|
1169
|
+
READ_ONLY,
|
|
1170
|
+
isErrorResult,
|
|
1171
|
+
registerTools
|
|
1172
|
+
};
|